From 33771ca2cf3d4dbaafb9b56da2ceec7ab2903f74 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Sat, 11 Mar 2023 11:40:53 -0500 Subject: [PATCH 01/54] Start working on new compiler design documentation --- crates/compiler/DESIGN.md | 159 ++++++++++++++++++++++++++++++++++++++ crates/compiler/README.md | 145 +--------------------------------- 2 files changed, 162 insertions(+), 142 deletions(-) create mode 100644 crates/compiler/DESIGN.md diff --git a/crates/compiler/DESIGN.md b/crates/compiler/DESIGN.md new file mode 100644 index 0000000000..815ffa2133 --- /dev/null +++ b/crates/compiler/DESIGN.md @@ -0,0 +1,159 @@ +# 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. + + + + +- [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) + + + +## 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 + + + +### Symbol Resolution + +### 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 diff --git a/crates/compiler/README.md b/crates/compiler/README.md index 7b59ffae58..28f983c3f1 100644 --- a/crates/compiler/README.md +++ b/crates/compiler/README.md @@ -1,147 +1,8 @@ # The Roc Compiler -Here's how the compiler is laid out. - -## Parsing - -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 - - - -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: - - +For an overview of the design and architecture of the compiler, see +[DESIGN.md](./DESIGN.md). If you want to dive into the +implementation or get some tips on debugging the compiler, see below ## Getting started with the code From 2c385bf60160d0be12dfc4db9b388b93f4b69b47 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 13 Mar 2023 14:07:42 +0000 Subject: [PATCH 02/54] Start a section on canonicalization --- crates/compiler/DESIGN.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/crates/compiler/DESIGN.md b/crates/compiler/DESIGN.md index 815ffa2133..be77a57225 100644 --- a/crates/compiler/DESIGN.md +++ b/crates/compiler/DESIGN.md @@ -74,10 +74,45 @@ of the scope an expression should be parsed in. Generally, failing to reach ## 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 From b0705a00ad627ad0890ff8bf665b88b292d2b59b Mon Sep 17 00:00:00 2001 From: "J.Teeuwissen" Date: Fri, 26 May 2023 15:56:18 +0200 Subject: [PATCH 03/54] saved info and added test --- .../compiler/mono/src/drop_specialization.rs | 66 +++++++++++++++---- .../drop_specialize_after_struct.txt | 6 ++ .../test_mono/generated/issue_4749.txt | 2 +- ..._4772_weakened_monomorphic_destructure.txt | 2 +- .../list_one_vs_one_spread_issue_4685.txt | 3 +- .../compiler/test_mono/generated/peano1.txt | 23 +++++-- .../polymorphic_expression_unification.txt | 15 ++++- crates/compiler/test_mono/src/tests.rs | 16 +++++ 8 files changed, 106 insertions(+), 27 deletions(-) create mode 100644 crates/compiler/test_mono/generated/drop_specialize_after_struct.txt diff --git a/crates/compiler/mono/src/drop_specialization.rs b/crates/compiler/mono/src/drop_specialization.rs index 61fcc63418..739b6a7ae3 100644 --- a/crates/compiler/mono/src/drop_specialization.rs +++ b/crates/compiler/mono/src/drop_specialization.rs @@ -18,8 +18,8 @@ use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_target::TargetInfo; use crate::ir::{ - BranchInfo, Call, CallType, Expr, JoinPointId, Literal, ModifyRc, Proc, ProcLayout, Stmt, - UpdateModeId, + BranchInfo, Call, CallType, Expr, JoinPointId, ListLiteralElement, Literal, ModifyRc, Proc, + ProcLayout, Stmt, UpdateModeId, }; use crate::layout::{ Builtin, InLayout, Layout, LayoutInterner, LayoutRepr, STLayoutInterner, UnionLayout, @@ -104,7 +104,7 @@ fn specialize_drops_stmt<'a, 'i>( _ => unreachable!("List get should have two arguments"), }; - environment.add_list_child(*structure, *binding, index); + environment.add_list_child_symbol(*structure, *binding, index); alloc_let_with_continuation!(environment) } @@ -125,9 +125,46 @@ fn specialize_drops_stmt<'a, 'i>( } } - Expr::Tag { tag_id, .. } => { + Expr::Tag { + tag_id, + arguments: children, + .. + } => { environment.symbol_tag.insert(*binding, *tag_id); + for (index, child) in children.iter().enumerate() { + environment.add_union_child(*binding, *child, *tag_id, index as u64); + } + + alloc_let_with_continuation!(environment) + } + Expr::Struct(children) => { + for (index, child) in children.iter().enumerate() { + environment.add_struct_child(*binding, *child, index as u64); + } + + alloc_let_with_continuation!(environment) + } + Expr::ExprBox { symbol: child } => { + environment.add_box_child(*binding, *child); + + alloc_let_with_continuation!(environment) + } + Expr::Array { + elems: children, .. + } => { + for (index, child) in + children + .iter() + .enumerate() + .filter_map(|(index, child)| match child { + ListLiteralElement::Literal(_) => None, + ListLiteralElement::Symbol(s) => Some((index, s)), + }) + { + environment.add_list_child(*binding, *child, index as u64); + } + alloc_let_with_continuation!(environment) } Expr::StructAtIndex { @@ -179,13 +216,10 @@ fn specialize_drops_stmt<'a, 'i>( } alloc_let_with_continuation!(environment) } - Expr::Struct(_) - | Expr::RuntimeErrorFunction(_) - | Expr::ExprBox { .. } + Expr::RuntimeErrorFunction(_) | Expr::NullPointer | Expr::GetTagId { .. } - | Expr::EmptyArray - | Expr::Array { .. } => { + | Expr::EmptyArray => { // Does nothing relevant to drop specialization. So we can just continue. alloc_let_with_continuation!(environment) } @@ -1222,12 +1256,16 @@ impl<'a> DropSpecializationEnvironment<'a> { .push(child); } - fn add_list_child(&mut self, parent: Parent, child: Child, index: &Symbol) { + fn add_list_child(&mut self, parent: Parent, child: Child, index: u64) { + self.list_children + .entry(parent) + .or_insert_with(|| Vec::new_in(self.arena)) + .push((child, index)); + } + + fn add_list_child_symbol(&mut self, parent: Parent, child: Child, index: &Symbol) { if let Some(index) = self.symbol_index.get(index) { - self.list_children - .entry(parent) - .or_insert_with(|| Vec::new_in(self.arena)) - .push((child, *index)); + self.add_list_child(parent, child, *index) } } diff --git a/crates/compiler/test_mono/generated/drop_specialize_after_struct.txt b/crates/compiler/test_mono/generated/drop_specialize_after_struct.txt new file mode 100644 index 0000000000..e0b69a232d --- /dev/null +++ b/crates/compiler/test_mono/generated/drop_specialize_after_struct.txt @@ -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; diff --git a/crates/compiler/test_mono/generated/issue_4749.txt b/crates/compiler/test_mono/generated/issue_4749.txt index 6d217e25fd..1d63f9ccca 100644 --- a/crates/compiler/test_mono/generated/issue_4749.txt +++ b/crates/compiler/test_mono/generated/issue_4749.txt @@ -832,7 +832,7 @@ procedure Test.3 (): let Test.7 : Str = "Roc"; let Test.6 : [C [C List U8, C ], C Str] = TagId(1) Test.7; let Test.5 : Int1 = CallByName Bool.11 Test.1 Test.6; - dec Test.6; + dec Test.7; expect Test.5; dec Test.0; dec Test.1; diff --git a/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt b/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt index 4d065de9d5..38d7128c04 100644 --- a/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt +++ b/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt @@ -865,7 +865,7 @@ procedure Test.12 (): let Test.16 : {List U8, I64} = Struct {Test.17, Test.18}; let Test.15 : [C Str, C {List U8, I64}] = TagId(1) Test.16; let Test.14 : Int1 = CallByName Bool.11 Test.10 Test.15; - dec Test.15; + dec Test.16; expect Test.14; dec Test.10; let Test.13 : {} = Struct {}; diff --git a/crates/compiler/test_mono/generated/list_one_vs_one_spread_issue_4685.txt b/crates/compiler/test_mono/generated/list_one_vs_one_spread_issue_4685.txt index e28549e81b..29f55a6889 100644 --- a/crates/compiler/test_mono/generated/list_one_vs_one_spread_issue_4685.txt +++ b/crates/compiler/test_mono/generated/list_one_vs_one_spread_issue_4685.txt @@ -5,7 +5,8 @@ procedure Test.0 (): let Test.9 : U64 = 1i64; let Test.10 : Int1 = lowlevel Eq Test.8 Test.9; if Test.10 then - dec Test.1; + dec Test.11; + decref Test.1; let Test.3 : Str = "B"; ret Test.3; else diff --git a/crates/compiler/test_mono/generated/peano1.txt b/crates/compiler/test_mono/generated/peano1.txt index 3e2bea837b..1d291bf98f 100644 --- a/crates/compiler/test_mono/generated/peano1.txt +++ b/crates/compiler/test_mono/generated/peano1.txt @@ -5,11 +5,20 @@ procedure Test.0 (): let Test.2 : [, C *self] = TagId(0) Test.13; let Test.10 : U8 = 1i64; let Test.11 : U8 = GetTagId Test.2; - dec Test.2; - let Test.12 : Int1 = lowlevel Eq Test.10 Test.11; - if Test.12 then - let Test.8 : I64 = 0i64; - ret Test.8; + joinpoint #Derived_gen.0: + let Test.12 : Int1 = lowlevel Eq Test.10 Test.11; + if Test.12 then + let Test.8 : I64 = 0i64; + ret Test.8; + else + let Test.9 : I64 = 1i64; + ret Test.9; + in + let #Derived_gen.1 : Int1 = lowlevel RefCountIsUnique Test.2; + if #Derived_gen.1 then + dec Test.13; + decref Test.2; + jump #Derived_gen.0; else - let Test.9 : I64 = 1i64; - ret Test.9; + decref Test.2; + jump #Derived_gen.0; diff --git a/crates/compiler/test_mono/generated/polymorphic_expression_unification.txt b/crates/compiler/test_mono/generated/polymorphic_expression_unification.txt index a84391b0ba..8984da0b47 100644 --- a/crates/compiler/test_mono/generated/polymorphic_expression_unification.txt +++ b/crates/compiler/test_mono/generated/polymorphic_expression_unification.txt @@ -30,6 +30,15 @@ procedure Test.0 (): let Test.16 : Str = ""; let Test.15 : [C List *self, C Str] = TagId(1) Test.16; let Test.13 : Int1 = CallByName Bool.11 Test.14 Test.15; - dec Test.15; - dec Test.14; - ret Test.13; + joinpoint #Derived_gen.0: + dec Test.14; + ret Test.13; + in + let #Derived_gen.1 : Int1 = lowlevel RefCountIsUnique Test.15; + if #Derived_gen.1 then + dec Test.16; + decref Test.15; + jump #Derived_gen.0; + else + decref Test.15; + jump #Derived_gen.0; diff --git a/crates/compiler/test_mono/src/tests.rs b/crates/compiler/test_mono/src/tests.rs index 89c1331d4b..638c631b31 100644 --- a/crates/compiler/test_mono/src/tests.rs +++ b/crates/compiler/test_mono/src/tests.rs @@ -3013,3 +3013,19 @@ fn rb_tree_fbip() { "# ) } + +#[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" + "# + ) +} From e8952dfc7192beb3cf9fa4796ae352b86747381f Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 26 May 2023 15:39:42 -0700 Subject: [PATCH 04/54] switch Dict to real tuples --- crates/compiler/builtins/roc/Dict.roc | 42 +++++++++++++-------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/crates/compiler/builtins/roc/Dict.roc b/crates/compiler/builtins/roc/Dict.roc index 485ef24364..097b094210 100644 --- a/crates/compiler/builtins/roc/Dict.roc +++ b/crates/compiler/builtins/roc/Dict.roc @@ -95,7 +95,7 @@ Dict k v := { # TODO: define Eq and Hash that are unordered. Only if value has hash/eq? metadata : List I8, dataIndices : List Nat, - data : List (T k v), + data : List (k, v), size : Nat, } | k has Hash & Eq has [ @@ -175,12 +175,12 @@ single = \k, v -> ## |> Dict.insert 2 "Two" ## |> Dict.insert 3 "Three" ## |> Dict.insert 4 "Four" -## |> Bool.isEq (Dict.fromList [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"]) +## |> Bool.isEq (Dict.fromList [(1, "One"), (2, "Two"), (3, "Three"), (4, "Four")]) ## ``` -fromList : List (T k v) -> Dict k v | k has Hash & Eq +fromList : List (k, v) -> Dict k v | k has Hash & Eq fromList = \data -> # TODO: make this efficient. Should just set data and then set all indicies in the hashmap. - List.walk data (empty {}) (\dict, T k v -> insert dict k v) + List.walk data (empty {}) (\dict, (k, v) -> insert dict k v) ## Returns the number of values in the dictionary. ## ``` @@ -238,7 +238,7 @@ clear = \@Dict { metadata, dataIndices, data } -> ## ``` walk : Dict k v, state, (state, k, v -> state) -> state | k has Hash & Eq walk = \@Dict { data }, initialState, transform -> - List.walk data initialState (\state, T k v -> transform state k v) + List.walk data initialState (\state, (k, v) -> transform state k v) ## Same as [Dict.walk], except you can stop walking early. ## @@ -270,7 +270,7 @@ walk = \@Dict { data }, initialState, transform -> ## ``` walkUntil : Dict k v, state, (state, k, v -> [Continue state, Break state]) -> state | k has Hash & Eq walkUntil = \@Dict { data }, initialState, transform -> - List.walkUntil data initialState (\state, T k v -> transform state k v) + List.walkUntil data initialState (\state, (k, v) -> transform state k v) ## Get the value for a given key. If there is a value for the specified key it ## will return [Ok value], otherwise return [Err KeyNotFound]. @@ -296,7 +296,7 @@ get = \@Dict { metadata, dataIndices, data }, key -> when findIndexHelper metadata dataIndices data h2Key key probe 0 is Ok index -> dataIndex = listGetUnsafe dataIndices index - (T _ v) = listGetUnsafe data dataIndex + (_, v) = listGetUnsafe data dataIndex Ok v @@ -353,7 +353,7 @@ insert = \@Dict { metadata, dataIndices, data, size }, key, value -> @Dict { metadata, dataIndices, - data: List.set data dataIndex (T key value), + data: List.set data dataIndex (key, value), size, } @@ -447,9 +447,9 @@ update = \dict, key, alter -> ## |> Dict.insert 3 "Three" ## |> Dict.insert 4 "Four" ## |> Dict.toList -## |> Bool.isEq [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"] +## |> Bool.isEq [(1, "One"), (2, "Two"), (3, "Three"), (4, "Four")] ## ``` -toList : Dict k v -> List (T k v) | k has Hash & Eq +toList : Dict k v -> List (k, v) | k has Hash & Eq toList = \@Dict { data } -> data @@ -466,7 +466,7 @@ toList = \@Dict { data } -> ## ``` keys : Dict k v -> List k | k has Hash & Eq keys = \@Dict { data } -> - List.map data (\T k _ -> k) + List.map data (\(k, _) -> k) ## Returns the values of a dictionary as a [List]. ## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead. @@ -481,7 +481,7 @@ keys = \@Dict { data } -> ## ``` values : Dict k v -> List v | k has Hash & Eq values = \@Dict { data } -> - List.map data (\T _ v -> v) + List.map data (\(_, v) -> v) ## Combine two dictionaries by keeping the [union](https://en.wikipedia.org/wiki/Union_(set_theory)) ## of all the key-value pairs. This means that all the key-value pairs in @@ -567,7 +567,7 @@ removeAll = \xs, ys -> swapAndUpdateDataIndex : Dict k v, Nat, Nat -> Dict k v | k has Hash & Eq swapAndUpdateDataIndex = \@Dict { metadata, dataIndices, data, size }, removedIndex, lastIndex -> - (T key _) = listGetUnsafe data lastIndex + (key, _) = listGetUnsafe data lastIndex hashKey = createLowLevelHasher PseudoRandSeed |> Hash.hash key @@ -603,7 +603,7 @@ insertNotFoundHelper = \@Dict { metadata, dataIndices, data, size }, key, value, probe = newProbe h1Key (div8 (List.len metadata)) index = nextEmptyOrDeletedHelper metadata probe 0 dataIndex = List.len data - nextData = List.append data (T key value) + nextData = List.append data (key, value) @Dict { metadata: List.set metadata index h2Key, @@ -629,7 +629,7 @@ nextEmptyOrDeletedHelper = \metadata, probe, offset -> # TODO: investigate if this needs to be split into more specific helper functions. # There is a chance that returning specific sub-info like the value would be faster. -findIndexHelper : List I8, List Nat, List (T k v), I8, k, Probe, Nat -> Result Nat [NotFound] | k has Hash & Eq +findIndexHelper : List I8, List Nat, List (k, v), I8, k, Probe, Nat -> Result Nat [NotFound] | k has Hash & Eq findIndexHelper = \metadata, dataIndices, data, h2Key, key, probe, offset -> # For finding a value, we must search past all deleted element tombstones. index = Num.addWrap (mul8 probe.slotIndex) offset @@ -642,7 +642,7 @@ findIndexHelper = \metadata, dataIndices, data, h2Key, key, probe, offset -> else if md == h2Key then # Potentially matching slot, check if the key is a match. dataIndex = listGetUnsafe dataIndices index - (T k _) = listGetUnsafe data dataIndex + (k, _) = listGetUnsafe data dataIndex if k == key then # We have a match, return its index. @@ -687,7 +687,7 @@ rehash = \@Dict { metadata, dataIndices, data, size } -> rehashHelper newDict metadata dataIndices data 0 -rehashHelper : Dict k v, List I8, List Nat, List (T k v), Nat -> Dict k v | k has Hash & Eq +rehashHelper : Dict k v, List I8, List Nat, List (k, v), Nat -> Dict k v | k has Hash & Eq rehashHelper = \dict, oldMetadata, oldDataIndices, oldData, index -> when List.get oldMetadata index is Ok md -> @@ -695,7 +695,7 @@ rehashHelper = \dict, oldMetadata, oldDataIndices, oldData, index -> if md >= 0 then # We have an actual element here dataIndex = listGetUnsafe oldDataIndices index - (T k _) = listGetUnsafe oldData dataIndex + (k, _) = listGetUnsafe oldData dataIndex insertForRehash dict k dataIndex else @@ -731,8 +731,6 @@ emptySlot = -128 deletedSlot : I8 deletedSlot = -2 -T k v : [T k v] - # Capacity must be a power of 2. # We still will use slots of 8 even though this version has no true slots. # We just move an element at a time. @@ -869,7 +867,7 @@ expect expect dict = - fromList [T 1u8 1u8, T 2 2, T 3 3] + fromList [(1u8, 1u8), (2, 2), (3, 3)] |> remove 1 |> remove 3 @@ -877,7 +875,7 @@ expect expect list = - fromList [T 1u8 1u8, T 2u8 2u8, T 3 3] + fromList [(1u8, 1u8), (2u8, 2u8), (3, 3)] |> remove 1 |> insert 0 0 |> remove 3 From 9eb7019e736d201dd2310c1b6269a4bc28e83802 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 26 May 2023 15:44:31 -0700 Subject: [PATCH 05/54] update mono and uitests --- crates/compiler/test_mono/generated/dict.txt | 40 ++++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/crates/compiler/test_mono/generated/dict.txt b/crates/compiler/test_mono/generated/dict.txt index a549ce50c7..ea2ba420a3 100644 --- a/crates/compiler/test_mono/generated/dict.txt +++ b/crates/compiler/test_mono/generated/dict.txt @@ -1,28 +1,28 @@ -procedure Dict.1 (Dict.547): - let Dict.556 : List {[], []} = Array []; - let Dict.563 : U64 = 0i64; - let Dict.564 : U64 = 8i64; - let Dict.557 : List U64 = CallByName List.11 Dict.563 Dict.564; - let Dict.560 : I8 = CallByName Dict.37; - let Dict.561 : U64 = 8i64; - let Dict.558 : List I8 = CallByName List.11 Dict.560 Dict.561; - let Dict.559 : U64 = 0i64; - let Dict.555 : {List {[], []}, List U64, List I8, U64} = Struct {Dict.556, Dict.557, Dict.558, Dict.559}; - ret Dict.555; +procedure Dict.1 (Dict.537): + let Dict.546 : List {[], []} = Array []; + let Dict.553 : U64 = 0i64; + let Dict.554 : U64 = 8i64; + let Dict.547 : List U64 = CallByName List.11 Dict.553 Dict.554; + let Dict.550 : I8 = CallByName Dict.36; + let Dict.551 : U64 = 8i64; + let Dict.548 : List I8 = CallByName List.11 Dict.550 Dict.551; + let Dict.549 : U64 = 0i64; + let Dict.545 : {List {[], []}, List U64, List I8, U64} = Struct {Dict.546, Dict.547, Dict.548, Dict.549}; + ret Dict.545; -procedure Dict.37 (): - let Dict.562 : I8 = -128i64; - ret Dict.562; +procedure Dict.36 (): + let Dict.552 : I8 = -128i64; + ret Dict.552; -procedure Dict.4 (Dict.553): - let Dict.99 : U64 = StructAtIndex 3 Dict.553; - let #Derived_gen.2 : List {[], []} = StructAtIndex 0 Dict.553; +procedure Dict.4 (Dict.543): + let Dict.97 : U64 = StructAtIndex 3 Dict.543; + let #Derived_gen.2 : List {[], []} = StructAtIndex 0 Dict.543; dec #Derived_gen.2; - let #Derived_gen.1 : List U64 = StructAtIndex 1 Dict.553; + let #Derived_gen.1 : List U64 = StructAtIndex 1 Dict.543; 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; - ret Dict.99; + ret Dict.97; procedure List.11 (List.115, List.116): let List.495 : List I8 = CallByName List.68 List.116; From c780bbbe35701f6b0b259b03813c45b573f7c6d9 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 26 May 2023 16:10:14 -0700 Subject: [PATCH 06/54] update test to newer Dict with tuple and eq --- .../platform/Html/Internal/Client.roc | 46 ++----------------- 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/examples/virtual-dom-wip/platform/Html/Internal/Client.roc b/examples/virtual-dom-wip/platform/Html/Internal/Client.roc index c69ed2ce67..73ac783263 100644 --- a/examples/virtual-dom-wip/platform/Html/Internal/Client.roc +++ b/examples/virtual-dom-wip/platform/Html/Internal/Client.roc @@ -656,49 +656,11 @@ nextNodeId = \rendered -> eqRenderedTree : RenderedTree state, RenderedTree state -> Bool eqRenderedTree = \a, b -> (a.root == b.root) - && eqRenderedNodes a.nodes b.nodes + && (a.nodes == b.nodes) && (List.len a.handlers == List.len b.handlers) && (a.deletedNodeCache == b.deletedNodeCache) && (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 has Eq -eqAttrDict = \a, b -> - Dict.keys a - |> List.all \k -> Dict.get a k == Dict.get b k - # indexNodes expect html : Html {} @@ -713,12 +675,12 @@ expect expected = { nodes: [ 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], } - (List.map2 actual.nodes expected.nodes eqRenderedNode |> List.walk Bool.true Bool.and) + (actual.nodes == expected.nodes) && (actual.siblingIds == expected.siblingIds) # diff @@ -822,7 +784,7 @@ expect Ok (RenderedText "The app"), Ok (RenderedElement "h1" emptyRenderedAttrs [0]), 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]), ], deletedNodeCache: [], From 378a298b45dd8bb4efc957e4980fdf8a4d802b6e Mon Sep 17 00:00:00 2001 From: "J.Teeuwissen" Date: Sat, 27 May 2023 14:42:37 +0200 Subject: [PATCH 07/54] move record index to start of update --- crates/compiler/load_internal/src/file.rs | 5 +- crates/compiler/mono/src/ir.rs | 297 ++++++++++++------ ...let_weakened_fields_referenced_in_list.txt | 18 +- .../test_mono/generated/issue_4759.txt | 16 +- .../test_mono/generated/record_update.txt | 47 +++ crates/compiler/test_mono/src/tests.rs | 14 + 6 files changed, 291 insertions(+), 106 deletions(-) create mode 100644 crates/compiler/test_mono/generated/record_update.txt diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index 6bd3289525..4bb27ee559 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -32,7 +32,7 @@ use roc_module::symbol::{ }; use roc_mono::ir::{ CapturedSymbols, ExternalSpecializations, GlueLayouts, LambdaSetId, PartialProc, Proc, - ProcLayout, Procs, ProcsBase, UpdateModeIds, + ProcLayout, Procs, ProcsBase, UpdateModeIds, UsageTrackingMap, }; use roc_mono::layout::LayoutInterner; use roc_mono::layout::{ @@ -5783,6 +5783,7 @@ fn make_specializations<'a>( abilities: AbilitiesView::World(&world_abilities), exposed_by_module, derived_module: &derived_module, + struct_indexing: UsageTrackingMap::new(), }; let mut procs = Procs::new_in(arena); @@ -5883,6 +5884,7 @@ fn build_pending_specializations<'a>( abilities: AbilitiesView::Module(&abilities_store), exposed_by_module, derived_module: &derived_module, + struct_indexing: UsageTrackingMap::new(), }; let layout_cache_snapshot = layout_cache.snapshot(); @@ -6364,6 +6366,7 @@ fn load_derived_partial_procs<'a>( abilities: AbilitiesView::World(world_abilities), exposed_by_module, derived_module, + struct_indexing: UsageTrackingMap::new(), }; let partial_proc = match derived_expr { diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 7c47bcbb8a..9b8e5cea4d 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -13,7 +13,7 @@ use roc_can::abilities::SpecializationId; use roc_can::expr::{AnnotatedMark, ClosureData, ExpectLookup}; use roc_can::module::ExposedByModule; use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap}; -use roc_collections::VecMap; +use roc_collections::{MutSet, VecMap}; use roc_debug_flags::dbg_do; #[cfg(debug_assertions)] use roc_debug_flags::{ @@ -1386,6 +1386,7 @@ pub struct Env<'a, 'i> { pub abilities: AbilitiesView<'i>, pub exposed_by_module: &'i ExposedByModule, pub derived_module: &'i SharedDerivedModule, + pub struct_indexing: UsageTrackingMap<(Symbol, u64), Symbol>, } 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 let ReuseSymbol::Value(_symbol) = can_reuse_symbol( env, + layout_cache, procs, &roc_can::expr::Expr::Var(symbol, variable), variable, @@ -4324,7 +4326,7 @@ pub fn with_hole<'a>( OpaqueRef { 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. ReuseSymbol::Value(symbol) => { let real_name = procs.get_or_insert_symbol_specialization( @@ -4909,13 +4911,13 @@ pub fn with_hole<'a>( RecordUpdate { record_var, symbol: structure, - updates, + ref updates, .. } => { use FieldType::*; enum FieldType<'a> { - CopyExisting(u64), + CopyExisting, UpdateExisting(&'a roc_can::expr::Field), } @@ -4938,43 +4940,55 @@ pub fn with_hole<'a>( 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 mut symbols = Vec::with_capacity_in(sorted_fields.len(), env.arena); + // The struct indexing generated by the current context + let mut current_struct_indexing = MutSet::default(); + // 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 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 { Err(_) => { debug_assert!(!updates.contains_key(&label)); // this was an optional field, and now does not exist! - // do not increment `current`! + // do not increment `index`! } - Ok(field_layout) => { - field_layouts.push(field_layout); + Ok(_field_layout) => { + current_struct_indexing.insert(record_index); + let original_struct_symbol = env.unique_symbol(); if let Some(field) = updates.get(&label) { - let field_symbol = possible_reuse_symbol_or_specialize( - env, - procs, - layout_cache, - &field.loc_expr.value, - field.var, - ); + // TODO this should probably used around here. + // let field_symbol = possible_reuse_symbol_or_specialize( + // env, + // procs, + // layout_cache, + // &field.loc_expr.value, + // field.var, + // ); + env.struct_indexing + .insert(record_index, original_struct_symbol); + let new_struct_symbol = env.unique_symbol(); + new_struct_symbols.push(new_struct_symbol); fields.push(UpdateExisting(field)); - symbols.push(field_symbol); } else { - fields.push(CopyExisting(current)); - symbols.push(env.unique_symbol()); + env.struct_indexing + .insert(record_index, original_struct_symbol); + new_struct_symbols + .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 .from_var(env.arena, record_var, env.subs) @@ -4985,8 +4999,8 @@ pub fn with_hole<'a>( _ => arena.alloc([record_layout]), }; - if symbols.len() == 1 { - // TODO we can probably special-case this more, skippiing the generation of + if new_struct_symbols.len() == 1 { + // TODO we can probably special-case this more, skipping the generation of // UpdateExisting let mut stmt = hole.clone(); @@ -4994,7 +5008,7 @@ pub fn with_hole<'a>( match what_to_do { 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( env, @@ -5002,11 +5016,11 @@ pub fn with_hole<'a>( layout_cache, field.var, *field.loc_expr.clone(), - symbols[0], + new_struct_symbols[0], stmt, ); } - CopyExisting(_) => { + CopyExisting => { unreachable!( r"when a record has just one field and is updated, it must update that one field" ); @@ -5015,12 +5029,12 @@ pub fn with_hole<'a>( stmt } else { - let expr = Expr::Struct(symbols); + let expr = Expr::Struct(new_struct_symbols); let mut stmt = Stmt::Let(assigned, expr, record_layout, hole); - let it = field_layouts.iter().zip(symbols.iter()).zip(fields); + let it = new_struct_symbols.iter().zip(fields); - for ((field_layout, symbol), what_to_do) in it { + for (new_struct_symbol, what_to_do) in it { match what_to_do { UpdateExisting(field) => { stmt = assign_to_symbol( @@ -5029,47 +5043,55 @@ pub fn with_hole<'a>( layout_cache, field.var, *field.loc_expr.clone(), - *symbol, + *new_struct_symbol, stmt, ); } - CopyExisting(index) => { - let structure_needs_specialization = - procs.ability_member_aliases.get(structure).is_some() - || procs.is_module_thunk(structure) - || procs.is_imported_module_thunk(structure); - - let specialized_structure_sym = if structure_needs_specialization { - // We need to specialize the record now; create a new one for it. - // TODO: reuse this symbol for all updates - env.unique_symbol() - } else { - // The record is already good. - structure - }; - - let access_expr = Expr::StructAtIndex { - structure: specialized_structure_sym, - index, - field_layouts, - }; - stmt = - Stmt::Let(*symbol, access_expr, *field_layout, arena.alloc(stmt)); - - if structure_needs_specialization { - stmt = specialize_symbol( - env, - procs, - layout_cache, - Some(record_var), - specialized_structure_sym, - env.arena.alloc(stmt), - structure, - ); - } - } + CopyExisting => {} } } + + let structure_needs_specialization = + procs.ability_member_aliases.get(structure).is_some() + || procs.is_module_thunk(structure) + || procs.is_imported_module_thunk(structure); + + let specialized_structure_sym = if structure_needs_specialization { + // We need to specialize the record now; create a new one for it. + env.unique_symbol() + } else { + // The record is already good. + structure + }; + + for record_index in current_struct_indexing.iter() { + let (symbol, usage) = env.struct_indexing.pop(record_index).unwrap(); + match usage { + Usage::Used => { + let layout = field_layouts[record_index.1 as usize]; + let access_expr = Expr::StructAtIndex { + structure: specialized_structure_sym, + index: record_index.1, + field_layouts, + }; + stmt = Stmt::Let(symbol, access_expr, layout, arena.alloc(stmt)); + } + Usage::Unused => {} + } + } + + if structure_needs_specialization { + stmt = specialize_symbol( + env, + procs, + layout_cache, + Some(record_var), + specialized_structure_sym, + env.arena.alloc(stmt), + structure, + ); + } + stmt } } @@ -5227,7 +5249,7 @@ pub fn with_hole<'a>( // re-use that symbol, and don't define its value again let mut result; 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(_) => { unreachable!("if this was known to be a function, we would not be here") } @@ -5685,22 +5707,28 @@ fn compile_struct_like<'a, L, UnusedLayout>( // TODO how should function pointers be handled here? use ReuseSymbol::*; match take_elem_expr(index) { - Some((var, loc_expr)) => match can_reuse_symbol(env, procs, &loc_expr.value, var) { - Imported(symbol) | LocalFunction(symbol) | UnspecializedExpr(symbol) => { - elem_symbols.push(symbol); - can_elems.push(Field::FunctionOrUnspecialized(symbol, variable)); + Some((var, loc_expr)) => { + match can_reuse_symbol(env, layout_cache, procs, &loc_expr.value, var) { + Imported(symbol) | LocalFunction(symbol) | UnspecializedExpr(symbol) => { + elem_symbols.push(symbol); + can_elems.push(Field::FunctionOrUnspecialized(symbol, variable)); + } + Value(symbol) => { + let reusable = procs.get_or_insert_symbol_specialization( + env, + layout_cache, + symbol, + var, + ); + elem_symbols.push(reusable); + can_elems.push(Field::ValueSymbol); + } + NotASymbol => { + elem_symbols.push(env.unique_symbol()); + can_elems.push(Field::Field(var, *loc_expr)); + } } - Value(symbol) => { - let reusable = - procs.get_or_insert_symbol_specialization(env, layout_cache, symbol, var); - elem_symbols.push(reusable); - can_elems.push(Field::ValueSymbol); - } - NotASymbol => { - elem_symbols.push(env.unique_symbol()); - can_elems.push(Field::Field(var, *loc_expr)); - } - }, + } None => { // this field was optional, but not given continue; @@ -6816,7 +6844,7 @@ pub fn from_can<'a>( store_specialized_expectation_lookups(env, [variable], &[spec_var]); 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(_) ); @@ -7615,7 +7643,8 @@ enum ReuseSymbol { fn can_reuse_symbol<'a>( env: &mut Env<'a, '_>, - procs: &Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + procs: &mut Procs<'a>, expr: &roc_can::expr::Expr, expr_var: Variable, ) -> ReuseSymbol { @@ -7627,6 +7656,61 @@ fn can_reuse_symbol<'a>( late_resolve_ability_specialization(env, *member, *specialization_id, expr_var) } 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, _, _))| { + if label == *field { + Some(current) + } else { + None + } + }, + ); + + 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, }; @@ -7660,7 +7744,7 @@ fn possible_reuse_symbol_or_specialize<'a>( expr: &roc_can::expr::Expr, var: Variable, ) -> Symbol { - match can_reuse_symbol(env, procs, expr, var) { + match can_reuse_symbol(env, layout_cache, procs, expr, var) { ReuseSymbol::Value(symbol) => { procs.get_or_insert_symbol_specialization(env, layout_cache, symbol, var) } @@ -7999,7 +8083,7 @@ fn assign_to_symbol<'a>( result: Stmt<'a>, ) -> Stmt<'a> { 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) => { // for functions we must make sure they are specialized correctly specialize_symbol( @@ -9983,3 +10067,40 @@ where answer } + +enum Usage { + Used, + Unused, +} + +pub struct UsageTrackingMap +where + K: std::cmp::Eq + std::hash::Hash, +{ + map: MutMap, +} + +impl UsageTrackingMap +where + K: std::cmp::Eq + std::hash::Hash, +{ + pub fn new() -> Self { + Self { + map: MutMap::default(), + } + } + + 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 pop(&mut self, key: &K) -> Option<(V, Usage)> { + self.map.remove(key) + } +} diff --git a/crates/compiler/test_mono/generated/issue_2535_let_weakened_fields_referenced_in_list.txt b/crates/compiler/test_mono/generated/issue_2535_let_weakened_fields_referenced_in_list.txt index 8296bb3412..d0b88def82 100644 --- a/crates/compiler/test_mono/generated/issue_2535_let_weakened_fields_referenced_in_list.txt +++ b/crates/compiler/test_mono/generated/issue_2535_let_weakened_fields_referenced_in_list.txt @@ -1,13 +1,13 @@ procedure Test.1 (): - let Test.7 : U8 = 1i64; - let Test.8 : U8 = 2i64; - let Test.6 : {U8, U8} = Struct {Test.7, Test.8}; - ret Test.6; + let Test.10 : U8 = 1i64; + let Test.11 : U8 = 2i64; + let Test.9 : {U8, U8} = Struct {Test.10, Test.11}; + ret Test.9; procedure Test.0 (): - let Test.9 : {U8, U8} = CallByName Test.1; - let Test.3 : U8 = StructAtIndex 0 Test.9; - let Test.5 : {U8, U8} = CallByName Test.1; - let Test.4 : U8 = StructAtIndex 1 Test.5; - let Test.2 : List U8 = Array [Test.3, Test.4]; + let Test.13 : {U8, U8} = CallByName Test.1; + let Test.4 : U8 = StructAtIndex 0 Test.13; + let Test.8 : {U8, U8} = CallByName Test.1; + let Test.6 : U8 = StructAtIndex 1 Test.8; + let Test.2 : List U8 = Array [Test.4, Test.6]; ret Test.2; diff --git a/crates/compiler/test_mono/generated/issue_4759.txt b/crates/compiler/test_mono/generated/issue_4759.txt index ba80e36d15..a429d92a25 100644 --- a/crates/compiler/test_mono/generated/issue_4759.txt +++ b/crates/compiler/test_mono/generated/issue_4759.txt @@ -1,13 +1,13 @@ procedure Test.1 (Test.2): dec Test.2; - let Test.7 : Str = "ux"; - let Test.8 : Str = "uy"; - let Test.6 : {Str, Str} = Struct {Test.7, Test.8}; - ret Test.6; + let Test.8 : Str = "ux"; + let Test.9 : Str = "uy"; + let Test.7 : {Str, Str} = Struct {Test.8, Test.9}; + ret Test.7; procedure Test.0 (): - let Test.10 : Str = "x"; - let Test.11 : Str = "y"; - let Test.9 : {Str, Str} = Struct {Test.10, Test.11}; - let Test.3 : {Str, Str} = CallByName Test.1 Test.9; + let Test.11 : Str = "x"; + let Test.12 : Str = "y"; + let Test.10 : {Str, Str} = Struct {Test.11, Test.12}; + let Test.3 : {Str, Str} = CallByName Test.1 Test.10; ret Test.3; diff --git a/crates/compiler/test_mono/generated/record_update.txt b/crates/compiler/test_mono/generated/record_update.txt new file mode 100644 index 0000000000..5f1e5a3559 --- /dev/null +++ b/crates/compiler/test_mono/generated/record_update.txt @@ -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.10 : List U64 = StructAtIndex 2 Test.2; + let Test.8 : List U64 = StructAtIndex 1 Test.2; + let Test.6 : List U64 = StructAtIndex 0 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; diff --git a/crates/compiler/test_mono/src/tests.rs b/crates/compiler/test_mono/src/tests.rs index 89c1331d4b..c762a82efa 100644 --- a/crates/compiler/test_mono/src/tests.rs +++ b/crates/compiler/test_mono/src/tests.rs @@ -3013,3 +3013,17 @@ fn rb_tree_fbip() { "# ) } + +#[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} + "# + ) +} From 16da790faca64634620c3ea9207660c900c24f11 Mon Sep 17 00:00:00 2001 From: "J.Teeuwissen" Date: Sat, 27 May 2023 14:52:25 +0200 Subject: [PATCH 08/54] Order by index + clippy --- crates/compiler/load_internal/src/file.rs | 6 ++--- crates/compiler/mono/src/ir.rs | 23 +++++++++++-------- .../test_mono/generated/record_update.txt | 4 ++-- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index 4bb27ee559..f3c5613e3e 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -5783,7 +5783,7 @@ fn make_specializations<'a>( abilities: AbilitiesView::World(&world_abilities), exposed_by_module, derived_module: &derived_module, - struct_indexing: UsageTrackingMap::new(), + struct_indexing: UsageTrackingMap::default(), }; let mut procs = Procs::new_in(arena); @@ -5884,7 +5884,7 @@ fn build_pending_specializations<'a>( abilities: AbilitiesView::Module(&abilities_store), exposed_by_module, derived_module: &derived_module, - struct_indexing: UsageTrackingMap::new(), + struct_indexing: UsageTrackingMap::default(), }; let layout_cache_snapshot = layout_cache.snapshot(); @@ -6366,7 +6366,7 @@ fn load_derived_partial_procs<'a>( abilities: AbilitiesView::World(world_abilities), exposed_by_module, derived_module, - struct_indexing: UsageTrackingMap::new(), + struct_indexing: UsageTrackingMap::default(), }; let partial_proc = match derived_expr { diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 9b8e5cea4d..1728f493e8 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -13,7 +13,7 @@ use roc_can::abilities::SpecializationId; use roc_can::expr::{AnnotatedMark, ClosureData, ExpectLookup}; use roc_can::module::ExposedByModule; use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap}; -use roc_collections::{MutSet, VecMap}; +use roc_collections::VecMap; use roc_debug_flags::dbg_do; #[cfg(debug_assertions)] use roc_debug_flags::{ @@ -4941,7 +4941,7 @@ pub fn with_hole<'a>( }; // The struct indexing generated by the current context - let mut current_struct_indexing = MutSet::default(); + 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 @@ -4952,15 +4952,15 @@ pub fn with_hole<'a>( match opt_field_layout { Err(_) => { - debug_assert!(!updates.contains_key(&label)); + debug_assert!(!updates.contains_key(label)); // this was an optional field, and now does not exist! // do not increment `index`! } Ok(_field_layout) => { - current_struct_indexing.insert(record_index); + current_struct_indexing.push(record_index); let original_struct_symbol = env.unique_symbol(); - if let Some(field) = updates.get(&label) { + if let Some(field) = updates.get(label) { // TODO this should probably used around here. // let field_symbol = possible_reuse_symbol_or_specialize( // env, @@ -5064,8 +5064,8 @@ pub fn with_hole<'a>( structure }; - for record_index in current_struct_indexing.iter() { - let (symbol, usage) = env.struct_indexing.pop(record_index).unwrap(); + for record_index in current_struct_indexing.into_iter().rev() { + let (symbol, usage) = env.struct_indexing.pop(&record_index).unwrap(); match usage { Usage::Used => { let layout = field_layouts[record_index.1 as usize]; @@ -10080,16 +10080,21 @@ where map: MutMap, } -impl UsageTrackingMap +impl Default for UsageTrackingMap where K: std::cmp::Eq + std::hash::Hash, { - pub fn new() -> Self { + fn default() -> Self { Self { map: MutMap::default(), } } +} +impl UsageTrackingMap +where + K: std::cmp::Eq + std::hash::Hash, +{ pub fn insert(&mut self, key: K, value: V) { self.map.insert(key, (value, Usage::Unused)); } diff --git a/crates/compiler/test_mono/generated/record_update.txt b/crates/compiler/test_mono/generated/record_update.txt index 5f1e5a3559..e937c6190d 100644 --- a/crates/compiler/test_mono/generated/record_update.txt +++ b/crates/compiler/test_mono/generated/record_update.txt @@ -26,9 +26,9 @@ procedure Num.22 (#Attr.2, #Attr.3): ret Num.281; procedure Test.1 (Test.2): - let Test.10 : List U64 = StructAtIndex 2 Test.2; - let Test.8 : List U64 = StructAtIndex 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; From 452bafc6166c43c1a0bf01e2f72bd1f6aa679b24 Mon Sep 17 00:00:00 2001 From: "J.Teeuwissen" Date: Sat, 27 May 2023 19:34:01 +0200 Subject: [PATCH 09/54] possible_reuse_symbol_or_specialize --- crates/compiler/mono/src/ir.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 1728f493e8..82b23334d9 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -4924,7 +4924,6 @@ pub fn with_hole<'a>( // 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 // counting - let sorted_fields_result = { let mut layout_env = layout::Env::from_components( layout_cache, @@ -4961,18 +4960,15 @@ pub fn with_hole<'a>( let original_struct_symbol = env.unique_symbol(); if let Some(field) = updates.get(label) { - // TODO this should probably used around here. - // let field_symbol = possible_reuse_symbol_or_specialize( - // env, - // procs, - // layout_cache, - // &field.loc_expr.value, - // field.var, - // ); - env.struct_indexing .insert(record_index, original_struct_symbol); - let new_struct_symbol = env.unique_symbol(); + let new_struct_symbol = possible_reuse_symbol_or_specialize( + env, + procs, + layout_cache, + &field.loc_expr.value, + field.var, + ); new_struct_symbols.push(new_struct_symbol); fields.push(UpdateExisting(field)); } else { From c6e7d56fb6ec29d5993bf9d7d0a13718a2d2cf1e Mon Sep 17 00:00:00 2001 From: "J.Teeuwissen" Date: Sat, 27 May 2023 21:12:59 +0200 Subject: [PATCH 10/54] For single update --- crates/compiler/mono/src/ir.rs | 43 ++++++++++--------- .../test_mono/generated/issue_4759.txt | 16 +++---- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 82b23334d9..22d344038b 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -4939,6 +4939,8 @@ pub fn with_hole<'a>( Err(_) => return runtime_error(env, "Can't update record with improper layout"), }; + let single_struct_field = sorted_fields.len() == 1; + // 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 @@ -4958,10 +4960,14 @@ pub fn with_hole<'a>( Ok(_field_layout) => { current_struct_indexing.push(record_index); - let original_struct_symbol = env.unique_symbol(); - 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. + // Thus, only insert these struct_indices if there is more than one field in the struct. + if !single_struct_field { + 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, procs, @@ -4972,8 +4978,6 @@ pub fn with_hole<'a>( new_struct_symbols.push(new_struct_symbol); fields.push(UpdateExisting(field)); } else { - env.struct_indexing - .insert(record_index, original_struct_symbol); new_struct_symbols .push(*env.struct_indexing.get(record_index).unwrap()); fields.push(CopyExisting); @@ -4995,7 +4999,7 @@ pub fn with_hole<'a>( _ => arena.alloc([record_layout]), }; - if new_struct_symbols.len() == 1 { + if single_struct_field { // TODO we can probably special-case this more, skipping the generation of // UpdateExisting let mut stmt = hole.clone(); @@ -5061,19 +5065,15 @@ pub fn with_hole<'a>( }; for record_index in current_struct_indexing.into_iter().rev() { - let (symbol, usage) = env.struct_indexing.pop(&record_index).unwrap(); - match usage { - Usage::Used => { - let layout = field_layouts[record_index.1 as usize]; - let access_expr = Expr::StructAtIndex { - structure: specialized_structure_sym, - index: record_index.1, - field_layouts, - }; - stmt = Stmt::Let(symbol, access_expr, layout, arena.alloc(stmt)); - } - Usage::Unused => {} - } + 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 { + structure: specialized_structure_sym, + index: record_index.1, + field_layouts, + }; + stmt = Stmt::Let(symbol, access_expr, layout, arena.alloc(stmt)); + }; } if structure_needs_specialization { @@ -10101,7 +10101,10 @@ where Some(value) } - fn pop(&mut self, key: &K) -> Option<(V, Usage)> { - self.map.remove(key) + fn get_used(&mut self, key: &K) -> Option { + self.map.remove(key).and_then(|(value, usage)| match usage { + Usage::Used => Some(value), + Usage::Unused => None, + }) } } diff --git a/crates/compiler/test_mono/generated/issue_4759.txt b/crates/compiler/test_mono/generated/issue_4759.txt index a429d92a25..ba80e36d15 100644 --- a/crates/compiler/test_mono/generated/issue_4759.txt +++ b/crates/compiler/test_mono/generated/issue_4759.txt @@ -1,13 +1,13 @@ procedure Test.1 (Test.2): dec Test.2; - let Test.8 : Str = "ux"; - let Test.9 : Str = "uy"; - let Test.7 : {Str, Str} = Struct {Test.8, Test.9}; - ret Test.7; + let Test.7 : Str = "ux"; + let Test.8 : Str = "uy"; + let Test.6 : {Str, Str} = Struct {Test.7, Test.8}; + ret Test.6; procedure Test.0 (): - let Test.11 : Str = "x"; - let Test.12 : Str = "y"; - let Test.10 : {Str, Str} = Struct {Test.11, Test.12}; - let Test.3 : {Str, Str} = CallByName Test.1 Test.10; + let Test.10 : Str = "x"; + let Test.11 : Str = "y"; + let Test.9 : {Str, Str} = Struct {Test.10, Test.11}; + let Test.3 : {Str, Str} = CallByName Test.1 Test.9; ret Test.3; From 576558b9d05b709a42e12e228a61dbc95f58f35c Mon Sep 17 00:00:00 2001 From: "J.Teeuwissen" Date: Sun, 28 May 2023 09:44:27 +0200 Subject: [PATCH 11/54] update test --- .../tests/specialize/record_update_between_modules.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt b/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt index 533e757884..a126e60f37 100644 --- a/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt +++ b/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt @@ -23,10 +23,10 @@ procedure Dep.0 (): ret Dep.1; procedure Test.0 (): - let Test.3 : Str = "http://www.example.com"; - let Test.4 : {Str, Str} = CallByName Dep.0; - let Test.2 : Str = StructAtIndex 0 Test.4; - let #Derived_gen.0 : Str = StructAtIndex 1 Test.4; + let Test.5 : {Str, Str} = CallByName Dep.0; + let Test.2 : Str = StructAtIndex 0 Test.5; + let #Derived_gen.0 : Str = StructAtIndex 1 Test.5; 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; From d988ab5378bba091e686c0288c0b469ca8ec8988 Mon Sep 17 00:00:00 2001 From: "J.Teeuwissen" Date: Sun, 28 May 2023 20:09:43 +0200 Subject: [PATCH 12/54] newlines --- crates/compiler/test_mono/src/tests.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/compiler/test_mono/src/tests.rs b/crates/compiler/test_mono/src/tests.rs index e7efdff6fa..efc25f6c78 100644 --- a/crates/compiler/test_mono/src/tests.rs +++ b/crates/compiler/test_mono/src/tests.rs @@ -3019,14 +3019,18 @@ fn specialize_after_match() { indoc!( r#" app "test" provides [main] to "./platform" + main = listA : LinkedList Str listA = Nil listB : LinkedList Str listB = Nil + longestLinkedList listA listB + LinkedList a : [Cons a (LinkedList a), Nil] + longestLinkedList : LinkedList a, LinkedList a -> Nat longestLinkedList = \listA, listB -> when listA is Nil -> linkedListLength listB From ffc19ec2a25eae03d5285ebb649387441a301bfb Mon Sep 17 00:00:00 2001 From: "J.Teeuwissen" Date: Sun, 28 May 2023 20:21:10 +0200 Subject: [PATCH 13/54] missing arm --- crates/compiler/mono/src/drop_specialization.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/compiler/mono/src/drop_specialization.rs b/crates/compiler/mono/src/drop_specialization.rs index 6e4a87805c..d9a1ea9df9 100644 --- a/crates/compiler/mono/src/drop_specialization.rs +++ b/crates/compiler/mono/src/drop_specialization.rs @@ -1443,6 +1443,7 @@ fn low_level_no_rc(lowlevel: &LowLevel) -> RC { ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrGraphemes | StrCountUtf8Bytes | StrGetCapacity | ListGetCapacity => RC::NoRc, ListWithCapacity | StrWithCapacity => RC::NoRc, + DictPseudoSeed => RC::NoRc, ListReplaceUnsafe => RC::Rc, StrGetUnsafe | ListGetUnsafe => RC::NoRc, ListConcat => RC::Rc, @@ -1502,6 +1503,7 @@ fn low_level_no_rc(lowlevel: &LowLevel) -> RC { | NumCountLeadingZeroBits | NumCountTrailingZeroBits | NumCountOneBits => RC::NoRc, + I128OfDec => RC::NoRc, NumBytesToU16 => RC::NoRc, NumBytesToU32 => RC::NoRc, NumBytesToU64 => RC::NoRc, From 8f022d431015e82de9f676e6ecf15a7c69d50593 Mon Sep 17 00:00:00 2001 From: "J.Teeuwissen" Date: Sun, 28 May 2023 21:18:28 +0200 Subject: [PATCH 14/54] fixed specialisation box --- crates/compiler/mono/src/drop_specialization.rs | 11 ++++------- crates/compiler/test_mono/src/tests.rs | 4 ++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/crates/compiler/mono/src/drop_specialization.rs b/crates/compiler/mono/src/drop_specialization.rs index d9a1ea9df9..1b200508ef 100644 --- a/crates/compiler/mono/src/drop_specialization.rs +++ b/crates/compiler/mono/src/drop_specialization.rs @@ -958,23 +958,20 @@ fn specialize_boxed<'a, 'i>( *symbol, // If the symbol is unique: // - free the box - |_, _, _| { + |_, _, continuation| { arena.alloc(Stmt::Refcounting( // TODO can be replaced by free if ever added to the IR. ModifyRc::DecRef(*symbol), - new_continuation, + continuation, )) }, // If the symbol is not unique: // - increment the child // - decref the box - |_, _, _| { + |_, _, continuation| { arena.alloc(Stmt::Refcounting( ModifyRc::Inc(s, 1), - arena.alloc(Stmt::Refcounting( - ModifyRc::DecRef(*symbol), - new_continuation, - )), + arena.alloc(Stmt::Refcounting(ModifyRc::DecRef(*symbol), continuation)), )) }, new_continuation, diff --git a/crates/compiler/test_mono/src/tests.rs b/crates/compiler/test_mono/src/tests.rs index efc25f6c78..b17d9c9ad3 100644 --- a/crates/compiler/test_mono/src/tests.rs +++ b/crates/compiler/test_mono/src/tests.rs @@ -3028,9 +3028,9 @@ fn specialize_after_match() { listB = Nil longestLinkedList listA listB - + LinkedList a : [Cons a (LinkedList a), Nil] - + longestLinkedList : LinkedList a, LinkedList a -> Nat longestLinkedList = \listA, listB -> when listA is Nil -> linkedListLength listB From dbebaf17a6cbb40cf266447f95a9960953e6fad1 Mon Sep 17 00:00:00 2001 From: "J.Teeuwissen" Date: Sun, 28 May 2023 23:10:24 +0200 Subject: [PATCH 15/54] using CountingMap for children --- .../compiler/mono/src/drop_specialization.rs | 149 +++++++++++------- 1 file changed, 90 insertions(+), 59 deletions(-) diff --git a/crates/compiler/mono/src/drop_specialization.rs b/crates/compiler/mono/src/drop_specialization.rs index 1b200508ef..9f00211514 100644 --- a/crates/compiler/mono/src/drop_specialization.rs +++ b/crates/compiler/mono/src/drop_specialization.rs @@ -27,7 +27,7 @@ use crate::layout::{ use bumpalo::Bump; -use roc_collections::{MutMap, MutSet}; +use roc_collections::MutMap; /** Try to find increments of symbols followed by decrements of the symbol they were indexed out of (their parent). @@ -317,10 +317,12 @@ fn specialize_drops_stmt<'a, 'i>( }; // 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 .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() .unwrap(); @@ -334,10 +336,14 @@ fn specialize_drops_stmt<'a, 'i>( let symbol_differences = environment .incremented_symbols + .map .iter() .filter_map(|(symbol, count)| { - let branch_count = - $branch_env.incremented_symbols.get(symbol).unwrap_or(&0); + let branch_count = $branch_env + .incremented_symbols + .map + .get(symbol) + .unwrap_or(&0); match branch_count - count { 0 => None, @@ -375,6 +381,7 @@ fn specialize_drops_stmt<'a, 'i>( // Remove all 0 counts as cleanup. environment .incremented_symbols + .map .retain(|_, count| *count > 0); arena.alloc(Stmt::Switch { @@ -388,10 +395,12 @@ fn specialize_drops_stmt<'a, 'i>( Stmt::Ret(symbol) => arena.alloc(Stmt::Ret(*symbol)), Stmt::Refcounting(rc, continuation) => match rc { ModifyRc::Inc(symbol, count) => { - let any = environment.any_incremented(symbol); + let any = environment.incremented_symbols.contains(symbol); // Add a symbol for every increment performed. - environment.add_incremented(*symbol, *count); + environment + .incremented_symbols + .insert_count(*symbol, *count); let new_continuation = specialize_drops_stmt( arena, @@ -406,7 +415,12 @@ fn specialize_drops_stmt<'a, 'i>( // Or there are no increments left, so we can just continue. new_continuation } else { - match environment.get_incremented(symbol) { + match environment + .incremented_symbols + .map + .remove(symbol) + .unwrap_or(0) + { // This is the first increment, but all increments are consumed. So don't insert any. 0 => new_continuation, // We still need to do some increments. @@ -427,7 +441,7 @@ fn specialize_drops_stmt<'a, 'i>( // dec a // dec b - if environment.pop_incremented(symbol) { + if environment.incremented_symbols.pop(symbol) { // This decremented symbol was incremented before, so we can remove it. specialize_drops_stmt( arena, @@ -445,10 +459,10 @@ fn specialize_drops_stmt<'a, 'i>( // As a might get dropped as a result of the decrement of b. let mut incremented_children = { 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() { - if environment.pop_incremented(&child) { + if environment.incremented_symbols.pop(&child) { incremented_children.insert(child); } else { todo_children.extend(environment.get_children(&child)); @@ -519,8 +533,10 @@ fn specialize_drops_stmt<'a, 'i>( }; // Add back the increments for the children to the environment. - for child_symbol in incremented_children.iter() { - environment.add_incremented(*child_symbol, 1) + for (child_symbol, symbol_count) in incremented_children.map.into_iter() { + environment + .incremented_symbols + .insert_count(child_symbol, symbol_count) } updated_stmt @@ -638,7 +654,7 @@ fn specialize_struct<'a, 'i>( environment: &mut DropSpecializationEnvironment<'a>, symbol: &Symbol, struct_layout: &'a [InLayout], - incremented_children: &mut MutSet, + incremented_children: &mut CountingMap, continuation: &'a Stmt<'a>, ) -> &'a Stmt<'a> { match environment.struct_children.get(symbol) { @@ -654,7 +670,7 @@ fn specialize_struct<'a, 'i>( for (index, _layout) in struct_layout.iter().enumerate() { for (child, _i) in children_clone.iter().filter(|(_, i)| *i == index as u64) { - let removed = incremented_children.remove(child); + let removed = incremented_children.pop(child); index_symbols.insert(index, (*child, removed)); if removed { @@ -727,7 +743,7 @@ fn specialize_union<'a, 'i>( environment: &mut DropSpecializationEnvironment<'a>, symbol: &Symbol, union_layout: UnionLayout<'a>, - incremented_children: &mut MutSet, + incremented_children: &mut CountingMap, continuation: &'a Stmt<'a>, ) -> &'a Stmt<'a> { let current_tag = environment.symbol_tag.get(symbol).copied(); @@ -770,7 +786,7 @@ fn specialize_union<'a, 'i>( { debug_assert_eq!(tag, *t); - let removed = incremented_children.remove(child); + let removed = incremented_children.pop(child); index_symbols.insert(index, (*child, removed)); if removed { @@ -932,14 +948,14 @@ fn specialize_boxed<'a, 'i>( layout_interner: &'i mut STLayoutInterner<'a>, ident_ids: &'i mut IdentIds, environment: &mut DropSpecializationEnvironment<'a>, - incremented_children: &mut MutSet, + incremented_children: &mut CountingMap, symbol: &Symbol, continuation: &'a Stmt<'a>, ) -> &'a Stmt<'a> { - let removed = match incremented_children.iter().next() { - Some(s) => { + let removed = match incremented_children.map.iter().next() { + Some((s, _)) => { let s = *s; - incremented_children.remove(&s); + incremented_children.pop(&s); Some(s) } None => None, @@ -989,7 +1005,7 @@ fn specialize_list<'a, 'i>( layout_interner: &'i mut STLayoutInterner<'a>, ident_ids: &'i mut IdentIds, environment: &mut DropSpecializationEnvironment<'a>, - incremented_children: &mut MutSet, + incremented_children: &mut CountingMap, symbol: &Symbol, item_layout: InLayout, continuation: &'a Stmt<'a>, @@ -1024,7 +1040,7 @@ fn specialize_list<'a, 'i>( for (child, i) in children_clone.iter().filter(|(_child, i)| *i == index) { debug_assert!(length > *i); - let removed = incremented_children.remove(child); + let removed = incremented_children.pop(child); index_symbols.insert(index, (*child, removed)); if removed { @@ -1262,7 +1278,7 @@ struct DropSpecializationEnvironment<'a> { list_children: MutMap>, // Keeps track of all incremented symbols. - incremented_symbols: MutMap, + incremented_symbols: CountingMap, // Map containing the current known tag of a layout. symbol_tag: MutMap, @@ -1286,7 +1302,7 @@ impl<'a> DropSpecializationEnvironment<'a> { union_children: MutMap::default(), box_children: MutMap::default(), list_children: MutMap::default(), - incremented_symbols: MutMap::default(), + incremented_symbols: CountingMap::new(), symbol_tag: MutMap::default(), symbol_index: MutMap::default(), list_length: MutMap::default(), @@ -1304,7 +1320,7 @@ impl<'a> DropSpecializationEnvironment<'a> { union_children: self.union_children.clone(), box_children: self.box_children.clone(), list_children: self.list_children.clone(), - incremented_symbols: MutMap::default(), + incremented_symbols: CountingMap::new(), symbol_tag: self.symbol_tag.clone(), symbol_index: self.symbol_index.clone(), list_length: self.list_length.clone(), @@ -1381,39 +1397,6 @@ impl<'a> DropSpecializationEnvironment<'a> { res } - - /** - Add a symbol for every increment performed. - */ - fn add_incremented(&mut self, symbol: Symbol, count: u64) { - self.incremented_symbols - .entry(symbol) - .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, - } - } } /** @@ -1525,3 +1508,51 @@ fn low_level_no_rc(lowlevel: &LowLevel) -> RC { } } } + +#[derive(Clone)] +struct CountingMap +where + K: Eq + std::hash::Hash, +{ + map: MutMap, +} + +impl CountingMap +where + K: Eq + std::hash::Hash, +{ + fn new() -> Self { + Self { + map: MutMap::default(), + } + } + + fn insert(&mut self, key: K) { + self.map.entry(key).and_modify(|c| *c += 1).or_insert(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(1) => { + self.map.remove(key); + true + } + Some(c) => { + *c -= 1; + true + } + None => false, + } + } + + fn contains(&self, symbol: &K) -> bool { + self.map.contains_key(symbol) + } +} From 0e28423aea02ddb6dc64fd0a358219be01e49824 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sun, 28 May 2023 18:46:01 -0700 Subject: [PATCH 16/54] fix segfault in Num.toStr for Dec --- crates/compiler/builtins/bitcode/src/dec.zig | 21 ++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/compiler/builtins/bitcode/src/dec.zig b/crates/compiler/builtins/bitcode/src/dec.zig index 8170c135ab..e439db7a98 100644 --- a/crates/compiler/builtins/bitcode/src/dec.zig +++ b/crates/compiler/builtins/bitcode/src/dec.zig @@ -44,6 +44,8 @@ pub const RocDec = extern struct { return ret; } + // TODO: If Str.toDec eventually supports more error types, return errors here. + // For now, just return null which will give the default error. pub fn fromStr(roc_str: RocStr) ?RocDec { if (roc_str.isEmpty()) { return null; @@ -79,7 +81,8 @@ pub const RocDec = extern struct { var after_str_len = (length - 1) - pi; if (after_str_len > decimal_places) { - @panic("TODO runtime exception for too many decimal places!"); + // TODO: runtime exception for too many decimal places! + return null; } var diff_decimal_places = decimal_places - after_str_len; @@ -96,7 +99,8 @@ pub const RocDec = extern struct { var result: i128 = undefined; var overflowed = @mulWithOverflow(i128, before, one_point_zero_i128, &result); if (overflowed) { - @panic("TODO runtime exception for overflow!"); + // TODO: runtime exception for overflow! + return null; } before_val_i128 = result; } @@ -107,7 +111,8 @@ pub const RocDec = extern struct { var result: i128 = undefined; var overflowed = @addWithOverflow(i128, before, after, &result); if (overflowed) { - @panic("TODO runtime exception for overflow!"); + // TODO: runtime exception for overflow! + return null; } break :blk .{ .num = result }; } else { @@ -184,7 +189,7 @@ pub const RocDec = extern struct { position += 1; const trailing_zeros: u6 = count_trailing_zeros_base10(num); - if (trailing_zeros == decimal_places) { + if (trailing_zeros >= decimal_places) { // add just a single zero if all decimal digits are zero str_bytes[position] = '0'; position += 1; @@ -851,6 +856,14 @@ test "fromStr: .123.1" { try expectEqual(dec, null); } +test "toStr: 100.00" { + var dec: RocDec = .{ .num = 100000000000000000000 }; + var res_roc_str = dec.toStr(); + + const res_slice: []const u8 = "100.0"[0..]; + try expectEqualSlices(u8, res_slice, res_roc_str.asSlice()); +} + test "toStr: 123.45" { var dec: RocDec = .{ .num = 123450000000000000000 }; var res_roc_str = dec.toStr(); From e29af85dcfe8e15b81b66b66cdfc9eab372ff69d Mon Sep 17 00:00:00 2001 From: "J.Teeuwissen" Date: Mon, 29 May 2023 08:39:59 +0200 Subject: [PATCH 17/54] Fixed tests --- crates/compiler/mono/src/drop_specialization.rs | 2 -- crates/compiler/test_mono/generated/issue_4749.txt | 4 ++-- crates/compiler/test_mono/generated/issue_4770.txt | 5 +++-- .../issue_4772_weakened_monomorphic_destructure.txt | 2 +- crates/compiler/test_mono/generated/rb_tree_fbip.txt | 2 +- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/compiler/mono/src/drop_specialization.rs b/crates/compiler/mono/src/drop_specialization.rs index fa7f63d28b..8a5c68a8c5 100644 --- a/crates/compiler/mono/src/drop_specialization.rs +++ b/crates/compiler/mono/src/drop_specialization.rs @@ -1423,7 +1423,6 @@ fn low_level_no_rc(lowlevel: &LowLevel) -> RC { ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrGraphemes | StrCountUtf8Bytes | StrGetCapacity | ListGetCapacity => RC::NoRc, ListWithCapacity | StrWithCapacity => RC::NoRc, - DictPseudoSeed => RC::NoRc, ListReplaceUnsafe => RC::Rc, StrGetUnsafe | ListGetUnsafe => RC::NoRc, ListConcat => RC::Rc, @@ -1483,7 +1482,6 @@ fn low_level_no_rc(lowlevel: &LowLevel) -> RC { | NumCountLeadingZeroBits | NumCountTrailingZeroBits | NumCountOneBits => RC::NoRc, - I128OfDec => RC::NoRc, NumBytesToU16 => RC::NoRc, NumBytesToU32 => RC::NoRc, NumBytesToU64 => RC::NoRc, diff --git a/crates/compiler/test_mono/generated/issue_4749.txt b/crates/compiler/test_mono/generated/issue_4749.txt index d0c31c8690..1d63f9ccca 100644 --- a/crates/compiler/test_mono/generated/issue_4749.txt +++ b/crates/compiler/test_mono/generated/issue_4749.txt @@ -41,8 +41,8 @@ procedure Decode.26 (Decode.105, Decode.106): procedure Decode.27 (Decode.107, Decode.108): let Decode.122 : {List U8, [C {}, C Str]} = CallByName Decode.26 Decode.107 Decode.108; let Decode.110 : List U8 = StructAtIndex 0 Decode.122; - inc Decode.110; let Decode.109 : [C {}, C Str] = StructAtIndex 1 Decode.122; + inc Decode.110; let Decode.125 : Int1 = CallByName List.1 Decode.110; if Decode.125 then dec Decode.110; @@ -532,9 +532,9 @@ procedure Json.68 (): procedure Json.69 (Json.1467): joinpoint Json.1197 Json.1165: let Json.599 : List U8 = StructAtIndex 0 Json.1165; - inc Json.599; let Json.600 : List U8 = StructAtIndex 1 Json.1165; let Json.1315 : U64 = 0i64; + inc Json.599; let Json.601 : [C {}, C U8] = CallByName List.2 Json.599 Json.1315; let Json.1314 : U64 = 1i64; inc Json.599; diff --git a/crates/compiler/test_mono/generated/issue_4770.txt b/crates/compiler/test_mono/generated/issue_4770.txt index 8a82d60a52..eec0073c5b 100644 --- a/crates/compiler/test_mono/generated/issue_4770.txt +++ b/crates/compiler/test_mono/generated/issue_4770.txt @@ -136,13 +136,14 @@ procedure Test.1 (Test.77): if Test.64 then let Test.52 : [C I64, C List *self] = StructAtIndex 0 Test.6; let Test.12 : List [C I64, C List *self] = UnionAtIndex (Id 1) (Index 0) Test.52; - inc 2 Test.12; + inc Test.12; let Test.51 : [C I64, C List *self] = StructAtIndex 1 Test.6; dec Test.52; let Test.14 : List [C I64, C List *self] = UnionAtIndex (Id 1) (Index 0) Test.51; - inc Test.14; joinpoint #Derived_gen.2: let Test.35 : {} = Struct {}; + inc Test.12; + inc Test.14; let Test.33 : List {[C I64, C List *self], [C I64, C List *self]} = CallByName List.23 Test.12 Test.14 Test.35; let Test.34 : {} = Struct {}; let Test.29 : Int1 = CallByName List.56 Test.33 Test.34; diff --git a/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt b/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt index 984d734da0..38d7128c04 100644 --- a/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt +++ b/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt @@ -506,9 +506,9 @@ procedure Json.68 (): procedure Json.69 (Json.1467): joinpoint Json.1197 Json.1165: let Json.599 : List U8 = StructAtIndex 0 Json.1165; - inc Json.599; let Json.600 : List U8 = StructAtIndex 1 Json.1165; let Json.1315 : U64 = 0i64; + inc Json.599; let Json.601 : [C {}, C U8] = CallByName List.2 Json.599 Json.1315; let Json.1314 : U64 = 1i64; inc Json.599; diff --git a/crates/compiler/test_mono/generated/rb_tree_fbip.txt b/crates/compiler/test_mono/generated/rb_tree_fbip.txt index ae16f83f63..6f6bce7ee1 100644 --- a/crates/compiler/test_mono/generated/rb_tree_fbip.txt +++ b/crates/compiler/test_mono/generated/rb_tree_fbip.txt @@ -203,8 +203,8 @@ procedure Test.3 (Test.9, Test.10, Test.11): let Test.173 : Int1 = true; let Test.177 : Int1 = lowlevel Eq Test.173 Test.172; if Test.177 then - inc Test.19; let #Derived_gen.272 : [C *self I64 *self I32 Int1, ] = Reset { symbol: Test.16, id: UpdateModeId { id: 242 } }; + inc Test.19; let Test.118 : [C *self I64 *self I32 Int1, ] = CallByName Test.3 Test.19 Test.10 Test.11; joinpoint Test.137 #Derived_gen.317 #Derived_gen.318: let Test.136 : [C *self I64 *self I32 Int1, ] = UnionAtIndex (Id 1) (Index 0) Test.118; From 079290dcaf1787200b72c34e0de38d2f30d32c2e Mon Sep 17 00:00:00 2001 From: "J.Teeuwissen" Date: Mon, 29 May 2023 10:54:09 +0200 Subject: [PATCH 18/54] Keep 0 in counting map --- crates/compiler/mono/src/drop_specialization.rs | 17 ++++------------- .../compiler/test_mono/generated/issue_4749.txt | 4 ++-- .../compiler/test_mono/generated/issue_4770.txt | 5 ++--- ...ue_4772_weakened_monomorphic_destructure.txt | 2 +- .../test_mono/generated/rb_tree_fbip.txt | 2 +- 5 files changed, 10 insertions(+), 20 deletions(-) diff --git a/crates/compiler/mono/src/drop_specialization.rs b/crates/compiler/mono/src/drop_specialization.rs index 8a5c68a8c5..04a2643c09 100644 --- a/crates/compiler/mono/src/drop_specialization.rs +++ b/crates/compiler/mono/src/drop_specialization.rs @@ -378,12 +378,6 @@ fn specialize_drops_stmt<'a, 'i>( (info.clone(), new_branch) }; - // Remove all 0 counts as cleanup. - environment - .incremented_symbols - .map - .retain(|_, count| *count > 0); - arena.alloc(Stmt::Switch { cond_symbol: *cond_symbol, cond_layout: *cond_layout, @@ -395,7 +389,7 @@ fn specialize_drops_stmt<'a, 'i>( Stmt::Ret(symbol) => arena.alloc(Stmt::Ret(*symbol)), Stmt::Refcounting(rc, continuation) => match rc { ModifyRc::Inc(symbol, count) => { - let any = environment.incremented_symbols.contains(symbol); + let inc_before = environment.incremented_symbols.contained(symbol); // Add a symbol for every increment performed. environment @@ -410,7 +404,7 @@ fn specialize_drops_stmt<'a, 'i>( continuation, ); - if any { + if inc_before { // There were increments before this one, best to let the first one do the increments. // Or there are no increments left, so we can just continue. new_continuation @@ -1540,10 +1534,7 @@ where fn pop(&mut self, key: &K) -> bool { match self.map.get_mut(key) { - Some(1) => { - self.map.remove(key); - true - } + Some(0) => false, Some(c) => { *c -= 1; true @@ -1552,7 +1543,7 @@ where } } - fn contains(&self, symbol: &K) -> bool { + fn contained(&self, symbol: &K) -> bool { self.map.contains_key(symbol) } } diff --git a/crates/compiler/test_mono/generated/issue_4749.txt b/crates/compiler/test_mono/generated/issue_4749.txt index 1d63f9ccca..d0c31c8690 100644 --- a/crates/compiler/test_mono/generated/issue_4749.txt +++ b/crates/compiler/test_mono/generated/issue_4749.txt @@ -41,8 +41,8 @@ procedure Decode.26 (Decode.105, Decode.106): procedure Decode.27 (Decode.107, Decode.108): let Decode.122 : {List U8, [C {}, C Str]} = CallByName Decode.26 Decode.107 Decode.108; let Decode.110 : List U8 = StructAtIndex 0 Decode.122; - let Decode.109 : [C {}, C Str] = StructAtIndex 1 Decode.122; inc Decode.110; + let Decode.109 : [C {}, C Str] = StructAtIndex 1 Decode.122; let Decode.125 : Int1 = CallByName List.1 Decode.110; if Decode.125 then dec Decode.110; @@ -532,9 +532,9 @@ procedure Json.68 (): procedure Json.69 (Json.1467): joinpoint Json.1197 Json.1165: let Json.599 : List U8 = StructAtIndex 0 Json.1165; + inc Json.599; let Json.600 : List U8 = StructAtIndex 1 Json.1165; let Json.1315 : U64 = 0i64; - inc Json.599; let Json.601 : [C {}, C U8] = CallByName List.2 Json.599 Json.1315; let Json.1314 : U64 = 1i64; inc Json.599; diff --git a/crates/compiler/test_mono/generated/issue_4770.txt b/crates/compiler/test_mono/generated/issue_4770.txt index eec0073c5b..8a82d60a52 100644 --- a/crates/compiler/test_mono/generated/issue_4770.txt +++ b/crates/compiler/test_mono/generated/issue_4770.txt @@ -136,14 +136,13 @@ procedure Test.1 (Test.77): if Test.64 then let Test.52 : [C I64, C List *self] = StructAtIndex 0 Test.6; let Test.12 : List [C I64, C List *self] = UnionAtIndex (Id 1) (Index 0) Test.52; - inc Test.12; + inc 2 Test.12; let Test.51 : [C I64, C List *self] = StructAtIndex 1 Test.6; dec Test.52; let Test.14 : List [C I64, C List *self] = UnionAtIndex (Id 1) (Index 0) Test.51; + inc Test.14; joinpoint #Derived_gen.2: let Test.35 : {} = Struct {}; - inc Test.12; - inc Test.14; let Test.33 : List {[C I64, C List *self], [C I64, C List *self]} = CallByName List.23 Test.12 Test.14 Test.35; let Test.34 : {} = Struct {}; let Test.29 : Int1 = CallByName List.56 Test.33 Test.34; diff --git a/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt b/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt index 38d7128c04..984d734da0 100644 --- a/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt +++ b/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt @@ -506,9 +506,9 @@ procedure Json.68 (): procedure Json.69 (Json.1467): joinpoint Json.1197 Json.1165: let Json.599 : List U8 = StructAtIndex 0 Json.1165; + inc Json.599; let Json.600 : List U8 = StructAtIndex 1 Json.1165; let Json.1315 : U64 = 0i64; - inc Json.599; let Json.601 : [C {}, C U8] = CallByName List.2 Json.599 Json.1315; let Json.1314 : U64 = 1i64; inc Json.599; diff --git a/crates/compiler/test_mono/generated/rb_tree_fbip.txt b/crates/compiler/test_mono/generated/rb_tree_fbip.txt index 6f6bce7ee1..ae16f83f63 100644 --- a/crates/compiler/test_mono/generated/rb_tree_fbip.txt +++ b/crates/compiler/test_mono/generated/rb_tree_fbip.txt @@ -203,8 +203,8 @@ procedure Test.3 (Test.9, Test.10, Test.11): let Test.173 : Int1 = true; let Test.177 : Int1 = lowlevel Eq Test.173 Test.172; if Test.177 then - let #Derived_gen.272 : [C *self I64 *self I32 Int1, ] = Reset { symbol: Test.16, id: UpdateModeId { id: 242 } }; inc Test.19; + let #Derived_gen.272 : [C *self I64 *self I32 Int1, ] = Reset { symbol: Test.16, id: UpdateModeId { id: 242 } }; let Test.118 : [C *self I64 *self I32 Int1, ] = CallByName Test.3 Test.19 Test.10 Test.11; joinpoint Test.137 #Derived_gen.317 #Derived_gen.318: let Test.136 : [C *self I64 *self I32 Int1, ] = UnionAtIndex (Id 1) (Index 0) Test.118; From b7a7a735fcb982b9fa3406b3165ee7f155d74cf4 Mon Sep 17 00:00:00 2001 From: "J.Teeuwissen" Date: Mon, 29 May 2023 12:09:18 +0200 Subject: [PATCH 19/54] keep inc env always, but set count to 0. --- crates/compiler/load_internal/src/file.rs | 1 - .../compiler/mono/src/drop_specialization.rs | 74 ++++++++++--------- .../encode_derived_nested_record_string.txt | 2 +- ...encode_derived_record_one_field_string.txt | 2 +- ...ncode_derived_record_two_field_strings.txt | 2 +- .../generated/encode_derived_string.txt | 2 +- .../encode_derived_tag_one_field_string.txt | 2 +- ...encode_derived_tag_two_payloads_string.txt | 2 +- .../test_mono/generated/issue_4749.txt | 7 +- ..._4772_weakened_monomorphic_destructure.txt | 7 +- .../test_mono/generated/quicksort_swap.txt | 3 +- .../compiler/test_mono/generated/rigids.txt | 3 +- ...not_duplicate_identical_concrete_types.txt | 2 +- 13 files changed, 54 insertions(+), 55 deletions(-) diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index 6bd3289525..2424efb9a4 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -3115,7 +3115,6 @@ fn update<'a>( &mut layout_interner, module_id, ident_ids, - state.target_info, &mut state.procedures, ); diff --git a/crates/compiler/mono/src/drop_specialization.rs b/crates/compiler/mono/src/drop_specialization.rs index 04a2643c09..40ba98813b 100644 --- a/crates/compiler/mono/src/drop_specialization.rs +++ b/crates/compiler/mono/src/drop_specialization.rs @@ -15,7 +15,6 @@ use bumpalo::collections::CollectIn; use roc_module::low_level::LowLevel; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; -use roc_target::TargetInfo; use crate::ir::{ BranchInfo, Call, CallType, Expr, JoinPointId, ListLiteralElement, Literal, ModifyRc, Proc, @@ -38,12 +37,10 @@ pub fn specialize_drops<'a, 'i>( layout_interner: &'i mut STLayoutInterner<'a>, home: ModuleId, ident_ids: &'i mut IdentIds, - target_info: TargetInfo, procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) { for ((_symbol, proc_layout), proc) in procs.iter_mut() { - let mut environment = - DropSpecializationEnvironment::new(arena, home, proc_layout.result, target_info); + let mut environment = DropSpecializationEnvironment::new(arena, home, proc_layout.result); specialize_drops_proc(arena, layout_interner, ident_ids, &mut environment, proc); } } @@ -114,9 +111,18 @@ fn specialize_drops_stmt<'a, 'i>( RC::NoRc => alloc_let_with_continuation!(environment), // We probably should not pass the increments to the continuation. 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,9 +133,18 @@ fn specialize_drops_stmt<'a, 'i>( // the parent might be deallocated before the function can use it. // Thus forget everything about any increments. - let mut new_environment = environment.clone_without_incremented(); + let incremented_symbols = environment.incremented_symbols.drain(); - alloc_let_with_continuation!(&mut new_environment) + let new_stmt = alloc_let_with_continuation!(environment); + + // The new_environment might have inserted increments that were set to 0 before. We need to add th + for (symbol, increment) in incremented_symbols.map.into_iter() { + environment + .incremented_symbols + .insert_count(symbol, increment); + } + + new_stmt } } } @@ -609,7 +624,8 @@ fn specialize_drops_stmt<'a, 'i>( body, remainder, } => { - let mut new_environment = environment.clone_without_incremented(); + let mut new_environment = environment.clone(); + new_environment.incremented_symbols.clear(); for param in parameters.iter() { new_environment.add_symbol_layout(param.symbol, param.layout); @@ -1255,7 +1271,6 @@ struct DropSpecializationEnvironment<'a> { arena: &'a Bump, home: ModuleId, layout: InLayout<'a>, - target_info: TargetInfo, symbol_layouts: MutMap>, @@ -1285,12 +1300,11 @@ struct DropSpecializationEnvironment<'a> { } impl<'a> DropSpecializationEnvironment<'a> { - fn new(arena: &'a Bump, home: ModuleId, layout: InLayout<'a>, target_info: TargetInfo) -> Self { + fn new(arena: &'a Bump, home: ModuleId, layout: InLayout<'a>) -> Self { Self { arena, home, layout, - target_info, symbol_layouts: MutMap::default(), struct_children: MutMap::default(), union_children: MutMap::default(), @@ -1303,24 +1317,6 @@ impl<'a> DropSpecializationEnvironment<'a> { } } - 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: CountingMap::new(), - symbol_tag: self.symbol_tag.clone(), - symbol_index: self.symbol_index.clone(), - list_length: self.list_length.clone(), - } - } - fn create_symbol<'i>(&self, ident_ids: &'i mut IdentIds, debug_name: &str) -> Symbol { let ident_id = ident_ids.add_str(debug_name); Symbol::new(self.home, ident_id) @@ -1506,14 +1502,14 @@ fn low_level_no_rc(lowlevel: &LowLevel) -> RC { #[derive(Clone)] struct CountingMap where - K: Eq + std::hash::Hash, + K: Eq + std::hash::Hash + Clone, { map: MutMap, } impl CountingMap where - K: Eq + std::hash::Hash, + K: Eq + std::hash::Hash + Clone, { fn new() -> Self { Self { @@ -1522,7 +1518,7 @@ where } fn insert(&mut self, key: K) { - self.map.entry(key).and_modify(|c| *c += 1).or_insert(1); + self.insert_count(key, 1); } fn insert_count(&mut self, key: K, count: u64) { @@ -1546,4 +1542,16 @@ where fn contained(&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(); + } } diff --git a/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt b/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt index 43ef33a492..ccef4a274f 100644 --- a/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt @@ -697,8 +697,8 @@ procedure Json.25 (Json.183): let Json.1906 : List U8 = CallByName List.8 Json.1907 Json.1908; ret Json.1906; else - let Json.1948 : U64 = StructAtIndex 0 Json.186; inc Json.184; + let Json.1948 : U64 = StructAtIndex 0 Json.186; let Json.1947 : {List U8, List U8} = CallByName List.52 Json.184 Json.1948; let Json.210 : List U8 = StructAtIndex 0 Json.1947; let Json.212 : List U8 = StructAtIndex 1 Json.1947; diff --git a/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt b/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt index 09eb6c8aea..eb0e2fa779 100644 --- a/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt @@ -623,8 +623,8 @@ procedure Json.25 (Json.183): let Json.1532 : List U8 = CallByName List.8 Json.1533 Json.1534; ret Json.1532; else - let Json.1574 : U64 = StructAtIndex 0 Json.186; inc Json.184; + let Json.1574 : U64 = StructAtIndex 0 Json.186; let Json.1573 : {List U8, List U8} = CallByName List.52 Json.184 Json.1574; let Json.210 : List U8 = StructAtIndex 0 Json.1573; let Json.212 : List U8 = StructAtIndex 1 Json.1573; diff --git a/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt b/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt index 28038830e7..b8615bf0eb 100644 --- a/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt +++ b/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt @@ -630,8 +630,8 @@ procedure Json.25 (Json.183): let Json.1532 : List U8 = CallByName List.8 Json.1533 Json.1534; ret Json.1532; else - let Json.1574 : U64 = StructAtIndex 0 Json.186; inc Json.184; + let Json.1574 : U64 = StructAtIndex 0 Json.186; let Json.1573 : {List U8, List U8} = CallByName List.52 Json.184 Json.1574; let Json.210 : List U8 = StructAtIndex 0 Json.1573; let Json.212 : List U8 = StructAtIndex 1 Json.1573; diff --git a/crates/compiler/test_mono/generated/encode_derived_string.txt b/crates/compiler/test_mono/generated/encode_derived_string.txt index 969f6eedd7..08dc7f69bb 100644 --- a/crates/compiler/test_mono/generated/encode_derived_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_string.txt @@ -118,8 +118,8 @@ procedure Json.25 (Json.183): let Json.1179 : List U8 = CallByName List.8 Json.1180 Json.1181; ret Json.1179; else - let Json.1221 : U64 = StructAtIndex 0 Json.186; inc Json.184; + let Json.1221 : U64 = StructAtIndex 0 Json.186; let Json.1220 : {List U8, List U8} = CallByName List.52 Json.184 Json.1221; let Json.210 : List U8 = StructAtIndex 0 Json.1220; let Json.212 : List U8 = StructAtIndex 1 Json.1220; diff --git a/crates/compiler/test_mono/generated/encode_derived_tag_one_field_string.txt b/crates/compiler/test_mono/generated/encode_derived_tag_one_field_string.txt index 63b59f013f..fcad92aee9 100644 --- a/crates/compiler/test_mono/generated/encode_derived_tag_one_field_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_tag_one_field_string.txt @@ -147,8 +147,8 @@ procedure Json.25 (Json.183): let Json.1220 : List U8 = CallByName List.8 Json.1221 Json.1222; ret Json.1220; else - let Json.1262 : U64 = StructAtIndex 0 Json.186; inc Json.184; + let Json.1262 : U64 = StructAtIndex 0 Json.186; let Json.1261 : {List U8, List U8} = CallByName List.52 Json.184 Json.1262; let Json.210 : List U8 = StructAtIndex 0 Json.1261; let Json.212 : List U8 = StructAtIndex 1 Json.1261; diff --git a/crates/compiler/test_mono/generated/encode_derived_tag_two_payloads_string.txt b/crates/compiler/test_mono/generated/encode_derived_tag_two_payloads_string.txt index c6817419be..fe755e729e 100644 --- a/crates/compiler/test_mono/generated/encode_derived_tag_two_payloads_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_tag_two_payloads_string.txt @@ -150,8 +150,8 @@ procedure Json.25 (Json.183): let Json.1220 : List U8 = CallByName List.8 Json.1221 Json.1222; ret Json.1220; else - let Json.1262 : U64 = StructAtIndex 0 Json.186; inc Json.184; + let Json.1262 : U64 = StructAtIndex 0 Json.186; let Json.1261 : {List U8, List U8} = CallByName List.52 Json.184 Json.1262; let Json.210 : List U8 = StructAtIndex 0 Json.1261; let Json.212 : List U8 = StructAtIndex 1 Json.1261; diff --git a/crates/compiler/test_mono/generated/issue_4749.txt b/crates/compiler/test_mono/generated/issue_4749.txt index d0c31c8690..9a5d0685da 100644 --- a/crates/compiler/test_mono/generated/issue_4749.txt +++ b/crates/compiler/test_mono/generated/issue_4749.txt @@ -179,8 +179,8 @@ procedure Json.60 (Json.540): let Json.1336 : U8 = GetTagId Json.1327; let Json.1337 : Int1 = lowlevel Eq Json.1335 Json.1336; if Json.1337 then - let Json.542 : U64 = UnionAtIndex (Id 2) (Index 0) Json.1327; inc Json.540; + let Json.542 : U64 = UnionAtIndex (Id 2) (Index 0) Json.1327; let Json.1329 : List U8 = CallByName List.29 Json.540 Json.542; let Json.1332 : U64 = 0i64; let Json.1331 : {U64, U64} = Struct {Json.542, Json.1332}; @@ -532,18 +532,15 @@ procedure Json.68 (): procedure Json.69 (Json.1467): joinpoint Json.1197 Json.1165: let Json.599 : List U8 = StructAtIndex 0 Json.1165; - inc Json.599; + inc 4 Json.599; let Json.600 : List U8 = StructAtIndex 1 Json.1165; let Json.1315 : U64 = 0i64; let Json.601 : [C {}, C U8] = CallByName List.2 Json.599 Json.1315; let Json.1314 : U64 = 1i64; - inc Json.599; let Json.602 : [C {}, C U8] = CallByName List.2 Json.599 Json.1314; let Json.1313 : U64 = 2i64; - inc Json.599; let Json.603 : List U8 = CallByName List.29 Json.599 Json.1313; let Json.1312 : U64 = 6i64; - inc Json.599; let Json.604 : List U8 = CallByName List.29 Json.599 Json.1312; let Json.1198 : {[C {}, C U8], [C {}, C U8]} = Struct {Json.601, Json.602}; joinpoint Json.1277: diff --git a/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt b/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt index 984d734da0..95b540c91e 100644 --- a/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt +++ b/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt @@ -153,8 +153,8 @@ procedure Json.60 (Json.540): let Json.1336 : U8 = GetTagId Json.1327; let Json.1337 : Int1 = lowlevel Eq Json.1335 Json.1336; if Json.1337 then - let Json.542 : U64 = UnionAtIndex (Id 2) (Index 0) Json.1327; inc Json.540; + let Json.542 : U64 = UnionAtIndex (Id 2) (Index 0) Json.1327; let Json.1329 : List U8 = CallByName List.29 Json.540 Json.542; let Json.1332 : U64 = 0i64; let Json.1331 : {U64, U64} = Struct {Json.542, Json.1332}; @@ -506,18 +506,15 @@ procedure Json.68 (): procedure Json.69 (Json.1467): joinpoint Json.1197 Json.1165: let Json.599 : List U8 = StructAtIndex 0 Json.1165; - inc Json.599; + inc 4 Json.599; let Json.600 : List U8 = StructAtIndex 1 Json.1165; let Json.1315 : U64 = 0i64; let Json.601 : [C {}, C U8] = CallByName List.2 Json.599 Json.1315; let Json.1314 : U64 = 1i64; - inc Json.599; let Json.602 : [C {}, C U8] = CallByName List.2 Json.599 Json.1314; let Json.1313 : U64 = 2i64; - inc Json.599; let Json.603 : List U8 = CallByName List.29 Json.599 Json.1313; let Json.1312 : U64 = 6i64; - inc Json.599; let Json.604 : List U8 = CallByName List.29 Json.599 Json.1312; let Json.1198 : {[C {}, C U8], [C {}, C U8]} = Struct {Json.601, Json.602}; joinpoint Json.1277: diff --git a/crates/compiler/test_mono/generated/quicksort_swap.txt b/crates/compiler/test_mono/generated/quicksort_swap.txt index add16507b3..5960c829d3 100644 --- a/crates/compiler/test_mono/generated/quicksort_swap.txt +++ b/crates/compiler/test_mono/generated/quicksort_swap.txt @@ -45,10 +45,9 @@ procedure Num.22 (#Attr.2, #Attr.3): procedure Test.1 (Test.2): let Test.28 : U64 = 0i64; - inc Test.2; + inc 2 Test.2; let Test.26 : [C {}, C I64] = CallByName List.2 Test.2 Test.28; let Test.27 : U64 = 0i64; - inc Test.2; let Test.25 : [C {}, C I64] = CallByName List.2 Test.2 Test.27; let Test.8 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.25, Test.26}; joinpoint Test.22: diff --git a/crates/compiler/test_mono/generated/rigids.txt b/crates/compiler/test_mono/generated/rigids.txt index 613e31d8ee..a64759f07a 100644 --- a/crates/compiler/test_mono/generated/rigids.txt +++ b/crates/compiler/test_mono/generated/rigids.txt @@ -44,9 +44,8 @@ procedure Num.22 (#Attr.2, #Attr.3): ret Num.283; procedure Test.1 (Test.2, Test.3, Test.4): - inc Test.4; + inc 2 Test.4; let Test.29 : [C {}, C I64] = CallByName List.2 Test.4 Test.3; - inc Test.4; let Test.28 : [C {}, C I64] = CallByName List.2 Test.4 Test.2; let Test.13 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.28, Test.29}; joinpoint Test.25: diff --git a/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt index d8b5ebb53d..86b5b51e0b 100644 --- a/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt +++ b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt @@ -136,8 +136,8 @@ procedure Json.25 (Json.183): let Json.1223 : List U8 = CallByName List.8 Json.1224 Json.1225; ret Json.1223; else - let Json.1265 : U64 = StructAtIndex 0 Json.186; inc Json.184; + let Json.1265 : U64 = StructAtIndex 0 Json.186; let Json.1264 : {List U8, List U8} = CallByName List.52 Json.184 Json.1265; let Json.210 : List U8 = StructAtIndex 0 Json.1264; let Json.212 : List U8 = StructAtIndex 1 Json.1264; From 9f23c3d26a254f0c0851b26713d2b25487b2043b Mon Sep 17 00:00:00 2001 From: "J.Teeuwissen" Date: Mon, 29 May 2023 16:38:34 +0200 Subject: [PATCH 20/54] Feedback --- crates/compiler/mono/src/drop_specialization.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/compiler/mono/src/drop_specialization.rs b/crates/compiler/mono/src/drop_specialization.rs index 40ba98813b..dda4959678 100644 --- a/crates/compiler/mono/src/drop_specialization.rs +++ b/crates/compiler/mono/src/drop_specialization.rs @@ -404,7 +404,7 @@ fn specialize_drops_stmt<'a, 'i>( Stmt::Ret(symbol) => arena.alloc(Stmt::Ret(*symbol)), Stmt::Refcounting(rc, continuation) => match rc { ModifyRc::Inc(symbol, count) => { - let inc_before = environment.incremented_symbols.contained(symbol); + let inc_before = environment.incremented_symbols.contains(symbol); // Add a symbol for every increment performed. environment @@ -1499,11 +1499,10 @@ 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 -where - K: Eq + std::hash::Hash + Clone, -{ +struct CountingMap { map: MutMap, } @@ -1539,7 +1538,7 @@ where } } - fn contained(&self, symbol: &K) -> bool { + fn contains(&self, symbol: &K) -> bool { self.map.contains_key(symbol) } From f0addf5300ffbf0e7c1900ed46f73aba240faef6 Mon Sep 17 00:00:00 2001 From: "J.Teeuwissen" Date: Tue, 30 May 2023 10:55:38 +0200 Subject: [PATCH 21/54] feedback --- crates/compiler/mono/src/ir.rs | 49 ++++++++++------------------------ 1 file changed, 14 insertions(+), 35 deletions(-) diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 22d344038b..157edcad83 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -4939,7 +4939,7 @@ pub fn with_hole<'a>( Err(_) => return runtime_error(env, "Can't update record with improper layout"), }; - let single_struct_field = sorted_fields.len() == 1; + let single_field_struct = sorted_fields.len() == 1; // The struct indexing generated by the current context let mut current_struct_indexing = Vec::with_capacity_in(sorted_fields.len(), env.arena); @@ -4962,7 +4962,7 @@ pub fn with_hole<'a>( // The struct with a single field is optimized in such a way that replacing later indexing will cause an incorrect IR. // Thus, only insert these struct_indices if there is more than one field in the struct. - if !single_struct_field { + if !single_field_struct { let original_struct_symbol = env.unique_symbol(); env.struct_indexing .insert(record_index, original_struct_symbol); @@ -4999,7 +4999,7 @@ pub fn with_hole<'a>( _ => arena.alloc([record_layout]), }; - if single_struct_field { + if single_field_struct { // TODO we can probably special-case this more, skipping the generation of // UpdateExisting let mut stmt = hole.clone(); @@ -5032,9 +5032,7 @@ pub fn with_hole<'a>( let expr = Expr::Struct(new_struct_symbols); let mut stmt = Stmt::Let(assigned, expr, record_layout, hole); - let it = new_struct_symbols.iter().zip(fields); - - for (new_struct_symbol, what_to_do) in it { + for (new_struct_symbol, what_to_do) in new_struct_symbols.iter().zip(fields) { match what_to_do { UpdateExisting(field) => { stmt = assign_to_symbol( @@ -5047,7 +5045,10 @@ pub fn with_hole<'a>( stmt, ); } - CopyExisting => {} + CopyExisting => { + // When a field is copied, the indexing symbol is already placed in new_struct_symbols + // Thus, we don't need additional logic here. + } } } @@ -7673,19 +7674,10 @@ fn can_reuse_symbol<'a>( Err(_) => unreachable!("Can't access record with improper layout"), }; - let index = - sorted_fields - .into_iter() - .enumerate() - .find_map( - |(current, (label, _, _))| { - if label == *field { - Some(current) - } else { - None - } - }, - ); + 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"); @@ -10069,24 +10061,11 @@ enum Usage { Unused, } -pub struct UsageTrackingMap -where - K: std::cmp::Eq + std::hash::Hash, -{ +#[derive(Default)] +pub struct UsageTrackingMap { map: MutMap, } -impl Default for UsageTrackingMap -where - K: std::cmp::Eq + std::hash::Hash, -{ - fn default() -> Self { - Self { - map: MutMap::default(), - } - } -} - impl UsageTrackingMap where K: std::cmp::Eq + std::hash::Hash, From 162e6bbb902e77833f82e18783822c81b323923c Mon Sep 17 00:00:00 2001 From: "J.Teeuwissen" Date: Tue, 30 May 2023 11:01:13 +0200 Subject: [PATCH 22/54] manual default --- crates/compiler/mono/src/ir.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 157edcad83..f6018dc60c 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -10061,11 +10061,18 @@ enum Usage { Unused, } -#[derive(Default)] pub struct UsageTrackingMap { map: MutMap, } +impl Default for UsageTrackingMap { + fn default() -> Self { + Self { + map: MutMap::default(), + } + } +} + impl UsageTrackingMap where K: std::cmp::Eq + std::hash::Hash, From 9289f289395fb75eb2cac45d0c628da617ecf876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Beir=C3=A3o?= Date: Tue, 30 May 2023 15:47:38 +0200 Subject: [PATCH 23/54] Fix small typo on user_error macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unless this is an inside joke that I am not aware of 😅 --- crates/error_macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/error_macros/src/lib.rs b/crates/error_macros/src/lib.rs index e2deb83cfc..e7947ffd5c 100644 --- a/crates/error_macros/src/lib.rs +++ b/crates/error_macros/src/lib.rs @@ -24,7 +24,7 @@ macro_rules! internal_error { macro_rules! user_error { ($($arg:tt)*) => ({ 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!($($arg)*); std::process::exit(1); From 81e8812f38b42f05e2604601c692e2425f5d3aeb Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Tue, 30 May 2023 09:21:59 -0700 Subject: [PATCH 24/54] Add all fns in compiler-rt that builtins are known to depend on Also removes the workaround in the surigical linker. This should mean we should get clear errors if we missed a function instead of crashes/segfaults. --- .../builtins/bitcode/src/compiler_rt.zig | 321 ++++++++++++++++++ crates/compiler/builtins/bitcode/src/main.zig | 61 +--- crates/linker/src/elf.rs | 20 +- 3 files changed, 330 insertions(+), 72 deletions(-) create mode 100644 crates/compiler/builtins/bitcode/src/compiler_rt.zig diff --git a/crates/compiler/builtins/bitcode/src/compiler_rt.zig b/crates/compiler/builtins/bitcode/src/compiler_rt.zig new file mode 100644 index 0000000000..7778df62f8 --- /dev/null +++ b/crates/compiler/builtins/bitcode/src/compiler_rt.zig @@ -0,0 +1,321 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const math = std.math; + +// Eventaully, 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(__udivti3_windows_x86_64, .{ .name = "__udivti3", .linkage = .Weak }); + } else { + @export(__divti3, .{ .name = "__divti3", .linkage = .Weak }); + @export(__modti3, .{ .name = "__modti3", .linkage = .Weak }); + @export(__udivti3, .{ .name = "__udivti3", .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 __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 +} + +// 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; +} diff --git a/crates/compiler/builtins/bitcode/src/main.zig b/crates/compiler/builtins/bitcode/src/main.zig index f089fab24c..04ce784d6a 100644 --- a/crates/compiler/builtins/bitcode/src/main.zig +++ b/crates/compiler/builtins/bitcode/src/main.zig @@ -5,6 +5,10 @@ const utils = @import("utils.zig"); const expect = @import("expect.zig"); const panic_utils = @import("panic.zig"); +comptime { + _ = @import("compiler_rt.zig"); +} + const ROC_BUILTINS = "roc_builtins"; const NUM = "num"; const STR = "str"; @@ -274,60 +278,3 @@ test "" { 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; -} diff --git a/crates/linker/src/elf.rs b/crates/linker/src/elf.rs index 8b7a91f49a..344a7a07c8 100644 --- a/crates/linker/src/elf.rs +++ b/crates/linker/src/elf.rs @@ -1471,21 +1471,11 @@ fn surgery_elf_help( } } } 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!( - "Undefined Symbol in relocation, {:+x?}: {:+x?}", - rel, - app_obj.symbol_by_index(index) - ); - } - } + internal_error!( + "Undefined Symbol in relocation, {:+x?}: {:+x?}", + rel, + app_obj.symbol_by_index(index) + ); } } From fe4577ce4c9ec9c471e6e9d47916354504624a8c Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Tue, 30 May 2023 09:38:30 -0700 Subject: [PATCH 25/54] fix typo --- crates/compiler/builtins/bitcode/src/compiler_rt.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/compiler/builtins/bitcode/src/compiler_rt.zig b/crates/compiler/builtins/bitcode/src/compiler_rt.zig index 7778df62f8..e0c937d3f4 100644 --- a/crates/compiler/builtins/bitcode/src/compiler_rt.zig +++ b/crates/compiler/builtins/bitcode/src/compiler_rt.zig @@ -2,7 +2,7 @@ const std = @import("std"); const builtin = @import("builtin"); const math = std.math; -// Eventaully, we need to statically ingest compiler-rt and get it working with the surgical linker, then these should not be needed anymore. +// 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 From a41611b553e99b36ac7e246b00e62936a00062d9 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Tue, 30 May 2023 12:11:15 -0700 Subject: [PATCH 26/54] allow ingested compiler-rt function in windows surgical linker --- crates/linker/src/pe.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/linker/src/pe.rs b/crates/linker/src/pe.rs index 68ce5ecd8e..423eef42ba 100644 --- a/crates/linker/src/pe.rs +++ b/crates/linker/src/pe.rs @@ -434,7 +434,9 @@ pub(crate) fn surgery_pe(executable_path: &Path, metadata_path: &Path, roc_app_b relocation, ); } else { - if *address == 0 && !name.starts_with("roc") { + let is_ingested_compiler_rt = + ["__muloti4", "__divti3", "__udivti3", "__modti3"].contains(&name.as_str()); + if *address == 0 && !name.starts_with("roc") && !is_ingested_compiler_rt { eprintln!( "I don't know the address of the {} function! this may cause segfaults", name From e7906b30a91abd607348d3b5977ecbb5f7cf6845 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Tue, 30 May 2023 14:27:38 -0700 Subject: [PATCH 27/54] add other compiler-rt functions in builtins.o --- .../builtins/bitcode/src/compiler_rt.zig | 123 +++++++++++++++++- crates/linker/src/pe.rs | 14 +- 2 files changed, 134 insertions(+), 3 deletions(-) diff --git a/crates/compiler/builtins/bitcode/src/compiler_rt.zig b/crates/compiler/builtins/bitcode/src/compiler_rt.zig index e0c937d3f4..b00961fa21 100644 --- a/crates/compiler/builtins/bitcode/src/compiler_rt.zig +++ b/crates/compiler/builtins/bitcode/src/compiler_rt.zig @@ -6,7 +6,7 @@ const math = std.math; // Until then, we are manually ingesting used parts of compiler-rt here. // // Taken from -// https://github.com/ziglang/zig/tree/4976b58ab16069f8d3267b69ed030f29685c1abe/lib/compiler_rt// +// 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 @@ -21,11 +21,21 @@ comptime { 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 }); } } @@ -65,6 +75,18 @@ 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); } @@ -85,6 +107,37 @@ inline fn mod(a: i128, b: i128) i128 { 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 @@ -319,3 +372,71 @@ pub fn udivmod(comptime DoubleInt: type, a: DoubleInt, b: DoubleInt, maybe_rem: } 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)), + }; +} diff --git a/crates/linker/src/pe.rs b/crates/linker/src/pe.rs index 423eef42ba..e4a2bd03c1 100644 --- a/crates/linker/src/pe.rs +++ b/crates/linker/src/pe.rs @@ -434,8 +434,18 @@ pub(crate) fn surgery_pe(executable_path: &Path, metadata_path: &Path, roc_app_b relocation, ); } else { - let is_ingested_compiler_rt = - ["__muloti4", "__divti3", "__udivti3", "__modti3"].contains(&name.as_str()); + 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!( "I don't know the address of the {} function! this may cause segfaults", From 291ee1f1c0c3930130a1f5ea9dfd6a77ca5d0c80 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Wed, 31 May 2023 14:09:25 +0200 Subject: [PATCH 28/54] Update docs command Signed-off-by: Anton-4 <17049058+Anton-4@users.noreply.github.com> --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9cab7583b..96963fed77 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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: ```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. From f6b76f2df5546a5ad25c1ea137ceaddd47554f85 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Wed, 31 May 2023 15:28:31 +0200 Subject: [PATCH 29/54] Num.exp is Num.pow now Signed-off-by: Anton-4 <17049058+Anton-4@users.noreply.github.com> --- crates/compiler/builtins/roc/Num.roc | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/crates/compiler/builtins/roc/Num.roc b/crates/compiler/builtins/roc/Num.roc index 2b5dd9869c..cf6745fcd2 100644 --- a/crates/compiler/builtins/roc/Num.roc +++ b/crates/compiler/builtins/roc/Num.roc @@ -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). ## ## For a [Frac] alternative to this function, which supports negative exponents, -## see #Num.exp. -## ``` -## Num.exp 5 0 +## see #Num.pow. ## -## Num.exp 5 1 +## ## Warning ## -## Num.exp 5 2 -## -## Num.exp 5 6 -## ``` -## ## Performance Details -## -## Be careful! It is very easy for this function to produce an answer +## It is very easy for this function to produce an answer ## so large it causes an overflow. powInt : Int a, Int a -> Int a From 842f8b9941679c704a563de92d74ae531dac5d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Beir=C3=A3o?= Date: Wed, 31 May 2023 16:48:33 +0200 Subject: [PATCH 30/54] Adjust packaging/https to check for misleading unicode characters Related to #5487 --- crates/packaging/src/https.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/packaging/src/https.rs b/crates/packaging/src/https.rs index cb605c6ec9..21aefbcb26 100644 --- a/crates/packaging/src/https.rs +++ b/crates/packaging/src/https.rs @@ -29,6 +29,14 @@ pub struct PackageMetadata<'a> { /// - .tar.br const VALID_EXTENSION_SUFFIXES: [&str; 2] = [".gz", ".br"]; +/// Characters that could be misleading if present in URLs: +/// +/// ⁄ - U+2044 Fraction Slash +/// ∕ - U+2215 Division Slash +/// / - U+FF0F Fullwidth Solidus +/// ⧸ - U+29F8 Big Solidus +const MISLEADING_CHARACTERS_IN_URL: [&str; 4] = ["\u{2044}", "\u{2215}", "\u{FF0F}", "\u{29F8}"]; + #[derive(Debug)] pub enum UrlProblem { InvalidExtensionSuffix(String), @@ -36,6 +44,7 @@ pub enum UrlProblem { InvalidFragment(String), MissingHash, MissingHttps, + MisleadingCharacter, } impl<'a> TryFrom<&'a str> for PackageMetadata<'a> { @@ -56,6 +65,13 @@ impl<'a> PackageMetadata<'a> { } }; + // Next, check if there are misleading characters in the URL + for misleading_character in MISLEADING_CHARACTERS_IN_URL { + if url.contains(misleading_character) { + return Err(UrlProblem::MisleadingCharacter); + } + } + // Next, get the (optional) URL fragment, which must be a .roc filename let (without_fragment, fragment) = match without_protocol.rsplit_once('#') { Some((before_fragment, fragment)) => { From 5744387693ad0d0769c3957357f65b5f1897b7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Beir=C3=A3o?= Date: Wed, 31 May 2023 18:50:49 +0200 Subject: [PATCH 31/54] Add "@" to the list of misleading characters Contributes to #5487 --- crates/packaging/src/https.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/packaging/src/https.rs b/crates/packaging/src/https.rs index 21aefbcb26..6e7bf227bd 100644 --- a/crates/packaging/src/https.rs +++ b/crates/packaging/src/https.rs @@ -31,11 +31,12 @@ const VALID_EXTENSION_SUFFIXES: [&str; 2] = [".gz", ".br"]; /// Characters that could be misleading if present in URLs: /// +/// @ - For now we avoid usage of the @, to avoid the "tld zip" attack vector /// ⁄ - U+2044 Fraction Slash /// ∕ - U+2215 Division Slash /// / - U+FF0F Fullwidth Solidus /// ⧸ - U+29F8 Big Solidus -const MISLEADING_CHARACTERS_IN_URL: [&str; 4] = ["\u{2044}", "\u{2215}", "\u{FF0F}", "\u{29F8}"]; +const MISLEADING_CHARACTERS_IN_URL: [&str; 5] = ["@", "\u{2044}", "\u{2215}", "\u{FF0F}", "\u{29F8}"]; #[derive(Debug)] pub enum UrlProblem { From a55773879181f27dab5f84101c3b69dee5e6df5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Beir=C3=A3o?= Date: Wed, 31 May 2023 18:51:28 +0200 Subject: [PATCH 32/54] Add a few unit tests to PackageMetadata Contributes to #5487 --- crates/packaging/src/https.rs | 89 ++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/crates/packaging/src/https.rs b/crates/packaging/src/https.rs index 6e7bf227bd..9ed891c0dd 100644 --- a/crates/packaging/src/https.rs +++ b/crates/packaging/src/https.rs @@ -13,6 +13,7 @@ use crate::tarball::Compression; // let's try to avoid doing that. const BROTLI_BUFFER_BYTES: usize = 8 * 1_000_000; // MB +#[derive(Debug, PartialEq)] pub struct PackageMetadata<'a> { /// The BLAKE3 hash of the tarball's contents. Also the .tar filename on disk. pub content_hash: &'a str, @@ -38,7 +39,7 @@ const VALID_EXTENSION_SUFFIXES: [&str; 2] = [".gz", ".br"]; /// ⧸ - U+29F8 Big Solidus const MISLEADING_CHARACTERS_IN_URL: [&str; 5] = ["@", "\u{2044}", "\u{2215}", "\u{FF0F}", "\u{29F8}"]; -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum UrlProblem { InvalidExtensionSuffix(String), MissingTarExt, @@ -119,6 +120,92 @@ impl<'a> PackageMetadata<'a> { } } +#[test] +#[should_panic(expected = "MissingHttps")] +fn url_problem_missing_https() { + PackageMetadata::try_from("http://example.com").unwrap(); +} + +#[test] +#[should_panic(expected = "MisleadingCharacter")] +fn url_problem_misleading_character_at() { + PackageMetadata::try_from("https://user:password@example.com/").unwrap(); +} + +#[test] +#[should_panic(expected = "MisleadingCharacter")] +fn url_problem_misleading_character_unicode_2044() { + PackageMetadata::try_from("https://example.com⁄path").unwrap(); +} + +#[test] +#[should_panic(expected = "MisleadingCharacter")] +fn url_problem_misleading_character_unicode_2215() { + PackageMetadata::try_from("https://example.com∕path").unwrap(); +} + +#[test] +#[should_panic(expected = "MisleadingCharacter")] +fn url_problem_misleading_character_unicode_ff0f() { + PackageMetadata::try_from("https://example.com/path").unwrap(); +} + +#[test] +#[should_panic(expected = "MisleadingCharacter")] +fn url_problem_misleading_character_unicode_29f8() { + PackageMetadata::try_from("https://example.com⧸path").unwrap(); +} + +#[test] +#[should_panic(expected = "InvalidFragment")] +fn url_problem_invalid_fragment_not_a_roc_file() { + PackageMetadata::try_from("https://example.com/#filename.sh").unwrap(); +} + +#[test] +#[should_panic(expected = "InvalidFragment")] +fn url_problem_invalid_fragment_empty_roc_filename() { + PackageMetadata::try_from("https://example.com/#.roc").unwrap(); +} + +#[test] +#[should_panic(expected = "MissingTarExt")] +fn url_problem_not_a_tar_url() { + PackageMetadata::try_from("https://example.com/filename.zip").unwrap(); +} + +#[test] +#[should_panic(expected = "InvalidExtensionSuffix")] +fn url_problem_invalid_tar_suffix() { + PackageMetadata::try_from("https://example.com/filename.tar.zip").unwrap(); +} + +#[test] +#[should_panic(expected = "MissingHash")] +fn url_problem_missing_hash() { + PackageMetadata::try_from("https://example.com/.tar.gz").unwrap(); +} + +#[test] +fn url_without_fragment() { + let actual = PackageMetadata::try_from("https://example.com/path/hash.tar.gz").unwrap(); + assert_eq!(PackageMetadata { + cache_subdir: "example.com/path", + content_hash: "hash", + root_module_filename: None, + }, actual); +} + +#[test] +fn url_with_fragment() { + let actual = PackageMetadata::try_from("https://example.com/path/hash.tar.gz#filename.roc").unwrap(); + assert_eq!(PackageMetadata { + cache_subdir: "example.com/path", + content_hash: "hash", + root_module_filename: Some("filename.roc"), + }, actual); +} + #[derive(Debug)] pub enum Problem { UnsupportedEncoding(String), From 17be5a6fd1c6d01d687d5ef39a23999f0cfaa937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tero=20Laxstr=C3=B6m?= Date: Wed, 31 May 2023 20:52:10 +0300 Subject: [PATCH 33/54] Fix typo in gen-dev for float_add_overflow test --- crates/compiler/test_gen/src/gen_num.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index e29b62f71e..21f54805bb 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -1842,7 +1842,7 @@ fn float_add_checked_fail() { } #[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() { assert_evals_to!( "1.7976931348623157e308 + 1.7976931348623157e308", From d633fbf89f9cbb2ee662fd278d708a709f928fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Beir=C3=A3o?= Date: Wed, 31 May 2023 21:37:50 +0200 Subject: [PATCH 34/54] Provide a better explanation to the motivation for disalowing misleading characters Contributes to #5487 --- crates/packaging/src/https.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/packaging/src/https.rs b/crates/packaging/src/https.rs index 9ed891c0dd..340e9e41a6 100644 --- a/crates/packaging/src/https.rs +++ b/crates/packaging/src/https.rs @@ -30,7 +30,13 @@ pub struct PackageMetadata<'a> { /// - .tar.br const VALID_EXTENSION_SUFFIXES: [&str; 2] = [".gz", ".br"]; -/// Characters that could be misleading if present in URLs: +/// 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.com∕kubernetes∕kubernetes∕archive∕refs∕tags∕@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 /// /// @ - For now we avoid usage of the @, to avoid the "tld zip" attack vector /// ⁄ - U+2044 Fraction Slash From 5b186fbebef2117d921c80c89817da71d91a1a8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Beir=C3=A3o?= Date: Wed, 31 May 2023 21:42:20 +0200 Subject: [PATCH 35/54] Move the unicode descriptions to be inline with the array items Improves readability Contributes to #5487 --- crates/packaging/src/https.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/packaging/src/https.rs b/crates/packaging/src/https.rs index 340e9e41a6..1a40821982 100644 --- a/crates/packaging/src/https.rs +++ b/crates/packaging/src/https.rs @@ -37,13 +37,13 @@ const VALID_EXTENSION_SUFFIXES: [&str; 2] = [".gz", ".br"]; /// 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 -/// -/// @ - For now we avoid usage of the @, to avoid the "tld zip" attack vector -/// ⁄ - U+2044 Fraction Slash -/// ∕ - U+2215 Division Slash -/// / - U+FF0F Fullwidth Solidus -/// ⧸ - U+29F8 Big Solidus -const MISLEADING_CHARACTERS_IN_URL: [&str; 5] = ["@", "\u{2044}", "\u{2215}", "\u{FF0F}", "\u{29F8}"]; +const MISLEADING_CHARACTERS_IN_URL: [&str; 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 { From 6448326e85283c892fb0d95b24cd955a442d6622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Beir=C3=A3o?= Date: Wed, 31 May 2023 21:47:56 +0200 Subject: [PATCH 36/54] Change the implementation to use chars instead of strings Also include a performance improvement by only iterating over the url's characters once Contributes to #5487 --- crates/packaging/src/https.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/crates/packaging/src/https.rs b/crates/packaging/src/https.rs index 1a40821982..1a898cccdd 100644 --- a/crates/packaging/src/https.rs +++ b/crates/packaging/src/https.rs @@ -37,12 +37,12 @@ const VALID_EXTENSION_SUFFIXES: [&str; 2] = [".gz", ".br"]; /// 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: [&str; 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 +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)] @@ -74,10 +74,8 @@ impl<'a> PackageMetadata<'a> { }; // Next, check if there are misleading characters in the URL - for misleading_character in MISLEADING_CHARACTERS_IN_URL { - if url.contains(misleading_character) { - return Err(UrlProblem::MisleadingCharacter); - } + 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 From 24b96d872c440a25e2e60256672834d7900184bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Beir=C3=A3o?= Date: Wed, 31 May 2023 22:10:19 +0200 Subject: [PATCH 37/54] Refactor the https tests to not use should_panic Thanks for the excellent tip, @rtfeldman Contributes to #5487 --- crates/packaging/src/https.rs | 75 ++++++++++++++--------------------- 1 file changed, 30 insertions(+), 45 deletions(-) diff --git a/crates/packaging/src/https.rs b/crates/packaging/src/https.rs index 1a898cccdd..27bcf28d3a 100644 --- a/crates/packaging/src/https.rs +++ b/crates/packaging/src/https.rs @@ -125,89 +125,74 @@ impl<'a> PackageMetadata<'a> { } #[test] -#[should_panic(expected = "MissingHttps")] fn url_problem_missing_https() { - PackageMetadata::try_from("http://example.com").unwrap(); + let expected = Err(UrlProblem::MissingHttps); + assert_eq!(PackageMetadata::try_from("http://example.com"), expected); } #[test] -#[should_panic(expected = "MisleadingCharacter")] -fn url_problem_misleading_character_at() { - PackageMetadata::try_from("https://user:password@example.com/").unwrap(); +fn url_problem_misleading_characters() { + let expected = Err(UrlProblem::MisleadingCharacter); + + for misleading_character_example in [ + "https://user:password@example.com/", + "https://example.com⁄path", + "https://example.com∕path", + "https://example.com/path", + "https://example.com⧸path", + ] { + assert_eq!(PackageMetadata::try_from(misleading_character_example), expected); + } } #[test] -#[should_panic(expected = "MisleadingCharacter")] -fn url_problem_misleading_character_unicode_2044() { - PackageMetadata::try_from("https://example.com⁄path").unwrap(); -} - -#[test] -#[should_panic(expected = "MisleadingCharacter")] -fn url_problem_misleading_character_unicode_2215() { - PackageMetadata::try_from("https://example.com∕path").unwrap(); -} - -#[test] -#[should_panic(expected = "MisleadingCharacter")] -fn url_problem_misleading_character_unicode_ff0f() { - PackageMetadata::try_from("https://example.com/path").unwrap(); -} - -#[test] -#[should_panic(expected = "MisleadingCharacter")] -fn url_problem_misleading_character_unicode_29f8() { - PackageMetadata::try_from("https://example.com⧸path").unwrap(); -} - -#[test] -#[should_panic(expected = "InvalidFragment")] fn url_problem_invalid_fragment_not_a_roc_file() { - PackageMetadata::try_from("https://example.com/#filename.sh").unwrap(); + let expected = Err(UrlProblem::InvalidFragment("filename.sh".to_string())); + assert_eq!(PackageMetadata::try_from("https://example.com/#filename.sh"), expected); } #[test] -#[should_panic(expected = "InvalidFragment")] fn url_problem_invalid_fragment_empty_roc_filename() { - PackageMetadata::try_from("https://example.com/#.roc").unwrap(); + let expected = Err(UrlProblem::InvalidFragment(".roc".to_string())); + assert_eq!(PackageMetadata::try_from("https://example.com/#.roc"), expected); } #[test] -#[should_panic(expected = "MissingTarExt")] fn url_problem_not_a_tar_url() { - PackageMetadata::try_from("https://example.com/filename.zip").unwrap(); + let expected = Err(UrlProblem::MissingTarExt); + assert_eq!(PackageMetadata::try_from("https://example.com/filename.zip"), expected); } #[test] -#[should_panic(expected = "InvalidExtensionSuffix")] fn url_problem_invalid_tar_suffix() { - PackageMetadata::try_from("https://example.com/filename.tar.zip").unwrap(); + let expected = Err(UrlProblem::InvalidExtensionSuffix(".zip".to_string())); + assert_eq!(PackageMetadata::try_from("https://example.com/filename.tar.zip"), expected); } #[test] -#[should_panic(expected = "MissingHash")] fn url_problem_missing_hash() { - PackageMetadata::try_from("https://example.com/.tar.gz").unwrap(); + let expected = Err(UrlProblem::MissingHash); + assert_eq!(PackageMetadata::try_from("https://example.com/.tar.gz"), expected); } #[test] fn url_without_fragment() { - let actual = PackageMetadata::try_from("https://example.com/path/hash.tar.gz").unwrap(); - assert_eq!(PackageMetadata { + let expected = Ok(PackageMetadata { cache_subdir: "example.com/path", content_hash: "hash", root_module_filename: None, - }, actual); + }); + assert_eq!(PackageMetadata::try_from("https://example.com/path/hash.tar.gz"), expected); } #[test] fn url_with_fragment() { - let actual = PackageMetadata::try_from("https://example.com/path/hash.tar.gz#filename.roc").unwrap(); - assert_eq!(PackageMetadata { + let expected = Ok(PackageMetadata { cache_subdir: "example.com/path", content_hash: "hash", root_module_filename: Some("filename.roc"), - }, actual); + }); + assert_eq!(PackageMetadata::try_from("https://example.com/path/hash.tar.gz#filename.roc"), expected); } #[derive(Debug)] From 778a0dcb67441e29f958b36b0295dd461a6a4b6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Beir=C3=A3o?= Date: Wed, 31 May 2023 22:15:45 +0200 Subject: [PATCH 38/54] Adjust the misleading characters to use the \u{} syntax, but still show the rendered example Contributes to #5487 --- crates/packaging/src/https.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/crates/packaging/src/https.rs b/crates/packaging/src/https.rs index 27bcf28d3a..5baa2dfedf 100644 --- a/crates/packaging/src/https.rs +++ b/crates/packaging/src/https.rs @@ -136,10 +136,18 @@ fn url_problem_misleading_characters() { for misleading_character_example in [ "https://user:password@example.com/", - "https://example.com⁄path", - "https://example.com∕path", - "https://example.com/path", - "https://example.com⧸path", + + //"https://example.com⁄path", + "https://example.com\u{2044}path", + + //"https://example.com∕path", + "https://example.com\u{2215}path", + + //"https://example.com/path", + "https://example.com\u{ff0f}path", + + //"https://example.com⧸path", + "https://example.com\u{29f8}path", ] { assert_eq!(PackageMetadata::try_from(misleading_character_example), expected); } From ed0e4450529316b1cb5ffa23bfdd16e1e7db3a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Beir=C3=A3o?= Date: Wed, 31 May 2023 23:09:09 +0200 Subject: [PATCH 39/54] Fix formatting errors Contributes to #5487 --- crates/packaging/src/https.rs | 53 +++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/crates/packaging/src/https.rs b/crates/packaging/src/https.rs index 5baa2dfedf..bfe02aaa3f 100644 --- a/crates/packaging/src/https.rs +++ b/crates/packaging/src/https.rs @@ -38,7 +38,7 @@ const VALID_EXTENSION_SUFFIXES: [&str; 2] = [".gz", ".br"]; /// 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 + '@', // @ - 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 @@ -74,7 +74,10 @@ 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)) { + if url + .chars() + .any(|ch| MISLEADING_CHARACTERS_IN_URL.contains(&ch)) + { return Err(UrlProblem::MisleadingCharacter); } @@ -136,51 +139,65 @@ fn url_problem_misleading_characters() { for misleading_character_example in [ "https://user:password@example.com/", - //"https://example.com⁄path", "https://example.com\u{2044}path", - //"https://example.com∕path", "https://example.com\u{2215}path", - //"https://example.com/path", "https://example.com\u{ff0f}path", - //"https://example.com⧸path", "https://example.com\u{29f8}path", - ] { - assert_eq!(PackageMetadata::try_from(misleading_character_example), expected); + ] { + 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); + 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); + 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); + 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); + 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); + assert_eq!( + PackageMetadata::try_from("https://example.com/.tar.gz"), + expected + ); } #[test] @@ -190,7 +207,10 @@ fn url_without_fragment() { content_hash: "hash", root_module_filename: None, }); - assert_eq!(PackageMetadata::try_from("https://example.com/path/hash.tar.gz"), expected); + assert_eq!( + PackageMetadata::try_from("https://example.com/path/hash.tar.gz"), + expected + ); } #[test] @@ -200,7 +220,10 @@ fn url_with_fragment() { content_hash: "hash", root_module_filename: Some("filename.roc"), }); - assert_eq!(PackageMetadata::try_from("https://example.com/path/hash.tar.gz#filename.roc"), expected); + assert_eq!( + PackageMetadata::try_from("https://example.com/path/hash.tar.gz#filename.roc"), + expected + ); } #[derive(Debug)] From 97aba2da41ce42136752cd010bd40c92c25604b0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 17 May 2023 17:40:57 +0200 Subject: [PATCH 40/54] add static offset to ptr read --- crates/compiler/gen_dev/src/generic64/mod.rs | 67 ++++++++++++++------ 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 9095100906..2df4eee6bd 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -2213,6 +2213,7 @@ impl< storage_manager, self.layout_interner, element_ptr, + 0, *ret_layout, *dst, ); @@ -2684,11 +2685,14 @@ impl< .storage_manager .load_to_general_reg(&mut self.buf, &ptr); + let offset = 0; + Self::ptr_read( &mut self.buf, &mut self.storage_manager, self.layout_interner, ptr_reg, + offset, element_layout, dst, ); @@ -3303,16 +3307,17 @@ impl< dst: Symbol, ptr_reg: GeneralReg, tmp_reg: GeneralReg, + offset: i32, ) { 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 + 0); 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_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); } @@ -3323,6 +3328,7 @@ impl< stack_size: u32, ptr_reg: GeneralReg, tmp_reg: GeneralReg, + read_offset: i32, ) { let mut copied = 0; let size = stack_size as i32; @@ -3336,7 +3342,7 @@ impl< if size - copied >= 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); copied += 8; @@ -3345,7 +3351,7 @@ impl< if size - copied >= 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); copied += 4; @@ -3354,7 +3360,7 @@ impl< if size - copied >= 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); copied += 2; @@ -3363,7 +3369,7 @@ impl< if size - copied >= 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); copied += 1; @@ -3376,6 +3382,7 @@ impl< storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>, layout_interner: &STLayoutInterner<'a>, ptr_reg: GeneralReg, + offset: i32, element_in_layout: InLayout<'a>, dst: Symbol, ) { @@ -3388,40 +3395,47 @@ impl< } IntWidth::I64 | IntWidth::U64 => { 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 => { 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 => { 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 => { 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) => { 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) => { 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 => { // the same as an 8-bit integer 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 => { // same as 128-bit integer } Builtin::Str | Builtin::List(_) => { 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, + ); }); } }, @@ -3429,7 +3443,7 @@ impl< LayoutRepr::Boxed(_) => { // the same as 64-bit integer (for 64-bit targets) 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 { .. } => { @@ -3437,7 +3451,15 @@ impl< let stack_size = layout_interner.stack_size(element_in_layout); 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 +3468,15 @@ impl< let stack_size = layout_interner.stack_size(element_in_layout); 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,6 +3486,7 @@ impl< storage_manager, layout_interner, ptr_reg, + offset, lambda_set.runtime_representation(), dst, ); From 3364d03fccba9baaa36ffa091b84c79cb684e684 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 31 May 2023 23:26:00 +0200 Subject: [PATCH 41/54] fix debug asserts in dev backend --- crates/compiler/gen_dev/src/lib.rs | 71 +++++++++++++----------------- 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index a4c7783553..679a24f7c1 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -1068,13 +1068,12 @@ trait Backend<'a> { } LowLevel::Eq => { debug_assert_eq!(2, args.len(), "Eq: expected to have exactly two argument"); - debug_assert_eq!( - arg_layouts[0], arg_layouts[1], + debug_assert!( + self.interner().eq_repr(arg_layouts[0], arg_layouts[1],), "Eq: expected all arguments of to have the same layout" ); - debug_assert_eq!( - Layout::BOOL, - *ret_layout, + debug_assert!( + self.interner().eq_repr(Layout::BOOL, *ret_layout,), "Eq: expected to have return layout of type Bool" ); self.build_eq(sym, &args[0], &args[1], &arg_layouts[0]) @@ -1085,22 +1084,20 @@ trait Backend<'a> { args.len(), "NotEq: expected to have exactly two argument" ); - debug_assert_eq!( - arg_layouts[0], arg_layouts[1], + debug_assert!( + self.interner().eq_repr(arg_layouts[0], arg_layouts[1],), "NotEq: expected all arguments of to have the same layout" ); - debug_assert_eq!( - Layout::BOOL, - *ret_layout, + debug_assert!( + self.interner().eq_repr(Layout::BOOL, *ret_layout,), "NotEq: expected to have return layout of type Bool" ); self.build_neq(sym, &args[0], &args[1], &arg_layouts[0]) } LowLevel::Not => { debug_assert_eq!(1, args.len(), "Not: expected to have exactly one argument"); - debug_assert_eq!( - Layout::BOOL, - *ret_layout, + debug_assert!( + self.interner().eq_repr(Layout::BOOL, *ret_layout,), "Not: expected to have return layout of type Bool" ); self.build_not(sym, &args[0], &arg_layouts[0]) @@ -1111,13 +1108,12 @@ trait Backend<'a> { args.len(), "NumLt: expected to have exactly two argument" ); - debug_assert_eq!( - arg_layouts[0], arg_layouts[1], + debug_assert!( + self.interner().eq_repr(arg_layouts[0], arg_layouts[1],), "NumLt: expected all arguments of to have the same layout" ); - debug_assert_eq!( - Layout::BOOL, - *ret_layout, + debug_assert!( + self.interner().eq_repr(Layout::BOOL, *ret_layout,), "NumLt: expected to have return layout of type Bool" ); self.build_num_lt(sym, &args[0], &args[1], &arg_layouts[0]) @@ -1128,13 +1124,12 @@ trait Backend<'a> { args.len(), "NumGt: expected to have exactly two argument" ); - debug_assert_eq!( - arg_layouts[0], arg_layouts[1], + debug_assert!( + self.interner().eq_repr(arg_layouts[0], arg_layouts[1],), "NumGt: expected all arguments of to have the same layout" ); - debug_assert_eq!( - Layout::BOOL, - *ret_layout, + debug_assert!( + self.interner().eq_repr(Layout::BOOL, *ret_layout,), "NumGt: expected to have return layout of type Bool" ); self.build_num_gt(sym, &args[0], &args[1], &arg_layouts[0]) @@ -1159,9 +1154,8 @@ trait Backend<'a> { "NumIsNan: expected to have exactly one argument" ); - debug_assert_eq!( - Layout::BOOL, - *ret_layout, + debug_assert!( + self.interner().eq_repr(Layout::BOOL, *ret_layout,), "NumIsNan: expected to have return layout of type Bool" ); self.build_num_is_nan(sym, &args[0], &arg_layouts[0]) @@ -1173,9 +1167,8 @@ trait Backend<'a> { "NumIsInfinite: expected to have exactly one argument" ); - debug_assert_eq!( - Layout::BOOL, - *ret_layout, + debug_assert!( + self.interner().eq_repr(Layout::BOOL, *ret_layout,), "NumIsInfinite: expected to have return layout of type Bool" ); self.build_num_is_infinite(sym, &args[0], &arg_layouts[0]) @@ -1187,9 +1180,8 @@ trait Backend<'a> { "NumIsFinite: expected to have exactly one argument" ); - debug_assert_eq!( - Layout::BOOL, - *ret_layout, + debug_assert!( + self.interner().eq_repr(Layout::BOOL, *ret_layout,), "NumIsFinite: expected to have return layout of type Bool" ); self.build_num_is_finite(sym, &args[0], &arg_layouts[0]) @@ -1204,9 +1196,8 @@ trait Backend<'a> { arg_layouts[0], arg_layouts[1], "NumLte: expected all arguments of to have the same layout" ); - debug_assert_eq!( - Layout::BOOL, - *ret_layout, + debug_assert!( + self.interner().eq_repr(Layout::BOOL, *ret_layout,), "NumLte: expected to have return layout of type Bool" ); self.build_num_lte(sym, &args[0], &args[1], &arg_layouts[0]) @@ -1221,9 +1212,8 @@ trait Backend<'a> { arg_layouts[0], arg_layouts[1], "NumGte: expected all arguments of to have the same layout" ); - debug_assert_eq!( - Layout::BOOL, - *ret_layout, + debug_assert!( + self.interner().eq_repr(Layout::BOOL, *ret_layout,), "NumGte: expected to have return layout of type Bool" ); self.build_num_gte(sym, &args[0], &args[1], &arg_layouts[0]) @@ -1761,9 +1751,8 @@ trait Backend<'a> { args.len(), "NumIsZero: expected to have exactly one argument" ); - debug_assert_eq!( - Layout::BOOL, - *ret_layout, + debug_assert!( + self.interner().eq_repr(Layout::BOOL, *ret_layout,), "NumIsZero: expected to have return layout of type Bool" ); From e33c2b3c847017a0855f3be41f0fe5e4c1c62786 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 17 May 2023 20:07:44 +0200 Subject: [PATCH 42/54] first working linked list test --- .../compiler/gen_dev/src/generic64/aarch64.rs | 2 +- crates/compiler/gen_dev/src/generic64/mod.rs | 278 +++++++++++++----- .../compiler/gen_dev/src/generic64/storage.rs | 201 +++++-------- .../compiler/gen_dev/src/generic64/x86_64.rs | 215 +++++++------- crates/compiler/gen_dev/src/lib.rs | 8 +- .../compiler/test_gen/src/gen_primitives.rs | 2 +- 6 files changed, 386 insertions(+), 320 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index 145e5d62fe..cf08ab036e 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -1065,7 +1065,7 @@ impl Assembler for AArch64Assembler { } #[inline(always)] - fn neq_reg64_reg64_reg64( + fn neq_reg_reg_reg( buf: &mut Vec<'_, u8>, _register_width: RegisterWidth, dst: AArch64GeneralReg, diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 2df4eee6bd..3795bcee38 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -1,6 +1,6 @@ use crate::{ - single_register_floats, single_register_int_builtins, single_register_integers, Backend, Env, - Relocation, + pointer_layouts, single_register_floats, single_register_int_builtins, + single_register_integers, Backend, Env, Relocation, }; use bumpalo::collections::{CollectIn, Vec}; use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; @@ -517,7 +517,7 @@ pub trait Assembler: Sized + Copy { 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>, register_width: RegisterWidth, dst: GeneralReg, @@ -904,27 +904,30 @@ impl< 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]); } - - other => { - // - match other { - LayoutRepr::Boxed(_) => { - 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]); - } - LayoutRepr::LambdaSet(lambda_set) => { - self.move_return_value(dst, &lambda_set.runtime_representation()) - } - _ => { - CC::load_returned_complex_symbol( - &mut self.buf, - &mut self.storage_manager, - self.layout_interner, - dst, - ret_layout, - ); - } - } + pointer_layouts!() => { + 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]); + } + LayoutRepr::LambdaSet(lambda_set) => { + 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( + &mut self.buf, + &mut self.storage_manager, + self.layout_interner, + dst, + ret_layout, + ); } } } @@ -1558,7 +1561,7 @@ impl< let src2_reg = self .storage_manager .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 => { self.build_fn_call( @@ -1576,7 +1579,7 @@ impl< let width = RegisterWidth::W8; // we're comparing booleans 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), } @@ -1714,25 +1717,13 @@ impl< 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::and_reg64_reg64_reg64(buf, dst_reg, dst_reg, mask_reg); - ASM::neq_reg64_reg64_reg64( - buf, - RegisterWidth::W32, - dst_reg, - dst_reg, - mask_reg, - ); + ASM::neq_reg_reg_reg(buf, RegisterWidth::W32, dst_reg, dst_reg, mask_reg); } Layout::F64 => { ASM::mov_reg64_imm64(buf, mask_reg, 0x7ff0_0000_0000_0000); ASM::mov_reg64_freg64(buf, dst_reg, src_reg); ASM::and_reg64_reg64_reg64(buf, dst_reg, dst_reg, mask_reg); - ASM::neq_reg64_reg64_reg64( - buf, - RegisterWidth::W64, - dst_reg, - dst_reg, - mask_reg, - ); + ASM::neq_reg_reg_reg(buf, RegisterWidth::W64, dst_reg, dst_reg, mask_reg); } _ => unreachable!(), } @@ -2601,7 +2592,7 @@ impl< union_layout: &UnionLayout<'a>, ) { match union_layout { - UnionLayout::NonRecursive(tag_layouts) | UnionLayout::Recursive(tag_layouts) => { + UnionLayout::NonRecursive(tag_layouts) => { self.storage_manager.load_field_at_index( self.layout_interner, sym, @@ -2610,6 +2601,34 @@ impl< 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, + ); + } + _ => { let union_in_layout = self .layout_interner @@ -2654,7 +2673,7 @@ impl< } // 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); } @@ -2699,13 +2718,53 @@ impl< } fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>) { - self.storage_manager.load_union_tag_id( - self.layout_interner, - &mut self.buf, - sym, - structure, - union_layout, - ); + let layout_interner: &mut STLayoutInterner<'a> = 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, + sym, + structure, + 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::neq_reg_reg_reg( + &mut self.buf, + RegisterWidth::W64, + dst_reg, + src1_reg, + src2_reg, + ); + } + false => { + ASM::eq_reg_reg_reg( + &mut self.buf, + RegisterWidth::W64, + dst_reg, + src1_reg, + src2_reg, + ); + } + } + } + x => todo!("getting tag id of union with layout ({:?})", x), + }; } fn tag( @@ -2715,14 +2774,73 @@ impl< union_layout: &UnionLayout<'a>, tag_id: TagIdIntType, ) { - self.storage_manager.create_union( - self.layout_interner, - &mut self.buf, - sym, - union_layout, - fields, - tag_id, - ) + 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; + match union_layout { + UnionLayout::NonRecursive(field_layouts) => { + let (data_size, data_alignment) = + union_layout.data_size_and_alignment(layout_interner, target_info); + 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 { + // step 1: make the struct + let temp_sym = Symbol::DEV_TMP5; + let layout = + layout_interner.insert_no_semantic(LayoutRepr::Struct(other_fields)); + self.storage_manager.create_struct( + layout_interner, + buf, + &temp_sym, + &layout, + fields, + ); + + // now effectively box this struct + self.expr_box(*sym, Symbol::DEV_TMP5, layout) + } else { + // it's just a null pointer + self.load_literal_i64(sym, 0); + } + } + x => todo!("creating unions with layout: {:?}", x), + } } fn load_literal(&mut self, sym: &Symbol, layout: &InLayout<'a>, lit: &Literal<'a>) { @@ -2864,7 +2982,7 @@ impl< if self.storage_manager.is_stored_primitive(sym) { // Just load it to the correct type of reg as a stand alone value. match repr { - single_register_integers!() => { + single_register_integers!() | pointer_layouts!() => { self.storage_manager.load_to_specified_general_reg( &mut self.buf, sym, @@ -2878,22 +2996,14 @@ impl< 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) => { - self.return_symbol(sym, &lambda_set.runtime_representation()) - } - _ => { - internal_error!("All primitive values should fit in a single register"); - } - }, + LayoutRepr::LambdaSet(lambda_set) => { + 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"); + } } } else { CC::return_complex_symbol( @@ -3440,7 +3550,7 @@ impl< } }, - LayoutRepr::Boxed(_) => { + pointer_layouts!() => { // the same as 64-bit integer (for 64-bit targets) let dst_reg = storage_manager.claim_general_reg(buf, &dst); ASM::mov_reg64_mem64_offset32(buf, dst_reg, ptr_reg, offset); @@ -3491,8 +3601,6 @@ impl< dst, ); } - - _ => todo!("unboxing of {:?}", layout_interner.dbg(element_in_layout)), } } @@ -3528,7 +3636,7 @@ impl< let sym_reg = storage_manager.load_to_float_reg(buf, &value); 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); ASM::mov_mem64_offset32_reg64(buf, ptr_reg, element_offset, sym_reg); } @@ -3702,3 +3810,17 @@ macro_rules! single_register_layouts { 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 { .. }, + ) + }; +} diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs index 799224493d..596f944075 100644 --- a/crates/compiler/gen_dev/src/generic64/storage.rs +++ b/crates/compiler/gen_dev/src/generic64/storage.rs @@ -1,7 +1,7 @@ use crate::{ generic64::{Assembler, CallConv, RegTrait}, - sign_extended_int_builtins, single_register_floats, single_register_int_builtins, - single_register_integers, single_register_layouts, Env, + pointer_layouts, sign_extended_int_builtins, single_register_floats, + single_register_int_builtins, single_register_integers, single_register_layouts, Env, }; use bumpalo::collections::Vec; use roc_builtins::bitcode::{FloatWidth, IntWidth}; @@ -91,7 +91,7 @@ pub struct StorageManager< phantom_cc: PhantomData, phantom_asm: PhantomData, pub(crate) env: &'r Env<'a>, - target_info: TargetInfo, + pub(crate) target_info: TargetInfo, // Data about where each symbol is stored. symbol_storage_map: MutMap>, @@ -598,40 +598,38 @@ impl< } } - pub fn load_union_tag_id( + pub fn load_union_tag_id_nonrecursive( &mut self, layout_interner: &mut STLayoutInterner<'a>, _buf: &mut Vec<'a, u8>, sym: &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. let owned_data = self.remove_allocation_for_sym(structure); self.allocation_map .insert(*structure, Rc::clone(&owned_data)); - match union_layout { - UnionLayout::NonRecursive(_) => { - let (union_offset, _) = self.stack_offset_and_size(structure); - let (data_size, data_alignment) = - union_layout.data_size_and_alignment(layout_interner, self.target_info); - let id_offset = data_size - data_alignment; - let discriminant = union_layout.discriminant(); + let (union_offset, _) = self.stack_offset_and_size(structure); - let size = discriminant.stack_size(); - self.allocation_map.insert(*sym, owned_data); - self.symbol_storage_map.insert( - *sym, - Stack(ReferencedPrimitive { - base_offset: union_offset + id_offset as i32, - size, - sign_extend: false, // tag ids are always unsigned - }), - ); - } - x => todo!("getting tag id of union with layout ({:?})", x), - } + let (data_size, data_alignment) = + union_layout.data_size_and_alignment(layout_interner, self.target_info); + let id_offset = data_size - data_alignment; + let discriminant = union_layout.discriminant(); + + let size = discriminant.stack_size(); + self.allocation_map.insert(*sym, owned_data); + self.symbol_storage_map.insert( + *sym, + Stack(ReferencedPrimitive { + base_offset: union_offset + id_offset as i32, + size, + sign_extend: false, // tag ids are always unsigned + }), + ); } // Loads the dst to be the later 64 bits of a list (its length). @@ -700,56 +698,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. pub fn copy_symbol_to_arg_pointer( &mut self, @@ -845,12 +793,6 @@ impl< 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) => { // like its runtime representation self.copy_symbol_to_stack_offset( @@ -861,14 +803,18 @@ impl< &lambda_set.runtime_representation(), ) } - _ if layout_interner.stack_size(*layout) == 0 => {} LayoutRepr::Struct { .. } | LayoutRepr::Union(UnionLayout::NonRecursive(_)) => { let (from_offset, size) = self.stack_offset_and_size(sym); debug_assert_eq!(size, layout_interner.stack_size(*layout)); 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 +1106,7 @@ impl< layout: InLayout<'a>, ) { match layout_interner.get(layout).repr { - single_register_layouts!() => { + single_register_layouts!() | pointer_layouts!() => { let base_offset = self.claim_stack_size(8); self.symbol_storage_map.insert( symbol, @@ -1172,20 +1118,17 @@ impl< self.allocation_map .insert(symbol, Rc::new((base_offset, 8))); } + LayoutRepr::LambdaSet(lambda_set) => self.joinpoint_argument_stack_storage( + layout_interner, + symbol, + lambda_set.runtime_representation(), + ), _ => { - if let LayoutRepr::LambdaSet(lambda_set) = layout_interner.get(layout).repr { - self.joinpoint_argument_stack_storage( - layout_interner, - symbol, - lambda_set.runtime_representation(), - ) + let stack_size = layout_interner.stack_size(layout); + if stack_size == 0 { + self.no_data(&symbol); } else { - let stack_size = layout_interner.stack_size(layout); - if stack_size == 0 { - self.no_data(&symbol); - } else { - self.claim_stack_area(&symbol, stack_size); - } + self.claim_stack_area(&symbol, stack_size); } } } @@ -1228,7 +1171,7 @@ impl< base_offset: i32, ) { match layout_interner.get(layout).repr { - single_register_integers!() => { + single_register_integers!() | pointer_layouts!() => { let reg = self.load_to_general_reg(buf, &symbol); ASM::mov_base32_reg64(buf, base_offset, reg); } @@ -1236,27 +1179,21 @@ impl< let reg = self.load_to_float_reg(buf, &symbol); ASM::mov_base32_freg64(buf, base_offset, reg); } - _ => match layout_interner.get(layout).repr { - LayoutRepr::LambdaSet(lambda_set) => { - self.jump_argument_stack_storage( - layout_interner, - buf, - symbol, - lambda_set.runtime_representation(), - base_offset, - ); - } - LayoutRepr::Boxed(_) => { - let reg = self.load_to_general_reg(buf, &symbol); - ASM::mov_base32_reg64(buf, base_offset, reg); - } - _ => { - internal_error!( - r"cannot load non-primitive layout ({:?}) to primitive stack location", - layout_interner.dbg(layout) - ) - } - }, + LayoutRepr::LambdaSet(lambda_set) => { + self.jump_argument_stack_storage( + layout_interner, + buf, + symbol, + lambda_set.runtime_representation(), + base_offset, + ); + } + _ => { + internal_error!( + r"cannot load non-primitive layout ({:?}) to primitive stack location", + layout_interner.dbg(layout) + ) + } } } @@ -1341,6 +1278,22 @@ impl< 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. /// 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. @@ -1541,12 +1494,10 @@ impl< fn is_primitive(layout_interner: &mut STLayoutInterner<'_>, layout: InLayout<'_>) -> bool { match layout_interner.get(layout).repr { single_register_layouts!() => true, - _ => match layout_interner.get(layout).repr { - LayoutRepr::Boxed(_) => true, - LayoutRepr::LambdaSet(lambda_set) => { - is_primitive(layout_interner, lambda_set.runtime_representation()) - } - _ => false, - }, + pointer_layouts!() => true, + LayoutRepr::LambdaSet(lambda_set) => { + is_primitive(layout_interner, lambda_set.runtime_representation()) + } + _ => false, } } diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index 8965c76857..17b2bc2d51 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -1,7 +1,7 @@ use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait}; use crate::{ - single_register_floats, single_register_int_builtins, single_register_integers, - single_register_layouts, Relocation, + pointer_layouts, single_register_floats, single_register_int_builtins, + single_register_integers, single_register_layouts, Relocation, }; use bumpalo::collections::Vec; use roc_builtins::bitcode::{FloatWidth, IntWidth}; @@ -461,6 +461,7 @@ impl X64_64SystemVStoreArgs { ) { match layout_interner.get(in_layout).repr { 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), LayoutRepr::I128 | LayoutRepr::U128 => { let (offset, _) = storage_manager.stack_offset_and_size(&sym); @@ -507,92 +508,83 @@ impl X64_64SystemVStoreArgs { } 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, + storage_manager, + layout_interner, + sym, + lambda_set.runtime_representation(), + ), + LayoutRepr::Struct { .. } => { + // for now, just also store this on the stack + let (base_offset, size) = storage_manager.stack_offset_and_size(&sym); + debug_assert_eq!(base_offset % 8, 0); + for i in (0..size as i32).step_by(8) { + X86_64Assembler::mov_reg64_base32( buf, - storage_manager, - layout_interner, - sym, - lambda_set.runtime_representation(), - ), - LayoutRepr::Struct { .. } => { - // for now, just also store this on the stack - let (base_offset, size) = storage_manager.stack_offset_and_size(&sym); - debug_assert_eq!(base_offset % 8, 0); - for i in (0..size as i32).step_by(8) { - X86_64Assembler::mov_reg64_base32( - buf, - Self::GENERAL_RETURN_REGS[0], - base_offset + i, - ); - X86_64Assembler::mov_stack32_reg64( - buf, - self.tmp_stack_offset + i, - Self::GENERAL_RETURN_REGS[0], - ); - } - self.tmp_stack_offset += size as i32; - } - LayoutRepr::Union(UnionLayout::NonRecursive(_)) => { - type ASM = X86_64Assembler; + Self::GENERAL_RETURN_REGS[0], + base_offset + i, + ); + X86_64Assembler::mov_stack32_reg64( + buf, + self.tmp_stack_offset + i, + Self::GENERAL_RETURN_REGS[0], + ); + } + self.tmp_stack_offset += size as i32; + } + LayoutRepr::Union(UnionLayout::NonRecursive(_)) => { + type ASM = X86_64Assembler; - let tmp_reg = Self::GENERAL_RETURN_REGS[0]; - let stack_offset = self.tmp_stack_offset; + let tmp_reg = Self::GENERAL_RETURN_REGS[0]; + let stack_offset = self.tmp_stack_offset; - let mut copied = 0; - let (base_offset, size) = storage_manager.stack_offset_and_size(&sym); + let mut copied = 0; + let (base_offset, size) = storage_manager.stack_offset_and_size(&sym); - if size - copied >= 8 { - for _ in (0..(size - copied)).step_by(8) { - ASM::mov_reg64_base32(buf, tmp_reg, base_offset + copied as i32); - ASM::mov_stack32_reg64(buf, stack_offset + copied as i32, tmp_reg); + if size - copied >= 8 { + for _ in (0..(size - copied)).step_by(8) { + ASM::mov_reg64_base32(buf, tmp_reg, base_offset + copied as i32); + ASM::mov_stack32_reg64(buf, stack_offset + copied as i32, tmp_reg); - copied += 8; - } - } - - if size - copied >= 4 { - for _ in (0..(size - copied)).step_by(4) { - ASM::mov_reg32_base32(buf, tmp_reg, base_offset + copied as i32); - ASM::mov_stack32_reg32(buf, stack_offset + copied as i32, tmp_reg); - - copied += 4; - } - } - - if size - copied >= 2 { - for _ in (0..(size - copied)).step_by(2) { - ASM::mov_reg16_base32(buf, tmp_reg, base_offset + copied as i32); - ASM::mov_stack32_reg16(buf, stack_offset + copied as i32, tmp_reg); - - copied += 2; - } - } - - if size - copied >= 1 { - for _ in (0..(size - copied)).step_by(1) { - ASM::mov_reg8_base32(buf, tmp_reg, base_offset + copied as i32); - ASM::mov_stack32_reg8(buf, stack_offset + copied as i32, tmp_reg); - - copied += 1; - } - } - - self.tmp_stack_offset += size as i32; - } - _ => { - todo!( - "calling with arg type, {:?}", - layout_interner.dbg(in_layout) - ); + copied += 8; } } + + if size - copied >= 4 { + for _ in (0..(size - copied)).step_by(4) { + ASM::mov_reg32_base32(buf, tmp_reg, base_offset + copied as i32); + ASM::mov_stack32_reg32(buf, stack_offset + copied as i32, tmp_reg); + + copied += 4; + } + } + + if size - copied >= 2 { + for _ in (0..(size - copied)).step_by(2) { + ASM::mov_reg16_base32(buf, tmp_reg, base_offset + copied as i32); + ASM::mov_stack32_reg16(buf, stack_offset + copied as i32, tmp_reg); + + copied += 2; + } + } + + if size - copied >= 1 { + for _ in (0..(size - copied)).step_by(1) { + ASM::mov_reg8_base32(buf, tmp_reg, base_offset + copied as i32); + ASM::mov_stack32_reg8(buf, stack_offset + copied as i32, tmp_reg); + + copied += 1; + } + } + + self.tmp_stack_offset += size as i32; + } + _ => { + todo!( + "calling with arg type, {:?}", + layout_interner.dbg(in_layout) + ); } } } @@ -664,6 +656,7 @@ impl X64_64SystemVLoadArgs { let stack_size = layout_interner.stack_size(in_layout); match layout_interner.get(in_layout).repr { 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), _ if stack_size == 0 => { storage_manager.no_data(&sym); @@ -673,38 +666,32 @@ impl X64_64SystemVLoadArgs { storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size); 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( - storage_manager, - layout_interner, - sym, - lambda_set.runtime_representation(), - ), - LayoutRepr::Struct { .. } => { - // for now, just also store this on the stack - storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size); - self.argument_offset += stack_size as i32; - } - LayoutRepr::Builtin(Builtin::Int(IntWidth::U128 | IntWidth::I128)) => { - storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size); - self.argument_offset += stack_size as i32; - } - LayoutRepr::Union(UnionLayout::NonRecursive(_)) => { - // for now, just also store this on the stack - storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size); - self.argument_offset += stack_size as i32; - } - _ => { - todo!( - "Loading args with layout {:?}", - layout_interner.dbg(in_layout) - ); - } - }, + LayoutRepr::LambdaSet(lambda_set) => self.load_arg( + storage_manager, + layout_interner, + sym, + lambda_set.runtime_representation(), + ), + LayoutRepr::Struct { .. } => { + // for now, just also store this on the stack + storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size); + self.argument_offset += stack_size as i32; + } + LayoutRepr::Builtin(Builtin::Int(IntWidth::U128 | IntWidth::I128)) => { + storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size); + self.argument_offset += stack_size as i32; + } + LayoutRepr::Union(UnionLayout::NonRecursive(_)) => { + // for now, just also store this on the stack + storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size); + self.argument_offset += stack_size as i32; + } + _ => { + todo!( + "Loading args with layout {:?}", + layout_interner.dbg(in_layout) + ); + } } } @@ -1756,7 +1743,7 @@ impl Assembler for X86_64Assembler { } #[inline(always)] - fn neq_reg64_reg64_reg64( + fn neq_reg_reg_reg( buf: &mut Vec<'_, u8>, register_width: RegisterWidth, dst: X86_64GeneralReg, diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index 679a24f7c1..db3483b5eb 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -815,7 +815,13 @@ trait Backend<'a> { self.load_literal_symbols([*ptr].as_slice()); self.expr_unbox(*sym, *ptr, element_layout) } - x => todo!("the expression, {:?}", x), + Expr::NullPointer => { + self.load_literal_i64(sym, 0); + } + Expr::Reuse { .. } => todo!(), + Expr::Reset { .. } => todo!(), + Expr::ResetRef { .. } => todo!(), + Expr::RuntimeErrorFunction(_) => todo!(), } } diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index f381a7870d..f75d6fefb7 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -1279,7 +1279,7 @@ fn linked_list_is_singleton() { } #[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() { assert_evals_to!( indoc!( From 58d4ae91d2178af5c735c266a179a1b687af9421 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 17 May 2023 20:20:14 +0200 Subject: [PATCH 43/54] clippy --- crates/compiler/gen_dev/src/generic64/storage.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs index 596f944075..71e0792d74 100644 --- a/crates/compiler/gen_dev/src/generic64/storage.rs +++ b/crates/compiler/gen_dev/src/generic64/storage.rs @@ -11,8 +11,7 @@ use roc_module::symbol::Symbol; use roc_mono::{ ir::{JoinPointId, Param}, layout::{ - Builtin, InLayout, Layout, LayoutInterner, LayoutRepr, STLayoutInterner, TagIdIntType, - UnionLayout, + Builtin, InLayout, Layout, LayoutInterner, LayoutRepr, STLayoutInterner, UnionLayout, }, }; use roc_target::TargetInfo; From c24312a0453e94ff1cf6f772bd36de6bf3882ccb Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 17 May 2023 22:49:59 +0200 Subject: [PATCH 44/54] fix logic error --- crates/compiler/gen_dev/src/generic64/mod.rs | 49 +++++++++++-------- .../compiler/test_gen/src/gen_primitives.rs | 6 +-- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 3795bcee38..430a37f0db 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -2744,15 +2744,6 @@ impl< match *nullable_id { true => { - ASM::neq_reg_reg_reg( - &mut self.buf, - RegisterWidth::W64, - dst_reg, - src1_reg, - src2_reg, - ); - } - false => { ASM::eq_reg_reg_reg( &mut self.buf, RegisterWidth::W64, @@ -2761,7 +2752,18 @@ impl< src2_reg, ); } + false => { + ASM::neq_reg_reg_reg( + &mut self.buf, + RegisterWidth::W64, + dst_reg, + src1_reg, + src2_reg, + ); + } } + + self.free_symbol(&tmp); } x => todo!("getting tag id of union with layout ({:?})", x), }; @@ -2778,10 +2780,12 @@ impl< 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 (data_size, data_alignment) = - union_layout.data_size_and_alignment(layout_interner, target_info); 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; @@ -2820,23 +2824,28 @@ impl< other_fields, } => { if tag_id == *nullable_id as TagIdIntType { - // step 1: make the struct + // 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 = - layout_interner.insert_no_semantic(LayoutRepr::Struct(other_fields)); + let layout = self + .layout_interner + .insert_no_semantic(LayoutRepr::Struct(other_fields)); + + self.load_literal_symbols(fields); self.storage_manager.create_struct( - layout_interner, - buf, + self.layout_interner, + &mut self.buf, &temp_sym, &layout, fields, ); // now effectively box this struct - self.expr_box(*sym, Symbol::DEV_TMP5, layout) - } else { - // it's just a null pointer - self.load_literal_i64(sym, 0); + self.expr_box(*sym, temp_sym, layout); + + self.free_symbol(&temp_sym); } } x => todo!("creating unions with layout: {:?}", x), diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index f75d6fefb7..07b5ffeb78 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -1244,7 +1244,7 @@ fn return_wrapped_closure() { } #[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() { assert_evals_to!( indoc!( @@ -1314,7 +1314,7 @@ fn linked_list_is_empty_1() { } #[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() { assert_evals_to!( indoc!( @@ -1346,7 +1346,7 @@ fn linked_list_is_empty_2() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn linked_list_singleton() { // verifies only that valid llvm is produced assert_evals_to!( From 8833096a0720d5ce4227b910248db2f1662ff7ae Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 17 May 2023 23:04:27 +0200 Subject: [PATCH 45/54] clippy --- crates/cli/src/main.rs | 6 ++---- crates/compiler/gen_dev/src/generic64/mod.rs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index baafb42f84..93fb5750d7 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -120,8 +120,7 @@ fn main() -> io::Result<()> { let input_path = matches.get_one::(ROC_FILE).unwrap(); let target = matches .get_one::(FLAG_TARGET) - .map(|s| Target::from_str(s).ok()) - .flatten() + .and_then(|s| Target::from_str(s).ok()) .unwrap_or_default(); roc_linker::generate_stub_lib( input_path, @@ -132,8 +131,7 @@ fn main() -> io::Result<()> { Some((CMD_BUILD, matches)) => { let target = matches .get_one::(FLAG_TARGET) - .map(|s| Target::from_str(s).ok()) - .flatten() + .and_then(|s| Target::from_str(s).ok()) .unwrap_or_default(); let link_type = match (matches.get_flag(FLAG_LIB), matches.get_flag(FLAG_NO_LINK)) { (true, false) => LinkType::Dylib, diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 430a37f0db..c862ba58d3 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -3430,7 +3430,7 @@ impl< ) { let base_offset = storage_manager.claim_stack_area(&dst, 24); - ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, offset + 0); + ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, offset); ASM::mov_base32_reg64(buf, base_offset, tmp_reg); ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, offset + 8); From dfed7b95e6240c3f87c1a38e3bb27d60806966c6 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 17 May 2023 23:04:40 +0200 Subject: [PATCH 46/54] enable more tests --- crates/compiler/test_gen/src/gen_primitives.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index 07b5ffeb78..4a6929ad02 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -597,7 +597,7 @@ fn top_level_destructure() { } #[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() { assert_evals_to!( indoc!( @@ -625,7 +625,7 @@ fn linked_list_len_0() { } #[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() { assert_evals_to!( indoc!( @@ -653,7 +653,7 @@ fn linked_list_len_twice_0() { } #[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() { assert_evals_to!( indoc!( @@ -681,7 +681,7 @@ fn linked_list_len_1() { } #[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() { assert_evals_to!( indoc!( @@ -709,7 +709,7 @@ fn linked_list_len_twice_1() { } #[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() { assert_evals_to!( indoc!( @@ -738,7 +738,7 @@ fn linked_list_len_3() { } #[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() { assert_evals_to!( indoc!( @@ -767,7 +767,7 @@ fn linked_list_sum_num_a() { } #[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() { assert_evals_to!( indoc!( @@ -1712,7 +1712,7 @@ fn nested_pattern_match_two_ways() { } #[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() { // the important part here is that the first case (with the nested Cons) does not match // TODO this also has undefined behavior @@ -1744,7 +1744,7 @@ fn linked_list_guarded_double_pattern_match() { } #[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() { assert_evals_to!( indoc!( From f609ec68fc79ebb9b36258e52b09a8563493589a Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 18 May 2023 01:53:06 +0200 Subject: [PATCH 47/54] update mono tests?! --- ...lambda_set_productive_nullable_wrapped.txt | 20 +++++++++---------- .../test_mono/generated/issue_4749.txt | 8 ++++---- ..._4772_weakened_monomorphic_destructure.txt | 8 ++++---- .../test_mono/generated/monomorphized_tag.txt | 4 ---- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/crates/compiler/test_mono/generated/compose_recursive_lambda_set_productive_nullable_wrapped.txt b/crates/compiler/test_mono/generated/compose_recursive_lambda_set_productive_nullable_wrapped.txt index bcbbc68376..7b25665927 100644 --- a/crates/compiler/test_mono/generated/compose_recursive_lambda_set_productive_nullable_wrapped.txt +++ b/crates/compiler/test_mono/generated/compose_recursive_lambda_set_productive_nullable_wrapped.txt @@ -58,7 +58,7 @@ procedure Test.11 (Test.53, Test.54): joinpoint Test.27 Test.12 #Attr.12: let Test.8 : Int1 = UnionAtIndex (Id 2) (Index 1) #Attr.12; let Test.7 : [, C *self Int1, C *self Int1] = UnionAtIndex (Id 2) (Index 0) #Attr.12; - joinpoint #Derived_gen.0: + joinpoint #Derived_gen.2: joinpoint Test.31 Test.29: let Test.30 : U8 = GetTagId Test.7; switch Test.30: @@ -85,14 +85,14 @@ procedure Test.11 (Test.53, Test.54): jump Test.31 Test.32; in - let #Derived_gen.1 : Int1 = lowlevel RefCountIsUnique #Attr.12; - if #Derived_gen.1 then + let #Derived_gen.3 : Int1 = lowlevel RefCountIsUnique #Attr.12; + if #Derived_gen.3 then decref #Attr.12; - jump #Derived_gen.0; + jump #Derived_gen.2; else inc Test.7; decref #Attr.12; - jump #Derived_gen.0; + jump #Derived_gen.2; in jump Test.27 Test.53 Test.54; @@ -125,7 +125,7 @@ procedure Test.6 (Test.7, Test.8, Test.5): procedure Test.9 (Test.10, #Attr.12): let Test.8 : Int1 = UnionAtIndex (Id 1) (Index 1) #Attr.12; let Test.7 : [, C *self Int1, C *self Int1] = UnionAtIndex (Id 1) (Index 0) #Attr.12; - joinpoint #Derived_gen.2: + joinpoint #Derived_gen.0: let Test.37 : U8 = GetTagId Test.7; joinpoint Test.38 Test.36: switch Test.8: @@ -153,14 +153,14 @@ procedure Test.9 (Test.10, #Attr.12): jump Test.38 Test.39; in - let #Derived_gen.3 : Int1 = lowlevel RefCountIsUnique #Attr.12; - if #Derived_gen.3 then + let #Derived_gen.1 : Int1 = lowlevel RefCountIsUnique #Attr.12; + if #Derived_gen.1 then decref #Attr.12; - jump #Derived_gen.2; + jump #Derived_gen.0; else inc Test.7; decref #Attr.12; - jump #Derived_gen.2; + jump #Derived_gen.0; procedure Test.0 (): let Test.41 : Int1 = false; diff --git a/crates/compiler/test_mono/generated/issue_4749.txt b/crates/compiler/test_mono/generated/issue_4749.txt index 9a5d0685da..3aba16cf00 100644 --- a/crates/compiler/test_mono/generated/issue_4749.txt +++ b/crates/compiler/test_mono/generated/issue_4749.txt @@ -161,8 +161,8 @@ procedure Json.533 (Json.534): procedure Json.535 (Json.1191): let Json.1192 : List U8 = StructAtIndex 1 Json.1191; - let #Derived_gen.0 : List U8 = StructAtIndex 0 Json.1191; - dec #Derived_gen.0; + let #Derived_gen.1 : List U8 = StructAtIndex 0 Json.1191; + dec #Derived_gen.1; ret Json.1192; procedure Json.59 (): @@ -815,8 +815,8 @@ procedure Str.9 (Str.79): else let Str.300 : U8 = StructAtIndex 3 Str.80; let Str.301 : U64 = StructAtIndex 0 Str.80; - let #Derived_gen.1 : Str = StructAtIndex 1 Str.80; - dec #Derived_gen.1; + let #Derived_gen.0 : Str = StructAtIndex 1 Str.80; + dec #Derived_gen.0; let Str.299 : {U64, U8} = Struct {Str.301, Str.300}; let Str.298 : [C {U64, U8}, C Str] = TagId(0) Str.299; ret Str.298; diff --git a/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt b/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt index 95b540c91e..2d7ade37a1 100644 --- a/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt +++ b/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt @@ -135,8 +135,8 @@ procedure Json.533 (Json.534): procedure Json.535 (Json.1191): let Json.1192 : List U8 = StructAtIndex 1 Json.1191; - let #Derived_gen.1 : List U8 = StructAtIndex 0 Json.1191; - dec #Derived_gen.1; + let #Derived_gen.0 : List U8 = StructAtIndex 0 Json.1191; + dec #Derived_gen.0; ret Json.1192; procedure Json.59 (): @@ -816,8 +816,8 @@ procedure Str.9 (Str.79): else let Str.314 : U8 = StructAtIndex 3 Str.80; let Str.315 : U64 = StructAtIndex 0 Str.80; - let #Derived_gen.0 : Str = StructAtIndex 1 Str.80; - dec #Derived_gen.0; + let #Derived_gen.1 : Str = StructAtIndex 1 Str.80; + dec #Derived_gen.1; let Str.313 : {U64, U8} = Struct {Str.315, Str.314}; let Str.312 : [C {U64, U8}, C Str] = TagId(0) Str.313; ret Str.312; diff --git a/crates/compiler/test_mono/generated/monomorphized_tag.txt b/crates/compiler/test_mono/generated/monomorphized_tag.txt index ee240168f8..7aa0cffa6d 100644 --- a/crates/compiler/test_mono/generated/monomorphized_tag.txt +++ b/crates/compiler/test_mono/generated/monomorphized_tag.txt @@ -2,10 +2,6 @@ procedure Test.1 (Test.4): let Test.12 : Int1 = false; ret Test.12; -procedure Test.1 (Test.4): - let Test.14 : Int1 = false; - ret Test.14; - procedure Test.2 (Test.5, Test.6): let Test.10 : U8 = 18i64; ret Test.10; From 0ace44ae8c23029552d54340edc5a0716ebb20ab Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 18 May 2023 12:28:52 +0200 Subject: [PATCH 48/54] implement reset/reuse in the dev backend --- crates/compiler/gen_dev/src/generic64/mod.rs | 73 ++++++++++++++++--- crates/compiler/gen_dev/src/lib.rs | 66 +++++++++++++++-- .../compiler/test_gen/src/gen_primitives.rs | 2 +- .../encode_derived_nested_record_string.txt | 8 +- ...encode_derived_record_one_field_string.txt | 8 +- ...ncode_derived_record_two_field_strings.txt | 8 +- 6 files changed, 135 insertions(+), 30 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index c862ba58d3..d807b44fb9 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -2677,7 +2677,13 @@ impl< 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, + ) { let element_width_symbol = Symbol::DEV_TMP; self.load_layout_stack_size(element_layout, element_width_symbol); @@ -2685,18 +2691,32 @@ impl< let element_alignment_symbol = Symbol::DEV_TMP2; self.load_layout_alignment(Layout::U32, element_alignment_symbol); - self.allocate_with_refcount( - Symbol::DEV_TMP3, - element_width_symbol, - element_alignment_symbol, - ); + let allocation = match reuse { + None => { + self.allocate_with_refcount( + Symbol::DEV_TMP3, + element_width_symbol, + element_alignment_symbol, + ); + + Symbol::DEV_TMP3 + } + Some(reuse) => { + // check for null + self.allocate_with_refcount_if_null(reuse, element_layout); + + reuse + } + }; self.free_symbol(&element_width_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); + if allocation == Symbol::DEV_TMP3 { + self.free_symbol(&Symbol::DEV_TMP3); + } } fn expr_unbox(&mut self, dst: Symbol, ptr: Symbol, element_layout: InLayout<'a>) { @@ -2775,6 +2795,7 @@ impl< fields: &'a [Symbol], union_layout: &UnionLayout<'a>, tag_id: TagIdIntType, + reuse: Option, ) { let target_info = self.storage_manager.target_info; @@ -2843,7 +2864,7 @@ impl< ); // now effectively box this struct - self.expr_box(*sym, temp_sym, layout); + self.expr_box(*sym, temp_sym, layout, reuse); self.free_symbol(&temp_sym); } @@ -3420,6 +3441,36 @@ impl< ); } + fn allocate_with_refcount_if_null(&mut self, dst: Symbol, layout: InLayout) { + let reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, &dst); + + // jump to where the pointer is valid, because it is already valid if non-zero + let jmp_index = ASM::jne_reg64_imm64_imm32(&mut self.buf, reg, 0x0, 0); + + // so, the pointer is NULL, allocate + + let data_bytes = Symbol::DEV_TMP; + self.load_layout_stack_size(layout, data_bytes); + + // Load allocation alignment (u32) + let element_alignment = Symbol::DEV_TMP2; + self.load_layout_alignment(Layout::U32, element_alignment); + + self.allocate_with_refcount(dst, data_bytes, element_alignment); + + self.free_symbol(&data_bytes); + self.free_symbol(&element_alignment); + + // update the jump + let destination_index = self.buf.len(); + let mut tmp = bumpalo::vec![in self.env.arena]; + ASM::jne_reg64_imm64_imm32(&mut tmp, reg, 0x0, (destination_index - jmp_index) as i32); + + self.buf[jmp_index..][..tmp.len()].copy_from_slice(tmp.as_slice()); + } + fn unbox_str_or_list( buf: &mut Vec<'a, u8>, storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>, @@ -3753,7 +3804,7 @@ impl< } /// 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 alignment = self.layout_interner.alignment_bytes(layout); let alignment_literal = Literal::Int((alignment as i128).to_ne_bytes()); @@ -3762,7 +3813,7 @@ impl< } /// 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 width = self.layout_interner.stack_size(layout); let width_literal = Literal::Int((width as i128).to_ne_bytes()); diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index db3483b5eb..af5b9f750e 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -798,7 +798,7 @@ trait Backend<'a> { .. } => { 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 } => { let element_layout = match self.interner().get(*layout).repr { @@ -807,7 +807,7 @@ trait Backend<'a> { }; 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 } => { let element_layout = *layout; @@ -818,9 +818,56 @@ trait Backend<'a> { Expr::NullPointer => { self.load_literal_i64(sym, 0); } - Expr::Reuse { .. } => todo!(), - Expr::Reset { .. } => todo!(), - Expr::ResetRef { .. } => todo!(), + 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!(), } } @@ -2211,13 +2258,20 @@ trait Backend<'a> { args: &'a [Symbol], tag_layout: &UnionLayout<'a>, tag_id: TagIdIntType, + reuse: Option, ); /// load a value from a pointer fn expr_unbox(&mut self, sym: Symbol, ptr: Symbol, element_layout: InLayout<'a>); /// 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, + ); /// 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>); diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index 4a6929ad02..4c8a918591 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -795,7 +795,7 @@ fn linked_list_sum_int() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn linked_list_map() { assert_evals_to!( indoc!( diff --git a/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt b/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt index ccef4a274f..f9f137371d 100644 --- a/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt @@ -803,14 +803,14 @@ procedure Json.81 (Json.801, Json.802): procedure Json.831 (Json.1492): let Json.1867 : List Str = StructAtIndex 1 Json.1492; - let #Derived_gen.28 : List Str = StructAtIndex 0 Json.1492; - dec #Derived_gen.28; + let #Derived_gen.30 : List Str = StructAtIndex 0 Json.1492; + dec #Derived_gen.30; ret Json.1867; procedure Json.839 (Json.1213): let Json.1588 : List Str = StructAtIndex 1 Json.1213; - let #Derived_gen.30 : List Str = StructAtIndex 0 Json.1213; - dec #Derived_gen.30; + let #Derived_gen.28 : List Str = StructAtIndex 0 Json.1213; + dec #Derived_gen.28; ret Json.1588; procedure Json.86 (Json.808): diff --git a/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt b/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt index eb0e2fa779..a27366cd85 100644 --- a/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt @@ -725,14 +725,14 @@ procedure Json.81 (Json.801, Json.802): procedure Json.831 (Json.1492): let Json.1493 : List Str = StructAtIndex 1 Json.1492; - let #Derived_gen.14 : List Str = StructAtIndex 0 Json.1492; - dec #Derived_gen.14; + let #Derived_gen.16 : List Str = StructAtIndex 0 Json.1492; + dec #Derived_gen.16; ret Json.1493; procedure Json.839 (Json.1213): let Json.1214 : List Str = StructAtIndex 1 Json.1213; - let #Derived_gen.16 : List Str = StructAtIndex 0 Json.1213; - dec #Derived_gen.16; + let #Derived_gen.14 : List Str = StructAtIndex 0 Json.1213; + dec #Derived_gen.14; ret Json.1214; procedure Json.86 (Json.808): diff --git a/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt b/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt index b8615bf0eb..67385089d2 100644 --- a/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt +++ b/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt @@ -732,8 +732,8 @@ procedure Json.81 (Json.801, Json.802): procedure Json.831 (Json.1492): let Json.1493 : List Str = StructAtIndex 1 Json.1492; - let #Derived_gen.19 : List Str = StructAtIndex 0 Json.1492; - dec #Derived_gen.19; + let #Derived_gen.18 : List Str = StructAtIndex 0 Json.1492; + dec #Derived_gen.18; ret Json.1493; procedure Json.839 (Json.1213): @@ -1369,8 +1369,8 @@ procedure Str.9 (Str.79): else let Str.300 : U8 = StructAtIndex 3 Str.80; let Str.301 : U64 = StructAtIndex 0 Str.80; - let #Derived_gen.18 : Str = StructAtIndex 1 Str.80; - dec #Derived_gen.18; + let #Derived_gen.19 : Str = StructAtIndex 1 Str.80; + dec #Derived_gen.19; let Str.299 : {U64, U8} = Struct {Str.301, Str.300}; let Str.298 : [C {U64, U8}, C Str] = TagId(0) Str.299; ret Str.298; From 4af9788b518d52f3efaea1a693f620218f003788 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 18 May 2023 16:01:36 +0200 Subject: [PATCH 49/54] fix register logic bug in reset/reuse --- crates/compiler/gen_dev/src/generic64/mod.rs | 64 +++++++++++++------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index d807b44fb9..0dfa1d9c1b 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -2691,21 +2691,18 @@ impl< let element_alignment_symbol = Symbol::DEV_TMP2; self.load_layout_alignment(Layout::U32, element_alignment_symbol); - let allocation = match reuse { + let allocation = self.debug_symbol("allocation"); + + match reuse { None => { self.allocate_with_refcount( - Symbol::DEV_TMP3, + allocation, element_width_symbol, element_alignment_symbol, ); - - Symbol::DEV_TMP3 } Some(reuse) => { - // check for null - self.allocate_with_refcount_if_null(reuse, element_layout); - - reuse + self.allocate_with_refcount_if_null(allocation, reuse, element_layout); } }; @@ -2714,9 +2711,7 @@ impl< self.build_ptr_write(sym, allocation, value, element_layout); - if allocation == Symbol::DEV_TMP3 { - self.free_symbol(&Symbol::DEV_TMP3); - } + self.free_symbol(&allocation); } fn expr_unbox(&mut self, dst: Symbol, ptr: Symbol, element_layout: InLayout<'a>) { @@ -3441,34 +3436,59 @@ impl< ); } - fn allocate_with_refcount_if_null(&mut self, dst: Symbol, layout: InLayout) { - let reg = self + 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, &dst); + .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_index = ASM::jne_reg64_imm64_imm32(&mut self.buf, reg, 0x0, 0); + 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 = Symbol::DEV_TMP; + let data_bytes = self.debug_symbol("data_bytes"); self.load_layout_stack_size(layout, data_bytes); - // Load allocation alignment (u32) - let element_alignment = Symbol::DEV_TMP2; - self.load_layout_alignment(Layout::U32, element_alignment); + 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(); - let mut tmp = bumpalo::vec![in self.env.arena]; - ASM::jne_reg64_imm64_imm32(&mut tmp, reg, 0x0, (destination_index - jmp_index) as i32); + ASM::jne_reg64_imm64_imm32( + &mut tmp, + src_reg, + 0x0, + (destination_index - jmp_end_index) as i32, + ); - self.buf[jmp_index..][..tmp.len()].copy_from_slice(tmp.as_slice()); + 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( From f1375b27cc48f06e814d2b10c3678dd18ceb7ef4 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 31 May 2023 23:59:26 +0200 Subject: [PATCH 50/54] fix mono tests, again --- ...lambda_set_productive_nullable_wrapped.txt | 20 +++++++++---------- .../encode_derived_nested_record_string.txt | 8 ++++---- ...encode_derived_record_one_field_string.txt | 8 ++++---- ...ncode_derived_record_two_field_strings.txt | 8 ++++---- .../test_mono/generated/issue_4749.txt | 8 ++++---- ..._4772_weakened_monomorphic_destructure.txt | 8 ++++---- .../test_mono/generated/monomorphized_tag.txt | 4 ++++ 7 files changed, 34 insertions(+), 30 deletions(-) diff --git a/crates/compiler/test_mono/generated/compose_recursive_lambda_set_productive_nullable_wrapped.txt b/crates/compiler/test_mono/generated/compose_recursive_lambda_set_productive_nullable_wrapped.txt index 7b25665927..bcbbc68376 100644 --- a/crates/compiler/test_mono/generated/compose_recursive_lambda_set_productive_nullable_wrapped.txt +++ b/crates/compiler/test_mono/generated/compose_recursive_lambda_set_productive_nullable_wrapped.txt @@ -58,7 +58,7 @@ procedure Test.11 (Test.53, Test.54): joinpoint Test.27 Test.12 #Attr.12: let Test.8 : Int1 = UnionAtIndex (Id 2) (Index 1) #Attr.12; let Test.7 : [, C *self Int1, C *self Int1] = UnionAtIndex (Id 2) (Index 0) #Attr.12; - joinpoint #Derived_gen.2: + joinpoint #Derived_gen.0: joinpoint Test.31 Test.29: let Test.30 : U8 = GetTagId Test.7; switch Test.30: @@ -85,14 +85,14 @@ procedure Test.11 (Test.53, Test.54): jump Test.31 Test.32; in - let #Derived_gen.3 : Int1 = lowlevel RefCountIsUnique #Attr.12; - if #Derived_gen.3 then + let #Derived_gen.1 : Int1 = lowlevel RefCountIsUnique #Attr.12; + if #Derived_gen.1 then decref #Attr.12; - jump #Derived_gen.2; + jump #Derived_gen.0; else inc Test.7; decref #Attr.12; - jump #Derived_gen.2; + jump #Derived_gen.0; in jump Test.27 Test.53 Test.54; @@ -125,7 +125,7 @@ procedure Test.6 (Test.7, Test.8, Test.5): procedure Test.9 (Test.10, #Attr.12): let Test.8 : Int1 = UnionAtIndex (Id 1) (Index 1) #Attr.12; let Test.7 : [, C *self Int1, C *self Int1] = UnionAtIndex (Id 1) (Index 0) #Attr.12; - joinpoint #Derived_gen.0: + joinpoint #Derived_gen.2: let Test.37 : U8 = GetTagId Test.7; joinpoint Test.38 Test.36: switch Test.8: @@ -153,14 +153,14 @@ procedure Test.9 (Test.10, #Attr.12): jump Test.38 Test.39; in - let #Derived_gen.1 : Int1 = lowlevel RefCountIsUnique #Attr.12; - if #Derived_gen.1 then + let #Derived_gen.3 : Int1 = lowlevel RefCountIsUnique #Attr.12; + if #Derived_gen.3 then decref #Attr.12; - jump #Derived_gen.0; + jump #Derived_gen.2; else inc Test.7; decref #Attr.12; - jump #Derived_gen.0; + jump #Derived_gen.2; procedure Test.0 (): let Test.41 : Int1 = false; diff --git a/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt b/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt index f9f137371d..ccef4a274f 100644 --- a/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt @@ -803,14 +803,14 @@ procedure Json.81 (Json.801, Json.802): procedure Json.831 (Json.1492): let Json.1867 : List Str = StructAtIndex 1 Json.1492; - let #Derived_gen.30 : List Str = StructAtIndex 0 Json.1492; - dec #Derived_gen.30; + let #Derived_gen.28 : List Str = StructAtIndex 0 Json.1492; + dec #Derived_gen.28; ret Json.1867; procedure Json.839 (Json.1213): let Json.1588 : List Str = StructAtIndex 1 Json.1213; - let #Derived_gen.28 : List Str = StructAtIndex 0 Json.1213; - dec #Derived_gen.28; + let #Derived_gen.30 : List Str = StructAtIndex 0 Json.1213; + dec #Derived_gen.30; ret Json.1588; procedure Json.86 (Json.808): diff --git a/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt b/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt index a27366cd85..eb0e2fa779 100644 --- a/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt @@ -725,14 +725,14 @@ procedure Json.81 (Json.801, Json.802): procedure Json.831 (Json.1492): let Json.1493 : List Str = StructAtIndex 1 Json.1492; - let #Derived_gen.16 : List Str = StructAtIndex 0 Json.1492; - dec #Derived_gen.16; + let #Derived_gen.14 : List Str = StructAtIndex 0 Json.1492; + dec #Derived_gen.14; ret Json.1493; procedure Json.839 (Json.1213): let Json.1214 : List Str = StructAtIndex 1 Json.1213; - let #Derived_gen.14 : List Str = StructAtIndex 0 Json.1213; - dec #Derived_gen.14; + let #Derived_gen.16 : List Str = StructAtIndex 0 Json.1213; + dec #Derived_gen.16; ret Json.1214; procedure Json.86 (Json.808): diff --git a/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt b/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt index 67385089d2..b8615bf0eb 100644 --- a/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt +++ b/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt @@ -732,8 +732,8 @@ procedure Json.81 (Json.801, Json.802): procedure Json.831 (Json.1492): let Json.1493 : List Str = StructAtIndex 1 Json.1492; - let #Derived_gen.18 : List Str = StructAtIndex 0 Json.1492; - dec #Derived_gen.18; + let #Derived_gen.19 : List Str = StructAtIndex 0 Json.1492; + dec #Derived_gen.19; ret Json.1493; procedure Json.839 (Json.1213): @@ -1369,8 +1369,8 @@ procedure Str.9 (Str.79): else let Str.300 : U8 = StructAtIndex 3 Str.80; let Str.301 : U64 = StructAtIndex 0 Str.80; - let #Derived_gen.19 : Str = StructAtIndex 1 Str.80; - dec #Derived_gen.19; + let #Derived_gen.18 : Str = StructAtIndex 1 Str.80; + dec #Derived_gen.18; let Str.299 : {U64, U8} = Struct {Str.301, Str.300}; let Str.298 : [C {U64, U8}, C Str] = TagId(0) Str.299; ret Str.298; diff --git a/crates/compiler/test_mono/generated/issue_4749.txt b/crates/compiler/test_mono/generated/issue_4749.txt index 3aba16cf00..9a5d0685da 100644 --- a/crates/compiler/test_mono/generated/issue_4749.txt +++ b/crates/compiler/test_mono/generated/issue_4749.txt @@ -161,8 +161,8 @@ procedure Json.533 (Json.534): procedure Json.535 (Json.1191): let Json.1192 : List U8 = StructAtIndex 1 Json.1191; - let #Derived_gen.1 : List U8 = StructAtIndex 0 Json.1191; - dec #Derived_gen.1; + let #Derived_gen.0 : List U8 = StructAtIndex 0 Json.1191; + dec #Derived_gen.0; ret Json.1192; procedure Json.59 (): @@ -815,8 +815,8 @@ procedure Str.9 (Str.79): else let Str.300 : U8 = StructAtIndex 3 Str.80; let Str.301 : U64 = StructAtIndex 0 Str.80; - let #Derived_gen.0 : Str = StructAtIndex 1 Str.80; - dec #Derived_gen.0; + let #Derived_gen.1 : Str = StructAtIndex 1 Str.80; + dec #Derived_gen.1; let Str.299 : {U64, U8} = Struct {Str.301, Str.300}; let Str.298 : [C {U64, U8}, C Str] = TagId(0) Str.299; ret Str.298; diff --git a/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt b/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt index 2d7ade37a1..95b540c91e 100644 --- a/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt +++ b/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt @@ -135,8 +135,8 @@ procedure Json.533 (Json.534): procedure Json.535 (Json.1191): let Json.1192 : List U8 = StructAtIndex 1 Json.1191; - let #Derived_gen.0 : List U8 = StructAtIndex 0 Json.1191; - dec #Derived_gen.0; + let #Derived_gen.1 : List U8 = StructAtIndex 0 Json.1191; + dec #Derived_gen.1; ret Json.1192; procedure Json.59 (): @@ -816,8 +816,8 @@ procedure Str.9 (Str.79): else let Str.314 : U8 = StructAtIndex 3 Str.80; let Str.315 : U64 = StructAtIndex 0 Str.80; - let #Derived_gen.1 : Str = StructAtIndex 1 Str.80; - dec #Derived_gen.1; + let #Derived_gen.0 : Str = StructAtIndex 1 Str.80; + dec #Derived_gen.0; let Str.313 : {U64, U8} = Struct {Str.315, Str.314}; let Str.312 : [C {U64, U8}, C Str] = TagId(0) Str.313; ret Str.312; diff --git a/crates/compiler/test_mono/generated/monomorphized_tag.txt b/crates/compiler/test_mono/generated/monomorphized_tag.txt index 7aa0cffa6d..ee240168f8 100644 --- a/crates/compiler/test_mono/generated/monomorphized_tag.txt +++ b/crates/compiler/test_mono/generated/monomorphized_tag.txt @@ -2,6 +2,10 @@ procedure Test.1 (Test.4): let Test.12 : Int1 = false; ret Test.12; +procedure Test.1 (Test.4): + let Test.14 : Int1 = false; + ret Test.14; + procedure Test.2 (Test.5, Test.6): let Test.10 : U8 = 18i64; ret Test.10; From 0adf0751238cf75d62d2841927557c4cee460801 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 17 May 2023 17:33:05 +0200 Subject: [PATCH 51/54] make bool intern with semanctis --- crates/compiler/mono/src/layout.rs | 4 +--- crates/compiler/mono/src/layout/intern.rs | 2 +- crates/compiler/mono/src/layout/semantic.rs | 7 ++++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/compiler/mono/src/layout.rs b/crates/compiler/mono/src/layout.rs index 379ec31b64..1482a88277 100644 --- a/crates/compiler/mono/src/layout.rs +++ b/crates/compiler/mono/src/layout.rs @@ -4134,9 +4134,7 @@ where Unit => env .cache .put_in(Layout::new(LayoutRepr::UNIT, compute_semantic())), - BoolUnion { .. } => env - .cache - .put_in(Layout::new(LayoutRepr::BOOL, compute_semantic())), + BoolUnion { .. } => Layout::BOOL, ByteUnion(_) => env .cache .put_in(Layout::new(LayoutRepr::U8, compute_semantic())), diff --git a/crates/compiler/mono/src/layout/intern.rs b/crates/compiler/mono/src/layout/intern.rs index 6a0b4f678f..0250df1c2b 100644 --- a/crates/compiler/mono/src/layout/intern.rs +++ b/crates/compiler/mono/src/layout/intern.rs @@ -59,7 +59,7 @@ macro_rules! nosema { cache_interned_layouts! { 0, VOID, pub, Layout::VOID_NAKED 1, UNIT, pub, Layout::UNIT_NAKED - 2, BOOL, pub, nosema!(LayoutRepr::BOOL) + 2, BOOL, pub, Layout { repr: LayoutRepr::BOOL, semantic: SemanticRepr::BOOL } 3, U8, pub, nosema!(LayoutRepr::U8) 4, U16, pub, nosema!(LayoutRepr::U16) 5, U32, pub, nosema!(LayoutRepr::U32) diff --git a/crates/compiler/mono/src/layout/semantic.rs b/crates/compiler/mono/src/layout/semantic.rs index 75e9a06d67..824bf8c453 100644 --- a/crates/compiler/mono/src/layout/semantic.rs +++ b/crates/compiler/mono/src/layout/semantic.rs @@ -27,20 +27,21 @@ enum Inner<'a> { impl<'a> SemanticRepr<'a> { pub(super) const NONE: Self = Self(Inner::None); pub(super) const EMPTY_RECORD: Self = Self::record(&[]); + pub(super) const BOOL: Self = Self::tag_union(&["False", "True"]); pub(super) const fn record(fields: &'a [&'a str]) -> Self { Self(Inner::Record(SemaRecord { fields })) } - pub(super) fn tuple(size: usize) -> Self { + pub(super) const fn tuple(size: usize) -> Self { Self(Inner::Tuple(SemaTuple { size })) } - pub(super) fn tag_union(tags: &'a [&'a str]) -> Self { + pub(super) const fn tag_union(tags: &'a [&'a str]) -> Self { Self(Inner::TagUnion(SemaTagUnion { tags })) } - pub(super) fn lambdas(lambdas: &'a [Symbol]) -> Self { + pub(super) const fn lambdas(lambdas: &'a [Symbol]) -> Self { Self(Inner::Lambdas(SemaLambdas { lambdas })) } } From f6fafdb01974ae0c20a9348545bc623d72790748 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 31 May 2023 23:14:37 +0200 Subject: [PATCH 52/54] nullable unwrapped for the dev backend --- .../compiler/builtins/bitcode/src/utils.zig | 4 + .../compiler/gen_dev/src/generic64/aarch64.rs | 5 + crates/compiler/gen_dev/src/generic64/mod.rs | 173 ++++++++++++++++++ .../compiler/gen_dev/src/generic64/x86_64.rs | 5 + crates/compiler/mono/src/layout.rs | 4 +- crates/compiler/mono/src/layout/intern.rs | 2 +- crates/compiler/mono/src/layout/semantic.rs | 7 +- crates/compiler/test_gen/src/gen_tags.rs | 8 +- 8 files changed, 198 insertions(+), 10 deletions(-) diff --git a/crates/compiler/builtins/bitcode/src/utils.zig b/crates/compiler/builtins/bitcode/src/utils.zig index aa2a9605e3..58849e6391 100644 --- a/crates/compiler/builtins/bitcode/src/utils.zig +++ b/crates/compiler/builtins/bitcode/src/utils.zig @@ -305,6 +305,10 @@ pub fn isUnique( 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]; return refcount == REFCOUNT_ONE_ISIZE; diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index cf08ab036e..7877b3ee4d 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -458,6 +458,11 @@ impl CallConv for AArch64C } impl Assembler for AArch64Assembler { + #[inline(always)] + fn base_pointer() -> AArch64GeneralReg { + AArch64GeneralReg::FP + } + #[inline(always)] fn abs_reg64_reg64(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, src: AArch64GeneralReg) { cmp_reg64_imm12(buf, src, 0); diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 0dfa1d9c1b..2eccb3b04a 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -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`. /// dst should always come before sources. pub trait Assembler: Sized + Copy { + fn base_pointer() -> GeneralReg; + fn abs_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg); fn abs_freg64_freg64( buf: &mut Vec<'_, u8>, @@ -2629,6 +2631,49 @@ impl< ); } + 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 .layout_interner @@ -2780,6 +2825,72 @@ impl< 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), }; } @@ -2864,6 +2975,68 @@ impl< 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), } } diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index 17b2bc2d51..0b3ff8d075 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -1139,6 +1139,11 @@ where } impl Assembler for X86_64Assembler { + #[inline(always)] + fn base_pointer() -> X86_64GeneralReg { + X86_64GeneralReg::RBP + } + // 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. #[inline(always)] diff --git a/crates/compiler/mono/src/layout.rs b/crates/compiler/mono/src/layout.rs index 1482a88277..379ec31b64 100644 --- a/crates/compiler/mono/src/layout.rs +++ b/crates/compiler/mono/src/layout.rs @@ -4134,7 +4134,9 @@ where Unit => env .cache .put_in(Layout::new(LayoutRepr::UNIT, compute_semantic())), - BoolUnion { .. } => Layout::BOOL, + BoolUnion { .. } => env + .cache + .put_in(Layout::new(LayoutRepr::BOOL, compute_semantic())), ByteUnion(_) => env .cache .put_in(Layout::new(LayoutRepr::U8, compute_semantic())), diff --git a/crates/compiler/mono/src/layout/intern.rs b/crates/compiler/mono/src/layout/intern.rs index 0250df1c2b..6a0b4f678f 100644 --- a/crates/compiler/mono/src/layout/intern.rs +++ b/crates/compiler/mono/src/layout/intern.rs @@ -59,7 +59,7 @@ macro_rules! nosema { cache_interned_layouts! { 0, VOID, pub, Layout::VOID_NAKED 1, UNIT, pub, Layout::UNIT_NAKED - 2, BOOL, pub, Layout { repr: LayoutRepr::BOOL, semantic: SemanticRepr::BOOL } + 2, BOOL, pub, nosema!(LayoutRepr::BOOL) 3, U8, pub, nosema!(LayoutRepr::U8) 4, U16, pub, nosema!(LayoutRepr::U16) 5, U32, pub, nosema!(LayoutRepr::U32) diff --git a/crates/compiler/mono/src/layout/semantic.rs b/crates/compiler/mono/src/layout/semantic.rs index 824bf8c453..75e9a06d67 100644 --- a/crates/compiler/mono/src/layout/semantic.rs +++ b/crates/compiler/mono/src/layout/semantic.rs @@ -27,21 +27,20 @@ enum Inner<'a> { impl<'a> SemanticRepr<'a> { pub(super) const NONE: Self = Self(Inner::None); pub(super) const EMPTY_RECORD: Self = Self::record(&[]); - pub(super) const BOOL: Self = Self::tag_union(&["False", "True"]); pub(super) const fn record(fields: &'a [&'a str]) -> Self { Self(Inner::Record(SemaRecord { fields })) } - pub(super) const fn tuple(size: usize) -> Self { + pub(super) fn tuple(size: usize) -> Self { Self(Inner::Tuple(SemaTuple { size })) } - pub(super) const fn tag_union(tags: &'a [&'a str]) -> Self { + pub(super) fn tag_union(tags: &'a [&'a str]) -> Self { Self(Inner::TagUnion(SemaTagUnion { tags })) } - pub(super) const fn lambdas(lambdas: &'a [Symbol]) -> Self { + pub(super) fn lambdas(lambdas: &'a [Symbol]) -> Self { Self(Inner::Lambdas(SemaLambdas { lambdas })) } } diff --git a/crates/compiler/test_gen/src/gen_tags.rs b/crates/compiler/test_gen/src/gen_tags.rs index 5eac23d524..bdfef4a93b 100644 --- a/crates/compiler/test_gen/src/gen_tags.rs +++ b/crates/compiler/test_gen/src/gen_tags.rs @@ -2063,7 +2063,7 @@ fn non_unary_union_with_lambda_set_with_imported_toplevels_issue_4733() { } #[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() { assert_evals_to!( indoc!( @@ -2094,7 +2094,7 @@ fn nullable_wrapped_with_non_nullable_singleton_tags() { } #[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() { assert_evals_to!( indoc!( @@ -2102,9 +2102,9 @@ fn nullable_wrapped_with_nullable_not_last_index() { app "test" provides [main] to "./platform" Parser : [ - OneOrMore Parser, - Keyword Str, CharLiteral, + Keyword Str, + OneOrMore Parser, ] toIdParser : Parser -> Str From 7683c5ae5351f3c0819e9d0587f48304c4984ad9 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 31 May 2023 19:29:24 -0700 Subject: [PATCH 53/54] Stop using llvm instrinsics that just call libc In a future PR. I will change zig to insure that all of these instrinsics use musl directly and do not call out to the linked libc. --- crates/compiler/builtins/bitcode/build.zig | 2 +- crates/compiler/builtins/bitcode/src/main.zig | 10 +- crates/compiler/builtins/bitcode/src/num.zig | 45 ++++++-- crates/compiler/builtins/src/bitcode.rs | 6 + .../compiler/gen_llvm/src/llvm/intrinsics.rs | 26 +---- crates/compiler/gen_llvm/src/llvm/lowlevel.rs | 108 ++++++++---------- 6 files changed, 102 insertions(+), 95 deletions(-) diff --git a/crates/compiler/builtins/bitcode/build.zig b/crates/compiler/builtins/bitcode/build.zig index c2e13a363a..a27d97bbb8 100644 --- a/crates/compiler/builtins/bitcode/build.zig +++ b/crates/compiler/builtins/bitcode/build.zig @@ -17,7 +17,7 @@ pub fn build(b: *Builder) void { // Tests var main_tests = b.addTest(main_path); main_tests.setBuildMode(mode); - main_tests.linkSystemLibrary("c"); + main_tests.linkLibC(); const test_step = b.step("test", "Run tests"); test_step.dependOn(&main_tests.step); diff --git a/crates/compiler/builtins/bitcode/src/main.zig b/crates/compiler/builtins/bitcode/src/main.zig index 04ce784d6a..06bc953fcd 100644 --- a/crates/compiler/builtins/bitcode/src/main.zig +++ b/crates/compiler/builtins/bitcode/src/main.zig @@ -85,8 +85,12 @@ comptime { num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int."); num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil."); - num.exportRoundF32(T, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f32."); - num.exportRoundF64(T, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f64."); + num.exportRound(f32, T, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f32."); + 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.exportAddOrPanic(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_or_panic."); @@ -126,6 +130,8 @@ comptime { num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow."); 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.exportSubWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".sub_with_overflow."); diff --git a/crates/compiler/builtins/bitcode/src/num.zig b/crates/compiler/builtins/bitcode/src/num.zig index 59fa2a48a7..e289e4230e 100644 --- a/crates/compiler/builtins/bitcode/src/num.zig +++ b/crates/compiler/builtins/bitcode/src/num.zig @@ -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 { comptime var f = struct { fn func(input: T) callconv(.C) T { - return @sin(input); + return math.sin(input); } }.func; @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 { comptime var f = struct { fn func(input: T) callconv(.C) T { - return @cos(input); + return math.cos(input); } }.func; @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 { comptime var f = struct { fn func(input: T) callconv(.C) T { - return @log(input); + return math.ln(input); } }.func; @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 { - fn func(input: f32) callconv(.C) T { - return @floatToInt(T, (@round(input))); + fn func(input: T) callconv(.C) T { + return math.absFloat(input); } }.func; @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 { - fn func(input: f64) callconv(.C) T { - return @floatToInt(T, (@round(input))); + fn func(input: T) callconv(.C) T { + 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; @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); diff --git a/crates/compiler/builtins/src/bitcode.rs b/crates/compiler/builtins/src/bitcode.rs index 2b640ceb2f..70349688a7 100644 --- a/crates/compiler/builtins/src/bitcode.rs +++ b/crates/compiler/builtins/src/bitcode.rs @@ -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_LOG: IntrinsicName = float_intrinsic!("roc_builtins.num.log"); 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_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_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f64"); diff --git a/crates/compiler/gen_llvm/src/llvm/intrinsics.rs b/crates/compiler/gen_llvm/src/llvm/intrinsics.rs index 6c68f29889..a039d4d97e 100644 --- a/crates/compiler/gen_llvm/src/llvm/intrinsics.rs +++ b/crates/compiler/gen_llvm/src/llvm/intrinsics.rs @@ -7,11 +7,12 @@ use inkwell::{ }; use roc_builtins::{ bitcode::{FloatWidth, IntWidth, IntrinsicName}, - float_intrinsic, llvm_int_intrinsic, + llvm_int_intrinsic, }; use super::build::{add_func, FunctionSpec}; +#[allow(dead_code)] fn add_float_intrinsic<'ctx, F>( ctx: &'ctx Context, 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), ); - 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| { let fields = [t.into(), i1_type.into()]; 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_I32: &str = "llvm.memset.p0i8.i32"; diff --git a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs index e914f1f480..5a141d9d96 100644 --- a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs +++ b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs @@ -41,9 +41,13 @@ use crate::llvm::{ self, basic_type_from_layout, zig_num_parse_result_type, zig_to_int_checked_result_type, }, intrinsics::{ - LLVM_ADD_SATURATED, LLVM_ADD_WITH_OVERFLOW, LLVM_CEILING, LLVM_COS, LLVM_FABS, LLVM_FLOOR, - LLVM_LOG, LLVM_MUL_WITH_OVERFLOW, LLVM_POW, LLVM_ROUND, LLVM_SIN, LLVM_SQRT, - LLVM_SUB_SATURATED, LLVM_SUB_WITH_OVERFLOW, + // These instrinsics do not generate calls to libc and are safe to keep. + // If we find that any of them generate calls to libc on some platforms, we need to define them as zig bitcode. + LLVM_ADD_SATURATED, + LLVM_ADD_WITH_OVERFLOW, + LLVM_MUL_WITH_OVERFLOW, + LLVM_SUB_SATURATED, + LLVM_SUB_WITH_OVERFLOW, }, refcounting::PointerToRefcount, }; @@ -1704,7 +1708,11 @@ fn build_float_binop<'ctx>( NumLt => bd.build_float_compare(OLT, lhs, rhs, "float_lt").into(), NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").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); } @@ -2316,9 +2324,9 @@ fn build_float_unary_op<'a, 'ctx>( // TODO: Handle different sized floats match op { NumNeg => bd.build_float_neg(arg, "negate_float").into(), - NumAbs => env.call_intrinsic(&LLVM_FABS[float_width], &[arg.into()]), - NumSqrtUnchecked => env.call_intrinsic(&LLVM_SQRT[float_width], &[arg.into()]), - NumLogUnchecked => env.call_intrinsic(&LLVM_LOG[float_width], &[arg.into()]), + NumAbs => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_FABS[float_width]), + NumSqrtUnchecked => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_SQRT[float_width]), + NumLogUnchecked => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_LOG[float_width]), NumToFrac => { let return_width = match layout_interner.get(layout).repr { LayoutRepr::Builtin(Builtin::Float(return_width)) => return_width, @@ -2342,64 +2350,46 @@ fn build_float_unary_op<'a, 'ctx>( } } NumCeiling => { - let (return_signed, return_type) = match layout_interner.get(layout).repr { - LayoutRepr::Builtin(Builtin::Int(int_width)) => ( - int_width.is_signed(), - convert::int_type_from_int_width(env, int_width), - ), + let int_width = match layout_interner.get(layout).repr { + LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width, _ => internal_error!("Ceiling return layout is not int: {:?}", layout), }; - let opcode = if return_signed { - InstructionOpcode::FPToSI - } else { - InstructionOpcode::FPToUI - }; - env.builder.build_cast( - opcode, - env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]), - return_type, - "num_ceiling", - ) + match float_width { + FloatWidth::F32 => { + call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_CEILING_F32[int_width]) + } + FloatWidth::F64 => { + call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_CEILING_F64[int_width]) + } + } } NumFloor => { - let (return_signed, return_type) = match layout_interner.get(layout).repr { - LayoutRepr::Builtin(Builtin::Int(int_width)) => ( - int_width.is_signed(), - convert::int_type_from_int_width(env, int_width), - ), - _ => internal_error!("Ceiling return layout is not int: {:?}", layout), + let int_width = match layout_interner.get(layout).repr { + LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width, + _ => internal_error!("Floor return layout is not int: {:?}", layout), }; - let opcode = if return_signed { - InstructionOpcode::FPToSI - } else { - InstructionOpcode::FPToUI - }; - env.builder.build_cast( - opcode, - env.call_intrinsic(&LLVM_FLOOR[float_width], &[arg.into()]), - return_type, - "num_floor", - ) + match float_width { + FloatWidth::F32 => { + call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_FLOOR_F32[int_width]) + } + FloatWidth::F64 => { + call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_FLOOR_F64[int_width]) + } + } } NumRound => { - let (return_signed, return_type) = match layout_interner.get(layout).repr { - LayoutRepr::Builtin(Builtin::Int(int_width)) => ( - int_width.is_signed(), - convert::int_type_from_int_width(env, int_width), - ), - _ => internal_error!("Ceiling return layout is not int: {:?}", layout), + let int_width = match layout_interner.get(layout).repr { + LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width, + _ => internal_error!("Round return layout is not int: {:?}", layout), }; - let opcode = if return_signed { - InstructionOpcode::FPToSI - } else { - InstructionOpcode::FPToUI - }; - env.builder.build_cast( - opcode, - env.call_intrinsic(&LLVM_ROUND[float_width], &[arg.into()]), - return_type, - "num_round", - ) + match float_width { + FloatWidth::F32 => { + call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND_F32[int_width]) + } + FloatWidth::F64 => { + call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND_F64[int_width]) + } + } } NumIsNan => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_NAN[float_width]), 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]), // trigonometry - NumSin => env.call_intrinsic(&LLVM_SIN[float_width], &[arg.into()]), - NumCos => env.call_intrinsic(&LLVM_COS[float_width], &[arg.into()]), + NumSin => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_SIN[float_width]), + NumCos => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_COS[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]), From ccb434bae1509580e232e24f531864e714c1546c Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 31 May 2023 22:15:05 -0700 Subject: [PATCH 54/54] Update cli_run cli_args test Note: this answer is less correct, but that is because zigs sub answers are more correct. Zig specifically gives a more accurate answer for Num.log 3. old answer: 1.0986122886681098 zig's answer: 1.0986122886681096 correct answer: 1.098612288668109691395 Both answers are technically correct when limited to doubles. Zig's answer is closer to truly correct. That said, the more correct answer leads to a slight rounding error in the final division. So the new output is 4.000000000000001 instead of 4.0 --- crates/cli/tests/cli_run.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/tests/cli_run.rs b/crates/cli/tests/cli_run.rs index 21a17eda78..826c8c146b 100644 --- a/crates/cli/tests/cli_run.rs +++ b/crates/cli/tests/cli_run.rs @@ -725,7 +725,7 @@ mod cli_run { Arg::PlainText("81"), ], &[], - "4\n", + "4.000000000000001\n", UseValgrind::No, TestCliCommands::Run, )