Merge branch 'main' into abilities-syntax

This commit is contained in:
Bryce Miller 2023-06-01 12:57:16 -04:00
commit 7e8a151604
No known key found for this signature in database
GPG Key ID: F1E97BF8DF152350
52 changed files with 2293 additions and 1078 deletions

View File

@ -38,7 +38,7 @@ Execute `cargo fmt --all` to fix the formatting.
If you make changes to [Roc's Standard Library](https://www.roc-lang.org/builtins/Str), you can add comments to the code following [the CommonMark Spec](https://spec.commonmark.org/current/) to further explain your intentions. You can view these changes locally with: If you make changes to [Roc's Standard Library](https://www.roc-lang.org/builtins/Str), you can add comments to the code following [the CommonMark Spec](https://spec.commonmark.org/current/) to further explain your intentions. You can view these changes locally with:
```sh ```sh
cargo run docs crates/compiler/builtins/roc cargo run docs crates/compiler/builtins/roc/main.roc
``` ```
This command will generate the documentation in the [`generated-docs`](generated-docs) directory. This command will generate the documentation in the [`generated-docs`](generated-docs) directory.

View File

@ -120,8 +120,7 @@ fn main() -> io::Result<()> {
let input_path = matches.get_one::<PathBuf>(ROC_FILE).unwrap(); let input_path = matches.get_one::<PathBuf>(ROC_FILE).unwrap();
let target = matches let target = matches
.get_one::<String>(FLAG_TARGET) .get_one::<String>(FLAG_TARGET)
.map(|s| Target::from_str(s).ok()) .and_then(|s| Target::from_str(s).ok())
.flatten()
.unwrap_or_default(); .unwrap_or_default();
roc_linker::generate_stub_lib( roc_linker::generate_stub_lib(
input_path, input_path,
@ -132,8 +131,7 @@ fn main() -> io::Result<()> {
Some((CMD_BUILD, matches)) => { Some((CMD_BUILD, matches)) => {
let target = matches let target = matches
.get_one::<String>(FLAG_TARGET) .get_one::<String>(FLAG_TARGET)
.map(|s| Target::from_str(s).ok()) .and_then(|s| Target::from_str(s).ok())
.flatten()
.unwrap_or_default(); .unwrap_or_default();
let link_type = match (matches.get_flag(FLAG_LIB), matches.get_flag(FLAG_NO_LINK)) { let link_type = match (matches.get_flag(FLAG_LIB), matches.get_flag(FLAG_NO_LINK)) {
(true, false) => LinkType::Dylib, (true, false) => LinkType::Dylib,

View File

@ -725,7 +725,7 @@ mod cli_run {
Arg::PlainText("81"), Arg::PlainText("81"),
], ],
&[], &[],
"4\n", "4.000000000000001\n",
UseValgrind::No, UseValgrind::No,
TestCliCommands::Run, TestCliCommands::Run,
) )

194
crates/compiler/DESIGN.md Normal file
View File

@ -0,0 +1,194 @@
# Compiler Design
The current Roc compiler is designed as a pipelining compiler parallelizable
across Roc modules.
Roc's compilation pipeline consists of a few major components, which form the
table of contents for this document.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Parsing](#parsing)
- [Canonicalization](#canonicalization)
- [Symbol Resolution](#symbol-resolution)
- [Type-alias normalization](#type-alias-normalization)
- [Closure naming](#closure-naming)
- [Constraint Generation](#constraint-generation)
- [(Mutually-)recursive definitions](#mutually-recursive-definitions)
- [Type Solving](#type-solving)
- [Unification](#unification)
- [Type Inference](#type-inference)
- [Recursive Types](#recursive-types)
- [Lambda Sets](#lambda-sets)
- [Ability Collection](#ability-collection)
- [Ability Specialization](#ability-specialization)
- [Ability Derivation](#ability-derivation)
- [Exhaustiveness Checking](#exhaustiveness-checking)
- [Debugging](#debugging)
- [IR Generation](#ir-generation)
- [Memory Layouts](#memory-layouts)
- [Compiling Calls](#compiling-calls)
- [Decision Trees](#decision-trees)
- [Tail-call Optimization](#tail-call-optimization)
- [Reference-count insertion](#reference-count-insertion)
- [Reusing Memory Allocations](#reusing-memory-allocations)
- [Debugging](#debugging-1)
- [LLVM Code Generator](#llvm-code-generator)
- [Morphic Analysis](#morphic-analysis)
- [C ABI](#c-abi)
- [Test Harness](#test-harness)
- [Debugging](#debugging-2)
- [WASM Code Generator](#wasm-code-generator)
- [WASM Interpreter](#wasm-interpreter)
- [Debugging](#debugging-3)
- [Dev Code Generator](#dev-code-generator)
- [Debugging](#debugging-4)
- [Builtins](#builtins)
- [Compiler Driver](#compiler-driver)
- [Caching types](#caching-types)
- [Repl](#repl)
- [`test` and `dbg`](#test-and-dbg)
- [Formatter](#formatter)
- [Glue](#glue)
- [Active areas of research / help wanted](#active-areas-of-research--help-wanted)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Parsing
Roc's parsers are designed as [combinators](https://en.wikipedia.org/wiki/Parser_combinator).
A list of Roc's parse AST and combinators can be found in [the root parse
file](./parse/src/parser.rs).
Combinators enable parsing to compose as functions would - for example, the
`one_of` combinator supports attempting multiple parsing strategies, and
succeeding on the first one; the `and_then` combinator chains two parsers
together, failing if either parser in the sequence fails.
Since Roc is an indentation-sensitive language, parsing must be cognizant and
deligent about handling indentation and de-indentation levels. Most parsing
functions take a `min_indent` parameter that specifies the minimum indentation
of the scope an expression should be parsed in. Generally, failing to reach
`min_indent` indicates that an expression has ended (but perhaps too early).
## Canonicalization
After parsing a Roc program into an AST, the AST is transformed into a [canonical
form](./can/src/expr.rs) AST. This may seem a bit redundant - why build another
tree, when we already have the AST? Canonicalization performs a few analyses
to catch user errors, and sets up the state necessary to solve the types in a
program. Among other things, canonicalization
- Uniquely identifies names (think variable and function names). Along the way,
canonicalization builds a graph of all variables' references, and catches
unused definitions, undefined definitions, and shadowed definitions.
- Resolves type signatures, including aliases, into a form suitable for type
solving.
- Determines the order definitions are used in, if they are defined
out-of-order.
- Eliminates syntax sugar (for example, renaming `+` to the function call `add`
and converting backpassing to function calls).
- Collects declared abilities, and ability implementations defined for opaque
types. Derived abilities for opaque types are elaborated during
canonicalization.
### Symbol Resolution
Identifiers, like variable names, are resolved to [Symbol](./module/src/symbol.rs)s.
Currently, a symbol is a 64-bit value with
- the bottom 32 bits defining the [ModuleId](./module/src/ident.rs) the symbol
is defined in
- the top 32 bits defining the [IdentId](./module/src/ident.rs) of the symbol
in the module
A symbol is unique per identifier name and the scope
that the identifier has been declared in. Symbols are how the rest of the
compiler refers to value definitions - since the unique scope and identifier
name is disambiguated when symbols are created, referencing symbols requires no
further name resolution.
As symbols are constructed, canonicalization also keeps track of all references
to a given symbol. This simplifies catching unused definitions, undefined
definitions, and shadowing, to an index into an array.
### Type-alias normalization
### Closure naming
## Constraint Generation
### (Mutually-)recursive definitions
## Type Solving
### Unification
### Type Inference
### Recursive Types
### Lambda Sets
### Ability Collection
### Ability Specialization
### Ability Derivation
### Exhaustiveness Checking
### Debugging
## IR Generation
### Memory Layouts
### Compiling Calls
### Decision Trees
### Tail-call Optimization
### Reference-count insertion
### Reusing Memory Allocations
### Debugging
## LLVM Code Generator
### Morphic Analysis
### C ABI
### Test Harness
### Debugging
## WASM Code Generator
### WASM Interpreter
### Debugging
## Dev Code Generator
### Debugging
## Builtins
## Compiler Driver
### Caching types
## Repl
## `test` and `dbg`
## Formatter
## Glue
## Active areas of research / help wanted

View File

@ -1,147 +1,8 @@
# The Roc Compiler # The Roc Compiler
Here's how the compiler is laid out. For an overview of the design and architecture of the compiler, see
[DESIGN.md](./DESIGN.md). If you want to dive into the
## Parsing implementation or get some tips on debugging the compiler, see below
The main goal of parsing is to take a plain old String (such as the contents a .roc source file read from the filesystem) and translate that String into an `Expr` value.
`Expr` is an `enum` defined in the `expr` module. An `Expr` represents a Roc expression.
For example, parsing would translate this string...
"1 + 2"
...into this `Expr` value:
BinOp(Int(1), Plus, Int(2))
> Technically it would be `Box::new(Int(1))` and `Box::new(Int(2))`, but that's beside the point for now.
This `Expr` representation of the expression is useful for things like:
- Checking that all variables are declared before they're used
- Type checking
> As of this writing, the compiler doesn't do any of those things yet. They'll be added later!
Since the parser is only concerned with translating String values into Expr values, it will happily translate syntactically valid strings into expressions that won't work at runtime.
For example, parsing will translate this string:
not "foo", "bar"
...into this `Expr`:
CallByName("not", vec!["foo", "bar"])
Now we may know that `not` takes a `Bool` and returns another `Bool`, but the parser doesn't know that.
The parser only knows how to translate a `String` into an `Expr`; it's the job of other parts of the compiler to figure out if `Expr` values have problems like type mismatches and non-exhaustive patterns.
That said, the parser can still run into syntax errors. This won't parse:
if then 5 then else then
This is gibberish to the parser, so it will produce an error rather than an `Expr`.
Roc's parser is implemented using the [`marwes/combine`](http://github.com/marwes/combine-language/) crate.
## Evaluating
One of the useful things we can do with an `Expr` is to evaluate it.
The process of evaluation is basically to transform an `Expr` into the simplest `Expr` we can that's still equivalent to the original.
For example, let's say we had this code:
"1 + 8 - 3"
The parser will translate this into the following `Expr`:
BinOp(
Int(1),
Plus,
BinOp(Int(8), Minus, Int(3))
)
The `eval` function will take this `Expr` and translate it into this much simpler `Expr`:
Int(6)
At this point it's become so simple that we can display it to the end user as the number `6`. So running `parse` and then `eval` on the original Roc string of `1 + 8 - 3` will result in displaying `6` as the final output.
> The `expr` module includes an `impl fmt::Display for Expr` that takes care of translating `Int(6)` into `6`, `Char('x')` as `'x'`, and so on.
`eval` accomplishes this by doing a `match` on an `Expr` and resolving every operation it encounters. For example, when it first sees this:
BinOp(
Int(1),
Plus,
BinOp(Int(8), Minus, Int(3))
)
The first thing it does is to call `eval` on the right `Expr` values on either side of the `Plus`. That results in:
1. Calling `eval` on `Int(1)`, which returns `Int(1)` since it can't be reduced any further.
2. Calling `eval` on `BinOp(Int(8), Minus, Int(3))`, which in fact can be reduced further.
Since the second call to `eval` will match on another `BinOp`, it's once again going to recursively call `eval` on both of its `Expr` values. Since those are both `Int` values, though, their `eval` calls will return them right away without doing anything else.
Now that it's evaluated the expressions on either side of the `Minus`, `eval` will look at the particular operator being applied to those expressions (in this case, a minus operator) and check to see if the expressions it was given work with that operation.
> Remember, this `Expr` value potentially came directly from the parser. `eval` can't be sure any type checking has been done on it!
If `eval` detects a non-numeric `Expr` value (that is, the `Expr` is not `Int` or `Frac`) on either side of the `Minus`, then it will immediately give an error and halt the evaluation. This sort of runtime type error is common to dynamic languages, and you can think of `eval` as being a dynamic evaluation of Roc code that hasn't necessarily been type-checked.
Assuming there's no type problem, `eval` can go ahead and run the Rust code of `8 - 3` and store the result in an `Int` expr.
That concludes our original recursive call to `eval`, after which point we'll be evaluating this expression:
BinOp(
Int(1),
Plus,
Int(5)
)
This will work the same way as `Minus` did, and will reduce down to `Int(6)`.
## Optimization philosophy
Focus on optimizations which are only safe in the absence of side effects, and leave the rest to LLVM.
This focus may lead to some optimizations becoming transitively in scope. For example, some deforestation
examples in the MSR paper benefit from multiple rounds of interleaved deforestation, beta-reduction, and inlining.
To get those benefits, we'd have to do some inlining and beta-reduction that we could otherwise leave to LLVM's
inlining and constant propagation/folding.
Even if we're doing those things, it may still make sense to have LLVM do a pass for them as well, since
early LLVM optimization passes may unlock later opportunities for inlining and constant propagation/folding.
## Inlining
If a function is called exactly once (it's a helper function), presumably we always want to inline those.
If a function is "small enough" it's probably worth inlining too.
## Fusion
<https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/deforestation-short-cut.pdf>
Basic approach:
Do list stuff using `build` passing Cons Nil (like a cons list) and then do foldr/build substitution/reduction.
Afterwards, we can do a separate pass to flatten nested Cons structures into properly initialized RRBTs.
This way we get both deforestation and efficient RRBT construction. Should work for the other collection types too.
It looks like we need to do some amount of inlining and beta reductions on the Roc side, rather than
leaving all of those to LLVM.
Advanced approach:
Express operations like map and filter in terms of toStream and fromStream, to unlock more deforestation.
More info on here:
<https://wiki.haskell.org/GHC_optimisations#Fusion>
## Getting started with the code ## Getting started with the code

View File

@ -17,7 +17,7 @@ pub fn build(b: *Builder) void {
// Tests // Tests
var main_tests = b.addTest(main_path); var main_tests = b.addTest(main_path);
main_tests.setBuildMode(mode); main_tests.setBuildMode(mode);
main_tests.linkSystemLibrary("c"); main_tests.linkLibC();
const test_step = b.step("test", "Run tests"); const test_step = b.step("test", "Run tests");
test_step.dependOn(&main_tests.step); test_step.dependOn(&main_tests.step);

View File

@ -0,0 +1,442 @@
const std = @import("std");
const builtin = @import("builtin");
const math = std.math;
// Eventually, we need to statically ingest compiler-rt and get it working with the surgical linker, then these should not be needed anymore.
// Until then, we are manually ingesting used parts of compiler-rt here.
//
// Taken from
// https://github.com/ziglang/zig/tree/4976b58ab16069f8d3267b69ed030f29685c1abe/lib/compiler_rt/
// Thank you Zig Contributors!
// Libcalls that involve u128 on Windows x86-64 are expected by LLVM to use the
// calling convention of @Vector(2, u64), rather than what's standard.
pub const want_windows_v2u64_abi = builtin.os.tag == .windows and builtin.cpu.arch == .x86_64 and @import("builtin").object_format != .c;
const v2u64 = @Vector(2, u64);
// Export it as weak incase it is already linked in by something else.
comptime {
@export(__muloti4, .{ .name = "__muloti4", .linkage = .Weak });
if (want_windows_v2u64_abi) {
@export(__divti3_windows_x86_64, .{ .name = "__divti3", .linkage = .Weak });
@export(__modti3_windows_x86_64, .{ .name = "__modti3", .linkage = .Weak });
@export(__umodti3_windows_x86_64, .{ .name = "__umodti3", .linkage = .Weak });
@export(__udivti3_windows_x86_64, .{ .name = "__udivti3", .linkage = .Weak });
@export(__fixdfti_windows_x86_64, .{ .name = "__fixdfti", .linkage = .Weak });
@export(__fixsfti_windows_x86_64, .{ .name = "__fixsfti", .linkage = .Weak });
@export(__fixunsdfti_windows_x86_64, .{ .name = "__fixunsdfti", .linkage = .Weak });
@export(__fixunssfti_windows_x86_64, .{ .name = "__fixunssfti", .linkage = .Weak });
} else {
@export(__divti3, .{ .name = "__divti3", .linkage = .Weak });
@export(__modti3, .{ .name = "__modti3", .linkage = .Weak });
@export(__umodti3, .{ .name = "__umodti3", .linkage = .Weak });
@export(__udivti3, .{ .name = "__udivti3", .linkage = .Weak });
@export(__fixdfti, .{ .name = "__fixdfti", .linkage = .Weak });
@export(__fixsfti, .{ .name = "__fixsfti", .linkage = .Weak });
@export(__fixunsdfti, .{ .name = "__fixunsdfti", .linkage = .Weak });
@export(__fixunssfti, .{ .name = "__fixunssfti", .linkage = .Weak });
}
}
pub fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 {
if (2 * @bitSizeOf(i128) <= @bitSizeOf(usize)) {
return muloXi4_genericFast(i128, a, b, overflow);
} else {
return muloXi4_genericSmall(i128, a, b, overflow);
}
}
pub fn __divti3(a: i128, b: i128) callconv(.C) i128 {
return div(a, b);
}
fn __divti3_windows_x86_64(a: v2u64, b: v2u64) callconv(.C) v2u64 {
return @bitCast(v2u64, div(@bitCast(i128, a), @bitCast(i128, b)));
}
inline fn div(a: i128, b: i128) i128 {
const s_a = a >> (128 - 1);
const s_b = b >> (128 - 1);
const an = (a ^ s_a) -% s_a;
const bn = (b ^ s_b) -% s_b;
const r = udivmod(u128, @bitCast(u128, an), @bitCast(u128, bn), null);
const s = s_a ^ s_b;
return (@bitCast(i128, r) ^ s) -% s;
}
pub fn __udivti3(a: u128, b: u128) callconv(.C) u128 {
return udivmod(u128, a, b, null);
}
fn __udivti3_windows_x86_64(a: v2u64, b: v2u64) callconv(.C) v2u64 {
return @bitCast(v2u64, udivmod(u128, @bitCast(u128, a), @bitCast(u128, b), null));
}
pub fn __umodti3(a: u128, b: u128) callconv(.C) u128 {
var r: u128 = undefined;
_ = udivmod(u128, a, b, &r);
return r;
}
fn __umodti3_windows_x86_64(a: v2u64, b: v2u64) callconv(.C) v2u64 {
var r: u128 = undefined;
_ = udivmod(u128, @bitCast(u128, a), @bitCast(u128, b), &r);
return @bitCast(v2u64, r);
}
pub fn __modti3(a: i128, b: i128) callconv(.C) i128 {
return mod(a, b);
}
fn __modti3_windows_x86_64(a: v2u64, b: v2u64) callconv(.C) v2u64 {
return @bitCast(v2u64, mod(@bitCast(i128, a), @bitCast(i128, b)));
}
inline fn mod(a: i128, b: i128) i128 {
const s_a = a >> (128 - 1); // s = a < 0 ? -1 : 0
const s_b = b >> (128 - 1); // s = b < 0 ? -1 : 0
const an = (a ^ s_a) -% s_a; // negate if s == -1
const bn = (b ^ s_b) -% s_b; // negate if s == -1
var r: u128 = undefined;
_ = udivmod(u128, @bitCast(u128, an), @bitCast(u128, bn), &r);
return (@bitCast(i128, r) ^ s_a) -% s_a; // negate if s == -1
}
pub fn __fixdfti(a: f64) callconv(.C) i128 {
return floatToInt(i128, a);
}
fn __fixdfti_windows_x86_64(a: f64) callconv(.C) v2u64 {
return @bitCast(v2u64, floatToInt(i128, a));
}
pub fn __fixsfti(a: f32) callconv(.C) i128 {
return floatToInt(i128, a);
}
fn __fixsfti_windows_x86_64(a: f32) callconv(.C) v2u64 {
return @bitCast(v2u64, floatToInt(i128, a));
}
pub fn __fixunsdfti(a: f64) callconv(.C) u128 {
return floatToInt(u128, a);
}
fn __fixunsdfti_windows_x86_64(a: f64) callconv(.C) v2u64 {
return @bitCast(v2u64, floatToInt(u128, a));
}
pub fn __fixunssfti(a: f32) callconv(.C) u128 {
return floatToInt(u128, a);
}
fn __fixunssfti_windows_x86_64(a: f32) callconv(.C) v2u64 {
return @bitCast(v2u64, floatToInt(u128, a));
}
// mulo - multiplication overflow
// * return a*%b.
// * return if a*b overflows => 1 else => 0
// - muloXi4_genericSmall as default
// - muloXi4_genericFast for 2*bitsize <= usize
inline fn muloXi4_genericSmall(comptime ST: type, a: ST, b: ST, overflow: *c_int) ST {
overflow.* = 0;
const min = math.minInt(ST);
var res: ST = a *% b;
// Hacker's Delight section Overflow subsection Multiplication
// case a=-2^{31}, b=-1 problem, because
// on some machines a*b = -2^{31} with overflow
// Then -2^{31}/-1 overflows and any result is possible.
// => check with a<0 and b=-2^{31}
if ((a < 0 and b == min) or (a != 0 and @divTrunc(res, a) != b))
overflow.* = 1;
return res;
}
inline fn muloXi4_genericFast(comptime ST: type, a: ST, b: ST, overflow: *c_int) ST {
overflow.* = 0;
const EST = switch (ST) {
i32 => i64,
i64 => i128,
i128 => i256,
else => unreachable,
};
const min = math.minInt(ST);
const max = math.maxInt(ST);
var res: EST = @as(EST, a) * @as(EST, b);
//invariant: -2^{bitwidth(EST)} < res < 2^{bitwidth(EST)-1}
if (res < min or max < res)
overflow.* = 1;
return @truncate(ST, res);
}
const native_endian = builtin.cpu.arch.endian();
const low = switch (native_endian) {
.Big => 1,
.Little => 0,
};
const high = 1 - low;
pub fn udivmod(comptime DoubleInt: type, a: DoubleInt, b: DoubleInt, maybe_rem: ?*DoubleInt) DoubleInt {
// @setRuntimeSafety(builtin.is_test);
const double_int_bits = @typeInfo(DoubleInt).Int.bits;
const single_int_bits = @divExact(double_int_bits, 2);
const SingleInt = std.meta.Int(.unsigned, single_int_bits);
const SignedDoubleInt = std.meta.Int(.signed, double_int_bits);
const Log2SingleInt = std.math.Log2Int(SingleInt);
const n = @bitCast([2]SingleInt, a);
const d = @bitCast([2]SingleInt, b);
var q: [2]SingleInt = undefined;
var r: [2]SingleInt = undefined;
var sr: c_uint = undefined;
// special cases, X is unknown, K != 0
if (n[high] == 0) {
if (d[high] == 0) {
// 0 X
// ---
// 0 X
if (maybe_rem) |rem| {
rem.* = n[low] % d[low];
}
return n[low] / d[low];
}
// 0 X
// ---
// K X
if (maybe_rem) |rem| {
rem.* = n[low];
}
return 0;
}
// n[high] != 0
if (d[low] == 0) {
if (d[high] == 0) {
// K X
// ---
// 0 0
if (maybe_rem) |rem| {
rem.* = n[high] % d[low];
}
return n[high] / d[low];
}
// d[high] != 0
if (n[low] == 0) {
// K 0
// ---
// K 0
if (maybe_rem) |rem| {
r[high] = n[high] % d[high];
r[low] = 0;
rem.* = @bitCast(DoubleInt, r);
}
return n[high] / d[high];
}
// K K
// ---
// K 0
if ((d[high] & (d[high] - 1)) == 0) {
// d is a power of 2
if (maybe_rem) |rem| {
r[low] = n[low];
r[high] = n[high] & (d[high] - 1);
rem.* = @bitCast(DoubleInt, r);
}
return n[high] >> @intCast(Log2SingleInt, @ctz(SingleInt, d[high]));
}
// K K
// ---
// K 0
sr = @bitCast(c_uint, @as(c_int, @clz(SingleInt, d[high])) - @as(c_int, @clz(SingleInt, n[high])));
// 0 <= sr <= single_int_bits - 2 or sr large
if (sr > single_int_bits - 2) {
if (maybe_rem) |rem| {
rem.* = a;
}
return 0;
}
sr += 1;
// 1 <= sr <= single_int_bits - 1
// q.all = a << (double_int_bits - sr);
q[low] = 0;
q[high] = n[low] << @intCast(Log2SingleInt, single_int_bits - sr);
// r.all = a >> sr;
r[high] = n[high] >> @intCast(Log2SingleInt, sr);
r[low] = (n[high] << @intCast(Log2SingleInt, single_int_bits - sr)) | (n[low] >> @intCast(Log2SingleInt, sr));
} else {
// d[low] != 0
if (d[high] == 0) {
// K X
// ---
// 0 K
if ((d[low] & (d[low] - 1)) == 0) {
// d is a power of 2
if (maybe_rem) |rem| {
rem.* = n[low] & (d[low] - 1);
}
if (d[low] == 1) {
return a;
}
sr = @ctz(SingleInt, d[low]);
q[high] = n[high] >> @intCast(Log2SingleInt, sr);
q[low] = (n[high] << @intCast(Log2SingleInt, single_int_bits - sr)) | (n[low] >> @intCast(Log2SingleInt, sr));
return @bitCast(DoubleInt, q);
}
// K X
// ---
// 0 K
sr = 1 + single_int_bits + @as(c_uint, @clz(SingleInt, d[low])) - @as(c_uint, @clz(SingleInt, n[high]));
// 2 <= sr <= double_int_bits - 1
// q.all = a << (double_int_bits - sr);
// r.all = a >> sr;
if (sr == single_int_bits) {
q[low] = 0;
q[high] = n[low];
r[high] = 0;
r[low] = n[high];
} else if (sr < single_int_bits) {
// 2 <= sr <= single_int_bits - 1
q[low] = 0;
q[high] = n[low] << @intCast(Log2SingleInt, single_int_bits - sr);
r[high] = n[high] >> @intCast(Log2SingleInt, sr);
r[low] = (n[high] << @intCast(Log2SingleInt, single_int_bits - sr)) | (n[low] >> @intCast(Log2SingleInt, sr));
} else {
// single_int_bits + 1 <= sr <= double_int_bits - 1
q[low] = n[low] << @intCast(Log2SingleInt, double_int_bits - sr);
q[high] = (n[high] << @intCast(Log2SingleInt, double_int_bits - sr)) | (n[low] >> @intCast(Log2SingleInt, sr - single_int_bits));
r[high] = 0;
r[low] = n[high] >> @intCast(Log2SingleInt, sr - single_int_bits);
}
} else {
// K X
// ---
// K K
sr = @bitCast(c_uint, @as(c_int, @clz(SingleInt, d[high])) - @as(c_int, @clz(SingleInt, n[high])));
// 0 <= sr <= single_int_bits - 1 or sr large
if (sr > single_int_bits - 1) {
if (maybe_rem) |rem| {
rem.* = a;
}
return 0;
}
sr += 1;
// 1 <= sr <= single_int_bits
// q.all = a << (double_int_bits - sr);
// r.all = a >> sr;
q[low] = 0;
if (sr == single_int_bits) {
q[high] = n[low];
r[high] = 0;
r[low] = n[high];
} else {
r[high] = n[high] >> @intCast(Log2SingleInt, sr);
r[low] = (n[high] << @intCast(Log2SingleInt, single_int_bits - sr)) | (n[low] >> @intCast(Log2SingleInt, sr));
q[high] = n[low] << @intCast(Log2SingleInt, single_int_bits - sr);
}
}
}
// Not a special case
// q and r are initialized with:
// q.all = a << (double_int_bits - sr);
// r.all = a >> sr;
// 1 <= sr <= double_int_bits - 1
var carry: u32 = 0;
var r_all: DoubleInt = undefined;
while (sr > 0) : (sr -= 1) {
// r:q = ((r:q) << 1) | carry
r[high] = (r[high] << 1) | (r[low] >> (single_int_bits - 1));
r[low] = (r[low] << 1) | (q[high] >> (single_int_bits - 1));
q[high] = (q[high] << 1) | (q[low] >> (single_int_bits - 1));
q[low] = (q[low] << 1) | carry;
// carry = 0;
// if (r.all >= b)
// {
// r.all -= b;
// carry = 1;
// }
r_all = @bitCast(DoubleInt, r);
const s: SignedDoubleInt = @bitCast(SignedDoubleInt, b -% r_all -% 1) >> (double_int_bits - 1);
carry = @intCast(u32, s & 1);
r_all -= b & @bitCast(DoubleInt, s);
r = @bitCast([2]SingleInt, r_all);
}
const q_all = (@bitCast(DoubleInt, q) << 1) | carry;
if (maybe_rem) |rem| {
rem.* = r_all;
}
return q_all;
}
pub inline fn floatToInt(comptime I: type, a: anytype) I {
const Log2Int = math.Log2Int;
const Int = @import("std").meta.Int;
const F = @TypeOf(a);
const float_bits = @typeInfo(F).Float.bits;
const int_bits = @typeInfo(I).Int.bits;
const rep_t = Int(.unsigned, float_bits);
const sig_bits = math.floatMantissaBits(F);
const exp_bits = math.floatExponentBits(F);
const fractional_bits = floatFractionalBits(F);
// const implicit_bit = if (F != f80) (@as(rep_t, 1) << sig_bits) else 0;
const implicit_bit = @as(rep_t, 1) << sig_bits;
const max_exp = (1 << (exp_bits - 1));
const exp_bias = max_exp - 1;
const sig_mask = (@as(rep_t, 1) << sig_bits) - 1;
// Break a into sign, exponent, significand
const a_rep: rep_t = @bitCast(rep_t, a);
const negative = (a_rep >> (float_bits - 1)) != 0;
const exponent = @intCast(i32, (a_rep << 1) >> (sig_bits + 1)) - exp_bias;
const significand: rep_t = (a_rep & sig_mask) | implicit_bit;
// If the exponent is negative, the result rounds to zero.
if (exponent < 0) return 0;
// If the value is too large for the integer type, saturate.
switch (@typeInfo(I).Int.signedness) {
.unsigned => {
if (negative) return 0;
if (@intCast(c_uint, exponent) >= @minimum(int_bits, max_exp)) return math.maxInt(I);
},
.signed => if (@intCast(c_uint, exponent) >= @minimum(int_bits - 1, max_exp)) {
return if (negative) math.minInt(I) else math.maxInt(I);
},
}
// If 0 <= exponent < sig_bits, right shift to get the result.
// Otherwise, shift left.
var result: I = undefined;
if (exponent < fractional_bits) {
result = @intCast(I, significand >> @intCast(Log2Int(rep_t), fractional_bits - exponent));
} else {
result = @intCast(I, significand) << @intCast(Log2Int(I), exponent - fractional_bits);
}
if ((@typeInfo(I).Int.signedness == .signed) and negative)
return ~result +% 1;
return result;
}
/// Returns the number of fractional bits in the mantissa of floating point type T.
pub inline fn floatFractionalBits(comptime T: type) comptime_int {
comptime std.debug.assert(@typeInfo(T) == .Float);
// standard IEEE floats have an implicit 0.m or 1.m integer part
// f80 is special and has an explicitly stored bit in the MSB
// this function corresponds to `MANT_DIG - 1' from C
return switch (@typeInfo(T).Float.bits) {
16 => 10,
32 => 23,
64 => 52,
80 => 63,
128 => 112,
else => @compileError("unknown floating point type " ++ @typeName(T)),
};
}

View File

@ -44,6 +44,8 @@ pub const RocDec = extern struct {
return ret; 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 { pub fn fromStr(roc_str: RocStr) ?RocDec {
if (roc_str.isEmpty()) { if (roc_str.isEmpty()) {
return null; return null;
@ -79,7 +81,8 @@ pub const RocDec = extern struct {
var after_str_len = (length - 1) - pi; var after_str_len = (length - 1) - pi;
if (after_str_len > decimal_places) { 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; var diff_decimal_places = decimal_places - after_str_len;
@ -96,7 +99,8 @@ pub const RocDec = extern struct {
var result: i128 = undefined; var result: i128 = undefined;
var overflowed = @mulWithOverflow(i128, before, one_point_zero_i128, &result); var overflowed = @mulWithOverflow(i128, before, one_point_zero_i128, &result);
if (overflowed) { if (overflowed) {
@panic("TODO runtime exception for overflow!"); // TODO: runtime exception for overflow!
return null;
} }
before_val_i128 = result; before_val_i128 = result;
} }
@ -107,7 +111,8 @@ pub const RocDec = extern struct {
var result: i128 = undefined; var result: i128 = undefined;
var overflowed = @addWithOverflow(i128, before, after, &result); var overflowed = @addWithOverflow(i128, before, after, &result);
if (overflowed) { if (overflowed) {
@panic("TODO runtime exception for overflow!"); // TODO: runtime exception for overflow!
return null;
} }
break :blk .{ .num = result }; break :blk .{ .num = result };
} else { } else {
@ -184,7 +189,7 @@ pub const RocDec = extern struct {
position += 1; position += 1;
const trailing_zeros: u6 = count_trailing_zeros_base10(num); 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 // add just a single zero if all decimal digits are zero
str_bytes[position] = '0'; str_bytes[position] = '0';
position += 1; position += 1;
@ -851,6 +856,14 @@ test "fromStr: .123.1" {
try expectEqual(dec, null); 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" { test "toStr: 123.45" {
var dec: RocDec = .{ .num = 123450000000000000000 }; var dec: RocDec = .{ .num = 123450000000000000000 };
var res_roc_str = dec.toStr(); var res_roc_str = dec.toStr();

View File

@ -5,6 +5,10 @@ const utils = @import("utils.zig");
const expect = @import("expect.zig"); const expect = @import("expect.zig");
const panic_utils = @import("panic.zig"); const panic_utils = @import("panic.zig");
comptime {
_ = @import("compiler_rt.zig");
}
const ROC_BUILTINS = "roc_builtins"; const ROC_BUILTINS = "roc_builtins";
const NUM = "num"; const NUM = "num";
const STR = "str"; const STR = "str";
@ -81,8 +85,12 @@ comptime {
num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int."); num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int.");
num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil."); num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil.");
num.exportRoundF32(T, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f32."); num.exportRound(f32, T, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f32.");
num.exportRoundF64(T, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f64."); num.exportRound(f64, T, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f64.");
num.exportFloor(f32, T, ROC_BUILTINS ++ "." ++ NUM ++ ".floor_f32.");
num.exportFloor(f64, T, ROC_BUILTINS ++ "." ++ NUM ++ ".floor_f64.");
num.exportCeiling(f32, T, ROC_BUILTINS ++ "." ++ NUM ++ ".ceiling_f32.");
num.exportCeiling(f64, T, ROC_BUILTINS ++ "." ++ NUM ++ ".ceiling_f64.");
num.exportAddWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_with_overflow."); num.exportAddWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_with_overflow.");
num.exportAddOrPanic(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_or_panic."); num.exportAddOrPanic(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_or_panic.");
@ -122,6 +130,8 @@ comptime {
num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow."); num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow.");
num.exportLog(T, ROC_BUILTINS ++ "." ++ NUM ++ ".log."); num.exportLog(T, ROC_BUILTINS ++ "." ++ NUM ++ ".log.");
num.exportFAbs(T, ROC_BUILTINS ++ "." ++ NUM ++ ".fabs.");
num.exportSqrt(T, ROC_BUILTINS ++ "." ++ NUM ++ ".sqrt.");
num.exportAddWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_with_overflow."); num.exportAddWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_with_overflow.");
num.exportSubWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".sub_with_overflow."); num.exportSubWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".sub_with_overflow.");
@ -274,60 +284,3 @@ test "" {
testing.refAllDecls(@This()); testing.refAllDecls(@This());
} }
// For unclear reasons, sometimes this function is not linked in on some machines.
// Therefore we provide it as LLVM bitcode and mark it as externally linked during our LLVM codegen
//
// Taken from
// https://github.com/ziglang/zig/blob/85755c51d529e7d9b406c6bdf69ce0a0f33f3353/lib/std/special/compiler_rt/muloti4.zig
//
// Thank you Zig Contributors!
// Export it as weak incase it is already linked in by something else.
comptime {
if (builtin.target.os.tag != .windows) {
@export(__muloti4, .{ .name = "__muloti4", .linkage = .Weak });
}
}
fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 {
// @setRuntimeSafety(std.builtin.is_test);
const min = @bitCast(i128, @as(u128, 1 << (128 - 1)));
const max = ~min;
overflow.* = 0;
const r = a *% b;
if (a == min) {
if (b != 0 and b != 1) {
overflow.* = 1;
}
return r;
}
if (b == min) {
if (a != 0 and a != 1) {
overflow.* = 1;
}
return r;
}
const sa = a >> (128 - 1);
const abs_a = (a ^ sa) -% sa;
const sb = b >> (128 - 1);
const abs_b = (b ^ sb) -% sb;
if (abs_a < 2 or abs_b < 2) {
return r;
}
if (sa == sb) {
if (abs_a > @divTrunc(max, abs_b)) {
overflow.* = 1;
}
} else {
if (abs_a > @divTrunc(min, -abs_b)) {
overflow.* = 1;
}
}
return r;
}

View File

@ -152,7 +152,7 @@ pub fn exportAtan(comptime T: type, comptime name: []const u8) void {
pub fn exportSin(comptime T: type, comptime name: []const u8) void { pub fn exportSin(comptime T: type, comptime name: []const u8) void {
comptime var f = struct { comptime var f = struct {
fn func(input: T) callconv(.C) T { fn func(input: T) callconv(.C) T {
return @sin(input); return math.sin(input);
} }
}.func; }.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@ -161,7 +161,7 @@ pub fn exportSin(comptime T: type, comptime name: []const u8) void {
pub fn exportCos(comptime T: type, comptime name: []const u8) void { pub fn exportCos(comptime T: type, comptime name: []const u8) void {
comptime var f = struct { comptime var f = struct {
fn func(input: T) callconv(.C) T { fn func(input: T) callconv(.C) T {
return @cos(input); return math.cos(input);
} }
}.func; }.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@ -170,25 +170,52 @@ pub fn exportCos(comptime T: type, comptime name: []const u8) void {
pub fn exportLog(comptime T: type, comptime name: []const u8) void { pub fn exportLog(comptime T: type, comptime name: []const u8) void {
comptime var f = struct { comptime var f = struct {
fn func(input: T) callconv(.C) T { fn func(input: T) callconv(.C) T {
return @log(input); return math.ln(input);
} }
}.func; }.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
} }
pub fn exportRoundF32(comptime T: type, comptime name: []const u8) void { pub fn exportFAbs(comptime T: type, comptime name: []const u8) void {
comptime var f = struct { comptime var f = struct {
fn func(input: f32) callconv(.C) T { fn func(input: T) callconv(.C) T {
return @floatToInt(T, (@round(input))); return math.absFloat(input);
} }
}.func; }.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
} }
pub fn exportRoundF64(comptime T: type, comptime name: []const u8) void { pub fn exportSqrt(comptime T: type, comptime name: []const u8) void {
comptime var f = struct { comptime var f = struct {
fn func(input: f64) callconv(.C) T { fn func(input: T) callconv(.C) T {
return @floatToInt(T, (@round(input))); return math.sqrt(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportRound(comptime F: type, comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: F) callconv(.C) T {
return @floatToInt(T, (math.round(input)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportFloor(comptime F: type, comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: F) callconv(.C) T {
return @floatToInt(T, (math.floor(input)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportCeiling(comptime F: type, comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: F) callconv(.C) T {
return @floatToInt(T, (math.ceil(input)));
} }
}.func; }.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });

View File

@ -305,6 +305,10 @@ pub fn isUnique(
const isizes: [*]isize = @intToPtr([*]isize, masked_ptr); const isizes: [*]isize = @intToPtr([*]isize, masked_ptr);
if (DEBUG_INCDEC and builtin.target.cpu.arch != .wasm32) {
std.debug.print("| is unique {*}\n", .{&bytes[0]});
}
const refcount = (isizes - 1)[0]; const refcount = (isizes - 1)[0];
return refcount == REFCOUNT_ONE_ISIZE; return refcount == REFCOUNT_ONE_ISIZE;

View File

@ -95,7 +95,7 @@ Dict k v := {
# TODO: define Eq and Hash that are unordered. Only if value implements hash/eq? # TODO: define Eq and Hash that are unordered. Only if value implements hash/eq?
metadata : List I8, metadata : List I8,
dataIndices : List Nat, dataIndices : List Nat,
data : List (T k v), data : List (k, v),
size : Nat, size : Nat,
} | k implements Hash & Eq } | k implements Hash & Eq
implements [ implements [
@ -175,12 +175,12 @@ single = \k, v ->
## |> Dict.insert 2 "Two" ## |> Dict.insert 2 "Two"
## |> Dict.insert 3 "Three" ## |> Dict.insert 3 "Three"
## |> Dict.insert 4 "Four" ## |> 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 implements Hash & Eq fromList : List (k, v) -> Dict k v | k Hash & Eq
fromList = \data -> fromList = \data ->
# TODO: make this efficient. Should just set data and then set all indicies in the hashmap. # 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. ## Returns the number of values in the dictionary.
## ``` ## ```
@ -238,7 +238,7 @@ clear = \@Dict { metadata, dataIndices, data } ->
## ``` ## ```
walk : Dict k v, state, (state, k, v -> state) -> state | k implements Hash & Eq walk : Dict k v, state, (state, k, v -> state) -> state | k implements Hash & Eq
walk = \@Dict { data }, initialState, transform -> 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. ## Same as [Dict.walk], except you can stop walking early.
## ##
@ -270,7 +270,7 @@ walk = \@Dict { data }, initialState, transform ->
## ``` ## ```
walkUntil : Dict k v, state, (state, k, v -> [Continue state, Break state]) -> state | k implements Hash & Eq walkUntil : Dict k v, state, (state, k, v -> [Continue state, Break state]) -> state | k implements Hash & Eq
walkUntil = \@Dict { data }, initialState, transform -> 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 ## 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]. ## will return [Ok value], otherwise return [Err KeyNotFound].
@ -296,7 +296,7 @@ get = \@Dict { metadata, dataIndices, data }, key ->
when findIndexHelper metadata dataIndices data h2Key key probe 0 is when findIndexHelper metadata dataIndices data h2Key key probe 0 is
Ok index -> Ok index ->
dataIndex = listGetUnsafe dataIndices index dataIndex = listGetUnsafe dataIndices index
(T _ v) = listGetUnsafe data dataIndex (_, v) = listGetUnsafe data dataIndex
Ok v Ok v
@ -353,7 +353,7 @@ insert = \@Dict { metadata, dataIndices, data, size }, key, value ->
@Dict { @Dict {
metadata, metadata,
dataIndices, dataIndices,
data: List.set data dataIndex (T key value), data: List.set data dataIndex (key, value),
size, size,
} }
@ -447,9 +447,9 @@ update = \dict, key, alter ->
## |> Dict.insert 3 "Three" ## |> Dict.insert 3 "Three"
## |> Dict.insert 4 "Four" ## |> Dict.insert 4 "Four"
## |> Dict.toList ## |> 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 implements Hash & Eq toList : Dict k v -> List (k, v) | k implements Hash & Eq
toList = \@Dict { data } -> toList = \@Dict { data } ->
data data
@ -466,7 +466,7 @@ toList = \@Dict { data } ->
## ``` ## ```
keys : Dict k v -> List k | k implements Hash & Eq keys : Dict k v -> List k | k implements Hash & Eq
keys = \@Dict { data } -> keys = \@Dict { data } ->
List.map data (\T k _ -> k) List.map data (\(k, _) -> k)
## Returns the values of a dictionary as a [List]. ## Returns the values of a dictionary as a [List].
## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead. ## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead.
@ -481,7 +481,7 @@ keys = \@Dict { data } ->
## ``` ## ```
values : Dict k v -> List v | k implements Hash & Eq values : Dict k v -> List v | k implements Hash & Eq
values = \@Dict { data } -> 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)) ## 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 ## of all the key-value pairs. This means that all the key-value pairs in
@ -567,7 +567,7 @@ removeAll = \xs, ys ->
swapAndUpdateDataIndex : Dict k v, Nat, Nat -> Dict k v | k implements Hash & Eq swapAndUpdateDataIndex : Dict k v, Nat, Nat -> Dict k v | k implements Hash & Eq
swapAndUpdateDataIndex = \@Dict { metadata, dataIndices, data, size }, removedIndex, lastIndex -> swapAndUpdateDataIndex = \@Dict { metadata, dataIndices, data, size }, removedIndex, lastIndex ->
(T key _) = listGetUnsafe data lastIndex (key, _) = listGetUnsafe data lastIndex
hashKey = hashKey =
createLowLevelHasher PseudoRandSeed createLowLevelHasher PseudoRandSeed
|> Hash.hash key |> Hash.hash key
@ -603,7 +603,7 @@ insertNotFoundHelper = \@Dict { metadata, dataIndices, data, size }, key, value,
probe = newProbe h1Key (div8 (List.len metadata)) probe = newProbe h1Key (div8 (List.len metadata))
index = nextEmptyOrDeletedHelper metadata probe 0 index = nextEmptyOrDeletedHelper metadata probe 0
dataIndex = List.len data dataIndex = List.len data
nextData = List.append data (T key value) nextData = List.append data (key, value)
@Dict { @Dict {
metadata: List.set metadata index h2Key, metadata: List.set metadata index h2Key,
@ -629,7 +629,7 @@ nextEmptyOrDeletedHelper = \metadata, probe, offset ->
# TODO: investigate if this needs to be split into more specific helper functions. # 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. # 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 implements Hash & Eq findIndexHelper : List I8, List Nat, List (k, v), I8, k, Probe, Nat -> Result Nat [NotFound] | k implements Hash & Eq
findIndexHelper = \metadata, dataIndices, data, h2Key, key, probe, offset -> findIndexHelper = \metadata, dataIndices, data, h2Key, key, probe, offset ->
# For finding a value, we must search past all deleted element tombstones. # For finding a value, we must search past all deleted element tombstones.
index = Num.addWrap (mul8 probe.slotIndex) offset index = Num.addWrap (mul8 probe.slotIndex) offset
@ -642,7 +642,7 @@ findIndexHelper = \metadata, dataIndices, data, h2Key, key, probe, offset ->
else if md == h2Key then else if md == h2Key then
# Potentially matching slot, check if the key is a match. # Potentially matching slot, check if the key is a match.
dataIndex = listGetUnsafe dataIndices index dataIndex = listGetUnsafe dataIndices index
(T k _) = listGetUnsafe data dataIndex (k, _) = listGetUnsafe data dataIndex
if k == key then if k == key then
# We have a match, return its index. # We have a match, return its index.
@ -687,7 +687,7 @@ rehash = \@Dict { metadata, dataIndices, data, size } ->
rehashHelper newDict metadata dataIndices data 0 rehashHelper newDict metadata dataIndices data 0
rehashHelper : Dict k v, List I8, List Nat, List (T k v), Nat -> Dict k v | k implements Hash & Eq rehashHelper : Dict k v, List I8, List Nat, List (k, v), Nat -> Dict k v | k implements Hash & implements
rehashHelper = \dict, oldMetadata, oldDataIndices, oldData, index -> rehashHelper = \dict, oldMetadata, oldDataIndices, oldData, index ->
when List.get oldMetadata index is when List.get oldMetadata index is
Ok md -> Ok md ->
@ -695,7 +695,7 @@ rehashHelper = \dict, oldMetadata, oldDataIndices, oldData, index ->
if md >= 0 then if md >= 0 then
# We have an actual element here # We have an actual element here
dataIndex = listGetUnsafe oldDataIndices index dataIndex = listGetUnsafe oldDataIndices index
(T k _) = listGetUnsafe oldData dataIndex (k, _) = listGetUnsafe oldData dataIndex
insertForRehash dict k dataIndex insertForRehash dict k dataIndex
else else
@ -731,8 +731,6 @@ emptySlot = -128
deletedSlot : I8 deletedSlot : I8
deletedSlot = -2 deletedSlot = -2
T k v : [T k v]
# Capacity must be a power of 2. # Capacity must be a power of 2.
# We still will use slots of 8 even though this version has no true slots. # We still will use slots of 8 even though this version has no true slots.
# We just move an element at a time. # We just move an element at a time.
@ -869,7 +867,7 @@ expect
expect expect
dict = dict =
fromList [T 1u8 1u8, T 2 2, T 3 3] fromList [(1u8, 1u8), (2, 2), (3, 3)]
|> remove 1 |> remove 1
|> remove 3 |> remove 3
@ -877,7 +875,7 @@ expect
expect expect
list = list =
fromList [T 1u8 1u8, T 2u8 2u8, T 3 3] fromList [(1u8, 1u8), (2u8, 2u8), (3, 3)]
|> remove 1 |> remove 1
|> insert 0 0 |> insert 0 0
|> remove 3 |> remove 3

View File

@ -989,19 +989,11 @@ pow : Frac a, Frac a -> Frac a
## This process is known as [exponentiation by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring). ## This process is known as [exponentiation by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring).
## ##
## For a [Frac] alternative to this function, which supports negative exponents, ## For a [Frac] alternative to this function, which supports negative exponents,
## see #Num.exp. ## see #Num.pow.
## ```
## Num.exp 5 0
## ##
## Num.exp 5 1 ## ## Warning
## ##
## Num.exp 5 2 ## It is very easy for this function to produce an answer
##
## Num.exp 5 6
## ```
## ## Performance Details
##
## Be careful! It is very easy for this function to produce an answer
## so large it causes an overflow. ## so large it causes an overflow.
powInt : Int a, Int a -> Int a powInt : Int a, Int a -> Int a

View File

@ -267,9 +267,15 @@ pub const NUM_IS_INFINITE: IntrinsicName = float_intrinsic!("roc_builtins.num.is
pub const NUM_IS_FINITE: IntrinsicName = float_intrinsic!("roc_builtins.num.is_finite"); pub const NUM_IS_FINITE: IntrinsicName = float_intrinsic!("roc_builtins.num.is_finite");
pub const NUM_LOG: IntrinsicName = float_intrinsic!("roc_builtins.num.log"); pub const NUM_LOG: IntrinsicName = float_intrinsic!("roc_builtins.num.log");
pub const NUM_POW: IntrinsicName = float_intrinsic!("roc_builtins.num.pow"); pub const NUM_POW: IntrinsicName = float_intrinsic!("roc_builtins.num.pow");
pub const NUM_FABS: IntrinsicName = float_intrinsic!("roc_builtins.num.fabs");
pub const NUM_SQRT: IntrinsicName = float_intrinsic!("roc_builtins.num.sqrt");
pub const NUM_POW_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.pow_int"); pub const NUM_POW_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.pow_int");
pub const NUM_DIV_CEIL: IntrinsicName = int_intrinsic!("roc_builtins.num.div_ceil"); pub const NUM_DIV_CEIL: IntrinsicName = int_intrinsic!("roc_builtins.num.div_ceil");
pub const NUM_CEILING_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.ceiling_f32");
pub const NUM_CEILING_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.ceiling_f64");
pub const NUM_FLOOR_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.floor_f32");
pub const NUM_FLOOR_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.floor_f64");
pub const NUM_ROUND_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f32"); pub const NUM_ROUND_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f32");
pub const NUM_ROUND_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f64"); pub const NUM_ROUND_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f64");

View File

@ -458,6 +458,11 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg, AArch64Assembler> for AArch64C
} }
impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler { impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
#[inline(always)]
fn base_pointer() -> AArch64GeneralReg {
AArch64GeneralReg::FP
}
#[inline(always)] #[inline(always)]
fn abs_reg64_reg64(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, src: AArch64GeneralReg) { fn abs_reg64_reg64(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, src: AArch64GeneralReg) {
cmp_reg64_imm12(buf, src, 0); cmp_reg64_imm12(buf, src, 0);
@ -1065,7 +1070,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
} }
#[inline(always)] #[inline(always)]
fn neq_reg64_reg64_reg64( fn neq_reg_reg_reg(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
_register_width: RegisterWidth, _register_width: RegisterWidth,
dst: AArch64GeneralReg, dst: AArch64GeneralReg,

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
single_register_floats, single_register_int_builtins, single_register_integers, Backend, Env, pointer_layouts, single_register_floats, single_register_int_builtins,
Relocation, single_register_integers, Backend, Env, Relocation,
}; };
use bumpalo::collections::{CollectIn, Vec}; use bumpalo::collections::{CollectIn, Vec};
use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
@ -150,6 +150,8 @@ pub enum CompareOperation {
/// Generally, I prefer explicit sources, as opposed to dst being one of the sources. Ex: `x = x + y` would be `add x, x, y` instead of `add x, y`. /// Generally, I prefer explicit sources, as opposed to dst being one of the sources. Ex: `x = x + y` would be `add x, x, y` instead of `add x, y`.
/// dst should always come before sources. /// dst should always come before sources.
pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy { pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
fn base_pointer() -> GeneralReg;
fn abs_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg); fn abs_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
fn abs_freg64_freg64( fn abs_freg64_freg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
@ -517,7 +519,7 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
Self::eq_reg_reg_reg(buf, RegisterWidth::W64, dst, src1, src2) Self::eq_reg_reg_reg(buf, RegisterWidth::W64, dst, src1, src2)
} }
fn neq_reg64_reg64_reg64( fn neq_reg_reg_reg(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
register_width: RegisterWidth, register_width: RegisterWidth,
dst: GeneralReg, dst: GeneralReg,
@ -904,17 +906,22 @@ impl<
ASM::mov_base32_reg64(&mut self.buf, offset, CC::GENERAL_RETURN_REGS[0]); ASM::mov_base32_reg64(&mut self.buf, offset, CC::GENERAL_RETURN_REGS[0]);
ASM::mov_base32_reg64(&mut self.buf, offset + 8, CC::GENERAL_RETURN_REGS[1]); ASM::mov_base32_reg64(&mut self.buf, offset + 8, CC::GENERAL_RETURN_REGS[1]);
} }
pointer_layouts!() => {
other => {
//
match other {
LayoutRepr::Boxed(_) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
ASM::mov_reg64_reg64(&mut self.buf, dst_reg, CC::GENERAL_RETURN_REGS[0]); ASM::mov_reg64_reg64(&mut self.buf, dst_reg, CC::GENERAL_RETURN_REGS[0]);
} }
LayoutRepr::LambdaSet(lambda_set) => { LayoutRepr::LambdaSet(lambda_set) => {
self.move_return_value(dst, &lambda_set.runtime_representation()) self.move_return_value(dst, &lambda_set.runtime_representation())
} }
LayoutRepr::Union(UnionLayout::NonRecursive(_)) => {
CC::load_returned_complex_symbol(
&mut self.buf,
&mut self.storage_manager,
self.layout_interner,
dst,
ret_layout,
);
}
_ => { _ => {
CC::load_returned_complex_symbol( CC::load_returned_complex_symbol(
&mut self.buf, &mut self.buf,
@ -926,8 +933,6 @@ impl<
} }
} }
} }
}
}
fn build_switch( fn build_switch(
&mut self, &mut self,
@ -1558,7 +1563,7 @@ impl<
let src2_reg = self let src2_reg = self
.storage_manager .storage_manager
.load_to_general_reg(&mut self.buf, src2); .load_to_general_reg(&mut self.buf, src2);
ASM::neq_reg64_reg64_reg64(&mut self.buf, width, dst_reg, src1_reg, src2_reg); ASM::neq_reg_reg_reg(&mut self.buf, width, dst_reg, src1_reg, src2_reg);
} }
LayoutRepr::STR => { LayoutRepr::STR => {
self.build_fn_call( self.build_fn_call(
@ -1576,7 +1581,7 @@ impl<
let width = RegisterWidth::W8; // we're comparing booleans let width = RegisterWidth::W8; // we're comparing booleans
let dst_reg = self.storage_manager.load_to_general_reg(&mut self.buf, dst); let dst_reg = self.storage_manager.load_to_general_reg(&mut self.buf, dst);
ASM::neq_reg64_reg64_reg64(&mut self.buf, width, dst_reg, dst_reg, tmp_reg); ASM::neq_reg_reg_reg(&mut self.buf, width, dst_reg, dst_reg, tmp_reg);
} }
x => todo!("NumNeq: layout, {:?}", x), x => todo!("NumNeq: layout, {:?}", x),
} }
@ -1714,25 +1719,13 @@ impl<
ASM::xor_reg64_reg64_reg64(buf, dst_reg, dst_reg, dst_reg); // zero out dst reg ASM::xor_reg64_reg64_reg64(buf, dst_reg, dst_reg, dst_reg); // zero out dst reg
ASM::mov_reg32_freg32(buf, dst_reg, src_reg); ASM::mov_reg32_freg32(buf, dst_reg, src_reg);
ASM::and_reg64_reg64_reg64(buf, dst_reg, dst_reg, mask_reg); ASM::and_reg64_reg64_reg64(buf, dst_reg, dst_reg, mask_reg);
ASM::neq_reg64_reg64_reg64( ASM::neq_reg_reg_reg(buf, RegisterWidth::W32, dst_reg, dst_reg, mask_reg);
buf,
RegisterWidth::W32,
dst_reg,
dst_reg,
mask_reg,
);
} }
Layout::F64 => { Layout::F64 => {
ASM::mov_reg64_imm64(buf, mask_reg, 0x7ff0_0000_0000_0000); ASM::mov_reg64_imm64(buf, mask_reg, 0x7ff0_0000_0000_0000);
ASM::mov_reg64_freg64(buf, dst_reg, src_reg); ASM::mov_reg64_freg64(buf, dst_reg, src_reg);
ASM::and_reg64_reg64_reg64(buf, dst_reg, dst_reg, mask_reg); ASM::and_reg64_reg64_reg64(buf, dst_reg, dst_reg, mask_reg);
ASM::neq_reg64_reg64_reg64( ASM::neq_reg_reg_reg(buf, RegisterWidth::W64, dst_reg, dst_reg, mask_reg);
buf,
RegisterWidth::W64,
dst_reg,
dst_reg,
mask_reg,
);
} }
_ => unreachable!(), _ => unreachable!(),
} }
@ -2213,6 +2206,7 @@ impl<
storage_manager, storage_manager,
self.layout_interner, self.layout_interner,
element_ptr, element_ptr,
0,
*ret_layout, *ret_layout,
*dst, *dst,
); );
@ -2600,7 +2594,7 @@ impl<
union_layout: &UnionLayout<'a>, union_layout: &UnionLayout<'a>,
) { ) {
match union_layout { match union_layout {
UnionLayout::NonRecursive(tag_layouts) | UnionLayout::Recursive(tag_layouts) => { UnionLayout::NonRecursive(tag_layouts) => {
self.storage_manager.load_field_at_index( self.storage_manager.load_field_at_index(
self.layout_interner, self.layout_interner,
sym, sym,
@ -2609,6 +2603,77 @@ impl<
tag_layouts[tag_id as usize], tag_layouts[tag_id as usize],
); );
} }
UnionLayout::NullableUnwrapped {
nullable_id,
other_fields,
} => {
debug_assert_ne!(tag_id, *nullable_id as TagIdIntType);
let element_layout = other_fields[index as usize];
let ptr_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, structure);
let mut offset = 0;
for field in &other_fields[..index as usize] {
offset += self.layout_interner.stack_size(*field);
}
Self::ptr_read(
&mut self.buf,
&mut self.storage_manager,
self.layout_interner,
ptr_reg,
offset as i32,
element_layout,
*sym,
);
}
UnionLayout::NullableWrapped {
nullable_id,
other_tags,
} => {
debug_assert_ne!(tag_id, *nullable_id as TagIdIntType);
let other_fields = if tag_id < *nullable_id {
other_tags[tag_id as usize]
} else {
other_tags[tag_id as usize - 1]
};
let element_layout = other_fields[index as usize];
let ptr_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, structure);
let mask_symbol = self.debug_symbol("tag_id_mask");
let mask_reg = self
.storage_manager
.claim_general_reg(&mut self.buf, &mask_symbol);
ASM::mov_reg64_imm64(&mut self.buf, mask_reg, (!0b111) as _);
// mask out the tag id bits
ASM::and_reg64_reg64_reg64(&mut self.buf, ptr_reg, ptr_reg, mask_reg);
let mut offset = 0;
for field in &other_fields[..index as usize] {
offset += self.layout_interner.stack_size(*field);
}
Self::ptr_read(
&mut self.buf,
&mut self.storage_manager,
self.layout_interner,
ptr_reg,
offset as i32,
element_layout,
*sym,
);
}
_ => { _ => {
let union_in_layout = self let union_in_layout = self
.layout_interner .layout_interner
@ -2653,11 +2718,17 @@ impl<
} }
// box is just a pointer on the stack // box is just a pointer on the stack
let base_offset = self.storage_manager.claim_stack_area(&sym, 8); let base_offset = self.storage_manager.claim_pointer_stack_area(sym);
ASM::mov_base32_reg64(&mut self.buf, base_offset, ptr_reg); ASM::mov_base32_reg64(&mut self.buf, base_offset, ptr_reg);
} }
fn expr_box(&mut self, sym: Symbol, value: Symbol, element_layout: InLayout<'a>) { fn expr_box(
&mut self,
sym: Symbol,
value: Symbol,
element_layout: InLayout<'a>,
reuse: Option<Symbol>,
) {
let element_width_symbol = Symbol::DEV_TMP; let element_width_symbol = Symbol::DEV_TMP;
self.load_layout_stack_size(element_layout, element_width_symbol); self.load_layout_stack_size(element_layout, element_width_symbol);
@ -2665,18 +2736,27 @@ impl<
let element_alignment_symbol = Symbol::DEV_TMP2; let element_alignment_symbol = Symbol::DEV_TMP2;
self.load_layout_alignment(Layout::U32, element_alignment_symbol); self.load_layout_alignment(Layout::U32, element_alignment_symbol);
let allocation = self.debug_symbol("allocation");
match reuse {
None => {
self.allocate_with_refcount( self.allocate_with_refcount(
Symbol::DEV_TMP3, allocation,
element_width_symbol, element_width_symbol,
element_alignment_symbol, element_alignment_symbol,
); );
}
Some(reuse) => {
self.allocate_with_refcount_if_null(allocation, reuse, element_layout);
}
};
self.free_symbol(&element_width_symbol); self.free_symbol(&element_width_symbol);
self.free_symbol(&element_alignment_symbol); self.free_symbol(&element_alignment_symbol);
self.build_ptr_write(sym, Symbol::DEV_TMP3, value, element_layout); self.build_ptr_write(sym, allocation, value, element_layout);
self.free_symbol(&Symbol::DEV_TMP3); self.free_symbol(&allocation);
} }
fn expr_unbox(&mut self, dst: Symbol, ptr: Symbol, element_layout: InLayout<'a>) { fn expr_unbox(&mut self, dst: Symbol, ptr: Symbol, element_layout: InLayout<'a>) {
@ -2684,25 +2764,136 @@ impl<
.storage_manager .storage_manager
.load_to_general_reg(&mut self.buf, &ptr); .load_to_general_reg(&mut self.buf, &ptr);
let offset = 0;
Self::ptr_read( Self::ptr_read(
&mut self.buf, &mut self.buf,
&mut self.storage_manager, &mut self.storage_manager,
self.layout_interner, self.layout_interner,
ptr_reg, ptr_reg,
offset,
element_layout, element_layout,
dst, dst,
); );
} }
fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>) { fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>) {
self.storage_manager.load_union_tag_id( let layout_interner: &mut STLayoutInterner<'a> = self.layout_interner;
self.layout_interner, let _buf: &mut Vec<'a, u8> = &mut self.buf;
match union_layout {
UnionLayout::NonRecursive(tags) => {
self.storage_manager.load_union_tag_id_nonrecursive(
layout_interner,
&mut self.buf, &mut self.buf,
sym, sym,
structure, structure,
union_layout, tags,
); );
} }
UnionLayout::NullableUnwrapped { nullable_id, .. } => {
// simple is_null check on the pointer
let tmp = Symbol::DEV_TMP5;
let reg = self.storage_manager.claim_general_reg(&mut self.buf, &tmp);
ASM::mov_reg64_imm64(&mut self.buf, reg, 0);
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, sym);
let src1_reg = reg;
let src2_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, structure);
match *nullable_id {
true => {
ASM::eq_reg_reg_reg(
&mut self.buf,
RegisterWidth::W64,
dst_reg,
src1_reg,
src2_reg,
);
}
false => {
ASM::neq_reg_reg_reg(
&mut self.buf,
RegisterWidth::W64,
dst_reg,
src1_reg,
src2_reg,
);
}
}
self.free_symbol(&tmp);
}
UnionLayout::NullableWrapped {
nullable_id,
other_tags,
} => {
let number_of_tags = other_tags.len() + 1;
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, sym);
// build a table to index into with the value that we find
let nullable_id = *nullable_id as usize;
let it = std::iter::once(nullable_id)
.chain(0..nullable_id)
.chain(nullable_id + 1..number_of_tags);
let table = self.debug_symbol("tag_id_table");
let table_offset = self
.storage_manager
.claim_stack_area(&table, (number_of_tags * 2) as _);
let mut offset = table_offset;
for i in it {
ASM::mov_reg64_imm64(&mut self.buf, dst_reg, i as i64);
ASM::mov_base32_reg16(&mut self.buf, offset, dst_reg);
offset += 2;
}
self.free_symbol(&table);
// mask the 3 lowest bits
let tmp = Symbol::DEV_TMP5;
let reg = self.storage_manager.claim_general_reg(&mut self.buf, &tmp);
ASM::mov_reg64_imm64(&mut self.buf, reg, 0b111);
let src1_reg = reg;
let src2_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, structure);
ASM::and_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
// we're indexing into an array of u16, so double this index
// also the stack grows down, so negate the index
ASM::mov_reg64_imm64(&mut self.buf, reg, 2);
ASM::umul_reg64_reg64_reg64(
&mut self.buf,
&mut self.storage_manager,
dst_reg,
dst_reg,
reg,
);
// index into the table
let base_pointer = ASM::base_pointer();
ASM::add_reg64_reg64_reg64(&mut self.buf, dst_reg, dst_reg, base_pointer);
// load the 16-bit value at the pointer
ASM::mov_reg16_mem16_offset32(&mut self.buf, dst_reg, dst_reg, table_offset);
// keep only the lowest 16 bits
ASM::mov_reg64_imm64(&mut self.buf, reg, 0xFFFF);
ASM::and_reg64_reg64_reg64(&mut self.buf, dst_reg, dst_reg, reg);
self.free_symbol(&tmp);
}
x => todo!("getting tag id of union with layout ({:?})", x),
};
}
fn tag( fn tag(
&mut self, &mut self,
@ -2710,15 +2901,144 @@ impl<
fields: &'a [Symbol], fields: &'a [Symbol],
union_layout: &UnionLayout<'a>, union_layout: &UnionLayout<'a>,
tag_id: TagIdIntType, tag_id: TagIdIntType,
reuse: Option<Symbol>,
) { ) {
self.storage_manager.create_union( let target_info = self.storage_manager.target_info;
let layout_interner: &mut STLayoutInterner<'a> = self.layout_interner;
let buf: &mut Vec<'a, u8> = &mut self.buf;
let (data_size, data_alignment) =
union_layout.data_size_and_alignment(layout_interner, target_info);
match union_layout {
UnionLayout::NonRecursive(field_layouts) => {
let id_offset = data_size - data_alignment;
let base_offset = self.storage_manager.claim_stack_area(sym, data_size);
let mut current_offset = base_offset;
let it = fields.iter().zip(field_layouts[tag_id as usize].iter());
for (field, field_layout) in it {
self.storage_manager.copy_symbol_to_stack_offset(
layout_interner,
buf,
current_offset,
field,
field_layout,
);
let field_size = layout_interner.stack_size(*field_layout);
current_offset += field_size as i32;
}
// put the tag id in the right place
self.storage_manager
.with_tmp_general_reg(buf, |_symbol_storage, buf, reg| {
ASM::mov_reg64_imm64(buf, reg, tag_id as i64);
let total_id_offset = base_offset as u32 + id_offset;
debug_assert!(total_id_offset % data_alignment == 0);
// pick the right instruction based on the alignment of the tag id
if field_layouts.len() <= u8::MAX as _ {
ASM::mov_base32_reg8(buf, total_id_offset as i32, reg);
} else {
ASM::mov_base32_reg16(buf, total_id_offset as i32, reg);
}
});
}
UnionLayout::NullableUnwrapped {
nullable_id,
other_fields,
} => {
if tag_id == *nullable_id as TagIdIntType {
// it's just a null pointer
self.load_literal_i64(sym, 0);
} else {
// construct the payload as a struct on the stack
let temp_sym = Symbol::DEV_TMP5;
let layout = self
.layout_interner
.insert_no_semantic(LayoutRepr::Struct(other_fields));
self.load_literal_symbols(fields);
self.storage_manager.create_struct(
self.layout_interner, self.layout_interner,
&mut self.buf, &mut self.buf,
sym, &temp_sym,
union_layout, &layout,
fields, fields,
tag_id, );
)
// now effectively box this struct
self.expr_box(*sym, temp_sym, layout, reuse);
self.free_symbol(&temp_sym);
}
}
UnionLayout::NullableWrapped {
nullable_id,
other_tags,
} => {
let nullable_id = *nullable_id;
if tag_id == nullable_id as TagIdIntType {
// it's just a null pointer
self.load_literal_i64(sym, 0);
} else {
let other_fields = if tag_id < nullable_id {
other_tags[tag_id as usize]
} else {
other_tags[tag_id as usize - 1]
};
// construct the payload as a struct on the stack
let temp_sym = Symbol::DEV_TMP5;
let layout = self
.layout_interner
.insert_no_semantic(LayoutRepr::Struct(other_fields));
self.load_literal_symbols(fields);
self.storage_manager.create_struct(
self.layout_interner,
&mut self.buf,
&temp_sym,
&layout,
fields,
);
// now effectively box this struct
let untagged_pointer_symbol = self.debug_symbol("untagged_pointer");
self.expr_box(untagged_pointer_symbol, temp_sym, layout, reuse);
self.free_symbol(&temp_sym);
let tag_id_symbol = self.debug_symbol("tag_id");
// index zero is taken up by the nullable tag, so any tags before it in the
// ordering need to be incremented by one
let pointer_tag = if tag_id < nullable_id {
tag_id + 1
} else {
tag_id
};
// finally, we need to tag the pointer
debug_assert!(tag_id < 8);
self.load_literal_i64(&tag_id_symbol, pointer_tag as _);
self.build_int_bitwise_or(
sym,
&untagged_pointer_symbol,
&tag_id_symbol,
IntWidth::U64,
);
self.free_symbol(&untagged_pointer_symbol);
self.free_symbol(&tag_id_symbol);
}
}
x => todo!("creating unions with layout: {:?}", x),
}
} }
fn load_literal(&mut self, sym: &Symbol, layout: &InLayout<'a>, lit: &Literal<'a>) { fn load_literal(&mut self, sym: &Symbol, layout: &InLayout<'a>, lit: &Literal<'a>) {
@ -2860,7 +3180,7 @@ impl<
if self.storage_manager.is_stored_primitive(sym) { if self.storage_manager.is_stored_primitive(sym) {
// Just load it to the correct type of reg as a stand alone value. // Just load it to the correct type of reg as a stand alone value.
match repr { match repr {
single_register_integers!() => { single_register_integers!() | pointer_layouts!() => {
self.storage_manager.load_to_specified_general_reg( self.storage_manager.load_to_specified_general_reg(
&mut self.buf, &mut self.buf,
sym, sym,
@ -2874,22 +3194,14 @@ impl<
CC::FLOAT_RETURN_REGS[0], CC::FLOAT_RETURN_REGS[0],
); );
} }
other => match other {
LayoutRepr::Boxed(_) => {
// treat like a 64-bit integer
self.storage_manager.load_to_specified_general_reg(
&mut self.buf,
sym,
CC::GENERAL_RETURN_REGS[0],
);
}
LayoutRepr::LambdaSet(lambda_set) => { LayoutRepr::LambdaSet(lambda_set) => {
self.return_symbol(sym, &lambda_set.runtime_representation()) self.return_symbol(sym, &lambda_set.runtime_representation())
} }
_ => { LayoutRepr::Union(UnionLayout::NonRecursive(_))
| LayoutRepr::Builtin(_)
| LayoutRepr::Struct(_) => {
internal_error!("All primitive values should fit in a single register"); internal_error!("All primitive values should fit in a single register");
} }
},
} }
} else { } else {
CC::return_complex_symbol( CC::return_complex_symbol(
@ -3297,22 +3609,78 @@ impl<
); );
} }
fn allocate_with_refcount_if_null(&mut self, dst: Symbol, src: Symbol, layout: InLayout) {
let src_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, &src);
// dummy mov that is supposed to move src into dst, and is filled in later because don't know yet which
// register the other branch will pick for dst. We must pass two different registers here
// otherwise the whole instruction is skipped!
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, &dst);
let mov_start_index = self.buf.len();
ASM::mov_reg64_reg64(&mut self.buf, dst_reg, src_reg);
// jump to where the pointer is valid, because it is already valid if non-zero
let jmp_start_index = self.buf.len();
let jmp_end_index = ASM::jne_reg64_imm64_imm32(&mut self.buf, src_reg, 0x0, 0);
self.free_symbol(&dst);
// so, the pointer is NULL, allocate
let data_bytes = self.debug_symbol("data_bytes");
self.load_layout_stack_size(layout, data_bytes);
let element_alignment = self.debug_symbol("element_alignment");
self.load_layout_alignment(layout, element_alignment);
self.allocate_with_refcount(dst, data_bytes, element_alignment);
self.free_symbol(&data_bytes);
self.free_symbol(&element_alignment);
let mut tmp = bumpalo::vec![in self.env.arena];
// update the jump
let destination_index = self.buf.len();
ASM::jne_reg64_imm64_imm32(
&mut tmp,
src_reg,
0x0,
(destination_index - jmp_end_index) as i32,
);
self.buf[jmp_start_index..][..tmp.len()].copy_from_slice(tmp.as_slice());
// figure out what register was actually used
let dst_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, &dst);
tmp.clear();
ASM::mov_reg64_reg64(&mut tmp, dst_reg, src_reg);
self.buf[mov_start_index..][..tmp.len()].copy_from_slice(tmp.as_slice());
}
fn unbox_str_or_list( fn unbox_str_or_list(
buf: &mut Vec<'a, u8>, buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>, storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>,
dst: Symbol, dst: Symbol,
ptr_reg: GeneralReg, ptr_reg: GeneralReg,
tmp_reg: GeneralReg, tmp_reg: GeneralReg,
offset: i32,
) { ) {
let base_offset = storage_manager.claim_stack_area(&dst, 24); let base_offset = storage_manager.claim_stack_area(&dst, 24);
ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, 0); ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, offset);
ASM::mov_base32_reg64(buf, base_offset, tmp_reg); ASM::mov_base32_reg64(buf, base_offset, tmp_reg);
ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, 8); ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, offset + 8);
ASM::mov_base32_reg64(buf, base_offset + 8, tmp_reg); ASM::mov_base32_reg64(buf, base_offset + 8, tmp_reg);
ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, 16); ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, offset + 16);
ASM::mov_base32_reg64(buf, base_offset + 16, tmp_reg); ASM::mov_base32_reg64(buf, base_offset + 16, tmp_reg);
} }
@ -3323,6 +3691,7 @@ impl<
stack_size: u32, stack_size: u32,
ptr_reg: GeneralReg, ptr_reg: GeneralReg,
tmp_reg: GeneralReg, tmp_reg: GeneralReg,
read_offset: i32,
) { ) {
let mut copied = 0; let mut copied = 0;
let size = stack_size as i32; let size = stack_size as i32;
@ -3336,7 +3705,7 @@ impl<
if size - copied >= 8 { if size - copied >= 8 {
for _ in (0..(size - copied)).step_by(8) { for _ in (0..(size - copied)).step_by(8) {
ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, copied); ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, read_offset + copied);
ASM::mov_base32_reg64(buf, base_offset + copied, tmp_reg); ASM::mov_base32_reg64(buf, base_offset + copied, tmp_reg);
copied += 8; copied += 8;
@ -3345,7 +3714,7 @@ impl<
if size - copied >= 4 { if size - copied >= 4 {
for _ in (0..(size - copied)).step_by(4) { for _ in (0..(size - copied)).step_by(4) {
ASM::mov_reg32_mem32_offset32(buf, tmp_reg, ptr_reg, copied); ASM::mov_reg32_mem32_offset32(buf, tmp_reg, ptr_reg, read_offset + copied);
ASM::mov_base32_reg32(buf, base_offset + copied, tmp_reg); ASM::mov_base32_reg32(buf, base_offset + copied, tmp_reg);
copied += 4; copied += 4;
@ -3354,7 +3723,7 @@ impl<
if size - copied >= 2 { if size - copied >= 2 {
for _ in (0..(size - copied)).step_by(2) { for _ in (0..(size - copied)).step_by(2) {
ASM::mov_reg16_mem16_offset32(buf, tmp_reg, ptr_reg, copied); ASM::mov_reg16_mem16_offset32(buf, tmp_reg, ptr_reg, read_offset + copied);
ASM::mov_base32_reg16(buf, base_offset + copied, tmp_reg); ASM::mov_base32_reg16(buf, base_offset + copied, tmp_reg);
copied += 2; copied += 2;
@ -3363,7 +3732,7 @@ impl<
if size - copied >= 1 { if size - copied >= 1 {
for _ in (0..(size - copied)).step_by(1) { for _ in (0..(size - copied)).step_by(1) {
ASM::mov_reg8_mem8_offset32(buf, tmp_reg, ptr_reg, copied); ASM::mov_reg8_mem8_offset32(buf, tmp_reg, ptr_reg, read_offset + copied);
ASM::mov_base32_reg8(buf, base_offset + copied, tmp_reg); ASM::mov_base32_reg8(buf, base_offset + copied, tmp_reg);
copied += 1; copied += 1;
@ -3376,6 +3745,7 @@ impl<
storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>, storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>,
layout_interner: &STLayoutInterner<'a>, layout_interner: &STLayoutInterner<'a>,
ptr_reg: GeneralReg, ptr_reg: GeneralReg,
offset: i32,
element_in_layout: InLayout<'a>, element_in_layout: InLayout<'a>,
dst: Symbol, dst: Symbol,
) { ) {
@ -3388,48 +3758,55 @@ impl<
} }
IntWidth::I64 | IntWidth::U64 => { IntWidth::I64 | IntWidth::U64 => {
let dst_reg = storage_manager.claim_general_reg(buf, &dst); let dst_reg = storage_manager.claim_general_reg(buf, &dst);
ASM::mov_reg64_mem64_offset32(buf, dst_reg, ptr_reg, 0); ASM::mov_reg64_mem64_offset32(buf, dst_reg, ptr_reg, offset);
} }
IntWidth::I32 | IntWidth::U32 => { IntWidth::I32 | IntWidth::U32 => {
let dst_reg = storage_manager.claim_general_reg(buf, &dst); let dst_reg = storage_manager.claim_general_reg(buf, &dst);
ASM::mov_reg32_mem32_offset32(buf, dst_reg, ptr_reg, 0); ASM::mov_reg32_mem32_offset32(buf, dst_reg, ptr_reg, offset);
} }
IntWidth::I16 | IntWidth::U16 => { IntWidth::I16 | IntWidth::U16 => {
let dst_reg = storage_manager.claim_general_reg(buf, &dst); let dst_reg = storage_manager.claim_general_reg(buf, &dst);
ASM::mov_reg16_mem16_offset32(buf, dst_reg, ptr_reg, 0); ASM::mov_reg16_mem16_offset32(buf, dst_reg, ptr_reg, offset);
} }
IntWidth::I8 | IntWidth::U8 => { IntWidth::I8 | IntWidth::U8 => {
let dst_reg = storage_manager.claim_general_reg(buf, &dst); let dst_reg = storage_manager.claim_general_reg(buf, &dst);
ASM::mov_reg8_mem8_offset32(buf, dst_reg, ptr_reg, 0); ASM::mov_reg8_mem8_offset32(buf, dst_reg, ptr_reg, offset);
} }
}, },
Builtin::Float(FloatWidth::F64) => { Builtin::Float(FloatWidth::F64) => {
let dst_reg = storage_manager.claim_float_reg(buf, &dst); let dst_reg = storage_manager.claim_float_reg(buf, &dst);
ASM::mov_freg64_mem64_offset32(buf, dst_reg, ptr_reg, 0); ASM::mov_freg64_mem64_offset32(buf, dst_reg, ptr_reg, offset);
} }
Builtin::Float(FloatWidth::F32) => { Builtin::Float(FloatWidth::F32) => {
let dst_reg = storage_manager.claim_float_reg(buf, &dst); let dst_reg = storage_manager.claim_float_reg(buf, &dst);
ASM::mov_freg32_mem32_offset32(buf, dst_reg, ptr_reg, 0); ASM::mov_freg32_mem32_offset32(buf, dst_reg, ptr_reg, offset);
} }
Builtin::Bool => { Builtin::Bool => {
// the same as an 8-bit integer // the same as an 8-bit integer
let dst_reg = storage_manager.claim_general_reg(buf, &dst); let dst_reg = storage_manager.claim_general_reg(buf, &dst);
ASM::mov_reg8_mem8_offset32(buf, dst_reg, ptr_reg, 0); ASM::mov_reg8_mem8_offset32(buf, dst_reg, ptr_reg, offset);
} }
Builtin::Decimal => { Builtin::Decimal => {
// same as 128-bit integer // same as 128-bit integer
} }
Builtin::Str | Builtin::List(_) => { Builtin::Str | Builtin::List(_) => {
storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp_reg| { storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp_reg| {
Self::unbox_str_or_list(buf, storage_manager, dst, ptr_reg, tmp_reg); Self::unbox_str_or_list(
buf,
storage_manager,
dst,
ptr_reg,
tmp_reg,
offset,
);
}); });
} }
}, },
LayoutRepr::Boxed(_) => { pointer_layouts!() => {
// the same as 64-bit integer (for 64-bit targets) // the same as 64-bit integer (for 64-bit targets)
let dst_reg = storage_manager.claim_general_reg(buf, &dst); let dst_reg = storage_manager.claim_general_reg(buf, &dst);
ASM::mov_reg64_mem64_offset32(buf, dst_reg, ptr_reg, 0); ASM::mov_reg64_mem64_offset32(buf, dst_reg, ptr_reg, offset);
} }
LayoutRepr::Struct { .. } => { LayoutRepr::Struct { .. } => {
@ -3437,7 +3814,15 @@ impl<
let stack_size = layout_interner.stack_size(element_in_layout); let stack_size = layout_interner.stack_size(element_in_layout);
storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp_reg| { storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp_reg| {
Self::unbox_to_stack(buf, storage_manager, dst, stack_size, ptr_reg, tmp_reg); Self::unbox_to_stack(
buf,
storage_manager,
dst,
stack_size,
ptr_reg,
tmp_reg,
offset,
);
}); });
} }
@ -3446,7 +3831,15 @@ impl<
let stack_size = layout_interner.stack_size(element_in_layout); let stack_size = layout_interner.stack_size(element_in_layout);
storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp_reg| { storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp_reg| {
Self::unbox_to_stack(buf, storage_manager, dst, stack_size, ptr_reg, tmp_reg); Self::unbox_to_stack(
buf,
storage_manager,
dst,
stack_size,
ptr_reg,
tmp_reg,
offset,
);
}); });
} }
@ -3456,12 +3849,11 @@ impl<
storage_manager, storage_manager,
layout_interner, layout_interner,
ptr_reg, ptr_reg,
offset,
lambda_set.runtime_representation(), lambda_set.runtime_representation(),
dst, dst,
); );
} }
_ => todo!("unboxing of {:?}", layout_interner.dbg(element_in_layout)),
} }
} }
@ -3497,7 +3889,7 @@ impl<
let sym_reg = storage_manager.load_to_float_reg(buf, &value); let sym_reg = storage_manager.load_to_float_reg(buf, &value);
ASM::movesd_mem64_offset32_freg64(buf, ptr_reg, element_offset, sym_reg); ASM::movesd_mem64_offset32_freg64(buf, ptr_reg, element_offset, sym_reg);
} }
LayoutRepr::Boxed(_) => { pointer_layouts!() => {
let sym_reg = storage_manager.load_to_general_reg(buf, &value); let sym_reg = storage_manager.load_to_general_reg(buf, &value);
ASM::mov_mem64_offset32_reg64(buf, ptr_reg, element_offset, sym_reg); ASM::mov_mem64_offset32_reg64(buf, ptr_reg, element_offset, sym_reg);
} }
@ -3605,7 +3997,7 @@ impl<
} }
/// Loads the alignment bytes of `layout` into the given `symbol` /// Loads the alignment bytes of `layout` into the given `symbol`
fn load_layout_alignment(&mut self, layout: InLayout<'a>, symbol: Symbol) { fn load_layout_alignment(&mut self, layout: InLayout<'_>, symbol: Symbol) {
let u32_layout = Layout::U32; let u32_layout = Layout::U32;
let alignment = self.layout_interner.alignment_bytes(layout); let alignment = self.layout_interner.alignment_bytes(layout);
let alignment_literal = Literal::Int((alignment as i128).to_ne_bytes()); let alignment_literal = Literal::Int((alignment as i128).to_ne_bytes());
@ -3614,7 +4006,7 @@ impl<
} }
/// Loads the stack size of `layout` into the given `symbol` /// Loads the stack size of `layout` into the given `symbol`
fn load_layout_stack_size(&mut self, layout: InLayout<'a>, symbol: Symbol) { fn load_layout_stack_size(&mut self, layout: InLayout<'_>, symbol: Symbol) {
let u64_layout = Layout::U64; let u64_layout = Layout::U64;
let width = self.layout_interner.stack_size(layout); let width = self.layout_interner.stack_size(layout);
let width_literal = Literal::Int((width as i128).to_ne_bytes()); let width_literal = Literal::Int((width as i128).to_ne_bytes());
@ -3671,3 +4063,17 @@ macro_rules! single_register_layouts {
single_register_integers!() | single_register_floats!() single_register_integers!() | single_register_floats!()
}; };
} }
#[macro_export]
macro_rules! pointer_layouts {
() => {
LayoutRepr::Boxed(_)
| LayoutRepr::RecursivePointer(_)
| LayoutRepr::Union(
UnionLayout::Recursive(_)
| UnionLayout::NonNullableUnwrapped(_)
| UnionLayout::NullableWrapped { .. }
| UnionLayout::NullableUnwrapped { .. },
)
};
}

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
generic64::{Assembler, CallConv, RegTrait}, generic64::{Assembler, CallConv, RegTrait},
sign_extended_int_builtins, single_register_floats, single_register_int_builtins, pointer_layouts, sign_extended_int_builtins, single_register_floats,
single_register_integers, single_register_layouts, Env, single_register_int_builtins, single_register_integers, single_register_layouts, Env,
}; };
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
@ -11,8 +11,7 @@ use roc_module::symbol::Symbol;
use roc_mono::{ use roc_mono::{
ir::{JoinPointId, Param}, ir::{JoinPointId, Param},
layout::{ layout::{
Builtin, InLayout, Layout, LayoutInterner, LayoutRepr, STLayoutInterner, TagIdIntType, Builtin, InLayout, Layout, LayoutInterner, LayoutRepr, STLayoutInterner, UnionLayout,
UnionLayout,
}, },
}; };
use roc_target::TargetInfo; use roc_target::TargetInfo;
@ -91,7 +90,7 @@ pub struct StorageManager<
phantom_cc: PhantomData<CC>, phantom_cc: PhantomData<CC>,
phantom_asm: PhantomData<ASM>, phantom_asm: PhantomData<ASM>,
pub(crate) env: &'r Env<'a>, pub(crate) env: &'r Env<'a>,
target_info: TargetInfo, pub(crate) target_info: TargetInfo,
// Data about where each symbol is stored. // Data about where each symbol is stored.
symbol_storage_map: MutMap<Symbol, Storage<GeneralReg, FloatReg>>, symbol_storage_map: MutMap<Symbol, Storage<GeneralReg, FloatReg>>,
@ -598,20 +597,21 @@ impl<
} }
} }
pub fn load_union_tag_id( pub fn load_union_tag_id_nonrecursive(
&mut self, &mut self,
layout_interner: &mut STLayoutInterner<'a>, layout_interner: &mut STLayoutInterner<'a>,
_buf: &mut Vec<'a, u8>, _buf: &mut Vec<'a, u8>,
sym: &Symbol, sym: &Symbol,
structure: &Symbol, structure: &Symbol,
union_layout: &UnionLayout<'a>, tags: &[&[InLayout]],
) { ) {
let union_layout = UnionLayout::NonRecursive(tags);
// This must be removed and reinserted for ownership and mutability reasons. // This must be removed and reinserted for ownership and mutability reasons.
let owned_data = self.remove_allocation_for_sym(structure); let owned_data = self.remove_allocation_for_sym(structure);
self.allocation_map self.allocation_map
.insert(*structure, Rc::clone(&owned_data)); .insert(*structure, Rc::clone(&owned_data));
match union_layout {
UnionLayout::NonRecursive(_) => {
let (union_offset, _) = self.stack_offset_and_size(structure); let (union_offset, _) = self.stack_offset_and_size(structure);
let (data_size, data_alignment) = let (data_size, data_alignment) =
@ -630,9 +630,6 @@ impl<
}), }),
); );
} }
x => todo!("getting tag id of union with layout ({:?})", x),
}
}
// Loads the dst to be the later 64 bits of a list (its length). // Loads the dst to be the later 64 bits of a list (its length).
pub fn list_len(&mut self, _buf: &mut Vec<'a, u8>, dst: &Symbol, list: &Symbol) { pub fn list_len(&mut self, _buf: &mut Vec<'a, u8>, dst: &Symbol, list: &Symbol) {
@ -700,56 +697,6 @@ impl<
} }
} }
/// Creates a union on the stack, moving the data in fields into the union and tagging it.
pub fn create_union(
&mut self,
layout_interner: &mut STLayoutInterner<'a>,
buf: &mut Vec<'a, u8>,
sym: &Symbol,
union_layout: &UnionLayout<'a>,
fields: &'a [Symbol],
tag_id: TagIdIntType,
) {
match union_layout {
UnionLayout::NonRecursive(field_layouts) => {
let (data_size, data_alignment) =
union_layout.data_size_and_alignment(layout_interner, self.target_info);
let id_offset = data_size - data_alignment;
let base_offset = self.claim_stack_area(sym, data_size);
let mut current_offset = base_offset;
let it = fields.iter().zip(field_layouts[tag_id as usize].iter());
for (field, field_layout) in it {
self.copy_symbol_to_stack_offset(
layout_interner,
buf,
current_offset,
field,
field_layout,
);
let field_size = layout_interner.stack_size(*field_layout);
current_offset += field_size as i32;
}
// put the tag id in the right place
self.with_tmp_general_reg(buf, |_symbol_storage, buf, reg| {
ASM::mov_reg64_imm64(buf, reg, tag_id as i64);
let total_id_offset = base_offset as u32 + id_offset;
debug_assert!(total_id_offset % data_alignment == 0);
// pick the right instruction based on the alignment of the tag id
if field_layouts.len() <= u8::MAX as _ {
ASM::mov_base32_reg8(buf, total_id_offset as i32, reg);
} else {
ASM::mov_base32_reg16(buf, total_id_offset as i32, reg);
}
});
}
x => todo!("creating unions with layout: {:?}", x),
}
}
/// Copies a complex symbol on the stack to the arg pointer. /// Copies a complex symbol on the stack to the arg pointer.
pub fn copy_symbol_to_arg_pointer( pub fn copy_symbol_to_arg_pointer(
&mut self, &mut self,
@ -845,12 +792,6 @@ impl<
self.copy_to_stack_offset(buf, size, from_offset, to_offset) self.copy_to_stack_offset(buf, size, from_offset, to_offset)
} }
}, },
LayoutRepr::Boxed(_) => {
// like a 64-bit integer
debug_assert_eq!(to_offset % 8, 0);
let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg64(buf, to_offset, reg);
}
LayoutRepr::LambdaSet(lambda_set) => { LayoutRepr::LambdaSet(lambda_set) => {
// like its runtime representation // like its runtime representation
self.copy_symbol_to_stack_offset( self.copy_symbol_to_stack_offset(
@ -861,14 +802,18 @@ impl<
&lambda_set.runtime_representation(), &lambda_set.runtime_representation(),
) )
} }
_ if layout_interner.stack_size(*layout) == 0 => {}
LayoutRepr::Struct { .. } | LayoutRepr::Union(UnionLayout::NonRecursive(_)) => { LayoutRepr::Struct { .. } | LayoutRepr::Union(UnionLayout::NonRecursive(_)) => {
let (from_offset, size) = self.stack_offset_and_size(sym); let (from_offset, size) = self.stack_offset_and_size(sym);
debug_assert_eq!(size, layout_interner.stack_size(*layout)); debug_assert_eq!(size, layout_interner.stack_size(*layout));
self.copy_to_stack_offset(buf, size, from_offset, to_offset) self.copy_to_stack_offset(buf, size, from_offset, to_offset)
} }
x => todo!("copying data to the stack with layout, {:?}", x), LayoutRepr::RecursivePointer(_) | LayoutRepr::Boxed(_) | LayoutRepr::Union(_) => {
// like a 64-bit integer
debug_assert_eq!(to_offset % 8, 0);
let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg64(buf, to_offset, reg);
}
} }
} }
@ -1160,7 +1105,7 @@ impl<
layout: InLayout<'a>, layout: InLayout<'a>,
) { ) {
match layout_interner.get(layout).repr { match layout_interner.get(layout).repr {
single_register_layouts!() => { single_register_layouts!() | pointer_layouts!() => {
let base_offset = self.claim_stack_size(8); let base_offset = self.claim_stack_size(8);
self.symbol_storage_map.insert( self.symbol_storage_map.insert(
symbol, symbol,
@ -1172,14 +1117,12 @@ impl<
self.allocation_map self.allocation_map
.insert(symbol, Rc::new((base_offset, 8))); .insert(symbol, Rc::new((base_offset, 8)));
} }
_ => { LayoutRepr::LambdaSet(lambda_set) => self.joinpoint_argument_stack_storage(
if let LayoutRepr::LambdaSet(lambda_set) = layout_interner.get(layout).repr {
self.joinpoint_argument_stack_storage(
layout_interner, layout_interner,
symbol, symbol,
lambda_set.runtime_representation(), lambda_set.runtime_representation(),
) ),
} else { _ => {
let stack_size = layout_interner.stack_size(layout); let stack_size = layout_interner.stack_size(layout);
if stack_size == 0 { if stack_size == 0 {
self.no_data(&symbol); self.no_data(&symbol);
@ -1189,7 +1132,6 @@ impl<
} }
} }
} }
}
/// Setups a join point. /// Setups a join point.
/// To do this, each of the join pionts params are given a storage location. /// To do this, each of the join pionts params are given a storage location.
@ -1228,7 +1170,7 @@ impl<
base_offset: i32, base_offset: i32,
) { ) {
match layout_interner.get(layout).repr { match layout_interner.get(layout).repr {
single_register_integers!() => { single_register_integers!() | pointer_layouts!() => {
let reg = self.load_to_general_reg(buf, &symbol); let reg = self.load_to_general_reg(buf, &symbol);
ASM::mov_base32_reg64(buf, base_offset, reg); ASM::mov_base32_reg64(buf, base_offset, reg);
} }
@ -1236,7 +1178,6 @@ impl<
let reg = self.load_to_float_reg(buf, &symbol); let reg = self.load_to_float_reg(buf, &symbol);
ASM::mov_base32_freg64(buf, base_offset, reg); ASM::mov_base32_freg64(buf, base_offset, reg);
} }
_ => match layout_interner.get(layout).repr {
LayoutRepr::LambdaSet(lambda_set) => { LayoutRepr::LambdaSet(lambda_set) => {
self.jump_argument_stack_storage( self.jump_argument_stack_storage(
layout_interner, layout_interner,
@ -1246,17 +1187,12 @@ impl<
base_offset, base_offset,
); );
} }
LayoutRepr::Boxed(_) => {
let reg = self.load_to_general_reg(buf, &symbol);
ASM::mov_base32_reg64(buf, base_offset, reg);
}
_ => { _ => {
internal_error!( internal_error!(
r"cannot load non-primitive layout ({:?}) to primitive stack location", r"cannot load non-primitive layout ({:?}) to primitive stack location",
layout_interner.dbg(layout) layout_interner.dbg(layout)
) )
} }
},
} }
} }
@ -1341,6 +1277,22 @@ impl<
base_offset base_offset
} }
pub fn claim_pointer_stack_area(&mut self, sym: Symbol) -> i32 {
let size = 8;
let base_offset = self.claim_stack_size(size);
self.symbol_storage_map.insert(
sym,
Stack(Primitive {
base_offset,
reg: None,
}),
);
base_offset
}
/// claim_stack_size claims `amount` bytes from the stack alignind to 8. /// claim_stack_size claims `amount` bytes from the stack alignind to 8.
/// This may be free space in the stack or result in increasing the stack size. /// This may be free space in the stack or result in increasing the stack size.
/// It returns base pointer relative offset of the new data. /// It returns base pointer relative offset of the new data.
@ -1541,12 +1493,10 @@ impl<
fn is_primitive(layout_interner: &mut STLayoutInterner<'_>, layout: InLayout<'_>) -> bool { fn is_primitive(layout_interner: &mut STLayoutInterner<'_>, layout: InLayout<'_>) -> bool {
match layout_interner.get(layout).repr { match layout_interner.get(layout).repr {
single_register_layouts!() => true, single_register_layouts!() => true,
_ => match layout_interner.get(layout).repr { pointer_layouts!() => true,
LayoutRepr::Boxed(_) => true,
LayoutRepr::LambdaSet(lambda_set) => { LayoutRepr::LambdaSet(lambda_set) => {
is_primitive(layout_interner, lambda_set.runtime_representation()) is_primitive(layout_interner, lambda_set.runtime_representation())
} }
_ => false, _ => false,
},
} }
} }

View File

@ -1,7 +1,7 @@
use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait}; use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
use crate::{ use crate::{
single_register_floats, single_register_int_builtins, single_register_integers, pointer_layouts, single_register_floats, single_register_int_builtins,
single_register_layouts, Relocation, single_register_integers, single_register_layouts, Relocation,
}; };
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
@ -461,6 +461,7 @@ impl X64_64SystemVStoreArgs {
) { ) {
match layout_interner.get(in_layout).repr { match layout_interner.get(in_layout).repr {
single_register_integers!() => self.store_arg_general(buf, storage_manager, sym), single_register_integers!() => self.store_arg_general(buf, storage_manager, sym),
pointer_layouts!() => self.store_arg_general(buf, storage_manager, sym),
single_register_floats!() => self.store_arg_float(buf, storage_manager, sym), single_register_floats!() => self.store_arg_float(buf, storage_manager, sym),
LayoutRepr::I128 | LayoutRepr::U128 => { LayoutRepr::I128 | LayoutRepr::U128 => {
let (offset, _) = storage_manager.stack_offset_and_size(&sym); let (offset, _) = storage_manager.stack_offset_and_size(&sym);
@ -507,13 +508,6 @@ impl X64_64SystemVStoreArgs {
} }
self.tmp_stack_offset += size as i32; self.tmp_stack_offset += size as i32;
} }
other => {
// look at the layout in more detail
match other {
LayoutRepr::Boxed(_) => {
// treat boxed like a 64-bit integer
self.store_arg_general(buf, storage_manager, sym)
}
LayoutRepr::LambdaSet(lambda_set) => self.store_arg( LayoutRepr::LambdaSet(lambda_set) => self.store_arg(
buf, buf,
storage_manager, storage_manager,
@ -594,8 +588,6 @@ impl X64_64SystemVStoreArgs {
} }
} }
} }
}
}
fn store_arg_general<'a>( fn store_arg_general<'a>(
&mut self, &mut self,
@ -664,6 +656,7 @@ impl X64_64SystemVLoadArgs {
let stack_size = layout_interner.stack_size(in_layout); let stack_size = layout_interner.stack_size(in_layout);
match layout_interner.get(in_layout).repr { match layout_interner.get(in_layout).repr {
single_register_integers!() => self.load_arg_general(storage_manager, sym), single_register_integers!() => self.load_arg_general(storage_manager, sym),
pointer_layouts!() => self.load_arg_general(storage_manager, sym),
single_register_floats!() => self.load_arg_float(storage_manager, sym), single_register_floats!() => self.load_arg_float(storage_manager, sym),
_ if stack_size == 0 => { _ if stack_size == 0 => {
storage_manager.no_data(&sym); storage_manager.no_data(&sym);
@ -673,11 +666,6 @@ impl X64_64SystemVLoadArgs {
storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size); storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size);
self.argument_offset += stack_size as i32; self.argument_offset += stack_size as i32;
} }
other => match other {
LayoutRepr::Boxed(_) => {
// boxed layouts are pointers, which we treat as 64-bit integers
self.load_arg_general(storage_manager, sym)
}
LayoutRepr::LambdaSet(lambda_set) => self.load_arg( LayoutRepr::LambdaSet(lambda_set) => self.load_arg(
storage_manager, storage_manager,
layout_interner, layout_interner,
@ -704,7 +692,6 @@ impl X64_64SystemVLoadArgs {
layout_interner.dbg(in_layout) layout_interner.dbg(in_layout)
); );
} }
},
} }
} }
@ -1152,6 +1139,11 @@ where
} }
impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler { impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
#[inline(always)]
fn base_pointer() -> X86_64GeneralReg {
X86_64GeneralReg::RBP
}
// These functions should map to the raw assembly functions below. // These functions should map to the raw assembly functions below.
// In some cases, that means you can just directly call one of the direct assembly functions. // In some cases, that means you can just directly call one of the direct assembly functions.
#[inline(always)] #[inline(always)]
@ -1756,7 +1748,7 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
} }
#[inline(always)] #[inline(always)]
fn neq_reg64_reg64_reg64( fn neq_reg_reg_reg(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
register_width: RegisterWidth, register_width: RegisterWidth,
dst: X86_64GeneralReg, dst: X86_64GeneralReg,

View File

@ -798,7 +798,7 @@ trait Backend<'a> {
.. ..
} => { } => {
self.load_literal_symbols(arguments); self.load_literal_symbols(arguments);
self.tag(sym, arguments, tag_layout, *tag_id); self.tag(sym, arguments, tag_layout, *tag_id, None);
} }
Expr::ExprBox { symbol: value } => { Expr::ExprBox { symbol: value } => {
let element_layout = match self.interner().get(*layout).repr { let element_layout = match self.interner().get(*layout).repr {
@ -807,7 +807,7 @@ trait Backend<'a> {
}; };
self.load_literal_symbols([*value].as_slice()); self.load_literal_symbols([*value].as_slice());
self.expr_box(*sym, *value, element_layout) self.expr_box(*sym, *value, element_layout, None)
} }
Expr::ExprUnbox { symbol: ptr } => { Expr::ExprUnbox { symbol: ptr } => {
let element_layout = *layout; let element_layout = *layout;
@ -815,7 +815,60 @@ trait Backend<'a> {
self.load_literal_symbols([*ptr].as_slice()); self.load_literal_symbols([*ptr].as_slice());
self.expr_unbox(*sym, *ptr, element_layout) self.expr_unbox(*sym, *ptr, element_layout)
} }
x => todo!("the expression, {:?}", x), Expr::NullPointer => {
self.load_literal_i64(sym, 0);
}
Expr::Reuse {
tag_layout,
tag_id,
symbol: reused,
arguments,
..
} => {
self.load_literal_symbols(arguments);
self.tag(sym, arguments, tag_layout, *tag_id, Some(*reused));
}
Expr::Reset { symbol, .. } => {
let layout = *self.layout_map().get(symbol).unwrap();
// Expand the Refcounting statement into more detailed IR with a function call
// If this layout requires a new RC proc, we get enough info to create a linker symbol
// for it. Here we don't create linker symbols at this time, but in Wasm backend, we do.
let (new_expr, new_specializations) = {
let (module_id, layout_interner, interns, rc_proc_gen, _) =
self.module_interns_helpers_mut();
let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap();
rc_proc_gen.call_reset_refcount(ident_ids, layout_interner, layout, *symbol)
};
for spec in new_specializations.into_iter() {
self.helper_proc_symbols_mut().push(spec);
}
self.build_expr(sym, &new_expr, &Layout::BOOL)
}
Expr::ResetRef { symbol, .. } => {
let layout = *self.layout_map().get(symbol).unwrap();
// Expand the Refcounting statement into more detailed IR with a function call
// If this layout requires a new RC proc, we get enough info to create a linker symbol
// for it. Here we don't create linker symbols at this time, but in Wasm backend, we do.
let (new_expr, new_specializations) = {
let (module_id, layout_interner, interns, rc_proc_gen, _) =
self.module_interns_helpers_mut();
let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap();
rc_proc_gen.call_resetref_refcount(ident_ids, layout_interner, layout, *symbol)
};
for spec in new_specializations.into_iter() {
self.helper_proc_symbols_mut().push(spec);
}
self.build_expr(sym, &new_expr, &Layout::BOOL)
}
Expr::RuntimeErrorFunction(_) => todo!(),
} }
} }
@ -1068,13 +1121,12 @@ trait Backend<'a> {
} }
LowLevel::Eq => { LowLevel::Eq => {
debug_assert_eq!(2, args.len(), "Eq: expected to have exactly two argument"); debug_assert_eq!(2, args.len(), "Eq: expected to have exactly two argument");
debug_assert_eq!( debug_assert!(
arg_layouts[0], arg_layouts[1], self.interner().eq_repr(arg_layouts[0], arg_layouts[1],),
"Eq: expected all arguments of to have the same layout" "Eq: expected all arguments of to have the same layout"
); );
debug_assert_eq!( debug_assert!(
Layout::BOOL, self.interner().eq_repr(Layout::BOOL, *ret_layout,),
*ret_layout,
"Eq: expected to have return layout of type Bool" "Eq: expected to have return layout of type Bool"
); );
self.build_eq(sym, &args[0], &args[1], &arg_layouts[0]) self.build_eq(sym, &args[0], &args[1], &arg_layouts[0])
@ -1085,22 +1137,20 @@ trait Backend<'a> {
args.len(), args.len(),
"NotEq: expected to have exactly two argument" "NotEq: expected to have exactly two argument"
); );
debug_assert_eq!( debug_assert!(
arg_layouts[0], arg_layouts[1], self.interner().eq_repr(arg_layouts[0], arg_layouts[1],),
"NotEq: expected all arguments of to have the same layout" "NotEq: expected all arguments of to have the same layout"
); );
debug_assert_eq!( debug_assert!(
Layout::BOOL, self.interner().eq_repr(Layout::BOOL, *ret_layout,),
*ret_layout,
"NotEq: expected to have return layout of type Bool" "NotEq: expected to have return layout of type Bool"
); );
self.build_neq(sym, &args[0], &args[1], &arg_layouts[0]) self.build_neq(sym, &args[0], &args[1], &arg_layouts[0])
} }
LowLevel::Not => { LowLevel::Not => {
debug_assert_eq!(1, args.len(), "Not: expected to have exactly one argument"); debug_assert_eq!(1, args.len(), "Not: expected to have exactly one argument");
debug_assert_eq!( debug_assert!(
Layout::BOOL, self.interner().eq_repr(Layout::BOOL, *ret_layout,),
*ret_layout,
"Not: expected to have return layout of type Bool" "Not: expected to have return layout of type Bool"
); );
self.build_not(sym, &args[0], &arg_layouts[0]) self.build_not(sym, &args[0], &arg_layouts[0])
@ -1111,13 +1161,12 @@ trait Backend<'a> {
args.len(), args.len(),
"NumLt: expected to have exactly two argument" "NumLt: expected to have exactly two argument"
); );
debug_assert_eq!( debug_assert!(
arg_layouts[0], arg_layouts[1], self.interner().eq_repr(arg_layouts[0], arg_layouts[1],),
"NumLt: expected all arguments of to have the same layout" "NumLt: expected all arguments of to have the same layout"
); );
debug_assert_eq!( debug_assert!(
Layout::BOOL, self.interner().eq_repr(Layout::BOOL, *ret_layout,),
*ret_layout,
"NumLt: expected to have return layout of type Bool" "NumLt: expected to have return layout of type Bool"
); );
self.build_num_lt(sym, &args[0], &args[1], &arg_layouts[0]) self.build_num_lt(sym, &args[0], &args[1], &arg_layouts[0])
@ -1128,13 +1177,12 @@ trait Backend<'a> {
args.len(), args.len(),
"NumGt: expected to have exactly two argument" "NumGt: expected to have exactly two argument"
); );
debug_assert_eq!( debug_assert!(
arg_layouts[0], arg_layouts[1], self.interner().eq_repr(arg_layouts[0], arg_layouts[1],),
"NumGt: expected all arguments of to have the same layout" "NumGt: expected all arguments of to have the same layout"
); );
debug_assert_eq!( debug_assert!(
Layout::BOOL, self.interner().eq_repr(Layout::BOOL, *ret_layout,),
*ret_layout,
"NumGt: expected to have return layout of type Bool" "NumGt: expected to have return layout of type Bool"
); );
self.build_num_gt(sym, &args[0], &args[1], &arg_layouts[0]) self.build_num_gt(sym, &args[0], &args[1], &arg_layouts[0])
@ -1159,9 +1207,8 @@ trait Backend<'a> {
"NumIsNan: expected to have exactly one argument" "NumIsNan: expected to have exactly one argument"
); );
debug_assert_eq!( debug_assert!(
Layout::BOOL, self.interner().eq_repr(Layout::BOOL, *ret_layout,),
*ret_layout,
"NumIsNan: expected to have return layout of type Bool" "NumIsNan: expected to have return layout of type Bool"
); );
self.build_num_is_nan(sym, &args[0], &arg_layouts[0]) self.build_num_is_nan(sym, &args[0], &arg_layouts[0])
@ -1173,9 +1220,8 @@ trait Backend<'a> {
"NumIsInfinite: expected to have exactly one argument" "NumIsInfinite: expected to have exactly one argument"
); );
debug_assert_eq!( debug_assert!(
Layout::BOOL, self.interner().eq_repr(Layout::BOOL, *ret_layout,),
*ret_layout,
"NumIsInfinite: expected to have return layout of type Bool" "NumIsInfinite: expected to have return layout of type Bool"
); );
self.build_num_is_infinite(sym, &args[0], &arg_layouts[0]) self.build_num_is_infinite(sym, &args[0], &arg_layouts[0])
@ -1187,9 +1233,8 @@ trait Backend<'a> {
"NumIsFinite: expected to have exactly one argument" "NumIsFinite: expected to have exactly one argument"
); );
debug_assert_eq!( debug_assert!(
Layout::BOOL, self.interner().eq_repr(Layout::BOOL, *ret_layout,),
*ret_layout,
"NumIsFinite: expected to have return layout of type Bool" "NumIsFinite: expected to have return layout of type Bool"
); );
self.build_num_is_finite(sym, &args[0], &arg_layouts[0]) self.build_num_is_finite(sym, &args[0], &arg_layouts[0])
@ -1204,9 +1249,8 @@ trait Backend<'a> {
arg_layouts[0], arg_layouts[1], arg_layouts[0], arg_layouts[1],
"NumLte: expected all arguments of to have the same layout" "NumLte: expected all arguments of to have the same layout"
); );
debug_assert_eq!( debug_assert!(
Layout::BOOL, self.interner().eq_repr(Layout::BOOL, *ret_layout,),
*ret_layout,
"NumLte: expected to have return layout of type Bool" "NumLte: expected to have return layout of type Bool"
); );
self.build_num_lte(sym, &args[0], &args[1], &arg_layouts[0]) self.build_num_lte(sym, &args[0], &args[1], &arg_layouts[0])
@ -1221,9 +1265,8 @@ trait Backend<'a> {
arg_layouts[0], arg_layouts[1], arg_layouts[0], arg_layouts[1],
"NumGte: expected all arguments of to have the same layout" "NumGte: expected all arguments of to have the same layout"
); );
debug_assert_eq!( debug_assert!(
Layout::BOOL, self.interner().eq_repr(Layout::BOOL, *ret_layout,),
*ret_layout,
"NumGte: expected to have return layout of type Bool" "NumGte: expected to have return layout of type Bool"
); );
self.build_num_gte(sym, &args[0], &args[1], &arg_layouts[0]) self.build_num_gte(sym, &args[0], &args[1], &arg_layouts[0])
@ -1761,9 +1804,8 @@ trait Backend<'a> {
args.len(), args.len(),
"NumIsZero: expected to have exactly one argument" "NumIsZero: expected to have exactly one argument"
); );
debug_assert_eq!( debug_assert!(
Layout::BOOL, self.interner().eq_repr(Layout::BOOL, *ret_layout,),
*ret_layout,
"NumIsZero: expected to have return layout of type Bool" "NumIsZero: expected to have return layout of type Bool"
); );
@ -2216,13 +2258,20 @@ trait Backend<'a> {
args: &'a [Symbol], args: &'a [Symbol],
tag_layout: &UnionLayout<'a>, tag_layout: &UnionLayout<'a>,
tag_id: TagIdIntType, tag_id: TagIdIntType,
reuse: Option<Symbol>,
); );
/// load a value from a pointer /// load a value from a pointer
fn expr_unbox(&mut self, sym: Symbol, ptr: Symbol, element_layout: InLayout<'a>); fn expr_unbox(&mut self, sym: Symbol, ptr: Symbol, element_layout: InLayout<'a>);
/// store a refcounted value on the heap /// store a refcounted value on the heap
fn expr_box(&mut self, sym: Symbol, value: Symbol, element_layout: InLayout<'a>); fn expr_box(
&mut self,
sym: Symbol,
value: Symbol,
element_layout: InLayout<'a>,
reuse: Option<Symbol>,
);
/// return_symbol moves a symbol to the correct return location for the backend and adds a jump to the end of the function. /// return_symbol moves a symbol to the correct return location for the backend and adds a jump to the end of the function.
fn return_symbol(&mut self, sym: &Symbol, layout: &InLayout<'a>); fn return_symbol(&mut self, sym: &Symbol, layout: &InLayout<'a>);

View File

@ -7,11 +7,12 @@ use inkwell::{
}; };
use roc_builtins::{ use roc_builtins::{
bitcode::{FloatWidth, IntWidth, IntrinsicName}, bitcode::{FloatWidth, IntWidth, IntrinsicName},
float_intrinsic, llvm_int_intrinsic, llvm_int_intrinsic,
}; };
use super::build::{add_func, FunctionSpec}; use super::build::{add_func, FunctionSpec};
#[allow(dead_code)]
fn add_float_intrinsic<'ctx, F>( fn add_float_intrinsic<'ctx, F>(
ctx: &'ctx Context, ctx: &'ctx Context,
module: &Module<'ctx>, module: &Module<'ctx>,
@ -111,18 +112,6 @@ pub(crate) fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
i8_ptr_type.fn_type(&[], false), i8_ptr_type.fn_type(&[], false),
); );
add_float_intrinsic(ctx, module, &LLVM_LOG, |t| t.fn_type(&[t.into()], false));
add_float_intrinsic(ctx, module, &LLVM_POW, |t| {
t.fn_type(&[t.into(), t.into()], false)
});
add_float_intrinsic(ctx, module, &LLVM_FABS, |t| t.fn_type(&[t.into()], false));
add_float_intrinsic(ctx, module, &LLVM_SIN, |t| t.fn_type(&[t.into()], false));
add_float_intrinsic(ctx, module, &LLVM_COS, |t| t.fn_type(&[t.into()], false));
add_float_intrinsic(ctx, module, &LLVM_CEILING, |t| {
t.fn_type(&[t.into()], false)
});
add_float_intrinsic(ctx, module, &LLVM_FLOOR, |t| t.fn_type(&[t.into()], false));
add_int_intrinsic(ctx, module, &LLVM_ADD_WITH_OVERFLOW, |t| { add_int_intrinsic(ctx, module, &LLVM_ADD_WITH_OVERFLOW, |t| {
let fields = [t.into(), i1_type.into()]; let fields = [t.into(), i1_type.into()];
ctx.struct_type(&fields, false) ctx.struct_type(&fields, false)
@ -150,17 +139,6 @@ pub(crate) fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
}); });
} }
pub const LLVM_POW: IntrinsicName = float_intrinsic!("llvm.pow");
pub const LLVM_FABS: IntrinsicName = float_intrinsic!("llvm.fabs");
pub static LLVM_SQRT: IntrinsicName = float_intrinsic!("llvm.sqrt");
pub static LLVM_LOG: IntrinsicName = float_intrinsic!("llvm.log");
pub static LLVM_SIN: IntrinsicName = float_intrinsic!("llvm.sin");
pub static LLVM_COS: IntrinsicName = float_intrinsic!("llvm.cos");
pub static LLVM_CEILING: IntrinsicName = float_intrinsic!("llvm.ceil");
pub static LLVM_FLOOR: IntrinsicName = float_intrinsic!("llvm.floor");
pub static LLVM_ROUND: IntrinsicName = float_intrinsic!("llvm.round");
pub static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64"; pub static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64";
pub static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32"; pub static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32";

View File

@ -41,9 +41,13 @@ use crate::llvm::{
self, basic_type_from_layout, zig_num_parse_result_type, zig_to_int_checked_result_type, self, basic_type_from_layout, zig_num_parse_result_type, zig_to_int_checked_result_type,
}, },
intrinsics::{ intrinsics::{
LLVM_ADD_SATURATED, LLVM_ADD_WITH_OVERFLOW, LLVM_CEILING, LLVM_COS, LLVM_FABS, LLVM_FLOOR, // These instrinsics do not generate calls to libc and are safe to keep.
LLVM_LOG, LLVM_MUL_WITH_OVERFLOW, LLVM_POW, LLVM_ROUND, LLVM_SIN, LLVM_SQRT, // If we find that any of them generate calls to libc on some platforms, we need to define them as zig bitcode.
LLVM_SUB_SATURATED, LLVM_SUB_WITH_OVERFLOW, LLVM_ADD_SATURATED,
LLVM_ADD_WITH_OVERFLOW,
LLVM_MUL_WITH_OVERFLOW,
LLVM_SUB_SATURATED,
LLVM_SUB_WITH_OVERFLOW,
}, },
refcounting::PointerToRefcount, refcounting::PointerToRefcount,
}; };
@ -1704,7 +1708,11 @@ fn build_float_binop<'ctx>(
NumLt => bd.build_float_compare(OLT, lhs, rhs, "float_lt").into(), NumLt => bd.build_float_compare(OLT, lhs, rhs, "float_lt").into(),
NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").into(), NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").into(),
NumDivFrac => bd.build_float_div(lhs, rhs, "div_float").into(), NumDivFrac => bd.build_float_div(lhs, rhs, "div_float").into(),
NumPow => env.call_intrinsic(&LLVM_POW[float_width], &[lhs.into(), rhs.into()]), NumPow => call_bitcode_fn(
env,
&[lhs.into(), rhs.into()],
&bitcode::NUM_POW[float_width],
),
_ => { _ => {
unreachable!("Unrecognized int binary operation: {:?}", op); unreachable!("Unrecognized int binary operation: {:?}", op);
} }
@ -2316,9 +2324,9 @@ fn build_float_unary_op<'a, 'ctx>(
// TODO: Handle different sized floats // TODO: Handle different sized floats
match op { match op {
NumNeg => bd.build_float_neg(arg, "negate_float").into(), NumNeg => bd.build_float_neg(arg, "negate_float").into(),
NumAbs => env.call_intrinsic(&LLVM_FABS[float_width], &[arg.into()]), NumAbs => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_FABS[float_width]),
NumSqrtUnchecked => env.call_intrinsic(&LLVM_SQRT[float_width], &[arg.into()]), NumSqrtUnchecked => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_SQRT[float_width]),
NumLogUnchecked => env.call_intrinsic(&LLVM_LOG[float_width], &[arg.into()]), NumLogUnchecked => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_LOG[float_width]),
NumToFrac => { NumToFrac => {
let return_width = match layout_interner.get(layout).repr { let return_width = match layout_interner.get(layout).repr {
LayoutRepr::Builtin(Builtin::Float(return_width)) => return_width, LayoutRepr::Builtin(Builtin::Float(return_width)) => return_width,
@ -2342,64 +2350,46 @@ fn build_float_unary_op<'a, 'ctx>(
} }
} }
NumCeiling => { NumCeiling => {
let (return_signed, return_type) = match layout_interner.get(layout).repr { let int_width = match layout_interner.get(layout).repr {
LayoutRepr::Builtin(Builtin::Int(int_width)) => ( LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width,
int_width.is_signed(),
convert::int_type_from_int_width(env, int_width),
),
_ => internal_error!("Ceiling return layout is not int: {:?}", layout), _ => internal_error!("Ceiling return layout is not int: {:?}", layout),
}; };
let opcode = if return_signed { match float_width {
InstructionOpcode::FPToSI FloatWidth::F32 => {
} else { call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_CEILING_F32[int_width])
InstructionOpcode::FPToUI }
}; FloatWidth::F64 => {
env.builder.build_cast( call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_CEILING_F64[int_width])
opcode, }
env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]), }
return_type,
"num_ceiling",
)
} }
NumFloor => { NumFloor => {
let (return_signed, return_type) = match layout_interner.get(layout).repr { let int_width = match layout_interner.get(layout).repr {
LayoutRepr::Builtin(Builtin::Int(int_width)) => ( LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width,
int_width.is_signed(), _ => internal_error!("Floor return layout is not int: {:?}", layout),
convert::int_type_from_int_width(env, int_width),
),
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
}; };
let opcode = if return_signed { match float_width {
InstructionOpcode::FPToSI FloatWidth::F32 => {
} else { call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_FLOOR_F32[int_width])
InstructionOpcode::FPToUI }
}; FloatWidth::F64 => {
env.builder.build_cast( call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_FLOOR_F64[int_width])
opcode, }
env.call_intrinsic(&LLVM_FLOOR[float_width], &[arg.into()]), }
return_type,
"num_floor",
)
} }
NumRound => { NumRound => {
let (return_signed, return_type) = match layout_interner.get(layout).repr { let int_width = match layout_interner.get(layout).repr {
LayoutRepr::Builtin(Builtin::Int(int_width)) => ( LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width,
int_width.is_signed(), _ => internal_error!("Round return layout is not int: {:?}", layout),
convert::int_type_from_int_width(env, int_width),
),
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
}; };
let opcode = if return_signed { match float_width {
InstructionOpcode::FPToSI FloatWidth::F32 => {
} else { call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND_F32[int_width])
InstructionOpcode::FPToUI }
}; FloatWidth::F64 => {
env.builder.build_cast( call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND_F64[int_width])
opcode, }
env.call_intrinsic(&LLVM_ROUND[float_width], &[arg.into()]), }
return_type,
"num_round",
)
} }
NumIsNan => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_NAN[float_width]), NumIsNan => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_NAN[float_width]),
NumIsInfinite => { NumIsInfinite => {
@ -2408,8 +2398,8 @@ fn build_float_unary_op<'a, 'ctx>(
NumIsFinite => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_FINITE[float_width]), NumIsFinite => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_FINITE[float_width]),
// trigonometry // trigonometry
NumSin => env.call_intrinsic(&LLVM_SIN[float_width], &[arg.into()]), NumSin => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_SIN[float_width]),
NumCos => env.call_intrinsic(&LLVM_COS[float_width], &[arg.into()]), NumCos => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_COS[float_width]),
NumAtan => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ATAN[float_width]), NumAtan => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ATAN[float_width]),
NumAcos => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ACOS[float_width]), NumAcos => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ACOS[float_width]),

View File

@ -32,7 +32,7 @@ use roc_module::symbol::{
}; };
use roc_mono::ir::{ use roc_mono::ir::{
CapturedSymbols, ExternalSpecializations, GlueLayouts, LambdaSetId, PartialProc, Proc, CapturedSymbols, ExternalSpecializations, GlueLayouts, LambdaSetId, PartialProc, Proc,
ProcLayout, Procs, ProcsBase, UpdateModeIds, ProcLayout, Procs, ProcsBase, UpdateModeIds, UsageTrackingMap,
}; };
use roc_mono::layout::LayoutInterner; use roc_mono::layout::LayoutInterner;
use roc_mono::layout::{ use roc_mono::layout::{
@ -3115,7 +3115,6 @@ fn update<'a>(
&mut layout_interner, &mut layout_interner,
module_id, module_id,
ident_ids, ident_ids,
state.target_info,
&mut state.procedures, &mut state.procedures,
); );
@ -5783,6 +5782,7 @@ fn make_specializations<'a>(
abilities: AbilitiesView::World(&world_abilities), abilities: AbilitiesView::World(&world_abilities),
exposed_by_module, exposed_by_module,
derived_module: &derived_module, derived_module: &derived_module,
struct_indexing: UsageTrackingMap::default(),
}; };
let mut procs = Procs::new_in(arena); let mut procs = Procs::new_in(arena);
@ -5883,6 +5883,7 @@ fn build_pending_specializations<'a>(
abilities: AbilitiesView::Module(&abilities_store), abilities: AbilitiesView::Module(&abilities_store),
exposed_by_module, exposed_by_module,
derived_module: &derived_module, derived_module: &derived_module,
struct_indexing: UsageTrackingMap::default(),
}; };
let layout_cache_snapshot = layout_cache.snapshot(); let layout_cache_snapshot = layout_cache.snapshot();
@ -6364,6 +6365,7 @@ fn load_derived_partial_procs<'a>(
abilities: AbilitiesView::World(world_abilities), abilities: AbilitiesView::World(world_abilities),
exposed_by_module, exposed_by_module,
derived_module, derived_module,
struct_indexing: UsageTrackingMap::default(),
}; };
let partial_proc = match derived_expr { let partial_proc = match derived_expr {

View File

@ -15,11 +15,10 @@ use bumpalo::collections::CollectIn;
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_target::TargetInfo;
use crate::ir::{ use crate::ir::{
BranchInfo, Call, CallType, Expr, JoinPointId, Literal, ModifyRc, Proc, ProcLayout, Stmt, BranchInfo, Call, CallType, Expr, JoinPointId, ListLiteralElement, Literal, ModifyRc, Proc,
UpdateModeId, ProcLayout, Stmt, UpdateModeId,
}; };
use crate::layout::{ use crate::layout::{
Builtin, InLayout, Layout, LayoutInterner, LayoutRepr, STLayoutInterner, UnionLayout, Builtin, InLayout, Layout, LayoutInterner, LayoutRepr, STLayoutInterner, UnionLayout,
@ -27,7 +26,7 @@ use crate::layout::{
use bumpalo::Bump; 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). 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>, layout_interner: &'i mut STLayoutInterner<'a>,
home: ModuleId, home: ModuleId,
ident_ids: &'i mut IdentIds, ident_ids: &'i mut IdentIds,
target_info: TargetInfo,
procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) { ) {
for ((_symbol, proc_layout), proc) in procs.iter_mut() { for ((_symbol, proc_layout), proc) in procs.iter_mut() {
let mut environment = let mut environment = DropSpecializationEnvironment::new(arena, home, proc_layout.result);
DropSpecializationEnvironment::new(arena, home, proc_layout.result, target_info);
specialize_drops_proc(arena, layout_interner, ident_ids, &mut environment, proc); specialize_drops_proc(arena, layout_interner, ident_ids, &mut environment, proc);
} }
} }
@ -104,7 +101,7 @@ fn specialize_drops_stmt<'a, 'i>(
_ => unreachable!("List get should have two arguments"), _ => 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) alloc_let_with_continuation!(environment)
} }
@ -114,9 +111,18 @@ fn specialize_drops_stmt<'a, 'i>(
RC::NoRc => alloc_let_with_continuation!(environment), RC::NoRc => alloc_let_with_continuation!(environment),
// We probably should not pass the increments to the continuation. // We probably should not pass the increments to the continuation.
RC::Rc | RC::Uknown => { RC::Rc | RC::Uknown => {
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
} }
}, },
_ => { _ => {
@ -127,16 +133,62 @@ fn specialize_drops_stmt<'a, 'i>(
// the parent might be deallocated before the function can use it. // the parent might be deallocated before the function can use it.
// Thus forget everything about any increments. // 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); 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) alloc_let_with_continuation!(environment)
} }
Expr::StructAtIndex { Expr::StructAtIndex {
@ -188,13 +240,10 @@ fn specialize_drops_stmt<'a, 'i>(
} }
alloc_let_with_continuation!(environment) alloc_let_with_continuation!(environment)
} }
Expr::Struct(_) Expr::RuntimeErrorFunction(_)
| Expr::RuntimeErrorFunction(_)
| Expr::ExprBox { .. }
| Expr::NullPointer | Expr::NullPointer
| Expr::GetTagId { .. } | Expr::GetTagId { .. }
| Expr::EmptyArray | Expr::EmptyArray => {
| Expr::Array { .. } => {
// Does nothing relevant to drop specialization. So we can just continue. // Does nothing relevant to drop specialization. So we can just continue.
alloc_let_with_continuation!(environment) alloc_let_with_continuation!(environment)
} }
@ -283,10 +332,12 @@ fn specialize_drops_stmt<'a, 'i>(
}; };
// Find the lowest symbol count for each symbol in each branch, and update the environment to match. // Find the lowest symbol count for each symbol in each branch, and update the environment to match.
for (symbol, count) in environment.incremented_symbols.iter_mut() { for (symbol, count) in environment.incremented_symbols.map.iter_mut() {
let consumed = branch_envs let consumed = branch_envs
.iter() .iter()
.map(|branch_env| branch_env.incremented_symbols.get(symbol).unwrap_or(&0)) .map(|branch_env| {
branch_env.incremented_symbols.map.get(symbol).unwrap_or(&0)
})
.min() .min()
.unwrap(); .unwrap();
@ -300,10 +351,14 @@ fn specialize_drops_stmt<'a, 'i>(
let symbol_differences = let symbol_differences =
environment environment
.incremented_symbols .incremented_symbols
.map
.iter() .iter()
.filter_map(|(symbol, count)| { .filter_map(|(symbol, count)| {
let branch_count = let branch_count = $branch_env
$branch_env.incremented_symbols.get(symbol).unwrap_or(&0); .incremented_symbols
.map
.get(symbol)
.unwrap_or(&0);
match branch_count - count { match branch_count - count {
0 => None, 0 => None,
@ -338,11 +393,6 @@ fn specialize_drops_stmt<'a, 'i>(
(info.clone(), new_branch) (info.clone(), new_branch)
}; };
// Remove all 0 counts as cleanup.
environment
.incremented_symbols
.retain(|_, count| *count > 0);
arena.alloc(Stmt::Switch { arena.alloc(Stmt::Switch {
cond_symbol: *cond_symbol, cond_symbol: *cond_symbol,
cond_layout: *cond_layout, cond_layout: *cond_layout,
@ -354,10 +404,12 @@ fn specialize_drops_stmt<'a, 'i>(
Stmt::Ret(symbol) => arena.alloc(Stmt::Ret(*symbol)), Stmt::Ret(symbol) => arena.alloc(Stmt::Ret(*symbol)),
Stmt::Refcounting(rc, continuation) => match rc { Stmt::Refcounting(rc, continuation) => match rc {
ModifyRc::Inc(symbol, count) => { 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. // 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( let new_continuation = specialize_drops_stmt(
arena, arena,
@ -367,12 +419,17 @@ fn specialize_drops_stmt<'a, 'i>(
continuation, continuation,
); );
if any { if inc_before {
// There were increments before this one, best to let the first one do the increments. // 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. // Or there are no increments left, so we can just continue.
new_continuation new_continuation
} else { } 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. // This is the first increment, but all increments are consumed. So don't insert any.
0 => new_continuation, 0 => new_continuation,
// We still need to do some increments. // We still need to do some increments.
@ -393,7 +450,7 @@ fn specialize_drops_stmt<'a, 'i>(
// dec a // dec a
// dec b // dec b
if environment.pop_incremented(symbol) { if environment.incremented_symbols.pop(symbol) {
// This decremented symbol was incremented before, so we can remove it. // This decremented symbol was incremented before, so we can remove it.
specialize_drops_stmt( specialize_drops_stmt(
arena, arena,
@ -411,10 +468,10 @@ fn specialize_drops_stmt<'a, 'i>(
// As a might get dropped as a result of the decrement of b. // As a might get dropped as a result of the decrement of b.
let mut incremented_children = { let mut incremented_children = {
let mut todo_children = bumpalo::vec![in arena; *symbol]; let mut todo_children = bumpalo::vec![in arena; *symbol];
let mut incremented_children = MutSet::default(); let mut incremented_children = CountingMap::new();
while let Some(child) = todo_children.pop() { while let Some(child) = todo_children.pop() {
if environment.pop_incremented(&child) { if environment.incremented_symbols.pop(&child) {
incremented_children.insert(child); incremented_children.insert(child);
} else { } else {
todo_children.extend(environment.get_children(&child)); todo_children.extend(environment.get_children(&child));
@ -485,8 +542,10 @@ fn specialize_drops_stmt<'a, 'i>(
}; };
// Add back the increments for the children to the environment. // Add back the increments for the children to the environment.
for child_symbol in incremented_children.iter() { for (child_symbol, symbol_count) in incremented_children.map.into_iter() {
environment.add_incremented(*child_symbol, 1) environment
.incremented_symbols
.insert_count(child_symbol, symbol_count)
} }
updated_stmt updated_stmt
@ -565,7 +624,8 @@ fn specialize_drops_stmt<'a, 'i>(
body, body,
remainder, 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() { for param in parameters.iter() {
new_environment.add_symbol_layout(param.symbol, param.layout); new_environment.add_symbol_layout(param.symbol, param.layout);
@ -604,7 +664,7 @@ fn specialize_struct<'a, 'i>(
environment: &mut DropSpecializationEnvironment<'a>, environment: &mut DropSpecializationEnvironment<'a>,
symbol: &Symbol, symbol: &Symbol,
struct_layout: &'a [InLayout], struct_layout: &'a [InLayout],
incremented_children: &mut MutSet<Child>, incremented_children: &mut CountingMap<Child>,
continuation: &'a Stmt<'a>, continuation: &'a Stmt<'a>,
) -> &'a Stmt<'a> { ) -> &'a Stmt<'a> {
match environment.struct_children.get(symbol) { match environment.struct_children.get(symbol) {
@ -620,7 +680,7 @@ fn specialize_struct<'a, 'i>(
for (index, _layout) in struct_layout.iter().enumerate() { for (index, _layout) in struct_layout.iter().enumerate() {
for (child, _i) in children_clone.iter().filter(|(_, i)| *i == index as u64) { 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)); index_symbols.insert(index, (*child, removed));
if removed { if removed {
@ -693,7 +753,7 @@ fn specialize_union<'a, 'i>(
environment: &mut DropSpecializationEnvironment<'a>, environment: &mut DropSpecializationEnvironment<'a>,
symbol: &Symbol, symbol: &Symbol,
union_layout: UnionLayout<'a>, union_layout: UnionLayout<'a>,
incremented_children: &mut MutSet<Child>, incremented_children: &mut CountingMap<Child>,
continuation: &'a Stmt<'a>, continuation: &'a Stmt<'a>,
) -> &'a Stmt<'a> { ) -> &'a Stmt<'a> {
let current_tag = environment.symbol_tag.get(symbol).copied(); let current_tag = environment.symbol_tag.get(symbol).copied();
@ -736,7 +796,7 @@ fn specialize_union<'a, 'i>(
{ {
debug_assert_eq!(tag, *t); debug_assert_eq!(tag, *t);
let removed = incremented_children.remove(child); let removed = incremented_children.pop(child);
index_symbols.insert(index, (*child, removed)); index_symbols.insert(index, (*child, removed));
if removed { if removed {
@ -898,14 +958,14 @@ fn specialize_boxed<'a, 'i>(
layout_interner: &'i mut STLayoutInterner<'a>, layout_interner: &'i mut STLayoutInterner<'a>,
ident_ids: &'i mut IdentIds, ident_ids: &'i mut IdentIds,
environment: &mut DropSpecializationEnvironment<'a>, environment: &mut DropSpecializationEnvironment<'a>,
incremented_children: &mut MutSet<Child>, incremented_children: &mut CountingMap<Child>,
symbol: &Symbol, symbol: &Symbol,
continuation: &'a Stmt<'a>, continuation: &'a Stmt<'a>,
) -> &'a Stmt<'a> { ) -> &'a Stmt<'a> {
let removed = match incremented_children.iter().next() { let removed = match incremented_children.map.iter().next() {
Some(s) => { Some((s, _)) => {
let s = *s; let s = *s;
incremented_children.remove(&s); incremented_children.pop(&s);
Some(s) Some(s)
} }
None => None, None => None,
@ -924,23 +984,20 @@ fn specialize_boxed<'a, 'i>(
*symbol, *symbol,
// If the symbol is unique: // If the symbol is unique:
// - free the box // - free the box
|_, _, _| { |_, _, continuation| {
arena.alloc(Stmt::Refcounting( arena.alloc(Stmt::Refcounting(
// TODO can be replaced by free if ever added to the IR. // TODO can be replaced by free if ever added to the IR.
ModifyRc::DecRef(*symbol), ModifyRc::DecRef(*symbol),
new_continuation, continuation,
)) ))
}, },
// If the symbol is not unique: // If the symbol is not unique:
// - increment the child // - increment the child
// - decref the box // - decref the box
|_, _, _| { |_, _, continuation| {
arena.alloc(Stmt::Refcounting( arena.alloc(Stmt::Refcounting(
ModifyRc::Inc(s, 1), ModifyRc::Inc(s, 1),
arena.alloc(Stmt::Refcounting( arena.alloc(Stmt::Refcounting(ModifyRc::DecRef(*symbol), continuation)),
ModifyRc::DecRef(*symbol),
new_continuation,
)),
)) ))
}, },
new_continuation, new_continuation,
@ -958,7 +1015,7 @@ fn specialize_list<'a, 'i>(
layout_interner: &'i mut STLayoutInterner<'a>, layout_interner: &'i mut STLayoutInterner<'a>,
ident_ids: &'i mut IdentIds, ident_ids: &'i mut IdentIds,
environment: &mut DropSpecializationEnvironment<'a>, environment: &mut DropSpecializationEnvironment<'a>,
incremented_children: &mut MutSet<Child>, incremented_children: &mut CountingMap<Child>,
symbol: &Symbol, symbol: &Symbol,
item_layout: InLayout, item_layout: InLayout,
continuation: &'a Stmt<'a>, continuation: &'a Stmt<'a>,
@ -993,7 +1050,7 @@ fn specialize_list<'a, 'i>(
for (child, i) in children_clone.iter().filter(|(_child, i)| *i == index) { for (child, i) in children_clone.iter().filter(|(_child, i)| *i == index) {
debug_assert!(length > *i); debug_assert!(length > *i);
let removed = incremented_children.remove(child); let removed = incremented_children.pop(child);
index_symbols.insert(index, (*child, removed)); index_symbols.insert(index, (*child, removed));
if removed { if removed {
@ -1214,7 +1271,6 @@ struct DropSpecializationEnvironment<'a> {
arena: &'a Bump, arena: &'a Bump,
home: ModuleId, home: ModuleId,
layout: InLayout<'a>, layout: InLayout<'a>,
target_info: TargetInfo,
symbol_layouts: MutMap<Symbol, InLayout<'a>>, symbol_layouts: MutMap<Symbol, InLayout<'a>>,
@ -1231,7 +1287,7 @@ struct DropSpecializationEnvironment<'a> {
list_children: MutMap<Parent, Vec<'a, (Child, Index)>>, list_children: MutMap<Parent, Vec<'a, (Child, Index)>>,
// Keeps track of all incremented symbols. // Keeps track of all incremented symbols.
incremented_symbols: MutMap<Symbol, u64>, incremented_symbols: CountingMap<Symbol>,
// Map containing the current known tag of a layout. // Map containing the current known tag of a layout.
symbol_tag: MutMap<Symbol, Tag>, symbol_tag: MutMap<Symbol, Tag>,
@ -1244,42 +1300,23 @@ struct DropSpecializationEnvironment<'a> {
} }
impl<'a> 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 { Self {
arena, arena,
home, home,
layout, layout,
target_info,
symbol_layouts: MutMap::default(), symbol_layouts: MutMap::default(),
struct_children: MutMap::default(), struct_children: MutMap::default(),
union_children: MutMap::default(), union_children: MutMap::default(),
box_children: MutMap::default(), box_children: MutMap::default(),
list_children: MutMap::default(), list_children: MutMap::default(),
incremented_symbols: MutMap::default(), incremented_symbols: CountingMap::new(),
symbol_tag: MutMap::default(), symbol_tag: MutMap::default(),
symbol_index: MutMap::default(), symbol_index: MutMap::default(),
list_length: 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 { fn create_symbol<'i>(&self, ident_ids: &'i mut IdentIds, debug_name: &str) -> Symbol {
let ident_id = ident_ids.add_str(debug_name); let ident_id = ident_ids.add_str(debug_name);
Symbol::new(self.home, ident_id) Symbol::new(self.home, ident_id)
@ -1316,12 +1353,16 @@ impl<'a> DropSpecializationEnvironment<'a> {
.push(child); .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) {
if let Some(index) = self.symbol_index.get(index) {
self.list_children self.list_children
.entry(parent) .entry(parent)
.or_insert_with(|| Vec::new_in(self.arena)) .or_insert_with(|| Vec::new_in(self.arena))
.push((child, *index)); .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.add_list_child(parent, child, *index)
} }
} }
@ -1346,39 +1387,6 @@ impl<'a> DropSpecializationEnvironment<'a> {
res res
} }
/**
Add a symbol for every increment performed.
*/
fn add_incremented(&mut self, symbol: Symbol, count: u64) {
self.incremented_symbols
.entry(symbol)
.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(0) => false,
Some(c) => {
*c -= 1;
true
}
None => false,
}
}
} }
/** /**
@ -1490,3 +1498,59 @@ fn low_level_no_rc(lowlevel: &LowLevel) -> RC {
} }
} }
} }
/// 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 pop(&mut self, key: &K) -> bool {
match self.map.get_mut(key) {
Some(0) => false,
Some(c) => {
*c -= 1;
true
}
None => false,
}
}
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

@ -1386,6 +1386,7 @@ pub struct Env<'a, 'i> {
pub abilities: AbilitiesView<'i>, pub abilities: AbilitiesView<'i>,
pub exposed_by_module: &'i ExposedByModule, pub exposed_by_module: &'i ExposedByModule,
pub derived_module: &'i SharedDerivedModule, pub derived_module: &'i SharedDerivedModule,
pub struct_indexing: UsageTrackingMap<(Symbol, u64), Symbol>,
} }
impl<'a, 'i> Env<'a, 'i> { impl<'a, 'i> Env<'a, 'i> {
@ -4225,6 +4226,7 @@ pub fn with_hole<'a>(
// If this symbol is a raw value, find the real name we gave to its specialized usage. // If this symbol is a raw value, find the real name we gave to its specialized usage.
if let ReuseSymbol::Value(_symbol) = can_reuse_symbol( if let ReuseSymbol::Value(_symbol) = can_reuse_symbol(
env, env,
layout_cache,
procs, procs,
&roc_can::expr::Expr::Var(symbol, variable), &roc_can::expr::Expr::Var(symbol, variable),
variable, variable,
@ -4324,7 +4326,7 @@ pub fn with_hole<'a>(
OpaqueRef { argument, .. } => { OpaqueRef { argument, .. } => {
let (arg_var, loc_arg_expr) = *argument; let (arg_var, loc_arg_expr) = *argument;
match can_reuse_symbol(env, procs, &loc_arg_expr.value, arg_var) { match can_reuse_symbol(env, layout_cache, procs, &loc_arg_expr.value, arg_var) {
// Opaques decay to their argument. // Opaques decay to their argument.
ReuseSymbol::Value(symbol) => { ReuseSymbol::Value(symbol) => {
let real_name = procs.get_or_insert_symbol_specialization( let real_name = procs.get_or_insert_symbol_specialization(
@ -4909,20 +4911,19 @@ pub fn with_hole<'a>(
RecordUpdate { RecordUpdate {
record_var, record_var,
symbol: structure, symbol: structure,
updates, ref updates,
.. ..
} => { } => {
use FieldType::*; use FieldType::*;
enum FieldType<'a> { enum FieldType<'a> {
CopyExisting(u64), CopyExisting,
UpdateExisting(&'a roc_can::expr::Field), UpdateExisting(&'a roc_can::expr::Field),
} }
// Strategy: turn a record update into the creation of a new record. // Strategy: turn a record update into the creation of a new record.
// This has the benefit that we don't need to do anything special for reference // This has the benefit that we don't need to do anything special for reference
// counting // counting
let sorted_fields_result = { let sorted_fields_result = {
let mut layout_env = layout::Env::from_components( let mut layout_env = layout::Env::from_components(
layout_cache, layout_cache,
@ -4938,43 +4939,56 @@ pub fn with_hole<'a>(
Err(_) => return runtime_error(env, "Can't update record with improper layout"), Err(_) => return runtime_error(env, "Can't update record with improper layout"),
}; };
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena); let single_field_struct = sorted_fields.len() == 1;
let mut symbols = Vec::with_capacity_in(sorted_fields.len(), env.arena); // The struct indexing generated by the current context
let mut current_struct_indexing = Vec::with_capacity_in(sorted_fields.len(), env.arena);
// The symbols that are used to create the new struct
let mut new_struct_symbols = Vec::with_capacity_in(sorted_fields.len(), env.arena);
// Information about the fields that are being updated
let mut fields = Vec::with_capacity_in(sorted_fields.len(), env.arena); let mut fields = Vec::with_capacity_in(sorted_fields.len(), env.arena);
let mut index = 0;
for (label, _, opt_field_layout) in sorted_fields.iter() {
let record_index = (structure, index);
let mut current = 0;
for (label, _, opt_field_layout) in sorted_fields.into_iter() {
match opt_field_layout { match opt_field_layout {
Err(_) => { Err(_) => {
debug_assert!(!updates.contains_key(&label)); debug_assert!(!updates.contains_key(label));
// this was an optional field, and now does not exist! // this was an optional field, and now does not exist!
// do not increment `current`! // do not increment `index`!
} }
Ok(field_layout) => { Ok(_field_layout) => {
field_layouts.push(field_layout); current_struct_indexing.push(record_index);
if let Some(field) = updates.get(&label) { // The struct with a single field is optimized in such a way that replacing later indexing will cause an incorrect IR.
let field_symbol = possible_reuse_symbol_or_specialize( // Thus, only insert these struct_indices if there is more than one field in the struct.
if !single_field_struct {
let original_struct_symbol = env.unique_symbol();
env.struct_indexing
.insert(record_index, original_struct_symbol);
}
if let Some(field) = updates.get(label) {
let new_struct_symbol = possible_reuse_symbol_or_specialize(
env, env,
procs, procs,
layout_cache, layout_cache,
&field.loc_expr.value, &field.loc_expr.value,
field.var, field.var,
); );
new_struct_symbols.push(new_struct_symbol);
fields.push(UpdateExisting(field)); fields.push(UpdateExisting(field));
symbols.push(field_symbol);
} else { } else {
fields.push(CopyExisting(current)); new_struct_symbols
symbols.push(env.unique_symbol()); .push(*env.struct_indexing.get(record_index).unwrap());
fields.push(CopyExisting);
} }
current += 1; index += 1;
} }
} }
} }
let symbols = symbols.into_bump_slice();
let new_struct_symbols = new_struct_symbols.into_bump_slice();
let record_layout = layout_cache let record_layout = layout_cache
.from_var(env.arena, record_var, env.subs) .from_var(env.arena, record_var, env.subs)
@ -4985,8 +4999,8 @@ pub fn with_hole<'a>(
_ => arena.alloc([record_layout]), _ => arena.alloc([record_layout]),
}; };
if symbols.len() == 1 { if single_field_struct {
// TODO we can probably special-case this more, skippiing the generation of // TODO we can probably special-case this more, skipping the generation of
// UpdateExisting // UpdateExisting
let mut stmt = hole.clone(); let mut stmt = hole.clone();
@ -4994,7 +5008,7 @@ pub fn with_hole<'a>(
match what_to_do { match what_to_do {
UpdateExisting(field) => { UpdateExisting(field) => {
substitute_in_exprs(env.arena, &mut stmt, assigned, symbols[0]); substitute_in_exprs(env.arena, &mut stmt, assigned, new_struct_symbols[0]);
stmt = assign_to_symbol( stmt = assign_to_symbol(
env, env,
@ -5002,11 +5016,11 @@ pub fn with_hole<'a>(
layout_cache, layout_cache,
field.var, field.var,
*field.loc_expr.clone(), *field.loc_expr.clone(),
symbols[0], new_struct_symbols[0],
stmt, stmt,
); );
} }
CopyExisting(_) => { CopyExisting => {
unreachable!( unreachable!(
r"when a record has just one field and is updated, it must update that one field" r"when a record has just one field and is updated, it must update that one field"
); );
@ -5015,12 +5029,10 @@ pub fn with_hole<'a>(
stmt stmt
} else { } else {
let expr = Expr::Struct(symbols); let expr = Expr::Struct(new_struct_symbols);
let mut stmt = Stmt::Let(assigned, expr, record_layout, hole); let mut stmt = Stmt::Let(assigned, expr, record_layout, hole);
let it = field_layouts.iter().zip(symbols.iter()).zip(fields); for (new_struct_symbol, what_to_do) in new_struct_symbols.iter().zip(fields) {
for ((field_layout, symbol), what_to_do) in it {
match what_to_do { match what_to_do {
UpdateExisting(field) => { UpdateExisting(field) => {
stmt = assign_to_symbol( stmt = assign_to_symbol(
@ -5029,11 +5041,17 @@ pub fn with_hole<'a>(
layout_cache, layout_cache,
field.var, field.var,
*field.loc_expr.clone(), *field.loc_expr.clone(),
*symbol, *new_struct_symbol,
stmt, stmt,
); );
} }
CopyExisting(index) => { CopyExisting => {
// When a field is copied, the indexing symbol is already placed in new_struct_symbols
// Thus, we don't need additional logic here.
}
}
}
let structure_needs_specialization = let structure_needs_specialization =
procs.ability_member_aliases.get(structure).is_some() procs.ability_member_aliases.get(structure).is_some()
|| procs.is_module_thunk(structure) || procs.is_module_thunk(structure)
@ -5041,20 +5059,23 @@ pub fn with_hole<'a>(
let specialized_structure_sym = if structure_needs_specialization { let specialized_structure_sym = if structure_needs_specialization {
// We need to specialize the record now; create a new one for it. // We need to specialize the record now; create a new one for it.
// TODO: reuse this symbol for all updates
env.unique_symbol() env.unique_symbol()
} else { } else {
// The record is already good. // The record is already good.
structure structure
}; };
for record_index in current_struct_indexing.into_iter().rev() {
if let Some(symbol) = env.struct_indexing.get_used(&record_index) {
let layout = field_layouts[record_index.1 as usize];
let access_expr = Expr::StructAtIndex { let access_expr = Expr::StructAtIndex {
structure: specialized_structure_sym, structure: specialized_structure_sym,
index, index: record_index.1,
field_layouts, field_layouts,
}; };
stmt = stmt = Stmt::Let(symbol, access_expr, layout, arena.alloc(stmt));
Stmt::Let(*symbol, access_expr, *field_layout, arena.alloc(stmt)); };
}
if structure_needs_specialization { if structure_needs_specialization {
stmt = specialize_symbol( stmt = specialize_symbol(
@ -5067,9 +5088,7 @@ pub fn with_hole<'a>(
structure, structure,
); );
} }
}
}
}
stmt stmt
} }
} }
@ -5227,7 +5246,7 @@ pub fn with_hole<'a>(
// re-use that symbol, and don't define its value again // re-use that symbol, and don't define its value again
let mut result; let mut result;
use ReuseSymbol::*; use ReuseSymbol::*;
match can_reuse_symbol(env, procs, &loc_expr.value, fn_var) { match can_reuse_symbol(env, layout_cache, procs, &loc_expr.value, fn_var) {
LocalFunction(_) => { LocalFunction(_) => {
unreachable!("if this was known to be a function, we would not be here") unreachable!("if this was known to be a function, we would not be here")
} }
@ -5685,14 +5704,19 @@ fn compile_struct_like<'a, L, UnusedLayout>(
// TODO how should function pointers be handled here? // TODO how should function pointers be handled here?
use ReuseSymbol::*; use ReuseSymbol::*;
match take_elem_expr(index) { match take_elem_expr(index) {
Some((var, loc_expr)) => match can_reuse_symbol(env, procs, &loc_expr.value, var) { Some((var, loc_expr)) => {
match can_reuse_symbol(env, layout_cache, procs, &loc_expr.value, var) {
Imported(symbol) | LocalFunction(symbol) | UnspecializedExpr(symbol) => { Imported(symbol) | LocalFunction(symbol) | UnspecializedExpr(symbol) => {
elem_symbols.push(symbol); elem_symbols.push(symbol);
can_elems.push(Field::FunctionOrUnspecialized(symbol, variable)); can_elems.push(Field::FunctionOrUnspecialized(symbol, variable));
} }
Value(symbol) => { Value(symbol) => {
let reusable = let reusable = procs.get_or_insert_symbol_specialization(
procs.get_or_insert_symbol_specialization(env, layout_cache, symbol, var); env,
layout_cache,
symbol,
var,
);
elem_symbols.push(reusable); elem_symbols.push(reusable);
can_elems.push(Field::ValueSymbol); can_elems.push(Field::ValueSymbol);
} }
@ -5700,7 +5724,8 @@ fn compile_struct_like<'a, L, UnusedLayout>(
elem_symbols.push(env.unique_symbol()); elem_symbols.push(env.unique_symbol());
can_elems.push(Field::Field(var, *loc_expr)); can_elems.push(Field::Field(var, *loc_expr));
} }
}, }
}
None => { None => {
// this field was optional, but not given // this field was optional, but not given
continue; continue;
@ -6816,7 +6841,7 @@ pub fn from_can<'a>(
store_specialized_expectation_lookups(env, [variable], &[spec_var]); store_specialized_expectation_lookups(env, [variable], &[spec_var]);
let symbol_is_reused = matches!( let symbol_is_reused = matches!(
can_reuse_symbol(env, procs, &loc_condition.value, variable), can_reuse_symbol(env, layout_cache, procs, &loc_condition.value, variable),
ReuseSymbol::Value(_) ReuseSymbol::Value(_)
); );
@ -7615,7 +7640,8 @@ enum ReuseSymbol {
fn can_reuse_symbol<'a>( fn can_reuse_symbol<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
procs: &Procs<'a>, layout_cache: &mut LayoutCache<'a>,
procs: &mut Procs<'a>,
expr: &roc_can::expr::Expr, expr: &roc_can::expr::Expr,
expr_var: Variable, expr_var: Variable,
) -> ReuseSymbol { ) -> ReuseSymbol {
@ -7627,6 +7653,52 @@ fn can_reuse_symbol<'a>(
late_resolve_ability_specialization(env, *member, *specialization_id, expr_var) late_resolve_ability_specialization(env, *member, *specialization_id, expr_var)
} }
Var(symbol, _) => *symbol, Var(symbol, _) => *symbol,
RecordAccess {
record_var,
field,
loc_expr,
..
} => {
let sorted_fields_result = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
layout::sort_record_fields(&mut layout_env, *record_var)
};
let sorted_fields = match sorted_fields_result {
Ok(fields) => fields,
Err(_) => unreachable!("Can't access record with improper layout"),
};
let index = sorted_fields
.into_iter()
.enumerate()
.find_map(|(current, (label, _, _))| (label == *field).then_some(current));
let struct_index = index.expect("field not in its own type");
let struct_symbol = possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&loc_expr.value,
*record_var,
);
match env
.struct_indexing
.get((struct_symbol, struct_index as u64))
{
Some(symbol) => *symbol,
None => {
return NotASymbol;
}
}
}
_ => return NotASymbol, _ => return NotASymbol,
}; };
@ -7660,7 +7732,7 @@ fn possible_reuse_symbol_or_specialize<'a>(
expr: &roc_can::expr::Expr, expr: &roc_can::expr::Expr,
var: Variable, var: Variable,
) -> Symbol { ) -> Symbol {
match can_reuse_symbol(env, procs, expr, var) { match can_reuse_symbol(env, layout_cache, procs, expr, var) {
ReuseSymbol::Value(symbol) => { ReuseSymbol::Value(symbol) => {
procs.get_or_insert_symbol_specialization(env, layout_cache, symbol, var) procs.get_or_insert_symbol_specialization(env, layout_cache, symbol, var)
} }
@ -7999,7 +8071,7 @@ fn assign_to_symbol<'a>(
result: Stmt<'a>, result: Stmt<'a>,
) -> Stmt<'a> { ) -> Stmt<'a> {
use ReuseSymbol::*; use ReuseSymbol::*;
match can_reuse_symbol(env, procs, &loc_arg.value, arg_var) { match can_reuse_symbol(env, layout_cache, procs, &loc_arg.value, arg_var) {
Imported(original) | LocalFunction(original) | UnspecializedExpr(original) => { Imported(original) | LocalFunction(original) | UnspecializedExpr(original) => {
// for functions we must make sure they are specialized correctly // for functions we must make sure they are specialized correctly
specialize_symbol( specialize_symbol(
@ -9983,3 +10055,42 @@ where
answer answer
} }
enum Usage {
Used,
Unused,
}
pub struct UsageTrackingMap<K, V> {
map: MutMap<K, (V, Usage)>,
}
impl<K, V> Default for UsageTrackingMap<K, V> {
fn default() -> Self {
Self {
map: MutMap::default(),
}
}
}
impl<K, V> UsageTrackingMap<K, V>
where
K: std::cmp::Eq + std::hash::Hash,
{
pub fn insert(&mut self, key: K, value: V) {
self.map.insert(key, (value, Usage::Unused));
}
pub fn get(&mut self, key: K) -> Option<&V> {
let (value, usage) = self.map.get_mut(&key)?;
*usage = Usage::Used;
Some(value)
}
fn get_used(&mut self, key: &K) -> Option<V> {
self.map.remove(key).and_then(|(value, usage)| match usage {
Usage::Used => Some(value),
Usage::Unused => None,
})
}
}

View File

@ -1842,7 +1842,7 @@ fn float_add_checked_fail() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen_dev"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn float_add_overflow() { fn float_add_overflow() {
assert_evals_to!( assert_evals_to!(
"1.7976931348623157e308 + 1.7976931348623157e308", "1.7976931348623157e308 + 1.7976931348623157e308",

View File

@ -597,7 +597,7 @@ fn top_level_destructure() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn linked_list_len_0() { fn linked_list_len_0() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -625,7 +625,7 @@ fn linked_list_len_0() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn linked_list_len_twice_0() { fn linked_list_len_twice_0() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -653,7 +653,7 @@ fn linked_list_len_twice_0() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn linked_list_len_1() { fn linked_list_len_1() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -681,7 +681,7 @@ fn linked_list_len_1() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn linked_list_len_twice_1() { fn linked_list_len_twice_1() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -709,7 +709,7 @@ fn linked_list_len_twice_1() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn linked_list_len_3() { fn linked_list_len_3() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -738,7 +738,7 @@ fn linked_list_len_3() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn linked_list_sum_num_a() { fn linked_list_sum_num_a() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -767,7 +767,7 @@ fn linked_list_sum_num_a() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn linked_list_sum_int() { fn linked_list_sum_int() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -795,7 +795,7 @@ fn linked_list_sum_int() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn linked_list_map() { fn linked_list_map() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -1244,7 +1244,7 @@ fn return_wrapped_closure() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn linked_list_is_singleton() { fn linked_list_is_singleton() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -1279,7 +1279,7 @@ fn linked_list_is_singleton() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn linked_list_is_empty_1() { fn linked_list_is_empty_1() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -1314,7 +1314,7 @@ fn linked_list_is_empty_1() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn linked_list_is_empty_2() { fn linked_list_is_empty_2() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -1346,7 +1346,7 @@ fn linked_list_is_empty_2() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn linked_list_singleton() { fn linked_list_singleton() {
// verifies only that valid llvm is produced // verifies only that valid llvm is produced
assert_evals_to!( assert_evals_to!(
@ -1712,7 +1712,7 @@ fn nested_pattern_match_two_ways() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn linked_list_guarded_double_pattern_match() { fn linked_list_guarded_double_pattern_match() {
// the important part here is that the first case (with the nested Cons) does not match // the important part here is that the first case (with the nested Cons) does not match
// TODO this also has undefined behavior // TODO this also has undefined behavior
@ -1744,7 +1744,7 @@ fn linked_list_guarded_double_pattern_match() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn linked_list_double_pattern_match() { fn linked_list_double_pattern_match() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(

View File

@ -2063,7 +2063,7 @@ fn non_unary_union_with_lambda_set_with_imported_toplevels_issue_4733() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn nullable_wrapped_with_non_nullable_singleton_tags() { fn nullable_wrapped_with_non_nullable_singleton_tags() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -2094,7 +2094,7 @@ fn nullable_wrapped_with_non_nullable_singleton_tags() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn nullable_wrapped_with_nullable_not_last_index() { fn nullable_wrapped_with_nullable_not_last_index() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -2102,9 +2102,9 @@ fn nullable_wrapped_with_nullable_not_last_index() {
app "test" provides [main] to "./platform" app "test" provides [main] to "./platform"
Parser : [ Parser : [
OneOrMore Parser,
Keyword Str,
CharLiteral, CharLiteral,
Keyword Str,
OneOrMore Parser,
] ]
toIdParser : Parser -> Str toIdParser : Parser -> Str

View File

@ -1,28 +1,28 @@
procedure Dict.1 (Dict.547): procedure Dict.1 (Dict.537):
let Dict.556 : List {[], []} = Array []; let Dict.546 : List {[], []} = Array [];
let Dict.563 : U64 = 0i64; let Dict.553 : U64 = 0i64;
let Dict.564 : U64 = 8i64; let Dict.554 : U64 = 8i64;
let Dict.557 : List U64 = CallByName List.11 Dict.563 Dict.564; let Dict.547 : List U64 = CallByName List.11 Dict.553 Dict.554;
let Dict.560 : I8 = CallByName Dict.37; let Dict.550 : I8 = CallByName Dict.36;
let Dict.561 : U64 = 8i64; let Dict.551 : U64 = 8i64;
let Dict.558 : List I8 = CallByName List.11 Dict.560 Dict.561; let Dict.548 : List I8 = CallByName List.11 Dict.550 Dict.551;
let Dict.559 : U64 = 0i64; let Dict.549 : U64 = 0i64;
let Dict.555 : {List {[], []}, List U64, List I8, U64} = Struct {Dict.556, Dict.557, Dict.558, Dict.559}; let Dict.545 : {List {[], []}, List U64, List I8, U64} = Struct {Dict.546, Dict.547, Dict.548, Dict.549};
ret Dict.555; ret Dict.545;
procedure Dict.37 (): procedure Dict.36 ():
let Dict.562 : I8 = -128i64; let Dict.552 : I8 = -128i64;
ret Dict.562; ret Dict.552;
procedure Dict.4 (Dict.553): procedure Dict.4 (Dict.543):
let Dict.99 : U64 = StructAtIndex 3 Dict.553; let Dict.97 : U64 = StructAtIndex 3 Dict.543;
let #Derived_gen.2 : List {[], []} = StructAtIndex 0 Dict.553; let #Derived_gen.2 : List {[], []} = StructAtIndex 0 Dict.543;
dec #Derived_gen.2; dec #Derived_gen.2;
let #Derived_gen.1 : List U64 = StructAtIndex 1 Dict.553; let #Derived_gen.1 : List U64 = StructAtIndex 1 Dict.543;
dec #Derived_gen.1; dec #Derived_gen.1;
let #Derived_gen.0 : List I8 = StructAtIndex 2 Dict.553; let #Derived_gen.0 : List I8 = StructAtIndex 2 Dict.543;
dec #Derived_gen.0; dec #Derived_gen.0;
ret Dict.99; ret Dict.97;
procedure List.11 (List.115, List.116): procedure List.11 (List.115, List.116):
let List.495 : List I8 = CallByName List.68 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; let Json.1906 : List U8 = CallByName List.8 Json.1907 Json.1908;
ret Json.1906; ret Json.1906;
else else
let Json.1948 : U64 = StructAtIndex 0 Json.186;
inc Json.184; 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.1947 : {List U8, List U8} = CallByName List.52 Json.184 Json.1948;
let Json.210 : List U8 = StructAtIndex 0 Json.1947; let Json.210 : List U8 = StructAtIndex 0 Json.1947;
let Json.212 : List U8 = StructAtIndex 1 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; let Json.1532 : List U8 = CallByName List.8 Json.1533 Json.1534;
ret Json.1532; ret Json.1532;
else else
let Json.1574 : U64 = StructAtIndex 0 Json.186;
inc Json.184; 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.1573 : {List U8, List U8} = CallByName List.52 Json.184 Json.1574;
let Json.210 : List U8 = StructAtIndex 0 Json.1573; let Json.210 : List U8 = StructAtIndex 0 Json.1573;
let Json.212 : List U8 = StructAtIndex 1 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; let Json.1532 : List U8 = CallByName List.8 Json.1533 Json.1534;
ret Json.1532; ret Json.1532;
else else
let Json.1574 : U64 = StructAtIndex 0 Json.186;
inc Json.184; 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.1573 : {List U8, List U8} = CallByName List.52 Json.184 Json.1574;
let Json.210 : List U8 = StructAtIndex 0 Json.1573; let Json.210 : List U8 = StructAtIndex 0 Json.1573;
let Json.212 : List U8 = StructAtIndex 1 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; let Json.1179 : List U8 = CallByName List.8 Json.1180 Json.1181;
ret Json.1179; ret Json.1179;
else else
let Json.1221 : U64 = StructAtIndex 0 Json.186;
inc Json.184; 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.1220 : {List U8, List U8} = CallByName List.52 Json.184 Json.1221;
let Json.210 : List U8 = StructAtIndex 0 Json.1220; let Json.210 : List U8 = StructAtIndex 0 Json.1220;
let Json.212 : List U8 = StructAtIndex 1 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; let Json.1220 : List U8 = CallByName List.8 Json.1221 Json.1222;
ret Json.1220; ret Json.1220;
else else
let Json.1262 : U64 = StructAtIndex 0 Json.186;
inc Json.184; 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.1261 : {List U8, List U8} = CallByName List.52 Json.184 Json.1262;
let Json.210 : List U8 = StructAtIndex 0 Json.1261; let Json.210 : List U8 = StructAtIndex 0 Json.1261;
let Json.212 : List U8 = StructAtIndex 1 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; let Json.1220 : List U8 = CallByName List.8 Json.1221 Json.1222;
ret Json.1220; ret Json.1220;
else else
let Json.1262 : U64 = StructAtIndex 0 Json.186;
inc Json.184; 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.1261 : {List U8, List U8} = CallByName List.52 Json.184 Json.1262;
let Json.210 : List U8 = StructAtIndex 0 Json.1261; let Json.210 : List U8 = StructAtIndex 0 Json.1261;
let Json.212 : List U8 = StructAtIndex 1 Json.1261; let Json.212 : List U8 = StructAtIndex 1 Json.1261;

View File

@ -1,13 +1,13 @@
procedure Test.1 (): procedure Test.1 ():
let Test.7 : U8 = 1i64; let Test.10 : U8 = 1i64;
let Test.8 : U8 = 2i64; let Test.11 : U8 = 2i64;
let Test.6 : {U8, U8} = Struct {Test.7, Test.8}; let Test.9 : {U8, U8} = Struct {Test.10, Test.11};
ret Test.6; ret Test.9;
procedure Test.0 (): procedure Test.0 ():
let Test.9 : {U8, U8} = CallByName Test.1; let Test.13 : {U8, U8} = CallByName Test.1;
let Test.3 : U8 = StructAtIndex 0 Test.9; let Test.4 : U8 = StructAtIndex 0 Test.13;
let Test.5 : {U8, U8} = CallByName Test.1; let Test.8 : {U8, U8} = CallByName Test.1;
let Test.4 : U8 = StructAtIndex 1 Test.5; let Test.6 : U8 = StructAtIndex 1 Test.8;
let Test.2 : List U8 = Array [Test.3, Test.4]; let Test.2 : List U8 = Array [Test.4, Test.6];
ret Test.2; ret Test.2;

View File

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

View File

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

View File

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

View File

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

View File

@ -30,6 +30,15 @@ procedure Test.0 ():
let Test.16 : Str = ""; let Test.16 : Str = "";
let Test.15 : [<r>C List *self, C Str] = TagId(1) Test.16; let Test.15 : [<r>C List *self, C Str] = TagId(1) Test.16;
let Test.13 : Int1 = CallByName Bool.11 Test.14 Test.15; let Test.13 : Int1 = CallByName Bool.11 Test.14 Test.15;
dec Test.15; joinpoint #Derived_gen.0:
dec Test.14; dec Test.14;
ret Test.13; 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): procedure Test.1 (Test.2):
let Test.28 : U64 = 0i64; 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.26 : [C {}, C I64] = CallByName List.2 Test.2 Test.28;
let Test.27 : U64 = 0i64; let Test.27 : U64 = 0i64;
inc Test.2;
let Test.25 : [C {}, C I64] = CallByName List.2 Test.2 Test.27; 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}; let Test.8 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.25, Test.26};
joinpoint Test.22: joinpoint Test.22:

View File

@ -0,0 +1,47 @@
procedure List.3 (List.104, List.105, List.106):
let List.503 : {List U64, U64} = CallByName List.64 List.104 List.105 List.106;
let List.502 : List U64 = StructAtIndex 0 List.503;
ret List.502;
procedure List.6 (#Attr.2):
let List.501 : U64 = lowlevel ListLen #Attr.2;
ret List.501;
procedure List.64 (List.101, List.102, List.103):
let List.500 : U64 = CallByName List.6 List.101;
let List.497 : Int1 = CallByName Num.22 List.102 List.500;
if List.497 then
let List.498 : {List U64, U64} = CallByName List.67 List.101 List.102 List.103;
ret List.498;
else
let List.496 : {List U64, U64} = Struct {List.101, List.103};
ret List.496;
procedure List.67 (#Attr.2, #Attr.3, #Attr.4):
let List.499 : {List U64, U64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4;
ret List.499;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.281 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.281;
procedure Test.1 (Test.2):
let Test.6 : List U64 = StructAtIndex 0 Test.2;
let Test.8 : List U64 = StructAtIndex 1 Test.2;
let Test.10 : List U64 = StructAtIndex 2 Test.2;
let Test.13 : U64 = 8i64;
let Test.14 : U64 = 8i64;
let Test.9 : List U64 = CallByName List.3 Test.8 Test.13 Test.14;
let Test.11 : U64 = 7i64;
let Test.12 : U64 = 7i64;
let Test.7 : List U64 = CallByName List.3 Test.6 Test.11 Test.12;
let Test.5 : {List U64, List U64, List U64} = Struct {Test.7, Test.9, Test.10};
ret Test.5;
procedure Test.0 ():
let Test.15 : List U64 = Array [];
let Test.16 : List U64 = Array [];
let Test.17 : List U64 = Array [];
let Test.4 : {List U64, List U64, List U64} = Struct {Test.15, Test.16, Test.17};
let Test.3 : {List U64, List U64, List U64} = CallByName Test.1 Test.4;
ret Test.3;

View File

@ -44,9 +44,8 @@ procedure Num.22 (#Attr.2, #Attr.3):
ret Num.283; ret Num.283;
procedure Test.1 (Test.2, Test.3, Test.4): 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; 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.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}; let Test.13 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.28, Test.29};
joinpoint Test.25: joinpoint Test.25:

View File

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

View File

@ -3050,3 +3050,31 @@ fn specialize_after_match() {
"# "#
) )
} }
#[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

@ -23,10 +23,10 @@ procedure Dep.0 ():
ret Dep.1; ret Dep.1;
procedure Test.0 (): procedure Test.0 ():
let Test.3 : Str = "http://www.example.com"; let Test.5 : {Str, Str} = CallByName Dep.0;
let Test.4 : {Str, Str} = CallByName Dep.0; let Test.2 : Str = StructAtIndex 0 Test.5;
let Test.2 : Str = StructAtIndex 0 Test.4; let #Derived_gen.0 : Str = StructAtIndex 1 Test.5;
let #Derived_gen.0 : Str = StructAtIndex 1 Test.4;
dec #Derived_gen.0; dec #Derived_gen.0;
let Test.1 : {Str, Str} = Struct {Test.2, Test.3}; let Test.4 : Str = "http://www.example.com";
let Test.1 : {Str, Str} = Struct {Test.2, Test.4};
ret Test.1; ret Test.1;

View File

@ -24,7 +24,7 @@ macro_rules! internal_error {
macro_rules! user_error { macro_rules! user_error {
($($arg:tt)*) => ({ ($($arg:tt)*) => ({
eprintln!("We ran into an issue while compiling your code."); eprintln!("We ran into an issue while compiling your code.");
eprintln!("Sadly, we don't havs a pretty error message for this case yet."); eprintln!("Sadly, we don't have a pretty error message for this case yet.");
eprintln!("If you can't figure out the problem from the context below, please reach out at: https://roc.zulipchat.com/"); eprintln!("If you can't figure out the problem from the context below, please reach out at: https://roc.zulipchat.com/");
eprintln!($($arg)*); eprintln!($($arg)*);
std::process::exit(1); std::process::exit(1);

View File

@ -1471,14 +1471,6 @@ fn surgery_elf_help(
} }
} }
} else { } else {
// Explicitly ignore some symbols that are currently always linked.
const ALWAYS_LINKED: &[&str] = &["__divti3", "__udivti3"];
match app_obj.symbol_by_index(index) {
Ok(sym) if ALWAYS_LINKED.contains(&sym.name().unwrap_or_default()) => {
continue
}
_ => {
internal_error!( internal_error!(
"Undefined Symbol in relocation, {:+x?}: {:+x?}", "Undefined Symbol in relocation, {:+x?}: {:+x?}",
rel, rel,
@ -1486,8 +1478,6 @@ fn surgery_elf_help(
); );
} }
} }
}
}
_ => { _ => {
internal_error!("Relocation target not yet support: {:+x?}", rel); internal_error!("Relocation target not yet support: {:+x?}", rel);

View File

@ -434,7 +434,19 @@ pub(crate) fn surgery_pe(executable_path: &Path, metadata_path: &Path, roc_app_b
relocation, relocation,
); );
} else { } else {
if *address == 0 && !name.starts_with("roc") { let is_ingested_compiler_rt = [
"__muloti4",
"__divti3",
"__udivti3",
"__modti3",
"__umodti3",
"__fixdfti",
"__fixsfti",
"__fixunsdfti",
"__fixunssfti",
]
.contains(&name.as_str());
if *address == 0 && !name.starts_with("roc") && !is_ingested_compiler_rt {
eprintln!( eprintln!(
"I don't know the address of the {} function! this may cause segfaults", "I don't know the address of the {} function! this may cause segfaults",
name name

View File

@ -13,6 +13,7 @@ use crate::tarball::Compression;
// let's try to avoid doing that. // let's try to avoid doing that.
const BROTLI_BUFFER_BYTES: usize = 8 * 1_000_000; // MB const BROTLI_BUFFER_BYTES: usize = 8 * 1_000_000; // MB
#[derive(Debug, PartialEq)]
pub struct PackageMetadata<'a> { pub struct PackageMetadata<'a> {
/// The BLAKE3 hash of the tarball's contents. Also the .tar filename on disk. /// The BLAKE3 hash of the tarball's contents. Also the .tar filename on disk.
pub content_hash: &'a str, pub content_hash: &'a str,
@ -29,13 +30,29 @@ pub struct PackageMetadata<'a> {
/// - .tar.br /// - .tar.br
const VALID_EXTENSION_SUFFIXES: [&str; 2] = [".gz", ".br"]; const VALID_EXTENSION_SUFFIXES: [&str; 2] = [".gz", ".br"];
#[derive(Debug)] /// Since the TLD (top level domain) `.zip` is now available, there is a new attack
/// vector where malicous URLs can be used to confuse the reader.
/// Example of a URL which would take you to example.zip:
/// https://github.comkuberneteskubernetesarchiverefstags@example.zip
/// roc employs a checksum mechanism to prevent tampering with packages.
/// Nevertheless we should avoid such issues earlier.
/// You can read more here: https://medium.com/@bobbyrsec/the-dangers-of-googles-zip-tld-5e1e675e59a5
const MISLEADING_CHARACTERS_IN_URL: [char; 5] = [
'@', // @ - For now we avoid usage of the @, to avoid the "tld zip" attack vector
'\u{2044}', // U+2044 == Fraction Slash
'\u{2215}', // U+2215 == Division Slash
'\u{FF0F}', // U+2215 == Fullwidth Solidus
'\u{29F8}', // U+29F8 == Big Solidus
];
#[derive(Debug, PartialEq)]
pub enum UrlProblem { pub enum UrlProblem {
InvalidExtensionSuffix(String), InvalidExtensionSuffix(String),
MissingTarExt, MissingTarExt,
InvalidFragment(String), InvalidFragment(String),
MissingHash, MissingHash,
MissingHttps, MissingHttps,
MisleadingCharacter,
} }
impl<'a> TryFrom<&'a str> for PackageMetadata<'a> { impl<'a> TryFrom<&'a str> for PackageMetadata<'a> {
@ -56,6 +73,14 @@ impl<'a> PackageMetadata<'a> {
} }
}; };
// Next, check if there are misleading characters in the URL
if url
.chars()
.any(|ch| MISLEADING_CHARACTERS_IN_URL.contains(&ch))
{
return Err(UrlProblem::MisleadingCharacter);
}
// Next, get the (optional) URL fragment, which must be a .roc filename // Next, get the (optional) URL fragment, which must be a .roc filename
let (without_fragment, fragment) = match without_protocol.rsplit_once('#') { let (without_fragment, fragment) = match without_protocol.rsplit_once('#') {
Some((before_fragment, fragment)) => { Some((before_fragment, fragment)) => {
@ -102,6 +127,105 @@ impl<'a> PackageMetadata<'a> {
} }
} }
#[test]
fn url_problem_missing_https() {
let expected = Err(UrlProblem::MissingHttps);
assert_eq!(PackageMetadata::try_from("http://example.com"), expected);
}
#[test]
fn url_problem_misleading_characters() {
let expected = Err(UrlProblem::MisleadingCharacter);
for misleading_character_example in [
"https://user:password@example.com/",
//"https://example.compath",
"https://example.com\u{2044}path",
//"https://example.compath",
"https://example.com\u{2215}path",
//"https://example.compath",
"https://example.com\u{ff0f}path",
//"https://example.compath",
"https://example.com\u{29f8}path",
] {
assert_eq!(
PackageMetadata::try_from(misleading_character_example),
expected
);
}
}
#[test]
fn url_problem_invalid_fragment_not_a_roc_file() {
let expected = Err(UrlProblem::InvalidFragment("filename.sh".to_string()));
assert_eq!(
PackageMetadata::try_from("https://example.com/#filename.sh"),
expected
);
}
#[test]
fn url_problem_invalid_fragment_empty_roc_filename() {
let expected = Err(UrlProblem::InvalidFragment(".roc".to_string()));
assert_eq!(
PackageMetadata::try_from("https://example.com/#.roc"),
expected
);
}
#[test]
fn url_problem_not_a_tar_url() {
let expected = Err(UrlProblem::MissingTarExt);
assert_eq!(
PackageMetadata::try_from("https://example.com/filename.zip"),
expected
);
}
#[test]
fn url_problem_invalid_tar_suffix() {
let expected = Err(UrlProblem::InvalidExtensionSuffix(".zip".to_string()));
assert_eq!(
PackageMetadata::try_from("https://example.com/filename.tar.zip"),
expected
);
}
#[test]
fn url_problem_missing_hash() {
let expected = Err(UrlProblem::MissingHash);
assert_eq!(
PackageMetadata::try_from("https://example.com/.tar.gz"),
expected
);
}
#[test]
fn url_without_fragment() {
let expected = Ok(PackageMetadata {
cache_subdir: "example.com/path",
content_hash: "hash",
root_module_filename: None,
});
assert_eq!(
PackageMetadata::try_from("https://example.com/path/hash.tar.gz"),
expected
);
}
#[test]
fn url_with_fragment() {
let expected = Ok(PackageMetadata {
cache_subdir: "example.com/path",
content_hash: "hash",
root_module_filename: Some("filename.roc"),
});
assert_eq!(
PackageMetadata::try_from("https://example.com/path/hash.tar.gz#filename.roc"),
expected
);
}
#[derive(Debug)] #[derive(Debug)]
pub enum Problem { pub enum Problem {
UnsupportedEncoding(String), UnsupportedEncoding(String),

View File

@ -656,49 +656,11 @@ nextNodeId = \rendered ->
eqRenderedTree : RenderedTree state, RenderedTree state -> Bool eqRenderedTree : RenderedTree state, RenderedTree state -> Bool
eqRenderedTree = \a, b -> eqRenderedTree = \a, b ->
(a.root == b.root) (a.root == b.root)
&& eqRenderedNodes a.nodes b.nodes && (a.nodes == b.nodes)
&& (List.len a.handlers == List.len b.handlers) && (List.len a.handlers == List.len b.handlers)
&& (a.deletedNodeCache == b.deletedNodeCache) && (a.deletedNodeCache == b.deletedNodeCache)
&& (a.deletedHandlerCache == b.deletedHandlerCache) && (a.deletedHandlerCache == b.deletedHandlerCache)
eqRenderedNodes : List (Result RenderedNode [DeletedNode]), List (Result RenderedNode [DeletedNode]) -> Bool
eqRenderedNodes = \a, b ->
List.map2 a b Tuple
|> List.all
(\t ->
when t is
Tuple (Ok x) (Ok y) -> eqRenderedNode x y
Tuple (Err x) (Err y) -> x == y
_ -> Bool.false)
eqRenderedNode : RenderedNode, RenderedNode -> Bool
eqRenderedNode = \a, b ->
when { a, b } is
{ a: RenderedNone, b: RenderedNone } ->
Bool.true
{ a: RenderedText aStr, b: RenderedText bStr } ->
aStr == bStr
{ a: RenderedElement aName aAttrs aChildren, b: RenderedElement bName bAttrs bChildren } ->
(aName == bName)
&& (aChildren == bChildren) # good enough for testing!
&& eqRenderedAttrs aAttrs bAttrs
_ -> Bool.false
eqRenderedAttrs : RenderedAttributes, RenderedAttributes -> Bool
eqRenderedAttrs = \a, b ->
eqAttrDict a.eventListeners b.eventListeners
&& eqAttrDict a.htmlAttrs b.htmlAttrs
&& eqAttrDict a.domProps b.domProps
&& eqAttrDict a.styles b.styles
eqAttrDict : Dict Str v, Dict Str v -> Bool | v implements Eq
eqAttrDict = \a, b ->
Dict.keys a
|> List.all \k -> Dict.get a k == Dict.get b k
# indexNodes # indexNodes
expect expect
html : Html {} html : Html {}
@ -713,12 +675,12 @@ expect
expected = { expected = {
nodes: [ nodes: [
RenderedText "Roc", RenderedText "Roc",
RenderedElement "a" { emptyRenderedAttrs & htmlAttrs: Dict.fromList [T "href" "https://www.roc-lang.org/"] } [0], RenderedElement "a" { emptyRenderedAttrs & htmlAttrs: Dict.fromList [("href", "https://www.roc-lang.org/")] } [0],
], ],
siblingIds: [1], siblingIds: [1],
} }
(List.map2 actual.nodes expected.nodes eqRenderedNode |> List.walk Bool.true Bool.and) (actual.nodes == expected.nodes)
&& (actual.siblingIds == expected.siblingIds) && (actual.siblingIds == expected.siblingIds)
# diff # diff
@ -822,7 +784,7 @@ expect
Ok (RenderedText "The app"), Ok (RenderedText "The app"),
Ok (RenderedElement "h1" emptyRenderedAttrs [0]), Ok (RenderedElement "h1" emptyRenderedAttrs [0]),
Ok (RenderedText "The answer is 42"), Ok (RenderedText "The answer is 42"),
Ok (RenderedElement "div" { emptyRenderedAttrs & eventListeners: Dict.fromList [T "click" { accessors: [], handlerId: 0 }] } [2]), Ok (RenderedElement "div" { emptyRenderedAttrs & eventListeners: Dict.fromList [("click", { accessors: [], handlerId: 0 })] } [2]),
Ok (RenderedElement "body" emptyRenderedAttrs [1, 3]), Ok (RenderedElement "body" emptyRenderedAttrs [1, 3]),
], ],
deletedNodeCache: [], deletedNodeCache: [],