diff --git a/src/build/roc/Builtin.roc b/src/build/roc/Builtin.roc index eb0de2678ce..0c5c3568ced 100644 --- a/src/build/roc/Builtin.roc +++ b/src/build/roc/Builtin.roc @@ -4717,20 +4717,57 @@ Builtin :: [].{ ## If given a unique list, [List.keep_if] will mutate it in place to assemble the appropriate list. ## If that happens, this function will not allocate any new memory on the heap. ## If all items in the list end up being kept, Roc will return the original - ## list unaltered. + ## list unaltered. Otherwise, a shared list is copied into one new list with + ## the original's length as its capacity. ## keep_if : List(a), (a -> Bool) -> List(a) - keep_if = |list, predicate| - List.fold( - list, - [], - |acc, item| - if predicate(item) { - List.concat(acc, [item]) + keep_if = |list, predicate| { + prepared = list_map_prepare_reuse(list) + len = prepared.len() + match list_owned_unique(prepared) { + 1 => { + # Swap each kept item down to the end of the kept prefix, so the + # kept items stay in order and the dropped ones collect behind + # them for drop_last to release. + var $out = prepared + var $kept = 0 + var $index = 0 + while $index < len { + if predicate(list_get_unsafe($out, $index)) { + if $kept != $index { + $out = list_swap_unsafe($out, $kept, $index) + } + $kept = $kept + 1 + } + $index = $index + 1 + } + List.drop_last($out, len - $kept) + } + _ => { + # A shared list is returned as it is when every item is kept. + var $first_dropped = 0 + while $first_dropped < len and predicate(list_get_unsafe(prepared, $first_dropped)) { + $first_dropped = $first_dropped + 1 + } + if $first_dropped == len { + prepared } else { - acc - }, - ) + # The items before $first_dropped were already tested and kept, + # so copy them in one step and test only the items after it. + var $new_list = list_append_sublist(List.with_capacity(len), prepared, 0, $first_dropped) + var $index = $first_dropped + 1 + while $index < len { + item = list_get_unsafe(prepared, $index) + if predicate(item) { + $new_list = list_append_unsafe($new_list, item) + } + $index = $index + 1 + } + $new_list + } + } + } + } ## Run the given function on each item of a list, and return all the ## items for which the function returned `Bool.False`. @@ -4742,17 +4779,7 @@ Builtin :: [].{ ## `List.drop_if` has the same performance characteristics as [List.keep_if]. ## See its documentation for details on those characteristics! drop_if : List(a), (a -> Bool) -> List(a) - drop_if = |list, predicate| - List.fold( - list, - [], - |acc, item| - if predicate(item) { - acc - } else { - List.concat(acc, [item]) - }, - ) + drop_if = |list, predicate| List.keep_if(list, |item| !predicate(item)) ## Run the given function on each item of a list, and return a list of ## the values it wrapped in `Ok`. Items the function maps to `Err` are @@ -4761,16 +4788,25 @@ Builtin :: [].{ ## expect [1.I64, 2, 3, 4].keep_oks(|n| if n.is_even() { Ok(n) } else { Err({}) }) == [2, 4] ## ``` keep_oks : List(before), (before -> Try(after, _err)) -> List(after) - keep_oks = |list, transform| - List.fold( - list, - [], - |acc, item| - match transform(item) { - Ok(after) => List.concat(acc, [after]) - Err(_) => acc - }, - ) + keep_oks = |list, transform| { + # Allocated when the first Ok value needs a place, so a list with no + # Ok value allocates nothing. + var $out = [] + for item in list { + $out = match transform(item) { + Ok(after) => { + reserved = if $out.is_empty() { + List.with_capacity(list.len()) + } else { + $out + } + list_append_unsafe(reserved, after) + } + Err(_) => $out + } + } + $out + } ## Run the given function on each item of a list, and return a list of ## the values it wrapped in `Err`. Items the function maps to `Ok` are @@ -4779,16 +4815,25 @@ Builtin :: [].{ ## expect [1.I64, 2, 3, 4].keep_errs(|n| if n.is_even() { Ok(n) } else { Err(n) }) == [1, 3] ## ``` keep_errs : List(before), (before -> Try(_ok, after)) -> List(after) - keep_errs = |list, transform| - List.fold( - list, - [], - |acc, item| - match transform(item) { - Err(after) => List.concat(acc, [after]) - Ok(_) => acc - }, - ) + keep_errs = |list, transform| { + # Allocated when the first Err value needs a place, so a list with no + # Err value allocates nothing. + var $out = [] + for item in list { + $out = match transform(item) { + Err(after) => { + reserved = if $out.is_empty() { + List.with_capacity(list.len()) + } else { + $out + } + list_append_unsafe(reserved, after) + } + Ok(_) => $out + } + } + $out + } ## Run the given function on each item of a list, and return the ## number of items for which the function returned `Bool.True`. diff --git a/src/cli/test/parallel_cli_runner.zig b/src/cli/test/parallel_cli_runner.zig index 556d6575474..0804a6cd54b 100644 --- a/src/cli/test/parallel_cli_runner.zig +++ b/src/cli/test/parallel_cli_runner.zig @@ -3604,6 +3604,8 @@ const subcommand_cases = [_]CliCase{ .{ .id = 0, .suite = .subcommands, .name = "issue 11934: clear/refill preserves parameter capacity (speed)", .backend = .speed, .skip = .{ .windows = "test/alloc-count platform does not have Windows host libraries" }, .body = .{ .command = .{ .args = &.{ "--opt=speed", "--no-cache" }, .roc_file = "test/alloc-count/app_list_clear.roc", .contains = &.{.{ .stream = .stderr, .text = "refill length: 5000, refill allocations: 1" }}, .not_contains = &.{.{ .stream = .stderr, .text = "Expect failed" }} } } }, .{ .id = 0, .suite = .subcommands, .name = "issue 12306: List.sort_with does not heap-allocate (dev)", .backend = .dev, .skip = .{ .windows = "test/alloc-count platform does not have Windows host libraries" }, .body = .{ .command = .{ .args = &.{ "--opt=dev", "--no-cache" }, .roc_file = "test/alloc-count/app_list_sort_with.roc", .contains = &.{.{ .stream = .stderr, .text = "sorted length: 8, sort allocations: 0" }}, .not_contains = &.{.{ .stream = .stderr, .text = "Expect failed" }} } } }, .{ .id = 0, .suite = .subcommands, .name = "issue 12306: List.sort_with does not heap-allocate (speed)", .backend = .speed, .skip = .{ .windows = "test/alloc-count platform does not have Windows host libraries" }, .body = .{ .command = .{ .args = &.{ "--opt=speed", "--no-cache" }, .roc_file = "test/alloc-count/app_list_sort_with.roc", .contains = &.{.{ .stream = .stderr, .text = "sorted length: 8, sort allocations: 0" }}, .not_contains = &.{.{ .stream = .stderr, .text = "Expect failed" }} } } }, + .{ .id = 0, .suite = .subcommands, .name = "issue 12644: List.keep_if compacts a unique list in place (dev)", .backend = .dev, .skip = .{ .windows = "test/alloc-count platform does not have Windows host libraries" }, .body = .{ .command = .{ .args = &.{ "--opt=dev", "--no-cache" }, .roc_file = "test/alloc-count/app_list_keep_if.roc", .contains = &.{ .{ .stream = .stderr, .text = "keep_if allocations: some 0, all 0, none 0, drop_if 0, strings 0, shared 1, shared all 0, shared none 1, keep_oks 1, keep_oks none 0" }, .{ .stream = .stderr, .text = "kept lengths: [6, 8, 0, 2, 7, 8, 0] 2 2 0, shared input sum: 156" } }, .not_contains = &.{.{ .stream = .stderr, .text = "Expect failed" }} } } }, + .{ .id = 0, .suite = .subcommands, .name = "issue 12644: List.keep_if compacts a unique list in place (speed)", .backend = .speed, .skip = .{ .windows = "test/alloc-count platform does not have Windows host libraries" }, .body = .{ .command = .{ .args = &.{ "--opt=speed", "--no-cache" }, .roc_file = "test/alloc-count/app_list_keep_if.roc", .contains = &.{ .{ .stream = .stderr, .text = "keep_if allocations: some 0, all 0, none 0, drop_if 0, strings 0, shared 1, shared all 0, shared none 1, keep_oks 1, keep_oks none 0" }, .{ .stream = .stderr, .text = "kept lengths: [6, 8, 0, 2, 7, 8, 0] 2 2 0, shared input sum: 156" } }, .not_contains = &.{.{ .stream = .stderr, .text = "Expect failed" }} } } }, .{ .id = 0, .suite = .subcommands, .name = "issue 12306: sorts are stable permutations for every shape, length, and item kind (dev)", .backend = .dev, .skip = .{ .windows = "test/alloc-count platform does not have Windows host libraries" }, .body = .{ .command = .{ .args = &.{ "--opt=dev", "--no-cache" }, .roc_file = "test/alloc-count/app_sort_property.roc", .contains = &.{.{ .stream = .stderr, .text = "sort property checks: 296, failures: 0, lengths: 31 1000 1000, in-place allocations: 0 1 2" }} } } }, .{ .id = 0, .suite = .subcommands, .name = "issue 12306: sorts are stable permutations for every shape, length, and item kind (speed)", .backend = .speed, .skip = .{ .windows = "test/alloc-count platform does not have Windows host libraries" }, .body = .{ .command = .{ .args = &.{ "--opt=speed", "--no-cache" }, .roc_file = "test/alloc-count/app_sort_property.roc", .contains = &.{.{ .stream = .stderr, .text = "sort property checks: 296, failures: 0, lengths: 31 1000 1000, in-place allocations: 0 1 2" }} } } }, .{ .id = 0, .suite = .subcommands, .name = "issue 11965: appends after a non-inlined List.drop_last reuse the allocation (dev)", .backend = .dev, .skip = .{ .windows = "test/alloc-count platform does not have Windows host libraries" }, .body = .{ .command = .{ .args = &.{ "--opt=dev", "--no-cache" }, .roc_file = "test/alloc-count/app_drop_last_append.roc", .contains = &.{.{ .stream = .stderr, .text = "drop_last steps: 1024 1024, allocations: 1 1" }} } } }, diff --git a/src/eval/test/eval_tests.zig b/src/eval/test/eval_tests.zig index c4ed547f5ba..cf896229e24 100644 --- a/src/eval/test/eval_tests.zig +++ b/src/eval/test/eval_tests.zig @@ -1714,6 +1714,183 @@ const core_tests = [_]TestCase{ .expected = .{ .crash = {} }, }, + // List.keep_if compacts a unique list in place. Kept items move down in + // their original order and the final truncation releases the dropped ones. + .{ + .name = "inspect: List.keep_if in place keeps order", + .source = "List.keep_if(List.concat([1, 2, 3], [4, 5, 6]), |x| x != 2 and x != 5)", + .expected = .{ .inspect_str = "[1.0, 3.0, 4.0, 6.0]" }, + }, + .{ + .name = "inspect: List.keep_if in place keeps every item", + .source = "List.keep_if(List.concat([1, 2], [3]), |_| True)", + .expected = .{ .inspect_str = "[1.0, 2.0, 3.0]" }, + }, + .{ + .name = "inspect: List.keep_if in place keeps no item", + .source = "List.keep_if(List.concat([1, 2], [3]), |_| False)", + .expected = .{ .inspect_str = "[]" }, + }, + .{ + .name = "inspect: List.keep_if over emptied unique list", + .source = "List.keep_if(List.drop_first(List.concat([1], [2]), 2), |_| True)", + .expected = .{ .inspect_str = "[]" }, + }, + // list_owned_unique answers 0 for a seamless slice at runtime, so this + // usually takes the copy path; either path must give the same result. + .{ + .name = "inspect: List.keep_if over unique seamless slice", + .source = "List.keep_if(List.sublist(List.concat([1, 2], [3, 4]), { start: 1, len: 3 }), |x| x != 3)", + .expected = .{ .inspect_str = "[2.0, 4.0]" }, + }, + .{ + .name = "inspect: List.keep_if leaves shared list unchanged", + .source = + \\{ + \\ a = List.concat([1, 2], [3, 4]) + \\ b = List.keep_if(a, |x| x != 2) + \\ (a, b) + \\} + , + .expected = .{ .inspect_str = "([1.0, 2.0, 3.0, 4.0], [1.0, 3.0, 4.0])" }, + }, + // The predicate captures the list, which holds its refcount above 1. An + // in-place compaction here would let the predicate see moved items. + .{ + .name = "inspect: List.keep_if predicate capturing the filtered list", + .source = + \\{ + \\ a = List.concat([5, 7], [5, 9]) + \\ List.keep_if(a, |x| x != (a.get(0) ?? 0) and a.len() == 4) + \\} + , + .expected = .{ .inspect_str = "[7.0, 9.0]" }, + }, + .{ + .name = "inspect: List.keep_if in place over unique list of heap strings", + .source = "List.keep_if(List.concat([Str.repeat(\"x\", 30), Str.repeat(\"y\", 30)], [Str.repeat(\"z\", 30)]), |s| !Str.starts_with(s, \"y\"))", + .expected = .{ .inspect_str = "[\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\"]" }, + }, + .{ + .name = "inspect: List.keep_if leaves shared string list unchanged", + .source = + \\{ + \\ a = List.concat([Str.repeat("x", 30)], [Str.repeat("y", 30)]) + \\ b = List.keep_if(a, |s| Str.starts_with(s, "y")) + \\ (a, b) + \\} + , + .expected = .{ .inspect_str = "([\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\"], [\"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\"])" }, + }, + // A shared list is returned as it is while every item is kept. Once an + // item is dropped, the kept prefix and the kept items after it are copied. + .{ + .name = "inspect: List.keep_if over shared list drops first, middle and last items", + .source = + \\{ + \\ a = List.concat([1, 2, 3], [4, 5, 6]) + \\ ( + \\ a, + \\ List.keep_if(a, |x| x != 1), + \\ List.keep_if(a, |x| x != 3 and x != 4), + \\ List.keep_if(a, |x| x != 6), + \\ List.keep_if(a, |_| True), + \\ List.keep_if(a, |_| False), + \\ ) + \\} + , + .expected = .{ .inspect_str = "([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2.0, 3.0, 4.0, 5.0, 6.0], [1.0, 2.0, 5.0, 6.0], [1.0, 2.0, 3.0, 4.0, 5.0], [1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [])" }, + }, + .{ + .name = "inspect: List.keep_oks and List.keep_errs with nothing to keep", + .source = + \\{ + \\ a = List.concat([Str.repeat("x", 30)], [Str.repeat("y", 30)]) + \\ no_oks : List(Str) + \\ no_oks = List.keep_oks(a, |s| Err(s)) + \\ no_errs : List(Str) + \\ no_errs = List.keep_errs(a, |s| Ok(s)) + \\ (a, no_oks, no_errs) + \\} + , + .expected = .{ .inspect_str = "([\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\"], [], [])" }, + }, + // Two list literals and the concat allocate. Keeping every item of the + // shared list returns it, and keeping none copies into one allocation. + .{ + .name = "allocation - List.keep_if over shared list keeping all or none", + .source = + \\{ + \\ a = List.concat([1, 2, 3], [4, 5, 6]) + \\ all = List.keep_if(a, |_| True) + \\ none = List.keep_if(a, |_| False) + \\ if all.len() == 6 and none.len() == 0 and a.len() == 6 { + \\ "ok" + \\ } else { + \\ "bad" + \\ } + \\} + , + .expected = .{ .allocations_at_most = .{ + .output = "ok", + .max_allocations = 4, + } }, + }, + .{ + .name = "inspect: List.drop_if in place keeps order", + .source = "List.drop_if(List.concat([1, 2, 3], [4, 5, 6]), |x| x == 2 or x == 5)", + .expected = .{ .inspect_str = "[1.0, 3.0, 4.0, 6.0]" }, + }, + .{ + .name = "inspect: List.keep_oks and List.keep_errs keep order", + .source = + \\{ + \\ a = List.concat([Str.repeat("x", 30), Str.repeat("y", 30)], [Str.repeat("z", 30)]) + \\ oks = List.keep_oks(a, |s| if Str.starts_with(s, "y") { Err(s) } else { Ok(s) }) + \\ errs = List.keep_errs(a, |s| if Str.starts_with(s, "y") { Err(s) } else { Ok(s) }) + \\ (oks, errs) + \\} + , + .expected = .{ .inspect_str = "([\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", \"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\"], [\"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\"])" }, + }, + // Two list literals and the concat allocate; the in-place keep_if adds + // nothing. The copy path would allocate one more and fail this ceiling. + .{ + .name = "allocation - List.keep_if compacts unique list in place", + .source = + \\{ + \\ kept = List.keep_if(List.concat([1, 2, 3], [4, 5, 6]), |x| x != 2 and x != 5) + \\ if kept.len() == 4 and kept.first() == Ok(1) and kept.last() == Ok(6) { + \\ "ok" + \\ } else { + \\ "bad" + \\ } + \\} + , + .expected = .{ .allocations_at_most = .{ + .output = "ok", + .max_allocations = 3, + } }, + }, + .{ + .name = "allocation - List.keep_if compacts unique list in place, optimized", + .source = + \\{ + \\ kept = List.keep_if(List.concat([1, 2, 3], [4, 5, 6]), |x| x != 2 and x != 5) + \\ if kept.len() == 4 and kept.first() == Ok(1) and kept.last() == Ok(6) { + \\ "ok" + \\ } else { + \\ "bad" + \\ } + \\} + , + .expected = .{ .allocations_at_most = .{ + .output = "ok", + .max_allocations = 3, + .optimized = true, + } }, + }, + // Basic expressions and control flow .{ .name = "inspect: integer literal", .source = "42", .expected = .{ .inspect_str = "42.0" } }, .{ .name = "inspect: negative integer literal", .source = "-1234", .expected = .{ .inspect_str = "-1234.0" } }, diff --git a/src/eval/test/rc_conformance_tests.zig b/src/eval/test/rc_conformance_tests.zig index b0802588513..28ce0392ead 100644 --- a/src/eval/test/rc_conformance_tests.zig +++ b/src/eval/test/rc_conformance_tests.zig @@ -688,6 +688,59 @@ const cases = [_]Case{ \\} , }, + .{ + .name = "list keep_if compacts or copies its input", + .source = + \\{ + \\ shared = List.concat( + \\ ["a list element long enough to allocate", "another list element long enough"], + \\ ["a third list element long enough to allocate"], + \\ ) + \\ holder = [shared, shared] + \\ unique_kept = List.keep_if( + \\ List.concat( + \\ ["a list element long enough to allocate", "another list element long enough"], + \\ ["a fourth list element long enough to allocate"], + \\ ), + \\ |text| text.count_utf8_bytes() > 33, + \\ ) + \\ unique_dropped = List.drop_if( + \\ List.concat( + \\ ["a list element long enough to allocate", "another list element long enough"], + \\ ["a fourth list element long enough to allocate"], + \\ ), + \\ |text| text.count_utf8_bytes() > 33, + \\ ) + \\ unique_none = List.keep_if( + \\ List.concat( + \\ ["a list element long enough to allocate", "another list element long enough"], + \\ ["a fourth list element long enough to allocate"], + \\ ), + \\ |_| False, + \\ ) + \\ shared_kept = List.keep_if(shared, |text| text.count_utf8_bytes() > 33) + \\ shared_all = List.keep_if(shared, |_| True) + \\ shared_none = List.keep_if(shared, |_| False) + \\ oks = List.keep_oks(shared, |text| if text.count_utf8_bytes() > 33 { Ok(text) } else { Err(text) }) + \\ errs = List.keep_errs( + \\ List.concat( + \\ ["a list element long enough to allocate", "another list element long enough"], + \\ ["a fourth list element long enough to allocate"], + \\ ), + \\ |text| if text.count_utf8_bytes() > 33 { Ok(text) } else { Err(text) }, + \\ ) + \\ List.len(unique_kept) + \\ + List.len(unique_dropped) + \\ + List.len(unique_none) + \\ + List.len(shared_kept) + \\ + List.len(shared_all) + \\ + List.len(shared_none) + \\ + List.len(oks) + \\ + List.len(errs) + \\ + List.len(holder) + \\} + , + }, .{ .name = "boxed values are allocated, read, and updated", .source = diff --git a/test/alloc-count/app_list_keep_if.roc b/test/alloc-count/app_list_keep_if.roc new file mode 100644 index 00000000000..69e4365a991 --- /dev/null +++ b/test/alloc-count/app_list_keep_if.roc @@ -0,0 +1,108 @@ +app [run!] { pf: platform "./platform/main.roc" } + +import pf.Host + +# List.keep_if and List.drop_if compact a uniquely owned list in place, so +# they allocate nothing. A shared list, and keep_oks, whose item type +# changes, build their result in one allocation. keep_oks allocates nothing +# when it keeps nothing. A shared list that keeps every item comes back as it is. +numbers : U64 -> List(U64) +numbers = |offset| { + var $list = List.with_capacity(8) + var $i = 0 + while $i < 8 { + $list = List.append($list, offset + $i) + $i = $i + 1 + } + $list +} + +words : U64 -> List(Str) +words = |offset| { + var $list = List.with_capacity(4) + var $i = 0 + while $i < 4 { + $list = List.append($list, Str.repeat("a word long enough for the heap", offset + $i)) + $i = $i + 1 + } + $list +} + +run! : Str => Str +run! = |input| { + offset = Str.count_utf8_bytes(input) + + some = numbers(offset) + some_before = Host.alloc_count!() + some_kept = List.keep_if(some, |n| n - offset != 1 and n - offset != 4) + some_allocs = Host.alloc_count!() - some_before + expect some_kept == [offset, offset + 2, offset + 3, offset + 5, offset + 6, offset + 7] + + all = numbers(offset) + all_before = Host.alloc_count!() + all_kept = List.keep_if(all, |n| n >= offset) + all_allocs = Host.alloc_count!() - all_before + expect List.len(all_kept) == 8 + expect List.capacity(all_kept) == 8 + + none = numbers(offset) + none_before = Host.alloc_count!() + none_kept = List.keep_if(none, |n| n < offset) + none_allocs = Host.alloc_count!() - none_before + expect none_kept == [] + expect List.capacity(none_kept) == 8 + + dropped = numbers(offset) + drop_before = Host.alloc_count!() + drop_kept = List.drop_if(dropped, |n| n - offset < 6) + drop_allocs = Host.alloc_count!() - drop_before + expect drop_kept == [offset + 6, offset + 7] + + strs = words(offset) + strs_before = Host.alloc_count!() + strs_kept = List.keep_if(strs, |s| Str.count_utf8_bytes(s) % 2 == 0) + strs_allocs = Host.alloc_count!() - strs_before + expect List.len(strs_kept) == 2 + + shared = numbers(offset) + shared_before = Host.alloc_count!() + shared_kept = List.keep_if(shared, |n| n - offset != 3) + shared_allocs = Host.alloc_count!() - shared_before + expect shared == numbers(offset) + expect shared_kept == [offset, offset + 1, offset + 2, offset + 4, offset + 5, offset + 6, offset + 7] + + shared_all_before = Host.alloc_count!() + shared_all_kept = List.keep_if(shared, |n| n >= offset) + shared_all_allocs = Host.alloc_count!() - shared_all_before + expect shared_all_kept == shared + + shared_none_before = Host.alloc_count!() + shared_none_kept = List.keep_if(shared, |n| n < offset) + shared_none_allocs = Host.alloc_count!() - shared_none_before + expect shared_none_kept == [] + + oks = numbers(offset) + oks_before = Host.alloc_count!() + oks_kept = List.keep_oks( + oks, + |n| if n - offset > 5 { + Ok(n.to_str()) + } else { + Err(n) + }, + ) + oks_allocs = Host.alloc_count!() - oks_before + expect List.len(oks_kept) == 2 + + no_oks = numbers(offset) + no_oks_before = Host.alloc_count!() + no_oks_kept : List(Str) + no_oks_kept = List.keep_oks(no_oks, |n| Err(n)) + no_oks_allocs = Host.alloc_count!() - no_oks_before + expect no_oks_kept == [] + + # --opt=speed drops expects, so the output reads `shared` after keep_if to + # keep it shared there too. + lengths = [some_kept, all_kept, none_kept, drop_kept, shared_kept, shared_all_kept, shared_none_kept].map(List.len) + "keep_if allocations: some ${some_allocs.to_str()}, all ${all_allocs.to_str()}, none ${none_allocs.to_str()}, drop_if ${drop_allocs.to_str()}, strings ${strs_allocs.to_str()}, shared ${shared_allocs.to_str()}, shared all ${shared_all_allocs.to_str()}, shared none ${shared_none_allocs.to_str()}, keep_oks ${oks_allocs.to_str()}, keep_oks none ${no_oks_allocs.to_str()}\nkept lengths: ${Str.inspect(lengths)} ${List.len(strs_kept).to_str()} ${List.len(oks_kept).to_str()} ${List.len(no_oks_kept).to_str()}, shared input sum: ${List.sum(shared).to_str()}" +} diff --git a/test/snapshots/bang_on_numeric_literal.md b/test/snapshots/bang_on_numeric_literal.md index aa8db1abc8e..5592673fd29 100644 --- a/test/snapshots/bang_on_numeric_literal.md +++ b/test/snapshots/bang_on_numeric_literal.md @@ -46,7 +46,7 @@ NO CHANGE # CANONICALIZE ~~~clojure (e-call (constraint-fn-var 222) - (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18250") (target-def "18250")) + (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18404") (target-def "18404")) (e-runtime-error (tag "erroneous_value_expr"))) ~~~ # TYPES diff --git a/test/snapshots/bool_closure_type_check.md b/test/snapshots/bool_closure_type_check.md index 6e4fe2af9c3..8cabdef46ca 100644 --- a/test/snapshots/bool_closure_type_check.md +++ b/test/snapshots/bool_closure_type_check.md @@ -38,7 +38,7 @@ NO CHANGE (args (p-assign (ident "x"))) (e-call (constraint-fn-var 219) - (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18250") (target-def "18250")) + (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18404") (target-def "18404")) (e-lookup-local (p-assign (ident "x"))))) (e-tag (name "True"))) diff --git a/test/snapshots/dev_object_float_math.md b/test/snapshots/dev_object_float_math.md index 6557e542e6a..e4302a2fdc5 100644 --- a/test/snapshots/dev_object_float_math.md +++ b/test/snapshots/dev_object_float_math.md @@ -51,37 +51,37 @@ main = |x, y, n| { ~~~ # DEV OUTPUT ~~~ini -x64mac=82d64fb118f3a3ca13b7e8ab232fcb3d6eaf0de1a725f3d11445e20030b59ee3 -x64win=07ada194ced28237c6495ab60e995924295789c486b151d23e47d4ba517c3035 -x64mingw=07ada194ced28237c6495ab60e995924295789c486b151d23e47d4ba517c3035 -x64freebsd=75ed4c7b6323afa32fe104e167e8f0443ee03a1071f64ed1633625bbcb4a0310 -x64openbsd=3a5f1fea8fde17ac3d28b0dbcfd6ce145019e6f166faad83393f196766d075a7 -x64netbsd=b4e2dece3ca792bd1c7ea95b62a0761fd261d2342bd450d6aaac6d9b7f1c44ca -x64musl=b4e2dece3ca792bd1c7ea95b62a0761fd261d2342bd450d6aaac6d9b7f1c44ca -x64glibc=b4e2dece3ca792bd1c7ea95b62a0761fd261d2342bd450d6aaac6d9b7f1c44ca -x64linux=b4e2dece3ca792bd1c7ea95b62a0761fd261d2342bd450d6aaac6d9b7f1c44ca -x64elf=b4e2dece3ca792bd1c7ea95b62a0761fd261d2342bd450d6aaac6d9b7f1c44ca -x64v1mac=82d64fb118f3a3ca13b7e8ab232fcb3d6eaf0de1a725f3d11445e20030b59ee3 -x64v1win=07ada194ced28237c6495ab60e995924295789c486b151d23e47d4ba517c3035 -x64v1mingw=07ada194ced28237c6495ab60e995924295789c486b151d23e47d4ba517c3035 -x64v1freebsd=75ed4c7b6323afa32fe104e167e8f0443ee03a1071f64ed1633625bbcb4a0310 -x64v1openbsd=3a5f1fea8fde17ac3d28b0dbcfd6ce145019e6f166faad83393f196766d075a7 -x64v1netbsd=b4e2dece3ca792bd1c7ea95b62a0761fd261d2342bd450d6aaac6d9b7f1c44ca -x64v1musl=b4e2dece3ca792bd1c7ea95b62a0761fd261d2342bd450d6aaac6d9b7f1c44ca -x64v1glibc=b4e2dece3ca792bd1c7ea95b62a0761fd261d2342bd450d6aaac6d9b7f1c44ca -x64v1linux=b4e2dece3ca792bd1c7ea95b62a0761fd261d2342bd450d6aaac6d9b7f1c44ca -x64v1elf=b4e2dece3ca792bd1c7ea95b62a0761fd261d2342bd450d6aaac6d9b7f1c44ca -arm64mac=85b0eb4b323af39fd3c48cf6854d3b1b8ea65ea14bfc21583e21b817981e8a91 -arm64win=231d21e5b31e5fbe484d07ae85983ea8d56889c2e8f5036ee7ac7def0a5453e7 -arm64mingw=231d21e5b31e5fbe484d07ae85983ea8d56889c2e8f5036ee7ac7def0a5453e7 -arm64linux=ce8a063be314cca6f3810ca9ac283b8f53762172c5f8f18ea7d2012faf0ca68e -arm64musl=ce8a063be314cca6f3810ca9ac283b8f53762172c5f8f18ea7d2012faf0ca68e -arm64glibc=ce8a063be314cca6f3810ca9ac283b8f53762172c5f8f18ea7d2012faf0ca68e -arm64v1win=231d21e5b31e5fbe484d07ae85983ea8d56889c2e8f5036ee7ac7def0a5453e7 -arm64v1mingw=231d21e5b31e5fbe484d07ae85983ea8d56889c2e8f5036ee7ac7def0a5453e7 -arm64v1linux=ce8a063be314cca6f3810ca9ac283b8f53762172c5f8f18ea7d2012faf0ca68e -arm64v1musl=ce8a063be314cca6f3810ca9ac283b8f53762172c5f8f18ea7d2012faf0ca68e -arm64v1glibc=ce8a063be314cca6f3810ca9ac283b8f53762172c5f8f18ea7d2012faf0ca68e +x64mac=a7f501fdac1a5b2b0166a715c0199352f30472f400069804cf3d15bc38880d66 +x64win=9eec1b5ae590a39f7955896c652170efb33b387f2026591c7b24b775900d3a14 +x64mingw=9eec1b5ae590a39f7955896c652170efb33b387f2026591c7b24b775900d3a14 +x64freebsd=9957b2387a7dfc0fec257e3403fd60b69780576351b49aae65f84ff7362b21ab +x64openbsd=9f2dc5a078a01840514b3f125b7dc14063c416561dd7db3a68397672c56e1e03 +x64netbsd=eff11e8b7d710fe286513190c07cacf6c85c539e63f327984d299b0e933c7ece +x64musl=eff11e8b7d710fe286513190c07cacf6c85c539e63f327984d299b0e933c7ece +x64glibc=eff11e8b7d710fe286513190c07cacf6c85c539e63f327984d299b0e933c7ece +x64linux=eff11e8b7d710fe286513190c07cacf6c85c539e63f327984d299b0e933c7ece +x64elf=eff11e8b7d710fe286513190c07cacf6c85c539e63f327984d299b0e933c7ece +x64v1mac=a7f501fdac1a5b2b0166a715c0199352f30472f400069804cf3d15bc38880d66 +x64v1win=9eec1b5ae590a39f7955896c652170efb33b387f2026591c7b24b775900d3a14 +x64v1mingw=9eec1b5ae590a39f7955896c652170efb33b387f2026591c7b24b775900d3a14 +x64v1freebsd=9957b2387a7dfc0fec257e3403fd60b69780576351b49aae65f84ff7362b21ab +x64v1openbsd=9f2dc5a078a01840514b3f125b7dc14063c416561dd7db3a68397672c56e1e03 +x64v1netbsd=eff11e8b7d710fe286513190c07cacf6c85c539e63f327984d299b0e933c7ece +x64v1musl=eff11e8b7d710fe286513190c07cacf6c85c539e63f327984d299b0e933c7ece +x64v1glibc=eff11e8b7d710fe286513190c07cacf6c85c539e63f327984d299b0e933c7ece +x64v1linux=eff11e8b7d710fe286513190c07cacf6c85c539e63f327984d299b0e933c7ece +x64v1elf=eff11e8b7d710fe286513190c07cacf6c85c539e63f327984d299b0e933c7ece +arm64mac=af2f29e8eea855b478f485e54bef0a4ab5453f91b7e419199456efbdf2585064 +arm64win=d6ef3d9594492fdef7733d95ac2a5b420105a38e1be0ec6d0b089356071da9d2 +arm64mingw=d6ef3d9594492fdef7733d95ac2a5b420105a38e1be0ec6d0b089356071da9d2 +arm64linux=3e36390e2514051d5b1c72dba6dfff0f313654ae26b1d3a6efc81cd888122410 +arm64musl=3e36390e2514051d5b1c72dba6dfff0f313654ae26b1d3a6efc81cd888122410 +arm64glibc=3e36390e2514051d5b1c72dba6dfff0f313654ae26b1d3a6efc81cd888122410 +arm64v1win=d6ef3d9594492fdef7733d95ac2a5b420105a38e1be0ec6d0b089356071da9d2 +arm64v1mingw=d6ef3d9594492fdef7733d95ac2a5b420105a38e1be0ec6d0b089356071da9d2 +arm64v1linux=3e36390e2514051d5b1c72dba6dfff0f313654ae26b1d3a6efc81cd888122410 +arm64v1musl=3e36390e2514051d5b1c72dba6dfff0f313654ae26b1d3a6efc81cd888122410 +arm64v1glibc=3e36390e2514051d5b1c72dba6dfff0f313654ae26b1d3a6efc81cd888122410 arm32linux=NOT_IMPLEMENTED arm32musl=NOT_IMPLEMENTED wasm32=NOT_IMPLEMENTED diff --git a/test/snapshots/ellipsis_expression_positions.md b/test/snapshots/ellipsis_expression_positions.md index b1139e7e2e3..d3d9a6e960c 100644 --- a/test/snapshots/ellipsis_expression_positions.md +++ b/test/snapshots/ellipsis_expression_positions.md @@ -1135,7 +1135,7 @@ lambda_arg = |f| f(|_| ...) (args (p-underscore)) (e-call (constraint-fn-var 658) - (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18250") (target-def "18250")) + (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18404") (target-def "18404")) (e-not-implemented)))) (d-let (p-assign (ident "question")) diff --git a/test/snapshots/eval/tuple_bool.md b/test/snapshots/eval/tuple_bool.md index 2a978fa7b3d..51265d03548 100644 --- a/test/snapshots/eval/tuple_bool.md +++ b/test/snapshots/eval/tuple_bool.md @@ -53,10 +53,10 @@ NO CHANGE (builtin) (e-tag (name "False"))) (e-call (constraint-fn-var 257) - (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18250") (target-def "18250")) + (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18404") (target-def "18404")) (e-tag (name "True"))) (e-call (constraint-fn-var 262) - (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18250") (target-def "18250")) + (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18404") (target-def "18404")) (e-tag (name "False"))) (e-if (if-branches @@ -71,14 +71,14 @@ NO CHANGE (if-branches (if-branch (e-call (constraint-fn-var 279) - (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18250") (target-def "18250")) + (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18404") (target-def "18404")) (e-tag (name "True"))) (e-nominal-external (builtin) (e-tag (name "True"))))) (if-else (e-call (constraint-fn-var 290) - (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18250") (target-def "18250")) + (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18404") (target-def "18404")) (e-tag (name "True"))))))) ~~~ # TYPES diff --git a/test/snapshots/expr/not_tag.md b/test/snapshots/expr/not_tag.md index 4c104bc2cdc..cbdd5696603 100644 --- a/test/snapshots/expr/not_tag.md +++ b/test/snapshots/expr/not_tag.md @@ -66,7 +66,7 @@ NO CHANGE # CANONICALIZE ~~~clojure (e-call - (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18250") (target-def "18250")) + (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18404") (target-def "18404")) (e-tag (name "C") (args (e-num (value "2"))))) diff --git a/test/snapshots/expr/unary_not.md b/test/snapshots/expr/unary_not.md index ee1ab14c76a..b16b5fea10b 100644 --- a/test/snapshots/expr/unary_not.md +++ b/test/snapshots/expr/unary_not.md @@ -28,7 +28,7 @@ NO CHANGE # CANONICALIZE ~~~clojure (e-call - (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18250") (target-def "18250")) + (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18404") (target-def "18404")) (e-runtime-error (tag "ident_not_in_scope"))) ~~~ # TYPES diff --git a/test/snapshots/expr/unary_op_not.md b/test/snapshots/expr/unary_op_not.md index 381c8666375..c91fee24803 100644 --- a/test/snapshots/expr/unary_op_not.md +++ b/test/snapshots/expr/unary_op_not.md @@ -28,7 +28,7 @@ NO CHANGE # CANONICALIZE ~~~clojure (e-call - (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18250") (target-def "18250")) + (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18404") (target-def "18404")) (e-runtime-error (tag "ident_not_in_scope"))) ~~~ # TYPES diff --git a/test/snapshots/multiline_string_complex.md b/test/snapshots/multiline_string_complex.md index d79312d8951..f33d082ae55 100644 --- a/test/snapshots/multiline_string_complex.md +++ b/test/snapshots/multiline_string_complex.md @@ -355,7 +355,7 @@ With multiple lines (e-string))) (field (name "e") (e-call (constraint-fn-var 372) - (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18250") (target-def "18250")) + (e-lookup-associated-resolved (source "Bool.not") (builtin) (target-node "18404") (target-def "18404")) (e-runtime-error (tag "erroneous_value_expr"))))))) (d-let (p-assign (ident "x"))