diff --git a/AUTHORS b/AUTHORS index 1825ce83ee..7053d22e0e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -56,3 +56,7 @@ Callum Dunster Martin Stewart James Hegedus Cristiano Piemontese +Yann Simon +Shahn Hogan +Tankor Smash +Matthias Devlamynck diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index 9006649559..86373b26c0 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -49,7 +49,7 @@ If you want to install it manually, you can also download Zig directly [here](ht **version: 12.0.x** For macOS, you can install LLVM 12 using `brew install llvm@12` and then adding -`/usr/local/opt/llvm@12/bin` to your `PATH`. You can confirm this worked by +`$(brew --prefix llvm@12)/bin` to your `PATH`. You can confirm this worked by running `llc --version` - it should mention "LLVM version 12.0.0" at the top. You may also need to manually specify a prefix env var like so: ``` diff --git a/Cargo.lock b/Cargo.lock index dabed4d0b4..30018b2ec8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,15 +125,6 @@ dependencies = [ name = "arena-pool" version = "0.1.0" -[[package]] -name = "arraystring" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d517c467117e1d8ca795bc8cc90857ff7f79790cca0e26f6e9462694ece0185" -dependencies = [ - "typenum", -] - [[package]] name = "arrayvec" version = "0.5.2" @@ -3144,7 +3135,7 @@ dependencies = [ name = "roc_ast" version = "0.1.0" dependencies = [ - "arraystring", + "arrayvec 0.7.2", "bumpalo", "indoc", "libc", @@ -3327,7 +3318,7 @@ dependencies = [ name = "roc_editor" version = "0.1.0" dependencies = [ - "arraystring", + "arrayvec 0.7.2", "bumpalo", "bytemuck", "cgmath", @@ -3383,6 +3374,7 @@ dependencies = [ "roc_module", "roc_parse", "roc_region", + "roc_test_utils", ] [[package]] @@ -3400,6 +3392,7 @@ dependencies = [ "roc_parse", "roc_problem", "roc_region", + "roc_reporting", "roc_solve", "roc_std", "roc_types", @@ -3524,9 +3517,7 @@ dependencies = [ name = "roc_parse" version = "0.1.0" dependencies = [ - "ansi_term", "bumpalo", - "diff", "encode_unicode", "indoc", "pretty_assertions", @@ -3535,6 +3526,7 @@ dependencies = [ "roc_collections", "roc_module", "roc_region", + "roc_test_utils", ] [[package]] @@ -3599,6 +3591,13 @@ dependencies = [ name = "roc_std" version = "0.1.0" +[[package]] +name = "roc_test_utils" +version = "0.1.0" +dependencies = [ + "pretty_assertions", +] + [[package]] name = "roc_types" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 4839856ae9..234708adcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ members = [ "code_markup", "reporting", "roc_std", + "test_utils", "utils", "docs", "linker", diff --git a/Earthfile b/Earthfile index 4ca4ee56f3..29fad0efa0 100644 --- a/Earthfile +++ b/Earthfile @@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt: copy-dirs: FROM +install-zig-llvm-valgrind-clippy-rustfmt - COPY --dir cli cli_utils compiler docs editor ast code_markup utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ + COPY --dir cli cli_utils compiler docs editor ast code_markup utils test_utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ test-zig: FROM +install-zig-llvm-valgrind-clippy-rustfmt @@ -117,7 +117,7 @@ build-nightly-release: RUN git log --pretty=format:'%h' -n 1 >> version.txt RUN printf " on: " >> version.txt RUN date >> version.txt - RUN cargo build --features with_sound --release + RUN RUSTFLAGS="-C target-cpu=x86-64-v2" cargo build --features with_sound --release RUN cd ./target/release && tar -czvf roc_linux_x86_64.tar.gz ./roc ../../LICENSE ../../LEGAL_DETAILS ../../examples/hello-world ../../examples/hello-rust ../../examples/hello-zig ../../compiler/builtins/bitcode/src/ ../../roc_std SAVE ARTIFACT ./target/release/roc_linux_x86_64.tar.gz AS LOCAL roc_linux_x86_64.tar.gz diff --git a/README.md b/README.md index 431a78f23d..a38cd4d02b 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ Roc is a language for making delightful software. -If you already know [Elm](https://elm-lang.org/), then [Roc for Elm Programmers](https://github.com/rtfeldman/roc/blob/trunk/roc-for-elm-programmers.md) may be of interest. +The [tutorial](TUTORIAL.md) is the best place to learn about how to use the language - it assumes no prior knowledge of Roc or similar languages. (If you already know [Elm](https://elm-lang.org/), then [Roc for Elm Programmers](https://github.com/rtfeldman/roc/blob/trunk/roc-for-elm-programmers.md) may be of interest.) -You can get help and discuss with other people on the [Roc Zulip chat](https://roc.zulipchat.com). +There's also a folder of [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) - the [CLI example](https://github.com/rtfeldman/roc/tree/trunk/examples/cli) in particular is a reasonable starting point to build on. + +[Roc Zulip chat](https://roc.zulipchat.com) is the best place to ask questions and get help! It's also where we discuss [ideas](https://roc.zulipchat.com/#narrow/stream/304641-ideas) for the language. If you want to get involved in contributing to the language, Zulip is also a great place to ask about good first projects. ## State of Roc diff --git a/TUTORIAL.md b/TUTORIAL.md new file mode 100644 index 0000000000..a545823ee5 --- /dev/null +++ b/TUTORIAL.md @@ -0,0 +1,1358 @@ +# Tutorial + +This is a tutorial for how to build Roc applications. It covers the REPL, basic +types (strings, lists, tags, and functions), syntax (`when`, `if then else`) +and more! + +Enjoy! + +## Strings and Numbers + +Let’s start by getting acquainted with Roc’s Read Eval Print Loop, or REPL for +short. Run this in a terminal: + +``` +$ roc repl +``` + +You should see this: + +``` +The rockin’ roc repl +``` + +Try typing this in and pressing enter: + +```coffee +>> "Hello, World!" +"Hello, World!" : Str +``` + +Congratulations! You've just written your first Roc code! + +Specifically, you entered the *expression* `"Hello, World!"` into the REPL, +and the REPL printed it back out. It also printed `: Str`, which is the +expression's type. We'll talk about types later; for now, we'll ignore the `:` +and whatever comes after it whenever the REPL prints them. + +Let's try putting in a more complicated expression: + +```coffee +>> 1 + 1 +2 : Num * +``` + +According to the Roc REPL, one plus one equals two. Checks out! + +Roc will respect [order of operations](https://en.wikipedia.org/wiki/Order_of_operations) when using multiple arithmetic operators +like `+` and `-`, but you can use parentheses to specify exactly how they should +be grouped. + +```coffee +>> 1 + 2 * (3 - 4) +-1 : Num * +``` + +Let's try calling a function: + +```coffee +>> Str.concat "Hi " "there!" +"Hi there!" : Str +``` + +In this expression, we're calling the `Str.concat` function +passing two arguments: the string `"Hi "` and the string `"there!"`. The +`Str.concat` function *concatenates* two strings together (that is, it puts +one after the other) and returns the resulting combined string of +`"Hi there!"`. + +Note that in Roc, we don't need parentheses or commas to call functions. +We don't write `Str.concat("Hi ", "there!")` but rather `Str.concat "Hi " "there!"`. + +Just like in the arithmetic example above, we can use parentheses to specify +how nested function calls should work. For example, we could write this: + +```coffee +>> Str.concat "Birds: " (Num.toStr 42) +"Birds: 42" : Str +``` + +This calls `Num.toStr` on the number `42`, which converts it into the string +`"42"`, and then passes that string as the second argument to `Str.concat`. +The parentheses are important here to specify how the function calls nest! +Try removing them, and see what happens: + +```coffee +>> Str.concat "Birds: " Num.toStr 42 + +``` + +This error message says that we've given `Str.concat` too many arguments. +Indeed we have! We've passed it three arguments: the string `"Birds"`, the +function `Num.toStr`, and the number `42`. That's not what we wanted to do. +Putting parentheses around the `Num.toStr 42` call clarifies that we want it +to be evaluated as its own expression, rather than being two arguments to +`Str.concat`. + +Both the `Str.concat` function and the `Num.toStr` function have a `.` in +their names. In `Str.concat`, `Str` is the name of a *module*, and `concat` +is the name of a function inside that module. Similarly, `Num` is a different +module, and `toStr` is a function inside that module. + +We'll get into more depth about modules later, but for now you can think of +a module as a named collection of functions. It'll be awhile before we want +to use them for more than that anyway! + +## Building an Application + +Let's move out of the REPL and create our first Roc application. + +Create a new file called `Hello.roc` and put this inside it: + +```coffee +app "hello" + packages { pf: "examples/cli/platform" } + imports [ pf.Stdout ] + provides [ main ] to pf + +main = Stdout.line "I'm a Roc application!" +``` + +> **NOTE:** This assumes you've put Hello.roc in the root directory of the +> Roc source code. If you'd like to put it somewhere else, you'll need to replace +> `"examples/cli/"` with the path to the `examples/cli/` folder in +> that source code. In the future, Roc will have the tutorial built in, and this +> aside will no longer be necessary! + +Try running this with: + +``` +$ roc Hello.roc +``` + +You should see this: + +``` +I'm a Roc application! +``` + +Congratulations - you've now written your first Roc application! We'll go over what the parts of +this file above `main` do later, but first let's play around a bit. +Try replacing the `main` line with this: + +```coffee +main = Stdout.line "There are \(total) animals." + +birds = 3 + +iguanas = 2 + +total = Num.toStr (birds + iguanas) +``` + +Now if you run `roc Hello.roc`, you should see this: + +``` +There are 5 animals. +``` + +`Hello.roc` now has four definitions - or *defs* for +short - namely, `main`, `birds`, `iguanas`, and `total`. + +A definition names an expression. +- The first def assigns the name `main` to the expression `Stdout.line "I have \(numDefs) definitions."`. The `Stdout.line` function takes a string and prints it as a line to [`stdout`] (the terminal's standard output device). +- The next two defs assign the names `birds` and `iguanas` to the expressions `3` and `2`. +- The last def assigns the name `total` to the expression `Num.toStr (birds + iguanas)`. + +Once we have a def, we can use its name in other expressions. +For example, the `total` expression refers to `birds` and `iguanas`. + +We can also refer to defs inside strings using *string interpolation*. The +string `"There are \(total) animals."` evaluates to the same thing as calling +`Str.concat "There are " (Str.concat total " animals.")` directly. + +You can name a def using any combination of letters and numbers, but they have +to start with a letter. Note that definitions are constant; once we've assigned +a name to an expression, we can't reassign it! We'd get an error if we wrote this: + +```coffee +birds = 3 + +birds = 2 +``` + +Order of defs doesn't matter. We defined `birds` and `iguanas` before +`total` (which uses both of them), but we defined `main` before `total` even though +it uses `total`. If you like, you can change the order of these defs to anything +you like, and everything will still work the same way! + +This works because Roc expressions don't have *side effects*. We'll talk more +about side effects later. + +## Functions and `if` + +So far we've called functions like `Num.toStr`, `Str.concat`, and `Stdout.line`. +Next let's try defining a function of our own. + +```coffee +main = Stdout.line "There are \(total) animals." + +birds = 3 + +iguanas = 2 + +total = addAndStringify birds iguanas + +addAndStringify = \num1, num2 -> + Num.toStr (num1 + num2) +``` + +This new `addAndStringify` function we've defined takes two numbers, adds them, +calls `Num.toStr` on the result, and returns that. The `\num1, num2 ->` syntax +defines a function's arguments, and the expression after the `->` is the body +of the function. The expression at the end of the body (`Num.toStr (num1 + num2)` +in this case) is returned automatically. + +Let's modify the function to return an empty string if the numbers add to zero. + +```coffee +addAndStringify = \num1, num2 -> + sum = num1 + num2 + + if sum == 0 then + "" + else + Num.toStr sum +``` + +We did two things here: +* We introduced a local def named `sum`, and set it equal to `num1 + num2`. Because we defined `sum` inside `addAndStringify`, it will not be accessible outside that function. +* We added an `if` / `then` / `else` conditional to return either `""` or `Num.toStr sum` depending on whether `sum == 0`. + +Of note, we couldn't have done `total = num1 + num2` because that would be +redefining `total` in the global scope, and defs can't be redefined. (However, we could use the name +`sum` for a def in a different function, because then they'd be in completely +different scopes and wouldn't affect each other.) + +Also note that every `if` must be accompanied by both `then` and also `else`. +Having an `if` without an `else` is an error, because in Roc, everything is +an expression - which means it must evaluate to a value. If there were ever an +`if` without an `else`, that would be an expression that might not evaluate to +a value! + +We can combine `if` and `else` to get `else if`, like so: + +```coffee +addAndStringify = \num1, num2 -> + sum = num1 + num2 + + if sum == 0 then + "" + else if sum < 0 then + "negative" + else + Num.toStr sum +``` + +Note that `else if` is not a separate language keyword! It's just an `if`/`else` where +the `else` branch contains another `if`/`else`. This is easier to see with different indentation: + +```coffee +addAndStringify = \num1, num2 -> + sum = num1 + num2 + + if sum == 0 then + "" + else + if sum < 0 then + "negative" + else + Num.toStr sum +``` + +This code is equivalent to writing `else if sum < 0 then` on one line, although the stylistic +convention is to write `else if` on the same line. + +## Records + +Currently our `addAndStringify` funcion takes two arguments. We can instead make +it take one argument like so: + +```coffee +total = addAndStringify { birds: 5, iguanas: 7 } + +addAndStringify = \counts -> + Num.toStr (counts.birds + counts.iguanas) +``` + +The function now takes a *record*, which is a group of values that travel together. +Records are not objects; they don't have methods or inheritance, they just store values. + +We create the record when we write `{ birds: 5, iguanas: 7 }`. This defines +a record with two *fields* - namely, the `birds` field and the `iguanas` field - +and then assigns the number `5` to the `birds` field and the number `7` to the +`iguanas` field. + +When we write `counts.birds`, it accesses the `birds` field of the `counts` record, +and when we write `counts.iguanas` it accesses the `iguanas` field. When we use `==` +on records, it compares all the fields in both records with `==`, and only returns true +if all fields on both records return true for their `==` comparisons. If one record has +more fields than the other, or if the types associated with a given field are different +between one field and the other, the Roc compiler will give an error at build time. + +> **Note:** Some other languages have a concept of "identity equality" that's separate from +> the "structural equality" we just described. Roc does not have a concept of identity equality; +> this is the only way equality works! + +The `addAndStringify` function will accept any record with at least the fields `birds` and +`iguanas`, but it will also accept records with more fields. For example: + +```coffee +total = addAndStringify { birds: 5, iguanas: 7 } + +totalWithNote = addAndStringify { birds: 4, iguanas: 3, note: "Whee!" } + +addAndStringify = \counts -> + Num.toStr (counts.birds + counts.iguanas) +``` + +This works because `addWithStringify` only uses `counts.birds` and `counts.iguanas`. +If we were to use `counts.note` inside `addWithStringify`, then we would get an error +because `total` is calling `addAndStringify` passing a record that doesn't have a `note` field. + +Record fields can have any combination of types we want. `totalWithNote` uses a record that +has a mixture of numbers and strings, but we can also have record fields that other types of +values - including other records, or even functions! + +```coffee +{ birds: 4, nestedRecord: { someFunction: (\arg -> arg + 1), name: "Sam" } } +``` + +Whenever we're setting a field to be a def that has the same name as the field - +for example, `{ x: x }` - we can shorten it to just writing the name of the def alone - +for example, `{ x }`. We can do this with as many fields as we like, e.g. +`{ x: x, y: y }` can alternately be written `{ x, y }`, `{ x: x, y }`, or `{ x, y: y }`. + +### Record destructuring + +We can use *destructuring* to avoid naming a record in a function argument, instead +giving names to its individual fields: + +```coffee +addAndStringify = \{ birds, iguanas } -> + Num.toStr (birds + iguanas) +``` + +Here, we've *destructured* the record to create a `birds` def that's assigned to its `birds` +field, and an `iguanas` def that's assigned to its `iguanas` field. We can customize this if we +like: + +```coffee +addAndStringify = \{ birds, iguanas: lizards } -> + Num.toStr (birds + lizards) +``` + +In this version, we created a `lizards` def that's assigned to the record's `iguanas` field. +(We could also do something similar with the `birds` field if we like.) + +It's possible to destructure a record while still naming it. Here's an example where we +use the `as` keyword to name the record `counts` while also destructuring its fields: + +```coffee +addAndStringify = \{ iguanas: lizards } as counts -> + Num.toStr (counts.birds + lizards) +``` + +Notice that here we didn't bother destructuring the `birds` field. You can always omit fields +from a destructure if you aren't going to use them! + +Finally, destructuring can be used in defs too: + +```coffee +{ x, y } = { x: 5, y: 10 } +``` + +### Building records from other records + +So far we've only constructed records from scratch, by specifying all of their fields. We can +also construct new records by using another record to use as a starting point, and then +specifying only the fields we want to be different. For example, here are two ways to +get the same record: + +```coffee +original = { birds: 5, iguanas: 7, zebras: 2, goats: 1 } + +fromScratch = { birds: 4, iguanas: 3, zebras: 2, goats: 1 } +fromOriginal = { original & birds: 4, iguanas: 3 } +``` + +The `fromScratch` and `fromOriginal` records are equal, although they're assembled in +different ways. + +* `fromScratch` was built using the same record syntax we've been using up to this point. +* `fromOriginal` created a new record using the contents of `original` as defaults for fields that it didn't specify after the `&`. + +Note that when we do this, the fields you're overriding must all be present on the original record, +and their values must have the same type as the corresponding values in the original record. + +## Tags + +Sometimes we want to represent that something can have one of several values. For example: + +```coffee +stoplightColor = + if something > 0 then + Red + else if something == 0 then + Yellow + else + Green +``` + +Here, `stoplightColor` can have one of three values: `Red`, `Yellow`, or `Green`. +The capitalization is very important! If these were lowercase (`red`, `yellow`, `green`), +then they would refer to defs. However, because they are capitalized, they instead +refer to *tags*. + +A tag is a literal value just like a number or a string. Similarly to how I can write +the number `42` or the string `"forty-two"` without defining them first, I can also write +the tag `FortyTwo` without defining it first. Also, similarly to how `42 == 42` and +`"forty-two" == "forty-two"`, it's also the case that `FortyTwo == FortyTwo`. + +Speaking of equals, if we put `42 == 42` into `roc repl`, the output we'll see is `True`. +This is because booleans in Roc are tags; a boolean is either the tag `True` or the tag +`False`. So I can write `if True then` or `if False then` and it will work as expected, +even though I'd get an error if I wrote `if "true" then` or `if 1 then`. (Roc doesn't +have a concept of "truthiness" - you always have to use booleans for conditionals!) + +Let's say we wanted to turn `stoplightColor` from a `Red`, `Green`, or `Yellow` into +a string. Here's one way we could do that: + +```elm +stoplightStr = + if stoplightColor == Red then + "red" + else if stoplightColor == Green then + "green" + else + "yellow" +``` + +We can express this logic more concisely using `when`/`is` instead of `if`/`then`: + +```elm +stoplightStr = + when stoplightColor is + Red -> "red" + Green -> "green" + Yellow -> "yellow" +``` + +This results in the same value for `stoplightStr`. In both the `when` version and the `if` version, we +have three conditional branches, and each of them evaluates to a string. The difference is how the +conditions are specified; here, we specify between `when` and `is` that we're making comparisons against +`stoplightColor`, and then we specify the different things we're comparing it to: `Red`, `Green`, and `Yellow`. + +Besides being more concise, there are other advantages to using `when` here. + +1. We don't have to specify an `else` branch, so the code can be more self-documenting about exactly what all the options are. +2. We get more compiler help. If we try deleting any of these branches, we'll get a compile-time error saying that we forgot to cover a case that could come up. For example, if we delete the `Green ->` branch, the compiler will say that we didn't handle the possibility that `stoplightColor` could be `Green`. It knows this because `Green` is one of the possibilities in our `stoplightColor = if …` definition. + +We can still have the equivalent of an `else` branch in our `when` if we like. Instead of writing "else", we write +"_ ->" like so: + +```coffee +stoplightStr = + when stoplightColor is + Red -> "red" + _ -> "not red" +``` + +This lets us more concisely handle multiple cases. However, it has the downside that if we add a new case - +for example, if we introduce the possibility of `stoplightColor` being `Orange`, the compiler can no longer +tell us we forgot to handle that possibility in our `when`. After all, we are handling it - just maybe not +in the way we'd decide to if the compiler had drawn our attention to it! + +We can make this `when` *exhaustive* (that is, covering all possibilities) without using `_ ->` by using +`|` to specify multiple matching conditions for the same branch: + +```coffee +stoplightStr = + when stoplightColor is + Red -> "red" + Green | Yellow -> "not red" +``` + +You can read `Green | Yellow` as "either `Green` or `Yellow`". By writing it this way, if we introduce the +possibility that `stoplightColor` can be `Orange`, we'll get a compiler error telling us we forgot to cover +that case in this `when`, and then we can handle it however we think is best. + +We can also combine `if` and `when` to make branches more specific: + +```coffee +stoplightStr = + when stoplightColor is + Red -> "red" + Green | Yellow if contrast > 75 -> "not red, but very high contrast" + Green | Yellow if contrast > 50 -> "not red, but high contrast" + Green | Yellow -> "not red" +``` + +This will give the same answer for `spotlightStr` as if we had written the following: + +```coffee +stoplightStr = + when stoplightColor is + Red -> "red" + Green | Yellow -> + if contrast > 75 then + "not red, but very high contrast" + else if saturation > 50 then + "not red, but high contrast" + else + "not red" +``` + +Either style can be a reasonable choice depending on the cirumstances. + +### Tags with payloads + +Tags can have *payloads* - that is, values contained within them. For example: + +```coffee +stoplightColor = + if something > 100 then + Red + else if something > 0 then + Yellow + else if something == 0 then + Green + else + Custom "some other color" + +stoplightStr = + when stoplightColor is + Red -> "red" + Green | Yellow -> "not red" + Custom description -> description +``` + +This makes two changes to our earlier `stoplightColor` / `stoplightStr` example. + +1. We sometimes set `stoplightColor` to be `Custom "some other color"`. When we did this, we gave the `Custom` tag a *payload* of the string `"some other color"`. +2. We added a `Custom` tag in our `when`, with a payload which we named `description`. Because we did this, we were able to refer to `description` in the body of the branch (that is, the part after the `->`) just like any other def. + +Any tag can be given a payload like this. A payload doesn't have to be a string; we could also have said (for example) `Custom { r: 40, g: 60, b: 80 }` to specify an RGB color instead of a string. Then in our `when` we could have written `Custom record ->` and then after the `->` used `record.r`, `record.g`, and `record.b` to access the `40`, `60`, `80` values. We could also have written `Custom { r, g, b } ->` to *destructure* the record, and then +accessed these `r`, `g`, and `b` defs after the `->` instead. + +## Lists + +Another thing we can do in Roc is to make a *list* of values. Here's an example: + +```coffee +names = [ "Sam", "Lee", "Ari" ] +``` + +This is a list with three elements in it, all strings. We can add a fourth +element using `List.append` like so: + +```coffee +List.append names "Jess" +``` + +This returns a **new** list with `"Jess"` after `"Ari"`, and doesn't modify the original list at all. +All values in Roc (including lists, but also records, strings, numbers, and so on) are immutable, +meaning whenever we want to "change" them, we want to instead pass them a function which returns some +variation of what was passed in. + +### List.map + +A common way to transform one list into another is to use `List.map`. Here's an example of how to +use it: + +```coffee +List.map [ 1, 2, 3 ] \num -> num * 2 +``` + +This returns `[ 2, 4, 6 ]`. `List.map` takes two arguments: + +1. An input list +2. A function that will be called on each element of that list + +It then returns a list which it creates by calling the given function on each element in the input list. +In this example, `List.map` calls the function `\num -> num * 2` on each element in +`[ 1, 2, 3 ]` to get a new list of `[ 2, 4, 6 ]`. + +We can also give `List.map` a named function, instead of an anonymous one: + +For example, the `Num.isOdd` function returns `True` if it's given an odd number, and `False` otherwise. +So `Num.isOdd 5` returns `True` and `Num.isOdd 2` returns `False`. + +So calling `List.map [ 1, 2, 3 ] Num.isOdd` returns a new list of `[ True, False, True ]`. + +### List element type compatibility + +If we tried to give `List.map` a function that didn't work on the elements in the list, then we'd get +an error at compile time. Here's a valid, and then an invalid example: + +```coffee +# working example +List.map [ -1, 2, 3, -4 ] Num.isNegative +# returns [ True, False, False, True ] +``` + +```coffee +# invalid example +List.map [ "A", "B", "C" ] Num.isNegative +# error: isNegative doesn't work on strings! +``` + +Because `Num.isNegative` works on numbers and not strings, calling `List.map` with `Num.isNegative` and a +list of numbers works, but doing the same with a list of strings doesn't work. + +This wouldn't work either: + +```coffee +List.map [ "A", "B", "C", 1, 2, 3 ] Num.isNegative +``` + +In fact, this wouldn't work for a more fundamental reason: every element in a Roc list has to share the same type. +For example, we can have a list of strings like `[ "Sam", "Lee", "Ari" ]`, or a list of numbers like +`[ 1, 2, 3, 4, 5 ]` but we can't have a list which mixes strings and numbers like `[ "Sam", 1, "Lee", 2, 3 ]` - +that would be a compile-time error. + +Ensuring all elements in a list share a type eliminates entire categories of problems. +For example, it means that whenever you use `List.append` to +add elements to a list, as long as you don't have any compile-time errors, you won't get any runtime errors +from calling `List.map` afterwards - no matter what you appended to the list! More generally, it's safe to assume +that unless you run out of memory, `List.map` will run successfully unless you got a compile-time error about an +incompatibility (like `Num.negate` on a list of strings). + +### Lists that hold elements of different types + +We can use tags with payloads to make a list that contains a mixture of different types. For example: + +```coffee +List.map [ StrElem "A", StrElem "b", NumElem 1, StrElem "c", NumElem -3 ] \elem -> + when elem is + NumElem num -> Num.isNegative num + StrElem str -> Str.isCapitalized str + +# returns [ True, False, False, False, True ] +``` + +Compare this with the example from earlier, which caused a compile-time error: + +```coffee +List.map [ "A", "B", "C", 1, 2, 3 ] Num.isNegative +``` + +The version that uses tags works because we aren't trying to call `Num.isNegative` on each element. +Instead, we're using a `when` to tell when we've got a string or a number, and then calling either +`Num.isNegative` or `Str.isCapitalized` depending on which type we have. + +We could take this as far as we like, adding more different tags (e.g. `BoolElem True`) and then adding +more branches to the `when` to handle them appropriately. + +### `List.any` and `List.all` + +There are several functions that work like `List.map` - they walk through each element of a list and do +something with it. Another is `List.any`, which returns `True` if calling the given function on any element +in the list returns `True`: + +```coffee +List.any [ 1, 2, 3 ] Num.isOdd +# returns True because 1 and 3 are odd +``` +```coffee +List.any [ 1, 2, 3 ] Num.isNegative +# returns False because none of these is negative +``` + +There's also `List.all` which only returns `True` if all the elements in the list pass the test: + +```coffee +List.all [ 1, 2, 3 ] Num.isOdd +# returns False because 2 is not odd +``` +```coffee +List.all [ 1, 2, 3 ] Num.isPositive +# returns True because all of these are positive +``` + +### Removing elements from a list + +You can also drop elements from a list. One way is `List.dropAt` - for example: + +```coffee +List.dropAt [ "Sam", "Lee", "Ari" ] 1 +# drops the element at offset 1 ("Lee") and returns [ "Sam", "Ari" ] +``` + +Another way is to use `List.keepIf`, which passes each of the list's elements to the given +function, and then keeps them only if that function returns `True`. + +```coffee +List.keepIf [ 1, 2, 3, 4, 5 ] Num.isEven +# returns [ 2, 4 ] +``` + +There's also `List.dropIf`, which does the reverse: + +```coffee +List.dropIf [ 1, 2, 3, 4, 5 ] Num.isEven +# returns [ 1, 3, 5 ] +``` + +### Custom operations that walk over a list + +You can make your own custom operations that walk over all the elements in a list, using `List.walk`. +Let's look at an example and then walk (ha!) through it. + +```coffee +List.walk [ 1, 2, 3, 4, 5 ] { evens: [], odds: [] } \state, elem -> + if Num.isEven elem then + { state & evens: List.append state.evens elem } + else + { state & odds: List.append state.odds elem } + +# returns { evens: [ 2, 4 ], odds: [ 1, 3, 5 ] } +``` + +`List.walk` walks through each element of the list, building up a state as it goes. At the end, +it returns the final state - whatever it ended up being after processing the last element. The `\state, elem ->` +function it takes as its last argument accepts both the current state as well as the current list element +it's looking at, and then returns the new state based on whatever it decides to do with that element. + +In this example, we walk over the list `[ 1, 2, 3, 4, 5 ]` and add each element to either the `evens` or `odds` +field of a `state` record `{ evens, odds }`. By the end, that record has a list of all the even numbers in the +list as well as a list of all the odd numbers. + +The state doesn't have to be a record; it can be anything you want. For example, if you made it a boolean, you +could implement `List.any` using `List.walk`. You could also make the state be a list, and implement `List.map`, +`List.keepIf`, or `List.dropIf`. There are a lot of things you can do with `List.walk` - it's very flexible! + +It can be tricky to remember the argument order for `List.walk` at first. A helpful trick is that the arguments +follow the same pattern as what we've seen with `List.map`, `List.any`, `List.keepIf`, and `List.dropIf`: the +first argument is a list, and the last argument is a function. The difference here is that `List.walk` has one +more argument than those other functions; the only place it could go while preserving that pattern is the middle! + +That third argument specifies the initial `state` - what it's set to before the `\state, elem ->` function has +been called on it even once. (If the list is empty, the `\state, elem ->` function will never get called and +the initial state gets returned immediately.) + +> **Note:** Other languages give this operation different names, such as "fold," "reduce," "accumulate," +> "aggregate," "compress," and "inject." + +### Getting an individual element from a list + +Another thing we can do with a list is to get an individual element out of it. `List.get` is a common way to do this; +it takes a list and an index, and then returns the element at that index...if there is one. But what if there isn't? + +For example, what do each of these return? + +```coffee +List.get [ "a", "b", "c" ] 1 +``` +```coffee +List.get [ "a", "b", "c" ] 100 +``` + +The answer is that the first one returns `Ok "b"` and the second one returns `Err OutOfBounds`. +They both return tags! This is done so that the caller becomes responsible for handling the possibility that +the index is outside the bounds of that particular list. + +Here's how calling `List.get` can look in practice: + +```coffee +when List.get [ "a", "b", "c" ] index is + Ok str -> "I got this string: \(str)" + Err OutOfBounds -> "That index was out of bounds, sorry!" +``` + +There's also `List.first`, which always gets the first element, and `List.last` which always gets the last. +They return `Err ListWasEmpty` instead of `Err OutOfBounds`, because the only way they can fail is if you +pass them an empty list! + +These functions demonstrate a common pattern in Roc: operations that can fail returning either an `Ok` tag +with the answer (if successful), or an `Err` tag with another tag describing what went wrong (if unsuccessful). +In fact, it's such a common pattern that there's a whole module called `Result` which deals with these two tags. +Here are some examples of `Result` functions: + +```coffee +Result.withDefault (List.get [ "a", "b", "c" ] 100) "" +# returns "" because that's the default we said to use if List.get returned an Err +``` + +```coffee +Result.isOk (List.get [ "a", "b", "c" ] 1) +# returns True because `List.get` returned an `Ok` tag. (The payload gets ignored.) + +# Note: There's a Result.isErr function that works similarly. +``` + +### The pipe operator + +When you have nested function calls, sometimes it can be clearer to write them in a "pipelined" +style using the `|>` operator. Here are three examples of writing the same expression; they all +compile to exactly the same thing, but two of them use the `|>` operator to change how the calls look. + +```coffee +Result.withDefault (List.get [ "a", "b", "c" ] 1) "" +``` + +```coffee +List.get [ "a", "b", "c" ] 1 + |> Result.withDefault "" +``` + +The `|>` operator takes the value that comes before the `|>` and passes it as the first argument to whatever +comes after the `|>` - so in the example above, the `|>` takes `List.get [ "a", "b", "c" ] 1` and passes that +value as the first argument to `Result.withDefault` - making `""` the second argument to `Result.withDefault`. + +We can take this a step further like so: + +```coffee +[ "a", "b", "c" ] + |> List.get 1 + |> Result.withDefault "" +``` + +This is still equivalent to the first expression. Since `|>` is known as the "pipe operator," we can read +this as "start with `[ "a", "b", "c" ]`, then pipe it to `List.get`, then pipe it to `Result.withDefault`." + +One reason the `|>` operator injects the value as the first argument is to make it work better with +functions where argument order matters. For example, these two uses of `List.append` are equivalent: + +```coffee +List.append [ "a", "b", "c" ] "d" +``` +```coffee +[ "a", "b", "c" ] + |> List.append "d" +``` + +Another example is `Num.div`. All three of the following do the same thing, because `a / b` in Roc is syntax +sugar for `Num.div a b`: + +```coffee +first / second +``` +```coffee +Num.div first second +``` +```coffee +first + |> Num.div second +``` + +All operators in Roc are syntax sugar for normal function calls. See the "Operator Desugaring Table" +at the end of this tutorial for a complete list of them. + +## Types + +Sometimes you may want to document the type of a definition. For example, you might write: + +```ruby +# Takes a firstName string and a lastName string, and returns a string +fullName = \firstName, lastName -> + "\(firstName) \(lastName)" +``` + +Comments can be valuable documentation, but they can also get out of date and become misleading. +If someone changes this function and forgets to update the comment, it will no longer be accurate. + +### Type annotations + +Here's another way to document this function's type, which doesn't have that problem: + +```coffee +fullName : Str, Str -> Str +fullName = \firstName, lastName -> + "\(firstName) \(lastName)" +``` + +The `fullName :` line is a *type annotation*. It's a strictly optional piece of metadata we can add +above a def to describe its type. Unlike a comment, the Roc compiler will check type annotations for +accuracy. If the annotation ever doesn't fit with the implementation, we'll get a compile-time error. + +The annotation `fullName : Str, Str -> Str` says "`fullName` is a function that takes two strings as +arguments and returns a string." + +We can give type annotations to any value, not just functions. For example: + +```coffee +firstName : Str +firstName = "Amy" + +lastName : Str +lastName = "Lee" +``` + +These annotations say that both `firstName` and `lastName` have the type `Str`. + +We can annotate records similarly. For example, we could move `firstName` and `lastName` into a record like so: + +```coffee +amy : { firstName : Str, lastName : Str } +amy = { firstName: "Amy", lastName: "Lee" } + +jen : { firstName : Str, lastName : Str } +jen = { firstName: "Jen", lastName: "Majura" } +``` + +When we have a recurring type annotation like this, it can be nice to give it its own name. We do this like +so: + +```coffee +Musician : { firstName : Str, lastName : Str } + +amy : Musician +amy = { firstName: "Amy", lastName: "Lee" } + +simone : Musician +simone = { firstName: "Simone", lastName: "Simons" } +``` + +Here, `Musician` is a *type alias*. A type alias is like a def, except it gives a name to a type +instead of to a value. Just like how you can read `name : Str` as "`name` has the type `Str`," +you can also read `Musician : { firstName : Str, lastName : Str }` as "`Musician` has the type +`{ firstName : Str, lastName : Str }`." + +We can also give type annotations to tag unions: + +```coffee +colorFromStr : Str -> [ Red, Green, Yellow ] +colorFromStr = \string -> + when string is + "red" -> Red + "green" -> Green + _ -> Yellow +``` + +You can read the type `[ Red, Green, Yellow ]` as "a tag union of the tags `Red`, `Green`, and `Yellow`." + +When we annotate a list type, we have to specify the type of its elements: + +```coffee +names : List Str +names = [ "Amy", "Simone", "Tarja" ] +``` + +You can read `List Str` as "a list of strings." Here, `Str` is a *type parameter* that tells us what type of +`List` we're dealing with. `List` is a *parameterized type*, which means it's a type that requires a type +parameter; there's no way to give something a type of `List` without a type parameter - you have to specify +what type of list it is, such as `List Str` or `List Bool` or `List { firstName : Str, lastName : Str }`. + +There are some functions that work on any list, regardless of its type parameter. For example, `List.isEmpty` +has this type: + +```coffee +isEmpty : List * -> Bool +``` + +The `*` is a *wildcard type* - that is, a type that's compatible with any other type. `List *` is compatible +with any type of `List` - so, `List Str`, `List Bool`, and so on. So you can call +`List.isEmpty [ "I am a List Str" ]` as well as `List.isEmpty [ True ]`, and they will both work fine. + +The wildcard type also comes up with empty lists. Suppose we have one function that takes a `List Str` and another +function that takes a `List Bool`. We might reasonably expect to be able to pass an empty list (that is, `[]`) to +either of these functions. And so we can! This is because a `[]` value has the type `List *` - that is, +"a list with a wildcard type parameter," or "a list whose element type could be anything." + +`List.reverse` works similarly to `List.isEmpty`, but with an important distinction. As with `isEmpty`, we can +call `List.reverse` on any list, regardless of its type parameter. However, consider these calls: + +```coffee +strings : List Str +strings = List.reverse [ "a", "b" ] + +bools : List Bool +bools = List.reverse [ True, False ] +``` + +In the `strings` example, we have `List.reverse` returning a `List Str`. In the `bools` example, it's returning a +`List Bool`. So what's the type of `List.reverse`? + +We saw that `List.isEmpty` has the type `List * -> Bool`, so we might think the type of `List.reverse` would be +`reverse : List * -> List *`. However, remember that we also saw that the type of the empty list is `List *`? +`List * -> List *` is actually the type of a function that always returns empty lists! That's not what we want. + +What we want is something like one of these: + +```coffee +reverse : List elem -> List elem +``` +```coffee +reverse : List value -> List value +``` +```coffee +reverse : List a -> List a +``` + +Any of these will work, because `elem`, `value`, and `a` are all *type variables*. A type variable connects +two or more types in the same annotation. So you can read `List elem -> List elem` as "takes a list and returns +a list that has the same element type." Just like `List.reverse` does! + +You can choose any name you like for a type variable, but it has to be lowercase. (You may have noticed all the +types we've used until now are uppercase; that is no accident! Lowercase types are always type variables, so +all other named types have to be uppercase.) All three of the above type annotations are equivalent; +the only difference is that we chose different names (`elem`, `value`, and `a`) for their type variables. + +You can tell some interesting things about functions based on the type parameters involved. For example, +any function that returns `List *` definitely always returns an empty list. You don't need to look at the rest +of the type annotation, or even the function's implementation! The only way to have a function that returns +`List *` is if it returns an empty list. + +Similarly, the only way to have a function whose type is `a -> a` is if the function's implementation returns +its argument without modifying it in any way. This is known as [the identity function](https://en.wikipedia.org/wiki/Identity_function). + +### Numeric types + +[ This part of the tutorial has not been written yet. Coming soon! ] + +### Open and closed records + +[ This part of the tutorial has not been written yet. Coming soon! ] + +### Open and closed tag unions + +[ This part of the tutorial has not been written yet. Coming soon! ] + +## Interface modules + +[ This part of the tutorial has not been written yet. Coming soon! ] + +## Builtin modules + +There are several modules that are built into the Roc compiler, which are imported automatically into every +Roc module. They are: + +1. `Bool` +2. `Str` +3. `Num` +4. `List` +5. `Result` +6. `Dict` +7. `Set` + +You may have noticed that we already used the first five - for example, when we wrote `Str.concat` and `Num.isEven`, +we were referencing functions stored in the `Str` and `Num` modules. + +These modules are not ordinary `.roc` files that live on your filesystem. Rather, they are built directly into the +Roc compiler. That's why they're called "builtins!" + +Besides being built into the compiler, the builtin modules are different from other modules in that: + +* They are always imported. You never need to add them to `imports`. +* All their types are imported unqualified automatically. So you never need to write `Num.Nat`, because it's as if the `Num` module was imported using `imports [ Num.{ Nat } ]` (and the same for all the other types in the `Num` module). + +## The app module header + +Let's take a closer look at the part of `Hello.roc` above `main`: + +```coffee +app "hello" + packages [ pf: "examples/cli/platform" ] + imports [ pf.Stdout ] + provides main to pf +``` + +This is known as a *module header*. Every `.roc` file is a *module*, and there +are different types of modules. We know this particular one is an *application module* +(or *app module* for short) because it begins with the `app` keyword. + +The line `app "hello"` states that this module defines a Roc application, and +that building this application should produce an executable named `hello`. This +means when you run `roc Hello.roc`, the Roc compiler will build an executable +named `hello` (or `hello.exe` on Windows) and run it. You can also build the executable +without running it by running `roc build Hello.roc`. + +The remaining lines all involve the *platform* this application is built on: + +```coffee +packages [ pf: "examples/cli/platform" ] +imports [ pf.Stdout ] +provides main to pf +``` + +The `packages [ pf: "examples/cli/platform" ]` part says two things: + +- We're going to be using a *package* (that is, a collection of modules) called `"examples/cli/platform"` +- We're going to name that package `pf` so we can refer to it more concisely in the future. + +The `imports [ pf.Stdout ]` line says that we want to import the `Stdout` module +from the `pf` package, and make it available in the current module. + +This import has a direct interaction with our definition of `main`. Let's look +at that again: + +```coffee +main = Stdout.line "I'm a Roc application!" +``` + +Here, `main` is calling a function called `Stdout.line`. More specifically, it's +calling a function named `line` which is exposed by a module named +`Stdout`. + +When we write `imports [ pf.Stdout ]`, it specifies that the `Stdout` +module comes from the `pf` package. + +Since `pf` was the name we chose for the `examples/cli/platform` package +(when we wrote `packages [ pf: "examples/cli/platform" ]`), this `imports` line +tells the Roc compiler that when we call `Stdout.line`, it should look for that +`line` function in the `Stdout` module of the `examples/cli/platform` package. + +# Building a Command-Line Interface (CLI) + +## Tasks + +Tasks are technically not part of the Roc language, but they're very common in +platforms. Let's use the CLI platform in `examples/cli` as an example! + +In the Tutorial platform, we have four operations we can do: + +* Write a string to the console +* Read a string from user input +* Write a string to a file +* Read a string from a file + +We'll use these four operations to learn about tasks. + +First, let's do a basic "Hello World" using the tutorial app. + +```coffee +app "cli-tutorial" + packages { pf: "examples/cli/platform" } + imports [ pf.Stdout ] + provides [ main ] to pf + +main = + Stdout.line "Hello, World!" +``` + +The `Stdout.line` function takes a `Str` and writes it to [standard output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)). +It has this type: + +```coffee +Stdout.line : Str -> Task {} * +``` + +A `Task` represents an *effect* - that is, an interaction with state outside your Roc program, +such as the console's standard output, or a file. + +When we set `main` to be a `Task`, the task will get run when we run our program. Here, we've set +`main` to be a task that writes `"Hello, World!"` to `stdout` when it gets run, so that's what +our program does! + +`Task` has two type parameters: the type of value it produces when it finishes running, and any +errors that might happen when running it. `Stdout.line` has the type `Task {} *` because it doesn't +produce any values when it finishes (hence the `{}`) and there aren't any errors that can happen +when it runs (hence the `*`). + +In contrast, `Stdin.line` produces a `Str` when it finishes reading from [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). That `Str` is reflected in its type: + +```coffee +Stdin.line : Task Str * +``` + +Let's change `main` to read a line from `stdin`, and then print it back out again: + +```swift +app "cli-tutorial" + packages { pf: "examples/cli/platform" } + imports [ pf.Stdout, pf.Stdin, pf.Task ] + provides [ main ] to pf + +main = + Task.await Stdin.line \text -> + Stdout.line "You just entered: \(text)" +``` + +If you run this program, at first it won't do anything. It's waiting for you to type something +in and press Enter! Once you do, it should print back out what you entered. + +The `Task.await` function combines two tasks into one bigger `Task` which first runs one of the +given tasks and then the other. In this case, it's combining a `Stdin.line` task with a `Stdout.line` +task into one bigger `Task`, and then setting `main` to be that bigger task. + +The type of `Task.await` is: + +```haskell +Task.await : Task a err, (a -> Task b err) -> Task b err +``` + +The second argument to `Task.await` is a "callback function" which runs after the first task +completes. This callback function receives the output of that first task, and then returns +the second task. This means the second task can make use of output from the first task, like +we did in our `\text -> …` callback function here: + +```swift +\text -> + Stdout.line "You just entered: \(text)" +``` + +Notice that, just like before, we're still setting `main` to be a single `Task`. This is how we'll +always do it! We'll keep building up bigger and bigger `Task`s out of smaller tasks, and then setting +`main` to be that one big `Task`. + +For example, we can print a prompt before we pause to read from `stdin`, so it no longer looks like +the program isn't doing anything when we start it up: + +```swift +main = + Task.await (Stdout.line "Type something press Enter:") \_ -> + Task.await Stdin.line \text -> + Stdout.line "You just entered: \(text)" +``` + +This works, but we can make it a little nicer to read. Let's change it to the following: + +```haskell +app "cli-tutorial" + packages { pf: "examples/cli/platform" } + imports [ pf.Stdout, pf.Stdin, pf.Task.{ await } ] + provides [ main ] to pf + +main = + await (Stdout.line "Type something press Enter:") \_ -> + await Stdin.line \text -> + Stdout.line "You just entered: \(text)" +``` + +Here we've changed how we're importing the `Task` module. Before it was +`pf.Task` and now it's `pf.Task.{ await }`. The difference is that we're +importing `await` in an *unqualified* way, meaning now whenever we write `await` +in this module, it will refer to `Task.await` - so we no longer need to write +`Task.` every time we want to `await`. + +It's most common in Roc to call functions from other modules in a *qualified* way +(`Task.await`) rather than unqualified (`await`) like this, but it can be nice +for a function with an uncommon name (like "await") which often gets called repeatedly +across a small number of lines of code. + +Speaking of calling `await` repeatedly, if we keep calling it more and more on this +code, we'll end up doing a lot of indenting. If we'd rather not indent so much, we +can rewrite `main` into this style which looks different but does the same thing: + +```swift +main = + _ <- await (Stdout.line "Type something press Enter:") + text <- await Stdin.line + + Stdout.line "You just entered: \(text)" +``` + +This `<-` syntax is called *backpassing*. The `<-` is a way to define an +anonymous function, just like `\ … ->` is. + +Here, we're using backpassing to define two anonymous functions. Here's one of them: + +```swift +text <- + +Stdout.line "You just entered: \(text)" +``` + +It may not look like it, but this code is defining an anonymous function! You might +remember it as the anonymous function we previously defined like this: + +```swift +\text -> + Stdout.line "You just entered: \(text)" +``` + +These two anonymous functions are the same, just defined using different syntax. + +The reason the `<-` syntax is called *backpassing* is because it both defines a +function and passes that function *back* as an argument to whatever comes after +the `<-` (which in this case is `await Stdin.line`). + +Let's look at these two complete expressions side by side. They are both +saying exactly the same thing, with different syntax! + +Here's the original: + +```swift +await Stdin.line \text -> + Stdout.line "You just entered: \(text)" +``` + +And here's the equivalent expression with backpassing syntax: + +```swift +text <- await Stdin.line + +Stdout.line "You just entered: \(text)" +``` + +Here's the other function we're defining with backpassing: + +```swift +_ <- +text <- await Stdin.line + +Stdout.line "You just entered: \(text)" +``` + +We could also have written that function this way if we preferred: + +```swift +_ <- + +await Stdin.line \text -> + Stdout.line "You just entered: \(text)" +``` + +This is using a mix of a backpassing function `_ <-` and a normal function `\text ->`, +which is totally allowed! Since backpassing is nothing more than syntax sugar for +defining a function and passing back as an argument to another function, there's no +reason we can't mix and match if we like. + +That said, the typical style in which this `main` would be written in Roc is using +backpassing for all the `await` calls, like we had above: + +```swift +main = + _ <- await (Stdout.line "Type something press Enter:") + text <- await Stdin.line + + Stdout.line "You just entered: \(text)" +``` + +This way, it reads like a series of instructions: +1. First, run the `Stdout.line` task and await its completion. Ignore its output (hence the underscore in `_ <-`) +2. Next, run the `Stdin.line` task and await its completion. Name its output `text`. +3. Finally, run the `Stdout.line` task again, using the `text` value we got from the `Stdin.line` effect. + +Some important things to note about backpassing and `await`: +* `await` is not a language keyword in Roc! It's referring to the `Task.await` function, which we imported unqualified by writing `Task.{ await }` in our module imports. (That said, it is playing a similar role here to the `await` keyword in languages that have `async`/`await` keywords, even though in this case it's a function instead of a special keyword.) +* Backpassing syntax does not need to be used with `await` in particular. It can be used with any function. +* Roc's compiler treats functions defined with backpassing exactly the same way as functions defined the other way. The only difference between `\text ->` and `text <-` is how they look, so feel free to use whichever looks nicer to you! + +## Operator Desugaring Table + +Here are various Roc expressions involving operators, and what they desugar to. + +| Expression | Desugars to | +| --------------- | ---------------- | +| `a + b` | `Num.add a b` | +| `a - b` | `Num.sub a b` | +| `a * b` | `Num.mul a b` | +| `a / b` | `Num.div a b` | +| `a // b` | `Num.divFloor a b` | +| `a ^ b` | `Num.pow a b` | +| `a % b` | `Num.rem a b` | +| `a %% b` | `Num.mod a b` | +| `a >> b` | `Num.shr a b` | +| `a << b` | `Num.shl a b` | +| `-a` | `Num.neg a` | +| `-f x y` | `Num.neg (f x y)` | +| `a == b` | `Bool.isEq a b` | +| `a != b` | `Bool.isNotEq a b` | +| `a && b` | `Bool.and a b` | +| `a \|\| b` | `Bool.or a b` | +| `!a` | `Bool.not a` | +| `!f x y` | `Bool.not (f x y)` | +| `a \|> b` | `b a` | +| `a b c \|> f x y` | `f (a b c) x y` | diff --git a/ast/Cargo.toml b/ast/Cargo.toml index fea9692486..2eb0046b2f 100644 --- a/ast/Cargo.toml +++ b/ast/Cargo.toml @@ -17,7 +17,7 @@ roc_problem = { path = "../compiler/problem" } roc_types = { path = "../compiler/types" } roc_unify = { path = "../compiler/unify"} roc_load = { path = "../compiler/load" } -arraystring = "0.3.0" +arrayvec = "0.7.2" bumpalo = { version = "3.8.0", features = ["collections"] } libc = "0.2.106" page_size = "0.4.2" diff --git a/ast/src/lang/core/expr/expr2.rs b/ast/src/lang/core/expr/expr2.rs index 86d204447b..d35dc693db 100644 --- a/ast/src/lang/core/expr/expr2.rs +++ b/ast/src/lang/core/expr/expr2.rs @@ -1,4 +1,4 @@ -use arraystring::{typenum::U30, ArrayString}; +use arrayvec::ArrayString; use roc_types::subs::Variable; use crate::{ @@ -12,7 +12,7 @@ use roc_module::symbol::Symbol; use super::record_field::RecordField; -pub type ArrString = ArrayString; +pub type ArrString = ArrayString<24>; // TODO make the inner types private? pub type ExprId = NodeId; diff --git a/ast/src/lang/core/str.rs b/ast/src/lang/core/str.rs index fb28813a02..fb11193b8d 100644 --- a/ast/src/lang/core/str.rs +++ b/ast/src/lang/core/str.rs @@ -181,23 +181,15 @@ pub fn update_str_expr( enum Either { MyString(String), MyPoolStr(PoolStr), - Done, } let insert_either = match str_expr { Expr2::SmallStr(arr_string) => { - // TODO make sure this works for unicode "characters" - let insert_res = arr_string.try_insert(insert_index as u8, new_char); + let mut new_string = arr_string.as_str().to_owned(); - match insert_res { - Ok(_) => Either::Done, - _ => { - let mut new_string = arr_string.as_str().to_string(); - new_string.insert(insert_index, new_char); + new_string.insert(insert_index, new_char); - Either::MyString(new_string) - } - } + Either::MyString(new_string) } Expr2::Str(old_pool_str) => Either::MyPoolStr(*old_pool_str), other => UnexpectedASTNode { @@ -222,7 +214,6 @@ pub fn update_str_expr( pool.set(node_id, Expr2::Str(new_pool_str)) } - Either::Done => (), } Ok(()) diff --git a/cli/src/format.rs b/cli/src/format.rs index 1d430c47d6..9fb48b7d39 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -46,15 +46,15 @@ pub fn format(files: std::vec::Vec) { ); })); - let ast = ast.remove_spaces(&arena); - let reparsed_ast = reparsed_ast.remove_spaces(&arena); + let ast_normalized = ast.remove_spaces(&arena); + let reparsed_ast_normalized = reparsed_ast.remove_spaces(&arena); // HACK! // We compare the debug format strings of the ASTs, because I'm finding in practice that _somewhere_ deep inside the ast, // the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same. // I don't have the patience to debug this right now, so let's leave it for another day... // TODO: fix PartialEq impl on ast types - if format!("{:?}", ast) != format!("{:?}", reparsed_ast) { + if format!("{:?}", ast_normalized) != format!("{:?}", reparsed_ast_normalized) { let mut fail_file = file.clone(); fail_file.set_extension("roc-format-failed"); std::fs::write(&fail_file, &buf).unwrap(); @@ -76,6 +76,27 @@ pub fn format(files: std::vec::Vec) { after_file.display()); } + // Now verify that the resultant formatting is _stable_ - i.e. that it doesn't change again if re-formatted + let mut reformatted_buf = String::new_in(&arena); + fmt_all(&arena, &mut reformatted_buf, reparsed_ast); + if buf.as_str() != reformatted_buf.as_str() { + let mut unstable_1_file = file.clone(); + unstable_1_file.set_extension("roc-format-unstable-1"); + std::fs::write(&unstable_1_file, &buf).unwrap(); + + let mut unstable_2_file = file.clone(); + unstable_2_file.set_extension("roc-format-unstable-2"); + std::fs::write(&unstable_2_file, &reformatted_buf).unwrap(); + + internal_error!( + "Formatting bug; formatting is not stable. Reformatting the formatted file changed it again.\n\n\ + I wrote the result of formatting to this file for debugging purposes:\n{}\n\n\ + I wrote the result of double-formatting here:\n{}\n\n", + unstable_1_file.display(), + unstable_2_file.display()); + } + + // If all the checks above passed, actually write out the new file. std::fs::write(&file, &buf).unwrap(); } } diff --git a/cli/src/main.rs b/cli/src/main.rs index 7da8594648..4f2dbfa3c6 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -21,7 +21,7 @@ use roc_cli::build; fn main() -> io::Result<()> { let matches = build_app().get_matches(); - let exit_code = match matches.subcommand_name() { + let exit_code = match matches.subcommand() { None => { match matches.index_of(ROC_FILE) { Some(arg_index) => { @@ -37,14 +37,10 @@ fn main() -> io::Result<()> { } } } - Some(CMD_BUILD) => Ok(build( - matches.subcommand_matches(CMD_BUILD).unwrap(), - BuildConfig::BuildOnly, - )?), - Some(CMD_CHECK) => { + Some((CMD_BUILD, matches)) => Ok(build(matches, BuildConfig::BuildOnly)?), + Some((CMD_CHECK, matches)) => { let arena = bumpalo::Bump::new(); - let matches = matches.subcommand_matches(CMD_CHECK).unwrap(); let emit_timings = matches.is_present(FLAG_TIME); let filename = matches.value_of(ROC_FILE).unwrap(); let roc_file_path = PathBuf::from(filename); @@ -66,16 +62,14 @@ fn main() -> io::Result<()> { } } } - Some(CMD_REPL) => { + Some((CMD_REPL, _)) => { repl::main()?; // Exit 0 if the repl exited normally Ok(0) } - Some(CMD_EDIT) => { + Some((CMD_EDIT, matches)) => { match matches - .subcommand_matches(CMD_EDIT) - .unwrap() .values_of_os(DIRECTORY_OR_FILES) .map(|mut values| values.next()) { @@ -90,11 +84,8 @@ fn main() -> io::Result<()> { // Exit 0 if the editor exited normally Ok(0) } - Some(CMD_DOCS) => { - let maybe_values = matches - .subcommand_matches(CMD_DOCS) - .unwrap() - .values_of_os(DIRECTORY_OR_FILES); + Some((CMD_DOCS, matches)) => { + let maybe_values = matches.values_of_os(DIRECTORY_OR_FILES); let mut values: Vec = Vec::new(); @@ -128,11 +119,8 @@ fn main() -> io::Result<()> { Ok(0) } - Some(CMD_FORMAT) => { - let maybe_values = matches - .subcommand_matches(CMD_FORMAT) - .unwrap() - .values_of_os(DIRECTORY_OR_FILES); + Some((CMD_FORMAT, matches)) => { + let maybe_values = matches.values_of_os(DIRECTORY_OR_FILES); let mut values: Vec = Vec::new(); @@ -166,7 +154,7 @@ fn main() -> io::Result<()> { Ok(0) } - Some(CMD_VERSION) => { + Some((CMD_VERSION, _)) => { println!("roc {}", concatcp!(include_str!("../../version.txt"), "\n")); Ok(0) diff --git a/cli_utils/Cargo.lock b/cli_utils/Cargo.lock index dcd01f8c6a..042871c44e 100644 --- a/cli_utils/Cargo.lock +++ b/cli_utils/Cargo.lock @@ -90,15 +90,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "arraystring" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d517c467117e1d8ca795bc8cc90857ff7f79790cca0e26f6e9462694ece0185" -dependencies = [ - "typenum", -] - [[package]] name = "arrayvec" version = "0.5.2" @@ -191,6 +182,18 @@ dependencies = [ "typenum", ] +[[package]] +name = "bitvec" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block" version = "0.1.6" @@ -968,6 +971,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" +[[package]] +name = "funty" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" + [[package]] name = "futures" version = "0.3.17" @@ -1919,6 +1928,28 @@ dependencies = [ "ttf-parser 0.13.2", ] +[[package]] +name = "packed_struct" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c48e482b9a59ad6c2cdb06f7725e7bd33fe3525baaf4699fde7bfea6a5b77b1" +dependencies = [ + "bitvec", + "packed_struct_codegen", + "serde", +] + +[[package]] +name = "packed_struct_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e3692b867ec1d48ccb441e951637a2cc3130d0912c0059e48319e1c83e44bc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "page_size" version = "0.4.2" @@ -2210,6 +2241,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" + [[package]] name = "rand" version = "0.8.4" @@ -2375,7 +2412,7 @@ dependencies = [ name = "roc_ast" version = "0.1.0" dependencies = [ - "arraystring", + "arrayvec 0.7.2", "bumpalo", "libc", "page_size", @@ -2545,7 +2582,7 @@ dependencies = [ name = "roc_editor" version = "0.1.0" dependencies = [ - "arraystring", + "arrayvec 0.7.2", "bumpalo", "bytemuck", "cgmath", @@ -2603,12 +2640,14 @@ version = "0.1.0" dependencies = [ "bumpalo", "object 0.26.2", + "packed_struct", "roc_builtins", "roc_collections", "roc_module", "roc_mono", "roc_problem", "roc_region", + "roc_reporting", "roc_solve", "roc_types", "roc_unify", @@ -2771,6 +2810,8 @@ dependencies = [ name = "roc_solve" version = "0.1.0" dependencies = [ + "arrayvec 0.7.2", + "bumpalo", "roc_can", "roc_collections", "roc_module", @@ -2787,6 +2828,7 @@ version = "0.1.0" name = "roc_types" version = "0.1.0" dependencies = [ + "bumpalo", "roc_collections", "roc_module", "roc_region", @@ -3163,6 +3205,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.2" @@ -3807,6 +3855,15 @@ dependencies = [ "rand_core 0.6.3", ] +[[package]] +name = "wyz" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129e027ad65ce1453680623c3fb5163cbf7107bfe1aa32257e7d0e63f9ced188" +dependencies = [ + "tap", +] + [[package]] name = "x11-clipboard" version = "0.5.3" diff --git a/compiler/README.md b/compiler/README.md index b7b1f0b9f8..77e55b5762 100644 --- a/compiler/README.md +++ b/compiler/README.md @@ -53,14 +53,14 @@ The process of evaluation is basically to transform an `Expr` into the simplest For example, let's say we had this code: - "1 + 2 - 3" + "1 + 8 - 3" The parser will translate this into the following `Expr`: BinOp( Int(1), Plus, - BinOp(Int(2), Minus, Int(3)) + BinOp(Int(8), Minus, Int(3)) ) The `eval` function will take this `Expr` and translate it into this much simpler `Expr`: diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 64c30c231a..1845932459 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -491,11 +491,12 @@ fn gen_from_mono_module_dev_wasm32( loaded: MonomorphizedModule, app_o_file: &Path, ) -> CodeGenTiming { - let mut procedures = MutMap::default(); - - for (key, proc) in loaded.procedures { - procedures.insert(key, proc); - } + let MonomorphizedModule { + module_id, + procedures, + mut interns, + .. + } = loaded; let exposed_to_host = loaded .exposed_to_host @@ -505,11 +506,11 @@ fn gen_from_mono_module_dev_wasm32( let env = roc_gen_wasm::Env { arena, - interns: loaded.interns, + module_id, exposed_to_host, }; - let bytes = roc_gen_wasm::build_module(&env, procedures).unwrap(); + let bytes = roc_gen_wasm::build_module(&env, &mut interns, procedures).unwrap(); std::fs::write(&app_o_file, &bytes).expect("failed to write object to file"); @@ -533,8 +534,7 @@ fn gen_from_mono_module_dev_assembly( generate_allocators, }; - let module_object = roc_gen_dev::build_module(&env, target, loaded.procedures) - .expect("failed to compile module"); + let module_object = roc_gen_dev::build_module(&env, target, loaded.procedures); let module_out = module_object .write() diff --git a/compiler/builtins/bitcode/build.zig b/compiler/builtins/bitcode/build.zig index 93b970edd7..7dfd533ede 100644 --- a/compiler/builtins/bitcode/build.zig +++ b/compiler/builtins/bitcode/build.zig @@ -2,6 +2,7 @@ const std = @import("std"); const mem = std.mem; const Builder = std.build.Builder; const CrossTarget = std.zig.CrossTarget; +const Arch = std.Target.Cpu.Arch; pub fn build(b: *Builder) void { // b.setPreferredReleaseMode(builtin.Mode.Debug @@ -21,7 +22,12 @@ pub fn build(b: *Builder) void { test_step.dependOn(&main_tests.step); // Targets - const host_target = b.standardTargetOptions(.{}); + const host_target = b.standardTargetOptions(.{ + .default_target = CrossTarget{ + .cpu_model = .baseline, + // TODO allow for native target for maximum speed + } + }); const i386_target = makeI386Target(); const wasm32_target = makeWasm32Target(); diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 8b081cd1cf..37c0a3011a 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -138,6 +138,7 @@ comptime { comptime { exportUtilsFn(utils.test_panic, "test_panic"); + exportUtilsFn(utils.increfC, "incref"); exportUtilsFn(utils.decrefC, "decref"); exportUtilsFn(utils.decrefCheckNullC, "decref_check_null"); diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index ab2ea26171..940e8d945b 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -104,6 +104,12 @@ pub const IntWidth = enum(u8) { I128 = 9, }; +pub fn increfC(ptr_to_refcount: *isize, amount: isize) callconv(.C) void { + var refcount = ptr_to_refcount.*; + var masked_amount = if (refcount == REFCOUNT_MAX_ISIZE) 0 else amount; + ptr_to_refcount.* = refcount + masked_amount; +} + pub fn decrefC( bytes_or_null: ?[*]isize, alignment: u32, @@ -261,3 +267,17 @@ pub const UpdateMode = enum(u8) { Immutable = 0, InPlace = 1, }; + +test "increfC, refcounted data" { + var mock_rc: isize = REFCOUNT_ONE_ISIZE + 17; + var ptr_to_refcount: *isize = &mock_rc; + increfC(ptr_to_refcount, 2); + try std.testing.expectEqual(mock_rc, REFCOUNT_ONE_ISIZE + 19); +} + +test "increfC, static data" { + var mock_rc: isize = REFCOUNT_MAX_ISIZE; + var ptr_to_refcount: *isize = &mock_rc; + increfC(ptr_to_refcount, 2); + try std.testing.expectEqual(mock_rc, REFCOUNT_MAX_ISIZE); +} diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 3790319194..10ede5fcd4 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -61,6 +61,7 @@ interface Num addChecked, atan, acos, + toStr, Signed128, Signed64, Signed32, diff --git a/compiler/builtins/docs/Str.roc b/compiler/builtins/docs/Str.roc index 071c89f97a..701cf07301 100644 --- a/compiler/builtins/docs/Str.roc +++ b/compiler/builtins/docs/Str.roc @@ -10,8 +10,6 @@ interface Str countGraphemes, startsWith, endsWith, - fromInt, - fromFloat, fromUtf8, Utf8Problem, Utf8ByteProblem, diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index afffd31dd9..da9de5a6e6 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -309,5 +309,6 @@ pub const DEC_MUL_WITH_OVERFLOW: &str = "roc_builtins.dec.mul_with_overflow"; pub const DEC_DIV: &str = "roc_builtins.dec.div"; pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic"; +pub const UTILS_INCREF: &str = "roc_builtins.utils.incref"; pub const UTILS_DECREF: &str = "roc_builtins.utils.decref"; pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null"; diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 917eed18ce..cc7fb4d7d2 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -385,6 +385,13 @@ pub fn types() -> MutMap { // maxI128 : I128 add_type!(Symbol::NUM_MAX_I128, i128_type()); + // toStr : Num a -> Str + add_top_level_function_type!( + Symbol::NUM_TO_STR, + vec![num_type(flex(TVAR1))], + Box::new(str_type()) + ); + // Float module // div : Float a, Float a -> Float a @@ -618,13 +625,6 @@ pub fn types() -> MutMap { Box::new(nat_type()) ); - // fromInt : Int a -> Str - add_top_level_function_type!( - Symbol::STR_FROM_INT, - vec![int_type(flex(TVAR1))], - Box::new(str_type()) - ); - // repeat : Str, Nat -> Str add_top_level_function_type!( Symbol::STR_REPEAT, @@ -702,13 +702,6 @@ pub fn types() -> MutMap { Box::new(list_type(u8_type())) ); - // fromFloat : Float a -> Str - add_top_level_function_type!( - Symbol::STR_FROM_FLOAT, - vec![float_type(flex(TVAR1))], - Box::new(str_type()) - ); - // List module // get : List elem, Nat -> Result elem [ OutOfBounds ]* diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index d2ce526f1b..550e30cc11 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -61,11 +61,9 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option STR_STARTS_WITH_CODE_PT => str_starts_with_code_point, STR_ENDS_WITH => str_ends_with, STR_COUNT_GRAPHEMES => str_count_graphemes, - STR_FROM_INT => str_from_int, STR_FROM_UTF8 => str_from_utf8, STR_FROM_UTF8_RANGE => str_from_utf8_range, STR_TO_UTF8 => str_to_utf8, - STR_FROM_FLOAT=> str_from_float, STR_REPEAT => str_repeat, STR_TRIM => str_trim, STR_TRIM_LEFT => str_trim_left, @@ -192,6 +190,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_SHIFT_RIGHT_ZERO_FILL => num_shift_right_zf_by, NUM_INT_CAST=> num_int_cast, NUM_MAX_I128=> num_max_i128, + NUM_TO_STR => num_to_str, RESULT_MAP => result_map, RESULT_MAP_ERR => result_map_err, RESULT_AFTER => result_after, @@ -369,6 +368,26 @@ fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def { } } +// Num.toStr : Num a -> Str +fn num_to_str(symbol: Symbol, var_store: &mut VarStore) -> Def { + let num_var = var_store.fresh(); + let str_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::NumToStr, + args: vec![(num_var, Var(Symbol::ARG_1))], + ret_var: str_var, + }; + + defn( + symbol, + vec![(num_var, Symbol::ARG_1)], + var_store, + body, + str_var, + ) +} + /// Bool.isEq : val, val -> Bool fn bool_eq(symbol: Symbol, var_store: &mut VarStore) -> Def { let arg_var = var_store.fresh(); @@ -1436,26 +1455,6 @@ fn str_count_graphemes(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -/// Str.fromInt : Int * -> Str -fn str_from_int(symbol: Symbol, var_store: &mut VarStore) -> Def { - let int_var = var_store.fresh(); - let str_var = var_store.fresh(); - - let body = RunLowLevel { - op: LowLevel::StrFromInt, - args: vec![(int_var, Var(Symbol::ARG_1))], - ret_var: str_var, - }; - - defn( - symbol, - vec![(int_var, Symbol::ARG_1)], - var_store, - body, - str_var, - ) -} - /// Str.fromUtf8 : List U8 -> Result Str [ BadUtf8 { byteIndex : Nat, problem : Utf8Problem } } ]* fn str_from_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def { let bytes_var = var_store.fresh(); @@ -1738,26 +1737,6 @@ fn str_to_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_1(symbol, LowLevel::StrToUtf8, var_store) } -/// Str.fromFloat : Float * -> Str -fn str_from_float(symbol: Symbol, var_store: &mut VarStore) -> Def { - let float_var = var_store.fresh(); - let str_var = var_store.fresh(); - - let body = RunLowLevel { - op: LowLevel::StrFromFloat, - args: vec![(float_var, Var(Symbol::ARG_1))], - ret_var: str_var, - }; - - defn( - symbol, - vec![(float_var, Symbol::ARG_1)], - var_store, - body, - str_var, - ) -} - /// List.concat : List elem, List elem -> List elem fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); diff --git a/compiler/fmt/Cargo.toml b/compiler/fmt/Cargo.toml index 6b19b61815..ea17bf7579 100644 --- a/compiler/fmt/Cargo.toml +++ b/compiler/fmt/Cargo.toml @@ -15,3 +15,4 @@ bumpalo = { version = "3.8.0", features = ["collections"] } [dev-dependencies] pretty_assertions = "1.0.0" indoc = "1.0.3" +roc_test_utils = { path = "../../test_utils" } diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index b77f538919..fd66c99bad 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -139,7 +139,12 @@ impl<'a> Formattable<'a> for Expr<'a> { sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); } else { buf.push('('); - sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + sub_expr.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + indent + INDENT, + ); buf.push(')'); } } @@ -576,8 +581,10 @@ fn fmt_when<'a>( while let Some(branch) = it.next() { let patterns = &branch.patterns; let expr = &branch.value; - add_spaces(buf, indent + INDENT); let (first_pattern, rest) = patterns.split_first().unwrap(); + if !has_newline_before(&first_pattern.value) { + add_spaces(buf, indent + INDENT); + } let is_multiline = match rest.last() { None => false, Some(last_pattern) => first_pattern.region.start_line != last_pattern.region.end_line, @@ -591,12 +598,9 @@ fn fmt_when<'a>( ); for when_pattern in rest { if is_multiline { - buf.push_str("\n"); - add_spaces(buf, indent + INDENT); - buf.push_str("| "); - } else { - buf.push_str(" | "); + newline(buf, indent + INDENT); } + buf.push_str(" | "); fmt_pattern(buf, &when_pattern.value, indent + INDENT, Parens::NotNeeded); } @@ -605,9 +609,9 @@ fn fmt_when<'a>( guard_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); } - buf.push_str(" ->\n"); + buf.push_str(" ->"); + newline(buf, indent + INDENT * 2); - add_spaces(buf, indent + (INDENT * 2)); match expr.value { Expr::SpaceBefore(nested, spaces) => { fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent + (INDENT * 2)); @@ -635,6 +639,16 @@ fn fmt_when<'a>( } } +fn has_newline_before(value: &Pattern) -> bool { + match value { + Pattern::SpaceAfter(v, _) => has_newline_before(v), + Pattern::SpaceBefore(v, spaces) => { + v.is_multiline() && spaces.last().map(|s| s.is_newline()).unwrap_or(false) + } + _ => false, + } +} + fn fmt_expect<'a>( buf: &mut String<'a>, condition: &'a Located>, @@ -1098,6 +1112,7 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool { BinOp::Pizza | BinOp::Assignment | BinOp::HasType | BinOp::Backpassing => false, }) } + Expr::If(_, _) => true, _ => false, } } diff --git a/compiler/fmt/src/spaces.rs b/compiler/fmt/src/spaces.rs index 1103449590..3da569b412 100644 --- a/compiler/fmt/src/spaces.rs +++ b/compiler/fmt/src/spaces.rs @@ -46,7 +46,7 @@ where match space { Newline => { if !encountered_comment && (consecutive_newlines < 2) { - if iter.peek() == Some(&&Newline) { + if iter.peek() == Some(&&Newline) && consecutive_newlines < 1 { buf.push('\n'); } else { newline(buf, indent); diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index e2465e98cc..1d078b8b24 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -1,6 +1,4 @@ #[macro_use] -extern crate pretty_assertions; -#[macro_use] extern crate indoc; extern crate bumpalo; extern crate roc_fmt; @@ -14,24 +12,35 @@ mod test_fmt { use roc_fmt::module::fmt_module; use roc_parse::module::{self, module_defs}; use roc_parse::parser::{Parser, State}; + use roc_test_utils::assert_multiline_str_eq; - fn expr_formats_to(input: &str, expected: &str) { + // Not intended to be used directly in tests; please use expr_formats_to or expr_formats_same + fn expect_format_helper(input: &str, expected: &str) { let arena = Bump::new(); - let input = input.trim_end(); - let expected = expected.trim_end(); - match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) { Ok(actual) => { let mut buf = String::new_in(&arena); actual.format_with_options(&mut buf, Parens::NotNeeded, Newlines::Yes, 0); - assert_eq!(buf, expected) + assert_multiline_str_eq!(expected, buf.as_str()); } Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:?}\n\n", input, error) }; } + fn expr_formats_to(input: &str, expected: &str) { + let input = input.trim_end(); + let expected = expected.trim_end(); + + // First check that input formats to the expected version + expect_format_helper(input, expected); + + // Parse the expected result format it, asserting that it doesn't change + // It's important that formatting be stable / idempotent + expect_format_helper(expected, expected); + } + fn expr_formats_same(input: &str) { expr_formats_to(input, input); } @@ -56,7 +65,7 @@ mod test_fmt { Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error) } - assert_eq!(buf, expected) + assert_multiline_str_eq!(expected, buf.as_str()) } Err(error) => panic!("Unexpected parse failure when parsing this for module header formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error) }; @@ -89,6 +98,32 @@ mod test_fmt { )); } + #[test] + #[ignore] + fn def_with_comment_on_same_line() { + // TODO(joshuawarner32): make trailing comments format stabily + // This test currently fails because the comment ends up as SpaceBefore for the following `a` + // This works fine when formatted _once_ - but if you format again, the formatter wants to + // insert a newline between `a = "Hello"` and the comment, further muddying the waters. + // Clearly the formatter shouldn't be allowed to migrate a comment around like that. + expr_formats_to( + indoc!( + r#" + a = "Hello" # This variable is for greeting + + a + "# + ), + indoc!( + r#" + a = "Hello" + # This variable is for greeting + a + "# + ), + ); + } + #[test] fn def_with_comment_and_extra_space() { expr_formats_to( @@ -1835,7 +1870,7 @@ mod test_fmt { } #[test] - fn when_with_alternatives() { + fn when_with_alternatives_1() { expr_formats_same(indoc!( r#" when b is @@ -1848,6 +1883,10 @@ mod test_fmt { 5 "# )); + } + + #[test] + fn when_with_alternatives_2() { expr_formats_same(indoc!( r#" when b is @@ -1857,6 +1896,10 @@ mod test_fmt { 1 "# )); + } + + #[test] + fn when_with_alternatives_3() { expr_formats_to( indoc!( r#" @@ -1874,6 +1917,10 @@ mod test_fmt { "# ), ); + } + + #[test] + fn when_with_alternatives_4() { expr_formats_to( indoc!( r#" @@ -1902,15 +1949,15 @@ mod test_fmt { r#" when b is 1 - | 2 - | 3 -> + | 2 + | 3 -> 4 5 | 6 | 7 -> 8 9 - | 10 -> + | 10 -> 11 12 | 13 -> @@ -1919,7 +1966,7 @@ mod test_fmt { 16 17 - | 18 -> + | 18 -> 19 20 -> @@ -1929,6 +1976,30 @@ mod test_fmt { ); } + #[test] + fn with_multiline_pattern_indentation() { + expr_formats_to( + indoc!( + r#" + when b is 3->4 + 9 + |8->9 + "# + ), + indoc!( + r#" + when b is + 3 -> + 4 + + 9 + | 8 -> + 9 + "# + ), + ); + } + #[test] fn when_with_moving_comments() { expr_formats_to( @@ -2111,6 +2182,35 @@ mod test_fmt { )); } + #[test] + fn inner_def_with_triple_newline_before() { + // The triple newline used to cause the code in add_spaces to not indent the next line, + // which of course is not the same tree (and nor does it parse) + expr_formats_to( + indoc!( + r#" + \x -> + m = 2 + + + m1 = insert m n powerOf10 + + 42 + "# + ), + indoc!( + r#" + \x -> + m = 2 + + m1 = insert m n powerOf10 + + 42 + "# + ), + ); + } + #[test] fn when_guard() { expr_formats_same(indoc!( @@ -2311,6 +2411,15 @@ mod test_fmt { )); } + #[test] + fn binop_if() { + expr_formats_same(indoc!( + r#" + 5 * (if x > 0 then 1 else 2) + "# + )); + } + // UNARY OP #[test] @@ -2759,6 +2868,21 @@ mod test_fmt { )); } + #[test] + fn backpassing_parens_body() { + expr_formats_same(indoc!( + r#" + Task.fromResult + (a, b <- binaryOp ctx + if a == b then + -1 + else + 0 + ) + "# + )); + } + #[test] fn backpassing_body_on_newline() { expr_formats_same(indoc!( diff --git a/compiler/gen_dev/Cargo.toml b/compiler/gen_dev/Cargo.toml index a34471b9ac..41fe784e7e 100644 --- a/compiler/gen_dev/Cargo.toml +++ b/compiler/gen_dev/Cargo.toml @@ -16,6 +16,7 @@ roc_builtins = { path = "../builtins" } roc_unify = { path = "../unify" } roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } +roc_reporting = { path = "../../reporting" } bumpalo = { version = "3.8.0", features = ["collections"] } target-lexicon = "0.12.2" # TODO: Deal with the update of object to 0.27. diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 26b92d33c6..34c34268ff 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -5,6 +5,7 @@ use packed_struct::prelude::*; use roc_collections::all::MutMap; use roc_module::symbol::Symbol; use roc_mono::layout::Layout; +use roc_reporting::internal_error; #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] #[allow(dead_code)] @@ -151,13 +152,15 @@ impl CallConv for AArch64Call { saved_regs: &[AArch64GeneralReg], requested_stack_size: i32, fn_call_stack_size: i32, - ) -> Result { + ) -> i32 { // Full size is upcast to i64 to make sure we don't overflow here. - let full_stack_size = requested_stack_size + let full_stack_size = match requested_stack_size .checked_add(8 * saved_regs.len() as i32 + 8) // The extra 8 is space to store the frame pointer. - .ok_or("Ran out of stack space")? - .checked_add(fn_call_stack_size) - .ok_or("Ran out of stack space")?; + .and_then(|size| size.checked_add(fn_call_stack_size)) + { + Some(size) => size, + _ => internal_error!("Ran out of stack space"), + }; let alignment = if full_stack_size <= 0 { 0 } else { @@ -194,12 +197,12 @@ impl CallConv for AArch64Call { offset -= 8; AArch64Assembler::mov_base32_reg64(buf, offset, *reg); } - Ok(aligned_stack_size) + aligned_stack_size } else { - Ok(0) + 0 } } else { - Err("Ran out of stack space".to_string()) + internal_error!("Ran out of stack space"); } } @@ -209,7 +212,7 @@ impl CallConv for AArch64Call { saved_regs: &[AArch64GeneralReg], aligned_stack_size: i32, fn_call_stack_size: i32, - ) -> Result<(), String> { + ) { if aligned_stack_size > 0 { // All the following stores could be optimized by using `STP` to store pairs. let mut offset = aligned_stack_size; @@ -230,7 +233,6 @@ impl CallConv for AArch64Call { aligned_stack_size, ); } - Ok(()) } #[inline(always)] @@ -239,8 +241,8 @@ impl CallConv for AArch64Call { _symbol_map: &mut MutMap>, _args: &'a [(Layout<'a>, Symbol)], _ret_layout: &Layout<'a>, - ) -> Result<(), String> { - Err("Loading args not yet implemented for AArch64".to_string()) + ) { + unimplemented!("Loading args not yet implemented for AArch64"); } #[inline(always)] @@ -250,8 +252,8 @@ impl CallConv for AArch64Call { _args: &'a [Symbol], _arg_layouts: &[Layout<'a>], _ret_layout: &Layout<'a>, - ) -> Result { - Err("Storing args not yet implemented for AArch64".to_string()) + ) -> u32 { + unimplemented!("Storing args not yet implemented for AArch64"); } fn return_struct<'a>( @@ -260,12 +262,12 @@ impl CallConv for AArch64Call { _struct_size: u32, _field_layouts: &[Layout<'a>], _ret_reg: Option, - ) -> Result<(), String> { - Err("Returning structs not yet implemented for AArch64".to_string()) + ) { + unimplemented!("Returning structs not yet implemented for AArch64"); } - fn returns_via_arg_pointer(_ret_layout: &Layout) -> Result { - Err("Returning via arg pointer not yet implemented for AArch64".to_string()) + fn returns_via_arg_pointer(_ret_layout: &Layout) -> bool { + unimplemented!("Returning via arg pointer not yet implemented for AArch64"); } } diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index d832f3f2a2..f1d0a31a57 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -5,6 +5,7 @@ use roc_collections::all::{MutMap, MutSet}; use roc_module::symbol::Symbol; use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, SelfRecursive, Stmt}; use roc_mono::layout::{Builtin, Layout}; +use roc_reporting::internal_error; use std::marker::PhantomData; pub mod aarch64; @@ -39,13 +40,13 @@ pub trait CallConv { general_saved_regs: &[GeneralReg], requested_stack_size: i32, fn_call_stack_size: i32, - ) -> Result; + ) -> i32; fn cleanup_stack<'a>( buf: &mut Vec<'a, u8>, general_saved_regs: &[GeneralReg], aligned_stack_size: i32, fn_call_stack_size: i32, - ) -> Result<(), String>; + ); // load_args updates the symbol map to know where every arg is stored. fn load_args<'a>( @@ -54,7 +55,7 @@ pub trait CallConv { args: &'a [(Layout<'a>, Symbol)], // ret_layout is needed because if it is a complex type, we pass a pointer as the first arg. ret_layout: &Layout<'a>, - ) -> Result<(), String>; + ); // store_args stores the args in registers and on the stack for function calling. // It returns the amount of stack space needed to temporarily store the args. @@ -65,7 +66,7 @@ pub trait CallConv { arg_layouts: &[Layout<'a>], // ret_layout is needed because if it is a complex type, we pass a pointer as the first arg. ret_layout: &Layout<'a>, - ) -> Result; + ) -> u32; // return_struct returns a struct currently on the stack at `struct_offset`. // It does so using registers and stack as necessary. @@ -75,10 +76,10 @@ pub trait CallConv { struct_size: u32, field_layouts: &[Layout<'a>], ret_reg: Option, - ) -> Result<(), String>; + ); // returns true if the layout should be returned via an argument pointer. - fn returns_via_arg_pointer(ret_layout: &Layout) -> Result; + fn returns_via_arg_pointer(ret_layout: &Layout) -> bool; } /// Assembler contains calls to the backend assembly generator. @@ -275,8 +276,8 @@ impl< CC: CallConv, > Backend<'a> for Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC> { - fn new(env: &'a Env) -> Result { - Ok(Backend64Bit { + fn new(env: &'a Env) -> Self { + Backend64Bit { phantom_asm: PhantomData, phantom_cc: PhantomData, env, @@ -299,7 +300,7 @@ impl< free_stack_chunks: bumpalo::vec![in env.arena], stack_size: 0, fn_call_stack_size: 0, - }) + } } fn env(&self) -> &'a Env<'a> { @@ -350,7 +351,7 @@ impl< &mut self.free_map } - fn finalize(&mut self) -> Result<(&'a [u8], &[Relocation]), String> { + fn finalize(&mut self) -> (&'a [u8], &[Relocation]) { let mut out = bumpalo::vec![in self.env.arena]; // Setup stack. @@ -361,7 +362,7 @@ impl< &used_regs, self.stack_size as i32, self.fn_call_stack_size as i32, - )?; + ); let setup_offset = out.len(); // Deal with jumps to the return address. @@ -414,7 +415,7 @@ impl< &used_regs, aligned_stack_size, self.fn_call_stack_size as i32, - )?; + ); ASM::ret(&mut out); // Update other relocs to include stack setup offset. @@ -439,20 +440,16 @@ impl< Relocation::JmpToReturn { .. } => unreachable!(), }), ); - Ok((out.into_bump_slice(), out_relocs.into_bump_slice())) + (out.into_bump_slice(), out_relocs.into_bump_slice()) } - fn load_args( - &mut self, - args: &'a [(Layout<'a>, Symbol)], - ret_layout: &Layout<'a>, - ) -> Result<(), String> { + fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>) { CC::load_args( &mut self.buf, &mut self.symbol_storage_map, args, ret_layout, - )?; + ); // Update used and free regs. for (sym, storage) in &self.symbol_storage_map { match storage { @@ -467,15 +464,14 @@ impl< SymbolStorage::Base { .. } => {} } } - Ok(()) } /// Used for generating wrappers for malloc/realloc/free - fn build_wrapped_jmp(&mut self) -> Result<(&'a [u8], u64), String> { + fn build_wrapped_jmp(&mut self) -> (&'a [u8], u64) { let mut out = bumpalo::vec![in self.env.arena]; let offset = ASM::tail_call(&mut out); - Ok((out.into_bump_slice(), offset)) + (out.into_bump_slice(), offset) } fn build_fn_call( @@ -485,14 +481,14 @@ impl< args: &'a [Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, - ) -> Result<(), String> { + ) { if let Some(SelfRecursive::SelfRecursive(id)) = self.is_self_recursive { if &fn_name == self.proc_name.as_ref().unwrap() && self.join_map.contains_key(&id) { return self.build_jump(&id, args, arg_layouts, ret_layout); } } // Save used caller saved regs. - self.push_used_caller_saved_regs_to_stack()?; + self.push_used_caller_saved_regs_to_stack(); // Put values in param regs or on top of the stack. let tmp_stack_size = CC::store_args( @@ -501,7 +497,7 @@ impl< args, arg_layouts, ret_layout, - )?; + ); self.fn_call_stack_size = std::cmp::max(self.fn_call_stack_size, tmp_stack_size); // Call function and generate reloc. @@ -510,21 +506,19 @@ impl< // move return value to dst. match ret_layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64) | Builtin::Bool) => { - let dst_reg = self.claim_general_reg(dst)?; + let dst_reg = self.claim_general_reg(dst); ASM::mov_reg64_reg64(&mut self.buf, dst_reg, CC::GENERAL_RETURN_REGS[0]); - Ok(()) } Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { - let dst_reg = self.claim_float_reg(dst)?; + let dst_reg = self.claim_float_reg(dst); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]); - Ok(()) } Layout::Builtin(Builtin::Str) => { - if CC::returns_via_arg_pointer(ret_layout)? { + if CC::returns_via_arg_pointer(ret_layout) { // This will happen on windows, return via pointer here. - Err("FnCall: Returning strings via pointer not yet implemented".to_string()) + unimplemented!("FnCall: Returning strings via pointer not yet implemented"); } else { - let offset = self.claim_stack_size(16)?; + let offset = self.claim_stack_size(16); self.symbol_storage_map.insert( *dst, SymbolStorage::Base { @@ -535,13 +529,12 @@ 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]); - Ok(()) } } - x => Err(format!( + x => unimplemented!( "FnCall: receiving return type, {:?}, is not yet implemented", x - )), + ), } } @@ -552,11 +545,11 @@ impl< branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)], default_branch: &(BranchInfo<'a>, &'a Stmt<'a>), ret_layout: &Layout<'a>, - ) -> Result<(), String> { + ) { // Switches are a little complex due to keeping track of jumps. // In general I am trying to not have to loop over things multiple times or waste memory. // The basic plan is to make jumps to nowhere and then correct them once we know the correct address. - let cond_reg = self.load_to_general_reg(cond_symbol)?; + let cond_reg = self.load_to_general_reg(cond_symbol); let mut ret_jumps = bumpalo::vec![in self.env.arena]; let mut tmp = bumpalo::vec![in self.env.arena]; @@ -569,7 +562,7 @@ impl< let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0); // Build all statements in this branch. - self.build_stmt(stmt, ret_layout)?; + self.build_stmt(stmt, ret_layout); // Build unconditional jump to the end of this switch. // Since we don't know the offset yet, set it to 0 and overwrite later. @@ -585,15 +578,15 @@ impl< self.buf[jne_location + i] = *byte; } } else { - return Err(format!( + unimplemented!( "Switch: branch info, {:?}, is not yet implemented", branch_info - )); + ); } } let (branch_info, stmt) = default_branch; if let BranchInfo::None = branch_info { - self.build_stmt(stmt, ret_layout)?; + self.build_stmt(stmt, ret_layout); // Update all return jumps to jump past the default case. let ret_offset = self.buf.len(); @@ -605,12 +598,11 @@ impl< ret_offset as u64, ); } - Ok(()) } else { - Err(format!( + unimplemented!( "Switch: branch info, {:?}, is not yet implemented", branch_info - )) + ); } } @@ -621,14 +613,14 @@ impl< body: &'a Stmt<'a>, remainder: &'a Stmt<'a>, ret_layout: &Layout<'a>, - ) -> Result<(), String> { + ) { // Create jump to remaining. let jmp_location = self.buf.len(); let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); // This section can essentially be seen as a sub function within the main function. // Thus we build using a new backend with some minor extra synchronization. - let mut sub_backend = Self::new(self.env)?; + let mut sub_backend = Self::new(self.env); sub_backend.reset( self.proc_name.as_ref().unwrap().clone(), self.is_self_recursive.as_ref().unwrap().clone(), @@ -651,10 +643,10 @@ impl< for param in parameters { args.push((param.layout, param.symbol)); } - sub_backend.load_args(args.into_bump_slice(), ret_layout)?; + sub_backend.load_args(args.into_bump_slice(), ret_layout); // Build all statements in body. - sub_backend.build_stmt(body, ret_layout)?; + sub_backend.build_stmt(body, ret_layout); // Merge the "sub function" into the main function. let sub_func_offset = self.buf.len() as u64; @@ -707,10 +699,10 @@ impl< args: &'a [Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, - ) -> Result<(), String> { + ) { // Treat this like a function call, but with a jump instead of a call instruction at the end. - self.push_used_caller_saved_regs_to_stack()?; + self.push_used_caller_saved_regs_to_stack(); let tmp_stack_size = CC::store_args( &mut self.buf, @@ -718,7 +710,7 @@ impl< args, arg_layouts, ret_layout, - )?; + ); self.fn_call_stack_size = std::cmp::max(self.fn_call_stack_size, tmp_stack_size); let jmp_location = self.buf.len(); @@ -733,154 +725,101 @@ impl< start_offset as u64, offset, ); - Ok(()) } else { - Err(format!( - "Jump: unknown point specified to jump to: {:?}", - id - )) + internal_error!("Jump: unknown point specified to jump to: {:?}", id); } } - fn build_num_abs( - &mut self, - dst: &Symbol, - src: &Symbol, - layout: &Layout<'a>, - ) -> Result<(), String> { + fn build_num_abs(&mut self, dst: &Symbol, src: &Symbol, layout: &Layout<'a>) { match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst)?; - let src_reg = self.load_to_general_reg(src)?; + let dst_reg = self.claim_general_reg(dst); + let src_reg = self.load_to_general_reg(src); ASM::abs_reg64_reg64(&mut self.buf, dst_reg, src_reg); - Ok(()) } Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { - let dst_reg = self.claim_float_reg(dst)?; - let src_reg = self.load_to_float_reg(src)?; + let dst_reg = self.claim_float_reg(dst); + let src_reg = self.load_to_float_reg(src); ASM::abs_freg64_freg64(&mut self.buf, &mut self.relocs, dst_reg, src_reg); - Ok(()) } - x => Err(format!("NumAbs: layout, {:?}, not implemented yet", x)), + x => unimplemented!("NumAbs: layout, {:?}, not implemented yet", x), } } - fn build_num_add( - &mut self, - dst: &Symbol, - src1: &Symbol, - src2: &Symbol, - layout: &Layout<'a>, - ) -> Result<(), String> { + fn build_num_add(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>) { match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst)?; - let src1_reg = self.load_to_general_reg(src1)?; - let src2_reg = self.load_to_general_reg(src2)?; + let dst_reg = self.claim_general_reg(dst); + let src1_reg = self.load_to_general_reg(src1); + let src2_reg = self.load_to_general_reg(src2); ASM::add_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); - Ok(()) } Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { - let dst_reg = self.claim_float_reg(dst)?; - let src1_reg = self.load_to_float_reg(src1)?; - let src2_reg = self.load_to_float_reg(src2)?; + let dst_reg = self.claim_float_reg(dst); + let src1_reg = self.load_to_float_reg(src1); + let src2_reg = self.load_to_float_reg(src2); ASM::add_freg64_freg64_freg64(&mut self.buf, dst_reg, src1_reg, src2_reg); - Ok(()) } - x => Err(format!("NumAdd: layout, {:?}, not implemented yet", x)), + x => unimplemented!("NumAdd: layout, {:?}, not implemented yet", x), } } - fn build_num_mul( - &mut self, - dst: &Symbol, - src1: &Symbol, - src2: &Symbol, - layout: &Layout<'a>, - ) -> Result<(), String> { + fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>) { match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst)?; - let src1_reg = self.load_to_general_reg(src1)?; - let src2_reg = self.load_to_general_reg(src2)?; + let dst_reg = self.claim_general_reg(dst); + let src1_reg = self.load_to_general_reg(src1); + let src2_reg = self.load_to_general_reg(src2); ASM::imul_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); - Ok(()) } - x => Err(format!("NumMul: layout, {:?}, not implemented yet", x)), + x => unimplemented!("NumMul: layout, {:?}, not implemented yet", x), } } - fn build_num_neg( - &mut self, - dst: &Symbol, - src: &Symbol, - layout: &Layout<'a>, - ) -> Result<(), String> { + fn build_num_neg(&mut self, dst: &Symbol, src: &Symbol, layout: &Layout<'a>) { match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst)?; - let src_reg = self.load_to_general_reg(src)?; + let dst_reg = self.claim_general_reg(dst); + let src_reg = self.load_to_general_reg(src); ASM::neg_reg64_reg64(&mut self.buf, dst_reg, src_reg); - Ok(()) } - x => Err(format!("NumNeg: layout, {:?}, not implemented yet", x)), + x => unimplemented!("NumNeg: layout, {:?}, not implemented yet", x), } } - fn build_num_sub( - &mut self, - dst: &Symbol, - src1: &Symbol, - src2: &Symbol, - layout: &Layout<'a>, - ) -> Result<(), String> { + fn build_num_sub(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>) { match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst)?; - let src1_reg = self.load_to_general_reg(src1)?; - let src2_reg = self.load_to_general_reg(src2)?; + let dst_reg = self.claim_general_reg(dst); + let src1_reg = self.load_to_general_reg(src1); + let src2_reg = self.load_to_general_reg(src2); ASM::sub_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); - Ok(()) } - x => Err(format!("NumSub: layout, {:?}, not implemented yet", x)), + x => unimplemented!("NumSub: layout, {:?}, not implemented yet", x), } } - fn build_eq( - &mut self, - dst: &Symbol, - src1: &Symbol, - src2: &Symbol, - arg_layout: &Layout<'a>, - ) -> Result<(), String> { + fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>) { match arg_layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst)?; - let src1_reg = self.load_to_general_reg(src1)?; - let src2_reg = self.load_to_general_reg(src2)?; + let dst_reg = self.claim_general_reg(dst); + let src1_reg = self.load_to_general_reg(src1); + let src2_reg = self.load_to_general_reg(src2); ASM::eq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); - Ok(()) } - x => Err(format!("NumEq: layout, {:?}, not implemented yet", x)), + x => unimplemented!("NumEq: layout, {:?}, not implemented yet", x), } } - fn build_neq( - &mut self, - dst: &Symbol, - src1: &Symbol, - src2: &Symbol, - arg_layout: &Layout<'a>, - ) -> Result<(), String> { + fn build_neq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>) { match arg_layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst)?; - let src1_reg = self.load_to_general_reg(src1)?; - let src2_reg = self.load_to_general_reg(src2)?; + let dst_reg = self.claim_general_reg(dst); + let src1_reg = self.load_to_general_reg(src1); + let src2_reg = self.load_to_general_reg(src2); ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); - Ok(()) } - x => Err(format!("NumNeq: layout, {:?}, not implemented yet", x)), + x => unimplemented!("NumNeq: layout, {:?}, not implemented yet", x), } } @@ -890,16 +829,15 @@ impl< src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>, - ) -> Result<(), String> { + ) { match arg_layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.claim_general_reg(dst)?; - let src1_reg = self.load_to_general_reg(src1)?; - let src2_reg = self.load_to_general_reg(src2)?; + let dst_reg = self.claim_general_reg(dst); + let src1_reg = self.load_to_general_reg(src1); + let src2_reg = self.load_to_general_reg(src2); ASM::lt_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); - Ok(()) } - x => Err(format!("NumLt: layout, {:?}, not implemented yet", x)), + x => unimplemented!("NumLt: layout, {:?}, not implemented yet", x), } } @@ -909,80 +847,70 @@ impl< src: &Symbol, arg_layout: &Layout<'a>, ret_layout: &Layout<'a>, - ) -> Result<(), String> { + ) { match (arg_layout, ret_layout) { ( Layout::Builtin(Builtin::Int(IntWidth::I32 | IntWidth::I64)), Layout::Builtin(Builtin::Float(FloatWidth::F64)), ) => { - let dst_reg = self.claim_float_reg(dst)?; - let src_reg = self.load_to_general_reg(src)?; + let dst_reg = self.claim_float_reg(dst); + let src_reg = self.load_to_general_reg(src); ASM::to_float_freg64_reg64(&mut self.buf, dst_reg, src_reg); - Ok(()) } ( Layout::Builtin(Builtin::Int(IntWidth::I32 | IntWidth::I64)), Layout::Builtin(Builtin::Float(FloatWidth::F32)), ) => { - let dst_reg = self.claim_float_reg(dst)?; - let src_reg = self.load_to_general_reg(src)?; + let dst_reg = self.claim_float_reg(dst); + let src_reg = self.load_to_general_reg(src); ASM::to_float_freg32_reg64(&mut self.buf, dst_reg, src_reg); - Ok(()) } ( Layout::Builtin(Builtin::Float(FloatWidth::F64)), Layout::Builtin(Builtin::Float(FloatWidth::F32)), ) => { - let dst_reg = self.claim_float_reg(dst)?; - let src_reg = self.load_to_float_reg(src)?; + let dst_reg = self.claim_float_reg(dst); + let src_reg = self.load_to_float_reg(src); ASM::to_float_freg32_freg64(&mut self.buf, dst_reg, src_reg); - Ok(()) } ( Layout::Builtin(Builtin::Float(FloatWidth::F32)), Layout::Builtin(Builtin::Float(FloatWidth::F64)), ) => { - let dst_reg = self.claim_float_reg(dst)?; - let src_reg = self.load_to_float_reg(src)?; + let dst_reg = self.claim_float_reg(dst); + let src_reg = self.load_to_float_reg(src); ASM::to_float_freg64_freg32(&mut self.buf, dst_reg, src_reg); - Ok(()) } ( Layout::Builtin(Builtin::Float(FloatWidth::F64)), Layout::Builtin(Builtin::Float(FloatWidth::F64)), ) => { - let dst_reg = self.claim_float_reg(dst)?; - let src_reg = self.load_to_float_reg(src)?; + let dst_reg = self.claim_float_reg(dst); + let src_reg = self.load_to_float_reg(src); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, src_reg); - Ok(()) } ( Layout::Builtin(Builtin::Float(FloatWidth::F32)), Layout::Builtin(Builtin::Float(FloatWidth::F32)), ) => { - let dst_reg = self.claim_float_reg(dst)?; - let src_reg = self.load_to_float_reg(src)?; + let dst_reg = self.claim_float_reg(dst); + let src_reg = self.load_to_float_reg(src); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, src_reg); - Ok(()) } - (a, r) => Err(format!( + (a, r) => unimplemented!( "NumToFloat: layout, arg {:?}, ret {:?}, not implemented yet", - a, r - )), + a, + r + ), } } - fn create_struct( - &mut self, - sym: &Symbol, - layout: &Layout<'a>, - fields: &'a [Symbol], - ) -> Result<(), String> { + fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) { let struct_size = layout.stack_size(PTR_SIZE); if let Layout::Struct(field_layouts) = layout { if struct_size > 0 { - let offset = self.claim_stack_size(struct_size)?; + let offset = self.claim_stack_size(struct_size); self.symbol_storage_map.insert( *sym, SymbolStorage::Base { @@ -994,7 +922,7 @@ impl< let mut current_offset = offset; for (field, field_layout) in fields.iter().zip(field_layouts.iter()) { - self.copy_symbol_to_stack_offset(current_offset, field, field_layout)?; + self.copy_symbol_to_stack_offset(current_offset, field, field_layout); let field_size = field_layout.stack_size(PTR_SIZE); current_offset += field_size as i32; } @@ -1008,10 +936,9 @@ impl< }, ); } - Ok(()) } else { // This is a single element struct. Just copy the single field to the stack. - let offset = self.claim_stack_size(struct_size)?; + let offset = self.claim_stack_size(struct_size); self.symbol_storage_map.insert( *sym, SymbolStorage::Base { @@ -1020,8 +947,7 @@ impl< owned: true, }, ); - self.copy_symbol_to_stack_offset(offset, &fields[0], layout)?; - Ok(()) + self.copy_symbol_to_stack_offset(offset, &fields[0], layout); } } @@ -1031,7 +957,7 @@ impl< structure: &Symbol, index: u64, field_layouts: &'a [Layout<'a>], - ) -> Result<(), String> { + ) { if let Some(SymbolStorage::Base { offset, .. }) = self.symbol_storage_map.get(structure) { let mut data_offset = *offset; for i in 0..index { @@ -1046,31 +972,28 @@ impl< owned: false, }, ); - Ok(()) } else { - Err(format!("unknown struct: {:?}", structure)) + internal_error!("unknown struct: {:?}", structure); } } - fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String> { + fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) { match lit { Literal::Int(x) => { - let reg = self.claim_general_reg(sym)?; + let reg = self.claim_general_reg(sym); let val = *x; ASM::mov_reg64_imm64(&mut self.buf, reg, val as i64); - Ok(()) } Literal::Float(x) => { - let reg = self.claim_float_reg(sym)?; + let reg = self.claim_float_reg(sym); let val = *x; ASM::mov_freg64_imm64(&mut self.buf, &mut self.relocs, reg, val); - Ok(()) } Literal::Str(x) if x.len() < 16 => { // Load small string. - let reg = self.get_tmp_general_reg()?; + let reg = self.get_tmp_general_reg(); - let offset = self.claim_stack_size(16)?; + let offset = self.claim_stack_size(16); self.symbol_storage_map.insert( *sym, SymbolStorage::Base { @@ -1093,13 +1016,12 @@ impl< let num = i64::from_ne_bytes(num_bytes); ASM::mov_reg64_imm64(&mut self.buf, reg, num); ASM::mov_base32_reg64(&mut self.buf, offset + 8, reg); - Ok(()) } - x => Err(format!("loading literal, {:?}, is not yet implemented", x)), + x => unimplemented!("loading literal, {:?}, is not yet implemented", x), } } - fn free_symbol(&mut self, sym: &Symbol) -> Result<(), String> { + fn free_symbol(&mut self, sym: &Symbol) { match self.symbol_storage_map.remove(sym) { Some( SymbolStorage::Base { @@ -1133,7 +1055,7 @@ impl< if let Some((prev_offset, prev_size)) = self.free_stack_chunks.get(pos - 1) { let prev_end = *prev_offset + *prev_size as i32; if prev_end > offset { - return Err("Double free? A previously freed stack location overlaps with the currently freed stack location.".to_string()); + internal_error!("Double free? A previously freed stack location overlaps with the currently freed stack location."); } prev_end == offset } else { @@ -1147,7 +1069,7 @@ impl< { let current_end = offset + size as i32; if current_end > *next_offset { - return Err("Double free? A previously freed stack location overlaps with the currently freed stack location.".to_string()); + internal_error!("Double free? A previously freed stack location overlaps with the currently freed stack location."); } current_end == *next_offset } else { @@ -1191,10 +1113,9 @@ impl< break; } } - Ok(()) } - fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String> { + fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) { let val = self.symbol_storage_map.get(sym); match val { Some(SymbolStorage::GeneralReg(reg)) if *reg == CC::GENERAL_RETURN_REGS[0] => {} @@ -1217,7 +1138,7 @@ impl< Layout::Builtin(Builtin::Str) => { if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) { // This will happen on windows, return via pointer here. - return Err("Returning strings via pointer not yet implemented".to_string()); + unimplemented!("Returning strings via pointer not yet implemented"); } else { ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset); ASM::mov_reg64_base32( @@ -1233,28 +1154,21 @@ impl< if size > 0 { let ret_reg = if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) { - Some(self.load_to_general_reg(&Symbol::RET_POINTER)?) + Some(self.load_to_general_reg(&Symbol::RET_POINTER)) } else { None }; - CC::return_struct(&mut self.buf, offset, size, field_layouts, ret_reg)?; + CC::return_struct(&mut self.buf, offset, size, field_layouts, ret_reg); } } - x => { - return Err(format!( - "returning symbol with layout, {:?}, is not yet implemented", - x - )); - } - }, - Some(x) => { - return Err(format!( - "returning symbol storage, {:?}, is not yet implemented", + x => unimplemented!( + "returning symbol with layout, {:?}, is not yet implemented", x - )); - } + ), + }, + Some(x) => unimplemented!("returning symbol storage, {:?}, is not yet implemented", x), None => { - return Err(format!("Unknown return symbol: {}", sym)); + internal_error!("Unknown return symbol: {}", sym); } } let inst_loc = self.buf.len() as u64; @@ -1264,7 +1178,6 @@ impl< inst_size: self.buf.len() as u64 - inst_loc, offset, }); - Ok(()) } } @@ -1278,7 +1191,7 @@ impl< CC: CallConv, > Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC> { - fn get_tmp_general_reg(&mut self) -> Result { + fn get_tmp_general_reg(&mut self) -> GeneralReg { if !self.general_free_regs.is_empty() { let free_reg = *self .general_free_regs @@ -1287,72 +1200,72 @@ impl< if CC::general_callee_saved(&free_reg) { self.general_used_callee_saved_regs.insert(free_reg); } - Ok(free_reg) + free_reg } else if !self.general_used_regs.is_empty() { let (reg, sym) = self.general_used_regs.remove(0); - self.free_to_stack(&sym)?; - Ok(reg) + self.free_to_stack(&sym); + reg } else { - Err("completely out of general purpose registers".to_string()) + internal_error!("completely out of general purpose registers"); } } - fn claim_general_reg(&mut self, sym: &Symbol) -> Result { + fn claim_general_reg(&mut self, sym: &Symbol) -> GeneralReg { let reg = if !self.general_free_regs.is_empty() { let free_reg = self.general_free_regs.pop().unwrap(); if CC::general_callee_saved(&free_reg) { self.general_used_callee_saved_regs.insert(free_reg); } - Ok(free_reg) + free_reg } else if !self.general_used_regs.is_empty() { let (reg, sym) = self.general_used_regs.remove(0); - self.free_to_stack(&sym)?; - Ok(reg) + self.free_to_stack(&sym); + reg } else { - Err("completely out of general purpose registers".to_string()) - }?; + internal_error!("completely out of general purpose registers"); + }; self.general_used_regs.push((reg, *sym)); self.symbol_storage_map .insert(*sym, SymbolStorage::GeneralReg(reg)); - Ok(reg) + reg } - fn claim_float_reg(&mut self, sym: &Symbol) -> Result { + fn claim_float_reg(&mut self, sym: &Symbol) -> FloatReg { let reg = if !self.float_free_regs.is_empty() { let free_reg = self.float_free_regs.pop().unwrap(); if CC::float_callee_saved(&free_reg) { self.float_used_callee_saved_regs.insert(free_reg); } - Ok(free_reg) + free_reg } else if !self.float_used_regs.is_empty() { let (reg, sym) = self.float_used_regs.remove(0); - self.free_to_stack(&sym)?; - Ok(reg) + self.free_to_stack(&sym); + reg } else { - Err("completely out of floating point registers".to_string()) - }?; + internal_error!("completely out of floating point registers"); + }; self.float_used_regs.push((reg, *sym)); self.symbol_storage_map .insert(*sym, SymbolStorage::FloatReg(reg)); - Ok(reg) + reg } - fn load_to_general_reg(&mut self, sym: &Symbol) -> Result { + fn load_to_general_reg(&mut self, sym: &Symbol) -> GeneralReg { let val = self.symbol_storage_map.remove(sym); match val { Some(SymbolStorage::GeneralReg(reg)) => { self.symbol_storage_map .insert(*sym, SymbolStorage::GeneralReg(reg)); - Ok(reg) + reg } Some(SymbolStorage::Base { offset, size, owned, }) => { - let reg = self.claim_general_reg(sym)?; + let reg = self.claim_general_reg(sym); self.symbol_storage_map.insert( *sym, SymbolStorage::BaseAndGeneralReg { @@ -1363,7 +1276,7 @@ impl< }, ); ASM::mov_reg64_base32(&mut self.buf, reg, offset as i32); - Ok(reg) + reg } Some(SymbolStorage::BaseAndGeneralReg { reg, @@ -1380,29 +1293,29 @@ impl< owned, }, ); - Ok(reg) + reg } Some(SymbolStorage::FloatReg(_)) | Some(SymbolStorage::BaseAndFloatReg { .. }) => { - Err("Cannot load floating point symbol into GeneralReg".to_string()) + internal_error!("Cannot load floating point symbol into GeneralReg") } - None => Err(format!("Unknown symbol: {}", sym)), + None => internal_error!("Unknown symbol: {}", sym), } } - fn load_to_float_reg(&mut self, sym: &Symbol) -> Result { + fn load_to_float_reg(&mut self, sym: &Symbol) -> FloatReg { let val = self.symbol_storage_map.remove(sym); match val { Some(SymbolStorage::FloatReg(reg)) => { self.symbol_storage_map .insert(*sym, SymbolStorage::FloatReg(reg)); - Ok(reg) + reg } Some(SymbolStorage::Base { offset, size, owned, }) => { - let reg = self.claim_float_reg(sym)?; + let reg = self.claim_float_reg(sym); self.symbol_storage_map.insert( *sym, SymbolStorage::BaseAndFloatReg { @@ -1413,7 +1326,7 @@ impl< }, ); ASM::mov_freg64_base32(&mut self.buf, reg, offset as i32); - Ok(reg) + reg } Some(SymbolStorage::BaseAndFloatReg { reg, @@ -1430,20 +1343,20 @@ impl< owned, }, ); - Ok(reg) + reg } Some(SymbolStorage::GeneralReg(_)) | Some(SymbolStorage::BaseAndGeneralReg { .. }) => { - Err("Cannot load integer symbol into FloatReg".to_string()) + internal_error!("Cannot load integer symbol into FloatReg") } - None => Err(format!("Unknown symbol: {}", sym)), + None => internal_error!("Unknown symbol: {}", sym), } } - fn free_to_stack(&mut self, sym: &Symbol) -> Result<(), String> { + fn free_to_stack(&mut self, sym: &Symbol) { let val = self.symbol_storage_map.remove(sym); match val { Some(SymbolStorage::GeneralReg(reg)) => { - let offset = self.claim_stack_size(8)?; + let offset = self.claim_stack_size(8); // For base addressing, use the negative offset - 8. ASM::mov_base32_reg64(&mut self.buf, offset, reg); self.symbol_storage_map.insert( @@ -1454,10 +1367,9 @@ impl< owned: true, }, ); - Ok(()) } Some(SymbolStorage::FloatReg(reg)) => { - let offset = self.claim_stack_size(8)?; + let offset = self.claim_stack_size(8); // For base addressing, use the negative offset. ASM::mov_base32_freg64(&mut self.buf, offset, reg); self.symbol_storage_map.insert( @@ -1468,7 +1380,6 @@ impl< owned: true, }, ); - Ok(()) } Some(SymbolStorage::Base { offset, @@ -1483,7 +1394,6 @@ impl< owned, }, ); - Ok(()) } Some(SymbolStorage::BaseAndGeneralReg { offset, @@ -1499,7 +1409,6 @@ impl< owned, }, ); - Ok(()) } Some(SymbolStorage::BaseAndFloatReg { offset, @@ -1515,16 +1424,15 @@ impl< owned, }, ); - Ok(()) } - None => Err(format!("Unknown symbol: {}", sym)), + None => internal_error!("Unknown symbol: {}", sym), } } /// claim_stack_size claims `amount` bytes from the stack. /// 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. - fn claim_stack_size(&mut self, amount: u32) -> Result { + fn claim_stack_size(&mut self, amount: u32) -> i32 { debug_assert!(amount > 0); if let Some(fitting_chunk) = self .free_stack_chunks @@ -1537,45 +1445,37 @@ impl< let (offset, size) = (*offset, *size); if size == amount { self.free_stack_chunks.remove(pos); - Ok(offset) + offset } else { let (prev_offset, prev_size) = self.free_stack_chunks[pos]; self.free_stack_chunks[pos] = (prev_offset + amount as i32, prev_size - amount); - Ok(prev_offset) + prev_offset } } else if let Some(new_size) = self.stack_size.checked_add(amount) { // Since stack size is u32, but the max offset is i32, if we pass i32 max, we have overflowed. if new_size > i32::MAX as u32 { - Err("Ran out of stack space".to_string()) + internal_error!("Ran out of stack space"); } else { self.stack_size = new_size; - let offset = -(self.stack_size as i32); - Ok(offset) + -(self.stack_size as i32) } } else { - Err("Ran out of stack space".to_string()) + internal_error!("Ran out of stack space"); } } - fn copy_symbol_to_stack_offset( - &mut self, - to_offset: i32, - sym: &Symbol, - layout: &Layout<'a>, - ) -> Result<(), String> { + fn copy_symbol_to_stack_offset(&mut self, to_offset: i32, sym: &Symbol, layout: &Layout<'a>) { match layout { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let reg = self.load_to_general_reg(sym)?; + let reg = self.load_to_general_reg(sym); ASM::mov_base32_reg64(&mut self.buf, to_offset, reg); - Ok(()) } Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { - let reg = self.load_to_float_reg(sym)?; + let reg = self.load_to_float_reg(sym); ASM::mov_base32_freg64(&mut self.buf, to_offset, reg); - Ok(()) } Layout::Struct(_) if layout.safe_to_memcpy() => { - let tmp_reg = self.get_tmp_general_reg()?; + let tmp_reg = self.get_tmp_general_reg(); if let Some(SymbolStorage::Base { offset: from_offset, size, @@ -1591,19 +1491,18 @@ impl< ASM::mov_reg64_base32(&mut self.buf, tmp_reg, from_offset + i); ASM::mov_base32_reg64(&mut self.buf, to_offset + i, tmp_reg); } - Ok(()) } else { - Err(format!("unknown struct: {:?}", sym)) + internal_error!("unknown struct: {:?}", sym); } } - x => Err(format!( + x => unimplemented!( "copying data to the stack with layout, {:?}, not implemented yet", x - )), + ), } } - fn push_used_caller_saved_regs_to_stack(&mut self) -> Result<(), String> { + fn push_used_caller_saved_regs_to_stack(&mut self) { let old_general_used_regs = std::mem::replace( &mut self.general_used_regs, bumpalo::vec![in self.env.arena], @@ -1611,7 +1510,7 @@ impl< for (reg, saved_sym) in old_general_used_regs.into_iter() { if CC::general_caller_saved(®) { self.general_free_regs.push(reg); - self.free_to_stack(&saved_sym)?; + self.free_to_stack(&saved_sym); } else { self.general_used_regs.push((reg, saved_sym)); } @@ -1621,12 +1520,11 @@ impl< for (reg, saved_sym) in old_float_used_regs.into_iter() { if CC::float_caller_saved(®) { self.float_free_regs.push(reg); - self.free_to_stack(&saved_sym)?; + self.free_to_stack(&saved_sym); } else { self.float_used_regs.push((reg, saved_sym)); } } - Ok(()) } // Updates a jump instruction to a new offset and returns the number of bytes written. diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 39e487effa..0b27d369c8 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -7,6 +7,7 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::MutMap; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout}; +use roc_reporting::internal_error; // Not sure exactly how I want to represent registers. // If we want max speed, we would likely make them structs that impl the same trait to avoid ifs. @@ -152,7 +153,7 @@ impl CallConv for X86_64SystemV { general_saved_regs: &[X86_64GeneralReg], requested_stack_size: i32, fn_call_stack_size: i32, - ) -> Result { + ) -> i32 { x86_64_generic_setup_stack( buf, general_saved_regs, @@ -167,7 +168,7 @@ impl CallConv for X86_64SystemV { general_saved_regs: &[X86_64GeneralReg], aligned_stack_size: i32, fn_call_stack_size: i32, - ) -> Result<(), String> { + ) { x86_64_generic_cleanup_stack( buf, general_saved_regs, @@ -182,11 +183,11 @@ impl CallConv for X86_64SystemV { symbol_map: &mut MutMap>, args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>, - ) -> Result<(), String> { + ) { let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer. let mut general_i = 0; let mut float_i = 0; - if X86_64SystemV::returns_via_arg_pointer(ret_layout)? { + if X86_64SystemV::returns_via_arg_pointer(ret_layout) { symbol_map.insert( Symbol::RET_POINTER, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[general_i]), @@ -251,21 +252,15 @@ impl CallConv for X86_64SystemV { ); general_i += 2; } else { - return Err( - "loading strings args on the stack is not yet implemented".to_string() - ); + unimplemented!("loading strings args on the stack is not yet implemented"); } } Layout::Struct(&[]) => {} x => { - return Err(format!( - "Loading args with layout {:?} not yet implemented", - x - )); + unimplemented!("Loading args with layout {:?} not yet implemented", x); } } } - Ok(()) } #[inline(always)] @@ -275,7 +270,7 @@ impl CallConv for X86_64SystemV { args: &'a [Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, - ) -> Result { + ) -> u32 { let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32; let mut general_i = 0; let mut float_i = 0; @@ -284,22 +279,22 @@ impl CallConv for X86_64SystemV { match ret_layout { Layout::Builtin(single_register_builtins!() | Builtin::Str) => {} x => { - return Err(format!( - "receiving return type, {:?}, is not yet implemented", - x - )); + unimplemented!("receiving return type, {:?}, is not yet implemented", x); } } for (i, layout) in arg_layouts.iter().enumerate() { match layout { Layout::Builtin(single_register_integers!()) => { + let storage = match symbol_map.get(&args[i]) { + Some(storage) => storage, + None => { + internal_error!("function argument does not reference any symbol") + } + }; if general_i < Self::GENERAL_PARAM_REGS.len() { // Load the value to the param reg. let dst = Self::GENERAL_PARAM_REGS[general_i]; - match symbol_map - .get(&args[i]) - .ok_or("function argument does not reference any symbol")? - { + match storage { SymbolStorage::GeneralReg(reg) | SymbolStorage::BaseAndGeneralReg { reg, .. } => { X86_64Assembler::mov_reg64_reg64(buf, dst, *reg); @@ -308,18 +303,13 @@ impl CallConv for X86_64SystemV { X86_64Assembler::mov_reg64_base32(buf, dst, *offset); } SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => { - return Err( - "Cannot load floating point symbol into GeneralReg".to_string() - ) + internal_error!("Cannot load floating point symbol into GeneralReg") } } general_i += 1; } else { // Load the value to the stack. - match symbol_map - .get(&args[i]) - .ok_or("function argument does not reference any symbol")? - { + match storage { SymbolStorage::GeneralReg(reg) | SymbolStorage::BaseAndGeneralReg { reg, .. } => { X86_64Assembler::mov_stack32_reg64(buf, stack_offset, *reg); @@ -338,22 +328,23 @@ impl CallConv for X86_64SystemV { ); } SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => { - return Err( - "Cannot load floating point symbol into GeneralReg".to_string() - ) + internal_error!("Cannot load floating point symbol into GeneralReg") } } stack_offset += 8; } } Layout::Builtin(single_register_floats!()) => { + let storage = match symbol_map.get(&args[i]) { + Some(storage) => storage, + None => { + internal_error!("function argument does not reference any symbol") + } + }; if float_i < Self::FLOAT_PARAM_REGS.len() { // Load the value to the param reg. let dst = Self::FLOAT_PARAM_REGS[float_i]; - match symbol_map - .get(&args[i]) - .ok_or("function argument does not reference any symbol")? - { + match storage { SymbolStorage::FloatReg(reg) | SymbolStorage::BaseAndFloatReg { reg, .. } => { X86_64Assembler::mov_freg64_freg64(buf, dst, *reg); @@ -363,16 +354,13 @@ impl CallConv for X86_64SystemV { } SymbolStorage::GeneralReg(_) | SymbolStorage::BaseAndGeneralReg { .. } => { - return Err("Cannot load general symbol into FloatReg".to_string()) + internal_error!("Cannot load general symbol into FloatReg") } } float_i += 1; } else { // Load the value to the stack. - match symbol_map - .get(&args[i]) - .ok_or("function argument does not reference any symbol")? - { + match storage { SymbolStorage::FloatReg(reg) | SymbolStorage::BaseAndFloatReg { reg, .. } => { X86_64Assembler::mov_stack32_freg64(buf, stack_offset, *reg); @@ -392,48 +380,48 @@ impl CallConv for X86_64SystemV { } SymbolStorage::GeneralReg(_) | SymbolStorage::BaseAndGeneralReg { .. } => { - return Err("Cannot load general symbol into FloatReg".to_string()) + internal_error!("Cannot load general symbol into FloatReg") } } stack_offset += 8; } } Layout::Builtin(Builtin::Str) => { + let storage = match symbol_map.get(&args[i]) { + Some(storage) => storage, + None => { + internal_error!("function argument does not reference any symbol") + } + }; if general_i + 1 < Self::GENERAL_PARAM_REGS.len() { // Load the value to the param reg. let dst1 = Self::GENERAL_PARAM_REGS[general_i]; let dst2 = Self::GENERAL_PARAM_REGS[general_i + 1]; - match symbol_map - .get(&args[i]) - .ok_or("function argument does not reference any symbol")? - { + match storage { SymbolStorage::Base { offset, .. } => { X86_64Assembler::mov_reg64_base32(buf, dst1, *offset); X86_64Assembler::mov_reg64_base32(buf, dst2, *offset + 8); } _ => { - return Err("Strings only support being loaded from base offsets" - .to_string()); + internal_error!( + "Strings only support being loaded from base offsets" + ); } } general_i += 2; } else { - return Err( + unimplemented!( "calling functions with strings on the stack is not yet implemented" - .to_string(), ); } } Layout::Struct(&[]) => {} x => { - return Err(format!( - "calling with arg type, {:?}, is not yet implemented", - x - )); + unimplemented!("calling with arg type, {:?}, is not yet implemented", x); } } } - Ok(stack_offset as u32) + stack_offset as u32 } fn return_struct<'a>( @@ -442,14 +430,14 @@ impl CallConv for X86_64SystemV { _struct_size: u32, _field_layouts: &[Layout<'a>], _ret_reg: Option, - ) -> Result<(), String> { - Err("Returning structs not yet implemented for X86_64".to_string()) + ) { + unimplemented!("Returning structs not yet implemented for X86_64"); } - fn returns_via_arg_pointer(ret_layout: &Layout) -> Result { + fn returns_via_arg_pointer(ret_layout: &Layout) -> bool { // TODO: This may need to be more complex/extended to fully support the calling convention. // details here: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf - Ok(ret_layout.stack_size(PTR_SIZE) > 16) + ret_layout.stack_size(PTR_SIZE) > 16 } } @@ -551,7 +539,7 @@ impl CallConv for X86_64WindowsFastcall { saved_regs: &[X86_64GeneralReg], requested_stack_size: i32, fn_call_stack_size: i32, - ) -> Result { + ) -> i32 { x86_64_generic_setup_stack(buf, saved_regs, requested_stack_size, fn_call_stack_size) } @@ -561,7 +549,7 @@ impl CallConv for X86_64WindowsFastcall { saved_regs: &[X86_64GeneralReg], aligned_stack_size: i32, fn_call_stack_size: i32, - ) -> Result<(), String> { + ) { x86_64_generic_cleanup_stack(buf, saved_regs, aligned_stack_size, fn_call_stack_size) } @@ -571,10 +559,10 @@ impl CallConv for X86_64WindowsFastcall { symbol_map: &mut MutMap>, args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>, - ) -> Result<(), String> { + ) { let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer. let mut i = 0; - if X86_64WindowsFastcall::returns_via_arg_pointer(ret_layout)? { + if X86_64WindowsFastcall::returns_via_arg_pointer(ret_layout) { symbol_map.insert( Symbol::RET_POINTER, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]), @@ -595,27 +583,20 @@ impl CallConv for X86_64WindowsFastcall { } Layout::Builtin(Builtin::Str) => { // I think this just needs to be passed on the stack, so not a huge deal. - return Err( + unimplemented!( "Passing str args with Windows fast call not yet implemented." - .to_string(), ); } Layout::Struct(&[]) => {} x => { - return Err(format!( - "Loading args with layout {:?} not yet implemented", - x - )); + unimplemented!("Loading args with layout {:?} not yet implemented", x); } } } else { base_offset += match layout { Layout::Builtin(single_register_builtins!()) => 8, x => { - return Err(format!( - "Loading args with layout {:?} not yet implemented", - x - )); + unimplemented!("Loading args with layout {:?} not yet implemented", x); } }; symbol_map.insert( @@ -628,7 +609,6 @@ impl CallConv for X86_64WindowsFastcall { ); } } - Ok(()) } #[inline(always)] @@ -638,29 +618,29 @@ impl CallConv for X86_64WindowsFastcall { args: &'a [Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, - ) -> Result { + ) -> u32 { let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32; // For most return layouts we will do nothing. // In some cases, we need to put the return address as the first arg. match ret_layout { Layout::Builtin(single_register_builtins!()) => {} x => { - return Err(format!( - "receiving return type, {:?}, is not yet implemented", - x - )); + unimplemented!("receiving return type, {:?}, is not yet implemented", x); } } for (i, layout) in arg_layouts.iter().enumerate() { match layout { Layout::Builtin(single_register_integers!()) => { + let storage = match symbol_map.get(&args[i]) { + Some(storage) => storage, + None => { + internal_error!("function argument does not reference any symbol") + } + }; if i < Self::GENERAL_PARAM_REGS.len() { // Load the value to the param reg. let dst = Self::GENERAL_PARAM_REGS[i]; - match symbol_map - .get(&args[i]) - .ok_or("function argument does not reference any symbol")? - { + match storage { SymbolStorage::GeneralReg(reg) | SymbolStorage::BaseAndGeneralReg { reg, .. } => { X86_64Assembler::mov_reg64_reg64(buf, dst, *reg); @@ -669,17 +649,12 @@ impl CallConv for X86_64WindowsFastcall { X86_64Assembler::mov_reg64_base32(buf, dst, *offset); } SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => { - return Err( - "Cannot load floating point symbol into GeneralReg".to_string() - ) + internal_error!("Cannot load floating point symbol into GeneralReg") } } } else { // Load the value to the stack. - match symbol_map - .get(&args[i]) - .ok_or("function argument does not reference any symbol")? - { + match storage { SymbolStorage::GeneralReg(reg) | SymbolStorage::BaseAndGeneralReg { reg, .. } => { X86_64Assembler::mov_stack32_reg64(buf, stack_offset, *reg); @@ -698,22 +673,23 @@ impl CallConv for X86_64WindowsFastcall { ); } SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => { - return Err( - "Cannot load floating point symbol into GeneralReg".to_string() - ) + internal_error!("Cannot load floating point symbol into GeneralReg") } } stack_offset += 8; } } Layout::Builtin(single_register_floats!()) => { + let storage = match symbol_map.get(&args[i]) { + Some(storage) => storage, + None => { + internal_error!("function argument does not reference any symbol") + } + }; if i < Self::FLOAT_PARAM_REGS.len() { // Load the value to the param reg. let dst = Self::FLOAT_PARAM_REGS[i]; - match symbol_map - .get(&args[i]) - .ok_or("function argument does not reference any symbol")? - { + match storage { SymbolStorage::FloatReg(reg) | SymbolStorage::BaseAndFloatReg { reg, .. } => { X86_64Assembler::mov_freg64_freg64(buf, dst, *reg); @@ -723,15 +699,12 @@ impl CallConv for X86_64WindowsFastcall { } SymbolStorage::GeneralReg(_) | SymbolStorage::BaseAndGeneralReg { .. } => { - return Err("Cannot load general symbol into FloatReg".to_string()) + unimplemented!("Cannot load general symbol into FloatReg") } } } else { // Load the value to the stack. - match symbol_map - .get(&args[i]) - .ok_or("function argument does not reference any symbol")? - { + match storage { SymbolStorage::FloatReg(reg) | SymbolStorage::BaseAndFloatReg { reg, .. } => { X86_64Assembler::mov_stack32_freg64(buf, stack_offset, *reg); @@ -751,7 +724,7 @@ impl CallConv for X86_64WindowsFastcall { } SymbolStorage::GeneralReg(_) | SymbolStorage::BaseAndGeneralReg { .. } => { - return Err("Cannot load general symbol into FloatReg".to_string()) + unimplemented!("Cannot load general symbol into FloatReg") } } stack_offset += 8; @@ -759,20 +732,15 @@ impl CallConv for X86_64WindowsFastcall { } Layout::Builtin(Builtin::Str) => { // I think this just needs to be passed on the stack, so not a huge deal. - return Err( - "Passing str args with Windows fast call not yet implemented.".to_string(), - ); + unimplemented!("Passing str args with Windows fast call not yet implemented."); } Layout::Struct(&[]) => {} x => { - return Err(format!( - "calling with arg type, {:?}, is not yet implemented", - x - )); + unimplemented!("calling with arg type, {:?}, is not yet implemented", x); } } } - Ok(stack_offset as u32) + stack_offset as u32 } fn return_struct<'a>( @@ -781,14 +749,14 @@ impl CallConv for X86_64WindowsFastcall { _struct_size: u32, _field_layouts: &[Layout<'a>], _ret_reg: Option, - ) -> Result<(), String> { - Err("Returning structs not yet implemented for X86_64WindowsFastCall".to_string()) + ) { + unimplemented!("Returning structs not yet implemented for X86_64WindowsFastCall"); } - fn returns_via_arg_pointer(ret_layout: &Layout) -> Result { + fn returns_via_arg_pointer(ret_layout: &Layout) -> bool { // TODO: This is not fully correct there are some exceptions for "vector" types. // details here: https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-160#return-values - Ok(ret_layout.stack_size(PTR_SIZE) > 8) + ret_layout.stack_size(PTR_SIZE) > 8 } } @@ -798,15 +766,17 @@ fn x86_64_generic_setup_stack<'a>( saved_regs: &[X86_64GeneralReg], requested_stack_size: i32, fn_call_stack_size: i32, -) -> Result { +) -> i32 { X86_64Assembler::push_reg64(buf, X86_64GeneralReg::RBP); X86_64Assembler::mov_reg64_reg64(buf, X86_64GeneralReg::RBP, X86_64GeneralReg::RSP); - let full_stack_size = requested_stack_size + let full_stack_size = match requested_stack_size .checked_add(8 * saved_regs.len() as i32) - .ok_or("Ran out of stack space")? - .checked_add(fn_call_stack_size) - .ok_or("Ran out of stack space")?; + .and_then(|size| size.checked_add(fn_call_stack_size)) + { + Some(size) => size, + _ => internal_error!("Ran out of stack space"), + }; let alignment = if full_stack_size <= 0 { 0 } else { @@ -832,12 +802,12 @@ fn x86_64_generic_setup_stack<'a>( X86_64Assembler::mov_base32_reg64(buf, -offset, *reg); offset -= 8; } - Ok(aligned_stack_size) + aligned_stack_size } else { - Ok(0) + 0 } } else { - Err("Ran out of stack space".to_string()) + internal_error!("Ran out of stack space"); } } @@ -848,7 +818,7 @@ fn x86_64_generic_cleanup_stack<'a>( saved_regs: &[X86_64GeneralReg], aligned_stack_size: i32, fn_call_stack_size: i32, -) -> Result<(), String> { +) { if aligned_stack_size > 0 { let mut offset = aligned_stack_size - fn_call_stack_size; for reg in saved_regs { @@ -864,7 +834,6 @@ fn x86_64_generic_cleanup_stack<'a>( } //X86_64Assembler::mov_reg64_reg64(buf, X86_64GeneralReg::RSP, X86_64GeneralReg::RBP); X86_64Assembler::pop_reg64(buf, X86_64GeneralReg::RBP); - Ok(()) } impl Assembler for X86_64Assembler { diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 5cd60e67e4..247ded86fd 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -13,6 +13,7 @@ use roc_mono::ir::{ SelfRecursive, Stmt, }; use roc_mono::layout::{Builtin, Layout, LayoutIds}; +use roc_reporting::internal_error; mod generic64; mod object_builder; @@ -58,7 +59,7 @@ where Self: Sized, { /// new creates a new backend that will output to the specific Object. - fn new(env: &'a Env) -> Result; + fn new(env: &'a Env) -> Self; fn env(&self) -> &'a Env<'a>; @@ -70,55 +71,48 @@ where /// finalize does setup because things like stack size and jump locations are not know until the function is written. /// For example, this can store the frame pointer and setup stack space. /// finalize is run at the end of build_proc when all internal code is finalized. - fn finalize(&mut self) -> Result<(&'a [u8], &[Relocation]), String>; + fn finalize(&mut self) -> (&'a [u8], &[Relocation]); // load_args is used to let the backend know what the args are. // The backend should track these args so it can use them as needed. - fn load_args( - &mut self, - args: &'a [(Layout<'a>, Symbol)], - ret_layout: &Layout<'a>, - ) -> Result<(), String>; + fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>); /// Used for generating wrappers for malloc/realloc/free - fn build_wrapped_jmp(&mut self) -> Result<(&'a [u8], u64), String>; + fn build_wrapped_jmp(&mut self) -> (&'a [u8], u64); /// build_proc creates a procedure and outputs it to the wrapped object writer. - fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> { + fn build_proc(&mut self, proc: Proc<'a>) -> (&'a [u8], &[Relocation]) { let proc_name = LayoutIds::default() .get(proc.name, &proc.ret_layout) .to_symbol_string(proc.name, &self.env().interns); self.reset(proc_name, proc.is_self_recursive); - self.load_args(proc.args, &proc.ret_layout)?; + self.load_args(proc.args, &proc.ret_layout); for (layout, sym) in proc.args { - self.set_layout_map(*sym, layout)?; + self.set_layout_map(*sym, layout); } self.scan_ast(&proc.body); self.create_free_map(); - self.build_stmt(&proc.body, &proc.ret_layout)?; + self.build_stmt(&proc.body, &proc.ret_layout); self.finalize() } /// build_stmt builds a statement and outputs at the end of the buffer. - fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> { + fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) { match stmt { Stmt::Let(sym, expr, layout, following) => { - self.build_expr(sym, expr, layout)?; - self.set_layout_map(*sym, layout)?; - self.free_symbols(stmt)?; - self.build_stmt(following, ret_layout)?; - Ok(()) + self.build_expr(sym, expr, layout); + self.set_layout_map(*sym, layout); + self.free_symbols(stmt); + self.build_stmt(following, ret_layout); } Stmt::Ret(sym) => { - self.load_literal_symbols(&[*sym])?; - self.return_symbol(sym, ret_layout)?; - self.free_symbols(stmt)?; - Ok(()) + self.load_literal_symbols(&[*sym]); + self.return_symbol(sym, ret_layout); + self.free_symbols(stmt); } Stmt::Refcounting(_modify, following) => { // TODO: actually deal with refcounting. For hello world, we just skipped it. - self.build_stmt(following, ret_layout)?; - Ok(()) + self.build_stmt(following, ret_layout); } Stmt::Switch { cond_symbol, @@ -127,16 +121,15 @@ where default_branch, ret_layout, } => { - self.load_literal_symbols(&[*cond_symbol])?; + self.load_literal_symbols(&[*cond_symbol]); self.build_switch( cond_symbol, cond_layout, branches, default_branch, ret_layout, - )?; - self.free_symbols(stmt)?; - Ok(()) + ); + self.free_symbols(stmt); } Stmt::Join { id, @@ -145,11 +138,10 @@ where remainder, } => { for param in parameters.iter() { - self.set_layout_map(param.symbol, ¶m.layout)?; + self.set_layout_map(param.symbol, ¶m.layout); } - self.build_join(id, parameters, body, remainder, ret_layout)?; - self.free_symbols(stmt)?; - Ok(()) + self.build_join(id, parameters, body, remainder, ret_layout); + self.free_symbols(stmt); } Stmt::Jump(id, args) => { let mut arg_layouts: bumpalo::collections::Vec> = @@ -160,14 +152,13 @@ where if let Some(layout) = layout_map.get(arg) { arg_layouts.push(*layout); } else { - return Err(format!("the argument, {:?}, has no know layout", arg)); + internal_error!("the argument, {:?}, has no know layout", arg); } } - self.build_jump(id, args, arg_layouts.into_bump_slice(), ret_layout)?; - self.free_symbols(stmt)?; - Ok(()) + self.build_jump(id, args, arg_layouts.into_bump_slice(), ret_layout); + self.free_symbols(stmt); } - x => Err(format!("the statement, {:?}, is not yet implemented", x)), + x => unimplemented!("the statement, {:?}, is not yet implemented", x), } } // build_switch generates a instructions for a switch statement. @@ -178,7 +169,7 @@ where branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)], default_branch: &(BranchInfo<'a>, &'a Stmt<'a>), ret_layout: &Layout<'a>, - ) -> Result<(), String>; + ); // build_join generates a instructions for a join statement. fn build_join( @@ -188,7 +179,7 @@ where body: &'a Stmt<'a>, remainder: &'a Stmt<'a>, ret_layout: &Layout<'a>, - ) -> Result<(), String>; + ); // build_jump generates a instructions for a jump statement. fn build_jump( @@ -197,24 +188,18 @@ where args: &'a [Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, - ) -> Result<(), String>; + ); /// build_expr builds the expressions for the specified symbol. /// The builder must keep track of the symbol because it may be referred to later. - fn build_expr( - &mut self, - sym: &Symbol, - expr: &Expr<'a>, - layout: &Layout<'a>, - ) -> Result<(), String> { + fn build_expr(&mut self, sym: &Symbol, expr: &Expr<'a>, layout: &Layout<'a>) { match expr { Expr::Literal(lit) => { if self.env().lazy_literals { self.literal_map().insert(*sym, *lit); } else { - self.load_literal(sym, lit)?; + self.load_literal(sym, lit); } - Ok(()) } Expr::Call(roc_mono::ir::Call { call_type, @@ -244,7 +229,7 @@ where .get(*func_sym, layout) .to_symbol_string(*func_sym, &self.env().interns); // Now that the arguments are needed, load them if they are literals. - self.load_literal_symbols(arguments)?; + self.load_literal_symbols(arguments); self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout) } else { self.build_inline_builtin( @@ -266,7 +251,7 @@ where if let Some(layout) = layout_map.get(arg) { arg_layouts.push(*layout); } else { - return Err(format!("the argument, {:?}, has no know layout", arg)); + internal_error!("the argument, {:?}, has no know layout", arg); } } self.build_run_low_level( @@ -277,19 +262,21 @@ where layout, ) } - x => Err(format!("the call type, {:?}, is not yet implemented", x)), + x => unimplemented!("the call type, {:?}, is not yet implemented", x), } } Expr::Struct(fields) => { - self.load_literal_symbols(fields)?; - self.create_struct(sym, layout, fields) + self.load_literal_symbols(fields); + self.create_struct(sym, layout, fields); } Expr::StructAtIndex { index, field_layouts, structure, - } => self.load_struct_at_index(sym, structure, *index, field_layouts), - x => Err(format!("the expression, {:?}, is not yet implemented", x)), + } => { + self.load_struct_at_index(sym, structure, *index, field_layouts); + } + x => unimplemented!("the expression, {:?}, is not yet implemented", x), } } @@ -302,9 +289,9 @@ where args: &'a [Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, - ) -> Result<(), String> { + ) { // Now that the arguments are needed, load them if they are literals. - self.load_literal_symbols(args)?; + self.load_literal_symbols(args); match lowlevel { LowLevel::NumAbs => { debug_assert_eq!( @@ -480,7 +467,7 @@ where arg_layouts, ret_layout, ), - x => Err(format!("low level, {:?}. is not yet implemented", x)), + x => unimplemented!("low level, {:?}. is not yet implemented", x), } } @@ -492,8 +479,8 @@ where args: &'a [Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, - ) -> Result<(), String> { - self.load_literal_symbols(args)?; + ) { + self.load_literal_symbols(args); match func_sym { Symbol::NUM_IS_ZERO => { debug_assert_eq!( @@ -507,14 +494,11 @@ where "NumIsZero: expected to have return layout of type Bool" ); - self.load_literal(&Symbol::DEV_TMP, &Literal::Int(0))?; - self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0])?; + self.load_literal(&Symbol::DEV_TMP, &Literal::Int(0)); + self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]); self.free_symbol(&Symbol::DEV_TMP) } - _ => Err(format!( - "the function, {:?}, is not yet implemented", - func_sym - )), + _ => unimplemented!("the function, {:?}, is not yet implemented", func_sym), } } @@ -527,77 +511,31 @@ where args: &'a [Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, - ) -> Result<(), String>; + ); /// build_num_abs stores the absolute value of src into dst. - fn build_num_abs( - &mut self, - dst: &Symbol, - src: &Symbol, - layout: &Layout<'a>, - ) -> Result<(), String>; + fn build_num_abs(&mut self, dst: &Symbol, src: &Symbol, layout: &Layout<'a>); /// build_num_add stores the sum of src1 and src2 into dst. - fn build_num_add( - &mut self, - dst: &Symbol, - src1: &Symbol, - src2: &Symbol, - layout: &Layout<'a>, - ) -> Result<(), String>; + fn build_num_add(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>); /// build_num_mul stores `src1 * src2` into dst. - fn build_num_mul( - &mut self, - dst: &Symbol, - src1: &Symbol, - src2: &Symbol, - layout: &Layout<'a>, - ) -> Result<(), String>; + fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>); /// build_num_neg stores the negated value of src into dst. - fn build_num_neg( - &mut self, - dst: &Symbol, - src: &Symbol, - layout: &Layout<'a>, - ) -> Result<(), String>; + fn build_num_neg(&mut self, dst: &Symbol, src: &Symbol, layout: &Layout<'a>); /// build_num_sub stores the `src1 - src2` difference into dst. - fn build_num_sub( - &mut self, - dst: &Symbol, - src1: &Symbol, - src2: &Symbol, - layout: &Layout<'a>, - ) -> Result<(), String>; + fn build_num_sub(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>); /// build_eq stores the result of `src1 == src2` into dst. - fn build_eq( - &mut self, - dst: &Symbol, - src1: &Symbol, - src2: &Symbol, - arg_layout: &Layout<'a>, - ) -> Result<(), String>; + fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>); /// build_neq stores the result of `src1 != src2` into dst. - fn build_neq( - &mut self, - dst: &Symbol, - src1: &Symbol, - src2: &Symbol, - arg_layout: &Layout<'a>, - ) -> Result<(), String>; + fn build_neq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>); /// build_num_lt stores the result of `src1 < src2` into dst. - fn build_num_lt( - &mut self, - dst: &Symbol, - src1: &Symbol, - src2: &Symbol, - arg_layout: &Layout<'a>, - ) -> Result<(), String>; + fn build_num_lt(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>); /// build_num_to_float convert Number to Float fn build_num_to_float( @@ -606,29 +544,23 @@ where src: &Symbol, arg_layout: &Layout<'a>, ret_layout: &Layout<'a>, - ) -> Result<(), String>; + ); /// literal_map gets the map from symbol to literal, used for lazy loading and literal folding. fn literal_map(&mut self) -> &mut MutMap>; - fn load_literal_symbols(&mut self, syms: &[Symbol]) -> Result<(), String> { + fn load_literal_symbols(&mut self, syms: &[Symbol]) { if self.env().lazy_literals { for sym in syms { if let Some(lit) = self.literal_map().remove(sym) { - self.load_literal(sym, &lit)?; + self.load_literal(sym, &lit); } } } - Ok(()) } /// create_struct creates a struct with the elements specified loaded into it as data. - fn create_struct( - &mut self, - sym: &Symbol, - layout: &Layout<'a>, - fields: &'a [Symbol], - ) -> Result<(), String>; + fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]); /// load_struct_at_index loads into `sym` the value at `index` in `structure`. fn load_struct_at_index( @@ -637,27 +569,26 @@ where structure: &Symbol, index: u64, field_layouts: &'a [Layout<'a>], - ) -> Result<(), String>; + ); /// load_literal sets a symbol to be equal to a literal. - fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String>; + fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>); /// 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: &Layout<'a>) -> Result<(), String>; + fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>); /// free_symbols will free all symbols for the given statement. - fn free_symbols(&mut self, stmt: &Stmt<'a>) -> Result<(), String> { + fn free_symbols(&mut self, stmt: &Stmt<'a>) { if let Some(syms) = self.free_map().remove(&(stmt as *const Stmt<'a>)) { for sym in syms { // println!("Freeing symbol: {:?}", sym); - self.free_symbol(&sym)?; + self.free_symbol(&sym); } } - Ok(()) } /// free_symbol frees any registers or stack space used to hold a symbol. - fn free_symbol(&mut self, sym: &Symbol) -> Result<(), String>; + fn free_symbol(&mut self, sym: &Symbol); /// set_last_seen sets the statement a symbol was last seen in. fn set_last_seen( @@ -676,20 +607,18 @@ where fn last_seen_map(&mut self) -> &mut MutMap>; /// set_layout_map sets the layout for a specific symbol. - fn set_layout_map(&mut self, sym: Symbol, layout: &Layout<'a>) -> Result<(), String> { + fn set_layout_map(&mut self, sym: Symbol, layout: &Layout<'a>) { if let Some(old_layout) = self.layout_map().insert(sym, *layout) { // Layout map already contains the symbol. We should never need to overwrite. // If the layout is not the same, that is a bug. if &old_layout != layout { - Err(format!( - "Overwriting layout for symbol, {:?}. This should never happen. got {:?}, want {:?}", - sym, layout, old_layout - )) - } else { - Ok(()) + internal_error!( + "Overwriting layout for symbol, {:?}: got {:?}, want {:?}", + sym, + layout, + old_layout + ) } - } else { - Ok(()) } } @@ -779,8 +708,8 @@ where self.set_last_seen(*sym, stmt, &owning_symbol); } } - Expr::Reset(sym) => { - self.set_last_seen(*sym, stmt, &owning_symbol); + Expr::Reset { symbol, .. } => { + self.set_last_seen(*symbol, stmt, &owning_symbol); } Expr::EmptyArray => {} Expr::RuntimeErrorFunction(_) => {} diff --git a/compiler/gen_dev/src/object_builder.rs b/compiler/gen_dev/src/object_builder.rs index 3ee65aa574..6a87092dcb 100644 --- a/compiler/gen_dev/src/object_builder.rs +++ b/compiler/gen_dev/src/object_builder.rs @@ -10,6 +10,7 @@ use object::{ use roc_collections::all::MutMap; use roc_module::symbol; use roc_mono::ir::{Proc, ProcLayout}; +use roc_reporting::internal_error; use target_lexicon::{Architecture as TargetArch, BinaryFormat as TargetBF, Triple}; // This is used by some code below which is currently commented out. @@ -22,7 +23,7 @@ pub fn build_module<'a>( env: &'a Env, target: &Triple, procedures: MutMap<(symbol::Symbol, ProcLayout<'a>), Proc<'a>>, -) -> Result { +) -> Object { match target { Triple { architecture: TargetArch::X86_64, @@ -34,7 +35,7 @@ pub fn build_module<'a>( x86_64::X86_64FloatReg, x86_64::X86_64Assembler, x86_64::X86_64SystemV, - > = Backend::new(env)?; + > = Backend::new(env); build_object( env, procedures, @@ -52,7 +53,7 @@ pub fn build_module<'a>( x86_64::X86_64FloatReg, x86_64::X86_64Assembler, x86_64::X86_64SystemV, - > = Backend::new(env)?; + > = Backend::new(env); build_object( env, procedures, @@ -74,7 +75,7 @@ pub fn build_module<'a>( aarch64::AArch64FloatReg, aarch64::AArch64Assembler, aarch64::AArch64Call, - > = Backend::new(env)?; + > = Backend::new(env); build_object( env, procedures, @@ -92,7 +93,7 @@ pub fn build_module<'a>( aarch64::AArch64FloatReg, aarch64::AArch64Assembler, aarch64::AArch64Call, - > = Backend::new(env)?; + > = Backend::new(env); build_object( env, procedures, @@ -104,9 +105,7 @@ pub fn build_module<'a>( ), ) } - x => Err(format! { - "the target, {:?}, is not yet implemented", - x}), + x => unimplemented!("the target, {:?}, is not yet implemented", x), } } @@ -115,7 +114,7 @@ fn generate_wrapper<'a, B: Backend<'a>>( output: &mut Object, wrapper_name: String, wraps: String, -) -> Result<(), String> { +) { let text_section = output.section_id(StandardSection::Text); let proc_symbol = Symbol { name: wrapper_name.as_bytes().to_vec(), @@ -128,7 +127,7 @@ fn generate_wrapper<'a, B: Backend<'a>>( flags: SymbolFlags::None, }; let proc_id = output.add_symbol(proc_symbol); - let (proc_data, offset) = backend.build_wrapped_jmp()?; + let (proc_data, offset) = backend.build_wrapped_jmp(); let proc_offset = output.add_symbol_data(proc_id, text_section, proc_data, 16); let name = wraps.as_str().as_bytes(); @@ -154,13 +153,12 @@ fn generate_wrapper<'a, B: Backend<'a>>( addend: -4, }; - output - .add_relocation(text_section, reloc) - .map_err(|e| format!("{:?}", e))?; - - Ok(()) + match output.add_relocation(text_section, reloc) { + Ok(obj) => obj, + Err(e) => internal_error!("{:?}", e), + } } else { - Err(format!("failed to find fn symbol for {:?}", wraps)) + unimplemented!("failed to find fn symbol for {:?}", wraps); } } @@ -169,7 +167,7 @@ fn build_object<'a, B: Backend<'a>>( procedures: MutMap<(symbol::Symbol, ProcLayout<'a>), Proc<'a>>, mut backend: B, mut output: Object, -) -> Result { +) -> Object { let data_section = output.section_id(StandardSection::Data); /* @@ -188,25 +186,25 @@ fn build_object<'a, B: Backend<'a>>( &mut output, "roc_alloc".into(), "malloc".into(), - )?; + ); generate_wrapper( &mut backend, &mut output, "roc_realloc".into(), "realloc".into(), - )?; + ); generate_wrapper( &mut backend, &mut output, "roc_dealloc".into(), "free".into(), - )?; + ); generate_wrapper( &mut backend, &mut output, "roc_panic".into(), "roc_builtins.utils.test_panic".into(), - )?; + ); } // Setup layout_ids for procedure calls. @@ -253,7 +251,7 @@ fn build_object<'a, B: Backend<'a>>( let mut relocations = bumpalo::vec![in env.arena]; for (fn_name, section_id, proc_id, proc) in procs { let mut local_data_index = 0; - let (proc_data, relocs) = backend.build_proc(proc)?; + let (proc_data, relocs) = backend.build_proc(proc); let proc_offset = output.add_symbol_data(proc_id, section_id, proc_data, 16); for reloc in relocs { let elfreloc = match reloc { @@ -293,7 +291,7 @@ fn build_object<'a, B: Backend<'a>>( addend: -4, } } else { - return Err(format!("failed to find data symbol for {:?}", name)); + internal_error!("failed to find data symbol for {:?}", name); } } Relocation::LinkedFunction { offset, name } => { @@ -323,7 +321,7 @@ fn build_object<'a, B: Backend<'a>>( addend: -4, } } else { - return Err(format!("failed to find fn symbol for {:?}", name)); + internal_error!("failed to find fn symbol for {:?}", name); } } Relocation::JmpToReturn { .. } => unreachable!(), @@ -332,9 +330,10 @@ fn build_object<'a, B: Backend<'a>>( } } for (section_id, reloc) in relocations { - output - .add_relocation(section_id, reloc) - .map_err(|e| format!("{:?}", e))?; + match output.add_relocation(section_id, reloc) { + Ok(obj) => obj, + Err(e) => internal_error!("{:?}", e), + } } - Ok(output) + output } diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index a5038fc2a5..96f084a49f 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -955,7 +955,7 @@ pub fn build_exp_call<'a, 'ctx, 'env>( } CallType::HigherOrder(higher_order) => { - let bytes = higher_order.specialization_id.to_bytes(); + let bytes = higher_order.passed_function.specialization_id.to_bytes(); let callee_var = CalleeSpecVar(&bytes); let func_spec = func_spec_solutions.callee_spec(callee_var).unwrap(); @@ -1108,7 +1108,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( .. } => build_tag(env, scope, union_layout, *tag_id, arguments, None, parent), - Reset(symbol) => { + Reset { symbol, .. } => { let (tag_ptr, layout) = load_symbol_and_layout(scope, symbol); let tag_ptr = tag_ptr.into_pointer_value(); @@ -4686,20 +4686,23 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( func_spec: FuncSpec, higher_order: &HigherOrderLowLevel<'a>, ) -> BasicValueEnum<'ctx> { + use roc_mono::ir::PassedFunction; use roc_mono::low_level::HigherOrder::*; let HigherOrderLowLevel { op, - arg_layouts: argument_layouts, - ret_layout: result_layout, - function_owns_closure_data, - function_name, - function_env, + passed_function, .. } = higher_order; - let function_owns_closure_data = *function_owns_closure_data; - let function_name = *function_name; + let PassedFunction { + argument_layouts, + return_layout: result_layout, + owns_captured_environment: function_owns_closure_data, + name: function_name, + captured_environment, + .. + } = *passed_function; // macros because functions cause lifetime issues related to the `env` or `layout_ids` macro_rules! function_details { @@ -4712,7 +4715,8 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( return_layout, ); - let (closure, closure_layout) = load_symbol_and_lambda_set(scope, function_env); + let (closure, closure_layout) = + load_symbol_and_lambda_set(scope, &captured_environment); (function, closure, closure_layout) }}; @@ -4737,14 +4741,14 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, - *result_layout, + result_layout, ); crate::llvm::build_list::list_walk_generic( env, layout_ids, roc_function_call, - result_layout, + &result_layout, list, element_layout, default, @@ -4974,7 +4978,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, - *result_layout, + result_layout, ); list_keep_if(env, layout_ids, roc_function_call, list, element_layout) @@ -5003,14 +5007,14 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, - *result_layout, + result_layout, ); list_keep_oks( env, layout_ids, roc_function_call, - result_layout, + &result_layout, list, before_layout, after_layout, @@ -5042,14 +5046,14 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, - *result_layout, + result_layout, ); list_keep_errs( env, layout_ids, roc_function_call, - result_layout, + &result_layout, list, before_layout, after_layout, @@ -5094,7 +5098,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, - *result_layout, + result_layout, ); list_sort_with( @@ -5197,7 +5201,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, - *result_layout, + result_layout, ); dict_walk( @@ -5522,6 +5526,24 @@ fn run_low_level<'a, 'ctx, 'env>( list_join(env, parent, list, outer_list_layout) } + NumToStr => { + // Num.toStr : Num a -> Str + debug_assert_eq!(args.len(), 1); + + let (num, num_layout) = load_symbol_and_layout(scope, &args[0]); + + match num_layout { + Layout::Builtin(Builtin::Int(int_width)) => { + let int = num.into_int_value(); + + str_from_int(env, int, *int_width) + } + Layout::Builtin(Builtin::Float(_float_width)) => { + str_from_float(env, scope, args[0]) + } + _ => unreachable!(), + } + } NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos | NumCeiling | NumFloor | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin => { debug_assert_eq!(args.len(), 1); @@ -6020,6 +6042,10 @@ fn run_low_level<'a, 'ctx, 'env>( | ListAny | ListAll | ListFindUnsafe | DictWalk => { unreachable!("these are higher order, and are handled elsewhere") } + + RefCountGetPtr | RefCountInc | RefCountDec => { + unreachable!("LLVM backend does not use lowlevels for refcounting"); + } } } diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index 3146f927eb..ce186340aa 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -406,7 +406,7 @@ pub fn str_from_utf8<'a, 'ctx, 'env>( decode_from_utf8_result(env, result_ptr).into() } -/// Str.fromInt : Int -> Str +/// Str.fromFloat : Int -> Str pub fn str_from_float<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &Scope<'a, 'ctx>, diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index 25a1dc500f..293a6d95ae 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -19,6 +19,8 @@ use roc_module::symbol::Interns; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout}; +/// "Infinite" reference count, for static values +/// Ref counts are encoded as negative numbers where isize::MIN represents 1 pub const REFCOUNT_MAX: usize = 0_usize; pub fn refcount_1(ctx: &Context, ptr_bytes: u32) -> IntValue<'_> { diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index ffeb2a5dfa..5788943018 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -3,11 +3,12 @@ use bumpalo::{self, collections::Vec}; use code_builder::Align; use roc_collections::all::MutMap; use roc_module::low_level::LowLevel; -use roc_module::symbol::Symbol; +use roc_module::symbol::{Interns, Symbol}; +use roc_mono::gen_refcount::{RefcountProcGenerator, REFCOUNT_MAX}; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout, LayoutIds}; -use crate::layout::{StackMemoryFormat, WasmLayout}; +use crate::layout::{CallConv, ReturnMethod, WasmLayout}; use crate::low_level::{decode_low_level, LowlevelBuildResult}; use crate::storage::{Storage, StoredValue, StoredValueKind}; use crate::wasm_module::linking::{ @@ -20,7 +21,7 @@ use crate::wasm_module::sections::{ }; use crate::wasm_module::{ code_builder, BlockType, CodeBuilder, ConstExpr, Export, ExportType, Global, GlobalType, - LocalId, Signature, SymInfo, ValueType, + LinkingSubSection, LocalId, Signature, SymInfo, ValueType, }; use crate::{ copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, MEMORY_NAME, PTR_SIZE, @@ -37,18 +38,21 @@ const CONST_SEGMENT_INDEX: usize = 0; pub struct WasmBackend<'a> { env: &'a Env<'a>, + interns: &'a mut Interns, // Module-level data - pub module: WasmModule<'a>, + module: WasmModule<'a>, layout_ids: LayoutIds<'a>, constant_sym_index_map: MutMap<&'a str, usize>, builtin_sym_index_map: MutMap<&'a str, usize>, - proc_symbols: Vec<'a, Symbol>, - pub linker_symbols: Vec<'a, SymInfo>, + proc_symbols: Vec<'a, (Symbol, u32)>, + linker_symbols: Vec<'a, SymInfo>, + refcount_proc_gen: RefcountProcGenerator<'a>, // Function-level data code_builder: CodeBuilder<'a>, storage: Storage<'a>, + symbol_layouts: MutMap>, /// how many blocks deep are we (used for jumps) block_depth: u32, @@ -58,10 +62,12 @@ pub struct WasmBackend<'a> { impl<'a> WasmBackend<'a> { pub fn new( env: &'a Env<'a>, + interns: &'a mut Interns, layout_ids: LayoutIds<'a>, - proc_symbols: Vec<'a, Symbol>, + proc_symbols: Vec<'a, (Symbol, u32)>, mut linker_symbols: Vec<'a, SymInfo>, mut exports: Vec<'a, Export>, + refcount_proc_gen: RefcountProcGenerator<'a>, ) -> Self { const MEMORY_INIT_SIZE: u32 = 1024 * 1024; let arena = env.arena; @@ -124,6 +130,7 @@ impl<'a> WasmBackend<'a> { WasmBackend { env, + interns, // Module-level data module, @@ -133,15 +140,47 @@ impl<'a> WasmBackend<'a> { builtin_sym_index_map: MutMap::default(), proc_symbols, linker_symbols, + refcount_proc_gen, // Function-level data block_depth: 0, joinpoint_label_map: MutMap::default(), code_builder: CodeBuilder::new(arena), storage: Storage::new(arena), + symbol_layouts: MutMap::default(), } } + pub fn generate_refcount_procs(&mut self) -> Vec<'a, Proc<'a>> { + let ident_ids = self + .interns + .all_ident_ids + .get_mut(&self.env.module_id) + .unwrap(); + + self.refcount_proc_gen + .generate_refcount_procs(self.env.arena, ident_ids) + } + + pub fn finalize_module(mut self) -> WasmModule<'a> { + let symbol_table = LinkingSubSection::SymbolTable(self.linker_symbols); + self.module.linking.subsections.push(symbol_table); + self.module + } + + /// Register the debug names of Symbols in a global lookup table + /// so that they have meaningful names when you print them. + /// Particularly useful after generating IR for refcount procedures + #[cfg(debug_assertions)] + pub fn register_symbol_debug_names(&self) { + let module_id = self.env.module_id; + let ident_ids = self.interns.all_ident_ids.get(&module_id).unwrap(); + self.env.module_id.register_debug_idents(ident_ids); + } + + #[cfg(not(debug_assertions))] + pub fn register_symbol_debug_names(&self) {} + /// Reset function-level data fn reset(&mut self) { // Push the completed CodeBuilder into the module and swap it for a new empty one @@ -151,6 +190,7 @@ impl<'a> WasmBackend<'a> { self.storage.clear(); self.joinpoint_label_map.clear(); + self.symbol_layouts.clear(); assert_eq!(self.block_depth, 0); } @@ -160,33 +200,37 @@ impl<'a> WasmBackend<'a> { ***********************************************************/ - pub fn build_proc(&mut self, proc: Proc<'a>, _sym: Symbol) -> Result<(), String> { - // println!("\ngenerating procedure {:?}\n", _sym); + pub fn build_proc(&mut self, proc: &Proc<'a>) -> Result<(), String> { + // println!("\ngenerating procedure {:?}\n", proc.name); - self.start_proc(&proc); + self.start_proc(proc); self.build_stmt(&proc.body, &proc.ret_layout)?; self.finalize_proc()?; self.reset(); - // println!("\nfinished generating {:?}\n", _sym); + // println!("\nfinished generating {:?}\n", proc.name); Ok(()) } fn start_proc(&mut self, proc: &Proc<'a>) { let ret_layout = WasmLayout::new(&proc.ret_layout); - let ret_type = if ret_layout.is_stack_memory() { - self.storage.arg_types.push(PTR_TYPE); - self.start_block(BlockType::NoResult); // block to ensure all paths pop stack memory (if any) - None - } else { - let ty = ret_layout.value_type(); - self.start_block(BlockType::Value(ty)); // block to ensure all paths pop stack memory (if any) - Some(ty) + + let ret_type = match ret_layout.return_method() { + ReturnMethod::Primitive(ty) => Some(ty), + ReturnMethod::NoReturnValue => None, + ReturnMethod::WriteToPointerArg => { + self.storage.arg_types.push(PTR_TYPE); + None + } }; + // Create a block so we can exit the function without skipping stack frame "pop" code. + // We never use the `return` instruction. Instead, we break from this block. + self.start_block(BlockType::from(ret_type)); + for (layout, symbol) in proc.args { let arg_layout = WasmLayout::new(layout); self.storage @@ -219,10 +263,9 @@ impl<'a> WasmBackend<'a> { ***********************************************************/ - /// start a loop that leaves a value on the stack - fn start_loop_with_return(&mut self, value_type: ValueType) { + fn start_loop(&mut self, block_type: BlockType) { self.block_depth += 1; - self.code_builder.loop_(BlockType::Value(value_type)); + self.code_builder.loop_(block_type); } fn start_block(&mut self, block_type: BlockType) { @@ -240,6 +283,8 @@ impl<'a> WasmBackend<'a> { Stmt::Let(_, _, _, _) => { let mut current_stmt = stmt; while let Stmt::Let(sym, expr, layout, following) = current_stmt { + // println!("let {:?} = {}", sym, expr.to_pretty(200)); // ignore `following`! Too confusing otherwise. + let wasm_layout = WasmLayout::new(layout); let kind = match following { @@ -265,6 +310,8 @@ impl<'a> WasmBackend<'a> { ); } + self.symbol_layouts.insert(*sym, *layout); + current_stmt = *following; } @@ -335,7 +382,7 @@ impl<'a> WasmBackend<'a> { } let is_bool = matches!(cond_layout, Layout::Builtin(Builtin::Bool)); - let cond_type = WasmLayout::new(cond_layout).value_type(); + let cond_type = WasmLayout::new(cond_layout).arg_types(CallConv::C)[0]; // then, we jump whenever the value under scrutiny is equal to the value of a branch for (i, (value, _, _)) in branches.iter().enumerate() { @@ -419,10 +466,14 @@ impl<'a> WasmBackend<'a> { self.end_block(); - // A `return` inside of a `loop` seems to make it so that the `loop` itself - // also "returns" (so, leaves on the stack) a value of the return type. - let return_wasm_layout = WasmLayout::new(ret_layout); - self.start_loop_with_return(return_wasm_layout.value_type()); + // A loop (or any block) needs to declare the type of the value it leaves on the stack on exit. + // The runtime needs this to statically validate the program before running it. + let loop_block_type = match WasmLayout::new(ret_layout).return_method() { + ReturnMethod::Primitive(ty) => BlockType::Value(ty), + ReturnMethod::WriteToPointerArg => BlockType::NoResult, + ReturnMethod::NoReturnValue => BlockType::NoResult, + }; + self.start_loop(loop_block_type); self.build_stmt(body, ret_layout)?; @@ -451,9 +502,46 @@ impl<'a> WasmBackend<'a> { Ok(()) } - Stmt::Refcounting(_modify, following) => { - // TODO: actually deal with refcounting. For hello world, we just skipped it. - self.build_stmt(following, ret_layout)?; + Stmt::Refcounting(modify, following) => { + let value = modify.get_symbol(); + let layout = self.symbol_layouts.get(&value).unwrap(); + + let ident_ids = self + .interns + .all_ident_ids + .get_mut(&self.env.module_id) + .unwrap(); + + let (rc_stmt, new_proc_info) = self + .refcount_proc_gen + .expand_refcount_stmt(ident_ids, *layout, modify, *following); + + if false { + self.register_symbol_debug_names(); + println!("## rc_stmt:\n{}\n{:?}", rc_stmt.to_pretty(200), rc_stmt); + } + + // If we're creating a new RC procedure, we need to store its symbol data, + // so that we can correctly generate calls to it. + if let Some((rc_proc_sym, rc_proc_layout)) = new_proc_info { + let wasm_fn_index = self.proc_symbols.len() as u32; + let linker_sym_index = self.linker_symbols.len() as u32; + + let name = self + .layout_ids + .get_toplevel(rc_proc_sym, &rc_proc_layout) + .to_symbol_string(rc_proc_sym, self.interns); + + self.proc_symbols.push((rc_proc_sym, linker_sym_index)); + self.linker_symbols + .push(SymInfo::Function(WasmObjectSymbol::Defined { + flags: 0, + index: wasm_fn_index, + name, + })); + } + + self.build_stmt(&rc_stmt, ret_layout)?; Ok(()) } @@ -488,45 +576,35 @@ impl<'a> WasmBackend<'a> { return self.build_low_level(lowlevel, arguments, *sym, wasm_layout); } - let mut wasm_args_tmp: Vec; - let (wasm_args, has_return_val) = match wasm_layout { - WasmLayout::StackMemory { .. } => { - wasm_args_tmp = - Vec::with_capacity_in(arguments.len() + 1, self.env.arena); - wasm_args_tmp.push(*sym); - wasm_args_tmp.extend_from_slice(*arguments); - (wasm_args_tmp.as_slice(), false) - } - _ => (*arguments, true), - }; - - self.storage.load_symbols(&mut self.code_builder, wasm_args); - - // Index of the called function in the code section. Assumes all functions end up in the binary. - // (We may decide to keep all procs even if calls are inlined, in case platform calls them) - let func_index = match self.proc_symbols.iter().position(|s| s == func_sym) { - Some(i) => i as u32, - None => { - // TODO: actually useful linking! Push a relocation for it. - return Err(format!( - "Not yet supported: calling foreign function {:?}", - func_sym - )); - } - }; - - // Index of the function's name in the symbol table - // Same as the function index since those are the first symbols we add - let symbol_index = func_index; - - self.code_builder.call( - func_index, - symbol_index, - wasm_args.len(), - has_return_val, + let (param_types, ret_type) = self.storage.load_symbols_for_call( + self.env.arena, + &mut self.code_builder, + arguments, + *sym, + &wasm_layout, + CallConv::C, ); - Ok(()) + for (func_index, (ir_sym, linker_sym_index)) in + self.proc_symbols.iter().enumerate() + { + if ir_sym == func_sym { + let num_wasm_args = param_types.len(); + let has_return_val = ret_type.is_some(); + self.code_builder.call( + func_index as u32, + *linker_sym_index, + num_wasm_args, + has_return_val, + ); + return Ok(()); + } + } + + unreachable!( + "Could not find procedure {:?}\nKnown procedures: {:?}", + func_sym, self.proc_symbols + ); } CallType::LowLevel { op: lowlevel, .. } => { @@ -561,6 +639,27 @@ impl<'a> WasmBackend<'a> { Ok(()) } + Expr::Array { .. } => Err(format!("Expression is not yet implemented {:?}", 2)), + + Expr::EmptyArray => { + if let StoredValue::StackMemory { location, .. } = storage { + let (local_id, offset) = + location.local_and_offset(self.storage.stack_frame_pointer); + + // This is a minor cheat. We only need the first two 32 bit + // chunks here. We fill both chunks with zeros, so we + // can simplify things to a single group of 64 bit operations instead of + // doing the below twice for 32 bits. + self.code_builder.get_local(local_id); + self.code_builder.i64_const(0); + self.code_builder.i64_store(Align::Bytes4, offset); + + Ok(()) + } else { + unreachable!("Unexpected storage for {:?}", sym) + } + } + x => Err(format!("Expression is not yet implemented {:?}", x)), } } @@ -572,14 +671,13 @@ impl<'a> WasmBackend<'a> { return_sym: Symbol, return_layout: WasmLayout, ) -> Result<(), String> { - // Load symbols using the "fast calling convention" that Zig uses instead of the C ABI we normally use. - // It's only different from the C ABI for small structs, and we are using Zig for all of those cases. - // This is a workaround for a bug in Zig. If later versions fix it, we can change to the C ABI. - self.storage.load_symbols_fastcc( + let (param_types, ret_type) = self.storage.load_symbols_for_call( + self.env.arena, &mut self.code_builder, arguments, return_sym, &return_layout, + CallConv::Zig, ); let build_result = decode_low_level( @@ -594,7 +692,7 @@ impl<'a> WasmBackend<'a> { match build_result { Done => Ok(()), BuiltinCall(name) => { - self.call_zig_builtin(name, arguments, &return_layout); + self.call_zig_builtin(name, param_types, ret_type); Ok(()) } NotImplemented => Err(format!( @@ -611,7 +709,7 @@ impl<'a> WasmBackend<'a> { sym: Symbol, layout: &Layout<'a>, ) -> Result<(), String> { - let not_supported_error = || Err(format!("Literal value {:?} is not yet implemented", lit)); + let not_supported_error = || panic!("Literal value {:?} is not yet implemented", lit); match storage { StoredValue::VirtualMachineStack { value_type, .. } => { @@ -655,6 +753,8 @@ impl<'a> WasmBackend<'a> { stack_mem_bytes[7] = 0x80 | (len as u8); let str_as_int = i64::from_le_bytes(stack_mem_bytes); + // Write all 8 bytes at once using an i64 + // Str is normally two i32's, but in this special case, we can get away with fewer instructions self.code_builder.get_local(local_id); self.code_builder.i64_const(str_as_int); self.code_builder.i64_store(Align::Bytes4, offset); @@ -712,10 +812,13 @@ impl<'a> WasmBackend<'a> { None => { let const_segment_bytes = &mut self.module.data.segments[CONST_SEGMENT_INDEX].init; - // Store the string in the data section, to be loaded on module instantiation - // RocStr `elements` field will point to that constant data, not the heap - let segment_offset = const_segment_bytes.len() as u32; - let elements_addr = segment_offset + CONST_SEGMENT_BASE_ADDR; + // Store the string in the data section + // Prefix it with a special refcount value (treated as "infinity") + // The string's `elements` field points at the data after the refcount + let refcount_max_bytes: [u8; 4] = (REFCOUNT_MAX as i32).to_le_bytes(); + const_segment_bytes.extend_from_slice(&refcount_max_bytes); + let elements_offset = const_segment_bytes.len() as u32; + let elements_addr = elements_offset + CONST_SEGMENT_BASE_ADDR; const_segment_bytes.extend_from_slice(string.as_bytes()); // Generate linker info @@ -723,12 +826,12 @@ impl<'a> WasmBackend<'a> { let name = self .layout_ids .get(sym, layout) - .to_symbol_string(sym, &self.env.interns); + .to_symbol_string(sym, self.interns); let linker_symbol = SymInfo::Data(DataSymbol::Defined { flags: 0, name, segment_index: CONST_SEGMENT_INDEX as u32, - segment_offset, + segment_offset: elements_offset, size: string.len() as u32, }); @@ -767,7 +870,9 @@ impl<'a> WasmBackend<'a> { ); } } else { - return Err(format!("Not supported yet: zero-size struct at {:?}", sym)); + // Zero-size struct. No code to emit. + // These values are purely conceptual, they only exist internally in the compiler + return Ok(()); } } _ => { @@ -789,7 +894,15 @@ impl<'a> WasmBackend<'a> { /// Generate a call instruction to a Zig builtin function. /// And if we haven't seen it before, add an Import and linker data for it. /// Zig calls use LLVM's "fast" calling convention rather than our usual C ABI. - fn call_zig_builtin(&mut self, name: &'a str, arguments: &[Symbol], ret_layout: &WasmLayout) { + fn call_zig_builtin( + &mut self, + name: &'a str, + param_types: Vec<'a, ValueType>, + ret_type: Option, + ) { + let num_wasm_args = param_types.len(); + let has_return_val = ret_type.is_some(); + let (fn_index, linker_symbol_index) = match self.builtin_sym_index_map.get(name) { Some(sym_idx) => match &self.linker_symbols[*sym_idx] { SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => { @@ -799,51 +912,14 @@ impl<'a> WasmBackend<'a> { }, None => { - let mut param_types = Vec::with_capacity_in(1 + arguments.len(), self.env.arena); - - let ret_type = if ret_layout.is_stack_memory() { - param_types.push(ValueType::I32); - None - } else { - Some(ret_layout.value_type()) - }; - - for arg in arguments { - match self.storage.get(arg) { - StoredValue::StackMemory { size, format, .. } => { - use StackMemoryFormat::*; - - match format { - Aggregate => { - // Zig's "fast calling convention" packs structs into CPU registers - // (stack machine slots) if possible. If they're small enough they - // can go into an I32 or I64. If they're big, they're pointers (I32). - if *size > 4 && *size <= 8 { - param_types.push(ValueType::I64) - } else { - // either - // - // - this is a small value, that fits in an i32 - // - this is a big value, we pass a memory address - param_types.push(ValueType::I32) - } - } - Int128 | Float128 | Decimal => { - // these types are passed as 2 i64s - param_types.push(ValueType::I64); - param_types.push(ValueType::I64); - } - } - } - stored => param_types.push(stored.value_type()), - } - } - - let signature_index = self.module.types.insert(Signature { + // Wasm function signature + let signature = Signature { param_types, ret_type, - }); + }; + let signature_index = self.module.types.insert(signature); + // Declare it as an import since it comes from a different .o file let import_index = self.module.import.entries.len() as u32; let import = Import { module: BUILTINS_IMPORT_MODULE_NAME, @@ -852,22 +928,22 @@ impl<'a> WasmBackend<'a> { }; self.module.import.entries.push(import); + // Provide symbol information for the linker let sym_idx = self.linker_symbols.len(); let sym_info = SymInfo::Function(WasmObjectSymbol::Imported { flags: WASM_SYM_UNDEFINED, index: import_index, }); self.linker_symbols.push(sym_info); + + // Remember that we have created all of this data, and don't need to do it again self.builtin_sym_index_map.insert(name, sym_idx); (import_index, sym_idx as u32) } }; - self.code_builder.call( - fn_index, - linker_symbol_index, - arguments.len(), - true, // TODO: handle builtins with no return value - ); + + self.code_builder + .call(fn_index, linker_symbol_index, num_wasm_args, has_return_val); } } diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 4bde804b7b..330de5261b 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -1,12 +1,26 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_mono::layout::{Layout, UnionLayout}; -use crate::{wasm_module::ValueType, PTR_SIZE, PTR_TYPE}; +use crate::wasm_module::ValueType; +use crate::{PTR_SIZE, PTR_TYPE}; + +/// Manually keep up to date with the Zig version we are using for builtins +pub const BUILTINS_ZIG_VERSION: ZigVersion = ZigVersion::Zig8; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ReturnMethod { + /// This layout is returned from a Wasm function "normally" as a Primitive + Primitive(ValueType), + /// This layout is returned by writing to a pointer passed as the first argument + WriteToPointerArg, + /// This layout is empty and requires no return value or argument (e.g. refcount helpers) + NoReturnValue, +} #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum StackMemoryFormat { /// Record, Str, List, Dict, etc. - Aggregate, + DataStructure, Int128, Float128, Decimal, @@ -25,9 +39,6 @@ pub enum WasmLayout { alignment_bytes: u32, format: StackMemoryFormat, }, - - // Local pointer to heap memory - HeapMemory, } impl WasmLayout { @@ -82,7 +93,7 @@ impl WasmLayout { | Layout::Union(NonRecursive(_)) => Self::StackMemory { size, alignment_bytes, - format: StackMemoryFormat::Aggregate, + format: StackMemoryFormat::DataStructure, }, Layout::Union( @@ -91,26 +102,95 @@ impl WasmLayout { | NullableWrapped { .. } | NullableUnwrapped { .. }, ) - | Layout::RecursivePointer => Self::HeapMemory, + | Layout::RecursivePointer => Self::Primitive(PTR_TYPE, PTR_SIZE), } } - pub fn value_type(&self) -> ValueType { + /// The `ValueType`s to use for this layout when calling a Wasm function + /// One Roc argument can become 0, 1, or 2 Wasm arguments + pub fn arg_types(&self, conv: CallConv) -> &'static [ValueType] { + use ValueType::*; + match self { - Self::Primitive(type_, _) => *type_, - _ => PTR_TYPE, + // 1 Roc argument => 1 Wasm argument (same for all calling conventions) + Self::Primitive(I32, _) => &[I32], + Self::Primitive(I64, _) => &[I64], + Self::Primitive(F32, _) => &[F32], + Self::Primitive(F64, _) => &[F64], + + // 1 Roc argument => 0-2 Wasm arguments (depending on size and calling convention) + Self::StackMemory { size, format, .. } => conv.stack_memory_arg_types(*size, *format), } } - pub fn size(&self) -> u32 { + pub fn return_method(&self) -> ReturnMethod { match self { - Self::Primitive(_, size) => *size, - Self::StackMemory { size, .. } => *size, - Self::HeapMemory => PTR_SIZE, + Self::Primitive(ty, _) => ReturnMethod::Primitive(*ty), + Self::StackMemory { size, .. } => { + if *size == 0 { + ReturnMethod::NoReturnValue + } else { + ReturnMethod::WriteToPointerArg + } + } + } + } +} + +#[derive(PartialEq, Eq)] +pub enum ZigVersion { + Zig8, + Zig9, +} + +#[derive(Debug, Clone, Copy)] +pub enum CallConv { + /// The C calling convention, as defined here: + /// https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md + C, + /// The calling convention that Zig 0.8 or 0.9 generates for Wasm when we *ask* it + /// for the .C calling convention, due to bugs in both versions of the Zig compiler. + Zig, +} + +impl CallConv { + /// The Wasm argument types to use when passing structs or 128-bit numbers + pub fn stack_memory_arg_types( + &self, + size: u32, + format: StackMemoryFormat, + ) -> &'static [ValueType] { + use StackMemoryFormat::*; + use ValueType::*; + + match format { + Int128 | Float128 | Decimal => &[I64, I64], + + DataStructure => { + if size == 0 { + // Zero-size Roc values like `{}` => no Wasm arguments + return &[]; + } + match self { + CallConv::C => { + &[I32] // Always pass structs by reference (pointer to stack memory) + } + + CallConv::Zig => { + if size <= 4 { + &[I32] // Small struct: pass by value + } else if size <= 8 { + &[I64] // Small struct: pass by value + } else if size <= 12 && BUILTINS_ZIG_VERSION == ZigVersion::Zig9 { + &[I64, I32] // Medium struct: pass by value, as two Wasm arguments + } else if size <= 16 { + &[I64, I64] // Medium struct: pass by value, as two Wasm arguments + } else { + &[I32] // Large struct: pass by reference + } + } + } + } } } - - pub fn is_stack_memory(&self) -> bool { - matches!(self, Self::StackMemory { .. }) - } } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index a8b19213dd..2b5aba3ecd 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -6,16 +6,17 @@ pub mod wasm_module; use bumpalo::{self, collections::Vec, Bump}; +use roc_builtins::bitcode::IntWidth; use roc_collections::all::{MutMap, MutSet}; use roc_module::low_level::LowLevel; -use roc_module::symbol::{Interns, Symbol}; +use roc_module::symbol::{Interns, ModuleId, Symbol}; +use roc_mono::gen_refcount::RefcountProcGenerator; use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::layout::LayoutIds; use crate::backend::WasmBackend; use crate::wasm_module::{ - Align, CodeBuilder, Export, ExportType, LinkingSubSection, LocalId, SymInfo, ValueType, - WasmModule, + Align, CodeBuilder, Export, ExportType, LocalId, SymInfo, ValueType, WasmModule, }; const PTR_SIZE: u32 = 4; @@ -29,27 +30,29 @@ pub const STACK_POINTER_NAME: &str = "__stack_pointer"; pub struct Env<'a> { pub arena: &'a Bump, - pub interns: Interns, + pub module_id: ModuleId, pub exposed_to_host: MutSet, } pub fn build_module<'a>( - env: &'a Env, + env: &'a Env<'a>, + interns: &'a mut Interns, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) -> Result, String> { - let (mut wasm_module, _) = build_module_help(env, procedures)?; + let (mut wasm_module, _) = build_module_help(env, interns, procedures)?; let mut buffer = std::vec::Vec::with_capacity(4096); wasm_module.serialize_mut(&mut buffer); Ok(buffer) } pub fn build_module_help<'a>( - env: &'a Env, + env: &'a Env<'a>, + interns: &'a mut Interns, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) -> Result<(WasmModule<'a>, u32), String> { let mut layout_ids = LayoutIds::default(); - let mut generated_procs = Vec::with_capacity_in(procedures.len(), env.arena); - let mut generated_symbols = Vec::with_capacity_in(procedures.len(), env.arena); + let mut procs = Vec::with_capacity_in(procedures.len(), env.arena); + let mut proc_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena); let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena); let mut exports = Vec::with_capacity_in(4, env.arena); let mut main_fn_index = None; @@ -61,12 +64,11 @@ pub fn build_module_help<'a>( if LowLevel::from_inlined_wrapper(sym).is_some() { continue; } - generated_procs.push(proc); - generated_symbols.push(sym); + procs.push(proc); let fn_name = layout_ids .get_toplevel(sym, &layout) - .to_symbol_string(sym, &env.interns); + .to_symbol_string(sym, interns); if env.exposed_to_host.contains(&sym) { main_fn_index = Some(fn_index); @@ -78,29 +80,54 @@ pub fn build_module_help<'a>( } let linker_sym = SymInfo::for_function(fn_index, fn_name); + proc_symbols.push((sym, linker_symbols.len() as u32)); linker_symbols.push(linker_sym); fn_index += 1; } - // Build the Wasm module - let (mut module, linker_symbols) = { - let mut backend = WasmBackend::new( - env, - layout_ids, - generated_symbols.clone(), - linker_symbols, - exports, - ); + let mut backend = WasmBackend::new( + env, + interns, + layout_ids, + proc_symbols, + linker_symbols, + exports, + RefcountProcGenerator::new(env.arena, IntWidth::I32, env.module_id), + ); - for (proc, sym) in generated_procs.into_iter().zip(generated_symbols) { - backend.build_proc(proc, sym)?; + if false { + println!("## procs"); + for proc in procs.iter() { + println!("{}", proc.to_pretty(200)); + println!("{:#?}", proc); } - (backend.module, backend.linker_symbols) - }; + } - let symbol_table = LinkingSubSection::SymbolTable(linker_symbols); - module.linking.subsections.push(symbol_table); + // Generate procs from user code + for proc in procs.iter() { + backend.build_proc(proc)?; + } + + // Generate IR for refcounting procs + let refcount_procs = backend.generate_refcount_procs(); + + backend.register_symbol_debug_names(); + + if false { + println!("## refcount_procs"); + for proc in refcount_procs.iter() { + println!("{}", proc.to_pretty(200)); + println!("{:#?}", proc); + } + } + + // Generate Wasm for refcounting procs + for proc in refcount_procs.iter() { + backend.build_proc(proc)?; + } + + let module = backend.finalize_module(); Ok((module, main_fn_index.unwrap())) } @@ -118,6 +145,9 @@ pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) { if config.from_ptr == config.to_ptr && config.from_offset == config.to_offset { return; } + if config.size == 0 { + return; + } let alignment = Align::from(config.alignment_bytes); let mut i = 0; diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index 700268c9fa..df7110ee09 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -2,12 +2,9 @@ use roc_builtins::bitcode::{self, FloatWidth}; use roc_module::low_level::{LowLevel, LowLevel::*}; use roc_module::symbol::Symbol; -use crate::layout::{StackMemoryFormat, WasmLayout}; -use crate::storage::Storage; -use crate::wasm_module::{ - CodeBuilder, - ValueType::{self, *}, -}; +use crate::layout::{StackMemoryFormat::*, WasmLayout}; +use crate::storage::{Storage, StoredValue}; +use crate::wasm_module::{CodeBuilder, ValueType::*}; pub enum LowlevelBuildResult { Done, @@ -71,206 +68,302 @@ pub fn decode_low_level<'a>( F64 => code_builder.f64_add(), }, WasmLayout::StackMemory { format, .. } => match format { - StackMemoryFormat::Aggregate => return NotImplemented, - StackMemoryFormat::Int128 => return NotImplemented, - StackMemoryFormat::Float128 => return NotImplemented, - StackMemoryFormat::Decimal => return BuiltinCall(bitcode::DEC_ADD_WITH_OVERFLOW), + DataStructure => return NotImplemented, + Int128 => return NotImplemented, + Float128 => return NotImplemented, + Decimal => return BuiltinCall(bitcode::DEC_ADD_WITH_OVERFLOW), }, - WasmLayout::HeapMemory { .. } => return NotImplemented, }, - NumAddWrap => match ret_layout.value_type() { - I32 => { - code_builder.i32_add(); - wrap_i32(code_builder, ret_layout.size()); - } - I64 => code_builder.i64_add(), - F32 => code_builder.f32_add(), - F64 => code_builder.f64_add(), + NumAddWrap => match ret_layout { + WasmLayout::Primitive(value_type, size) => match value_type { + I32 => { + code_builder.i32_add(); + // TODO: is *deliberate* wrapping really in the spirit of things? + // The point of choosing NumAddWrap is to go fast by skipping checks, but we're making it slower. + wrap_i32(code_builder, *size); + } + I64 => code_builder.i64_add(), + F32 => code_builder.f32_add(), + F64 => code_builder.f64_add(), + }, + WasmLayout::StackMemory { format, .. } => match format { + DataStructure => return NotImplemented, + Int128 => return NotImplemented, + Float128 => return NotImplemented, + Decimal => return BuiltinCall(bitcode::DEC_ADD_WITH_OVERFLOW), + }, }, + NumToStr => return NotImplemented, NumAddChecked => return NotImplemented, - NumSub => match ret_layout.value_type() { - I32 => code_builder.i32_sub(), - I64 => code_builder.i64_sub(), - F32 => code_builder.f32_sub(), - F64 => code_builder.f64_sub(), + NumSub => match ret_layout { + WasmLayout::Primitive(value_type, _) => match value_type { + I32 => code_builder.i32_sub(), + I64 => code_builder.i64_sub(), + F32 => code_builder.f32_sub(), + F64 => code_builder.f64_sub(), + }, + WasmLayout::StackMemory { format, .. } => match format { + DataStructure => return NotImplemented, + Int128 => return NotImplemented, + Float128 => return NotImplemented, + Decimal => return BuiltinCall(bitcode::DEC_SUB_WITH_OVERFLOW), + }, }, - NumSubWrap => match ret_layout.value_type() { - I32 => { - code_builder.i32_sub(); - wrap_i32(code_builder, ret_layout.size()); - } - I64 => code_builder.i64_sub(), - F32 => code_builder.f32_sub(), - F64 => code_builder.f64_sub(), + NumSubWrap => match ret_layout { + WasmLayout::Primitive(value_type, size) => match value_type { + I32 => { + code_builder.i32_sub(); + wrap_i32(code_builder, *size); + } + I64 => code_builder.i64_sub(), + F32 => code_builder.f32_sub(), + F64 => code_builder.f64_sub(), + }, + WasmLayout::StackMemory { format, .. } => match format { + DataStructure => return NotImplemented, + Int128 => return NotImplemented, + Float128 => return NotImplemented, + Decimal => return BuiltinCall(bitcode::DEC_SUB_WITH_OVERFLOW), + }, }, NumSubChecked => return NotImplemented, - NumMul => match ret_layout.value_type() { - I32 => code_builder.i32_mul(), - I64 => code_builder.i64_mul(), - F32 => code_builder.f32_mul(), - F64 => code_builder.f64_mul(), + NumMul => match ret_layout { + WasmLayout::Primitive(value_type, _) => match value_type { + I32 => code_builder.i32_mul(), + I64 => code_builder.i64_mul(), + F32 => code_builder.f32_mul(), + F64 => code_builder.f64_mul(), + }, + WasmLayout::StackMemory { format, .. } => match format { + DataStructure => return NotImplemented, + Int128 => return NotImplemented, + Float128 => return NotImplemented, + Decimal => return BuiltinCall(bitcode::DEC_MUL_WITH_OVERFLOW), + }, }, - NumMulWrap => match ret_layout.value_type() { - I32 => { - code_builder.i32_mul(); - wrap_i32(code_builder, ret_layout.size()); - } - I64 => code_builder.i64_mul(), - F32 => code_builder.f32_mul(), - F64 => code_builder.f64_mul(), + NumMulWrap => match ret_layout { + WasmLayout::Primitive(value_type, size) => match value_type { + I32 => { + code_builder.i32_mul(); + wrap_i32(code_builder, *size); + } + I64 => code_builder.i64_mul(), + F32 => code_builder.f32_mul(), + F64 => code_builder.f64_mul(), + }, + WasmLayout::StackMemory { format, .. } => match format { + DataStructure => return NotImplemented, + Int128 => return NotImplemented, + Float128 => return NotImplemented, + Decimal => return BuiltinCall(bitcode::DEC_MUL_WITH_OVERFLOW), + }, }, NumMulChecked => return NotImplemented, - NumGt => match storage.get(&args[0]).value_type() { - I32 => code_builder.i32_gt_s(), - I64 => code_builder.i64_gt_s(), - F32 => code_builder.f32_gt(), - F64 => code_builder.f64_gt(), + NumGt => match storage.get(&args[0]) { + StoredValue::VirtualMachineStack { value_type, .. } + | StoredValue::Local { value_type, .. } => match value_type { + I32 => code_builder.i32_gt_s(), + I64 => code_builder.i64_gt_s(), + F32 => code_builder.f32_gt(), + F64 => code_builder.f64_gt(), + }, + StoredValue::StackMemory { .. } => return NotImplemented, }, - NumGte => match storage.get(&args[0]).value_type() { - I32 => code_builder.i32_ge_s(), - I64 => code_builder.i64_ge_s(), - F32 => code_builder.f32_ge(), - F64 => code_builder.f64_ge(), + NumGte => match storage.get(&args[0]) { + StoredValue::VirtualMachineStack { value_type, .. } + | StoredValue::Local { value_type, .. } => match value_type { + I32 => code_builder.i32_ge_s(), + I64 => code_builder.i64_ge_s(), + F32 => code_builder.f32_ge(), + F64 => code_builder.f64_ge(), + }, + StoredValue::StackMemory { .. } => return NotImplemented, }, - NumLt => match storage.get(&args[0]).value_type() { - I32 => code_builder.i32_lt_s(), - I64 => code_builder.i64_lt_s(), - F32 => code_builder.f32_lt(), - F64 => code_builder.f64_lt(), + NumLt => match storage.get(&args[0]) { + StoredValue::VirtualMachineStack { value_type, .. } + | StoredValue::Local { value_type, .. } => match value_type { + I32 => code_builder.i32_lt_s(), + I64 => code_builder.i64_lt_s(), + F32 => code_builder.f32_lt(), + F64 => code_builder.f64_lt(), + }, + StoredValue::StackMemory { .. } => return NotImplemented, }, - NumLte => match storage.get(&args[0]).value_type() { - I32 => code_builder.i32_le_s(), - I64 => code_builder.i64_le_s(), - F32 => code_builder.f32_le(), - F64 => code_builder.f64_le(), + NumLte => match storage.get(&args[0]) { + StoredValue::VirtualMachineStack { value_type, .. } + | StoredValue::Local { value_type, .. } => match value_type { + I32 => code_builder.i32_le_s(), + I64 => code_builder.i64_le_s(), + F32 => code_builder.f32_le(), + F64 => code_builder.f64_le(), + }, + StoredValue::StackMemory { .. } => return NotImplemented, }, NumCompare => return NotImplemented, - NumDivUnchecked => match ret_layout.value_type() { - I32 => code_builder.i32_div_s(), - I64 => code_builder.i64_div_s(), - F32 => code_builder.f32_div(), - F64 => code_builder.f64_div(), + NumDivUnchecked => match storage.get(&args[0]) { + StoredValue::VirtualMachineStack { value_type, .. } + | StoredValue::Local { value_type, .. } => match value_type { + I32 => code_builder.i32_div_s(), + I64 => code_builder.i64_div_s(), + F32 => code_builder.f32_div(), + F64 => code_builder.f64_div(), + }, + StoredValue::StackMemory { format, .. } => match format { + DataStructure => return NotImplemented, + Int128 => return NotImplemented, + Float128 => return NotImplemented, + Decimal => return BuiltinCall(bitcode::DEC_DIV), + }, }, NumDivCeilUnchecked => return NotImplemented, - NumRemUnchecked => match ret_layout.value_type() { - I32 => code_builder.i32_rem_s(), - I64 => code_builder.i64_rem_s(), - F32 => return NotImplemented, - F64 => return NotImplemented, + NumRemUnchecked => match storage.get(&args[0]) { + StoredValue::VirtualMachineStack { value_type, .. } + | StoredValue::Local { value_type, .. } => match value_type { + I32 => code_builder.i32_rem_s(), + I64 => code_builder.i64_rem_s(), + F32 => return NotImplemented, + F64 => return NotImplemented, + }, + StoredValue::StackMemory { .. } => return NotImplemented, }, NumIsMultipleOf => return NotImplemented, - NumAbs => match ret_layout.value_type() { - I32 => { - let arg_storage = storage.get(&args[0]).to_owned(); - storage.ensure_value_has_local(code_builder, args[0], arg_storage); - storage.load_symbols(code_builder, args); - code_builder.i32_const(0); - storage.load_symbols(code_builder, args); - code_builder.i32_sub(); - storage.load_symbols(code_builder, args); - code_builder.i32_const(0); - code_builder.i32_ge_s(); - code_builder.select(); - } - I64 => { - let arg_storage = storage.get(&args[0]).to_owned(); - storage.ensure_value_has_local(code_builder, args[0], arg_storage); - storage.load_symbols(code_builder, args); - code_builder.i64_const(0); - storage.load_symbols(code_builder, args); - code_builder.i64_sub(); - storage.load_symbols(code_builder, args); - code_builder.i64_const(0); - code_builder.i64_ge_s(); - code_builder.select(); - } - F32 => code_builder.f32_abs(), - F64 => code_builder.f64_abs(), - }, - NumNeg => { - match ret_layout.value_type() { + NumAbs => match storage.get(&args[0]) { + StoredValue::VirtualMachineStack { value_type, .. } + | StoredValue::Local { value_type, .. } => match value_type { + I32 => { + let arg_storage = storage.get(&args[0]).to_owned(); + storage.ensure_value_has_local(code_builder, args[0], arg_storage); + storage.load_symbols(code_builder, args); + code_builder.i32_const(0); + storage.load_symbols(code_builder, args); + code_builder.i32_sub(); + storage.load_symbols(code_builder, args); + code_builder.i32_const(0); + code_builder.i32_ge_s(); + code_builder.select(); + } + I64 => { + let arg_storage = storage.get(&args[0]).to_owned(); + storage.ensure_value_has_local(code_builder, args[0], arg_storage); + storage.load_symbols(code_builder, args); + code_builder.i64_const(0); + storage.load_symbols(code_builder, args); + code_builder.i64_sub(); + storage.load_symbols(code_builder, args); + code_builder.i64_const(0); + code_builder.i64_ge_s(); + code_builder.select(); + } + F32 => code_builder.f32_abs(), + F64 => code_builder.f64_abs(), + }, + StoredValue::StackMemory { .. } => return NotImplemented, + }, + NumNeg => match ret_layout { + WasmLayout::Primitive(value_type, _) => match value_type { I32 => { - // Unfortunate local.set/local.get code_builder.i32_const(0); storage.load_symbols(code_builder, args); code_builder.i32_sub(); } I64 => { - // Unfortunate local.set/local.get code_builder.i64_const(0); storage.load_symbols(code_builder, args); code_builder.i64_sub(); } F32 => code_builder.f32_neg(), F64 => code_builder.f64_neg(), - } - } + }, + WasmLayout::StackMemory { .. } => return NotImplemented, + }, NumSin => return NotImplemented, NumCos => return NotImplemented, NumSqrtUnchecked => return NotImplemented, NumLogUnchecked => return NotImplemented, - NumRound => { - // FIXME - // thread 'gen_num::f64_round' panicked at 'called `Result::unwrap()` on an `Err` value: - // Io(Os { code: 2, kind: NotFound, message: "No such file or directory" })', - // compiler/test_gen/src/helpers/wasm.rs:185:53 - // Note: Wasm has a `nearest` op, but it does round-to-even when fraction is exactly 0.5 - // which fails tests. Will this do? Or is specific behaviour important? - let width = float_width_from_layout(ret_layout); - return BuiltinCall(&bitcode::NUM_ROUND[width]); - } - NumToFloat => match (ret_layout.value_type(), storage.get(&args[0]).value_type()) { - (F32, I32) => code_builder.f32_convert_s_i32(), - (F32, I64) => code_builder.f32_convert_s_i64(), - (F32, F32) => {} - (F32, F64) => code_builder.f32_demote_f64(), - (F64, I32) => code_builder.f64_convert_s_i32(), - (F64, I64) => code_builder.f64_convert_s_i64(), - (F64, F32) => code_builder.f64_promote_f32(), - (F64, F64) => {} - _ => panic_ret_type(), + NumRound => match storage.get(&args[0]) { + StoredValue::VirtualMachineStack { value_type, .. } + | StoredValue::Local { value_type, .. } => match value_type { + F32 => return BuiltinCall(&bitcode::NUM_ROUND[FloatWidth::F32]), + F64 => return BuiltinCall(&bitcode::NUM_ROUND[FloatWidth::F64]), + _ => return NotImplemented, + }, + StoredValue::StackMemory { .. } => return NotImplemented, }, + NumToFloat => { + use StoredValue::*; + let stored = storage.get(&args[0]); + match ret_layout { + WasmLayout::Primitive(ret_type, _) => match stored { + VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { + match (ret_type, value_type) { + (F32, I32) => code_builder.f32_convert_s_i32(), + (F32, I64) => code_builder.f32_convert_s_i64(), + (F32, F32) => {} + (F32, F64) => code_builder.f32_demote_f64(), + + (F64, I32) => code_builder.f64_convert_s_i32(), + (F64, I64) => code_builder.f64_convert_s_i64(), + (F64, F32) => code_builder.f64_promote_f32(), + (F64, F64) => {} + + _ => panic_ret_type(), + } + } + StackMemory { .. } => return NotImplemented, + }, + WasmLayout::StackMemory { .. } => return NotImplemented, + } + } NumPow => return NotImplemented, - NumCeiling => match ret_layout.value_type() { - I32 => { - code_builder.f32_ceil(); - code_builder.i32_trunc_s_f32() - } - I64 => { - code_builder.f64_ceil(); - code_builder.i64_trunc_s_f64() - } - _ => panic_ret_type(), + NumCeiling => match ret_layout { + WasmLayout::Primitive(value_type, _) => match value_type { + I32 => { + code_builder.f32_ceil(); + code_builder.i32_trunc_s_f32() + } + I64 => { + code_builder.f64_ceil(); + code_builder.i64_trunc_s_f64() + } + _ => panic_ret_type(), + }, + WasmLayout::StackMemory { .. } => return NotImplemented, }, NumPowInt => return NotImplemented, - NumFloor => match ret_layout.value_type() { - I32 => { - code_builder.f32_floor(); - code_builder.i32_trunc_s_f32() - } - I64 => { - code_builder.f64_floor(); - code_builder.i64_trunc_s_f64() - } - _ => panic_ret_type(), + NumFloor => match ret_layout { + WasmLayout::Primitive(value_type, _) => match value_type { + I32 => { + code_builder.f32_floor(); + code_builder.i32_trunc_s_f32() + } + I64 => { + code_builder.f64_floor(); + code_builder.i64_trunc_s_f64() + } + _ => panic_ret_type(), + }, + WasmLayout::StackMemory { .. } => return NotImplemented, }, - NumIsFinite => match ret_layout.value_type() { - I32 => code_builder.i32_const(1), - I64 => code_builder.i32_const(1), - F32 => { - code_builder.i32_reinterpret_f32(); - code_builder.i32_const(0x7f800000); - code_builder.i32_and(); - code_builder.i32_const(0x7f800000); - code_builder.i32_ne(); - } - F64 => { - code_builder.i64_reinterpret_f64(); - code_builder.i64_const(0x7ff0000000000000); - code_builder.i64_and(); - code_builder.i64_const(0x7ff0000000000000); - code_builder.i64_ne(); - } + NumIsFinite => match ret_layout { + WasmLayout::Primitive(value_type, _) => match value_type { + I32 => code_builder.i32_const(1), + I64 => code_builder.i32_const(1), + F32 => { + code_builder.i32_reinterpret_f32(); + code_builder.i32_const(0x7f800000); + code_builder.i32_and(); + code_builder.i32_const(0x7f800000); + code_builder.i32_ne(); + } + F64 => { + code_builder.i64_reinterpret_f64(); + code_builder.i64_const(0x7ff0000000000000); + code_builder.i64_and(); + code_builder.i64_const(0x7ff0000000000000); + code_builder.i64_ne(); + } + }, + WasmLayout::StackMemory { .. } => return NotImplemented, }, NumAtan => { let width = float_width_from_layout(ret_layout); @@ -286,84 +379,123 @@ pub fn decode_low_level<'a>( } NumBytesToU16 => return NotImplemented, NumBytesToU32 => return NotImplemented, - NumBitwiseAnd => match ret_layout.value_type() { - I32 => code_builder.i32_and(), - I64 => code_builder.i64_and(), - _ => panic_ret_type(), + NumBitwiseAnd => match ret_layout { + WasmLayout::Primitive(value_type, _) => match value_type { + I32 => code_builder.i32_and(), + I64 => code_builder.i64_and(), + _ => panic_ret_type(), + }, + WasmLayout::StackMemory { .. } => return NotImplemented, }, - NumBitwiseXor => match ret_layout.value_type() { - I32 => code_builder.i32_xor(), - I64 => code_builder.i64_xor(), - _ => panic_ret_type(), + NumBitwiseXor => match ret_layout { + WasmLayout::Primitive(value_type, _) => match value_type { + I32 => code_builder.i32_xor(), + I64 => code_builder.i64_xor(), + _ => panic_ret_type(), + }, + WasmLayout::StackMemory { .. } => return NotImplemented, }, - NumBitwiseOr => match ret_layout.value_type() { - I32 => code_builder.i32_or(), - I64 => code_builder.i64_or(), - _ => panic_ret_type(), + NumBitwiseOr => match ret_layout { + WasmLayout::Primitive(value_type, _) => match value_type { + I32 => code_builder.i32_or(), + I64 => code_builder.i64_or(), + _ => panic_ret_type(), + }, + WasmLayout::StackMemory { .. } => return NotImplemented, }, NumShiftLeftBy => { - // Unfortunate local.set/local.get + // Swap order of arguments storage.load_symbols(code_builder, &[args[1], args[0]]); - match ret_layout.value_type() { - I32 => code_builder.i32_shl(), - I64 => code_builder.i64_shl(), - _ => panic_ret_type(), + match ret_layout { + WasmLayout::Primitive(value_type, _) => match value_type { + I32 => code_builder.i32_shl(), + I64 => code_builder.i64_shl(), + _ => panic_ret_type(), + }, + WasmLayout::StackMemory { .. } => return NotImplemented, } } - NumShiftRightBy => match ret_layout.value_type() { - I32 => code_builder.i32_shr_s(), - I64 => code_builder.i64_shr_s(), - _ => panic_ret_type(), + NumShiftRightBy => match ret_layout { + WasmLayout::Primitive(value_type, _) => match value_type { + I32 => code_builder.i32_shr_s(), + I64 => code_builder.i64_shr_s(), + _ => panic_ret_type(), + }, + WasmLayout::StackMemory { .. } => return NotImplemented, }, - NumShiftRightZfBy => match ret_layout.value_type() { - I32 => code_builder.i32_shr_u(), - I64 => code_builder.i64_shr_u(), - _ => panic_ret_type(), + NumShiftRightZfBy => match ret_layout { + WasmLayout::Primitive(value_type, _) => match value_type { + I32 => code_builder.i32_shr_u(), + I64 => code_builder.i64_shr_u(), + _ => panic_ret_type(), + }, + WasmLayout::StackMemory { .. } => return NotImplemented, }, - NumIntCast => match (ret_layout.value_type(), storage.get(&args[0]).value_type()) { - (I32, I32) => {} - (I32, I64) => code_builder.i32_wrap_i64(), - (I32, F32) => code_builder.i32_trunc_s_f32(), - (I32, F64) => code_builder.i32_trunc_s_f64(), + NumIntCast => { + use StoredValue::*; + let stored = storage.get(&args[0]); + match ret_layout { + WasmLayout::Primitive(ret_type, _) => match stored { + VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { + match (ret_type, value_type) { + (I32, I32) => {} + (I32, I64) => code_builder.i32_wrap_i64(), + (I32, F32) => code_builder.i32_trunc_s_f32(), + (I32, F64) => code_builder.i32_trunc_s_f64(), - (I64, I32) => code_builder.i64_extend_s_i32(), - (I64, I64) => {} - (I64, F32) => code_builder.i64_trunc_s_f32(), - (I64, F64) => code_builder.i64_trunc_s_f64(), + (I64, I32) => code_builder.i64_extend_s_i32(), + (I64, I64) => {} + (I64, F32) => code_builder.i64_trunc_s_f32(), + (I64, F64) => code_builder.i64_trunc_s_f64(), - (F32, I32) => code_builder.f32_convert_s_i32(), - (F32, I64) => code_builder.f32_convert_s_i64(), - (F32, F32) => {} - (F32, F64) => code_builder.f32_demote_f64(), + (F32, I32) => code_builder.f32_convert_s_i32(), + (F32, I64) => code_builder.f32_convert_s_i64(), + (F32, F32) => {} + (F32, F64) => code_builder.f32_demote_f64(), - (F64, I32) => code_builder.f64_convert_s_i32(), - (F64, I64) => code_builder.f64_convert_s_i64(), - (F64, F32) => code_builder.f64_promote_f32(), - (F64, F64) => {} - }, - Eq => { - // TODO: For non-number types, this will implement pointer equality, which is wrong - match storage.get(&args[0]).value_type() { + (F64, I32) => code_builder.f64_convert_s_i32(), + (F64, I64) => code_builder.f64_convert_s_i64(), + (F64, F32) => code_builder.f64_promote_f32(), + (F64, F64) => {} + } + } + + StackMemory { .. } => return NotImplemented, + }, + WasmLayout::StackMemory { .. } => return NotImplemented, + } + } + Eq => match storage.get(&args[0]) { + StoredValue::VirtualMachineStack { value_type, .. } + | StoredValue::Local { value_type, .. } => match value_type { I32 => code_builder.i32_eq(), I64 => code_builder.i64_eq(), F32 => code_builder.f32_eq(), F64 => code_builder.f64_eq(), - } - } - NotEq => { - // TODO: For non-number types, this will implement pointer inequality, which is wrong - match storage.get(&args[0]).value_type() { + }, + StoredValue::StackMemory { .. } => return NotImplemented, + }, + NotEq => match storage.get(&args[0]) { + StoredValue::VirtualMachineStack { value_type, .. } + | StoredValue::Local { value_type, .. } => match value_type { I32 => code_builder.i32_ne(), I64 => code_builder.i64_ne(), F32 => code_builder.f32_ne(), F64 => code_builder.f64_ne(), - } - } + }, + StoredValue::StackMemory { .. } => return NotImplemented, + }, And => code_builder.i32_and(), Or => code_builder.i32_or(), Not => code_builder.i32_eqz(), Hash => return NotImplemented, ExpectTrue => return NotImplemented, + RefCountGetPtr => { + code_builder.i32_const(4); + code_builder.i32_sub(); + } + RefCountInc => return BuiltinCall(bitcode::UTILS_INCREF), + RefCountDec => return BuiltinCall(bitcode::UTILS_DECREF), } Done } @@ -390,9 +522,9 @@ fn wrap_i32(code_builder: &mut CodeBuilder, size: u32) { } fn float_width_from_layout(wasm_layout: &WasmLayout) -> FloatWidth { - if wasm_layout.value_type() == ValueType::F32 { - FloatWidth::F32 - } else { - FloatWidth::F64 + match wasm_layout { + WasmLayout::Primitive(F32, _) => FloatWidth::F32, + WasmLayout::Primitive(F64, _) => FloatWidth::F64, + _ => panic!("{:?} does not have a FloatWidth", wasm_layout), } } diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 64bbf90cb4..9ae9aee67b 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -4,9 +4,11 @@ use bumpalo::Bump; use roc_collections::all::MutMap; use roc_module::symbol::Symbol; -use crate::layout::{StackMemoryFormat, WasmLayout}; +use crate::layout::{ + CallConv, ReturnMethod, StackMemoryFormat, WasmLayout, ZigVersion, BUILTINS_ZIG_VERSION, +}; use crate::wasm_module::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState}; -use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_SIZE, PTR_TYPE}; +use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_TYPE}; pub enum StoredValueKind { Parameter, @@ -55,17 +57,29 @@ pub enum StoredValue { } impl StoredValue { - pub fn value_type(&self) -> ValueType { + /// Value types to pass to Wasm functions + /// One Roc value can become 0, 1, or 2 Wasm arguments + pub fn arg_types(&self, conv: CallConv) -> &'static [ValueType] { + use ValueType::*; match self { - Self::VirtualMachineStack { value_type, .. } => *value_type, - Self::Local { value_type, .. } => *value_type, - Self::StackMemory { .. } => ValueType::I32, + // Simple numbers: 1 Roc argument => 1 Wasm argument + Self::VirtualMachineStack { value_type, .. } | Self::Local { value_type, .. } => { + match value_type { + I32 => &[I32], + I64 => &[I64], + F32 => &[F32], + F64 => &[F64], + } + } + // Stack memory values: 1 Roc argument => 0-2 Wasm arguments + Self::StackMemory { size, format, .. } => conv.stack_memory_arg_types(*size, *format), } } } /// Helper structure for WasmBackend, to keep track of how values are stored, /// including the VM stack, local variables, and linear memory +#[derive(Debug)] pub struct Storage<'a> { pub arg_types: Vec<'a, ValueType>, pub local_types: Vec<'a, ValueType>, @@ -133,18 +147,6 @@ impl<'a> Storage<'a> { }, }, - WasmLayout::HeapMemory => { - match kind { - StoredValueKind::Parameter => self.arg_types.push(PTR_TYPE), - _ => self.local_types.push(PTR_TYPE), - } - StoredValue::Local { - local_id: next_local_id, - value_type: PTR_TYPE, - size: PTR_SIZE, - } - } - WasmLayout::StackMemory { size, alignment_bytes, @@ -152,12 +154,18 @@ impl<'a> Storage<'a> { } => { let location = match kind { StoredValueKind::Parameter => { - self.arg_types.push(PTR_TYPE); - StackMemoryLocation::PointerArg(next_local_id) + if *size > 0 { + self.arg_types.push(PTR_TYPE); + StackMemoryLocation::PointerArg(next_local_id) + } else { + // An argument with zero size is purely conceptual, and will not exist in Wasm. + // However we need to track the symbol, so we treat it like a local variable. + StackMemoryLocation::FrameOffset(0) + } } StoredValueKind::Variable => { - if self.stack_frame_pointer.is_none() { + if self.stack_frame_pointer.is_none() && *size > 0 { self.stack_frame_pointer = Some(next_local_id); self.local_types.push(PTR_TYPE); } @@ -243,13 +251,20 @@ impl<'a> Storage<'a> { } StoredValue::StackMemory { - location, format, .. + location, + format, + size, + .. } => { + if size == 0 { + return; + } + let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer); code_builder.get_local(local_id); - if format == StackMemoryFormat::Aggregate { + if format == StackMemoryFormat::DataStructure { if offset != 0 { code_builder.i32_const(offset as i32); code_builder.i32_add(); @@ -269,6 +284,44 @@ impl<'a> Storage<'a> { } } + fn load_symbol_zig(&mut self, code_builder: &mut CodeBuilder, arg: Symbol) { + if let StoredValue::StackMemory { + location, + size, + alignment_bytes, + format: StackMemoryFormat::DataStructure, + } = self.get(&arg) + { + if *size == 0 { + // do nothing + } else if *size > 16 { + self.load_symbol_ccc(code_builder, arg); + } else { + let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer); + code_builder.get_local(local_id); + let align = Align::from(*alignment_bytes); + + if *size == 1 { + code_builder.i32_load8_u(align, offset); + } else if *size == 2 { + code_builder.i32_load16_u(align, offset); + } else if *size <= 4 { + code_builder.i32_load(align, offset); + } else if *size <= 8 { + code_builder.i64_load(align, offset); + } else if *size <= 12 && BUILTINS_ZIG_VERSION == ZigVersion::Zig9 { + code_builder.i64_load(align, offset); + code_builder.i32_load(align, offset + 8); + } else { + code_builder.i64_load(align, offset); + code_builder.i64_load(align, offset + 8); + } + } + } else { + self.load_symbol_ccc(code_builder, arg); + } + } + /// stack memory values are returned by pointer. e.g. a roc function /// /// add : I128, I128 -> I128 @@ -284,7 +337,11 @@ impl<'a> Storage<'a> { StoredValue::VirtualMachineStack { .. } | StoredValue::Local { .. } => { unreachable!("these storage types are not returned by writing to a pointer") } - StoredValue::StackMemory { location, .. } => { + StoredValue::StackMemory { location, size, .. } => { + if size == 0 { + return; + } + let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer); code_builder.get_local(local_id); @@ -311,58 +368,58 @@ impl<'a> Storage<'a> { } } - /// Load symbols in a way compatible with LLVM's "fast calling convention" - /// A bug in Zig means it always uses this for Wasm even when we specify C calling convention. - /// It squashes small structs into primitive values where possible, avoiding stack memory - /// in favour of CPU registers (or VM stack values, which eventually become CPU registers). - /// We need to convert some of our structs from our internal C-like representation to work with Zig. - /// We are sticking to C ABI for better compatibility on the platform side. - pub fn load_symbols_fastcc( + /// Load symbols for a function call + pub fn load_symbols_for_call( &mut self, + arena: &'a Bump, code_builder: &mut CodeBuilder, - symbols: &[Symbol], + arguments: &[Symbol], return_symbol: Symbol, return_layout: &WasmLayout, - ) { - // Note: we are not doing verify_stack_match in this case so we may generate more code. - // We would need more bookkeeping in CodeBuilder to track which representation is on the stack! + call_conv: CallConv, + ) -> (Vec<'a, ValueType>, Option) { + let mut wasm_arg_types = Vec::with_capacity_in(arguments.len() * 2 + 1, arena); + let mut wasm_args = Vec::with_capacity_in(arguments.len() * 2 + 1, arena); - if return_layout.is_stack_memory() { - // Load the address where the return value should be written - self.load_return_address_ccc(code_builder, return_symbol); - } + let return_method = return_layout.return_method(); + let return_type = match return_method { + ReturnMethod::Primitive(ty) => Some(ty), + ReturnMethod::NoReturnValue => None, + ReturnMethod::WriteToPointerArg => { + wasm_arg_types.push(PTR_TYPE); + wasm_args.push(return_symbol); + None + } + }; - for sym in symbols { - if let StoredValue::StackMemory { - location, - size, - alignment_bytes, - format: StackMemoryFormat::Aggregate, - } = self.get(sym) - { - if *size == 0 { - unimplemented!("Passing zero-sized values is not implemented yet"); - } else if *size > 8 { - return self.load_symbol_ccc(code_builder, *sym); - } - - let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer); - code_builder.get_local(local_id); - let align = Align::from(*alignment_bytes); - - if *size == 1 { - code_builder.i32_load8_u(align, offset); - } else if *size == 2 { - code_builder.i32_load16_u(align, offset); - } else if *size <= 4 { - code_builder.i32_load(align, offset); - } else { - code_builder.i64_load(align, offset); - } - } else { - self.load_symbol_ccc(code_builder, *sym); + for arg in arguments { + let stored = self.symbol_storage_map.get(arg).unwrap(); + let arg_types = stored.arg_types(call_conv); + wasm_arg_types.extend_from_slice(arg_types); + match arg_types.len() { + 0 => {} + 1 => wasm_args.push(*arg), + 2 => wasm_args.extend_from_slice(&[*arg, *arg]), + n => unreachable!("Cannot have {} Wasm arguments for 1 Roc argument", n), } } + + // If the symbols were already at the top of the stack, do nothing! + // Should be common for simple cases, due to the structure of the Mono IR + if !code_builder.verify_stack_match(&wasm_args) { + if return_method == ReturnMethod::WriteToPointerArg { + self.load_return_address_ccc(code_builder, return_symbol); + }; + + for arg in arguments { + match call_conv { + CallConv::C => self.load_symbol_ccc(code_builder, *arg), + CallConv::Zig => self.load_symbol_zig(code_builder, *arg), + } + } + } + + (wasm_arg_types, return_type) } /// Generate code to copy a StoredValue to an arbitrary memory location diff --git a/compiler/gen_wasm/src/wasm_module/code_builder.rs b/compiler/gen_wasm/src/wasm_module/code_builder.rs index 2109f3e436..10e6408cc3 100644 --- a/compiler/gen_wasm/src/wasm_module/code_builder.rs +++ b/compiler/gen_wasm/src/wasm_module/code_builder.rs @@ -50,6 +50,15 @@ impl BlockType { } } +impl From> for BlockType { + fn from(opt: Option) -> Self { + match opt { + Some(ty) => BlockType::Value(ty), + None => BlockType::NoResult, + } + } +} + /// A control block in our model of the VM /// Child blocks cannot "see" values from their parent block struct VmBlock<'a> { @@ -225,7 +234,9 @@ impl<'a> CodeBuilder<'a> { pub fn set_top_symbol(&mut self, sym: Symbol) -> VmSymbolState { let current_stack = &mut self.vm_block_stack.last_mut().unwrap().value_stack; let pushed_at = self.code.len(); - let top_symbol: &mut Symbol = current_stack.last_mut().unwrap(); + let top_symbol: &mut Symbol = current_stack + .last_mut() + .unwrap_or_else(|| unreachable!("Empty stack when trying to set Symbol {:?}", sym)); *top_symbol = sym; VmSymbolState::Pushed { pushed_at } @@ -429,10 +440,12 @@ impl<'a> CodeBuilder<'a> { ) { self.build_local_declarations(local_types); - if let Some(frame_ptr_id) = frame_pointer { - let aligned_size = round_up_to_alignment(frame_size, FRAME_ALIGNMENT_BYTES); - self.build_stack_frame_push(aligned_size, frame_ptr_id); - self.build_stack_frame_pop(aligned_size, frame_ptr_id); + if frame_size != 0 { + if let Some(frame_ptr_id) = frame_pointer { + let aligned_size = round_up_to_alignment(frame_size, FRAME_ALIGNMENT_BYTES); + self.build_stack_frame_push(aligned_size, frame_ptr_id); + self.build_stack_frame_pop(aligned_size, frame_ptr_id); + } } self.code.push(END as u8); diff --git a/compiler/gen_wasm/test-compare.sh b/compiler/gen_wasm/test-compare.sh deleted file mode 100755 index 31529968b9..0000000000 --- a/compiler/gen_wasm/test-compare.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -if [[ -z "$1" || -z "$2" ]] -then - echo "$0 needs 2 arguments: the directories to compare" - exit 1 -fi - - -OVERHEAD_BYTES=114 # total file size minus generated code size (test wrapper + module headers) - - -printf "filename \tLHS\tRHS\tchange\n" -printf "======== \t===\t===\t======\n" - -for f in `ls $1/wasm` -do - if [[ ! -f "$2/wasm/$f" ]] - then - echo "$f found in $1/wasm but not in $2/wasm" - continue - fi - SIZE1=$(stat --format '%s' "$1/wasm/$f") - SIZE2=$(stat --format '%s' "$2/wasm/$f") - CHANGE=$(( $SIZE2 - $SIZE1 )) - NET_SIZE1=$(( $SIZE1 - $OVERHEAD_BYTES )) - NET_SIZE2=$(( $SIZE2 - $OVERHEAD_BYTES )) - PERCENT_CHANGE=$(( $CHANGE * 100 / $NET_SIZE1 )) - printf "%s\t%d\t%d\t%d\t%d%%\n" $f $NET_SIZE1 $NET_SIZE2 $CHANGE $PERCENT_CHANGE -done diff --git a/compiler/gen_wasm/test-run.sh b/compiler/gen_wasm/test-run.sh deleted file mode 100755 index df7b015e05..0000000000 --- a/compiler/gen_wasm/test-run.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -TARGET_DIR=$1 - -if [[ -z "$TARGET_DIR" ]] -then - echo "$0 needs an argument: target directory for output wasm and wat files" - exit 1 -fi - -rm -rf output $TARGET_DIR -mkdir -p output $TARGET_DIR $TARGET_DIR/wasm $TARGET_DIR/wat -cargo test -- --test-threads=1 --nocapture - -mv output/* $TARGET_DIR/wasm - -for f in `ls $TARGET_DIR/wasm` -do - wasm2wat $TARGET_DIR/wasm/$f -o $TARGET_DIR/wat/${f%.wasm}.wat -done - - -SIZE=$(du -b "$TARGET_DIR/wasm") -echo "Total bytes *.wasm = $SIZE" diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 4ac1f371e1..c75a803ea2 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -20,6 +20,7 @@ use roc_module::symbol::{ }; use roc_mono::ir::{ CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs, + UpdateModeIds, }; use roc_mono::layout::{Layout, LayoutCache, LayoutProblem}; use roc_parse::ast::{self, StrLiteral, TypeAnnotation}; @@ -835,6 +836,7 @@ enum Msg<'a> { external_specializations_requested: BumpMap, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, problems: Vec, + update_mode_ids: UpdateModeIds, module_timing: ModuleTiming, subs: Subs, }, @@ -2098,6 +2100,7 @@ fn update<'a>( MadeSpecializations { module_id, mut ident_ids, + mut update_mode_ids, subs, procedures, external_specializations_requested, @@ -2124,6 +2127,7 @@ fn update<'a>( arena, module_id, &mut ident_ids, + &mut update_mode_ids, &mut state.procedures, ); @@ -3922,6 +3926,7 @@ fn make_specializations<'a>( ) -> Msg<'a> { let make_specializations_start = SystemTime::now(); let mut mono_problems = Vec::new(); + let mut update_mode_ids = UpdateModeIds::new(); // do the thing let mut mono_env = roc_mono::ir::Env { arena, @@ -3930,7 +3935,7 @@ fn make_specializations<'a>( home, ident_ids: &mut ident_ids, ptr_bytes, - update_mode_counter: 0, + update_mode_ids: &mut update_mode_ids, // call_specialization_counter=0 is reserved call_specialization_counter: 1, }; @@ -3973,6 +3978,7 @@ fn make_specializations<'a>( layout_cache, procedures, problems: mono_problems, + update_mode_ids, subs, external_specializations_requested, module_timing, @@ -4016,6 +4022,7 @@ fn build_pending_specializations<'a>( }; let mut mono_problems = std::vec::Vec::new(); + let mut update_mode_ids = UpdateModeIds::new(); let mut subs = solved_subs.into_inner(); let mut mono_env = roc_mono::ir::Env { arena, @@ -4024,7 +4031,7 @@ fn build_pending_specializations<'a>( home, ident_ids: &mut ident_ids, ptr_bytes, - update_mode_counter: 0, + update_mode_ids: &mut update_mode_ids, // call_specialization_counter=0 is reserved call_specialization_counter: 1, }; diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 9405956fc0..92dafda7ae 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -108,6 +108,7 @@ pub enum LowLevel { NumShiftRightBy, NumShiftRightZfBy, NumIntCast, + NumToStr, Eq, NotEq, And, @@ -115,6 +116,9 @@ pub enum LowLevel { Not, Hash, ExpectTrue, + RefCountGetPtr, + RefCountInc, + RefCountDec, } macro_rules! higher_order { @@ -186,12 +190,10 @@ impl LowLevel { Symbol::STR_ENDS_WITH => Some(StrEndsWith), Symbol::STR_SPLIT => Some(StrSplit), Symbol::STR_COUNT_GRAPHEMES => Some(StrCountGraphemes), - Symbol::STR_FROM_INT => Some(StrFromInt), Symbol::STR_FROM_UTF8 => None, Symbol::STR_FROM_UTF8_RANGE => None, Symbol::STR_TO_UTF8 => Some(StrToUtf8), Symbol::STR_REPEAT => Some(StrRepeat), - Symbol::STR_FROM_FLOAT => Some(StrFromFloat), Symbol::STR_TRIM => Some(StrTrim), Symbol::STR_TRIM_LEFT => Some(StrTrimLeft), Symbol::STR_TRIM_RIGHT => Some(StrTrimRight), @@ -268,6 +270,7 @@ impl LowLevel { Symbol::NUM_CEILING => Some(NumCeiling), Symbol::NUM_POW_INT => Some(NumPowInt), Symbol::NUM_FLOOR => Some(NumFloor), + Symbol::NUM_TO_STR => Some(NumToStr), // => Some(NumIsFinite), Symbol::NUM_ATAN => Some(NumAtan), Symbol::NUM_ACOS => Some(NumAcos), diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 2dbdc47ce3..51560a96f3 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -992,6 +992,7 @@ define_builtins! { 104 NUM_BYTES_TO_U32: "bytesToU32" 105 NUM_CAST_TO_NAT: "#castToNat" 106 NUM_DIV_CEIL: "divCeil" + 107 NUM_TO_STR: "toStr" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias @@ -1017,19 +1018,17 @@ define_builtins! { 7 STR_COUNT_GRAPHEMES: "countGraphemes" 8 STR_STARTS_WITH: "startsWith" 9 STR_ENDS_WITH: "endsWith" - 10 STR_FROM_INT: "fromInt" - 11 STR_FROM_FLOAT: "fromFloat" - 12 STR_FROM_UTF8: "fromUtf8" - 13 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias - 14 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias - 15 STR_TO_UTF8: "toUtf8" - 16 STR_STARTS_WITH_CODE_PT: "startsWithCodePt" - 17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime - 18 STR_FROM_UTF8_RANGE: "fromUtf8Range" - 19 STR_REPEAT: "repeat" - 20 STR_TRIM: "trim" - 21 STR_TRIM_LEFT: "trimLeft" - 22 STR_TRIM_RIGHT: "trimRight" + 10 STR_FROM_UTF8: "fromUtf8" + 11 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias + 12 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias + 13 STR_TO_UTF8: "toUtf8" + 14 STR_STARTS_WITH_CODE_PT: "startsWithCodePt" + 15 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime + 16 STR_FROM_UTF8_RANGE: "fromUtf8Range" + 17 STR_REPEAT: "repeat" + 18 STR_TRIM: "trim" + 19 STR_TRIM_LEFT: "trimLeft" + 20 STR_TRIM_RIGHT: "trimRight" } 4 LIST: "List" => { 0 LIST_LIST: "List" imported // the List.List type alias diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index 88a2b1ce46..6481f924c4 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -210,12 +210,15 @@ where let mut builder = TypeDefBuilder::new(); - let variant_types = build_variant_types(&mut builder, &union_layout)?; + let variant_types = recursive_variant_types(&mut builder, &union_layout)?; let root_type = if let UnionLayout::NonNullableUnwrapped(_) = union_layout { debug_assert_eq!(variant_types.len(), 1); variant_types[0] } else { - builder.add_union_type(&variant_types)? + let data_type = builder.add_union_type(&variant_types)?; + let cell_type = builder.add_heap_cell_type(); + + builder.add_tuple_type(&[cell_type, data_type])? }; let type_def = builder.build(root_type)?; @@ -699,30 +702,30 @@ fn call_spec( call.arguments, ), HigherOrder(HigherOrderLowLevel { - specialization_id, closure_env_layout, update_mode, op, - arg_layouts, - ret_layout, - function_name, - function_env, + passed_function, .. }) => { use crate::low_level::HigherOrder::*; - let array = specialization_id.to_bytes(); + let array = passed_function.specialization_id.to_bytes(); let spec_var = CalleeSpecVar(&array); let mode = update_mode.to_bytes(); let update_mode_var = UpdateModeVar(&mode); - let it = arg_layouts.iter().copied(); - let bytes = func_name_bytes_help(*function_name, it, ret_layout); + let it = passed_function.argument_layouts.iter().copied(); + let bytes = + func_name_bytes_help(passed_function.name, it, &passed_function.return_layout); let name = FuncName(&bytes); let module = MOD_APP; - let closure_env = env.symbols[function_env]; + let closure_env = env.symbols[&passed_function.captured_environment]; + + let return_layout = &passed_function.return_layout; + let argument_layouts = passed_function.argument_layouts; macro_rules! call_function { ($builder: expr, $block:expr, [$($arg:expr),+ $(,)?]) => {{ @@ -754,7 +757,7 @@ fn call_spec( Ok(new_state) }; - let state_layout = arg_layouts[0]; + let state_layout = argument_layouts[0]; let state_type = layout_spec(builder, &state_layout)?; let init_state = state; @@ -775,7 +778,7 @@ fn call_spec( Ok(new_state) }; - let state_layout = arg_layouts[0]; + let state_layout = argument_layouts[0]; let state_type = layout_spec(builder, &state_layout)?; let init_state = state; @@ -799,7 +802,7 @@ fn call_spec( Ok(new_state) }; - let state_layout = arg_layouts[0]; + let state_layout = argument_layouts[0]; let state_type = layout_spec(builder, &state_layout)?; let init_state = state; @@ -820,9 +823,9 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, ret_layout)?; + let output_element_type = layout_spec(builder, return_layout)?; - let state_layout = Layout::Builtin(Builtin::List(ret_layout)); + let state_layout = Layout::Builtin(Builtin::List(return_layout)); let state_type = layout_spec(builder, &state_layout)?; let init_state = new_list(builder, block, output_element_type)?; @@ -843,9 +846,9 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, ret_layout)?; + let output_element_type = layout_spec(builder, return_layout)?; - let state_layout = Layout::Builtin(Builtin::List(ret_layout)); + let state_layout = Layout::Builtin(Builtin::List(return_layout)); let state_type = layout_spec(builder, &state_layout)?; let init_state = new_list(builder, block, output_element_type)?; @@ -867,11 +870,10 @@ fn call_spec( builder.add_update(block, update_mode_var, cell)?; - let new_cell = builder.add_new_heap_cell(block)?; - builder.add_make_tuple(block, &[new_cell, bag]) + with_new_heap_cell(builder, block, bag) }; - let state_layout = Layout::Builtin(Builtin::List(&arg_layouts[0])); + let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0])); let state_type = layout_spec(builder, &state_layout)?; let init_state = list; @@ -896,9 +898,9 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, ret_layout)?; + let output_element_type = layout_spec(builder, return_layout)?; - let state_layout = Layout::Builtin(Builtin::List(ret_layout)); + let state_layout = Layout::Builtin(Builtin::List(return_layout)); let state_type = layout_spec(builder, &state_layout)?; let init_state = new_list(builder, block, output_element_type)?; @@ -929,9 +931,9 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, ret_layout)?; + let output_element_type = layout_spec(builder, return_layout)?; - let state_layout = Layout::Builtin(Builtin::List(ret_layout)); + let state_layout = Layout::Builtin(Builtin::List(return_layout)); let state_type = layout_spec(builder, &state_layout)?; let init_state = new_list(builder, block, output_element_type)?; @@ -968,9 +970,9 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, ret_layout)?; + let output_element_type = layout_spec(builder, return_layout)?; - let state_layout = Layout::Builtin(Builtin::List(ret_layout)); + let state_layout = Layout::Builtin(Builtin::List(return_layout)); let state_type = layout_spec(builder, &state_layout)?; let init_state = new_list(builder, block, output_element_type)?; @@ -998,12 +1000,11 @@ fn call_spec( builder.add_recursive_touch(block, removed_element)?; let new_bag = builder.add_get_tuple_field(block, removed, 0)?; - let new_cell = builder.add_new_heap_cell(block)?; - builder.add_make_tuple(block, &[new_cell, new_bag]) + with_new_heap_cell(builder, block, new_bag) }; - let state_layout = Layout::Builtin(Builtin::List(&arg_layouts[0])); + let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0])); let state_type = layout_spec(builder, &state_layout)?; let init_state = list; @@ -1018,7 +1019,7 @@ fn call_spec( _ => unreachable!(), }; - let result_repr = ResultRepr::from_layout(ret_layout); + let result_repr = ResultRepr::from_layout(return_layout); let output_element_layout = match (keep_result, result_repr) { (KeepResult::Errs, ResultRepr::ResultConcrete { err, .. }) => err, @@ -1131,7 +1132,7 @@ fn call_spec( let list = env.symbols[xs]; // ListFindUnsafe returns { value: v, found: Bool=Int1 } - let output_layouts = vec![arg_layouts[0], Layout::Builtin(Builtin::Bool)]; + let output_layouts = vec![argument_layouts[0], Layout::Builtin(Builtin::Bool)]; let output_layout = Layout::Struct(&output_layouts); let output_type = layout_spec(builder, &output_layout)?; @@ -1181,8 +1182,7 @@ fn list_append( let new_bag = builder.add_bag_insert(block, bag, to_insert)?; - let new_cell = builder.add_new_heap_cell(block)?; - builder.add_make_tuple(block, &[new_cell, new_bag]) + with_new_heap_cell(builder, block, new_bag) } fn lowlevel_spec( @@ -1268,8 +1268,7 @@ fn lowlevel_spec( builder.add_bag_insert(block, bag, to_insert)?; - let new_cell = builder.add_new_heap_cell(block)?; - builder.add_make_tuple(block, &[new_cell, bag]) + with_new_heap_cell(builder, block, bag) } ListSwap => { let list = env.symbols[&arguments[0]]; @@ -1279,8 +1278,7 @@ fn lowlevel_spec( let _unit = builder.add_update(block, update_mode_var, cell)?; - let new_cell = builder.add_new_heap_cell(block)?; - builder.add_make_tuple(block, &[new_cell, bag]) + with_new_heap_cell(builder, block, bag) } ListReverse => { let list = env.symbols[&arguments[0]]; @@ -1290,8 +1288,7 @@ fn lowlevel_spec( let _unit = builder.add_update(block, update_mode_var, cell)?; - let new_cell = builder.add_new_heap_cell(block)?; - builder.add_make_tuple(block, &[new_cell, bag]) + with_new_heap_cell(builder, block, bag) } ListAppend => { let list = env.symbols[&arguments[0]]; @@ -1359,8 +1356,7 @@ fn lowlevel_spec( builder.add_bag_insert(block, bag, key_value)?; - let new_cell = builder.add_new_heap_cell(block)?; - builder.add_make_tuple(block, &[new_cell, bag]) + with_new_heap_cell(builder, block, bag) } _other => { // println!("missing {:?}", _other); @@ -1381,13 +1377,10 @@ fn recursive_tag_variant( ) -> Result { let when_recursive = WhenRecursive::Loop(*union_layout); - let data_id = build_recursive_tuple_type(builder, fields, &when_recursive)?; - let cell_id = builder.add_heap_cell_type(); - - builder.add_tuple_type(&[cell_id, data_id]) + build_recursive_tuple_type(builder, fields, &when_recursive) } -fn build_variant_types( +fn recursive_variant_types( builder: &mut impl TypeContext, union_layout: &UnionLayout, ) -> Result> { @@ -1396,12 +1389,8 @@ fn build_variant_types( let mut result; match union_layout { - NonRecursive(tags) => { - result = Vec::with_capacity(tags.len()); - - for tag in tags.iter() { - result.push(build_tuple_type(builder, tag)?); - } + NonRecursive(_) => { + unreachable!() } Recursive(tags) => { result = Vec::with_capacity(tags.len()); @@ -1425,8 +1414,7 @@ fn build_variant_types( result.push(recursive_tag_variant(builder, union_layout, tag)?); } - let unit = builder.add_tuple_type(&[])?; - result.push(unit); + result.push(recursive_tag_variant(builder, union_layout, &[])?); for tag in tags[cutoff..].iter() { result.push(recursive_tag_variant(builder, union_layout, tag)?); @@ -1436,7 +1424,7 @@ fn build_variant_types( nullable_id, other_fields: fields, } => { - let unit = builder.add_tuple_type(&[])?; + let unit = recursive_tag_variant(builder, union_layout, &[])?; let other_type = recursive_tag_variant(builder, union_layout, fields)?; if *nullable_id { @@ -1482,18 +1470,16 @@ fn expr_spec<'a>( tag_id, arguments, } => { - let variant_types = build_variant_types(builder, tag_layout)?; - let data_id = build_tuple_value(builder, env, block, arguments)?; - let cell_id = builder.add_new_heap_cell(block)?; let value_id = match tag_layout { - UnionLayout::NonRecursive(_) => { + UnionLayout::NonRecursive(tags) => { + let variant_types = non_recursive_variant_types(builder, tags)?; let value_id = build_tuple_value(builder, env, block, arguments)?; return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id); } UnionLayout::NonNullableUnwrapped(_) => { - let value_id = builder.add_make_tuple(block, &[cell_id, data_id])?; + let value_id = data_id; let type_name_bytes = recursive_tag_union_name_bytes(tag_layout).as_bytes(); let type_name = TypeName(&type_name_bytes); @@ -1502,32 +1488,24 @@ fn expr_spec<'a>( return builder.add_make_named(block, MOD_APP, type_name, value_id); } - UnionLayout::Recursive(_) => builder.add_make_tuple(block, &[cell_id, data_id])?, - UnionLayout::NullableWrapped { nullable_id, .. } => { - if *tag_id == *nullable_id as _ { - data_id - } else { - builder.add_make_tuple(block, &[cell_id, data_id])? - } - } - UnionLayout::NullableUnwrapped { nullable_id, .. } => { - if *tag_id == *nullable_id as _ { - data_id - } else { - builder.add_make_tuple(block, &[cell_id, data_id])? - } - } + UnionLayout::Recursive(_) => data_id, + UnionLayout::NullableWrapped { .. } => data_id, + UnionLayout::NullableUnwrapped { .. } => data_id, }; + let variant_types = recursive_variant_types(builder, tag_layout)?; + let union_id = builder.add_make_union(block, &variant_types, *tag_id as u32, value_id)?; + let tag_value_id = with_new_heap_cell(builder, block, union_id)?; + let type_name_bytes = recursive_tag_union_name_bytes(tag_layout).as_bytes(); let type_name = TypeName(&type_name_bytes); env.type_names.insert(*tag_layout); - builder.add_make_named(block, MOD_APP, type_name, union_id) + builder.add_make_named(block, MOD_APP, type_name, tag_value_id) } Struct(fields) => build_tuple_value(builder, env, block, fields), UnionAtIndex { @@ -1553,16 +1531,20 @@ fn expr_spec<'a>( let type_name_bytes = recursive_tag_union_name_bytes(union_layout).as_bytes(); let type_name = TypeName(&type_name_bytes); + // unwrap the named wrapper let union_id = builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?; - let variant_id = builder.add_unwrap_union(block, union_id, *tag_id as u32)?; + + // now we have a tuple (cell, union { ... }); decompose + let heap_cell = builder.add_get_tuple_field(block, union_id, TAG_CELL_INDEX)?; + let union_data = builder.add_get_tuple_field(block, union_id, TAG_DATA_INDEX)?; // we're reading from this value, so touch the heap cell - let heap_cell = builder.add_get_tuple_field(block, variant_id, 0)?; builder.add_touch(block, heap_cell)?; - let tuple_value_id = builder.add_get_tuple_field(block, variant_id, 1)?; + // next, unwrap the union at the tag id that we've got + let variant_id = builder.add_unwrap_union(block, union_data, *tag_id as u32)?; - builder.add_get_tuple_field(block, tuple_value_id, index) + builder.add_get_tuple_field(block, variant_id, index) } UnionLayout::NonNullableUnwrapped { .. } => { let index = (*index) as u32; @@ -1573,16 +1555,20 @@ fn expr_spec<'a>( let type_name_bytes = recursive_tag_union_name_bytes(union_layout).as_bytes(); let type_name = TypeName(&type_name_bytes); - let variant_id = - builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?; + // a tuple ( cell, union { ... } ) + let union_id = builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?; + + // decompose + let heap_cell = builder.add_get_tuple_field(block, union_id, TAG_CELL_INDEX)?; + let union_data = builder.add_get_tuple_field(block, union_id, TAG_DATA_INDEX)?; // we're reading from this value, so touch the heap cell - let heap_cell = builder.add_get_tuple_field(block, variant_id, 0)?; builder.add_touch(block, heap_cell)?; - let tuple_value_id = builder.add_get_tuple_field(block, variant_id, 1)?; + // next, unwrap the union at the tag id that we've got + let variant_id = builder.add_unwrap_union(block, union_data, *tag_id as u32)?; - builder.add_get_tuple_field(block, tuple_value_id, index) + builder.add_get_tuple_field(block, variant_id, index) } }, StructAtIndex { @@ -1613,9 +1599,7 @@ fn expr_spec<'a>( if all_constants { new_static_list(builder, block) } else { - let cell = builder.add_new_heap_cell(block)?; - - builder.add_make_tuple(block, &[cell, bag]) + with_new_heap_cell(builder, block, bag) } } @@ -1626,7 +1610,7 @@ fn expr_spec<'a>( } _ => unreachable!("empty array does not have a list layout"), }, - Reset(symbol) => { + Reset { symbol, .. } => { let type_id = layout_spec(builder, layout)?; let value_id = env.symbols[symbol]; @@ -1637,7 +1621,11 @@ fn expr_spec<'a>( builder.add_terminate(block, type_id) } - GetTagId { .. } => builder.add_make_tuple(block, &[]), + GetTagId { .. } => { + // TODO touch heap cell in recursive cases + + builder.add_make_tuple(block, &[]) + } } } @@ -1658,6 +1646,19 @@ fn layout_spec(builder: &mut impl TypeContext, layout: &Layout) -> Result Result> { + let mut result = Vec::with_capacity(tags.len()); + + for tag in tags.iter() { + result.push(build_tuple_type(builder, tag)?); + } + + Ok(result) +} + fn layout_spec_help( builder: &mut impl TypeContext, layout: &Layout, @@ -1674,8 +1675,6 @@ fn layout_spec_help( when_recursive, ), Union(union_layout) => { - let variant_types = build_variant_types(builder, union_layout)?; - match union_layout { UnionLayout::NonRecursive(&[]) => { // must model Void as Unit, otherwise we run into problems where @@ -1683,7 +1682,10 @@ fn layout_spec_help( // which is of course not possible builder.add_tuple_type(&[]) } - UnionLayout::NonRecursive(_) => builder.add_union_type(&variant_types), + UnionLayout::NonRecursive(tags) => { + let variant_types = non_recursive_variant_types(builder, tags)?; + builder.add_union_type(&variant_types) + } UnionLayout::Recursive(_) | UnionLayout::NullableUnwrapped { .. } | UnionLayout::NullableWrapped { .. } @@ -1777,10 +1779,21 @@ const LIST_BAG_INDEX: u32 = 1; const DICT_CELL_INDEX: u32 = LIST_CELL_INDEX; const DICT_BAG_INDEX: u32 = LIST_BAG_INDEX; -fn new_list(builder: &mut FuncDefBuilder, block: BlockId, element_type: TypeId) -> Result { +const TAG_CELL_INDEX: u32 = 0; +const TAG_DATA_INDEX: u32 = 1; + +fn with_new_heap_cell( + builder: &mut FuncDefBuilder, + block: BlockId, + value: ValueId, +) -> Result { let cell = builder.add_new_heap_cell(block)?; + builder.add_make_tuple(block, &[cell, value]) +} + +fn new_list(builder: &mut FuncDefBuilder, block: BlockId, element_type: TypeId) -> Result { let bag = builder.add_empty_bag(block, element_type)?; - builder.add_make_tuple(block, &[cell, bag]) + with_new_heap_cell(builder, block, bag) } fn new_dict( @@ -1789,10 +1802,9 @@ fn new_dict( key_type: TypeId, value_type: TypeId, ) -> Result { - let cell = builder.add_new_heap_cell(block)?; let element_type = builder.add_tuple_type(&[key_type, value_type])?; let bag = builder.add_empty_bag(block, element_type)?; - builder.add_make_tuple(block, &[cell, bag]) + with_new_heap_cell(builder, block, bag) } fn new_static_string(builder: &mut FuncDefBuilder, block: BlockId) -> Result { diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index bc5492c8bf..8bb30cd1d2 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -595,20 +595,17 @@ impl<'a> BorrowInfState<'a> { HigherOrder(HigherOrderLowLevel { op, - arg_layouts, - ret_layout, - function_name, - function_env, + passed_function, .. }) => { use crate::low_level::HigherOrder::*; let closure_layout = ProcLayout { - arguments: arg_layouts, - result: *ret_layout, + arguments: passed_function.argument_layouts, + result: passed_function.return_layout, }; - let function_ps = match param_map.get_symbol(*function_name, closure_layout) { + let function_ps = match param_map.get_symbol(passed_function.name, closure_layout) { Some(function_ps) => function_ps, None => unreachable!(), }; @@ -692,7 +689,7 @@ impl<'a> BorrowInfState<'a> { // own the closure environment if the function needs to own it let function_env_position = op.function_arity(); if let Some(false) = function_ps.get(function_env_position).map(|p| p.borrow) { - self.own_var(*function_env); + self.own_var(passed_function.captured_environment); } } @@ -726,7 +723,7 @@ impl<'a> BorrowInfState<'a> { // the function must take it as an owned parameter self.own_args_if_param(xs); } - Reset(x) => { + Reset { symbol: x, .. } => { self.own_var(z); self.own_var(*x); } @@ -980,9 +977,9 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { | NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]), - NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound - | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin - | NumIntCast => arena.alloc_slice_copy(&[irrelevant]), + NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked + | NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos + | NumAsin | NumIntCast => arena.alloc_slice_copy(&[irrelevant]), NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]), NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]), @@ -1008,6 +1005,10 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { SetFromList => arena.alloc_slice_copy(&[owned]), ExpectTrue => arena.alloc_slice_copy(&[irrelevant]), + + RefCountGetPtr | RefCountInc | RefCountDec => { + unreachable!("Refcounting lowlevel calls are inserted *after* borrow checking"); + } } } diff --git a/compiler/mono/src/gen_refcount.rs b/compiler/mono/src/gen_refcount.rs new file mode 100644 index 0000000000..53cc12b73b --- /dev/null +++ b/compiler/mono/src/gen_refcount.rs @@ -0,0 +1,410 @@ +use bumpalo::collections::vec::Vec; +use bumpalo::Bump; +use roc_builtins::bitcode::IntWidth; +use roc_module::ident::Ident; +use roc_module::low_level::LowLevel; +use roc_module::symbol::{IdentIds, ModuleId, Symbol}; + +use crate::ir::{ + BranchInfo, Call, CallSpecId, CallType, Expr, HostExposedLayouts, Literal, ModifyRc, Proc, + ProcLayout, SelfRecursive, Stmt, UpdateModeId, +}; +use crate::layout::{Builtin, Layout}; + +const LAYOUT_BOOL: Layout = Layout::Builtin(Builtin::Bool); +const LAYOUT_UNIT: Layout = Layout::Struct(&[]); +const LAYOUT_PTR: Layout = Layout::RecursivePointer; +const LAYOUT_U32: Layout = Layout::Builtin(Builtin::Int(IntWidth::U32)); + +/// "Infinite" reference count, for static values +/// Ref counts are encoded as negative numbers where isize::MIN represents 1 +pub const REFCOUNT_MAX: usize = 0; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum RefcountOp { + Inc, + Dec, + DecRef, +} + +/// Generate specialized refcounting code in mono IR format +/// ------------------------------------------------------- +/// +/// Any backend that wants to use this, needs a field of type `RefcountProcGenerator`. +/// +/// Whenever the backend sees a `Stmt::Refcounting`, it calls +/// `RefcountProcGenerator::expand_refcount_stmt()`, which returns IR statements +/// to call a refcounting procedure. The backend can then generate target code +/// for those IR statements instead of the original `Refcounting` statement. +/// +/// Essentially we are expanding the `Refcounting` statement into a more detailed +/// form that's more suitable for code generation. +/// +/// But so far, we've only mentioned _calls_ to the refcounting procedures. +/// The procedures themselves don't exist yet! +/// +/// So when the backend has finished with all the `Proc`s from user code, +/// it's time to call `RefcountProcGenerator::generate_refcount_procs()`, +/// which generates the `Procs` for refcounting helpers. The backend can +/// simply generate target code for these `Proc`s just like any other Proc. +/// +pub struct RefcountProcGenerator<'a> { + arena: &'a Bump, + home: ModuleId, + ptr_size: u32, + layout_isize: Layout<'a>, + /// List of refcounting procs to generate, specialised by Layout and RefCountOp + /// Order of insertion is preserved, since it is important for Wasm backend + procs_to_generate: Vec<'a, (Layout<'a>, RefcountOp, Symbol)>, +} + +impl<'a> RefcountProcGenerator<'a> { + pub fn new(arena: &'a Bump, intwidth_isize: IntWidth, home: ModuleId) -> Self { + RefcountProcGenerator { + arena, + home, + ptr_size: intwidth_isize.stack_size(), + layout_isize: Layout::Builtin(Builtin::Int(intwidth_isize)), + procs_to_generate: Vec::with_capacity_in(16, arena), + } + } + + /// Expands the IR node Stmt::Refcounting to a more detailed IR Stmt that calls a helper proc. + /// The helper procs themselves can be generated later by calling `generate_refcount_procs` + pub fn expand_refcount_stmt( + &mut self, + ident_ids: &mut IdentIds, + layout: Layout<'a>, + modify: &ModifyRc, + following: &'a Stmt<'a>, + ) -> (Stmt<'a>, Option<(Symbol, ProcLayout<'a>)>) { + match modify { + ModifyRc::Inc(structure, amount) => { + let layout_isize = self.layout_isize; + + let (is_existing, proc_name) = + self.get_proc_symbol(ident_ids, layout, RefcountOp::Inc); + + // Define a constant for the amount to increment + let amount_sym = self.create_symbol(ident_ids, "amount"); + let amount_expr = Expr::Literal(Literal::Int(*amount as i128)); + let amount_stmt = |next| Stmt::Let(amount_sym, amount_expr, layout_isize, next); + + // Call helper proc, passing the Roc structure and constant amount + let arg_layouts = self.arena.alloc([layout, layout_isize]); + let call_result_empty = self.create_symbol(ident_ids, "call_result_empty"); + let call_expr = Expr::Call(Call { + call_type: CallType::ByName { + name: proc_name, + ret_layout: &LAYOUT_UNIT, + arg_layouts, + specialization_id: CallSpecId::BACKEND_DUMMY, + }, + arguments: self.arena.alloc([*structure, amount_sym]), + }); + let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); + let rc_stmt = amount_stmt(self.arena.alloc(call_stmt)); + + // Create a linker symbol for the helper proc if this is the first usage + let new_proc_info = if is_existing { + None + } else { + Some(( + proc_name, + ProcLayout { + arguments: arg_layouts, + result: LAYOUT_UNIT, + }, + )) + }; + + (rc_stmt, new_proc_info) + } + + ModifyRc::Dec(structure) => { + let (is_existing, proc_name) = + self.get_proc_symbol(ident_ids, layout, RefcountOp::Dec); + + // Call helper proc, passing the Roc structure + let arg_layouts = self.arena.alloc([layout, self.layout_isize]); + let call_result_empty = self.create_symbol(ident_ids, "call_result_empty"); + let call_expr = Expr::Call(Call { + call_type: CallType::ByName { + name: proc_name, + ret_layout: &LAYOUT_UNIT, + arg_layouts: self.arena.alloc([layout]), + specialization_id: CallSpecId::BACKEND_DUMMY, + }, + arguments: self.arena.alloc([*structure]), + }); + + let rc_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); + + // Create a linker symbol for the helper proc if this is the first usage + let new_proc_info = if is_existing { + None + } else { + Some(( + proc_name, + ProcLayout { + arguments: arg_layouts, + result: LAYOUT_UNIT, + }, + )) + }; + + (rc_stmt, new_proc_info) + } + + ModifyRc::DecRef(structure) => { + // No generated procs for DecRef, just lowlevel calls + + // Get a pointer to the refcount itself + let rc_ptr_sym = self.create_symbol(ident_ids, "rc_ptr"); + let rc_ptr_expr = Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::RefCountGetPtr, + update_mode: UpdateModeId::BACKEND_DUMMY, + }, + arguments: self.arena.alloc([*structure]), + }); + let rc_ptr_stmt = |next| Stmt::Let(rc_ptr_sym, rc_ptr_expr, LAYOUT_PTR, next); + + // Pass the refcount pointer to the lowlevel call (see utils.zig) + let call_result_empty = self.create_symbol(ident_ids, "call_result_empty"); + let call_expr = Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::RefCountDec, + update_mode: UpdateModeId::BACKEND_DUMMY, + }, + arguments: self.arena.alloc([rc_ptr_sym]), + }); + let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); + let rc_stmt = rc_ptr_stmt(self.arena.alloc(call_stmt)); + + (rc_stmt, None) + } + } + } + + /// Generate refcounting helper procs, each specialized to a particular Layout. + /// For example `List (Result { a: Str, b: Int } Str)` would get its own helper + /// to update the refcounts on the List, the Result and the strings. + pub fn generate_refcount_procs( + &mut self, + arena: &'a Bump, + ident_ids: &mut IdentIds, + ) -> Vec<'a, Proc<'a>> { + // Move the vector so we can loop over it safely + let mut procs_to_generate = Vec::with_capacity_in(0, arena); + std::mem::swap(&mut self.procs_to_generate, &mut procs_to_generate); + + let mut procs = Vec::with_capacity_in(procs_to_generate.len(), arena); + for (layout, op, proc_symbol) in procs_to_generate.drain(0..) { + let proc = match layout { + Layout::Builtin(Builtin::Str) => self.gen_modify_str(ident_ids, op, proc_symbol), + _ => todo!("Refcounting is not yet implemented for Layout {:?}", layout), + }; + procs.push(proc); + } + + procs + } + + /// Find the Symbol of the procedure for this layout and refcount operation, + /// or create one if needed. + fn get_proc_symbol( + &mut self, + ident_ids: &mut IdentIds, + layout: Layout<'a>, + op: RefcountOp, + ) -> (bool, Symbol) { + let found = self + .procs_to_generate + .iter() + .find(|(l, o, _)| *l == layout && *o == op); + + if let Some((_, _, existing_symbol)) = found { + (true, *existing_symbol) + } else { + let layout_name = layout_debug_name(&layout); + let unique_idx = self.procs_to_generate.len(); + let debug_name = format!("#rc{:?}_{}_{}", op, layout_name, unique_idx); + let new_symbol: Symbol = self.create_symbol(ident_ids, &debug_name); + self.procs_to_generate.push((layout, op, new_symbol)); + (false, new_symbol) + } + } + + fn create_symbol(&mut self, ident_ids: &mut IdentIds, debug_name: &str) -> Symbol { + let ident_id = ident_ids.add(Ident::from(debug_name)); + Symbol::new(self.home, ident_id) + } + + fn return_unit(&mut self, ident_ids: &mut IdentIds) -> Stmt<'a> { + let unit = self.create_symbol(ident_ids, "unit"); + let ret_stmt = self.arena.alloc(Stmt::Ret(unit)); + Stmt::Let(unit, Expr::Struct(&[]), LAYOUT_UNIT, ret_stmt) + } + + fn gen_args(&mut self, op: RefcountOp, layout: Layout<'a>) -> &'a [(Layout<'a>, Symbol)] { + let roc_value = (layout, Symbol::ARG_1); + match op { + RefcountOp::Inc => { + let inc_amount = (self.layout_isize, Symbol::ARG_2); + self.arena.alloc([roc_value, inc_amount]) + } + RefcountOp::Dec | RefcountOp::DecRef => self.arena.alloc([roc_value]), + } + } + + /// Generate a procedure to modify the reference count of a Str + fn gen_modify_str( + &mut self, + ident_ids: &mut IdentIds, + op: RefcountOp, + proc_name: Symbol, + ) -> Proc<'a> { + let string = Symbol::ARG_1; + let layout_isize = self.layout_isize; + + // Get the string length as a signed int + let len = self.create_symbol(ident_ids, "len"); + let len_expr = Expr::StructAtIndex { + index: 1, + field_layouts: self.arena.alloc([LAYOUT_PTR, layout_isize]), + structure: string, + }; + let len_stmt = |next| Stmt::Let(len, len_expr, layout_isize, next); + + // Zero + let zero = self.create_symbol(ident_ids, "zero"); + let zero_expr = Expr::Literal(Literal::Int(0)); + let zero_stmt = |next| Stmt::Let(zero, zero_expr, layout_isize, next); + + // is_big_str = (len >= 0); + // Treat len as isize so that the small string flag is the same as the sign bit + let is_big_str = self.create_symbol(ident_ids, "is_big_str"); + let is_big_str_expr = Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::NumGte, + update_mode: UpdateModeId::BACKEND_DUMMY, + }, + arguments: self.arena.alloc([len, zero]), + }); + let is_big_str_stmt = |next| Stmt::Let(is_big_str, is_big_str_expr, LAYOUT_BOOL, next); + + // Get the pointer to the string elements + let elements = self.create_symbol(ident_ids, "elements"); + let elements_expr = Expr::StructAtIndex { + index: 0, + field_layouts: self.arena.alloc([LAYOUT_PTR, layout_isize]), + structure: string, + }; + let elements_stmt = |next| Stmt::Let(elements, elements_expr, LAYOUT_PTR, next); + + // Get a pointer to the refcount value, just below the elements pointer + let rc_ptr = self.create_symbol(ident_ids, "rc_ptr"); + let rc_ptr_expr = Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::RefCountGetPtr, + update_mode: UpdateModeId::BACKEND_DUMMY, + }, + arguments: self.arena.alloc([elements]), + }); + let rc_ptr_stmt = |next| Stmt::Let(rc_ptr, rc_ptr_expr, LAYOUT_PTR, next); + + // Alignment constant + let alignment = self.create_symbol(ident_ids, "alignment"); + let alignment_expr = Expr::Literal(Literal::Int(self.ptr_size as i128)); + let alignment_stmt = |next| Stmt::Let(alignment, alignment_expr, LAYOUT_U32, next); + + // Call the relevant Zig lowlevel to actually modify the refcount + let zig_call_result = self.create_symbol(ident_ids, "zig_call_result"); + let zig_call_expr = match op { + RefcountOp::Inc => Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::RefCountInc, + update_mode: UpdateModeId::BACKEND_DUMMY, + }, + arguments: self.arena.alloc([rc_ptr, Symbol::ARG_2]), + }), + RefcountOp::Dec | RefcountOp::DecRef => Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::RefCountDec, + update_mode: UpdateModeId::BACKEND_DUMMY, + }, + arguments: self.arena.alloc([rc_ptr, alignment]), + }), + }; + let zig_call_stmt = |next| Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, next); + + // Generate an `if` to skip small strings but modify big strings + let then_branch = elements_stmt(self.arena.alloc( + // + rc_ptr_stmt(self.arena.alloc( + // + alignment_stmt(self.arena.alloc( + // + zig_call_stmt(self.arena.alloc( + // + Stmt::Ret(zig_call_result), + )), + )), + )), + )); + let if_stmt = Stmt::Switch { + cond_symbol: is_big_str, + cond_layout: LAYOUT_BOOL, + branches: self.arena.alloc([(1, BranchInfo::None, then_branch)]), + default_branch: ( + BranchInfo::None, + self.arena.alloc(self.return_unit(ident_ids)), + ), + ret_layout: LAYOUT_UNIT, + }; + + // Combine the statements in sequence + let body = len_stmt(self.arena.alloc( + // + zero_stmt(self.arena.alloc( + // + is_big_str_stmt(self.arena.alloc( + // + if_stmt, + )), + )), + )); + + let args = self.gen_args(op, Layout::Builtin(Builtin::Str)); + + Proc { + name: proc_name, + args, + body, + closure_data_layout: None, + ret_layout: LAYOUT_UNIT, + is_self_recursive: SelfRecursive::NotSelfRecursive, + must_own_arguments: false, + host_exposed_layouts: HostExposedLayouts::NotHostExposed, + } + } +} + +/// Helper to derive a debug function name from a layout +fn layout_debug_name<'a>(layout: &Layout<'a>) -> &'static str { + match layout { + Layout::Builtin(Builtin::List(_)) => "list", + Layout::Builtin(Builtin::Set(_)) => "set", + Layout::Builtin(Builtin::Dict(_, _)) => "dict", + Layout::Builtin(Builtin::Str) => "str", + Layout::Builtin(builtin) => { + debug_assert!(!builtin.is_refcounted()); + unreachable!("Builtin {:?} is not refcounted", builtin); + } + Layout::Struct(_) => "struct", + Layout::Union(_) => "union", + Layout::LambdaSet(_) => "lambdaset", + Layout::RecursivePointer => "recursive_pointer", + } +} diff --git a/compiler/mono/src/inc_dec.rs b/compiler/mono/src/inc_dec.rs index c6dfd67314..c2513ca1ec 100644 --- a/compiler/mono/src/inc_dec.rs +++ b/compiler/mono/src/inc_dec.rs @@ -110,7 +110,7 @@ pub fn occurring_variables_expr(expr: &Expr<'_>, result: &mut MutSet) { result.extend(arguments.iter().copied()); result.insert(*symbol); } - Reset(x) => { + Reset { symbol: x, .. } => { result.insert(*x); } @@ -468,12 +468,8 @@ impl<'a> Context<'a> { HigherOrder(HigherOrderLowLevel { op, closure_env_layout, - specialization_id, update_mode, - arg_layouts, - ret_layout, - function_name, - function_env, + passed_function, .. }) => { // setup @@ -483,16 +479,14 @@ impl<'a> Context<'a> { ($borrows:expr) => { Expr::Call(crate::ir::Call { call_type: if let Some(OWNED) = $borrows.map(|p| p.borrow) { + let mut passed_function = *passed_function; + passed_function.owns_captured_environment = true; + let higher_order = HigherOrderLowLevel { op: *op, closure_env_layout: *closure_env_layout, - function_owns_closure_data: true, - specialization_id: *specialization_id, update_mode: *update_mode, - function_name: *function_name, - function_env: *function_env, - arg_layouts, - ret_layout: *ret_layout, + passed_function, }; CallType::HigherOrder(self.arena.alloc(higher_order)) @@ -521,11 +515,14 @@ impl<'a> Context<'a> { const CLOSURE_DATA: bool = BORROWED; let function_layout = ProcLayout { - arguments: arg_layouts, - result: *ret_layout, + arguments: passed_function.argument_layouts, + result: passed_function.return_layout, }; - let function_ps = match self.param_map.get_symbol(*function_name, function_layout) { + let function_ps = match self + .param_map + .get_symbol(passed_function.name, function_layout) + { Some(function_ps) => function_ps, None => unreachable!(), }; @@ -761,7 +758,7 @@ impl<'a> Context<'a> { self.arena.alloc(Stmt::Let(z, v, l, b)) } - EmptyArray | Literal(_) | Reset(_) | RuntimeErrorFunction(_) => { + EmptyArray | Literal(_) | Reset { .. } | RuntimeErrorFunction(_) => { // EmptyArray is always stack-allocated // function pointers are persistent self.arena.alloc(Stmt::Let(z, v, l, b)) @@ -779,7 +776,7 @@ impl<'a> Context<'a> { // must this value be consumed? let consume = consume_expr(&self.vars, expr); - let reset = matches!(expr, Expr::Reset(_)); + let reset = matches!(expr, Expr::Reset { .. }); self.update_var_info_help(symbol, layout, persistent, consume, reset) } diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index d4956d4ba9..9ecb317d21 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -37,8 +37,8 @@ static_assertions::assert_eq_size!([u8; 19 * 8], Stmt); #[cfg(target_arch = "aarch64")] static_assertions::assert_eq_size!([u8; 20 * 8], Stmt); static_assertions::assert_eq_size!([u8; 6 * 8], ProcLayout); -static_assertions::assert_eq_size!([u8; 8 * 8], Call); -static_assertions::assert_eq_size!([u8; 6 * 8], CallType); +static_assertions::assert_eq_size!([u8; 7 * 8], Call); +static_assertions::assert_eq_size!([u8; 5 * 8], CallType); macro_rules! return_on_layout_error { ($env:expr, $layout_result:expr) => { @@ -318,11 +318,17 @@ impl<'a> Proc<'a> { arena: &'a Bump, home: ModuleId, ident_ids: &'i mut IdentIds, + update_mode_ids: &'i mut UpdateModeIds, procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) { for (_, proc) in procs.iter_mut() { - let new_proc = - crate::reset_reuse::insert_reset_reuse(arena, home, ident_ids, proc.clone()); + let new_proc = crate::reset_reuse::insert_reset_reuse( + arena, + home, + ident_ids, + update_mode_ids, + proc.clone(), + ); *proc = new_proc; } } @@ -988,8 +994,8 @@ pub struct Env<'a, 'i> { pub home: ModuleId, pub ident_ids: &'i mut IdentIds, pub ptr_bytes: u32, - pub update_mode_counter: u64, - pub call_specialization_counter: u64, + pub update_mode_ids: &'i mut UpdateModeIds, + pub call_specialization_counter: u32, } impl<'a, 'i> Env<'a, 'i> { @@ -1000,13 +1006,7 @@ impl<'a, 'i> Env<'a, 'i> { } pub fn next_update_mode_id(&mut self) -> UpdateModeId { - let id = UpdateModeId { - id: self.update_mode_counter, - }; - - self.update_mode_counter += 1; - - id + self.update_mode_ids.next_id() } pub fn next_call_specialization_id(&mut self) -> CallSpecId { @@ -1282,24 +1282,49 @@ impl<'a> Call<'a> { #[derive(Clone, Copy, Debug, PartialEq)] pub struct CallSpecId { - id: u64, + id: u32, } impl CallSpecId { - pub fn to_bytes(self) -> [u8; 8] { + pub fn to_bytes(self) -> [u8; 4] { self.id.to_ne_bytes() } + + /// Dummy value for generating refcount helper procs in the backends + /// This happens *after* specialization so it's safe + pub const BACKEND_DUMMY: Self = Self { id: 0 }; } #[derive(Clone, Copy, Debug, PartialEq)] pub struct UpdateModeId { - id: u64, + id: u32, } impl UpdateModeId { - pub fn to_bytes(self) -> [u8; 8] { + pub fn to_bytes(self) -> [u8; 4] { self.id.to_ne_bytes() } + + /// Dummy value for generating refcount helper procs in the backends + /// This happens *after* alias analysis so it's safe + pub const BACKEND_DUMMY: Self = Self { id: 0 }; +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct UpdateModeIds { + next: u32, +} + +impl UpdateModeIds { + pub const fn new() -> Self { + Self { next: 0 } + } + + pub fn next_id(&mut self) -> UpdateModeId { + let id = UpdateModeId { id: self.next }; + self.next += 1; + id + } } #[derive(Clone, Debug, PartialEq)] @@ -1321,31 +1346,35 @@ pub enum CallType<'a> { HigherOrder(&'a HigherOrderLowLevel<'a>), } +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct PassedFunction<'a> { + /// name of the top-level function that is passed as an argument + /// e.g. in `List.map xs Num.abs` this would be `Num.abs` + pub name: Symbol, + + pub argument_layouts: &'a [Layout<'a>], + pub return_layout: Layout<'a>, + + pub specialization_id: CallSpecId, + + /// Symbol of the environment captured by the function argument + pub captured_environment: Symbol, + + pub owns_captured_environment: bool, +} + #[derive(Clone, Debug, PartialEq)] pub struct HigherOrderLowLevel<'a> { pub op: crate::low_level::HigherOrder, + + /// TODO I _think_ we can get rid of this, perhaps only keeping track of /// the layout of the closure argument, if any pub closure_env_layout: Option>, - /// name of the top-level function that is passed as an argument - /// e.g. in `List.map xs Num.abs` this would be `Num.abs` - pub function_name: Symbol, - - /// Symbol of the environment captured by the function argument - pub function_env: Symbol, - - /// does the function argument need to own the closure data - pub function_owns_closure_data: bool, - - /// specialization id of the function argument, used for name generation - pub specialization_id: CallSpecId, - /// update mode of the higher order lowlevel itself pub update_mode: UpdateModeId, - /// function layout, used for name generation - pub arg_layouts: &'a [Layout<'a>], - pub ret_layout: Layout<'a>, + pub passed_function: PassedFunction<'a>, } #[derive(Clone, Debug, PartialEq)] @@ -1390,13 +1419,17 @@ pub enum Expr<'a> { Reuse { symbol: Symbol, update_tag_id: bool, + update_mode: UpdateModeId, // normal Tag fields tag_layout: UnionLayout<'a>, tag_name: TagName, tag_id: TagIdIntType, arguments: &'a [Symbol], }, - Reset(Symbol), + Reset { + symbol: Symbol, + update_mode: UpdateModeId, + }, RuntimeErrorFunction(&'a str), } @@ -1491,6 +1524,7 @@ impl<'a> Expr<'a> { symbol, tag_name, arguments, + update_mode, .. } => { let doc_tag = match tag_name { @@ -1508,11 +1542,19 @@ impl<'a> Expr<'a> { .text("Reuse ") .append(symbol_to_doc(alloc, *symbol)) .append(alloc.space()) + .append(format!("{:?}", update_mode)) + .append(alloc.space()) .append(doc_tag) .append(alloc.space()) .append(alloc.intersperse(it, " ")) } - Reset(symbol) => alloc.text("Reset ").append(symbol_to_doc(alloc, *symbol)), + Reset { + symbol, + update_mode, + } => alloc.text(format!( + "Reset {{ symbol: {:?}, id: {} }}", + symbol, update_mode.id + )), Struct(args) => { let it = args.iter().map(|s| symbol_to_doc(alloc, *s)); @@ -1557,6 +1599,17 @@ impl<'a> Expr<'a> { .append(symbol_to_doc(alloc, *structure)), } } + + pub fn to_pretty(&self, width: usize) -> String { + let allocator = BoxAllocator; + let mut w = std::vec::Vec::new(); + self.to_doc::<_, ()>(&allocator) + .1 + .render(width, &mut w) + .unwrap(); + w.push(b'\n'); + String::from_utf8(w).unwrap() + } } impl<'a> Stmt<'a> { @@ -4144,16 +4197,21 @@ pub fn with_hole<'a>( op, closure_data_symbol, |(top_level_function, closure_data, closure_env_layout, specialization_id, update_mode)| { + let passed_function = PassedFunction { + name: top_level_function, + captured_environment: closure_data_symbol, + owns_captured_environment: false, + specialization_id, + argument_layouts: arg_layouts, + return_layout: ret_layout, + }; + + let higher_order = HigherOrderLowLevel { op: crate::low_level::HigherOrder::$ho { $($x,)* }, closure_env_layout, - specialization_id, update_mode, - function_owns_closure_data: false, - function_env: closure_data_symbol, - function_name: top_level_function, - arg_layouts, - ret_layout, + passed_function, }; self::Call { @@ -5715,7 +5773,7 @@ fn substitute_in_expr<'a>( } } - Reuse { .. } | Reset(_) => unreachable!("reset/reuse have not been introduced yet"), + Reuse { .. } | Reset { .. } => unreachable!("reset/reuse have not been introduced yet"), Struct(args) => { let mut did_change = false; diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index e2856d0558..bad3cc8389 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -2367,7 +2367,7 @@ fn layout_from_tag_union<'a>( } #[cfg(debug_assertions)] -fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool { +pub fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool { // the ext_var is empty let fields = roc_types::types::gather_fields(subs, RecordFields::empty(), ext_var); @@ -2375,13 +2375,13 @@ fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool { } #[cfg(not(debug_assertions))] -fn ext_var_is_empty_record(_subs: &Subs, _ext_var: Variable) -> bool { +pub fn ext_var_is_empty_record(_subs: &Subs, _ext_var: Variable) -> bool { // This should only ever be used in debug_assert! macros unreachable!(); } #[cfg(debug_assertions)] -fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool { +pub fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool { // the ext_var is empty let mut ext_fields = std::vec::Vec::new(); match roc_types::pretty_print::chase_ext_tag_union(subs, ext_var, &mut ext_fields) { @@ -2391,7 +2391,7 @@ fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool { } #[cfg(not(debug_assertions))] -fn ext_var_is_empty_tag_union(_: &Subs, _: Variable) -> bool { +pub fn ext_var_is_empty_tag_union(_: &Subs, _: Variable) -> bool { // This should only ever be used in debug_assert! macros unreachable!(); } diff --git a/compiler/mono/src/layout_soa.rs b/compiler/mono/src/layout_soa.rs new file mode 100644 index 0000000000..1481b38723 --- /dev/null +++ b/compiler/mono/src/layout_soa.rs @@ -0,0 +1,842 @@ +use crate::layout::{ext_var_is_empty_record, ext_var_is_empty_tag_union}; +use roc_builtins::bitcode::{FloatWidth, IntWidth}; +use roc_collections::all::MutMap; +use roc_module::ident::TagName; +use roc_module::symbol::Symbol; +use roc_types::subs::{Content, FlatType, Subs, Variable}; +use roc_types::types::RecordField; +use std::collections::hash_map::Entry; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Index { + index: u32, + _marker: std::marker::PhantomData, +} + +impl Index { + pub const fn new(index: u32) -> Self { + Self { + index, + _marker: std::marker::PhantomData, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Slice { + start: u32, + length: u16, + _marker: std::marker::PhantomData, +} + +impl Slice { + pub const fn new(start: u32, length: u16) -> Self { + Self { + start, + length, + _marker: std::marker::PhantomData, + } + } + + pub const fn len(&self) -> usize { + self.length as _ + } + + pub const fn is_empty(&self) -> bool { + self.length == 0 + } + + pub const fn indices(&self) -> std::ops::Range { + self.start as usize..(self.start as usize + self.length as usize) + } + + pub fn into_iter(&self) -> impl Iterator> { + self.indices().map(|i| Index::new(i as _)) + } +} + +trait Reserve { + fn reserve(layouts: &mut Layouts, length: usize) -> Self; +} + +impl Reserve for Slice { + fn reserve(layouts: &mut Layouts, length: usize) -> Self { + let start = layouts.layouts.len() as u32; + + let it = std::iter::repeat(Layout::Reserved).take(length); + layouts.layouts.extend(it); + + Self { + start, + length: length as u16, + _marker: Default::default(), + } + } +} + +impl Reserve for Slice> { + fn reserve(layouts: &mut Layouts, length: usize) -> Self { + let start = layouts.layout_slices.len() as u32; + + let empty: Slice = Slice::new(0, 0); + let it = std::iter::repeat(empty).take(length); + layouts.layout_slices.extend(it); + + Self { + start, + length: length as u16, + _marker: Default::default(), + } + } +} + +static_assertions::assert_eq_size!([u8; 12], Layout); + +pub struct Layouts { + layouts: Vec, + layout_slices: Vec>, + // function_layouts: Vec<(Slice, Index)>, + lambda_sets: Vec, + symbols: Vec, + recursion_variable_to_structure_variable_map: MutMap>, + usize_int_width: IntWidth, +} + +pub struct FunctionLayout { + /// last element is the result, prior elements the arguments + arguments_and_result: Slice, + pub lambda_set: Index, +} + +impl FunctionLayout { + pub fn from_var( + layouts: &mut Layouts, + subs: &Subs, + var: Variable, + ) -> Result { + // so we can set some things/clean up + Self::from_var_help(layouts, subs, var) + } + + fn from_var_help( + layouts: &mut Layouts, + subs: &Subs, + var: Variable, + ) -> Result { + let content = &subs.get_ref(var).content; + Self::from_content(layouts, subs, var, content) + } + + fn from_content( + layouts: &mut Layouts, + subs: &Subs, + var: Variable, + content: &Content, + ) -> Result { + use LayoutError::*; + + match content { + Content::FlexVar(_) => Err(UnresolvedVariable(var)), + Content::RigidVar(_) => Err(UnresolvedVariable(var)), + Content::RecursionVar { .. } => Err(TypeError(())), + Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), + Content::Alias(_, _, actual) => Self::from_var_help(layouts, subs, *actual), + Content::Error => Err(TypeError(())), + } + } + + fn from_flat_type( + layouts: &mut Layouts, + subs: &Subs, + flat_type: &FlatType, + ) -> Result { + use LayoutError::*; + + match flat_type { + FlatType::Func(arguments, lambda_set, result) => { + let slice = Slice::reserve(layouts, arguments.len() + 1); + + let variable_slice = &subs.variables[arguments.indices()]; + let it = slice.indices().zip(variable_slice); + for (target_index, var) in it { + let layout = Layout::from_var_help(layouts, subs, *var)?; + layouts.layouts[target_index] = layout; + } + + let result_layout = Layout::from_var_help(layouts, subs, *result)?; + let result_index: Index = Index::new(slice.start + slice.len() as u32 - 1); + layouts.layouts[result_index.index as usize] = result_layout; + + let lambda_set = LambdaSet::from_var(layouts, subs, *lambda_set)?; + let lambda_set_index = Index::new(layouts.lambda_sets.len() as u32); + layouts.lambda_sets.push(lambda_set); + + Ok(Self { + arguments_and_result: slice, + lambda_set: lambda_set_index, + }) + } + + FlatType::Erroneous(_) => Err(TypeError(())), + + _ => todo!(), + } + } + + pub fn argument_slice(&self) -> Slice { + let mut result = self.arguments_and_result; + result.length -= 1; + + result + } + pub fn result_index(&self) -> Index { + Index::new(self.arguments_and_result.start + self.arguments_and_result.length as u32 - 1) + } +} + +/// Idea: don't include the symbols for the first 3 cases in --optimize mode +pub enum LambdaSet { + Empty { + symbol: Index, + }, + Single { + symbol: Index, + layout: Index, + }, + Struct { + symbol: Index, + layouts: Slice, + }, + Union { + symbols: Slice, + layouts: Slice>, + }, +} + +impl LambdaSet { + pub fn from_var( + layouts: &mut Layouts, + subs: &Subs, + var: Variable, + ) -> Result { + // so we can set some things/clean up + Self::from_var_help(layouts, subs, var) + } + + fn from_var_help( + layouts: &mut Layouts, + subs: &Subs, + var: Variable, + ) -> Result { + let content = &subs.get_ref(var).content; + Self::from_content(layouts, subs, var, content) + } + + fn from_content( + layouts: &mut Layouts, + subs: &Subs, + var: Variable, + content: &Content, + ) -> Result { + use LayoutError::*; + + match content { + Content::FlexVar(_) => Err(UnresolvedVariable(var)), + Content::RigidVar(_) => Err(UnresolvedVariable(var)), + Content::RecursionVar { .. } => { + unreachable!("lambda sets cannot currently be recursive") + } + Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), + Content::Alias(_, _, actual) => Self::from_var_help(layouts, subs, *actual), + Content::Error => Err(TypeError(())), + } + } + + fn from_flat_type( + layouts: &mut Layouts, + subs: &Subs, + flat_type: &FlatType, + ) -> Result { + use FlatType::*; + use LayoutError::*; + + match flat_type { + TagUnion(union_tags, ext) => { + debug_assert!(ext_var_is_empty_tag_union(subs, *ext)); + + debug_assert!( + !union_tags.is_empty(), + "lambda set must contain atleast the function itself" + ); + + let tag_names = union_tags.tag_names(); + let closure_names = Self::get_closure_names(layouts, subs, tag_names); + + let variables = union_tags.variables(); + if variables.len() == 1 { + let tag_name = &subs.tag_names[tag_names.start as usize]; + let symbol = if let TagName::Closure(symbol) = tag_name { + let index = Index::new(layouts.symbols.len() as u32); + layouts.symbols.push(*symbol); + index + } else { + unreachable!("must be a closure tag") + }; + let variable_slice = subs.variable_slices[variables.start as usize]; + + match variable_slice.len() { + 0 => Ok(LambdaSet::Empty { symbol }), + 1 => { + let var = subs.variables[variable_slice.start as usize]; + let layout = Layout::from_var(layouts, subs, var)?; + + let index = Index::new(layouts.layouts.len() as u32); + layouts.layouts.push(layout); + + Ok(LambdaSet::Single { + symbol, + layout: index, + }) + } + _ => { + let slice = Layout::from_variable_slice(layouts, subs, variable_slice)?; + + Ok(LambdaSet::Struct { + symbol, + layouts: slice, + }) + } + } + } else { + let layouts = + Layout::from_slice_variable_slice(layouts, subs, union_tags.variables())?; + + Ok(LambdaSet::Union { + symbols: closure_names, + layouts, + }) + } + } + Erroneous(_) => Err(TypeError(())), + _ => unreachable!(), + } + } + + fn get_closure_names( + layouts: &mut Layouts, + subs: &Subs, + subs_slice: roc_types::subs::SubsSlice, + ) -> Slice { + let slice = Slice::new(layouts.symbols.len() as u32, subs_slice.len() as u16); + + let tag_names = &subs.tag_names[subs_slice.indices()]; + + for tag_name in tag_names { + match tag_name { + TagName::Closure(symbol) => { + layouts.symbols.push(*symbol); + } + TagName::Global(_) => unreachable!("lambda set tags must be closure tags"), + TagName::Private(_) => unreachable!("lambda set tags must be closure tags"), + } + } + + slice + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Layout { + // theory: we can zero out memory to reserve space for many layouts + Reserved, + + // Question: where to store signedness information? + Int(IntWidth), + Float(FloatWidth), + Decimal, + + Str, + Dict(Index<(Layout, Layout)>), + Set(Index), + List(Index), + + Struct(Slice), + + UnionNonRecursive(Slice>), + + Boxed(Index), + UnionRecursive(Slice>), + // UnionNonNullableUnwrapped(Slice), + // UnionNullableWrapper { + // data: NullableUnionIndex, + // tag_id: u16, + // }, + // + // UnionNullableUnwrappedTrue(Slice), + // UnionNullableUnwrappedFalse(Slice), + + // RecursivePointer, +} + +fn round_up_to_alignment(unaligned: u16, alignment_bytes: u16) -> u16 { + let unaligned = unaligned as i32; + let alignment_bytes = alignment_bytes as i32; + if alignment_bytes <= 1 { + return unaligned as u16; + } + if alignment_bytes.count_ones() != 1 { + panic!( + "Cannot align to {} bytes. Not a power of 2.", + alignment_bytes + ); + } + let mut aligned = unaligned; + aligned += alignment_bytes - 1; // if lower bits are non-zero, push it over the next boundary + aligned &= -alignment_bytes; // mask with a flag that has upper bits 1, lower bits 0 + + aligned as u16 +} + +impl Layouts { + const VOID_INDEX: Index = Index::new(0); + const VOID_TUPLE: Index<(Layout, Layout)> = Index::new(0); + const UNIT_INDEX: Index = Index::new(2); + + pub fn new(usize_int_width: IntWidth) -> Self { + let mut layouts = Vec::with_capacity(64); + + layouts.push(Layout::VOID); + layouts.push(Layout::VOID); + layouts.push(Layout::UNIT); + + // sanity check + debug_assert_eq!(layouts[Self::VOID_INDEX.index as usize], Layout::VOID); + debug_assert_eq!(layouts[Self::VOID_TUPLE.index as usize + 1], Layout::VOID); + debug_assert_eq!(layouts[Self::UNIT_INDEX.index as usize], Layout::UNIT); + + Layouts { + layouts: Vec::default(), + layout_slices: Vec::default(), + lambda_sets: Vec::default(), + symbols: Vec::default(), + recursion_variable_to_structure_variable_map: MutMap::default(), + usize_int_width, + } + } + + /// sort a slice according to elements' alignment + fn sort_slice_by_alignment(&mut self, layout_slice: Slice) { + let slice = &mut self.layouts[layout_slice.indices()]; + + // SAFETY: the align_of function does not mutate the layouts vector + // this unsafety is required to circumvent the borrow checker + let sneaky_slice = + unsafe { std::slice::from_raw_parts_mut(slice.as_mut_ptr(), slice.len()) }; + + sneaky_slice.sort_by(|layout1, layout2| { + let align1 = self.align_of_layout(*layout1); + let align2 = self.align_of_layout(*layout2); + + // we want the biggest alignment first + align2.cmp(&align1) + }); + } + + fn usize(&self) -> Layout { + Layout::Int(self.usize_int_width) + } + + fn align_of_layout_index(&self, index: Index) -> u16 { + let layout = self.layouts[index.index as usize]; + + self.align_of_layout(layout) + } + + fn align_of_layout(&self, layout: Layout) -> u16 { + let ptr_alignment = self.usize_int_width.alignment_bytes() as u16; + + match layout { + Layout::Reserved => unreachable!(), + Layout::Int(int_width) => int_width.alignment_bytes() as u16, + Layout::Float(float_width) => float_width.alignment_bytes() as u16, + Layout::Decimal => IntWidth::U128.alignment_bytes() as u16, + Layout::Str | Layout::Dict(_) | Layout::Set(_) | Layout::List(_) => ptr_alignment, + Layout::Struct(slice) => self.align_of_layout_slice(slice), + Layout::Boxed(_) | Layout::UnionRecursive(_) => ptr_alignment, + Layout::UnionNonRecursive(slices) => { + let tag_id_align = IntWidth::I64.alignment_bytes() as u16; + + self.align_of_layout_slices(slices).max(tag_id_align) + } +// Layout::UnionNonNullableUnwrapped(_) => todo!(), +// Layout::UnionNullableWrapper { data, tag_id } => todo!(), +// Layout::UnionNullableUnwrappedTrue(_) => todo!(), +// Layout::UnionNullableUnwrappedFalse(_) => todo!(), +// Layout::RecursivePointer => todo!(), + } + } + + /// Invariant: the layouts are sorted from biggest to smallest alignment + fn align_of_layout_slice(&self, slice: Slice) -> u16 { + match slice.into_iter().next() { + None => 0, + Some(first_index) => self.align_of_layout_index(first_index), + } + } + + fn align_of_layout_slices(&self, slice: Slice>) -> u16 { + slice + .into_iter() + .map(|index| self.layout_slices[index.index as usize]) + .map(|slice| self.align_of_layout_slice(slice)) + .max() + .unwrap_or_default() + } + + /// Invariant: the layouts are sorted from biggest to smallest alignment + fn size_of_layout_slice(&self, slice: Slice) -> u16 { + match slice.into_iter().next() { + None => 0, + Some(first_index) => { + let alignment = self.align_of_layout_index(first_index); + + let mut sum = 0; + + for index in slice.into_iter() { + sum += self.size_of_layout_index(index); + } + + round_up_to_alignment(sum, alignment) + } + } + } + + pub fn size_of_layout_index(&self, index: Index) -> u16 { + let layout = self.layouts[index.index as usize]; + + self.size_of_layout(layout) + } + + pub fn size_of_layout(&self, layout: Layout) -> u16 { + let ptr_width = self.usize_int_width.stack_size() as u16; + + match layout { + Layout::Reserved => unreachable!(), + Layout::Int(int_width) => int_width.stack_size() as _, + Layout::Float(float_width) => float_width as _, + Layout::Decimal => (std::mem::size_of::()) as _, + Layout::Str | Layout::Dict(_) | Layout::Set(_) | Layout::List(_) => 2 * ptr_width, + Layout::Struct(slice) => self.size_of_layout_slice(slice), + Layout::Boxed(_) | Layout::UnionRecursive(_) => ptr_width, + Layout::UnionNonRecursive(slices) if slices.is_empty() => 0, + Layout::UnionNonRecursive(slices) => { + let tag_id = IntWidth::I64; + + let max_slice_size = slices + .into_iter() + .map(|index| self.layout_slices[index.index as usize]) + .map(|slice| self.align_of_layout_slice(slice)) + .max() + .unwrap_or_default(); + + tag_id.stack_size() as u16 + max_slice_size + } +// Layout::UnionNonNullableUnwrapped(_) => todo!(), +// Layout::UnionNullableWrapper { data, tag_id } => todo!(), +// Layout::UnionNullableUnwrappedTrue(_) => todo!(), +// Layout::UnionNullableUnwrappedFalse(_) => todo!(), +// Layout::RecursivePointer => todo!(), + } + } +} + +pub enum LayoutError { + UnresolvedVariable(Variable), + TypeError(()), +} + +impl Layout { + pub const UNIT: Self = Self::Struct(Slice::new(0, 0)); + pub const VOID: Self = Self::UnionNonRecursive(Slice::new(0, 0)); + + pub const EMPTY_LIST: Self = Self::List(Layouts::VOID_INDEX); + pub const EMPTY_DICT: Self = Self::Dict(Layouts::VOID_TUPLE); + pub const EMPTY_SET: Self = Self::Set(Layouts::VOID_INDEX); + + pub fn from_var( + layouts: &mut Layouts, + subs: &Subs, + var: Variable, + ) -> Result { + // so we can set some things/clean up + Self::from_var_help(layouts, subs, var) + } + + fn from_var_help( + layouts: &mut Layouts, + subs: &Subs, + var: Variable, + ) -> Result { + let content = &subs.get_ref(var).content; + Self::from_content(layouts, subs, var, content) + } + + /// Used in situations where an unspecialized variable is not a problem, + /// and we can substitute with `[]`, the empty tag union. + /// e.g. an empty list literal has type `List *`. We can still generate code + /// in those cases by just picking any concrete type for the list element, + /// and we pick the empty tag union in practice. + fn from_var_help_or_void( + layouts: &mut Layouts, + subs: &Subs, + var: Variable, + ) -> Result { + let content = &subs.get_ref(var).content; + + match content { + Content::FlexVar(_) | Content::RigidVar(_) => Ok(Layout::VOID), + + _ => Self::from_content(layouts, subs, var, content), + } + } + + fn from_content( + layouts: &mut Layouts, + subs: &Subs, + var: Variable, + content: &Content, + ) -> Result { + use LayoutError::*; + + match content { + Content::FlexVar(_) => Err(UnresolvedVariable(var)), + Content::RigidVar(_) => Err(UnresolvedVariable(var)), + Content::RecursionVar { + structure, + opt_name: _, + } => { + let structure = subs.get_root_key_without_compacting(*structure); + + let entry = layouts + .recursion_variable_to_structure_variable_map + .entry(structure); + + match entry { + Entry::Vacant(vacant) => { + let reserved = Index::new(layouts.layouts.len() as _); + layouts.layouts.push(Layout::Reserved); + + vacant.insert(reserved); + + let layout = Layout::from_var(layouts, subs, structure)?; + + layouts.layouts[reserved.index as usize] = layout; + + Ok(Layout::Boxed(reserved)) + } + Entry::Occupied(occupied) => { + let index = occupied.get(); + + Ok(Layout::Boxed(*index)) + } + } + } + Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), + Content::Alias(symbol, _, actual) => { + let symbol = *symbol; + + if let Some(int_width) = IntWidth::try_from_symbol(symbol) { + return Ok(Layout::Int(int_width)); + } + + if let Some(float_width) = FloatWidth::try_from_symbol(symbol) { + return Ok(Layout::Float(float_width)); + } + + match symbol { + Symbol::NUM_DECIMAL | Symbol::NUM_AT_DECIMAL => Ok(Layout::Decimal), + + Symbol::NUM_NAT | Symbol::NUM_NATURAL | Symbol::NUM_AT_NATURAL => { + Ok(layouts.usize()) + } + + _ => { + // at this point we throw away alias information + Self::from_var_help(layouts, subs, *actual) + } + } + } + Content::Error => Err(TypeError(())), + } + } + + fn from_flat_type( + layouts: &mut Layouts, + subs: &Subs, + flat_type: &FlatType, + ) -> Result { + use LayoutError::*; + + match flat_type { + FlatType::Apply(Symbol::LIST_LIST, arguments) => { + debug_assert_eq!(arguments.len(), 1); + + let element_var = subs.variables[arguments.start as usize]; + let element_layout = Self::from_var_help_or_void(layouts, subs, element_var)?; + + let element_index = Index::new(layouts.layouts.len() as _); + layouts.layouts.push(element_layout); + + Ok(Layout::List(element_index)) + } + + FlatType::Apply(Symbol::DICT_DICT, arguments) => { + debug_assert_eq!(arguments.len(), 2); + + let key_var = subs.variables[arguments.start as usize]; + let value_var = subs.variables[arguments.start as usize + 1]; + + let key_layout = Self::from_var_help_or_void(layouts, subs, key_var)?; + let value_layout = Self::from_var_help_or_void(layouts, subs, value_var)?; + + let index = Index::new(layouts.layouts.len() as _); + layouts.layouts.push(key_layout); + layouts.layouts.push(value_layout); + + Ok(Layout::Dict(index)) + } + + FlatType::Apply(Symbol::SET_SET, arguments) => { + debug_assert_eq!(arguments.len(), 1); + + let element_var = subs.variables[arguments.start as usize]; + let element_layout = Self::from_var_help_or_void(layouts, subs, element_var)?; + + let element_index = Index::new(layouts.layouts.len() as _); + layouts.layouts.push(element_layout); + + Ok(Layout::Set(element_index)) + } + + FlatType::Apply(symbol, _) => { + unreachable!("Symbol {:?} does not have a layout", symbol) + } + + FlatType::Func(_arguments, lambda_set, _result) => { + // in this case, a function (pointer) is represented by the environment it + // captures: the lambda set + + Self::from_var_help(layouts, subs, *lambda_set) + } + FlatType::Record(fields, ext) => { + debug_assert!(ext_var_is_empty_record(subs, *ext)); + + let mut slice = Slice::reserve(layouts, fields.len()); + + let mut non_optional_fields = 0; + let it = slice.indices().zip(fields.iter_all()); + for (target_index, (_, field_index, var_index)) in it { + match subs.record_fields[field_index.index as usize] { + RecordField::Optional(_) => { + // do nothing + } + RecordField::Required(_) | RecordField::Demanded(_) => { + let var = subs.variables[var_index.index as usize]; + let layout = Layout::from_var_help(layouts, subs, var)?; + + layouts.layouts[target_index] = layout; + + non_optional_fields += 1; + } + } + } + + // we have some wasted space in the case of optional fields; so be it + slice.length = non_optional_fields; + + layouts.sort_slice_by_alignment(slice); + + Ok(Layout::Struct(slice)) + } + FlatType::TagUnion(union_tags, ext) => { + debug_assert!(ext_var_is_empty_tag_union(subs, *ext)); + + let slices = + Self::from_slice_variable_slice(layouts, subs, union_tags.variables())?; + + Ok(Layout::UnionNonRecursive(slices)) + } + + FlatType::FunctionOrTagUnion(_, _, ext) => { + debug_assert!(ext_var_is_empty_tag_union(subs, *ext)); + + // at this point we know this is a tag + Ok(Layout::UNIT) + } + FlatType::RecursiveTagUnion(rec_var, union_tags, ext) => { + debug_assert!(ext_var_is_empty_tag_union(subs, *ext)); + + let rec_var = subs.get_root_key_without_compacting(*rec_var); + + let cached = layouts + .recursion_variable_to_structure_variable_map + .get(&rec_var); + + if let Some(layout_index) = cached { + match layouts.layouts[layout_index.index as usize] { + Layout::Reserved => { + // we have to do the work here to fill this reserved variable in + } + other => { + return Ok(other); + } + } + } + + let slices = + Self::from_slice_variable_slice(layouts, subs, union_tags.variables())?; + + Ok(Layout::UnionRecursive(slices)) + } + FlatType::Erroneous(_) => Err(TypeError(())), + FlatType::EmptyRecord => Ok(Layout::UNIT), + FlatType::EmptyTagUnion => Ok(Layout::VOID), + } + } + + fn from_slice_variable_slice( + layouts: &mut Layouts, + subs: &Subs, + slice_variable_slice: roc_types::subs::SubsSlice, + ) -> Result>, LayoutError> { + let slice = Slice::reserve(layouts, slice_variable_slice.len()); + + let variable_slices = &subs.variable_slices[slice_variable_slice.indices()]; + let it = slice.indices().zip(variable_slices); + for (target_index, variable_slice) in it { + let layout_slice = Layout::from_variable_slice(layouts, subs, *variable_slice)?; + layouts.layout_slices[target_index] = layout_slice; + } + + Ok(slice) + } + + fn from_variable_slice( + layouts: &mut Layouts, + subs: &Subs, + variable_subs_slice: roc_types::subs::VariableSubsSlice, + ) -> Result, LayoutError> { + let slice = Slice::reserve(layouts, variable_subs_slice.len()); + + let variable_slice = &subs.variables[variable_subs_slice.indices()]; + let it = slice.indices().zip(variable_slice); + for (target_index, var) in it { + let layout = Layout::from_var_help(layouts, subs, *var)?; + layouts.layouts[target_index] = layout; + } + + layouts.sort_slice_by_alignment(slice); + + Ok(slice) + } +} diff --git a/compiler/mono/src/lib.rs b/compiler/mono/src/lib.rs index b1986bc2f8..9dca478015 100644 --- a/compiler/mono/src/lib.rs +++ b/compiler/mono/src/lib.rs @@ -4,9 +4,11 @@ pub mod alias_analysis; pub mod borrow; +pub mod gen_refcount; pub mod inc_dec; pub mod ir; pub mod layout; +pub mod layout_soa; pub mod low_level; pub mod reset_reuse; pub mod tail_recursion; diff --git a/compiler/mono/src/reset_reuse.rs b/compiler/mono/src/reset_reuse.rs index cf8ecff9cf..fc63890d73 100644 --- a/compiler/mono/src/reset_reuse.rs +++ b/compiler/mono/src/reset_reuse.rs @@ -1,5 +1,7 @@ use crate::inc_dec::{collect_stmt, occurring_variables_expr, JPLiveVarMap, LiveVarSet}; -use crate::ir::{BranchInfo, Call, Expr, ListLiteralElement, Proc, Stmt}; +use crate::ir::{ + BranchInfo, Call, Expr, ListLiteralElement, Proc, Stmt, UpdateModeId, UpdateModeIds, +}; use crate::layout::{Layout, TagIdIntType, UnionLayout}; use bumpalo::collections::Vec; use bumpalo::Bump; @@ -10,12 +12,14 @@ pub fn insert_reset_reuse<'a, 'i>( arena: &'a Bump, home: ModuleId, ident_ids: &'i mut IdentIds, + update_mode_ids: &'i mut UpdateModeIds, mut proc: Proc<'a>, ) -> Proc<'a> { let mut env = Env { arena, home, ident_ids, + update_mode_ids, jp_live_vars: Default::default(), }; @@ -50,6 +54,7 @@ struct Env<'a, 'i> { /// required for creating new `Symbol`s home: ModuleId, ident_ids: &'i mut IdentIds, + update_mode_ids: &'i mut UpdateModeIds, jp_live_vars: JPLiveVarMap, } @@ -64,7 +69,7 @@ impl<'a, 'i> Env<'a, 'i> { fn function_s<'a, 'i>( env: &mut Env<'a, 'i>, - w: Symbol, + w: Opportunity, c: &CtorInfo<'a>, stmt: &'a Stmt<'a>, ) -> &'a Stmt<'a> { @@ -84,7 +89,8 @@ fn function_s<'a, 'i>( let update_tag_id = true; let new_expr = Expr::Reuse { - symbol: w, + symbol: w.symbol, + update_mode: w.update_mode, update_tag_id, tag_layout: *tag_layout, tag_id: *tag_id, @@ -175,13 +181,22 @@ fn function_s<'a, 'i>( } } +#[derive(Clone, Copy)] +struct Opportunity { + symbol: Symbol, + update_mode: UpdateModeId, +} + fn try_function_s<'a, 'i>( env: &mut Env<'a, 'i>, x: Symbol, c: &CtorInfo<'a>, stmt: &'a Stmt<'a>, ) -> &'a Stmt<'a> { - let w = env.unique_symbol(); + let w = Opportunity { + symbol: env.unique_symbol(), + update_mode: env.update_mode_ids.next_id(), + }; let new_stmt = function_s(env, w, c, stmt); @@ -194,7 +209,7 @@ fn try_function_s<'a, 'i>( fn insert_reset<'a>( env: &mut Env<'a, '_>, - w: Symbol, + w: Opportunity, x: Symbol, union_layout: UnionLayout<'a>, mut stmt: &'a Stmt<'a>, @@ -216,16 +231,21 @@ fn insert_reset<'a>( | Array { .. } | EmptyArray | Reuse { .. } - | Reset(_) + | Reset { .. } | RuntimeErrorFunction(_) => break, } } - let reset_expr = Expr::Reset(x); + let reset_expr = Expr::Reset { + symbol: x, + update_mode: w.update_mode, + }; let layout = Layout::Union(union_layout); - stmt = env.arena.alloc(Stmt::Let(w, reset_expr, layout, stmt)); + stmt = env + .arena + .alloc(Stmt::Let(w.symbol, reset_expr, layout, stmt)); for (symbol, expr, expr_layout) in stack.into_iter().rev() { stmt = env @@ -584,7 +604,7 @@ fn has_live_var_expr<'a>(expr: &'a Expr<'a>, needle: Symbol) -> bool { Expr::Reuse { symbol, arguments, .. } => needle == *symbol || arguments.iter().any(|s| *s == needle), - Expr::Reset(symbol) => needle == *symbol, + Expr::Reset { symbol, .. } => needle == *symbol, Expr::RuntimeErrorFunction(_) => false, } } diff --git a/compiler/parse/Cargo.toml b/compiler/parse/Cargo.toml index d76b482822..0e60203af5 100644 --- a/compiler/parse/Cargo.toml +++ b/compiler/parse/Cargo.toml @@ -17,5 +17,4 @@ pretty_assertions = "1.0.0" indoc = "1.0.3" quickcheck = "1.0.3" quickcheck_macros = "1.0.0" -diff = "0.1.12" -ansi_term = "0.12.1" +roc_test_utils = { path = "../../test_utils" } diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index b9dae1d201..cb831eac57 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -23,6 +23,7 @@ mod test_parse { use roc_parse::parser::{Parser, State, SyntaxError}; use roc_parse::test_helpers::parse_expr_with; use roc_region::all::{Located, Region}; + use roc_test_utils::assert_multiline_str_eq; use std::{f64, i64}; macro_rules! snapshot_tests { @@ -254,10 +255,7 @@ mod test_parse { } else { let expected_result = std::fs::read_to_string(&result_path).unwrap(); - // TODO: do a diff over the "real" content of these strings, rather than - // the debug-formatted content. As is, we get an ugly single-line diff - // from pretty_assertions - assert_eq!(expected_result, actual_result); + assert_multiline_str_eq!(expected_result, actual_result); } } diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 13f60ea072..dd713960f1 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -837,25 +837,13 @@ fn type_to_variable<'a>( actual, lambda_set_variables, } => { - // the rank of these variables is NONE (encoded as 0 in practice) - // using them for other ranks causes issues - if rank.is_none() { - // TODO replace by arithmetic? - match *symbol { - Symbol::NUM_I128 => return Variable::I128, - Symbol::NUM_I64 => return Variable::I64, - Symbol::NUM_I32 => return Variable::I32, - Symbol::NUM_I16 => return Variable::I16, - Symbol::NUM_I8 => return Variable::I8, - - Symbol::NUM_U128 => return Variable::U128, - Symbol::NUM_U64 => return Variable::U64, - Symbol::NUM_U32 => return Variable::U32, - Symbol::NUM_U16 => return Variable::U16, - Symbol::NUM_U8 => return Variable::U8, - - Symbol::NUM_NAT => return Variable::NAT, - _ => {} + if let Some(reserved) = Variable::get_reserved(*symbol) { + if rank.is_none() { + // reserved variables are stored with rank NONE + return reserved; + } else { + // for any other rank, we need to copy; it takes care of adjusting the rank + return deep_copy_var(subs, rank, pools, reserved); } } @@ -868,7 +856,11 @@ fn type_to_variable<'a>( lambda_set_variables, ); - let alias_variable = type_to_variable(subs, rank, pools, arena, actual); + let alias_variable = if let Symbol::RESULT_RESULT = *symbol { + roc_result_to_var(subs, rank, pools, arena, actual) + } else { + type_to_variable(subs, rank, pools, arena, actual) + }; let content = Content::Alias(*symbol, alias_variables, alias_variable); register(subs, rank, pools, content) @@ -941,6 +933,52 @@ fn alias_to_var<'a>( } } +fn roc_result_to_var<'a>( + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + arena: &'a bumpalo::Bump, + result_type: &Type, +) -> Variable { + match result_type { + Type::TagUnion(tags, ext) => { + debug_assert!(ext.is_empty_tag_union()); + debug_assert!(tags.len() == 2); + + if let [(err, err_args), (ok, ok_args)] = &tags[..] { + debug_assert_eq!(err, &subs.tag_names[0]); + debug_assert_eq!(ok, &subs.tag_names[1]); + + if let ([err_type], [ok_type]) = (err_args.as_slice(), ok_args.as_slice()) { + let err_var = type_to_variable(subs, rank, pools, arena, err_type); + let ok_var = type_to_variable(subs, rank, pools, arena, ok_type); + + let start = subs.variables.len() as u32; + let err_slice = SubsSlice::new(start, 1); + let ok_slice = SubsSlice::new(start + 1, 1); + + subs.variables.push(err_var); + subs.variables.push(ok_var); + + let variables = SubsSlice::new(subs.variable_slices.len() as _, 2); + subs.variable_slices.push(err_slice); + subs.variable_slices.push(ok_slice); + + let union_tags = UnionTags::from_slices(Subs::RESULT_TAG_NAMES, variables); + let ext = Variable::EMPTY_TAG_UNION; + + let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); + + return register(subs, rank, pools, content); + } + } + + unreachable!("invalid arguments to Result.Result; canonicalization should catch this!") + } + _ => unreachable!("not a valid type inside a Result.Result alias"), + } +} + fn insertion_sort_by(arr: &mut [T], mut compare: F) where F: FnMut(&T, &T) -> std::cmp::Ordering, diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index ff5691383c..64137bece2 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -228,10 +228,10 @@ mod solve_expr { infer_eq_without_problem( indoc!( r#" - Str.fromInt + Num.toStr "# ), - "Int * -> Str", + "Num * -> Str", ); } @@ -4543,8 +4543,8 @@ mod solve_expr { |> Str.concat ") (" |> Str.concat (printExpr b) |> Str.concat ")" - Val v -> Str.fromInt v - Var v -> "Var " |> Str.concat (Str.fromInt v) + Val v -> Num.toStr v + Var v -> "Var " |> Str.concat (Num.toStr v) main : Str main = printExpr (Var 3) diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 3ed20624f1..34e02bfff8 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -1,4 +1,5 @@ #![cfg(feature = "gen-llvm")] +#![cfg(feature = "gen-wasm")] #[cfg(feature = "gen-llvm")] use crate::helpers::llvm::assert_evals_to; @@ -6,8 +7,8 @@ use crate::helpers::llvm::assert_evals_to; // #[cfg(feature = "gen-dev")] // use crate::helpers::dev::assert_evals_to; -// #[cfg(feature = "gen-wasm")] -// use crate::helpers::wasm::assert_evals_to; +#[cfg(feature = "gen-wasm")] +use crate::helpers::wasm::assert_evals_to; use crate::helpers::with_larger_debug_stack; //use crate::assert_wasm_evals_to as assert_evals_to; @@ -22,7 +23,7 @@ fn roc_list_construction() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn empty_list_literal() { assert_evals_to!("[]", RocList::from_slice(&[]), RocList); } @@ -34,6 +35,7 @@ fn list_literal_empty_record() { } #[test] +#[cfg(any(feature = "gen-llvm"))] fn int_singleton_list_literal() { assert_evals_to!("[1, 2]", RocList::from_slice(&[1, 2]), RocList); } diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index b416209ec1..30ffc026ac 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -520,7 +520,7 @@ fn f64_log_negative() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn f64_round() { assert_evals_to!("Num.round 3.6", 4, i64); assert_evals_to!("Num.round 3.4", 3, i64); @@ -813,7 +813,7 @@ fn gen_add_i64() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn gen_sub_dec() { assert_evals_to!( indoc!( @@ -1171,7 +1171,7 @@ fn gen_order_of_arithmetic_ops_complex_float() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn if_guard_bind_variable_false() { assert_evals_to!( indoc!( @@ -1190,7 +1190,7 @@ fn if_guard_bind_variable_false() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn if_guard_bind_variable_true() { assert_evals_to!( indoc!( @@ -1990,3 +1990,35 @@ fn when_on_i16() { i16 ); } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn num_to_str() { + use roc_std::RocStr; + + assert_evals_to!( + r#"Num.toStr 1234"#, + RocStr::from_slice("1234".as_bytes()), + RocStr + ); + assert_evals_to!(r#"Num.toStr 0"#, RocStr::from_slice("0".as_bytes()), RocStr); + assert_evals_to!( + r#"Num.toStr -1"#, + RocStr::from_slice("-1".as_bytes()), + RocStr + ); + + let max = format!("{}", i64::MAX); + assert_evals_to!( + r#"Num.toStr Num.maxInt"#, + RocStr::from_slice(max.as_bytes()), + RocStr + ); + + let min = format!("{}", i64::MIN); + assert_evals_to!( + r#"Num.toStr Num.minInt"#, + RocStr::from_slice(min.as_bytes()), + RocStr + ); +} diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index 7928fc109e..d6dc7efeb6 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -996,7 +996,7 @@ fn annotation_without_body() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn simple_closure() { assert_evals_to!( indoc!( @@ -2602,7 +2602,7 @@ fn hit_unresolved_type_variable() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn pattern_match_empty_record() { assert_evals_to!( indoc!( diff --git a/compiler/test_gen/src/gen_records.rs b/compiler/test_gen/src/gen_records.rs index 41babc0e07..4cb8911eaa 100644 --- a/compiler/test_gen/src/gen_records.rs +++ b/compiler/test_gen/src/gen_records.rs @@ -254,7 +254,7 @@ fn twice_record_access() { ); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn empty_record() { assert_evals_to!( indoc!( diff --git a/compiler/test_gen/src/gen_str.rs b/compiler/test_gen/src/gen_str.rs index 691ae32971..1b77aa9a64 100644 --- a/compiler/test_gen/src/gen_str.rs +++ b/compiler/test_gen/src/gen_str.rs @@ -527,40 +527,6 @@ fn str_starts_with_false_small_str() { assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool); } -#[test] -#[cfg(any(feature = "gen-llvm"))] -fn str_from_int() { - assert_evals_to!( - r#"Str.fromInt 1234"#, - roc_std::RocStr::from_slice("1234".as_bytes()), - roc_std::RocStr - ); - assert_evals_to!( - r#"Str.fromInt 0"#, - roc_std::RocStr::from_slice("0".as_bytes()), - roc_std::RocStr - ); - assert_evals_to!( - r#"Str.fromInt -1"#, - roc_std::RocStr::from_slice("-1".as_bytes()), - roc_std::RocStr - ); - - let max = format!("{}", i64::MAX); - assert_evals_to!( - r#"Str.fromInt Num.maxInt"#, - RocStr::from_slice(max.as_bytes()), - RocStr - ); - - let min = format!("{}", i64::MIN); - assert_evals_to!( - r#"Str.fromInt Num.minInt"#, - RocStr::from_slice(min.as_bytes()), - RocStr - ); -} - #[test] #[cfg(any(feature = "gen-llvm"))] fn str_from_utf8_pass_single_ascii() { @@ -838,8 +804,8 @@ fn nested_recursive_literal() { |> Str.concat ") (" |> Str.concat (printExpr b) |> Str.concat ")" - Val v -> "Val " |> Str.concat (Str.fromInt v) - Var v -> "Var " |> Str.concat (Str.fromInt v) + Val v -> "Val " |> Str.concat (Num.toStr v) + Var v -> "Var " |> Str.concat (Num.toStr v) printExpr expr "# @@ -875,12 +841,6 @@ fn str_join_comma_single() { assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr); } -#[test] -#[cfg(any(feature = "gen-llvm"))] -fn str_from_float() { - assert_evals_to!(r#"Str.fromFloat 3.14"#, RocStr::from("3.14"), RocStr); -} - #[test] #[cfg(any(feature = "gen-llvm"))] fn str_to_utf8() { diff --git a/compiler/test_gen/src/helpers/dev.rs b/compiler/test_gen/src/helpers/dev.rs index 5b03b95eca..51906b3fae 100644 --- a/compiler/test_gen/src/helpers/dev.rs +++ b/compiler/test_gen/src/helpers/dev.rs @@ -188,8 +188,7 @@ pub fn helper( }; let target = target_lexicon::Triple::host(); - let module_object = - roc_gen_dev::build_module(&env, &target, procedures).expect("failed to compile module"); + let module_object = roc_gen_dev::build_module(&env, &target, procedures); let module_out = module_object .write() diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index f1633301de..e5750978e0 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -79,8 +79,9 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( use roc_load::file::MonomorphizedModule; let MonomorphizedModule { + module_id, procedures, - interns, + mut interns, exposed_to_host, .. } = loaded; @@ -114,12 +115,12 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( let env = roc_gen_wasm::Env { arena, - interns, + module_id, exposed_to_host, }; let (mut wasm_module, main_fn_index) = - roc_gen_wasm::build_module_help(&env, procedures).unwrap(); + roc_gen_wasm::build_module_help(&env, &mut interns, procedures).unwrap(); T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index); @@ -136,7 +137,7 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( let store = Store::default(); // Keep the final .wasm file for debugging with wasm-objdump or wasm2wat - const DEBUG_WASM_FILE: bool = true; + const DEBUG_WASM_FILE: bool = false; let wasmer_module = { let tmp_dir: TempDir; // directory for normal test runs, deleted when dropped @@ -166,33 +167,42 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( // write the module to a file so the linker can access it std::fs::write(&app_o_file, &module_bytes).unwrap(); - let _linker_output = std::process::Command::new("zig") - .args(&[ - "wasm-ld", - // input files - app_o_file.to_str().unwrap(), - bitcode::BUILTINS_WASM32_OBJ_PATH, - libc_a_file, - // output - "-o", - final_wasm_file.to_str().unwrap(), - // we don't define `_start` - "--no-entry", - // If you only specify test_wrapper, it will stop at the call to UserApp_main_1 - // But if you specify both exports, you get all the dependencies. - // - // It seems that it will not write out an export you didn't explicitly specify, - // even if it's a dependency of another export! - // In our case we always export main and test_wrapper so that's OK. - "--export", - "test_wrapper", - "--export", - "#UserApp_main_1", - ]) + let args = &[ + "wasm-ld", + // input files + app_o_file.to_str().unwrap(), + bitcode::BUILTINS_WASM32_OBJ_PATH, + libc_a_file, + // output + "-o", + final_wasm_file.to_str().unwrap(), + // we don't define `_start` + "--no-entry", + // If you only specify test_wrapper, it will stop at the call to UserApp_main_1 + // But if you specify both exports, you get all the dependencies. + // + // It seems that it will not write out an export you didn't explicitly specify, + // even if it's a dependency of another export! + // In our case we always export main and test_wrapper so that's OK. + "--export", + "test_wrapper", + "--export", + "#UserApp_main_1", + ]; + + let linker_output = std::process::Command::new("zig") + .args(args) .output() .unwrap(); - // dbg!(_linker_output); + if !linker_output.status.success() { + print!("\nLINKER FAILED\n"); + for arg in args { + print!("{} ", arg); + } + println!("\n{}", std::str::from_utf8(&linker_output.stdout).unwrap()); + println!("{}", std::str::from_utf8(&linker_output.stderr).unwrap()); + } Module::from_file(&store, &final_wasm_file).unwrap() }; diff --git a/compiler/test_gen/src/helpers/wasm32_test_result.rs b/compiler/test_gen/src/helpers/wasm32_test_result.rs index ddefb6b54a..869eb4cab8 100644 --- a/compiler/test_gen/src/helpers/wasm32_test_result.rs +++ b/compiler/test_gen/src/helpers/wasm32_test_result.rs @@ -141,6 +141,16 @@ where } } +impl Wasm32TestResult for () { + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + // Main's symbol index is the same as its function index, since the first symbols we created were for procs + let main_symbol_index = main_function_index; + code_builder.call(main_function_index, main_symbol_index, 0, false); + code_builder.get_global(0); + code_builder.build_fn_header(&[], 0, None); + } +} + impl Wasm32TestResult for (T, U) where T: Wasm32TestResult + FromWasm32Memory, diff --git a/compiler/test_gen/src/wasm_str.rs b/compiler/test_gen/src/wasm_str.rs index 7dafb39a5f..04013a41ca 100644 --- a/compiler/test_gen/src/wasm_str.rs +++ b/compiler/test_gen/src/wasm_str.rs @@ -446,39 +446,6 @@ fn str_starts_with_false_small_str() { assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool); } -// #[test] -// fn str_from_int() { -// assert_evals_to!( -// r#"Str.fromInt 1234"#, -// roc_std::RocStr::from_slice("1234".as_bytes()), -// roc_std::RocStr -// ); -// assert_evals_to!( -// r#"Str.fromInt 0"#, -// roc_std::RocStr::from_slice("0".as_bytes()), -// roc_std::RocStr -// ); -// assert_evals_to!( -// r#"Str.fromInt -1"#, -// roc_std::RocStr::from_slice("-1".as_bytes()), -// roc_std::RocStr -// ); - -// let max = format!("{}", i64::MAX); -// assert_evals_to!( -// r#"Str.fromInt Num.maxInt"#, -// RocStr::from_slice(max.as_bytes()), -// RocStr -// ); - -// let min = format!("{}", i64::MIN); -// assert_evals_to!( -// r#"Str.fromInt Num.minInt"#, -// RocStr::from_slice(min.as_bytes()), -// RocStr -// ); -// } - // #[test] // fn str_from_utf8_pass_single_ascii() { // assert_evals_to!( @@ -729,8 +696,8 @@ fn str_starts_with_false_small_str() { // |> Str.concat ") (" // |> Str.concat (printExpr b) // |> Str.concat ")" -// Val v -> "Val " |> Str.concat (Str.fromInt v) -// Var v -> "Var " |> Str.concat (Str.fromInt v) +// Val v -> "Val " |> Str.concat (Num.toStr v) +// Var v -> "Var " |> Str.concat (Num.toStr v) // printExpr expr // "# @@ -763,11 +730,6 @@ fn str_starts_with_false_small_str() { // assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr); // } -// #[test] -// fn str_from_float() { -// assert_evals_to!(r#"Str.fromFloat 3.14"#, RocStr::from("3.14"), RocStr); -// } - // #[test] // fn str_to_utf8() { // assert_evals_to!( @@ -903,8 +865,8 @@ fn str_starts_with_false_small_str() { #[test] fn str_repeat_small() { assert_evals_to!( - indoc!(r#"Str.repeat "Roc" 3"#), - RocStr::from("RocRocRoc"), + indoc!(r#"Str.repeat "Roc" 2"#), + RocStr::from("RocRoc"), RocStr ); } @@ -941,8 +903,8 @@ fn str_trim_small_blank_string() { #[test] fn str_trim_small_to_small() { assert_evals_to!( - indoc!(r#"Str.trim " hello world ""#), - RocStr::from("hello world"), + indoc!(r#"Str.trim " hello ""#), + RocStr::from("hello"), RocStr ); } @@ -959,8 +921,8 @@ fn str_trim_large_to_large_unique() { #[test] fn str_trim_large_to_small_unique() { assert_evals_to!( - indoc!(r#"Str.trim (Str.concat " " "hello world ")"#), - RocStr::from("hello world"), + indoc!(r#"Str.trim (Str.concat " " "hello ")"#), + RocStr::from("hello"), RocStr ); } @@ -990,15 +952,12 @@ fn str_trim_large_to_small_shared() { indoc!( r#" original : Str - original = " hello world " + original = " hello " { trimmed: Str.trim original, original: original } "# ), - ( - RocStr::from(" hello world "), - RocStr::from("hello world"), - ), + (RocStr::from(" hello "), RocStr::from("hello"),), (RocStr, RocStr) ); } @@ -1009,12 +968,12 @@ fn str_trim_small_to_small_shared() { indoc!( r#" original : Str - original = " hello world " + original = " hello " { trimmed: Str.trim original, original: original } "# ), - (RocStr::from(" hello world "), RocStr::from("hello world"),), + (RocStr::from(" hello "), RocStr::from("hello"),), (RocStr, RocStr) ); } @@ -1027,8 +986,8 @@ fn str_trim_left_small_blank_string() { #[test] fn str_trim_left_small_to_small() { assert_evals_to!( - indoc!(r#"Str.trimLeft " hello world ""#), - RocStr::from("hello world "), + indoc!(r#"Str.trimLeft " hello ""#), + RocStr::from("hello "), RocStr ); } @@ -1045,8 +1004,8 @@ fn str_trim_left_large_to_large_unique() { #[test] fn str_trim_left_large_to_small_unique() { assert_evals_to!( - indoc!(r#"Str.trimLeft (Str.concat " " "hello world ")"#), - RocStr::from("hello world "), + indoc!(r#"Str.trimLeft (Str.concat " " "hello ")"#), + RocStr::from("hello "), RocStr ); } @@ -1059,8 +1018,8 @@ fn str_trim_right_small_blank_string() { #[test] fn str_trim_right_small_to_small() { assert_evals_to!( - indoc!(r#"Str.trimRight " hello world ""#), - RocStr::from(" hello world"), + indoc!(r#"Str.trimRight " hello ""#), + RocStr::from(" hello"), RocStr ); } @@ -1077,8 +1036,8 @@ fn str_trim_right_large_to_large_unique() { #[test] fn str_trim_right_large_to_small_unique() { assert_evals_to!( - indoc!(r#"Str.trimRight (Str.concat " hello world" " ")"#), - RocStr::from(" hello world"), + indoc!(r#"Str.trimRight (Str.concat " hello" " ")"#), + RocStr::from(" hello"), RocStr ); } @@ -1108,15 +1067,12 @@ fn str_trim_right_large_to_small_shared() { indoc!( r#" original : Str - original = " hello world " + original = " hello " { trimmed: Str.trimRight original, original: original } "# ), - ( - RocStr::from(" hello world "), - RocStr::from(" hello world"), - ), + (RocStr::from(" hello "), RocStr::from(" hello"),), (RocStr, RocStr) ); } @@ -1127,12 +1083,12 @@ fn str_trim_right_small_to_small_shared() { indoc!( r#" original : Str - original = " hello world " + original = " hello " { trimmed: Str.trimRight original, original: original } "# ), - (RocStr::from(" hello world "), RocStr::from(" hello world"),), + (RocStr::from(" hello "), RocStr::from(" hello"),), (RocStr, RocStr) ); } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index bbca449522..4fcbc5f4da 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -708,6 +708,29 @@ impl Variable { pub const fn index(&self) -> u32 { self.0 } + + pub const fn get_reserved(symbol: Symbol) -> Option { + // Must be careful here: the variables must in fact be in Subs + match symbol { + Symbol::NUM_I128 => Some(Variable::I128), + Symbol::NUM_I64 => Some(Variable::I64), + Symbol::NUM_I32 => Some(Variable::I32), + Symbol::NUM_I16 => Some(Variable::I16), + Symbol::NUM_I8 => Some(Variable::I8), + + Symbol::NUM_U128 => Some(Variable::U128), + Symbol::NUM_U64 => Some(Variable::U64), + Symbol::NUM_U32 => Some(Variable::U32), + Symbol::NUM_U16 => Some(Variable::U16), + Symbol::NUM_U8 => Some(Variable::U8), + + Symbol::NUM_NAT => Some(Variable::NAT), + + Symbol::BOOL_BOOL => Some(Variable::BOOL), + + _ => None, + } + } } impl From for OptVariable { @@ -1012,6 +1035,8 @@ fn define_integer_types(subs: &mut Subs) { } impl Subs { + pub const RESULT_TAG_NAMES: SubsSlice = SubsSlice::new(0, 2); + pub fn new() -> Self { Self::with_capacity(0) } @@ -1019,10 +1044,15 @@ impl Subs { pub fn with_capacity(capacity: usize) -> Self { let capacity = capacity.max(Variable::NUM_RESERVED_VARS); + let mut tag_names = Vec::with_capacity(32); + + tag_names.push(TagName::Global("Err".into())); + tag_names.push(TagName::Global("Ok".into())); + let mut subs = Subs { utable: UnificationTable::default(), variables: Default::default(), - tag_names: Default::default(), + tag_names, field_names: Default::default(), record_fields: Default::default(), // store an empty slice at the first position diff --git a/docs/tests/insert_syntax_highlighting.rs b/docs/tests/insert_syntax_highlighting.rs index e211786673..ad75f0a64a 100644 --- a/docs/tests/insert_syntax_highlighting.rs +++ b/docs/tests/insert_syntax_highlighting.rs @@ -175,8 +175,8 @@ main = "Hello, world!" #[test] fn call_builtin() { expect_html_def( - r#"myVal = Str.fromInt 1234"#, - "myVal = Str.fromInt 1234\n\n", + r#"myVal = Num.toStr 1234"#, + "myVal = Num.toStr 1234\n\n", ); } diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 8cf82eec86..82128eabef 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -33,7 +33,7 @@ roc_reporting = { path = "../reporting" } roc_solve = { path = "../compiler/solve" } ven_graph = { path = "../vendor/pathfinding" } bumpalo = { version = "3.8.0", features = ["collections"] } -arraystring = "0.3.0" +arrayvec = "0.7.2" libc = "0.2.106" page_size = "0.4.2" # once winit 0.26 is out, check if copypasta can be updated simultaneously so they use the same versions for their dependencies. This will save build time. diff --git a/editor/src/editor/mvc/string_update.rs b/editor/src/editor/mvc/string_update.rs index 8ee2ddd881..f88c6c11ef 100644 --- a/editor/src/editor/mvc/string_update.rs +++ b/editor/src/editor/mvc/string_update.rs @@ -36,14 +36,12 @@ pub fn update_small_string( .get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?; if node_caret_offset != 0 && node_caret_offset < content_str_mut.len() { - if old_array_str.len() < ArrString::capacity() { + if old_array_str.len() < old_array_str.capacity() { if let Expr2::SmallStr(ref mut mut_array_str) = ed_model.module.env.pool.get_mut(ast_node_id.to_expr_id()?) { // safe because we checked the length - unsafe { - mut_array_str.push_unchecked(*new_char); - } + mut_array_str.push(*new_char); } else { unreachable!() } @@ -137,7 +135,7 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult { } = get_node_context(ed_model)?; if curr_mark_node.is_blank() { - let new_expr2_node = Expr2::SmallStr(arraystring::ArrayString::new()); + let new_expr2_node = Expr2::SmallStr(arrayvec::ArrayString::new()); let curr_mark_node_nls = curr_mark_node.get_newlines_at_end(); ed_model diff --git a/examples/benchmarks/CFold.roc b/examples/benchmarks/CFold.roc index 7c17abf066..f29be78d9e 100644 --- a/examples/benchmarks/CFold.roc +++ b/examples/benchmarks/CFold.roc @@ -13,9 +13,9 @@ main = optimized = eval (constFolding (reassoc e)) unoptimized - |> Str.fromInt + |> Num.toStr |> Str.concat " & " - |> Str.concat (Str.fromInt optimized) + |> Str.concat (Num.toStr optimized) |> Task.putLine Expr : [ diff --git a/examples/benchmarks/Deriv.roc b/examples/benchmarks/Deriv.roc index cd8e5fd800..564163aa5f 100644 --- a/examples/benchmarks/Deriv.roc +++ b/examples/benchmarks/Deriv.roc @@ -115,9 +115,9 @@ deriv : I64, Expr -> IO Expr deriv = \i, f -> fprime = d "x" f line = - Str.fromInt (i + 1) + Num.toStr (i + 1) |> Str.concat " count: " - |> Str.concat (Str.fromInt (count fprime)) + |> Str.concat (Num.toStr (count fprime)) Task.putLine line |> Task.after \_ -> Task.succeed fprime diff --git a/examples/benchmarks/NQueens.roc b/examples/benchmarks/NQueens.roc index 7435e52f2c..bff4245709 100644 --- a/examples/benchmarks/NQueens.roc +++ b/examples/benchmarks/NQueens.roc @@ -7,7 +7,7 @@ main : Task.Task {} [] main = Task.after Task.getInt \n -> queens n # original koka 13 - |> Str.fromInt + |> Num.toStr |> Task.putLine ConsList a : [ Nil, Cons a (ConsList a) ] diff --git a/examples/benchmarks/Quicksort.roc b/examples/benchmarks/Quicksort.roc index 271be43894..e3f4585959 100644 --- a/examples/benchmarks/Quicksort.roc +++ b/examples/benchmarks/Quicksort.roc @@ -7,7 +7,7 @@ show = \list -> else content = list - |> List.map Str.fromInt + |> List.map Num.toStr |> Str.joinWith ", " "[ \(content) ]" diff --git a/examples/benchmarks/RBTreeCk.roc b/examples/benchmarks/RBTreeCk.roc index c9595875ec..8f3f735ad3 100644 --- a/examples/benchmarks/RBTreeCk.roc +++ b/examples/benchmarks/RBTreeCk.roc @@ -48,14 +48,15 @@ resultWithDefault = \res, default -> main : Task.Task {} [] main = Task.after Task.getInt \n -> + # original koka n = 4_200_000 ms : ConsList Map - ms = makeMap 5 n # original koka n = 4_200_000 + ms = makeMap 5 n when ms is Cons head _ -> val = fold (\_, v, r -> if v then r + 1 else r) head 0 val - |> Str.fromInt + |> Num.toStr |> Task.putLine Nil -> diff --git a/examples/benchmarks/RBTreeDel.roc b/examples/benchmarks/RBTreeDel.roc index ccb1753042..97ebb3bacf 100644 --- a/examples/benchmarks/RBTreeDel.roc +++ b/examples/benchmarks/RBTreeDel.roc @@ -20,7 +20,7 @@ main = val = fold (\_, v, r -> if v then r + 1 else r) m 0 val - |> Str.fromInt + |> Num.toStr |> Task.putLine boom : Str -> a diff --git a/examples/benchmarks/RBTreeInsert.roc b/examples/benchmarks/RBTreeInsert.roc index e12b4e700d..d938ecc1d0 100644 --- a/examples/benchmarks/RBTreeInsert.roc +++ b/examples/benchmarks/RBTreeInsert.roc @@ -13,7 +13,7 @@ main = |> Task.putLine show : RedBlackTree I64 {} -> Str -show = \tree -> showRBTree tree Str.fromInt (\{} -> "{}") +show = \tree -> showRBTree tree Num.toStr (\{} -> "{}") showRBTree : RedBlackTree k v, (k -> Str), (v -> Str) -> Str showRBTree = \tree, showKey, showValue -> diff --git a/examples/benchmarks/TestAStar.roc b/examples/benchmarks/TestAStar.roc index 0401566b6a..f906a2b7bb 100644 --- a/examples/benchmarks/TestAStar.roc +++ b/examples/benchmarks/TestAStar.roc @@ -13,7 +13,7 @@ main = # Task.putLine (showBool test1) # # _ -> -# ns = Str.fromInt n +# ns = Num.toStr n # Task.putLine "No test \(ns)" showBool : Bool -> Str diff --git a/examples/cli/README.md b/examples/cli/README.md new file mode 100644 index 0000000000..91591d963f --- /dev/null +++ b/examples/cli/README.md @@ -0,0 +1,8 @@ +# Command Line Interface (CLI) Example + +This is an example of how to make an extremely basic CLI in Roc. + +There's not currently much documentation for the CLI platform (which also doesn't support many operations at this point!) +but you can look at [the modules it includes](platform) - for example, +multiple other modules use the [`Task`](platform/Task.roc) module, including the +[`Stdin`](platform/Stdin.roc) and [`Stdout`](platform/Stdout.roc) modules. diff --git a/examples/false-interpreter/Context.roc b/examples/false-interpreter/Context.roc index 77022737ae..75c702acd3 100644 --- a/examples/false-interpreter/Context.roc +++ b/examples/false-interpreter/Context.roc @@ -33,7 +33,7 @@ toStrData: Data -> Str toStrData = \data -> when data is Lambda _ -> "[]" - Number n -> Str.fromInt (Num.intCast n) + Number n -> Num.toStr (Num.intCast n) Var v -> Variable.toStr v toStrState: State -> Str @@ -49,7 +49,7 @@ toStrState = \state -> toStr: Context -> Str toStr = \{scopes, stack, state, vars} -> - depth = Str.fromInt (List.len scopes) + depth = Num.toStr (List.len scopes) stateStr = toStrState state stackStr = Str.joinWith (List.map stack toStrData) " " varsStr = Str.joinWith (List.map vars toStrData) " " diff --git a/examples/false-interpreter/False.roc b/examples/false-interpreter/False.roc index 716197cc99..632b9905cb 100644 --- a/examples/false-interpreter/False.roc +++ b/examples/false-interpreter/False.roc @@ -203,7 +203,7 @@ interpretCtx = \ctx -> # This is supposed to flush io buffers. We don't buffer, so it does nothing interpretCtx newCtx Ok (T x _) -> - data = Str.fromInt (Num.intCast x) + data = Num.toStr (Num.intCast x) Task.fail (InvalidChar data) Err NoScope -> Task.fail NoScope @@ -358,7 +358,7 @@ stepExecCtx = \ctx, char -> 0x2E -> # `.` write int when popNumber ctx is Ok (T popCtx num) -> - {} <- Task.await (Stdout.raw (Str.fromInt (Num.intCast num))) + {} <- Task.await (Stdout.raw (Num.toStr (Num.intCast num))) Task.succeed popCtx Err e -> Task.fail e @@ -395,7 +395,7 @@ stepExecCtx = \ctx, char -> Ok var -> Task.succeed (Context.pushStack ctx (Var var)) Err _ -> - data = Str.fromInt (Num.intCast x) + data = Num.toStr (Num.intCast x) Task.fail (InvalidChar data) unaryOp: Context, (I32 -> I32) -> Result Context InterpreterErrors diff --git a/getting_started/linux_x86.md b/getting_started/linux_x86.md index fb60a1293d..478a146dfc 100644 --- a/getting_started/linux_x86.md +++ b/getting_started/linux_x86.md @@ -1,9 +1,10 @@ -1. Download the latest nightly from the assets [here](https://github.com/rtfeldman/roc/releases). -2. Untar the archive: +0. Download the latest nightly from the assets [here](https://github.com/rtfeldman/roc/releases). +0. Untar the archive: ``` tar -xf roc_nightly-linux_x86_64-.tar.gz ``` -3. To be able to run examples: +0. Some fresh installs require executing `sudo apt update`, it is not needed to execute `sudo apt upgrade` after this. +0. To be able to run examples: - for the Rust example: ``` curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh @@ -16,15 +17,15 @@ ``` - for the C example: ``` - sudo apt install clang + sudo apt install build-essential clang ``` -4. Run examples with: +0. Run examples with: ``` - # Rust + # Rust. If you installed rust in this terminal you'll need to open a new one first! ./roc examples/hello-rust/Hello.roc # Zig ./roc examples/hello-zig/Hello.roc # C ./roc examples/hello-world/Hello.roc ``` -5. See [here](../README.md#examples) for the other examples. +0. See [here](../README.md#examples) for the other examples. diff --git a/linker/src/main.rs b/linker/src/main.rs index 6c1f341f9d..93a4c2a9d8 100644 --- a/linker/src/main.rs +++ b/linker/src/main.rs @@ -4,16 +4,10 @@ use std::io; fn main() -> io::Result<()> { let matches = build_app().get_matches(); - let exit_code = match matches.subcommand_name() { + let exit_code = match matches.subcommand() { None => Ok::(-1), - Some(CMD_PREPROCESS) => { - let sub_matches = matches.subcommand_matches(CMD_PREPROCESS).unwrap(); - preprocess(sub_matches) - } - Some(CMD_SURGERY) => { - let sub_matches = matches.subcommand_matches(CMD_SURGERY).unwrap(); - surgery(sub_matches) - } + Some((CMD_PREPROCESS, sub_matches)) => preprocess(sub_matches), + Some((CMD_SURGERY, sub_matches)) => surgery(sub_matches), _ => unreachable!(), }?; std::process::exit(exit_code); diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 735861ebb3..948860825c 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -13,7 +13,7 @@ mod test_reporting { use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut}; use bumpalo::Bump; use roc_module::symbol::{Interns, ModuleId}; - use roc_mono::ir::{Procs, Stmt}; + use roc_mono::ir::{Procs, Stmt, UpdateModeIds}; use roc_mono::layout::LayoutCache; use roc_reporting::report::{ can_problem, mono_problem, parse_problem, type_problem, Report, Severity, BLUE_CODE, @@ -91,6 +91,7 @@ mod test_reporting { // Compile and add all the Procs before adding main let mut procs = Procs::new_in(&arena); let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); + let mut update_mode_ids = UpdateModeIds::new(); // Populate Procs and Subs, and get the low-level Expr from the canonical Expr let ptr_bytes = 8; @@ -101,8 +102,8 @@ mod test_reporting { problems: &mut mono_problems, home, ident_ids: &mut ident_ids, + update_mode_ids: &mut update_mode_ids, ptr_bytes, - update_mode_counter: 0, // call_specialization_counter=0 is reserved call_specialization_counter: 1, }; diff --git a/roc-for-elm-programmers.md b/roc-for-elm-programmers.md index da0215f5a4..5d4ddb6d62 100644 --- a/roc-for-elm-programmers.md +++ b/roc-for-elm-programmers.md @@ -319,7 +319,7 @@ table = \{ height, width, title ? "", description ? "" } -> This says that `table` takes a record with two *required* fields (`height` and `width` and two *optional* fields (`title` and `description`). It also says that the `height` and `width` fields have the type `Pixels` (a type alias for some -numeric type), whereas the `title` and `description` fields have the type `Str.` +numeric type), whereas the `title` and `description` fields have the type `Str`. This means you can choose to omit `title`, `description`, or both, when calling the function...but if you provide them, they must have the type `Str`. @@ -708,7 +708,7 @@ because `@` tags are not allowed in the exposing list. Only code written in this `Username` module can instantiate a `@Username` value. > If I were to write `@Username` inside another module (e.g. `Main`), it would compile, -> but that `@Username` would be type-incompatible with one created inside the `Username` module. +> but that `@Username` would be type-incompatible with the one created inside the `Username` module. > Even trying to use `==` on them would be a type mismatch, because I would be comparing > a `[ Username.@Username Str ]*` with a `[ Main.@Username Str ]*`, which are incompatible. diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml new file mode 100644 index 0000000000..d71698cd2f --- /dev/null +++ b/test_utils/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "roc_test_utils" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" +description = "Utility functions used all over the code base." + +[dependencies] +pretty_assertions = "1.0.0" + +[dev-dependencies] diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs new file mode 100644 index 0000000000..bca9060a5d --- /dev/null +++ b/test_utils/src/lib.rs @@ -0,0 +1,18 @@ +#[doc(hidden)] +pub use pretty_assertions::assert_eq as _pretty_assert_eq; + +#[derive(PartialEq)] +pub struct DebugAsDisplay(pub T); + +impl std::fmt::Debug for DebugAsDisplay { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +#[macro_export] +macro_rules! assert_multiline_str_eq { + ($a:expr, $b:expr) => { + $crate::_pretty_assert_eq!($crate::DebugAsDisplay($a), $crate::DebugAsDisplay($b)) + }; +}