Merge branch 'main' into record-update-index-top

This commit is contained in:
J.Teeuwissen 2023-05-30 10:47:19 +02:00
commit 93ea086115
No known key found for this signature in database
GPG key ID: DB5F7A1ED8D478AD
66 changed files with 1154 additions and 472 deletions

View file

@ -44,6 +44,8 @@ pub const RocDec = extern struct {
return ret;
}
// TODO: If Str.toDec eventually supports more error types, return errors here.
// For now, just return null which will give the default error.
pub fn fromStr(roc_str: RocStr) ?RocDec {
if (roc_str.isEmpty()) {
return null;
@ -79,7 +81,8 @@ pub const RocDec = extern struct {
var after_str_len = (length - 1) - pi;
if (after_str_len > decimal_places) {
@panic("TODO runtime exception for too many decimal places!");
// TODO: runtime exception for too many decimal places!
return null;
}
var diff_decimal_places = decimal_places - after_str_len;
@ -96,7 +99,8 @@ pub const RocDec = extern struct {
var result: i128 = undefined;
var overflowed = @mulWithOverflow(i128, before, one_point_zero_i128, &result);
if (overflowed) {
@panic("TODO runtime exception for overflow!");
// TODO: runtime exception for overflow!
return null;
}
before_val_i128 = result;
}
@ -107,7 +111,8 @@ pub const RocDec = extern struct {
var result: i128 = undefined;
var overflowed = @addWithOverflow(i128, before, after, &result);
if (overflowed) {
@panic("TODO runtime exception for overflow!");
// TODO: runtime exception for overflow!
return null;
}
break :blk .{ .num = result };
} else {
@ -184,7 +189,7 @@ pub const RocDec = extern struct {
position += 1;
const trailing_zeros: u6 = count_trailing_zeros_base10(num);
if (trailing_zeros == decimal_places) {
if (trailing_zeros >= decimal_places) {
// add just a single zero if all decimal digits are zero
str_bytes[position] = '0';
position += 1;
@ -209,6 +214,10 @@ pub const RocDec = extern struct {
return RocStr.init(&str_bytes, position);
}
pub fn toI128(self: RocDec) i128 {
return self.num;
}
pub fn eq(self: RocDec, other: RocDec) bool {
return self.num == other.num;
}
@ -847,6 +856,14 @@ test "fromStr: .123.1" {
try expectEqual(dec, null);
}
test "toStr: 100.00" {
var dec: RocDec = .{ .num = 100000000000000000000 };
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "100.0"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: 123.45" {
var dec: RocDec = .{ .num = 123450000000000000000 };
var res_roc_str = dec.toStr();
@ -1108,6 +1125,10 @@ pub fn fromF64C(arg: f64) callconv(.C) i128 {
return if (@call(.{ .modifier = always_inline }, RocDec.fromF64, .{arg})) |dec| dec.num else @panic("TODO runtime exception failing convert f64 to RocDec");
}
pub fn toI128(arg: RocDec) callconv(.C) i128 {
return @call(.{ .modifier = always_inline }, RocDec.toI128, .{arg});
}
pub fn eqC(arg1: RocDec, arg2: RocDec) callconv(.C) bool {
return @call(.{ .modifier = always_inline }, RocDec.eq, .{ arg1, arg2 });
}

View file

@ -16,6 +16,7 @@ comptime {
exportDecFn(dec.fromStr, "from_str");
exportDecFn(dec.toStr, "to_str");
exportDecFn(dec.fromF64C, "from_f64");
exportDecFn(dec.toI128, "to_i128");
exportDecFn(dec.eqC, "eq");
exportDecFn(dec.neqC, "neq");
exportDecFn(dec.negateC, "negate");
@ -188,6 +189,7 @@ comptime {
exportUtilsFn(utils.isUnique, "is_unique");
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
exportUtilsFn(utils.allocateWithRefcountC, "allocate_with_refcount");
exportUtilsFn(utils.dictPseudoSeed, "dict_pseudo_seed");
@export(panic_utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });

View file

@ -436,3 +436,15 @@ test "increfC, static data" {
increfRcPtrC(ptr_to_refcount, 2);
try std.testing.expectEqual(mock_rc, REFCOUNT_MAX_ISIZE);
}
// This returns a compilation dependent pseudo random seed for dictionaries.
// The seed is the address of this function.
// This avoids all roc Dicts using a known seed and being trivial to DOS.
// Still not as secure as true random, but a lot better.
// This value must not change between calls unless Dict is changed to store the seed on creation.
// Note: On esstentially all OSes, this will be affected by ASLR and different each run.
// In wasm, the value will be constant to the build as a whole.
// Either way, it can not be know by an attacker unless they get access to the executable.
pub fn dictPseudoSeed() callconv(.C) u64 {
return @intCast(u64, @ptrToInt(dictPseudoSeed));
}

View file

@ -2,7 +2,7 @@ interface Box
exposes [box, unbox]
imports []
## Allocate a value on the heap. Boxing is an expensive process as it copies
## Allocates a value on the heap. Boxing is an expensive process as it copies
## the value from the stack to the heap. This may provide a performance
## optimization for advanced use cases with large values. A platform may require
## that some values are boxed.

View file

@ -80,7 +80,7 @@ interface Dict
## vacated spot.
##
## This move is done as a performance optimization, and it lets [remove] have
## [constant time complexity](https://en.wikipedia.org/wiki/Time_complexity#Constant_time). ##
## [constant time complexity](https://en.wikipedia.org/wiki/Time_complexity#Constant_time).
##
## Dict is inspired by [IndexMap](https://docs.rs/indexmap/latest/indexmap/map/struct.IndexMap.html).
## The internal implementation of a dictionary is similar to [absl::flat_hash_map](https://abseil.io/docs/cpp/guides/container).
@ -95,9 +95,33 @@ Dict k v := {
# TODO: define Eq and Hash that are unordered. Only if value has hash/eq?
metadata : List I8,
dataIndices : List Nat,
data : List (T k v),
data : List (k, v),
size : Nat,
} | k has Hash & Eq
has [
Eq {
isEq,
},
Hash {
hash: hashDict,
},
]
isEq : Dict k v, Dict k v -> Bool | k has Hash & Eq, v has Eq
isEq = \xs, ys ->
if len xs != len ys then
Bool.false
else
walkUntil xs Bool.true \_, k, xVal ->
when get ys k is
Ok yVal if yVal == xVal ->
Continue Bool.true
_ ->
Break Bool.false
hashDict : hasher, Dict k v -> hasher | k has Hash & Eq, v has Hash, hasher has Hasher
hashDict = \hasher, dict -> Hash.hashUnordered hasher (toList dict) List.walk
## Return an empty dictionary.
## ```
@ -127,7 +151,7 @@ capacity = \@Dict { dataIndices } ->
cap - Num.shiftRightZfBy cap 3
## Return a dictionary with space allocated for a number of entries. This
## may provide a performance optimisation if you know how many entries will be
## may provide a performance optimization if you know how many entries will be
## inserted.
withCapacity : Nat -> Dict k v | k has Hash & Eq
withCapacity = \_ ->
@ -151,12 +175,12 @@ single = \k, v ->
## |> Dict.insert 2 "Two"
## |> Dict.insert 3 "Three"
## |> Dict.insert 4 "Four"
## |> Bool.isEq (Dict.fromList [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"])
## |> Bool.isEq (Dict.fromList [(1, "One"), (2, "Two"), (3, "Three"), (4, "Four")])
## ```
fromList : List (T k v) -> Dict k v | k has Hash & Eq
fromList : List (k, v) -> Dict k v | k has Hash & Eq
fromList = \data ->
# TODO: make this efficient. Should just set data and then set all indicies in the hashmap.
List.walk data (empty {}) (\dict, T k v -> insert dict k v)
List.walk data (empty {}) (\dict, (k, v) -> insert dict k v)
## Returns the number of values in the dictionary.
## ```
@ -214,7 +238,7 @@ clear = \@Dict { metadata, dataIndices, data } ->
## ```
walk : Dict k v, state, (state, k, v -> state) -> state | k has Hash & Eq
walk = \@Dict { data }, initialState, transform ->
List.walk data initialState (\state, T k v -> transform state k v)
List.walk data initialState (\state, (k, v) -> transform state k v)
## Same as [Dict.walk], except you can stop walking early.
##
@ -246,7 +270,7 @@ walk = \@Dict { data }, initialState, transform ->
## ```
walkUntil : Dict k v, state, (state, k, v -> [Continue state, Break state]) -> state | k has Hash & Eq
walkUntil = \@Dict { data }, initialState, transform ->
List.walkUntil data initialState (\state, T k v -> transform state k v)
List.walkUntil data initialState (\state, (k, v) -> transform state k v)
## Get the value for a given key. If there is a value for the specified key it
## will return [Ok value], otherwise return [Err KeyNotFound].
@ -262,7 +286,7 @@ walkUntil = \@Dict { data }, initialState, transform ->
get : Dict k v, k -> Result v [KeyNotFound] | k has Hash & Eq
get = \@Dict { metadata, dataIndices, data }, key ->
hashKey =
createLowLevelHasher {}
createLowLevelHasher PseudoRandSeed
|> Hash.hash key
|> complete
h1Key = h1 hashKey
@ -272,7 +296,7 @@ get = \@Dict { metadata, dataIndices, data }, key ->
when findIndexHelper metadata dataIndices data h2Key key probe 0 is
Ok index ->
dataIndex = listGetUnsafe dataIndices index
(T _ v) = listGetUnsafe data dataIndex
(_, v) = listGetUnsafe data dataIndex
Ok v
@ -290,7 +314,7 @@ get = \@Dict { metadata, dataIndices, data }, key ->
contains : Dict k v, k -> Bool | k has Hash & Eq
contains = \@Dict { metadata, dataIndices, data }, key ->
hashKey =
createLowLevelHasher {}
createLowLevelHasher PseudoRandSeed
|> Hash.hash key
|> complete
h1Key = h1 hashKey
@ -315,7 +339,7 @@ contains = \@Dict { metadata, dataIndices, data }, key ->
insert : Dict k v, k, v -> Dict k v | k has Hash & Eq
insert = \@Dict { metadata, dataIndices, data, size }, key, value ->
hashKey =
createLowLevelHasher {}
createLowLevelHasher PseudoRandSeed
|> Hash.hash key
|> complete
h1Key = h1 hashKey
@ -329,7 +353,7 @@ insert = \@Dict { metadata, dataIndices, data, size }, key, value ->
@Dict {
metadata,
dataIndices,
data: List.set data dataIndex (T key value),
data: List.set data dataIndex (key, value),
size,
}
@ -362,7 +386,7 @@ remove : Dict k v, k -> Dict k v | k has Hash & Eq
remove = \@Dict { metadata, dataIndices, data, size }, key ->
# TODO: change this from swap remove to tombstone and test is performance is still good.
hashKey =
createLowLevelHasher {}
createLowLevelHasher PseudoRandSeed
|> Hash.hash key
|> complete
h1Key = h1 hashKey
@ -388,7 +412,7 @@ remove = \@Dict { metadata, dataIndices, data, size }, key ->
@Dict { metadata, dataIndices, data, size }
## Insert or remove a value for a specified key. This function enables a
## performance optimisation for the use case of providing a default when a value
## performance optimization for the use case of providing a default when a value
## is missing. This is more efficient than doing both a `Dict.get` and then a
## `Dict.insert` call, and supports being piped.
## ```
@ -423,9 +447,9 @@ update = \dict, key, alter ->
## |> Dict.insert 3 "Three"
## |> Dict.insert 4 "Four"
## |> Dict.toList
## |> Bool.isEq [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"]
## |> Bool.isEq [(1, "One"), (2, "Two"), (3, "Three"), (4, "Four")]
## ```
toList : Dict k v -> List (T k v) | k has Hash & Eq
toList : Dict k v -> List (k, v) | k has Hash & Eq
toList = \@Dict { data } ->
data
@ -442,7 +466,7 @@ toList = \@Dict { data } ->
## ```
keys : Dict k v -> List k | k has Hash & Eq
keys = \@Dict { data } ->
List.map data (\T k _ -> k)
List.map data (\(k, _) -> k)
## Returns the values of a dictionary as a [List].
## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead.
@ -457,7 +481,7 @@ keys = \@Dict { data } ->
## ```
values : Dict k v -> List v | k has Hash & Eq
values = \@Dict { data } ->
List.map data (\T _ v -> v)
List.map data (\(_, v) -> v)
## Combine two dictionaries by keeping the [union](https://en.wikipedia.org/wiki/Union_(set_theory))
## of all the key-value pairs. This means that all the key-value pairs in
@ -543,9 +567,9 @@ removeAll = \xs, ys ->
swapAndUpdateDataIndex : Dict k v, Nat, Nat -> Dict k v | k has Hash & Eq
swapAndUpdateDataIndex = \@Dict { metadata, dataIndices, data, size }, removedIndex, lastIndex ->
(T key _) = listGetUnsafe data lastIndex
(key, _) = listGetUnsafe data lastIndex
hashKey =
createLowLevelHasher {}
createLowLevelHasher PseudoRandSeed
|> Hash.hash key
|> complete
h1Key = h1 hashKey
@ -579,7 +603,7 @@ insertNotFoundHelper = \@Dict { metadata, dataIndices, data, size }, key, value,
probe = newProbe h1Key (div8 (List.len metadata))
index = nextEmptyOrDeletedHelper metadata probe 0
dataIndex = List.len data
nextData = List.append data (T key value)
nextData = List.append data (key, value)
@Dict {
metadata: List.set metadata index h2Key,
@ -605,7 +629,7 @@ nextEmptyOrDeletedHelper = \metadata, probe, offset ->
# TODO: investigate if this needs to be split into more specific helper functions.
# There is a chance that returning specific sub-info like the value would be faster.
findIndexHelper : List I8, List Nat, List (T k v), I8, k, Probe, Nat -> Result Nat [NotFound] | k has Hash & Eq
findIndexHelper : List I8, List Nat, List (k, v), I8, k, Probe, Nat -> Result Nat [NotFound] | k has Hash & Eq
findIndexHelper = \metadata, dataIndices, data, h2Key, key, probe, offset ->
# For finding a value, we must search past all deleted element tombstones.
index = Num.addWrap (mul8 probe.slotIndex) offset
@ -618,7 +642,7 @@ findIndexHelper = \metadata, dataIndices, data, h2Key, key, probe, offset ->
else if md == h2Key then
# Potentially matching slot, check if the key is a match.
dataIndex = listGetUnsafe dataIndices index
(T k _) = listGetUnsafe data dataIndex
(k, _) = listGetUnsafe data dataIndex
if k == key then
# We have a match, return its index.
@ -663,7 +687,7 @@ rehash = \@Dict { metadata, dataIndices, data, size } ->
rehashHelper newDict metadata dataIndices data 0
rehashHelper : Dict k v, List I8, List Nat, List (T k v), Nat -> Dict k v | k has Hash & Eq
rehashHelper : Dict k v, List I8, List Nat, List (k, v), Nat -> Dict k v | k has Hash & Eq
rehashHelper = \dict, oldMetadata, oldDataIndices, oldData, index ->
when List.get oldMetadata index is
Ok md ->
@ -671,7 +695,7 @@ rehashHelper = \dict, oldMetadata, oldDataIndices, oldData, index ->
if md >= 0 then
# We have an actual element here
dataIndex = listGetUnsafe oldDataIndices index
(T k _) = listGetUnsafe oldData dataIndex
(k, _) = listGetUnsafe oldData dataIndex
insertForRehash dict k dataIndex
else
@ -687,7 +711,7 @@ rehashHelper = \dict, oldMetadata, oldDataIndices, oldData, index ->
insertForRehash : Dict k v, k, Nat -> Dict k v | k has Hash & Eq
insertForRehash = \@Dict { metadata, dataIndices, data, size }, key, dataIndex ->
hashKey =
createLowLevelHasher {}
createLowLevelHasher PseudoRandSeed
|> Hash.hash key
|> complete
h1Key = h1 hashKey
@ -707,8 +731,6 @@ emptySlot = -128
deletedSlot : I8
deletedSlot = -2
T k v : [T k v]
# Capacity must be a power of 2.
# We still will use slots of 8 even though this version has no true slots.
# We just move an element at a time.
@ -747,6 +769,71 @@ expect
val == Ok "bar"
expect
dict1 =
empty {}
|> insert 1 "bar"
|> insert 2 "baz"
dict2 =
empty {}
|> insert 2 "baz"
|> insert 1 "bar"
dict1 == dict2
expect
dict1 =
empty {}
|> insert 1 "bar"
|> insert 2 "baz"
dict2 =
empty {}
|> insert 1 "bar"
|> insert 2 "baz!"
dict1 != dict2
expect
inner1 =
empty {}
|> insert 1 "bar"
|> insert 2 "baz"
inner2 =
empty {}
|> insert 2 "baz"
|> insert 1 "bar"
outer =
empty {}
|> insert inner1 "wrong"
|> insert inner2 "right"
get outer inner1 == Ok "right"
expect
inner1 =
empty {}
|> insert 1 "bar"
|> insert 2 "baz"
inner2 =
empty {}
|> insert 2 "baz"
|> insert 1 "bar"
outer1 =
empty {}
|> insert inner1 "val"
outer2 =
empty {}
|> insert inner2 "val"
outer1 == outer2
expect
val =
empty {}
@ -780,7 +867,7 @@ expect
expect
dict =
fromList [T 1u8 1u8, T 2 2, T 3 3]
fromList [(1u8, 1u8), (2, 2), (3, 3)]
|> remove 1
|> remove 3
@ -788,7 +875,7 @@ expect
expect
list =
fromList [T 1u8 1u8, T 2u8 2u8, T 3 3]
fromList [(1u8, 1u8), (2u8, 2u8), (3, 3)]
|> remove 1
|> insert 0 0
|> remove 3
@ -904,8 +991,16 @@ LowLevelHasher := { originalSeed : U64, state : U64 } has [
# TODO hide behind an InternalList.roc module
listGetUnsafe : List a, Nat -> a
createLowLevelHasher : { seed ? U64 } -> LowLevelHasher
createLowLevelHasher = \{ seed ? 0x526F_6352_616E_643F } ->
# Returns a application specific pseudo random seed for Dict.
# This avoids trivial DOS attacks.
pseudoSeed : {} -> U64
createLowLevelHasher : [PseudoRandSeed, WithSeed U64] -> LowLevelHasher
createLowLevelHasher = \seedOpt ->
seed =
when seedOpt is
PseudoRandSeed -> pseudoSeed {}
WithSeed s -> s
@LowLevelHasher { originalSeed: seed, state: seed }
combineState : LowLevelHasher, { a : U64, b : U64, seed : U64, length : U64 } -> LowLevelHasher
@ -1099,12 +1194,14 @@ wyr3 = \list, index, k ->
Num.bitwiseOr a p3
testSeed = WithSeed 0x526F_6352_616E_643F
# TODO: would be great to have table driven expects for this.
# Would also be great to have some sort of property based hasher
# where we can compare `addU*` functions to the `addBytes` function.
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> addBytes []
|> complete
@ -1112,7 +1209,7 @@ expect
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> addBytes [0x42]
|> complete
@ -1120,7 +1217,7 @@ expect
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> addU8 0x42
|> complete
@ -1128,7 +1225,7 @@ expect
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> addBytes [0xFF, 0xFF]
|> complete
@ -1136,7 +1233,7 @@ expect
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> addU16 0xFFFF
|> complete
@ -1144,7 +1241,7 @@ expect
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> addBytes [0x36, 0xA7]
|> complete
@ -1152,7 +1249,7 @@ expect
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> addU16 0xA736
|> complete
@ -1160,7 +1257,7 @@ expect
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> addBytes [0x00, 0x00, 0x00, 0x00]
|> complete
@ -1168,7 +1265,7 @@ expect
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> addU32 0x0000_0000
|> complete
@ -1176,7 +1273,7 @@ expect
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> addBytes [0xA9, 0x2F, 0xEE, 0x21]
|> complete
@ -1184,7 +1281,7 @@ expect
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> addU32 0x21EE_2FA9
|> complete
@ -1192,7 +1289,7 @@ expect
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> addBytes [0x5D, 0x66, 0xB1, 0x8F, 0x68, 0x44, 0xC7, 0x03, 0xE1, 0xDD, 0x23, 0x34, 0xBB, 0x9A, 0x42, 0xA7]
|> complete
@ -1200,7 +1297,7 @@ expect
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> addU128 0xA742_9ABB_3423_DDE1_03C7_4468_8FB1_665D
|> complete
@ -1208,7 +1305,7 @@ expect
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> Hash.hashStrBytes "abcdefghijklmnopqrstuvwxyz"
|> complete
@ -1216,7 +1313,7 @@ expect
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> Hash.hashStrBytes "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|> complete
@ -1224,7 +1321,7 @@ expect
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> Hash.hashStrBytes "1234567890123456789012345678901234567890123456789012345678901234567890"
|> complete
@ -1232,7 +1329,7 @@ expect
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> addBytes (List.repeat 0x77 100)
|> complete
@ -1242,7 +1339,7 @@ expect
# Apparently it won't pick the default integer.
expect
hash =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> Hash.hashUnordered [8u8, 82u8, 3u8, 8u8, 24u8] List.walk
|> complete
@ -1250,12 +1347,12 @@ expect
expect
hash1 =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> Hash.hashUnordered ([0u8, 1u8, 2u8, 3u8, 4u8]) List.walk
|> complete
hash2 =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> Hash.hashUnordered [4u8, 3u8, 2u8, 1u8, 0u8] List.walk
|> complete
@ -1263,12 +1360,12 @@ expect
expect
hash1 =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> Hash.hashUnordered [0u8, 1u8, 2u8, 3u8, 4u8] List.walk
|> complete
hash2 =
createLowLevelHasher {}
createLowLevelHasher testSeed
|> Hash.hashUnordered [4u8, 3u8, 2u8, 1u8, 0u8, 0u8] List.walk
|> complete

View file

@ -16,6 +16,7 @@ interface Hash
hashI64,
hashI128,
hashNat,
hashDec,
complete,
hashStrBytes,
hashList,
@ -24,7 +25,7 @@ interface Hash
Bool.{ Bool, isEq },
List,
Str,
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, Nat },
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, Nat, Dec },
]
## A value that can hashed.
@ -112,6 +113,13 @@ hashNat = \hasher, n ->
else
addU64 hasher (Num.toU64 n)
## LOWLEVEL get the i128 representation of a Dec.
i128OfDec : Dec -> I128
## Adds a single [Dec] to a hasher.
hashDec : a, Dec -> a | a has Hasher
hashDec = \hasher, n -> hashI128 hasher (i128OfDec n)
## Adds a container of [Hash]able elements to a [Hasher] by hashing each element.
## The container is iterated using the walk method passed in.
## The order of the elements does not affect the final hash.

View file

@ -1,8 +1,8 @@
## JSON is a data format that is easy for humans to read and write. It is
## commonly used to exhange data between two systems such as a server and a
## commonly used to exchange data between two systems such as a server and a
## client (e.g. web browser).
##
## This module implements functionality to serialise and de-serialise Roc types
## This module implements functionality to serialize and de-serialize Roc types
## to and from JSON data. Using the `Encode` and `Decode` builtins this process
## can be achieved without the need to write custom encoder and decoder functions
## to parse UTF-8 strings.

View file

@ -72,7 +72,8 @@ interface List
Num.{ Nat, Num, Int },
]
## Types
## ## Types
##
## A sequential list of values.
## ```
## [1, 2, 3] # a list of numbers
@ -512,7 +513,7 @@ all = \list, predicate ->
## length that's too low, it would have to re-allocate.
##
## (If you want to do an operation like this which reduces the memory footprint
## of the resulting list, you can do two passes over the lis with [List.walk] - one
## of the resulting list, you can do two passes over the list with [List.walk] - one
## to calculate the precise new size, and another to populate the new list.)
##
## If given a unique list, [List.keepIf] will mutate it in place to assemble the appropriate list.

View file

@ -233,7 +233,7 @@ Num range := range
##
## [I8] is a signed integer that takes up 8 bits. The `I` is for Integer, since
## integers in mathematics are signed by default. Because it has 8 bits just
## like [U8], it can store 256 numbers (still 2^16), but because it is signed,
## like [U8], it can store 256 numbers (still 2^8), but because it is signed,
## the range is different. Its 256 numbers range from -128 to 127.
##
## Here are some other examples:
@ -283,7 +283,7 @@ Num range := range
## general trade-offs are:
##
## * Larger integer sizes can represent a wider range of numbers. If you absolutely need to represent numbers in a certain range, make sure to pick an integer size that can hold them!
## * Smaller integer sizes take up less memory. This savings rarely matters in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can be a performance bottleneck.
## * Smaller integer sizes take up less memory. These savings rarely matter in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can be a performance bottleneck.
## * Certain CPUs work faster on some numeric sizes than others. If the CPU is taking too long to run numeric calculations, you may find a performance improvement by experimenting with numeric sizes that are larger than otherwise necessary. However, in practice, doing this typically degrades overall performance, so be careful to measure properly!
##
## Here are the different fixed size integer types:
@ -327,7 +327,7 @@ Num range := range
##
## A common use for [Nat] is to store the length ("len" for short) of a
## collection like a [List]. 64-bit systems can represent longer
## lists in memory than 32-bit systems can, which is why the length of a list
## lists in memory than 32-bit systems, which is why the length of a list
## is represented as a [Nat] in Roc.
##
## If any operation would result in an [Int] that is either too big
@ -442,7 +442,7 @@ U8 : Num (Integer Unsigned8)
## on 32-bit systems, and so on.
##
## This system-specific size makes it useful for certain data structure
## functions like [List.len], because the number of elements many data strucures
## functions like [List.len], because the number of elements many data structures
## can hold is also system-specific. For example, the maximum number of elements
## a [List] can hold on a 64-bit system fits in a 64-bit unsigned integer, and
## on a 32-bit system it fits in 32-bit unsigned integer. This makes [Nat] a
@ -473,7 +473,7 @@ F32 : Num (FloatingPoint Binary32)
##
## This means a [Dec] can represent whole numbers up to slightly over 170
## quintillion, along with 18 decimal places. (To be precise, it can store
## numbers betwween `-170_141_183_460_469_231_731.687303715884105728`
## numbers between `-170_141_183_460_469_231_731.687303715884105728`
## and `170_141_183_460_469_231_731.687303715884105727`.) Why 18
## decimal places? It's the highest number of decimal places where you can still
## convert any [U64] to a [Dec] without losing information.
@ -647,7 +647,7 @@ isInfinite : Frac * -> Bool
## ```
isFinite : Frac * -> Bool
## Return the absolute value of the number.
## Returns the absolute value of the number.
##
## * For a positive number, returns the same number.
## * For a negative number, returns the same number except positive.
@ -670,7 +670,7 @@ isFinite : Frac * -> Bool
## Calling this on an unsigned integer (like [U32] or [U64]) never does anything.
abs : Num a -> Num a
## Return the absolute difference between two numbers.
## Returns the absolute difference between two numbers.
##
## ```
## Num.absDiff 5 3
@ -691,7 +691,7 @@ absDiff = \a, b ->
else
b - a
## Return a negative number when given a positive one, and vice versa.
## Returns a negative number when given a positive one, and vice versa.
## ```
## Num.neg 5
##
@ -712,7 +712,7 @@ absDiff = \a, b ->
## (It will never crash when given a [Frac], however, because of how floating point numbers represent positive and negative numbers.)
neg : Num a -> Num a
## Add two numbers of the same type.
## Adds two numbers of the same type.
##
## (To add an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
##
@ -733,7 +733,7 @@ neg : Num a -> Num a
## ∞ or -∞. For all other number types, overflow results in a panic.
add : Num a, Num a -> Num a
## Subtract two numbers of the same type.
## Subtracts two numbers of the same type.
##
## (To subtract an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
##
@ -754,7 +754,7 @@ add : Num a, Num a -> Num a
## ∞ or -∞. For all other number types, overflow results in a panic.
sub : Num a, Num a -> Num a
## Multiply two numbers of the same type.
## Multiplies two numbers of the same type.
##
## (To multiply an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
##
@ -833,12 +833,12 @@ logChecked = \x ->
else
Ok (Num.log x)
## Divide one [Frac] by another.
## Divides one [Frac] by another.
##
## `a / b` is shorthand for `Num.div a b`.
##
## [Division by zero is undefined in mathematics](https://en.wikipedia.org/wiki/Division_by_zero).
## As such, you should make sure never to pass zero as the denomaintor to this function!
## As such, you should make sure never to pass zero as the denominator to this function!
## Calling [div] on a [Dec] denominator of zero will cause a panic.
##
## Calling [div] on [F32] and [F64] values follows these rules:
@ -882,12 +882,12 @@ divCeilChecked = \a, b ->
else
Ok (Num.divCeil a b)
## Divide two integers, truncating the result towards zero.
## Divides two integers, truncating the result towards zero.
##
## `a // b` is shorthand for `Num.divTrunc a b`.
##
## Division by zero is undefined in mathematics. As such, you should make
## sure never to pass zero as the denomaintor to this function! If you do,
## sure never to pass zero as the denominator to this function! If you do,
## it will crash.
## ```
## 5 // 7
@ -907,7 +907,7 @@ divTruncChecked = \a, b ->
else
Ok (Num.divTrunc a b)
## Obtain the remainder (truncating modulo) from the division of two integers.
## Obtains the remainder (truncating modulo) from the division of two integers.
##
## `a % b` is shorthand for `Num.rem a b`.
## ```
@ -1046,7 +1046,7 @@ countOneBits : Int a -> Nat
addWrap : Int range, Int range -> Int range
## Add two numbers, clamping on the maximum representable number rather than
## Adds two numbers, clamping on the maximum representable number rather than
## overflowing.
##
## This is the same as [Num.add] except for the saturating behavior if the
@ -1055,7 +1055,7 @@ addWrap : Int range, Int range -> Int range
## yield 255, the maximum value of a `U8`.
addSaturated : Num a, Num a -> Num a
## Add two numbers and check for overflow.
## Adds two numbers and checks for overflow.
##
## This is the same as [Num.add] except if the operation overflows, instead of
## panicking or returning ∞ or -∞, it will return `Err Overflow`.
@ -1072,7 +1072,7 @@ addCheckedLowlevel : Num a, Num a -> { b : Bool, a : Num a }
subWrap : Int range, Int range -> Int range
## Subtract two numbers, clamping on the minimum representable number rather
## Subtracts two numbers, clamping on the minimum representable number rather
## than overflowing.
##
## This is the same as [Num.sub] except for the saturating behavior if the
@ -1081,7 +1081,7 @@ subWrap : Int range, Int range -> Int range
## yield 0, the minimum value of a `U8`.
subSaturated : Num a, Num a -> Num a
## Subtract two numbers and check for overflow.
## Subtracts two numbers and checks for overflow.
##
## This is the same as [Num.sub] except if the operation overflows, instead of
## panicking or returning ∞ or -∞, it will return `Err Overflow`.
@ -1098,14 +1098,14 @@ subCheckedLowlevel : Num a, Num a -> { b : Bool, a : Num a }
mulWrap : Int range, Int range -> Int range
## Multiply two numbers, clamping on the maximum representable number rather than
## Multiplies two numbers, clamping on the maximum representable number rather than
## overflowing.
##
## This is the same as [Num.mul] except for the saturating behavior if the
## addition is to overflow.
mulSaturated : Num a, Num a -> Num a
## Multiply two numbers and check for overflow.
## Multiplies two numbers and checks for overflow.
##
## This is the same as [Num.mul] except if the operation overflows, instead of
## panicking or returning ∞ or -∞, it will return `Err Overflow`.
@ -1120,8 +1120,8 @@ mulChecked = \a, b ->
mulCheckedLowlevel : Num a, Num a -> { b : Bool, a : Num a }
## The lowest number that can be stored in an [I8] without underflowing its
## available memory and crashing.
## Returns the lowest number that can be stored in an [I8] without underflowing
## its available memory and crashing.
##
## For reference, this number is `-128`.
##
@ -1130,8 +1130,8 @@ mulCheckedLowlevel : Num a, Num a -> { b : Bool, a : Num a }
minI8 : I8
minI8 = -128i8
## The highest number that can be stored in an [I8] without overflowing its
## available memory and crashing.
## Returns the highest number that can be stored in an [I8] without overflowing
## its available memory and crashing.
##
## For reference, this number is `127`.
##
@ -1140,8 +1140,8 @@ minI8 = -128i8
maxI8 : I8
maxI8 = 127i8
## The lowest number that can be stored in a [U8] without underflowing its
## available memory and crashing.
## Returns the lowest number that can be stored in a [U8] without underflowing
## its available memory and crashing.
##
## For reference, this number is zero, because [U8] is
## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
@ -1150,15 +1150,15 @@ maxI8 = 127i8
minU8 : U8
minU8 = 0u8
## The highest number that can be stored in a [U8] without overflowing its
## available memory and crashing.
## Returns the highest number that can be stored in a [U8] without overflowing
## its available memory and crashing.
##
## For reference, this number is `255`.
maxU8 : U8
maxU8 = 255u8
## The lowest number that can be stored in an [I16] without underflowing its
## available memory and crashing.
## Returns the lowest number that can be stored in an [I16] without underflowing
## its available memory and crashing.
##
## For reference, this number is `-32_768`.
##
@ -1167,8 +1167,8 @@ maxU8 = 255u8
minI16 : I16
minI16 = -32768i16
## The highest number that can be stored in an [I16] without overflowing its
## available memory and crashing.
## Returns the highest number that can be stored in an [I16] without overflowing
## its available memory and crashing.
##
## For reference, this number is `32_767`.
##
@ -1177,8 +1177,8 @@ minI16 = -32768i16
maxI16 : I16
maxI16 = 32767i16
## The lowest number that can be stored in a [U16] without underflowing its
## available memory and crashing.
## Returns the lowest number that can be stored in a [U16] without underflowing
## its available memory and crashing.
##
## For reference, this number is zero, because [U16] is
## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
@ -1187,15 +1187,15 @@ maxI16 = 32767i16
minU16 : U16
minU16 = 0u16
## The highest number that can be stored in a [U16] without overflowing its
## available memory and crashing.
## Returns the highest number that can be stored in a [U16] without overflowing
## its available memory and crashing.
##
## For reference, this number is `65_535`.
maxU16 : U16
maxU16 = 65535u16
## The lowest number that can be stored in an [I32] without underflowing its
## available memory and crashing.
## Returns the lowest number that can be stored in an [I32] without underflowing
## its available memory and crashing.
##
## For reference, this number is `-2_147_483_648`.
##
@ -1204,8 +1204,8 @@ maxU16 = 65535u16
minI32 : I32
minI32 = -2147483648
## The highest number that can be stored in an [I32] without overflowing its
## available memory and crashing.
## Returns the highest number that can be stored in an [I32] without overflowing
## its available memory and crashing.
##
## For reference, this number is `2_147_483_647`,
## which is over 2 million.
@ -1215,8 +1215,8 @@ minI32 = -2147483648
maxI32 : I32
maxI32 = 2147483647
## The lowest number that can be stored in a [U32] without underflowing its
## available memory and crashing.
## Returns the lowest number that can be stored in a [U32] without underflowing
## its available memory and crashing.
##
## For reference, this number is zero, because [U32] is
## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
@ -1225,15 +1225,15 @@ maxI32 = 2147483647
minU32 : U32
minU32 = 0
## The highest number that can be stored in a [U32] without overflowing its
## available memory and crashing.
## Returns the highest number that can be stored in a [U32] without overflowing
## its available memory and crashing.
##
## For reference, this number is `4_294_967_295`.
maxU32 : U32
maxU32 = 4294967295
## The lowest number that can be stored in an [I64] without underflowing its
## available memory and crashing.
## Returns the lowest number that can be stored in an [I64] without underflowing
## its available memory and crashing.
##
## For reference, this number is `-9_223_372_036_854_775_808`,
## which is under 9 quintillion.
@ -1243,8 +1243,8 @@ maxU32 = 4294967295
minI64 : I64
minI64 = -9223372036854775808
## The highest number that can be stored in an [I64] without overflowing its
## available memory and crashing.
## Returns the highest number that can be stored in an [I64] without overflowing
## its available memory and crashing.
##
## For reference, this number is `9_223_372_036_854_775_807`,
## which is over 9 quintillion.
@ -1254,8 +1254,8 @@ minI64 = -9223372036854775808
maxI64 : I64
maxI64 = 9223372036854775807
## The lowest number that can be stored in a [U64] without underflowing its
## available memory and crashing.
## Returns the lowest number that can be stored in a [U64] without underflowing
## its available memory and crashing.
##
## For reference, this number is zero, because [U64] is
## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
@ -1264,16 +1264,16 @@ maxI64 = 9223372036854775807
minU64 : U64
minU64 = 0
## The highest number that can be stored in a [U64] without overflowing its
## available memory and crashing.
## Returns the highest number that can be stored in a [U64] without overflowing
## its available memory and crashing.
##
## For reference, this number is `18_446_744_073_709_551_615`,
## which is over 18 quintillion.
maxU64 : U64
maxU64 = 18446744073709551615
## The lowest number that can be stored in an [I128] without underflowing its
## available memory and crashing.
## Returns the lowest number that can be stored in an [I128] without underflowing
## its available memory and crashing.
##
## For reference, this number is `-170_141_183_460_469_231_731_687_303_715_884_105_728`.
## which is under 170 undecillion.
@ -1283,8 +1283,8 @@ maxU64 = 18446744073709551615
minI128 : I128
minI128 = -170141183460469231731687303715884105728
## The highest number that can be stored in an [I128] without overflowing its
## available memory and crashing.
## Returns the highest number that can be stored in an [I128] without overflowing
## its available memory and crashing.
##
## For reference, this number is `170_141_183_460_469_231_731_687_303_715_884_105_727`,
## which is over 170 undecillion.
@ -1294,8 +1294,8 @@ minI128 = -170141183460469231731687303715884105728
maxI128 : I128
maxI128 = 170141183460469231731687303715884105727
## The lowest number that can be stored in a [U128] without underflowing its
## available memory and crashing.
## Returns the lowest number that can be stored in a [U128] without underflowing
## its available memory and crashing.
##
## For reference, this number is zero, because [U128] is
## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
@ -1304,8 +1304,8 @@ maxI128 = 170141183460469231731687303715884105727
minU128 : U128
minU128 = 0
## The highest number that can be stored in a [U128] without overflowing its
## available memory and crashing.
## Returns the highest number that can be stored in a [U128] without overflowing
## its available memory and crashing.
##
## For reference, this number is `340_282_366_920_938_463_463_374_607_431_768_211_455`,
## which is over 340 undecillion.
@ -1337,7 +1337,7 @@ toU32 : Int * -> U32
toU64 : Int * -> U64
toU128 : Int * -> U128
## Convert an [Int] to a [Nat]. If the given number doesn't fit in [Nat], it will be truncated.
## Converts an [Int] to a [Nat]. If the given number doesn't fit in [Nat], it will be truncated.
## Since [Nat] has a different maximum number depending on the system you're building
## for, this may give a different answer on different systems.
##

View file

@ -6,7 +6,7 @@ interface Result
## okay, or else there was an error of some sort.
Result ok err : [Ok ok, Err err]
## Return `Bool.true` if the result indicates a success, else return `Bool.false`
## Returns `Bool.true` if the result indicates a success, else returns `Bool.false`
## ```
## Result.isOk (Ok 5)
## ```
@ -16,7 +16,7 @@ isOk = \result ->
Ok _ -> Bool.true
Err _ -> Bool.false
## Return `Bool.true` if the result indicates a failure, else return `Bool.false`
## Returns `Bool.true` if the result indicates a failure, else returns `Bool.false`
## ```
## Result.isErr (Err "uh oh")
## ```
@ -26,7 +26,7 @@ isErr = \result ->
Ok _ -> Bool.false
Err _ -> Bool.true
## If the result is `Ok`, return the value it holds. Otherwise, return
## If the result is `Ok`, returns the value it holds. Otherwise, returns
## the given default value.
## ```
## Result.withDefault (Ok 7) 42
@ -38,8 +38,8 @@ withDefault = \result, default ->
Ok value -> value
Err _ -> default
## If the result is `Ok`, transform the value it holds by running a conversion
## function on it. Then return a new `Ok` holding the transformed value. If the
## If the result is `Ok`, transforms the value it holds by running a conversion
## function on it. Then returns a new `Ok` holding the transformed value. If the
## result is `Err`, this has no effect. Use [mapErr] to transform an `Err`.
## ```
## Result.map (Ok 12) Num.negate
@ -54,8 +54,8 @@ map = \result, transform ->
Ok v -> Ok (transform v)
Err e -> Err e
## If the result is `Err`, transform the value it holds by running a conversion
## function on it. Then return a new `Err` holding the transformed value. If
## If the result is `Err`, transforms the value it holds by running a conversion
## function on it. Then returns a new `Err` holding the transformed value. If
## the result is `Ok`, this has no effect. Use [map] to transform an `Ok`.
## ```
## Result.mapErr (Err "yipes!") Str.isEmpty
@ -67,8 +67,8 @@ mapErr = \result, transform ->
Ok v -> Ok v
Err e -> Err (transform e)
## If the result is `Ok`, transform the entire result by running a conversion
## function on the value the `Ok` holds. Then return that new result. If the
## If the result is `Ok`, transforms the entire result by running a conversion
## function on the value the `Ok` holds. Then returns that new result. If the
## result is `Err`, this has no effect. Use `onErr` to transform an `Err`.
## ```
## Result.try (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num
@ -80,8 +80,8 @@ try = \result, transform ->
Ok v -> transform v
Err e -> Err e
## If the result is `Err`, transform the entire result by running a conversion
## function on the value the `Err` holds. Then return that new result. If the
## If the result is `Err`, transforms the entire result by running a conversion
## function on the value the `Err` holds. Then returns that new result. If the
## result is `Ok`, this has no effect. Use `try` to transform an `Ok`.
## ```
## Result.onErr (Ok 10) \errorNum -> Str.toNat errorNum

View file

@ -20,19 +20,19 @@ interface Set
Bool.{ Bool, Eq },
Dict.{ Dict },
Num.{ Nat },
Hash.{ Hash },
Hash.{ Hash, Hasher },
]
# We should have this line above the next has.
# It causes the formatter to fail currently.
# | k has Hash & Eq
## Provides a [set](https://en.wikipedia.org/wiki/Set_(abstract_data_type))
## type which stores a collection of unique values, without any ordering
Set k := Dict.Dict k {}
Set k := Dict.Dict k {} | k has Hash & Eq
has [
Eq {
isEq,
},
Hash {
hash: hashSet,
},
]
isEq : Set k, Set k -> Bool | k has Hash & Eq
@ -46,6 +46,9 @@ isEq = \xs, ys ->
else
Break Bool.false
hashSet : hasher, Set k -> hasher | k has Hash & Eq, hasher has Hasher
hashSet = \hasher, @Set inner -> Hash.hash hasher inner
## Creates a new empty `Set`.
## ```
## emptySet = Set.empty {}
@ -353,3 +356,26 @@ expect
|> insert 9
x == fromList (toList x)
expect
orderOne : Set Nat
orderOne =
single 1
|> insert 2
orderTwo : Set Nat
orderTwo =
single 2
|> insert 1
wrapperOne : Set (Set Nat)
wrapperOne =
single orderOne
|> insert orderTwo
wrapperTwo : Set (Set Nat)
wrapperTwo =
single orderTwo
|> insert orderOne
wrapperOne == wrapperTwo

View file

@ -216,7 +216,7 @@ withCapacity : Nat -> Str
## In this example:
## 1. We start with `greeting`, which has both a length and capacity of 24 (bytes).
## 2. `|> Str.concat ", "` will see that there isn't enough capacity to add 2 more bytes for the `", "`, so it will create a new heap allocation with enough bytes to hold both. (This probably will be more than 7 bytes, because when [Str] functions reallocate, they apply a multiplier to the exact capacity required. This makes it less likely that future realloctions will be needed. The multiplier amount is not specified, because it may change in future releases of Roc, but it will likely be around 1.5 to 2 times the exact capacity required.) Then it will copy the current bytes (`"Hello"`) into the new allocation, and finally concatenate the `", "` into the new allocation. The old allocation will then be deallocated because it's no longer referenced anywhere in the program.
## 3. `|> Str.concat subject` will again check if there is enough capacity in the string. If it doesn't find enough capacity once again, it will make a third allocation, copy the existing bytes (`"Hello, "`) into that third allocation, and then deallocate the second allocation because it's already no longer being referenced anywhere else in the program. (It may find enough capacity in this prticular case, because the previous [Str.concat] allocated something like 1.5 to 2 times the necessary capacity in order to anticipate future concatenations like this...but if something longer than `"World"` were being concatenated here, it might still require further reallocation and copying.)
## 3. `|> Str.concat subject` will again check if there is enough capacity in the string. If it doesn't find enough capacity once again, it will make a third allocation, copy the existing bytes (`"Hello, "`) into that third allocation, and then deallocate the second allocation because it's already no longer being referenced anywhere else in the program. (It may find enough capacity in this particular case, because the previous [Str.concat] allocated something like 1.5 to 2 times the necessary capacity in order to anticipate future concatenations like this...but if something longer than `"World"` were being concatenated here, it might still require further reallocation and copying.)
## 4. `|> Str.concat "!\n"` will repeat this process once more.
##
## This process can have significant performance costs due to multiple reallocation of new strings, copying between old strings and new strings, and deallocation of immediately obsolete strings.
@ -704,7 +704,7 @@ expect Str.replaceLast "abXdeXghi" "X" "_" == Ok "abXde_ghi"
## Returns the given [Str] before the first occurrence of a [delimiter](https://www.computerhope.com/jargon/d/delimite.htm), as well
## as the rest of the string after that occurrence.
## Returns [ Err NotFound] if the delimiter is not found.
## Returns [Err NotFound] if the delimiter is not found.
## ```
## expect Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
## expect Str.splitFirst "no slashes here" "/" == Err NotFound
@ -894,7 +894,7 @@ walkUtf8Help = \str, state, step, index, length ->
expect (walkUtf8 "ABC" [] List.append) == [65, 66, 67]
expect (walkUtf8 "鹏" [] List.append) == [233, 185, 143]
## Shrink the memory footprint of a str such that it's capacity and length are equal.
## Shrink the memory footprint of a str such that its capacity and length are equal.
## Note: This will also convert seamless slices to regular lists.
releaseExcessCapacity : Str -> Str

View file

@ -368,6 +368,7 @@ pub const LIST_RELEASE_EXCESS_CAPACITY: &str = "roc_builtins.list.release_excess
pub const DEC_FROM_STR: &str = "roc_builtins.dec.from_str";
pub const DEC_TO_STR: &str = "roc_builtins.dec.to_str";
pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64";
pub const DEC_TO_I128: &str = "roc_builtins.dec.to_i128";
pub const DEC_EQ: &str = "roc_builtins.dec.eq";
pub const DEC_NEQ: &str = "roc_builtins.dec.neq";
pub const DEC_NEGATE: &str = "roc_builtins.dec.negate";
@ -390,6 +391,7 @@ pub const UTILS_INCREF_DATA_PTR: &str = "roc_builtins.utils.incref_data_ptr";
pub const UTILS_DECREF_DATA_PTR: &str = "roc_builtins.utils.decref_data_ptr";
pub const UTILS_IS_UNIQUE: &str = "roc_builtins.utils.is_unique";
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
pub const UTILS_DICT_PSEUDO_SEED: &str = "roc_builtins.utils.dict_pseudo_seed";
pub const UTILS_EXPECT_FAILED_START_SHARED_BUFFER: &str =
"roc_builtins.utils.expect_failed_start_shared_buffer";

View file

@ -448,8 +448,9 @@ pub fn find_type_def_symbols(
As(actual, _, _) => {
stack.push(&actual.value);
}
Tuple { elems: _, ext: _ } => {
todo!("find_type_def_symbols: Tuple");
Tuple { elems, ext } => {
stack.extend(elems.iter().map(|t| &t.value));
stack.extend(ext.iter().map(|t| &t.value));
}
Record { fields, ext } => {
let mut inner_stack = Vec::with_capacity(fields.items.len());

View file

@ -208,6 +208,7 @@ map_symbol_to_lowlevel_and_arity! {
NumCountLeadingZeroBits; NUM_COUNT_LEADING_ZERO_BITS; 1,
NumCountTrailingZeroBits; NUM_COUNT_TRAILING_ZERO_BITS; 1,
NumCountOneBits; NUM_COUNT_ONE_BITS; 1,
I128OfDec; I128_OF_DEC; 1,
Eq; BOOL_STRUCTURAL_EQ; 2,
NotEq; BOOL_STRUCTURAL_NOT_EQ; 2,
@ -217,6 +218,7 @@ map_symbol_to_lowlevel_and_arity! {
BoxExpr; BOX_BOX_FUNCTION; 1,
UnboxExpr; BOX_UNBOX; 1,
Unreachable; LIST_UNREACHABLE; 1,
DictPseudoSeed; DICT_PSEUDO_SEED; 1,
}
/// Some builtins cannot be constructed in code gen alone, and need to be defined

View file

@ -512,13 +512,13 @@ pub fn constrain_pattern(
let expected =
constraints.push_pat_expected_type(PExpected::NoExpectation(pat_type_index));
let (guard_var, loc_guard) = typ;
let (guard_var, loc_pattern) = typ;
let elem_type = {
let guard_type = constraints.push_variable(*guard_var);
let expected_pat = constraints.push_pat_expected_type(PExpected::ForReason(
PReason::PatternGuard,
pat_type_index,
loc_guard.region,
loc_pattern.region,
));
state.constraints.push(constraints.pattern_presence(
@ -533,8 +533,8 @@ pub fn constrain_pattern(
types,
constraints,
env,
&loc_guard.value,
loc_guard.region,
&loc_pattern.value,
loc_pattern.region,
expected,
state,
);
@ -676,6 +676,17 @@ pub fn constrain_pattern(
RecordField::Optional(pat_type)
}
DestructType::Required => {
// Named destructures like
// {foo} -> ...
// are equivalent to wildcards on the type of `foo`, so if `foo` is a tag
// union, we must add a constraint to ensure that this destructure opens it
// up.
if could_be_a_tag_union(types, pat_type_index) {
state
.delayed_is_open_constraints
.push(constraints.is_open_type(pat_type_index));
}
// No extra constraints necessary.
RecordField::Demanded(pat_type)
}

View file

@ -194,6 +194,9 @@ const fn builtin_symbol_to_hash_lambda(symbol: Symbol) -> Option<FlatHash> {
Symbol::NUM_NAT | Symbol::NUM_NATURAL => {
Some(SingleLambdaSetImmediate(Symbol::HASH_HASH_NAT))
}
Symbol::NUM_DEC | Symbol::NUM_DECIMAL => {
Some(SingleLambdaSetImmediate(Symbol::HASH_HASH_DEC))
}
_ => None,
}
}

View file

@ -1548,6 +1548,13 @@ trait Backend<'a> {
arg_layouts,
ret_layout,
),
LowLevel::DictPseudoSeed => self.build_fn_call(
sym,
bitcode::UTILS_DICT_PSEUDO_SEED.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::NumToStr => {
let arg_layout = arg_layouts[0];
let intrinsic = match self.interner().get(arg_layout).repr {

View file

@ -1123,7 +1123,10 @@ fn build_tag_eq_help<'a, 'ctx>(
env.builder.build_return(Some(&answer));
}
NullableWrapped { other_tags, .. } => {
NullableWrapped {
other_tags,
nullable_id,
} => {
let ptr_equal = env.builder.build_int_compare(
IntPredicate::EQ,
env.builder
@ -1204,7 +1207,9 @@ fn build_tag_eq_help<'a, 'ctx>(
let tags = other_tags;
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
for (tag_id, field_layouts) in tags.iter().enumerate() {
for (i, field_layouts) in tags.iter().enumerate() {
let tag_id = if i >= (*nullable_id as _) { i + 1 } else { i };
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);

View file

@ -1187,6 +1187,10 @@ pub(crate) fn run_low_level<'a, 'ctx>(
// which could be useful to look at when implementing this.
todo!("implement checked float conversion");
}
I128OfDec => {
arguments!(dec);
dec_to_i128(env, dec)
}
Eq => {
arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout));
@ -1310,6 +1314,11 @@ pub(crate) fn run_low_level<'a, 'ctx>(
ptr.into()
}
},
DictPseudoSeed => {
// Dict.pseudoSeed : {} -> u64
call_bitcode_fn(env, &[], bitcode::UTILS_DICT_PSEUDO_SEED)
}
}
}
@ -1802,6 +1811,25 @@ fn dec_to_str<'ctx>(env: &Env<'_, 'ctx, '_>, dec: BasicValueEnum<'ctx>) -> Basic
}
}
fn dec_to_i128<'ctx>(env: &Env<'_, 'ctx, '_>, dec: BasicValueEnum<'ctx>) -> BasicValueEnum<'ctx> {
use roc_target::OperatingSystem::*;
let dec = dec.into_int_value();
match env.target_info.operating_system {
Windows => {
//
call_bitcode_fn(env, &[dec_alloca(env, dec).into()], bitcode::DEC_TO_I128)
}
Unix => {
let (low, high) = dec_split_into_words(env, dec);
call_bitcode_fn(env, &[low.into(), high.into()], bitcode::DEC_TO_I128)
}
Wasi => unimplemented!(),
}
}
fn dec_binop_with_overflow<'ctx>(
env: &Env<'_, 'ctx, '_>,
fn_name: &str,

View file

@ -1935,6 +1935,7 @@ impl<'a> LowLevelCall<'a> {
NumToFloatChecked => {
todo!("implement toF32Checked and toF64Checked");
}
I128OfDec => self.load_args_and_call_zig(backend, bitcode::DEC_TO_I128),
And => {
self.load_args(backend);
backend.code_builder.i32_and();
@ -1982,6 +1983,7 @@ impl<'a> LowLevelCall<'a> {
},
StoredValue::StackMemory { .. } => { /* do nothing */ }
},
DictPseudoSeed => self.load_args_and_call_zig(backend, bitcode::UTILS_DICT_PSEUDO_SEED),
}
}

View file

@ -3115,7 +3115,6 @@ fn update<'a>(
&mut layout_interner,
module_id,
ident_ids,
state.target_info,
&mut state.procedures,
);

View file

@ -110,6 +110,7 @@ pub enum LowLevel {
NumCountLeadingZeroBits,
NumCountTrailingZeroBits,
NumCountOneBits,
I128OfDec,
Eq,
NotEq,
And,
@ -126,6 +127,7 @@ pub enum LowLevel {
BoxExpr,
UnboxExpr,
Unreachable,
DictPseudoSeed,
}
macro_rules! higher_order {
@ -339,10 +341,12 @@ map_symbol_to_lowlevel! {
NumCountLeadingZeroBits <= NUM_COUNT_LEADING_ZERO_BITS,
NumCountTrailingZeroBits <= NUM_COUNT_TRAILING_ZERO_BITS,
NumCountOneBits <= NUM_COUNT_ONE_BITS,
I128OfDec <= I128_OF_DEC,
Eq <= BOOL_STRUCTURAL_EQ,
NotEq <= BOOL_STRUCTURAL_NOT_EQ,
And <= BOOL_AND,
Or <= BOOL_OR,
Not <= BOOL_NOT,
Unreachable <= LIST_UNREACHABLE,
DictPseudoSeed <= DICT_PSEUDO_SEED,
}

View file

@ -1463,6 +1463,7 @@ define_builtins! {
21 DICT_UPDATE: "update"
22 DICT_LIST_GET_UNSAFE: "listGetUnsafe"
23 DICT_PSEUDO_SEED: "pseudoSeed"
}
9 SET: "Set" => {
0 SET_SET: "Set" exposed_type=true // the Set.Set type alias
@ -1565,10 +1566,12 @@ define_builtins! {
13 HASH_HASH_I64: "hashI64"
14 HASH_HASH_I128: "hashI128"
15 HASH_HASH_NAT: "hashNat"
16 HASH_COMPLETE: "complete"
17 HASH_HASH_STR_BYTES: "hashStrBytes"
18 HASH_HASH_LIST: "hashList"
19 HASH_HASH_UNORDERED: "hashUnordered"
16 I128_OF_DEC: "i128OfDec"
17 HASH_HASH_DEC: "hashDec"
18 HASH_COMPLETE: "complete"
19 HASH_HASH_STR_BYTES: "hashStrBytes"
20 HASH_HASH_LIST: "hashList"
21 HASH_HASH_UNORDERED: "hashUnordered"
}
14 JSON: "Json" => {
0 JSON_JSON: "Json"

View file

@ -947,6 +947,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[Ownership] {
// - other refcounted arguments are Borrowed
match op {
Unreachable => arena.alloc_slice_copy(&[irrelevant]),
DictPseudoSeed => arena.alloc_slice_copy(&[irrelevant]),
ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrGraphemes
| StrCountUtf8Bytes | StrGetCapacity | ListGetCapacity => {
arena.alloc_slice_copy(&[borrowed])
@ -1014,7 +1015,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[Ownership] {
| NumToFloatChecked
| NumCountLeadingZeroBits
| NumCountTrailingZeroBits
| NumCountOneBits => arena.alloc_slice_copy(&[irrelevant]),
| NumCountOneBits
| I128OfDec => arena.alloc_slice_copy(&[irrelevant]),
NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
NumBytesToU64 => arena.alloc_slice_copy(&[borrowed, irrelevant]),

View file

@ -15,11 +15,10 @@ use bumpalo::collections::CollectIn;
use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_target::TargetInfo;
use crate::ir::{
BranchInfo, Call, CallType, Expr, JoinPointId, Literal, ModifyRc, Proc, ProcLayout, Stmt,
UpdateModeId,
BranchInfo, Call, CallType, Expr, JoinPointId, ListLiteralElement, Literal, ModifyRc, Proc,
ProcLayout, Stmt, UpdateModeId,
};
use crate::layout::{
Builtin, InLayout, Layout, LayoutInterner, LayoutRepr, STLayoutInterner, UnionLayout,
@ -27,7 +26,7 @@ use crate::layout::{
use bumpalo::Bump;
use roc_collections::{MutMap, MutSet};
use roc_collections::MutMap;
/**
Try to find increments of symbols followed by decrements of the symbol they were indexed out of (their parent).
@ -38,12 +37,10 @@ pub fn specialize_drops<'a, 'i>(
layout_interner: &'i mut STLayoutInterner<'a>,
home: ModuleId,
ident_ids: &'i mut IdentIds,
target_info: TargetInfo,
procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) {
for ((_symbol, proc_layout), proc) in procs.iter_mut() {
let mut environment =
DropSpecializationEnvironment::new(arena, home, proc_layout.result, target_info);
let mut environment = DropSpecializationEnvironment::new(arena, home, proc_layout.result);
specialize_drops_proc(arena, layout_interner, ident_ids, &mut environment, proc);
}
}
@ -104,13 +101,31 @@ fn specialize_drops_stmt<'a, 'i>(
_ => unreachable!("List get should have two arguments"),
};
environment.add_list_child(*structure, *binding, index);
environment.add_list_child_symbol(*structure, *binding, index);
alloc_let_with_continuation!(environment)
}
_ => {
// TODO perhaps allow for some e.g. lowlevel functions to be called if they cannot modify the RC of the symbol.
// Check whether the increments can be passed to the continuation.
CallType::LowLevel { op, .. } => match low_level_no_rc(&op) {
// It should be safe to pass the increments to the continuation.
RC::NoRc => alloc_let_with_continuation!(environment),
// We probably should not pass the increments to the continuation.
RC::Rc | RC::Uknown => {
let incremented_symbols = environment.incremented_symbols.drain();
let new_stmt = alloc_let_with_continuation!(environment);
// The new_environment might have inserted increments that were set to 0 before. We need to add th
for (symbol, increment) in incremented_symbols.map.into_iter() {
environment
.incremented_symbols
.insert_count(symbol, increment);
}
new_stmt
}
},
_ => {
// Calls can modify the RC of the symbol.
// If we move a increment of children after the function,
// the function might deallocate the child before we can use it after the function.
@ -118,16 +133,62 @@ fn specialize_drops_stmt<'a, 'i>(
// the parent might be deallocated before the function can use it.
// Thus forget everything about any increments.
let mut new_environment = environment.clone_without_incremented();
let incremented_symbols = environment.incremented_symbols.drain();
alloc_let_with_continuation!(&mut new_environment)
let new_stmt = alloc_let_with_continuation!(environment);
// The new_environment might have inserted increments that were set to 0 before. We need to add th
for (symbol, increment) in incremented_symbols.map.into_iter() {
environment
.incremented_symbols
.insert_count(symbol, increment);
}
new_stmt
}
}
}
Expr::Tag { tag_id, .. } => {
Expr::Tag {
tag_id,
arguments: children,
..
} => {
environment.symbol_tag.insert(*binding, *tag_id);
for (index, child) in children.iter().enumerate() {
environment.add_union_child(*binding, *child, *tag_id, index as u64);
}
alloc_let_with_continuation!(environment)
}
Expr::Struct(children) => {
for (index, child) in children.iter().enumerate() {
environment.add_struct_child(*binding, *child, index as u64);
}
alloc_let_with_continuation!(environment)
}
Expr::ExprBox { symbol: child } => {
environment.add_box_child(*binding, *child);
alloc_let_with_continuation!(environment)
}
Expr::Array {
elems: children, ..
} => {
for (index, child) in
children
.iter()
.enumerate()
.filter_map(|(index, child)| match child {
ListLiteralElement::Literal(_) => None,
ListLiteralElement::Symbol(s) => Some((index, s)),
})
{
environment.add_list_child(*binding, *child, index as u64);
}
alloc_let_with_continuation!(environment)
}
Expr::StructAtIndex {
@ -179,13 +240,10 @@ fn specialize_drops_stmt<'a, 'i>(
}
alloc_let_with_continuation!(environment)
}
Expr::Struct(_)
| Expr::RuntimeErrorFunction(_)
| Expr::ExprBox { .. }
Expr::RuntimeErrorFunction(_)
| Expr::NullPointer
| Expr::GetTagId { .. }
| Expr::EmptyArray
| Expr::Array { .. } => {
| Expr::EmptyArray => {
// Does nothing relevant to drop specialization. So we can just continue.
alloc_let_with_continuation!(environment)
}
@ -222,7 +280,7 @@ fn specialize_drops_stmt<'a, 'i>(
let new_branches = branches
.iter()
.map(|(label, info, branch)| {
let mut branch_env = environment.clone_without_incremented();
let mut branch_env = environment.clone();
insert_branch_info!(branch_env, info);
@ -234,7 +292,7 @@ fn specialize_drops_stmt<'a, 'i>(
branch,
);
(*label, info.clone(), new_branch.clone())
(*label, info.clone(), new_branch.clone(), branch_env)
})
.collect_in::<Vec<_>>(arena)
.into_bump_slice();
@ -242,7 +300,7 @@ fn specialize_drops_stmt<'a, 'i>(
let new_default_branch = {
let (info, branch) = default_branch;
let mut branch_env = environment.clone_without_incremented();
let mut branch_env = environment.clone();
insert_branch_info!(branch_env, info);
@ -254,24 +312,104 @@ fn specialize_drops_stmt<'a, 'i>(
branch,
);
(info.clone(), new_branch, branch_env)
};
// Find consumed increments in each branch and make sure they are consumed in all branches.
// By incrementing them in each branch where they were not consumed.
{
let branch_envs = {
let mut branch_environments =
Vec::with_capacity_in(new_branches.len() + 1, arena);
for (_, _, _, branch_env) in new_branches.iter() {
branch_environments.push(branch_env);
}
branch_environments.push(&new_default_branch.2);
branch_environments
};
// Find the lowest symbol count for each symbol in each branch, and update the environment to match.
for (symbol, count) in environment.incremented_symbols.map.iter_mut() {
let consumed = branch_envs
.iter()
.map(|branch_env| {
branch_env.incremented_symbols.map.get(symbol).unwrap_or(&0)
})
.min()
.unwrap();
// Update the existing env to match the lowest count.
*count = *consumed;
}
}
macro_rules! insert_incs {
($branch_env:expr, $branch:expr ) => {{
let symbol_differences =
environment
.incremented_symbols
.map
.iter()
.filter_map(|(symbol, count)| {
let branch_count = $branch_env
.incremented_symbols
.map
.get(symbol)
.unwrap_or(&0);
match branch_count - count {
0 => None,
difference => Some((symbol, difference)),
}
});
symbol_differences.fold($branch, |new_branch, (symbol, difference)| {
arena.alloc(Stmt::Refcounting(
ModifyRc::Inc(*symbol, difference),
new_branch,
))
})
}};
}
let newer_branches = new_branches
.iter()
.map(|(label, info, branch, branch_env)| {
let new_branch = insert_incs!(branch_env, branch);
(*label, info.clone(), new_branch.clone())
})
.collect_in::<Vec<_>>(arena)
.into_bump_slice();
let newer_default_branch = {
let (info, branch, branch_env) = new_default_branch;
let new_branch = insert_incs!(branch_env, branch);
(info.clone(), new_branch)
};
arena.alloc(Stmt::Switch {
cond_symbol: *cond_symbol,
cond_layout: *cond_layout,
branches: new_branches,
default_branch: new_default_branch,
branches: newer_branches,
default_branch: newer_default_branch,
ret_layout: *ret_layout,
})
}
Stmt::Ret(symbol) => arena.alloc(Stmt::Ret(*symbol)),
Stmt::Refcounting(rc, continuation) => match rc {
ModifyRc::Inc(symbol, count) => {
let any = environment.any_incremented(symbol);
let inc_before = environment.incremented_symbols.contains(symbol);
// Add a symbol for every increment performed.
environment.add_incremented(*symbol, *count);
environment
.incremented_symbols
.insert_count(*symbol, *count);
let new_continuation = specialize_drops_stmt(
arena,
@ -281,12 +419,17 @@ fn specialize_drops_stmt<'a, 'i>(
continuation,
);
if any {
if inc_before {
// There were increments before this one, best to let the first one do the increments.
// Or there are no increments left, so we can just continue.
new_continuation
} else {
match environment.get_incremented(symbol) {
match environment
.incremented_symbols
.map
.remove(symbol)
.unwrap_or(0)
{
// This is the first increment, but all increments are consumed. So don't insert any.
0 => new_continuation,
// We still need to do some increments.
@ -307,7 +450,7 @@ fn specialize_drops_stmt<'a, 'i>(
// dec a
// dec b
if environment.pop_incremented(symbol) {
if environment.incremented_symbols.pop(symbol) {
// This decremented symbol was incremented before, so we can remove it.
specialize_drops_stmt(
arena,
@ -317,24 +460,32 @@ fn specialize_drops_stmt<'a, 'i>(
continuation,
)
} else {
// Collect all children that were incremented and make sure that one increment remains in the environment afterwards.
// Collect all children (recursively) that were incremented and make sure that one increment remains in the environment afterwards.
// To prevent
// let a = index b; inc a; dec b; ...; dec a
// from being translated to
// let a = index b; dec b
// As a might get dropped as a result of the decrement of b.
let mut incremented_children = environment
.get_children(symbol)
.iter()
.copied()
.filter_map(|child| environment.pop_incremented(&child).then_some(child))
.collect::<MutSet<_>>();
let mut incremented_children = {
let mut todo_children = bumpalo::vec![in arena; *symbol];
let mut incremented_children = CountingMap::new();
while let Some(child) = todo_children.pop() {
if environment.incremented_symbols.pop(&child) {
incremented_children.insert(child);
} else {
todo_children.extend(environment.get_children(&child));
}
}
incremented_children
};
// This decremented symbol was not incremented before, perhaps the children were.
let in_layout = environment.get_symbol_layout(symbol);
let runtime_layout = layout_interner.runtime_representation(*in_layout);
let new_dec = match runtime_layout.repr {
let updated_stmt = match runtime_layout.repr {
// Layout has children, try to inline them.
LayoutRepr::Struct(field_layouts) => specialize_struct(
arena,
@ -391,11 +542,13 @@ fn specialize_drops_stmt<'a, 'i>(
};
// Add back the increments for the children to the environment.
for child_symbol in incremented_children.iter() {
environment.add_incremented(*child_symbol, 1)
for (child_symbol, symbol_count) in incremented_children.map.into_iter() {
environment
.incremented_symbols
.insert_count(child_symbol, symbol_count)
}
new_dec
updated_stmt
}
}
ModifyRc::DecRef(_) => {
@ -471,7 +624,8 @@ fn specialize_drops_stmt<'a, 'i>(
body,
remainder,
} => {
let mut new_environment = environment.clone_without_incremented();
let mut new_environment = environment.clone();
new_environment.incremented_symbols.clear();
for param in parameters.iter() {
new_environment.add_symbol_layout(param.symbol, param.layout);
@ -510,7 +664,7 @@ fn specialize_struct<'a, 'i>(
environment: &mut DropSpecializationEnvironment<'a>,
symbol: &Symbol,
struct_layout: &'a [InLayout],
incremented_children: &mut MutSet<Child>,
incremented_children: &mut CountingMap<Child>,
continuation: &'a Stmt<'a>,
) -> &'a Stmt<'a> {
match environment.struct_children.get(symbol) {
@ -526,7 +680,7 @@ fn specialize_struct<'a, 'i>(
for (index, _layout) in struct_layout.iter().enumerate() {
for (child, _i) in children_clone.iter().filter(|(_, i)| *i == index as u64) {
let removed = incremented_children.remove(child);
let removed = incremented_children.pop(child);
index_symbols.insert(index, (*child, removed));
if removed {
@ -599,7 +753,7 @@ fn specialize_union<'a, 'i>(
environment: &mut DropSpecializationEnvironment<'a>,
symbol: &Symbol,
union_layout: UnionLayout<'a>,
incremented_children: &mut MutSet<Child>,
incremented_children: &mut CountingMap<Child>,
continuation: &'a Stmt<'a>,
) -> &'a Stmt<'a> {
let current_tag = environment.symbol_tag.get(symbol).copied();
@ -642,7 +796,7 @@ fn specialize_union<'a, 'i>(
{
debug_assert_eq!(tag, *t);
let removed = incremented_children.remove(child);
let removed = incremented_children.pop(child);
index_symbols.insert(index, (*child, removed));
if removed {
@ -804,14 +958,14 @@ fn specialize_boxed<'a, 'i>(
layout_interner: &'i mut STLayoutInterner<'a>,
ident_ids: &'i mut IdentIds,
environment: &mut DropSpecializationEnvironment<'a>,
incremented_children: &mut MutSet<Child>,
incremented_children: &mut CountingMap<Child>,
symbol: &Symbol,
continuation: &'a Stmt<'a>,
) -> &'a Stmt<'a> {
let removed = match incremented_children.iter().next() {
Some(s) => {
let removed = match incremented_children.map.iter().next() {
Some((s, _)) => {
let s = *s;
incremented_children.remove(&s);
incremented_children.pop(&s);
Some(s)
}
None => None,
@ -830,23 +984,20 @@ fn specialize_boxed<'a, 'i>(
*symbol,
// If the symbol is unique:
// - free the box
|_, _, _| {
|_, _, continuation| {
arena.alloc(Stmt::Refcounting(
// TODO can be replaced by free if ever added to the IR.
ModifyRc::DecRef(*symbol),
new_continuation,
continuation,
))
},
// If the symbol is not unique:
// - increment the child
// - decref the box
|_, _, _| {
|_, _, continuation| {
arena.alloc(Stmt::Refcounting(
ModifyRc::Inc(s, 1),
arena.alloc(Stmt::Refcounting(
ModifyRc::DecRef(*symbol),
new_continuation,
)),
arena.alloc(Stmt::Refcounting(ModifyRc::DecRef(*symbol), continuation)),
))
},
new_continuation,
@ -864,7 +1015,7 @@ fn specialize_list<'a, 'i>(
layout_interner: &'i mut STLayoutInterner<'a>,
ident_ids: &'i mut IdentIds,
environment: &mut DropSpecializationEnvironment<'a>,
incremented_children: &mut MutSet<Child>,
incremented_children: &mut CountingMap<Child>,
symbol: &Symbol,
item_layout: InLayout,
continuation: &'a Stmt<'a>,
@ -899,7 +1050,7 @@ fn specialize_list<'a, 'i>(
for (child, i) in children_clone.iter().filter(|(_child, i)| *i == index) {
debug_assert!(length > *i);
let removed = incremented_children.remove(child);
let removed = incremented_children.pop(child);
index_symbols.insert(index, (*child, removed));
if removed {
@ -1120,7 +1271,6 @@ struct DropSpecializationEnvironment<'a> {
arena: &'a Bump,
home: ModuleId,
layout: InLayout<'a>,
target_info: TargetInfo,
symbol_layouts: MutMap<Symbol, InLayout<'a>>,
@ -1137,7 +1287,7 @@ struct DropSpecializationEnvironment<'a> {
list_children: MutMap<Parent, Vec<'a, (Child, Index)>>,
// Keeps track of all incremented symbols.
incremented_symbols: MutMap<Symbol, u64>,
incremented_symbols: CountingMap<Symbol>,
// Map containing the current known tag of a layout.
symbol_tag: MutMap<Symbol, Tag>,
@ -1150,42 +1300,23 @@ struct DropSpecializationEnvironment<'a> {
}
impl<'a> DropSpecializationEnvironment<'a> {
fn new(arena: &'a Bump, home: ModuleId, layout: InLayout<'a>, target_info: TargetInfo) -> Self {
fn new(arena: &'a Bump, home: ModuleId, layout: InLayout<'a>) -> Self {
Self {
arena,
home,
layout,
target_info,
symbol_layouts: MutMap::default(),
struct_children: MutMap::default(),
union_children: MutMap::default(),
box_children: MutMap::default(),
list_children: MutMap::default(),
incremented_symbols: MutMap::default(),
incremented_symbols: CountingMap::new(),
symbol_tag: MutMap::default(),
symbol_index: MutMap::default(),
list_length: MutMap::default(),
}
}
fn clone_without_incremented(&self) -> Self {
Self {
arena: self.arena,
home: self.home,
layout: self.layout,
target_info: self.target_info,
symbol_layouts: self.symbol_layouts.clone(),
struct_children: self.struct_children.clone(),
union_children: self.union_children.clone(),
box_children: self.box_children.clone(),
list_children: self.list_children.clone(),
incremented_symbols: MutMap::default(),
symbol_tag: self.symbol_tag.clone(),
symbol_index: self.symbol_index.clone(),
list_length: self.list_length.clone(),
}
}
fn create_symbol<'i>(&self, ident_ids: &'i mut IdentIds, debug_name: &str) -> Symbol {
let ident_id = ident_ids.add_str(debug_name);
Symbol::new(self.home, ident_id)
@ -1222,12 +1353,16 @@ impl<'a> DropSpecializationEnvironment<'a> {
.push(child);
}
fn add_list_child(&mut self, parent: Parent, child: Child, index: &Symbol) {
fn add_list_child(&mut self, parent: Parent, child: Child, index: u64) {
self.list_children
.entry(parent)
.or_insert_with(|| Vec::new_in(self.arena))
.push((child, index));
}
fn add_list_child_symbol(&mut self, parent: Parent, child: Child, index: &Symbol) {
if let Some(index) = self.symbol_index.get(index) {
self.list_children
.entry(parent)
.or_insert_with(|| Vec::new_in(self.arena))
.push((child, *index));
self.add_list_child(parent, child, *index)
}
}
@ -1252,35 +1387,149 @@ impl<'a> DropSpecializationEnvironment<'a> {
res
}
}
/**
Add a symbol for every increment performed.
*/
fn add_incremented(&mut self, symbol: Symbol, count: u64) {
self.incremented_symbols
.entry(symbol)
/**
Reference count information
*/
enum RC {
// Rc is important, moving an increment to after this function might break the program.
// E.g. if the function checks for uniqueness and behaves differently based on that.
Rc,
// Rc is not important, moving an increment to after this function should have no effect.
NoRc,
// Rc effect is unknown.
Uknown,
}
/*
Returns whether the reference count of arguments to this function is relevant to the program.
*/
fn low_level_no_rc(lowlevel: &LowLevel) -> RC {
use LowLevel::*;
match lowlevel {
Unreachable => RC::Uknown,
ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrGraphemes
| StrCountUtf8Bytes | StrGetCapacity | ListGetCapacity => RC::NoRc,
ListWithCapacity | StrWithCapacity => RC::NoRc,
ListReplaceUnsafe => RC::Rc,
StrGetUnsafe | ListGetUnsafe => RC::NoRc,
ListConcat => RC::Rc,
StrConcat => RC::Rc,
StrSubstringUnsafe => RC::NoRc,
StrReserve => RC::Rc,
StrAppendScalar => RC::Rc,
StrGetScalarUnsafe => RC::NoRc,
StrTrim => RC::Rc,
StrTrimLeft => RC::Rc,
StrTrimRight => RC::Rc,
StrSplit => RC::NoRc,
StrToNum => RC::NoRc,
ListPrepend => RC::Rc,
StrJoinWith => RC::NoRc,
ListMap | ListMap2 | ListMap3 | ListMap4 | ListSortWith => RC::Rc,
ListAppendUnsafe
| ListReserve
| ListSublist
| ListDropAt
| ListSwap
| ListReleaseExcessCapacity
| StrReleaseExcessCapacity => RC::Rc,
Eq | NotEq => RC::NoRc,
And | Or | NumAdd | NumAddWrap | NumAddChecked | NumAddSaturated | NumSub | NumSubWrap
| NumSubChecked | NumSubSaturated | NumMul | NumMulWrap | NumMulSaturated
| NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivFrac
| NumDivTruncUnchecked | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf
| NumPow | NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy
| NumShiftRightBy | NumShiftRightZfBy => RC::NoRc,
NumToStr
| NumAbs
| NumNeg
| NumSin
| NumCos
| NumSqrtUnchecked
| NumLogUnchecked
| NumRound
| NumCeiling
| NumFloor
| NumToFrac
| Not
| NumIsNan
| NumIsInfinite
| NumIsFinite
| NumAtan
| NumAcos
| NumAsin
| NumIntCast
| NumToIntChecked
| NumToFloatCast
| NumToFloatChecked
| NumCountLeadingZeroBits
| NumCountTrailingZeroBits
| NumCountOneBits => RC::NoRc,
NumBytesToU16 => RC::NoRc,
NumBytesToU32 => RC::NoRc,
NumBytesToU64 => RC::NoRc,
NumBytesToU128 => RC::NoRc,
I128OfDec => RC::NoRc,
DictPseudoSeed => RC::NoRc,
StrStartsWith | StrEndsWith => RC::NoRc,
StrStartsWithScalar => RC::NoRc,
StrFromUtf8Range => RC::Rc,
StrToUtf8 => RC::Rc,
StrRepeat => RC::NoRc,
StrFromInt | StrFromFloat => RC::NoRc,
Hash => RC::NoRc,
ListIsUnique => RC::Rc,
BoxExpr | UnboxExpr => {
unreachable!("These lowlevel operations are turned into mono Expr's")
}
PtrCast | PtrWrite | RefCountIncRcPtr | RefCountDecRcPtr | RefCountIncDataPtr
| RefCountDecDataPtr | RefCountIsUnique => {
unreachable!("Only inserted *after* borrow checking: {:?}", lowlevel);
}
}
}
/// Map that contains a count for each key.
/// Keys with a count of 0 are kept around, so that it can be seen that they were once present.
#[derive(Clone)]
struct CountingMap<K> {
map: MutMap<K, u64>,
}
impl<K> CountingMap<K>
where
K: Eq + std::hash::Hash + Clone,
{
fn new() -> Self {
Self {
map: MutMap::default(),
}
}
fn insert(&mut self, key: K) {
self.insert_count(key, 1);
}
fn insert_count(&mut self, key: K, count: u64) {
self.map
.entry(key)
.and_modify(|c| *c += count)
.or_insert(count);
}
fn any_incremented(&self, symbol: &Symbol) -> bool {
self.incremented_symbols.contains_key(symbol)
}
/**
Return the amount of times a symbol still has to be incremented.
Accounting for later consumtion and removal of the increment.
*/
fn get_incremented(&mut self, symbol: &Symbol) -> u64 {
self.incremented_symbols.remove(symbol).unwrap_or(0)
}
fn pop_incremented(&mut self, symbol: &Symbol) -> bool {
match self.incremented_symbols.get_mut(symbol) {
Some(1) => {
self.incremented_symbols.remove(symbol);
true
}
fn pop(&mut self, key: &K) -> bool {
match self.map.get_mut(key) {
Some(0) => false,
Some(c) => {
*c -= 1;
true
@ -1289,5 +1538,19 @@ impl<'a> DropSpecializationEnvironment<'a> {
}
}
// TODO assert that a parent is only inlined once / assert max single dec per parent.
fn contains(&self, symbol: &K) -> bool {
self.map.contains_key(symbol)
}
fn drain(&mut self) -> Self {
let res = self.clone();
for (_, v) in self.map.iter_mut() {
*v = 0;
}
res
}
fn clear(&mut self) {
self.map.clear();
}
}

View file

@ -1609,6 +1609,16 @@ mod hash {
)
}
#[test]
#[cfg(not(feature = "gen-wasm"))] // shr not implemented for U128
fn dec() {
assert_evals_to!(
&build_test("1.1dec"),
RocList::from_slice(&[0, 0, 238, 4, 44, 252, 67, 15, 0, 0, 0, 0, 0, 0, 0, 0]),
RocList<u8>
)
}
#[test]
fn string() {
assert_evals_to!(

View file

@ -2189,3 +2189,34 @@ fn issue_5162_recast_nested_nullable_unwrapped_layout() {
bool
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn nullable_wrapped_eq_issue_5434() {
assert_evals_to!(
indoc!(
r###"
app "test" provides [main] to "./platform"
Value : [
A,
B I64,
C,
D (List [T Str Value]),
]
main =
x : Value
x = B 32
y : Value
y = B 0
if x == y then
"OK"
else
"FAIL"
"###
),
RocStr::from("FAIL"),
RocStr
);
}

View file

@ -298,6 +298,10 @@ fn create_llvm_module<'a>(
);
}
if let Ok(path) = std::env::var("ROC_DEBUG_LLVM") {
env.module.print_to_file(path).unwrap();
}
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();

View file

@ -1,28 +1,28 @@
procedure Dict.1 (Dict.515):
let Dict.518 : List {[], []} = Array [];
let Dict.525 : U64 = 0i64;
let Dict.526 : U64 = 8i64;
let Dict.519 : List U64 = CallByName List.11 Dict.525 Dict.526;
let Dict.522 : I8 = CallByName Dict.34;
let Dict.523 : U64 = 8i64;
let Dict.520 : List I8 = CallByName List.11 Dict.522 Dict.523;
let Dict.521 : U64 = 0i64;
let Dict.517 : {List {[], []}, List U64, List I8, U64} = Struct {Dict.518, Dict.519, Dict.520, Dict.521};
ret Dict.517;
procedure Dict.1 (Dict.537):
let Dict.546 : List {[], []} = Array [];
let Dict.553 : U64 = 0i64;
let Dict.554 : U64 = 8i64;
let Dict.547 : List U64 = CallByName List.11 Dict.553 Dict.554;
let Dict.550 : I8 = CallByName Dict.36;
let Dict.551 : U64 = 8i64;
let Dict.548 : List I8 = CallByName List.11 Dict.550 Dict.551;
let Dict.549 : U64 = 0i64;
let Dict.545 : {List {[], []}, List U64, List I8, U64} = Struct {Dict.546, Dict.547, Dict.548, Dict.549};
ret Dict.545;
procedure Dict.34 ():
let Dict.524 : I8 = -128i64;
ret Dict.524;
procedure Dict.36 ():
let Dict.552 : I8 = -128i64;
ret Dict.552;
procedure Dict.4 (Dict.504):
let Dict.85 : U64 = StructAtIndex 3 Dict.504;
let #Derived_gen.2 : List {[], []} = StructAtIndex 0 Dict.504;
procedure Dict.4 (Dict.543):
let Dict.97 : U64 = StructAtIndex 3 Dict.543;
let #Derived_gen.2 : List {[], []} = StructAtIndex 0 Dict.543;
dec #Derived_gen.2;
let #Derived_gen.1 : List U64 = StructAtIndex 1 Dict.504;
let #Derived_gen.1 : List U64 = StructAtIndex 1 Dict.543;
dec #Derived_gen.1;
let #Derived_gen.0 : List I8 = StructAtIndex 2 Dict.504;
let #Derived_gen.0 : List I8 = StructAtIndex 2 Dict.543;
dec #Derived_gen.0;
ret Dict.85;
ret Dict.97;
procedure List.11 (List.115, List.116):
let List.495 : List I8 = CallByName List.68 List.116;

View file

@ -0,0 +1,6 @@
procedure Test.0 ():
let Test.2 : Str = "value";
let Test.3 : {Str, Str} = Struct {Test.2, Test.2};
dec Test.2;
let Test.4 : Str = "result";
ret Test.4;

View file

@ -697,8 +697,8 @@ procedure Json.25 (Json.183):
let Json.1906 : List U8 = CallByName List.8 Json.1907 Json.1908;
ret Json.1906;
else
let Json.1948 : U64 = StructAtIndex 0 Json.186;
inc Json.184;
let Json.1948 : U64 = StructAtIndex 0 Json.186;
let Json.1947 : {List U8, List U8} = CallByName List.52 Json.184 Json.1948;
let Json.210 : List U8 = StructAtIndex 0 Json.1947;
let Json.212 : List U8 = StructAtIndex 1 Json.1947;

View file

@ -623,8 +623,8 @@ procedure Json.25 (Json.183):
let Json.1532 : List U8 = CallByName List.8 Json.1533 Json.1534;
ret Json.1532;
else
let Json.1574 : U64 = StructAtIndex 0 Json.186;
inc Json.184;
let Json.1574 : U64 = StructAtIndex 0 Json.186;
let Json.1573 : {List U8, List U8} = CallByName List.52 Json.184 Json.1574;
let Json.210 : List U8 = StructAtIndex 0 Json.1573;
let Json.212 : List U8 = StructAtIndex 1 Json.1573;

View file

@ -630,8 +630,8 @@ procedure Json.25 (Json.183):
let Json.1532 : List U8 = CallByName List.8 Json.1533 Json.1534;
ret Json.1532;
else
let Json.1574 : U64 = StructAtIndex 0 Json.186;
inc Json.184;
let Json.1574 : U64 = StructAtIndex 0 Json.186;
let Json.1573 : {List U8, List U8} = CallByName List.52 Json.184 Json.1574;
let Json.210 : List U8 = StructAtIndex 0 Json.1573;
let Json.212 : List U8 = StructAtIndex 1 Json.1573;

View file

@ -118,8 +118,8 @@ procedure Json.25 (Json.183):
let Json.1179 : List U8 = CallByName List.8 Json.1180 Json.1181;
ret Json.1179;
else
let Json.1221 : U64 = StructAtIndex 0 Json.186;
inc Json.184;
let Json.1221 : U64 = StructAtIndex 0 Json.186;
let Json.1220 : {List U8, List U8} = CallByName List.52 Json.184 Json.1221;
let Json.210 : List U8 = StructAtIndex 0 Json.1220;
let Json.212 : List U8 = StructAtIndex 1 Json.1220;

View file

@ -147,8 +147,8 @@ procedure Json.25 (Json.183):
let Json.1220 : List U8 = CallByName List.8 Json.1221 Json.1222;
ret Json.1220;
else
let Json.1262 : U64 = StructAtIndex 0 Json.186;
inc Json.184;
let Json.1262 : U64 = StructAtIndex 0 Json.186;
let Json.1261 : {List U8, List U8} = CallByName List.52 Json.184 Json.1262;
let Json.210 : List U8 = StructAtIndex 0 Json.1261;
let Json.212 : List U8 = StructAtIndex 1 Json.1261;

View file

@ -150,8 +150,8 @@ procedure Json.25 (Json.183):
let Json.1220 : List U8 = CallByName List.8 Json.1221 Json.1222;
ret Json.1220;
else
let Json.1262 : U64 = StructAtIndex 0 Json.186;
inc Json.184;
let Json.1262 : U64 = StructAtIndex 0 Json.186;
let Json.1261 : {List U8, List U8} = CallByName List.52 Json.184 Json.1262;
let Json.210 : List U8 = StructAtIndex 0 Json.1261;
let Json.212 : List U8 = StructAtIndex 1 Json.1261;

View file

@ -41,8 +41,8 @@ procedure Decode.26 (Decode.105, Decode.106):
procedure Decode.27 (Decode.107, Decode.108):
let Decode.122 : {List U8, [C {}, C Str]} = CallByName Decode.26 Decode.107 Decode.108;
let Decode.110 : List U8 = StructAtIndex 0 Decode.122;
let Decode.109 : [C {}, C Str] = StructAtIndex 1 Decode.122;
inc Decode.110;
let Decode.109 : [C {}, C Str] = StructAtIndex 1 Decode.122;
let Decode.125 : Int1 = CallByName List.1 Decode.110;
if Decode.125 then
dec Decode.110;
@ -179,8 +179,8 @@ procedure Json.60 (Json.540):
let Json.1336 : U8 = GetTagId Json.1327;
let Json.1337 : Int1 = lowlevel Eq Json.1335 Json.1336;
if Json.1337 then
let Json.542 : U64 = UnionAtIndex (Id 2) (Index 0) Json.1327;
inc Json.540;
let Json.542 : U64 = UnionAtIndex (Id 2) (Index 0) Json.1327;
let Json.1329 : List U8 = CallByName List.29 Json.540 Json.542;
let Json.1332 : U64 = 0i64;
let Json.1331 : {U64, U64} = Struct {Json.542, Json.1332};
@ -532,18 +532,15 @@ procedure Json.68 ():
procedure Json.69 (Json.1467):
joinpoint Json.1197 Json.1165:
let Json.599 : List U8 = StructAtIndex 0 Json.1165;
inc 4 Json.599;
let Json.600 : List U8 = StructAtIndex 1 Json.1165;
let Json.1315 : U64 = 0i64;
inc Json.599;
let Json.601 : [C {}, C U8] = CallByName List.2 Json.599 Json.1315;
let Json.1314 : U64 = 1i64;
inc Json.599;
let Json.602 : [C {}, C U8] = CallByName List.2 Json.599 Json.1314;
let Json.1313 : U64 = 2i64;
inc Json.599;
let Json.603 : List U8 = CallByName List.29 Json.599 Json.1313;
let Json.1312 : U64 = 6i64;
inc Json.599;
let Json.604 : List U8 = CallByName List.29 Json.599 Json.1312;
let Json.1198 : {[C {}, C U8], [C {}, C U8]} = Struct {Json.601, Json.602};
joinpoint Json.1277:
@ -832,7 +829,7 @@ procedure Test.3 ():
let Test.7 : Str = "Roc";
let Test.6 : [C [C List U8, C ], C Str] = TagId(1) Test.7;
let Test.5 : Int1 = CallByName Bool.11 Test.1 Test.6;
dec Test.6;
dec Test.7;
expect Test.5;
dec Test.0;
dec Test.1;

View file

@ -140,9 +140,9 @@ procedure Test.1 (Test.77):
let Test.51 : [<r>C I64, C List *self] = StructAtIndex 1 Test.6;
dec Test.52;
let Test.14 : List [<r>C I64, C List *self] = UnionAtIndex (Id 1) (Index 0) Test.51;
inc Test.14;
joinpoint #Derived_gen.2:
let Test.35 : {} = Struct {};
inc Test.14;
let Test.33 : List {[<r>C I64, C List *self], [<r>C I64, C List *self]} = CallByName List.23 Test.12 Test.14 Test.35;
let Test.34 : {} = Struct {};
let Test.29 : Int1 = CallByName List.56 Test.33 Test.34;

View file

@ -153,8 +153,8 @@ procedure Json.60 (Json.540):
let Json.1336 : U8 = GetTagId Json.1327;
let Json.1337 : Int1 = lowlevel Eq Json.1335 Json.1336;
if Json.1337 then
let Json.542 : U64 = UnionAtIndex (Id 2) (Index 0) Json.1327;
inc Json.540;
let Json.542 : U64 = UnionAtIndex (Id 2) (Index 0) Json.1327;
let Json.1329 : List U8 = CallByName List.29 Json.540 Json.542;
let Json.1332 : U64 = 0i64;
let Json.1331 : {U64, U64} = Struct {Json.542, Json.1332};
@ -506,18 +506,15 @@ procedure Json.68 ():
procedure Json.69 (Json.1467):
joinpoint Json.1197 Json.1165:
let Json.599 : List U8 = StructAtIndex 0 Json.1165;
inc 4 Json.599;
let Json.600 : List U8 = StructAtIndex 1 Json.1165;
let Json.1315 : U64 = 0i64;
inc Json.599;
let Json.601 : [C {}, C U8] = CallByName List.2 Json.599 Json.1315;
let Json.1314 : U64 = 1i64;
inc Json.599;
let Json.602 : [C {}, C U8] = CallByName List.2 Json.599 Json.1314;
let Json.1313 : U64 = 2i64;
inc Json.599;
let Json.603 : List U8 = CallByName List.29 Json.599 Json.1313;
let Json.1312 : U64 = 6i64;
inc Json.599;
let Json.604 : List U8 = CallByName List.29 Json.599 Json.1312;
let Json.1198 : {[C {}, C U8], [C {}, C U8]} = Struct {Json.601, Json.602};
joinpoint Json.1277:
@ -865,7 +862,7 @@ procedure Test.12 ():
let Test.16 : {List U8, I64} = Struct {Test.17, Test.18};
let Test.15 : [C Str, C {List U8, I64}] = TagId(1) Test.16;
let Test.14 : Int1 = CallByName Bool.11 Test.10 Test.15;
dec Test.15;
dec Test.16;
expect Test.14;
dec Test.10;
let Test.13 : {} = Struct {};

View file

@ -5,7 +5,8 @@ procedure Test.0 ():
let Test.9 : U64 = 1i64;
let Test.10 : Int1 = lowlevel Eq Test.8 Test.9;
if Test.10 then
dec Test.1;
dec Test.11;
decref Test.1;
let Test.3 : Str = "B";
ret Test.3;
else

View file

@ -5,11 +5,20 @@ procedure Test.0 ():
let Test.2 : [<rnu><null>, C *self] = TagId(0) Test.13;
let Test.10 : U8 = 1i64;
let Test.11 : U8 = GetTagId Test.2;
dec Test.2;
let Test.12 : Int1 = lowlevel Eq Test.10 Test.11;
if Test.12 then
let Test.8 : I64 = 0i64;
ret Test.8;
joinpoint #Derived_gen.0:
let Test.12 : Int1 = lowlevel Eq Test.10 Test.11;
if Test.12 then
let Test.8 : I64 = 0i64;
ret Test.8;
else
let Test.9 : I64 = 1i64;
ret Test.9;
in
let #Derived_gen.1 : Int1 = lowlevel RefCountIsUnique Test.2;
if #Derived_gen.1 then
dec Test.13;
decref Test.2;
jump #Derived_gen.0;
else
let Test.9 : I64 = 1i64;
ret Test.9;
decref Test.2;
jump #Derived_gen.0;

View file

@ -30,6 +30,15 @@ procedure Test.0 ():
let Test.16 : Str = "";
let Test.15 : [<r>C List *self, C Str] = TagId(1) Test.16;
let Test.13 : Int1 = CallByName Bool.11 Test.14 Test.15;
dec Test.15;
dec Test.14;
ret Test.13;
joinpoint #Derived_gen.0:
dec Test.14;
ret Test.13;
in
let #Derived_gen.1 : Int1 = lowlevel RefCountIsUnique Test.15;
if #Derived_gen.1 then
dec Test.16;
decref Test.15;
jump #Derived_gen.0;
else
decref Test.15;
jump #Derived_gen.0;

View file

@ -45,10 +45,9 @@ procedure Num.22 (#Attr.2, #Attr.3):
procedure Test.1 (Test.2):
let Test.28 : U64 = 0i64;
inc Test.2;
inc 2 Test.2;
let Test.26 : [C {}, C I64] = CallByName List.2 Test.2 Test.28;
let Test.27 : U64 = 0i64;
inc Test.2;
let Test.25 : [C {}, C I64] = CallByName List.2 Test.2 Test.27;
let Test.8 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.25, Test.26};
joinpoint Test.22:

View file

@ -203,8 +203,8 @@ procedure Test.3 (Test.9, Test.10, Test.11):
let Test.173 : Int1 = true;
let Test.177 : Int1 = lowlevel Eq Test.173 Test.172;
if Test.177 then
let #Derived_gen.272 : [<rnu>C *self I64 *self I32 Int1, <null>] = Reset { symbol: Test.16, id: UpdateModeId { id: 242 } };
inc Test.19;
let #Derived_gen.272 : [<rnu>C *self I64 *self I32 Int1, <null>] = Reset { symbol: Test.16, id: UpdateModeId { id: 242 } };
let Test.118 : [<rnu>C *self I64 *self I32 Int1, <null>] = CallByName Test.3 Test.19 Test.10 Test.11;
joinpoint Test.137 #Derived_gen.317 #Derived_gen.318:
let Test.136 : [<rnu>C *self I64 *self I32 Int1, <null>] = UnionAtIndex (Id 1) (Index 0) Test.118;

View file

@ -44,9 +44,8 @@ procedure Num.22 (#Attr.2, #Attr.3):
ret Num.283;
procedure Test.1 (Test.2, Test.3, Test.4):
inc Test.4;
inc 2 Test.4;
let Test.29 : [C {}, C I64] = CallByName List.2 Test.4 Test.3;
inc Test.4;
let Test.28 : [C {}, C I64] = CallByName List.2 Test.4 Test.2;
let Test.13 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.28, Test.29};
joinpoint Test.25:

View file

@ -0,0 +1,79 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.282 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.282;
procedure Num.24 (#Attr.2, #Attr.3):
let Num.283 : Int1 = lowlevel NumGt #Attr.2 #Attr.3;
ret Num.283;
procedure Test.2 (Test.9, Test.10):
let Test.38 : U8 = 1i64;
let Test.39 : U8 = GetTagId Test.9;
let Test.40 : Int1 = lowlevel Eq Test.38 Test.39;
if Test.40 then
let Test.20 : U64 = CallByName Test.3 Test.10;
ret Test.20;
else
let Test.11 : Str = UnionAtIndex (Id 0) (Index 0) Test.9;
let Test.12 : [<rnu><null>, C Str *self] = UnionAtIndex (Id 0) (Index 1) Test.9;
let Test.35 : U8 = 1i64;
let Test.36 : U8 = GetTagId Test.10;
let Test.37 : Int1 = lowlevel Eq Test.35 Test.36;
if Test.37 then
let Test.29 : U64 = CallByName Test.3 Test.9;
ret Test.29;
else
joinpoint #Derived_gen.3:
let Test.13 : Str = UnionAtIndex (Id 0) (Index 0) Test.10;
let Test.14 : [<rnu><null>, C Str *self] = UnionAtIndex (Id 0) (Index 1) Test.10;
let Test.33 : U64 = CallByName Test.3 Test.12;
let Test.34 : U64 = 1i64;
let Test.15 : U64 = CallByName Num.19 Test.33 Test.34;
let Test.16 : U64 = CallByName Test.3 Test.10;
let Test.31 : Int1 = CallByName Num.24 Test.15 Test.16;
if Test.31 then
ret Test.15;
else
ret Test.16;
in
let #Derived_gen.4 : Int1 = lowlevel RefCountIsUnique Test.9;
if #Derived_gen.4 then
dec Test.11;
decref Test.9;
jump #Derived_gen.3;
else
inc Test.12;
decref Test.9;
jump #Derived_gen.3;
procedure Test.3 (Test.17):
let Test.26 : U8 = 1i64;
let Test.27 : U8 = GetTagId Test.17;
let Test.28 : Int1 = lowlevel Eq Test.26 Test.27;
if Test.28 then
let Test.22 : U64 = 0i64;
ret Test.22;
else
let Test.18 : [<rnu><null>, C Str *self] = UnionAtIndex (Id 0) (Index 1) Test.17;
joinpoint #Derived_gen.0:
let Test.24 : U64 = 1i64;
let Test.25 : U64 = CallByName Test.3 Test.18;
let Test.23 : U64 = CallByName Num.19 Test.24 Test.25;
ret Test.23;
in
let #Derived_gen.2 : Int1 = lowlevel RefCountIsUnique Test.17;
if #Derived_gen.2 then
let #Derived_gen.1 : Str = UnionAtIndex (Id 0) (Index 0) Test.17;
dec #Derived_gen.1;
decref Test.17;
jump #Derived_gen.0;
else
inc Test.18;
decref Test.17;
jump #Derived_gen.0;
procedure Test.0 ():
let Test.5 : [<rnu><null>, C Str *self] = TagId(1) ;
let Test.6 : [<rnu><null>, C Str *self] = TagId(1) ;
let Test.19 : U64 = CallByName Test.2 Test.5 Test.6;
ret Test.19;

View file

@ -136,8 +136,8 @@ procedure Json.25 (Json.183):
let Json.1223 : List U8 = CallByName List.8 Json.1224 Json.1225;
ret Json.1223;
else
let Json.1265 : U64 = StructAtIndex 0 Json.186;
inc Json.184;
let Json.1265 : U64 = StructAtIndex 0 Json.186;
let Json.1264 : {List U8, List U8} = CallByName List.52 Json.184 Json.1265;
let Json.210 : List U8 = StructAtIndex 0 Json.1264;
let Json.212 : List U8 = StructAtIndex 1 Json.1264;

View file

@ -3015,13 +3015,64 @@ fn rb_tree_fbip() {
}
#[mono_test]
fn record_update() {
fn specialize_after_match() {
indoc!(
r#"
app "test" provides [main] to "./platform"
main = f {a: [], b: [], c:[]}
main =
listA : LinkedList Str
listA = Nil
listB : LinkedList Str
listB = Nil
longestLinkedList listA listB
LinkedList a : [Cons a (LinkedList a), Nil]
longestLinkedList : LinkedList a, LinkedList a -> Nat
longestLinkedList = \listA, listB -> when listA is
Nil -> linkedListLength listB
Cons a aa -> when listB is
Nil -> linkedListLength listA
Cons b bb ->
lengthA = (linkedListLength aa) + 1
lengthB = linkedListLength listB
if lengthA > lengthB
then lengthA
else lengthB
linkedListLength : LinkedList a -> Nat
linkedListLength = \list -> when list is
Nil -> 0
Cons _ rest -> 1 + linkedListLength rest
"#
)
}
#[mono_test]
fn drop_specialize_after_struct() {
indoc!(
r#"
app "test" provides [main] to "./platform"
Tuple a b : { left : a, right : b }
main =
v = "value"
t = { left: v, right: v }
"result"
"#
)
}
#[mono_test]
fn record_update() {
indoc!(
r#"
app "test" provides [main] to "./platform"
main = f {a: [], b: [], c:[]}
f : {a: List Nat, b: List Nat, c: List Nat} -> {a: List Nat, b: List Nat, c: List Nat}
f = \record -> {record & a: List.set record.a 7 7, b: List.set record.b 8 8}
"#

View file

@ -5300,7 +5300,6 @@ pub struct CopiedImport {
pub rigid: Vec<Variable>,
pub flex_able: Vec<Variable>,
pub rigid_able: Vec<Variable>,
pub translations: Vec<(Variable, Variable)>,
pub registered: Vec<Variable>,
}
@ -5320,7 +5319,6 @@ struct CopyImportEnv<'a> {
rigid: Vec<Variable>,
flex_able: Vec<Variable>,
rigid_able: Vec<Variable>,
translations: Vec<(Variable, Variable)>,
registered: Vec<Variable>,
}
@ -5348,7 +5346,6 @@ pub fn copy_import_to(
rigid: Vec::new(),
flex_able: Vec::new(),
rigid_able: Vec::new(),
translations: Vec::new(),
registered: Vec::new(),
};
@ -5362,7 +5359,6 @@ pub fn copy_import_to(
rigid,
flex_able,
rigid_able,
translations,
registered,
target: _,
bookkeep_unspecialized_lambda_sets: _,
@ -5374,7 +5370,6 @@ pub fn copy_import_to(
rigid,
flex_able,
rigid_able,
translations,
registered,
}
};
@ -5668,13 +5663,21 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
let name = env.source.field_names[name_index.index as usize].clone();
let new_name_index = SubsIndex::push_new(&mut env.target.field_names, name);
env.target
.set(copy, make_descriptor(RigidVar(new_name_index)));
// If we are copying the import as generalized, we can keep it as rigid.
// Otherwise we must make it flex, as this is copying to a non-generalized site.
//
// The rigid distinction is never necessary for imports, since their types have already
// been checked completely.
let content = if max_rank.is_generalized() {
RigidVar(new_name_index)
} else {
FlexVar(Some(new_name_index))
};
env.target.set(copy, make_descriptor(content));
env.rigid.push(copy);
env.translations.push((var, copy));
copy
}
@ -5687,15 +5690,21 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
env.source.get_subs_slice(abilities).iter().copied(),
);
env.target.set(
copy,
make_descriptor(RigidAbleVar(new_name_index, new_abilities)),
);
// If we are copying the import as generalized, we can keep it as rigid.
// Otherwise we must make it flex, as this is copying to a non-generalized site.
//
// The rigid distinction is never necessary for imports, since their types have already
// been checked completely.
let content = if max_rank.is_generalized() {
RigidAbleVar(new_name_index, new_abilities)
} else {
FlexAbleVar(Some(new_name_index), new_abilities)
};
env.target.set(copy, make_descriptor(content));
env.rigid_able.push(copy);
env.translations.push((var, copy));
copy
}

View file

@ -0,0 +1,5 @@
app "test" provides [main] to "./platform"
main =
\h -> Hash.hash h 1.1dec
# ^^^^^^^^^ Hash#Hash.hash(1): a, Dec -[[Hash.hashDec(17)]]-> a | a has Hasher

View file

@ -0,0 +1,16 @@
# +opt infer:print_only_under_alias
app "test" provides [limitedKind] to "./platform"
Kind : [ A, B, C ]
Data : {kind: Kind}
limitedKind : Data -> Str
limitedKind = \data ->
when data is
# ^^^^ { kind : [A, B, C] }
{kind: A} -> "A is special"
{kind} -> when kind is
B -> "B"
C -> "C"
_ -> ""

View file

@ -0,0 +1,16 @@
# +opt infer:print_only_under_alias
app "test" provides [limitedKind] to "./platform"
Kind : [ A, B, C ]
Data : ({}, Kind)
limitedKind : Data -> Str
limitedKind = \data ->
when data is
# ^^^^ ( {}, [A, B, C] )*
({}, A) -> "A is special"
({}, kind) -> when kind is
B -> "B"
C -> "C"
_ -> ""

View file

@ -3,22 +3,22 @@
app "test" provides [main] to "./platform"
f = \{} ->
#^{-1} <1527><116>{} -<119>[[f(1)]]-> <115>[Ok <1535>{}]<79>*
#^{-1} <1599><116>{} -<119>[[f(1)]]-> <115>[Ok <1607>{}]<79>*
when g {} is
# ^ <1517><1535>{} -<1525>[[g(2)]]-> <71>[Ok <1535>{}]<101>*
# ^ <1589><1607>{} -<1597>[[g(2)]]-> <71>[Ok <1607>{}]<101>*
_ -> Ok {}
g = \{} ->
#^{-1} <1517><1535>{} -<1525>[[g(2)]]-> <71>[Ok <1535>{}]<101>*
#^{-1} <1589><1607>{} -<1597>[[g(2)]]-> <71>[Ok <1607>{}]<101>*
when h {} is
# ^ <1522><1535>{} -<1530>[[h(3)]]-> <93>[Ok <1535>{}]<123>*
# ^ <1594><1607>{} -<1602>[[h(3)]]-> <93>[Ok <1607>{}]<123>*
_ -> Ok {}
h = \{} ->
#^{-1} <1522><1535>{} -<1530>[[h(3)]]-> <93>[Ok <1535>{}]<123>*
#^{-1} <1594><1607>{} -<1602>[[h(3)]]-> <93>[Ok <1607>{}]<123>*
when f {} is
# ^ <1527><116>{} -<119>[[f(1)]]-> <115>[Ok <1535>{}]<79>*
# ^ <1599><116>{} -<119>[[f(1)]]-> <115>[Ok <1607>{}]<79>*
_ -> Ok {}
main = f {}
# ^ <1537><132>{} -<135>[[f(1)]]-> <137>[Ok <1535>{}]<1536>w_a
# ^ <1609><132>{} -<135>[[f(1)]]-> <137>[Ok <1607>{}]<1608>w_a

View file

@ -0,0 +1,5 @@
interface Test exposes [x] imports []
x : (I64, Str)
x = (1, "")
#^{-1} ( I64, Str )*