mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 14:24:45 +00:00
Merge remote-tracking branch 'origin/trunk' into list-str-capacity
This commit is contained in:
commit
c4feacb94a
16 changed files with 2031 additions and 1257 deletions
1
AUTHORS
1
AUTHORS
|
@ -64,3 +64,4 @@ Jan Van Bruggen <JanCVanB@users.noreply.github.com>
|
|||
Mats Sigge <<mats.sigge@gmail.com>>
|
||||
Drew Lazzeri <dlazzeri1@gmail.com>
|
||||
Tom Dohrmann <erbse.13@gmx.de>
|
||||
Elijah Schow <elijah.schow@gmail.com>
|
||||
|
|
277
FAQ.md
Normal file
277
FAQ.md
Normal file
|
@ -0,0 +1,277 @@
|
|||
# Frequently Asked Questions
|
||||
|
||||
## Why doesn't Roc have higher-kinded polymorphism or arbitrary-rank types?
|
||||
|
||||
_Since this is a FAQ answer, I'm going to assume familiarity with higher-kinded types and higher-rank types instead of including a primer on them._
|
||||
|
||||
A valuable aspect of Roc's type system is that it has [principal](https://en.wikipedia.org/wiki/Principal_type)
|
||||
type inference. This means that:
|
||||
|
||||
* At compile time, Roc can correctly infer the types for every expression in a program, even if you don't annotate any of the types.
|
||||
* This inference always infers the most general type possible; you couldn't possibly add a valid type annotation that would make the type more flexible than the one that Roc would infer if you deleted the annotation.
|
||||
|
||||
It's been proven that any type system which supports either [higher-kinded polymorphism](https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-kinded-polymorphism.pdf) or [arbitrary-rank types](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/putting.pdf) cannot have
|
||||
principal type inference. With either of those features in the language, there will be situations where the compiler
|
||||
reports an error that can only be fixed by the programmer adding a type annotation. This also means there would be
|
||||
situations where the editor would not be able to reliably tell you the type of part of your program, unlike today
|
||||
where it can accurately tell you the type of anything, even if you have no type annotations in your entire code base.
|
||||
|
||||
### Arbitrary-rank types
|
||||
|
||||
Unlike arbitrary-rank (aka "Rank-N") types, both Rank-1 and Rank-2 type systems are compatible with principal
|
||||
type inference. Roc currently uses Rank-1 types, and the benefits of Rank-N over Rank-2 don't seem worth
|
||||
sacrificing principal type inference to attain, so let's focus on the trade-offs between Rank-1 and Rank-2.
|
||||
|
||||
Supporting Rank-2 types in Roc has been discussed before, but it has several important downsides:
|
||||
|
||||
* It would increase the complexity of the language.
|
||||
* It would make some compiler error messages more confusing (e.g. they might mention `forall` because that was the most general type that could be inferred, even if that wasn't helpful or related to the actual problem).
|
||||
* It would substantially increase the complexity of the type checker, which would necessarily slow it down.
|
||||
|
||||
No implementation of Rank-2 types can remove any of these downsides. Thus far, we've been able to come up
|
||||
with sufficiently nice APIs that only require Rank-1 types, and we haven't seen a really compelling use case
|
||||
where the gap between the Rank-2 and Rank-1 designs was big enough to justify switching to Rank-2.
|
||||
|
||||
Since I prefer Roc being simpler and having a faster compiler with nicer error messages, my hope is that Roc
|
||||
will never get Rank-2 types. However, it may turn out that in the future we learn about currently-unknown
|
||||
upsides that somehow outweigh these downsides, so I'm open to considering the possibility - while rooting against it.
|
||||
|
||||
### Higher-kinded polymorphism
|
||||
|
||||
I want to be really clear about this one: the explicit plan is that Roc will never support higher-kinded polymorphism.
|
||||
|
||||
On the technical side, the reasons for this are ordinary: I understand the practical benefits and
|
||||
drawbacks of HKP, and I think the drawbacks outweigh the benefits when it comes to Roc. (Those who come to a
|
||||
different conclusion may think HKP's drawbacks would be less of a big a deal in Roc than I do. That's reasonable;
|
||||
we programmers often weigh the same trade-offs differently.) To be clear, I think this in the specific context of
|
||||
Roc; there are plenty of other languages where HKP seems like a great fit. For example, it's hard to imagine Haskell
|
||||
without it. Similarly, I think lifetime annotations are a great fit for Rust, but don't think they'd be right
|
||||
for Roc either.
|
||||
|
||||
I also think it's important to consider the cultural implications of deciding whether or not to support HKP.
|
||||
To illustrate what I mean, imagine this conversation:
|
||||
|
||||
**Programmer 1:** "How do you feel about higher-kinded polymorphism?"
|
||||
|
||||
**Programmer 2:** "I have no idea what that is."
|
||||
|
||||
**Programmer 1:** "Okay, how do you feel about monads?"
|
||||
|
||||
**Programmer 2:** "OH NO."
|
||||
|
||||
I've had several variations of this conversation: I'm talking about higher-kinded types,
|
||||
another programmer asks what that means, I give monads as an example, and their reaction is strongly negative.
|
||||
I've also had plenty of conversations with programmers who love HKP and vigorously advocate for its addition
|
||||
to languages they use which don't have it. Feelings about HKP seem strongly divided, maybe more so
|
||||
than any other type system feature besides static and dynamic types.
|
||||
|
||||
It's impossible for a programming language to be neutral on this. If the language doesn't support HKP, nobody can
|
||||
implement a Monad typeclass (or equivalent) in any way that can be expected to catch on. Advocacy to add HKP to the
|
||||
language will inevitably follow. If the language does support HKP, one or more alternate standard libraries built
|
||||
around monads will inevitably follow, along with corresponding cultural changes. (See Scala for example.)
|
||||
Culturally, to support HKP is to take a side, and to decline to support it is also to take a side.
|
||||
|
||||
Given this, language designers have three options:
|
||||
|
||||
* Have HKP and have Monad in the standard library. Embrace them and build a culture and ecosystem around them.
|
||||
* Have HKP and don't have Monad in the standard library. An alternate standard lbirary built around monads will inevitably emerge, and both the community and ecosystem will divide themselves along pro-monad and anti-monad lines.
|
||||
* Don't have HKP; build a culture and ecosystem around other things.
|
||||
|
||||
Considering that these are the only three options, I think the best choice for Roc—not only on a technical
|
||||
level, but on a cultural level as well—is to make it clear that the plan is for Roc never to support HKP.
|
||||
I hope this clarity can save a lot of community members' time that would otherwise be spent on advocacy or
|
||||
arguing between the two sides of the divide. Again, I think it's completely reasonable for anyone to have a
|
||||
different preference, but given that language designers can only choose one of these options, I'm confident
|
||||
I've made the right choice for Roc by designing it never to have higher-kinded polymorphism.
|
||||
|
||||
## Why does Roc's syntax and standard library differ from Elm's?
|
||||
|
||||
Roc is a direct descendant of [Elm](https://elm-lang.org/). However, there are some differences between the two languages.
|
||||
|
||||
Syntactic differences are among these. This is a feature, not a bug; if Roc had identical syntax to Elm, then it's
|
||||
predictable that people would write code that was designed to work in both languages - and would then rely on
|
||||
that being true, for example by making a package which advertised "Works in both Elm and Roc!" This in turn
|
||||
would mean that later if either language were to change its syntax in a way that didn't make sense for the other,
|
||||
the result would be broken code and sadness.
|
||||
|
||||
So why does Roc have the specific syntax changes it does? Here are some brief explanations:
|
||||
|
||||
* `#` instead of `--` for comments - this allows [hashbang](https://senthilnayagan.medium.com/shebang-hashbang-10966b8f28a8)s to work without needing special syntax. That isn't a use case Elm supports, but it is one Roc is designed to support.
|
||||
* `{}` instead of `()` for the unit type - Elm has both, and they can both be used as a unit type. Since `{}` has other uses in the type system, but `()` doesn't, I consider it redundant and took it out.
|
||||
* No tuples - I wanted to try simplifying the language and seeing how much we'd miss them. Anything that could be represented as a tuple can be represented with either a record or a single-tag union instead (e.g. `Pair x y = ...`), so is it really necessary to have a third syntax for representing a [product type](https://en.wikipedia.org/wiki/Product_type)?
|
||||
* `when`...`is` instead of `case`...`of` - I predict it will be easier for beginners to pick up, because usually the way I explain `case`...`of` to beginners is by saying the words "when" and "is" out loud - e.g. "when `color` is `Red`, it runs this first branch; when `color` is `Blue`, it runs this other branch..."
|
||||
* `:` instead of `=` for record field names: I like `=` being reserved for definitions, and `:` is the most popular alternative.
|
||||
* Backpassing syntax - since Roc is designed to be used for use cases like command-line apps, shell scripts, and servers, I expect chained effects to come up a lot more often than they do in Elm. I think backpassing is nice for those use cases, similarly to how `do` notation is nice for them in Haskell.
|
||||
* Tag unions instead of Elm's custom types (aka algebraic data types). This isn't just a syntactic change; tag unions are mainly in Roc because they can facilitate errors being accumulated across chained effects, which (as noted a moment ago) I expect to be a lot more common in Roc than in Elm. If you have tag unions, you don't really need a separate language feature for algebraic data types, since closed tag unions essentially work the same way - aside from not giving you a way to selectively expose variants or define phantom types. Roc's opaque types language feature covers those use cases instead.
|
||||
* No `<|` operator. In Elm, I almost exclusively found myself wanting to use this in conjunction with anonymous functions (e.g. `foo <| \bar -> ...`) or conditionals (e.g. `foo <| if bar then ...`). In Roc you can do both of these without the `<|`. That means the main remaining use for `<|` is to reduce parentheses, but I tend to think `|>` is better at that (or else the parens are fine), so after the other syntactic changes, I considered `<|` an unnecessary stylistic alternative to `|>` or parens.
|
||||
* `:` instead of `type alias` - I like to avoid reserved keywords for terms that are desirable in userspace, so that people don't have to name things `typ` because `type` is a reserved keyword, or `clazz` because `class` is reserved. (I couldn't think of satisfactory alternatives for `as`, `when`, `is`, or `if` other than different reserved keywords. I could see an argument for `then`—and maybe even `is`—being replaced with a `->` or `=>` or something, but I don't anticipate missing either of those words much in userspace. `then` is used in JavaScript promises, but I think there are several better names for that function.)
|
||||
* No underscores in variable names - I've seen Elm beginners reflexively use `snake_case` over `camelCase` and then need to un-learn the habit after the compiler accepted it. I'd rather have the compiler give feedback that this isn't the way to do it in Roc, and suggest a camelCase alternative. I've also seen underscores used for lazy naming, e.g. `foo` and then `foo_`. If lazy naming is the goal, `foo2` is just as concise as `foo_`, but `foo3` is more concise than `foo__`. So in a way, removing `_` is a forcing function for improved laziness. (Of course, more descriptive naming would be even better.)
|
||||
* Trailing commas - I've seen people walk away (in some cases physically!) from Elm as soon as they saw the leading commas in collection literals. While I think they've made a mistake by not pushing past this aesthetic preference to give the language a chance, I also would prefer not put them in a position to make such a mistake in the first place. Secondarily, while I'm personally fine with either style, between the two I prefer the look of trailing commas.
|
||||
* The `!` unary prefix operator. I didn't want to have a `Basics` module (more on that in a moment), and without `Basics`, this would either need to be called fully-qualified (`Bool.not`) or else a module import of `Bool.{ not }` would be necessary. Both seemed less nice than supporting the `!` prefix that's common to so many widely-used languages, especially when we already have a unary prefix operator of `-` for negation (e.g. `-x`).
|
||||
* `!=` for the inequality operator (instead of Elm's `/=`) - this one pairs more naturally with the `!` prefix operator and is also very common in other languages.
|
||||
|
||||
Roc also has a different standard library from Elm. Some of the differences come down to platforms and applications (e.g. having `Task` in Roc's standard library wouldn't make sense), but others do not. Here are some brief explanations:
|
||||
|
||||
* No `Basics` module. I wanted to have a simple rule of "all modules in the standard library are imported by default, and so are their exposed types," and that's it. Given that I wanted the comparison operators (e.g. `<`) to work only on numbers, it ended up that having `Num` and `Bool` modules meant that almost nothing would be left for a `Basics` equivalent in Roc except `identity` and `Never`. The Roc type `[]` (empty tag union) is equivalent to `Never`, so that wasn't necessary, and I generally think that `identity` is a good concept but a sign of an incomplete API whenever its use comes up in practice. For example, instead of calling `|> List.filterMap identity` I'd rather have access to a more self-descriptive function like `|> List.dropNothings`. With `Num` and `Bool`, and without `identity` and `Never`, there was nothing left in `Basics`.
|
||||
* `Str` instead of `String` - after using the `str` type in Rust, I realized I had no issue whatsoever with the more concise name, especially since it was used in so many places (similar to `Msg` and `Cmd` in Elm) - so I decided to save a couple of letters.
|
||||
* No function composition operators - I stopped using these in Elm so long ago, at one point I forgot they were in the language! See the FAQ entry on currying for details about why.
|
||||
* No `Maybe`. If a function returns a potential error, I prefer `Result` with an error type that uses a no-payload tag to describe what went wrong. (For example, `List.first : List a -> Result a [ ListWasEmpty ]*` instead of `List.first : List a -> Maybe a`.) This is not only more self-descriptive, it also composes better with operations that have multiple ways to fail. Optional record fields can be handled using the explicit Optional Record Field language feature. To describe something that's neither an operation that can fail nor an optional field, I prefer using a more descriptive tag - e.g. for a nullable JSON decoder, instead of `nullable : Decoder a -> Decoder (Maybe a)`, making a self-documenting API like `nullable : Decoder a -> Decoder [ Null, NonNull a ]`. Joël's legendary [talk about Maybe](https://youtu.be/43eM4kNbb6c) is great, but the fact that a whole talk about such a simple type can be so useful speaks to how easy the type is to misuse. Imagine a 20-minute talk about `Result` - could it be anywhere near as hepful? On a historical note, it's conceivable that the creation of `Maybe` predated `Result`, and `Maybe` might have been thought of as a substitute for null pointers—as opposed to something that emerged organically based on specific motivating use cases after `Result` already existed.
|
||||
* No `Char`. What most people think of as a "character" is a rendered glyph. However, rendered glyphs are comprised of [grapheme clusters](https://stackoverflow.com/a/27331885), which are a variable number of Unicode code points - and there's no upper bound on how many code points there can be in a single cluster. In a world of emoji, I think this makes `Char` error-prone and it's better to have `Str` be the only first-class unit. For convenience when working with unicode code points (e.g. for performance-critical tasks like parsing), the single-quote syntax is sugar for the corresponding `U32` code point - for example, writing `'鹏'` is exactly the same as writing `40527`. Like Rust, you get a compiler error if you put something in single quotes that's not a valid [Unicode scalar value](http://www.unicode.org/glossary/#unicode_scalar_value).
|
||||
* No `Debug.log` - the editor can do a better job at this, or you can write `expect x != x` to see what `x` is when the expectation fails. Using the editor means your code doesn't change, and using `expect` gives a natural reminder to remove the debugging code before shipping: the build will fail.
|
||||
* No `Debug.todo` - instead you can write a type annotation with no implementation below it; the type checker will treat it normally, but attempting to use the value will cause a runtime exception. This is a feature I've often wanted in Elm, because I like prototyping APIs by writing out the types only, but then when I want the compiler to type-check them for me, I end up having to add `Debug.todo` in various places.
|
||||
|
||||
## Why aren't Roc functions curried by default?
|
||||
|
||||
Although technically any language with first-class functions makes it possible to curry
|
||||
any function (e.g. I can manually curry a Roc function `\x, y, z ->` by writing `\x -> \y -> \z ->` instead),
|
||||
typically what people mean when they say Roc isn't a curried language is that Roc functions aren't curried
|
||||
by default. For the rest of this section, I'll use "currying" as a shorthand for "functions that are curried
|
||||
by default" for the sake of brevity.
|
||||
|
||||
As I see it, currying has one major upside and three major downsides. The upside:
|
||||
|
||||
* It makes function calls more concise in some cases.
|
||||
|
||||
The downsides:
|
||||
|
||||
* It makes the `|>` operator more error-prone in some cases.
|
||||
* It makes higher-order function calls need more parentheses in some cases.
|
||||
* It significantly increases the language's learning curve.
|
||||
* It facilitates pointfree function composition. (More on why this is listed as a downside later.)
|
||||
|
||||
There's also a downside that it would make runtime performance of compiled progarms worse by default,
|
||||
but I assume it would be possible to optimize that away at the cost of slightly longer compile times.
|
||||
|
||||
I consider the one upside (conciseness in some places) extremely minor, and have almost never missed it in Roc.
|
||||
Here are some more details about the downsides as I see them.
|
||||
|
||||
### Currying and the `|>` operator
|
||||
|
||||
In Roc, this code produces `"Hello, World!"`
|
||||
|
||||
```elm
|
||||
"Hello, World"
|
||||
|> Str.concat "!"
|
||||
```
|
||||
|
||||
This is because Roc's `|>` operator uses the expression before the `|>` as the *first* argument to the function
|
||||
after it. For functions where both arguments have the same type, but it's obvious which argument goes where (e.g.
|
||||
`Str.concat "Hello, " "World!"`, `List.concat [ 1, 2 ] [ 3, 4 ]`), this works out well. Another example would
|
||||
be `|> Num.sub 1`, which subtracts 1 from whatever came before the `|>`.
|
||||
|
||||
For this reason, "pipeline-friendliness" in Roc means that the first argument to each function is typically
|
||||
the one that's most likely to be built up using a pipeline. For example, `List.map`:
|
||||
|
||||
```elm
|
||||
numbers
|
||||
|> List.map Num.abs
|
||||
```
|
||||
|
||||
This argument ordering convention also often makes it possible to pass anonymous functions to higher-order
|
||||
functions without needing parentheses, like so:
|
||||
|
||||
```elm
|
||||
List.map numbers \num -> Num.abs (num - 1)
|
||||
```
|
||||
|
||||
(If the arguments were reversed, this would be `List.map (\num -> Num.abs (num - 1)) numbers` and the
|
||||
extra parentheses would be required.)
|
||||
|
||||
Neither of these benefits is compatible with the argument ordering currying encourages. Currying encourages
|
||||
`List.map` to take the `List` as its second argument instead of the first, so that you can partially apply it
|
||||
like `(List.map Num.abs)`; if Roc introduced currying but kept the order of `List.map` the same way it is today,
|
||||
then partially applying `List.map` (e.g. `(List.map numbers)`) would be much less useful than if the arguments
|
||||
were swapped - but that in turn would make it less useful with `|>` and would require parentheses when passing
|
||||
it an anonymous function.
|
||||
|
||||
This is a fundamental design tension. One argument order works well with `|>` (at least the way it works in Roc
|
||||
today) and with passing anonymous functions to higher-order functions, and the other works well with currying.
|
||||
It's impossible to have both.
|
||||
|
||||
Of note, one possible design is to have currying while also having `|>` pass the *last* argument instead of the first.
|
||||
This is what Elm does, and it makes pipeline-friendliness and curry-friendliness the same thing. However, it also
|
||||
means that either `|> Str.concat "!"` would add the `"!"` to the front of the string, or else `Str.concat`'s
|
||||
arguments would have to be flipped - meaning that `Str.concat "Hello, World" "!"` would evaluate to `"!Hello, World"`.
|
||||
|
||||
The only way to have `Str.concat` work the way it does in Roc today (where both pipelines and non-pipeline calling
|
||||
do what you'd want them to) is to order function arguments in a way that is not conducive to currying. This design
|
||||
tension only exists if there's currying in the language; without it, you can order arguments for pipeline-friendliness
|
||||
without concern.
|
||||
|
||||
### Currying and learning curve
|
||||
|
||||
Prior to designing Roc, I taught a lot of beginner [Elm](https://elm-lang.org/) workshops. Sometimes at
|
||||
conferences, sometimes for [Frontend Masters](https://frontendmasters.com/courses/intro-elm/),
|
||||
sometimes for free at local coding bootcamps or meetup groups.
|
||||
In total I've spent well over 100 hours standing in front of a class, introducing the students to their
|
||||
first pure functional programming language.
|
||||
|
||||
Here was my experience teaching currying:
|
||||
|
||||
* The only way to avoid teaching it is to refuse to explain why multi-argument functions have multiple `->`s in them. (If you don't explain it, at least one student will ask about it - and many if not all of the others will wonder.)
|
||||
* Teaching currying properly takes a solid chunk of time, because it requires explaining partial application, explaining how curried functions facilitate partial application, how function signatures accurately reflect that they're curried, and going through examples for all of these.
|
||||
* Even after doing all this, and iterating on my approach each time to try to explain it more effectively than I had the time before, I'd estimate that under 50% of the class ended up actually understanding currying. I consistently heard that in practice it only "clicked" for most people after spending significantly more time writing code with it.
|
||||
|
||||
This is not the end of the world, especially because it's easy enough to think "okay, I still don't totally get this
|
||||
even after that explanation, but I can remember that function arguments are separated by `->` in this language
|
||||
and maybe I'll understand the rest later." (Which they almost always do, if they stick with the language.)
|
||||
Clearly currying doesn't preclude a language from being easy to learn, because Elm has currying, and Elm's learning
|
||||
curve is famously gentle.
|
||||
|
||||
That said, beginners who feel confused while learning the language are less likely to continue with it.
|
||||
And however easy Roc would be to learn if it had currying, the language is certainly easier to learn without it.
|
||||
|
||||
### Pointfree function composition
|
||||
|
||||
[Pointfree function composition](https://en.wikipedia.org/wiki/Tacit_programming) is where you define
|
||||
a new function by composing together two existing functions without naming intermediate arguments.
|
||||
Here's an example:
|
||||
|
||||
```elm
|
||||
reverseSort : List elem -> List elem
|
||||
reverseSort = compose List.reverse List.sort
|
||||
|
||||
compose : (a -> b), (c -> a) -> (c -> b)
|
||||
compose = \f, g, x -> f (g x)
|
||||
```
|
||||
|
||||
Here's how I would instead write this:
|
||||
|
||||
```elm
|
||||
reverseSort : List elem -> List elem
|
||||
reverseSort = \list -> List.reverse (List.sort list)
|
||||
```
|
||||
|
||||
I've consistently found that I can more quickly and accurately understand function definitions that use
|
||||
named arguments, even though the code is longer. I suspect this is because I'm faster at reading than I am at
|
||||
desugaring, and whenever I read the top version I end up needing to mentally desugar it into the bottom version.
|
||||
In more complex examples (this is among the tamest pointfree function composition examples I've seen), I make
|
||||
a mistake in my mental desugaring, and misunderstand what the function is doing - which can cause bugs.
|
||||
|
||||
I assumed I would get faster and more accurate at this over time. However, by now it's been about a decade
|
||||
since I first learned about the technique, and I'm still slower and less accurate at reading code that uses
|
||||
pointfree function composition (including if I wrote it - but even moreso if I didn't) than code written with
|
||||
with named arguments. I've asked a lot of other programmers about their experiences with pointfree function
|
||||
composition over the years, and the overwhelming majority of responses have been consistent with my experience.
|
||||
|
||||
As such, my opinion about pointfree function composition has gotten less and less nuanced over time. I've now moved
|
||||
past "it's the right tool for the job, sometimes" to concluding it's best thought of as an antipattern. This is
|
||||
because I realized how much time I was spending evaluating on a case-by-case basis whether it might be the
|
||||
right fit for a given situation. The time spent on this analysis alone vastly outweighed the sum of all the
|
||||
benefits I got in the rare cases where I concluded it was a fit. So I've found the way to get the most out of
|
||||
pointfree function composition is to never even think about using it; every other strategy leads to a worse outcome.
|
||||
|
||||
Currying facilitates the antipattern of pointfree function composition, which I view as a downside of currying.
|
||||
|
||||
Stacking up all these downsides of currying against the one upside of making certain function calls more concise,
|
||||
I concluded that it would be a mistake to have it in Roc.
|
||||
|
||||
## Is there syntax highlighting for Vim/Emacs/VS Code or a LSP?
|
||||
|
||||
Not currently. Although they will presumably exist someday, while Roc is in the early days there's actually a conscious
|
||||
effort to focus on the Roc Editor *instead of* adding Roc support to other editors - specifically in order to give the Roc
|
||||
Editor the best possible chance at kickstarting a virtuous cycle of plugin authorship.
|
||||
|
||||
This is an unusual approach, but there are more details in [this 2021 interview](https://youtu.be/ITrDd6-PbvY?t=212).
|
||||
|
||||
In the meantime, using CoffeeScript syntax highlighting for .roc files turns out to work surprisingly well!
|
|
@ -6,7 +6,7 @@ The [tutorial](TUTORIAL.md) is the best place to learn about how to use the lang
|
|||
|
||||
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.
|
||||
If you have a specific question, the [FAQ](FAQ.md) might have an answer, although [Roc Zulip chat](https://roc.zulipchat.com) is overall 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
|
||||
|
||||
|
|
|
@ -204,7 +204,11 @@ pub fn build_file<'a>(
|
|||
buf.push_str("Code Generation");
|
||||
buf.push('\n');
|
||||
|
||||
report_timing(buf, "Generate LLVM IR", code_gen_timing.code_gen);
|
||||
report_timing(
|
||||
buf,
|
||||
"Generate Assembly from Mono IR",
|
||||
code_gen_timing.code_gen,
|
||||
);
|
||||
report_timing(buf, "Emit .o file", code_gen_timing.emit_o_file);
|
||||
|
||||
let compilation_end = compilation_start.elapsed().unwrap();
|
||||
|
|
|
@ -69,6 +69,17 @@ mod cli_run {
|
|||
assert_multiline_str_eq!(err, expected.into());
|
||||
}
|
||||
|
||||
fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) {
|
||||
let flags = &["--check"];
|
||||
let out = run_roc(&[&["format", &file.to_str().unwrap()], &flags[..]].concat());
|
||||
|
||||
if expects_success_exit_code {
|
||||
assert!(out.status.success());
|
||||
} else {
|
||||
assert!(!out.status.success());
|
||||
}
|
||||
}
|
||||
|
||||
fn check_output_with_stdin(
|
||||
file: &Path,
|
||||
stdin: &[&str],
|
||||
|
@ -863,6 +874,16 @@ mod cli_run {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_check_good() {
|
||||
check_format_check_as_expected(&fixture_file("format", "Formatted.roc"), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_check_reformatting_needed() {
|
||||
check_format_check_as_expected(&fixture_file("format", "NotFormatted.roc"), false);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
6
cli/tests/fixtures/format/Formatted.roc
vendored
Normal file
6
cli/tests/fixtures/format/Formatted.roc
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
app "formatted"
|
||||
packages { pf: "platform" } imports []
|
||||
provides [ main ] to pf
|
||||
|
||||
main : Str
|
||||
main = Dep1.value1 {}
|
6
cli/tests/fixtures/format/NotFormatted.roc
vendored
Normal file
6
cli/tests/fixtures/format/NotFormatted.roc
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
app "formatted"
|
||||
packages { pf: "platform" }
|
||||
provides [ main ] to pf
|
||||
|
||||
main : Str
|
||||
main = Dep1.value1 {}
|
|
@ -7,7 +7,7 @@ use roc_module::symbol::{Interns, ModuleId};
|
|||
use roc_mono::ir::OptLevel;
|
||||
use roc_region::all::LineInfo;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use roc_collections::all::MutMap;
|
||||
#[cfg(feature = "target-wasm32")]
|
||||
|
@ -230,7 +230,6 @@ pub fn gen_from_mono_module_llvm(
|
|||
use inkwell::context::Context;
|
||||
use inkwell::module::Linkage;
|
||||
use inkwell::targets::{CodeModel, FileType, RelocMode};
|
||||
use std::time::SystemTime;
|
||||
|
||||
let code_gen_start = SystemTime::now();
|
||||
|
||||
|
@ -486,6 +485,7 @@ fn gen_from_mono_module_dev_wasm32(
|
|||
loaded: MonomorphizedModule,
|
||||
app_o_file: &Path,
|
||||
) -> CodeGenTiming {
|
||||
let code_gen_start = SystemTime::now();
|
||||
let MonomorphizedModule {
|
||||
module_id,
|
||||
procedures,
|
||||
|
@ -519,9 +519,17 @@ fn gen_from_mono_module_dev_wasm32(
|
|||
procedures,
|
||||
);
|
||||
|
||||
let code_gen = code_gen_start.elapsed().unwrap();
|
||||
let emit_o_file_start = SystemTime::now();
|
||||
|
||||
std::fs::write(&app_o_file, &bytes).expect("failed to write object to file");
|
||||
|
||||
CodeGenTiming::default()
|
||||
let emit_o_file = emit_o_file_start.elapsed().unwrap();
|
||||
|
||||
CodeGenTiming {
|
||||
code_gen,
|
||||
emit_o_file,
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_from_mono_module_dev_assembly(
|
||||
|
@ -530,6 +538,8 @@ fn gen_from_mono_module_dev_assembly(
|
|||
target: &target_lexicon::Triple,
|
||||
app_o_file: &Path,
|
||||
) -> CodeGenTiming {
|
||||
let code_gen_start = SystemTime::now();
|
||||
|
||||
let lazy_literals = true;
|
||||
let generate_allocators = false; // provided by the platform
|
||||
|
||||
|
@ -551,10 +561,18 @@ fn gen_from_mono_module_dev_assembly(
|
|||
|
||||
let module_object = roc_gen_dev::build_module(&env, &mut interns, target, procedures);
|
||||
|
||||
let code_gen = code_gen_start.elapsed().unwrap();
|
||||
let emit_o_file_start = SystemTime::now();
|
||||
|
||||
let module_out = module_object
|
||||
.write()
|
||||
.expect("failed to build output object");
|
||||
std::fs::write(&app_o_file, module_out).expect("failed to write object to file");
|
||||
|
||||
CodeGenTiming::default()
|
||||
let emit_o_file = emit_o_file_start.elapsed().unwrap();
|
||||
|
||||
CodeGenTiming {
|
||||
code_gen,
|
||||
emit_o_file,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage};
|
||||
use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
|
||||
use crate::Relocation;
|
||||
use bumpalo::collections::Vec;
|
||||
use packed_struct::prelude::*;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::Layout;
|
||||
|
@ -75,7 +74,7 @@ pub struct AArch64Call {}
|
|||
|
||||
const STACK_ALIGNMENT: u8 = 16;
|
||||
|
||||
impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
||||
impl CallConv<AArch64GeneralReg, AArch64FloatReg, AArch64Assembler> for AArch64Call {
|
||||
const BASE_PTR_REG: AArch64GeneralReg = AArch64GeneralReg::FP;
|
||||
const STACK_PTR_REG: AArch64GeneralReg = AArch64GeneralReg::ZRSP;
|
||||
|
||||
|
@ -160,13 +159,14 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
|||
#[inline(always)]
|
||||
fn setup_stack(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
saved_regs: &[AArch64GeneralReg],
|
||||
saved_general_regs: &[AArch64GeneralReg],
|
||||
saved_float_regs: &[AArch64FloatReg],
|
||||
requested_stack_size: i32,
|
||||
fn_call_stack_size: i32,
|
||||
) -> i32 {
|
||||
// Full size is upcast to i64 to make sure we don't overflow here.
|
||||
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.
|
||||
.checked_add(8 * (saved_general_regs.len() + saved_float_regs.len()) as i32 + 8) // The extra 8 is space to store the frame pointer.
|
||||
.and_then(|size| size.checked_add(fn_call_stack_size))
|
||||
{
|
||||
Some(size) => size,
|
||||
|
@ -204,10 +204,14 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
|||
AArch64Assembler::mov_stack32_reg64(buf, offset, AArch64GeneralReg::FP);
|
||||
|
||||
offset = aligned_stack_size - fn_call_stack_size;
|
||||
for reg in saved_regs {
|
||||
for reg in saved_general_regs {
|
||||
offset -= 8;
|
||||
AArch64Assembler::mov_base32_reg64(buf, offset, *reg);
|
||||
}
|
||||
for reg in saved_float_regs {
|
||||
offset -= 8;
|
||||
AArch64Assembler::mov_base32_freg64(buf, offset, *reg);
|
||||
}
|
||||
aligned_stack_size
|
||||
} else {
|
||||
0
|
||||
|
@ -220,7 +224,8 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
|||
#[inline(always)]
|
||||
fn cleanup_stack(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
saved_regs: &[AArch64GeneralReg],
|
||||
saved_general_regs: &[AArch64GeneralReg],
|
||||
saved_float_regs: &[AArch64FloatReg],
|
||||
aligned_stack_size: i32,
|
||||
fn_call_stack_size: i32,
|
||||
) {
|
||||
|
@ -233,10 +238,14 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
|||
AArch64Assembler::mov_reg64_stack32(buf, AArch64GeneralReg::FP, offset);
|
||||
|
||||
offset = aligned_stack_size - fn_call_stack_size;
|
||||
for reg in saved_regs {
|
||||
for reg in saved_general_regs {
|
||||
offset -= 8;
|
||||
AArch64Assembler::mov_reg64_base32(buf, *reg, offset);
|
||||
}
|
||||
for reg in saved_float_regs {
|
||||
offset -= 8;
|
||||
AArch64Assembler::mov_freg64_base32(buf, *reg, offset);
|
||||
}
|
||||
AArch64Assembler::add_reg64_reg64_imm32(
|
||||
buf,
|
||||
AArch64GeneralReg::ZRSP,
|
||||
|
@ -249,37 +258,64 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
|||
#[inline(always)]
|
||||
fn load_args<'a>(
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
_symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>,
|
||||
_storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
AArch64GeneralReg,
|
||||
AArch64FloatReg,
|
||||
AArch64Assembler,
|
||||
AArch64Call,
|
||||
>,
|
||||
_args: &'a [(Layout<'a>, Symbol)],
|
||||
_ret_layout: &Layout<'a>,
|
||||
mut _stack_size: u32,
|
||||
) -> u32 {
|
||||
) {
|
||||
todo!("Loading args for AArch64");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn store_args<'a>(
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
_symbol_map: &MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>,
|
||||
_storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
AArch64GeneralReg,
|
||||
AArch64FloatReg,
|
||||
AArch64Assembler,
|
||||
AArch64Call,
|
||||
>,
|
||||
_args: &'a [Symbol],
|
||||
_arg_layouts: &[Layout<'a>],
|
||||
_ret_layout: &Layout<'a>,
|
||||
) -> u32 {
|
||||
) {
|
||||
todo!("Storing args for AArch64");
|
||||
}
|
||||
|
||||
fn return_struct<'a>(
|
||||
fn return_complex_symbol<'a>(
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
_struct_offset: i32,
|
||||
_struct_size: u32,
|
||||
_field_layouts: &[Layout<'a>],
|
||||
_ret_reg: Option<AArch64GeneralReg>,
|
||||
_storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
AArch64GeneralReg,
|
||||
AArch64FloatReg,
|
||||
AArch64Assembler,
|
||||
AArch64Call,
|
||||
>,
|
||||
_sym: &Symbol,
|
||||
_layout: &Layout<'a>,
|
||||
) {
|
||||
todo!("Returning structs for AArch64");
|
||||
todo!("Returning complex symbols for AArch64");
|
||||
}
|
||||
|
||||
fn returns_via_arg_pointer(_ret_layout: &Layout) -> bool {
|
||||
todo!("Returning via arg pointer for AArch64");
|
||||
fn load_returned_complex_symbol<'a>(
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
_storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
AArch64GeneralReg,
|
||||
AArch64FloatReg,
|
||||
AArch64Assembler,
|
||||
AArch64Call,
|
||||
>,
|
||||
_sym: &Symbol,
|
||||
_layout: &Layout<'a>,
|
||||
) {
|
||||
todo!("Loading returned complex symbols for AArch64");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
1084
compiler/gen_dev/src/generic64/storage.rs
Normal file
1084
compiler/gen_dev/src/generic64/storage.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,13 +1,15 @@
|
|||
use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage, TARGET_INFO};
|
||||
use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
|
||||
use crate::{
|
||||
single_register_builtins, single_register_floats, single_register_integers, Relocation,
|
||||
single_register_floats, single_register_integers, single_register_layouts, Relocation,
|
||||
};
|
||||
use bumpalo::collections::Vec;
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{Builtin, Layout};
|
||||
use roc_target::TargetInfo;
|
||||
|
||||
const TARGET_INFO: TargetInfo = TargetInfo::default_x86_64();
|
||||
|
||||
// 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.
|
||||
|
@ -67,7 +69,7 @@ pub struct X86_64SystemV {}
|
|||
|
||||
const STACK_ALIGNMENT: u8 = 16;
|
||||
|
||||
impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
||||
impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64SystemV {
|
||||
const BASE_PTR_REG: X86_64GeneralReg = X86_64GeneralReg::RBP;
|
||||
const STACK_PTR_REG: X86_64GeneralReg = X86_64GeneralReg::RSP;
|
||||
|
||||
|
@ -161,13 +163,15 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
#[inline(always)]
|
||||
fn setup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
general_saved_regs: &[X86_64GeneralReg],
|
||||
saved_general_regs: &[X86_64GeneralReg],
|
||||
saved_float_regs: &[X86_64FloatReg],
|
||||
requested_stack_size: i32,
|
||||
fn_call_stack_size: i32,
|
||||
) -> i32 {
|
||||
x86_64_generic_setup_stack(
|
||||
buf,
|
||||
general_saved_regs,
|
||||
saved_general_regs,
|
||||
saved_float_regs,
|
||||
requested_stack_size,
|
||||
fn_call_stack_size,
|
||||
)
|
||||
|
@ -176,13 +180,15 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
#[inline(always)]
|
||||
fn cleanup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
general_saved_regs: &[X86_64GeneralReg],
|
||||
saved_general_regs: &[X86_64GeneralReg],
|
||||
saved_float_regs: &[X86_64FloatReg],
|
||||
aligned_stack_size: i32,
|
||||
fn_call_stack_size: i32,
|
||||
) {
|
||||
x86_64_generic_cleanup_stack(
|
||||
buf,
|
||||
general_saved_regs,
|
||||
saved_general_regs,
|
||||
saved_float_regs,
|
||||
aligned_stack_size,
|
||||
fn_call_stack_size,
|
||||
)
|
||||
|
@ -191,271 +197,230 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
#[inline(always)]
|
||||
fn load_args<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
|
||||
storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
X86_64GeneralReg,
|
||||
X86_64FloatReg,
|
||||
X86_64Assembler,
|
||||
X86_64SystemV,
|
||||
>,
|
||||
args: &'a [(Layout<'a>, Symbol)],
|
||||
ret_layout: &Layout<'a>,
|
||||
mut stack_size: u32,
|
||||
) -> u32 {
|
||||
let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer.
|
||||
) {
|
||||
let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 16; // 16 is the size of the pushed return address and base pointer.
|
||||
let mut general_i = 0;
|
||||
let mut float_i = 0;
|
||||
if X86_64SystemV::returns_via_arg_pointer(ret_layout) {
|
||||
symbol_map.insert(
|
||||
Symbol::RET_POINTER,
|
||||
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[general_i]),
|
||||
);
|
||||
storage_manager.ret_pointer_arg(Self::GENERAL_PARAM_REGS[0]);
|
||||
general_i += 1;
|
||||
}
|
||||
for (layout, sym) in args.iter() {
|
||||
match layout {
|
||||
single_register_integers!() => {
|
||||
if general_i < Self::GENERAL_PARAM_REGS.len() {
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[general_i]),
|
||||
);
|
||||
storage_manager.general_reg_arg(sym, Self::GENERAL_PARAM_REGS[general_i]);
|
||||
general_i += 1;
|
||||
} else {
|
||||
storage_manager.primitive_stack_arg(sym, arg_offset);
|
||||
arg_offset += 8;
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset: arg_offset,
|
||||
size: 8,
|
||||
owned: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
single_register_floats!() => {
|
||||
if float_i < Self::FLOAT_PARAM_REGS.len() {
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[float_i]),
|
||||
);
|
||||
storage_manager.float_reg_arg(sym, Self::FLOAT_PARAM_REGS[float_i]);
|
||||
float_i += 1;
|
||||
} else {
|
||||
storage_manager.primitive_stack_arg(sym, arg_offset);
|
||||
arg_offset += 8;
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset: arg_offset,
|
||||
size: 8,
|
||||
owned: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Layout::Builtin(Builtin::Str) => {
|
||||
Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
|
||||
if general_i + 1 < Self::GENERAL_PARAM_REGS.len() {
|
||||
// Load the value from the param reg into a useable base offset.
|
||||
let src1 = Self::GENERAL_PARAM_REGS[general_i];
|
||||
let src2 = Self::GENERAL_PARAM_REGS[general_i + 1];
|
||||
stack_size += 16;
|
||||
let offset = -(stack_size as i32);
|
||||
X86_64Assembler::mov_base32_reg64(buf, offset, src1);
|
||||
X86_64Assembler::mov_base32_reg64(buf, offset + 8, src2);
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset,
|
||||
size: 16,
|
||||
owned: true,
|
||||
},
|
||||
);
|
||||
let base_offset = storage_manager.claim_stack_area(sym, 16);
|
||||
X86_64Assembler::mov_base32_reg64(buf, base_offset, src1);
|
||||
X86_64Assembler::mov_base32_reg64(buf, base_offset + 8, src2);
|
||||
general_i += 2;
|
||||
} else {
|
||||
todo!("loading strings args on the stack");
|
||||
todo!("loading lists and strings args on the stack");
|
||||
}
|
||||
}
|
||||
Layout::Struct(&[]) => {}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x => {
|
||||
todo!("Loading args with layout {:?}", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
stack_size
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn store_args<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
symbol_map: &MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
|
||||
storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
X86_64GeneralReg,
|
||||
X86_64FloatReg,
|
||||
X86_64Assembler,
|
||||
X86_64SystemV,
|
||||
>,
|
||||
args: &'a [Symbol],
|
||||
arg_layouts: &[Layout<'a>],
|
||||
ret_layout: &Layout<'a>,
|
||||
) -> u32 {
|
||||
let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32;
|
||||
) {
|
||||
let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32;
|
||||
if Self::returns_via_arg_pointer(ret_layout) {
|
||||
// Save space on the stack for the arg we will return.
|
||||
storage_manager
|
||||
.claim_stack_area(&Symbol::RET_POINTER, ret_layout.stack_size(TARGET_INFO));
|
||||
todo!("claim first parama reg for the address");
|
||||
}
|
||||
let mut general_i = 0;
|
||||
let mut float_i = 0;
|
||||
// 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 {
|
||||
single_register_builtins!() | Layout::Builtin(Builtin::Str) | Layout::Struct([]) => {
|
||||
// Nothing needs to be done for any of these cases.
|
||||
}
|
||||
x => {
|
||||
todo!("receiving return type, {:?}", x);
|
||||
}
|
||||
}
|
||||
for (i, layout) in arg_layouts.iter().enumerate() {
|
||||
for (sym, layout) in args.iter().zip(arg_layouts.iter()) {
|
||||
match layout {
|
||||
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 storage {
|
||||
SymbolStorage::GeneralReg(reg)
|
||||
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||
X86_64Assembler::mov_reg64_reg64(buf, dst, *reg);
|
||||
}
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
X86_64Assembler::mov_reg64_base32(buf, dst, *offset);
|
||||
}
|
||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
||||
internal_error!("Cannot load floating point symbol into GeneralReg")
|
||||
}
|
||||
}
|
||||
storage_manager.load_to_specified_general_reg(
|
||||
buf,
|
||||
sym,
|
||||
Self::GENERAL_PARAM_REGS[general_i],
|
||||
);
|
||||
general_i += 1;
|
||||
} else {
|
||||
// Load the value to the stack.
|
||||
match storage {
|
||||
SymbolStorage::GeneralReg(reg)
|
||||
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||
X86_64Assembler::mov_stack32_reg64(buf, stack_offset, *reg);
|
||||
}
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
// Use RAX as a tmp reg because it will be free before function calls.
|
||||
X86_64Assembler::mov_reg64_base32(
|
||||
buf,
|
||||
X86_64GeneralReg::RAX,
|
||||
*offset,
|
||||
);
|
||||
X86_64Assembler::mov_stack32_reg64(
|
||||
buf,
|
||||
stack_offset,
|
||||
X86_64GeneralReg::RAX,
|
||||
);
|
||||
}
|
||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
||||
internal_error!("Cannot load floating point symbol into GeneralReg")
|
||||
}
|
||||
}
|
||||
stack_offset += 8;
|
||||
// Copy to stack using return reg as buffer.
|
||||
storage_manager.load_to_specified_general_reg(
|
||||
buf,
|
||||
sym,
|
||||
Self::GENERAL_RETURN_REGS[0],
|
||||
);
|
||||
X86_64Assembler::mov_stack32_reg64(
|
||||
buf,
|
||||
tmp_stack_offset,
|
||||
Self::GENERAL_RETURN_REGS[0],
|
||||
);
|
||||
tmp_stack_offset += 8;
|
||||
}
|
||||
}
|
||||
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 storage {
|
||||
SymbolStorage::FloatReg(reg)
|
||||
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||
X86_64Assembler::mov_freg64_freg64(buf, dst, *reg);
|
||||
}
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
X86_64Assembler::mov_freg64_base32(buf, dst, *offset);
|
||||
}
|
||||
SymbolStorage::GeneralReg(_)
|
||||
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
||||
internal_error!("Cannot load general symbol into FloatReg")
|
||||
}
|
||||
}
|
||||
storage_manager.load_to_specified_float_reg(
|
||||
buf,
|
||||
sym,
|
||||
Self::FLOAT_PARAM_REGS[float_i],
|
||||
);
|
||||
float_i += 1;
|
||||
} else {
|
||||
// Load the value to the stack.
|
||||
match storage {
|
||||
SymbolStorage::FloatReg(reg)
|
||||
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||
X86_64Assembler::mov_stack32_freg64(buf, stack_offset, *reg);
|
||||
}
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
// Use XMM0 as a tmp reg because it will be free before function calls.
|
||||
X86_64Assembler::mov_freg64_base32(
|
||||
buf,
|
||||
X86_64FloatReg::XMM0,
|
||||
*offset,
|
||||
);
|
||||
X86_64Assembler::mov_stack32_freg64(
|
||||
buf,
|
||||
stack_offset,
|
||||
X86_64FloatReg::XMM0,
|
||||
);
|
||||
}
|
||||
SymbolStorage::GeneralReg(_)
|
||||
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
||||
internal_error!("Cannot load general symbol into FloatReg")
|
||||
}
|
||||
}
|
||||
stack_offset += 8;
|
||||
// Copy to stack using return reg as buffer.
|
||||
storage_manager.load_to_specified_float_reg(
|
||||
buf,
|
||||
sym,
|
||||
Self::FLOAT_RETURN_REGS[0],
|
||||
);
|
||||
X86_64Assembler::mov_stack32_freg64(
|
||||
buf,
|
||||
tmp_stack_offset,
|
||||
Self::FLOAT_RETURN_REGS[0],
|
||||
);
|
||||
tmp_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 storage {
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
X86_64Assembler::mov_reg64_base32(buf, dst1, *offset);
|
||||
X86_64Assembler::mov_reg64_base32(buf, dst2, *offset + 8);
|
||||
}
|
||||
_ => {
|
||||
internal_error!(
|
||||
"Strings only support being loaded from base offsets"
|
||||
);
|
||||
}
|
||||
}
|
||||
let (base_offset, _size) = storage_manager.stack_offset_and_size(sym);
|
||||
debug_assert_eq!(base_offset % 8, 0);
|
||||
X86_64Assembler::mov_reg64_base32(
|
||||
buf,
|
||||
Self::GENERAL_PARAM_REGS[general_i],
|
||||
base_offset,
|
||||
);
|
||||
X86_64Assembler::mov_reg64_base32(
|
||||
buf,
|
||||
Self::GENERAL_PARAM_REGS[general_i + 1],
|
||||
base_offset + 8,
|
||||
);
|
||||
general_i += 2;
|
||||
} else {
|
||||
todo!("calling functions with strings on the stack");
|
||||
}
|
||||
}
|
||||
Layout::Struct(&[]) => {}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x => {
|
||||
todo!("calling with arg type, {:?}", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
stack_offset as u32
|
||||
storage_manager.update_fn_call_stack_size(tmp_stack_offset as u32);
|
||||
}
|
||||
|
||||
fn return_struct<'a>(
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
_struct_offset: i32,
|
||||
_struct_size: u32,
|
||||
_field_layouts: &[Layout<'a>],
|
||||
_ret_reg: Option<X86_64GeneralReg>,
|
||||
fn return_complex_symbol<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
X86_64GeneralReg,
|
||||
X86_64FloatReg,
|
||||
X86_64Assembler,
|
||||
X86_64SystemV,
|
||||
>,
|
||||
sym: &Symbol,
|
||||
layout: &Layout<'a>,
|
||||
) {
|
||||
todo!("Returning structs for X86_64");
|
||||
match layout {
|
||||
single_register_layouts!() => {
|
||||
internal_error!("single register layouts are not complex symbols");
|
||||
}
|
||||
Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
|
||||
let (base_offset, _size) = storage_manager.stack_offset_and_size(sym);
|
||||
debug_assert_eq!(base_offset % 8, 0);
|
||||
X86_64Assembler::mov_reg64_base32(buf, Self::GENERAL_RETURN_REGS[0], base_offset);
|
||||
X86_64Assembler::mov_reg64_base32(
|
||||
buf,
|
||||
Self::GENERAL_RETURN_REGS[1],
|
||||
base_offset + 8,
|
||||
);
|
||||
}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x => todo!("returning complex type, {:?}", x),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_returned_complex_symbol<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
X86_64GeneralReg,
|
||||
X86_64FloatReg,
|
||||
X86_64Assembler,
|
||||
X86_64SystemV,
|
||||
>,
|
||||
sym: &Symbol,
|
||||
layout: &Layout<'a>,
|
||||
) {
|
||||
match layout {
|
||||
single_register_layouts!() => {
|
||||
internal_error!("single register layouts are not complex symbols");
|
||||
}
|
||||
Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
|
||||
let offset = storage_manager.claim_stack_area(sym, 16);
|
||||
X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]);
|
||||
X86_64Assembler::mov_base32_reg64(buf, offset + 8, Self::GENERAL_RETURN_REGS[1]);
|
||||
}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x => todo!("receiving complex return type, {:?}", x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl X86_64SystemV {
|
||||
fn returns_via_arg_pointer(ret_layout: &Layout) -> bool {
|
||||
// TODO: This may need to be more complex/extended to fully support the calling convention.
|
||||
// TODO: This will 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
|
||||
ret_layout.stack_size(TARGET_INFO) > 16
|
||||
}
|
||||
}
|
||||
|
||||
impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
||||
impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64WindowsFastcall {
|
||||
const BASE_PTR_REG: X86_64GeneralReg = X86_64GeneralReg::RBP;
|
||||
const STACK_PTR_REG: X86_64GeneralReg = X86_64GeneralReg::RSP;
|
||||
|
||||
|
@ -553,225 +518,202 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
#[inline(always)]
|
||||
fn setup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
saved_regs: &[X86_64GeneralReg],
|
||||
saved_general_regs: &[X86_64GeneralReg],
|
||||
saved_float_regs: &[X86_64FloatReg],
|
||||
requested_stack_size: i32,
|
||||
fn_call_stack_size: i32,
|
||||
) -> i32 {
|
||||
x86_64_generic_setup_stack(buf, saved_regs, requested_stack_size, fn_call_stack_size)
|
||||
x86_64_generic_setup_stack(
|
||||
buf,
|
||||
saved_general_regs,
|
||||
saved_float_regs,
|
||||
requested_stack_size,
|
||||
fn_call_stack_size,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn cleanup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
saved_regs: &[X86_64GeneralReg],
|
||||
saved_general_regs: &[X86_64GeneralReg],
|
||||
saved_float_regs: &[X86_64FloatReg],
|
||||
aligned_stack_size: i32,
|
||||
fn_call_stack_size: i32,
|
||||
) {
|
||||
x86_64_generic_cleanup_stack(buf, saved_regs, aligned_stack_size, fn_call_stack_size)
|
||||
x86_64_generic_cleanup_stack(
|
||||
buf,
|
||||
saved_general_regs,
|
||||
saved_float_regs,
|
||||
aligned_stack_size,
|
||||
fn_call_stack_size,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn load_args<'a>(
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
|
||||
storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
X86_64GeneralReg,
|
||||
X86_64FloatReg,
|
||||
X86_64Assembler,
|
||||
X86_64WindowsFastcall,
|
||||
>,
|
||||
args: &'a [(Layout<'a>, Symbol)],
|
||||
ret_layout: &Layout<'a>,
|
||||
stack_size: u32,
|
||||
) -> u32 {
|
||||
let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer.
|
||||
) {
|
||||
let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 16; // 16 is the size of the pushed return address and base pointer.
|
||||
let mut i = 0;
|
||||
if X86_64WindowsFastcall::returns_via_arg_pointer(ret_layout) {
|
||||
symbol_map.insert(
|
||||
Symbol::RET_POINTER,
|
||||
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]),
|
||||
);
|
||||
storage_manager.ret_pointer_arg(Self::GENERAL_PARAM_REGS[i]);
|
||||
i += 1;
|
||||
}
|
||||
for (layout, sym) in args.iter() {
|
||||
if i < Self::GENERAL_PARAM_REGS.len() {
|
||||
match layout {
|
||||
single_register_integers!() => {
|
||||
symbol_map
|
||||
.insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]));
|
||||
storage_manager.general_reg_arg(sym, Self::GENERAL_PARAM_REGS[i]);
|
||||
i += 1;
|
||||
}
|
||||
single_register_floats!() => {
|
||||
symbol_map.insert(*sym, SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[i]));
|
||||
storage_manager.float_reg_arg(sym, Self::FLOAT_PARAM_REGS[i]);
|
||||
i += 1;
|
||||
}
|
||||
Layout::Builtin(Builtin::Str) => {
|
||||
// I think this just needs to be passed on the stack, so not a huge deal.
|
||||
todo!("Passing str args with Windows fast call");
|
||||
}
|
||||
Layout::Struct(&[]) => {}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x => {
|
||||
todo!("Loading args with layout {:?}", x);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
arg_offset += match layout {
|
||||
single_register_builtins!() => 8,
|
||||
match layout {
|
||||
single_register_layouts!() => {
|
||||
storage_manager.primitive_stack_arg(sym, arg_offset);
|
||||
arg_offset += 8;
|
||||
}
|
||||
x => {
|
||||
todo!("Loading args with layout {:?}", x);
|
||||
}
|
||||
};
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset: arg_offset,
|
||||
size: 8,
|
||||
owned: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
stack_size
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn store_args<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
symbol_map: &MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
|
||||
storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
X86_64GeneralReg,
|
||||
X86_64FloatReg,
|
||||
X86_64Assembler,
|
||||
X86_64WindowsFastcall,
|
||||
>,
|
||||
args: &'a [Symbol],
|
||||
arg_layouts: &[Layout<'a>],
|
||||
ret_layout: &Layout<'a>,
|
||||
) -> 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 {
|
||||
single_register_builtins!() | Layout::Struct([]) => {
|
||||
// Nothing needs to be done for any of these cases.
|
||||
}
|
||||
x => {
|
||||
todo!("receiving return type, {:?}", x);
|
||||
}
|
||||
) {
|
||||
let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32;
|
||||
if Self::returns_via_arg_pointer(ret_layout) {
|
||||
// Save space on the stack for the arg we will return.
|
||||
storage_manager
|
||||
.claim_stack_area(&Symbol::RET_POINTER, ret_layout.stack_size(TARGET_INFO));
|
||||
todo!("claim first parama reg for the address");
|
||||
}
|
||||
for (i, layout) in arg_layouts.iter().enumerate() {
|
||||
for (i, (sym, layout)) in args.iter().zip(arg_layouts.iter()).enumerate() {
|
||||
match layout {
|
||||
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 storage {
|
||||
SymbolStorage::GeneralReg(reg)
|
||||
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||
X86_64Assembler::mov_reg64_reg64(buf, dst, *reg);
|
||||
}
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
X86_64Assembler::mov_reg64_base32(buf, dst, *offset);
|
||||
}
|
||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
||||
internal_error!("Cannot load floating point symbol into GeneralReg")
|
||||
}
|
||||
}
|
||||
storage_manager.load_to_specified_general_reg(
|
||||
buf,
|
||||
sym,
|
||||
Self::GENERAL_PARAM_REGS[i],
|
||||
);
|
||||
} else {
|
||||
// Load the value to the stack.
|
||||
match storage {
|
||||
SymbolStorage::GeneralReg(reg)
|
||||
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||
X86_64Assembler::mov_stack32_reg64(buf, stack_offset, *reg);
|
||||
}
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
// Use RAX as a tmp reg because it will be free before function calls.
|
||||
X86_64Assembler::mov_reg64_base32(
|
||||
buf,
|
||||
X86_64GeneralReg::RAX,
|
||||
*offset,
|
||||
);
|
||||
X86_64Assembler::mov_stack32_reg64(
|
||||
buf,
|
||||
stack_offset,
|
||||
X86_64GeneralReg::RAX,
|
||||
);
|
||||
}
|
||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
||||
internal_error!("Cannot load floating point symbol into GeneralReg")
|
||||
}
|
||||
}
|
||||
stack_offset += 8;
|
||||
// Copy to stack using return reg as buffer.
|
||||
storage_manager.load_to_specified_general_reg(
|
||||
buf,
|
||||
sym,
|
||||
Self::GENERAL_RETURN_REGS[0],
|
||||
);
|
||||
X86_64Assembler::mov_stack32_reg64(
|
||||
buf,
|
||||
tmp_stack_offset,
|
||||
Self::GENERAL_RETURN_REGS[0],
|
||||
);
|
||||
tmp_stack_offset += 8;
|
||||
}
|
||||
}
|
||||
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 storage {
|
||||
SymbolStorage::FloatReg(reg)
|
||||
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||
X86_64Assembler::mov_freg64_freg64(buf, dst, *reg);
|
||||
}
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
X86_64Assembler::mov_freg64_base32(buf, dst, *offset);
|
||||
}
|
||||
SymbolStorage::GeneralReg(_)
|
||||
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
||||
internal_error!("Cannot load general symbol into FloatReg")
|
||||
}
|
||||
}
|
||||
storage_manager.load_to_specified_float_reg(
|
||||
buf,
|
||||
sym,
|
||||
Self::FLOAT_PARAM_REGS[i],
|
||||
);
|
||||
} else {
|
||||
// Load the value to the stack.
|
||||
match storage {
|
||||
SymbolStorage::FloatReg(reg)
|
||||
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||
X86_64Assembler::mov_stack32_freg64(buf, stack_offset, *reg);
|
||||
}
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
// Use XMM0 as a tmp reg because it will be free before function calls.
|
||||
X86_64Assembler::mov_freg64_base32(
|
||||
buf,
|
||||
X86_64FloatReg::XMM0,
|
||||
*offset,
|
||||
);
|
||||
X86_64Assembler::mov_stack32_freg64(
|
||||
buf,
|
||||
stack_offset,
|
||||
X86_64FloatReg::XMM0,
|
||||
);
|
||||
}
|
||||
SymbolStorage::GeneralReg(_)
|
||||
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
||||
internal_error!("Cannot load general symbol into FloatReg")
|
||||
}
|
||||
}
|
||||
stack_offset += 8;
|
||||
// Copy to stack using return reg as buffer.
|
||||
storage_manager.load_to_specified_float_reg(
|
||||
buf,
|
||||
sym,
|
||||
Self::FLOAT_RETURN_REGS[0],
|
||||
);
|
||||
X86_64Assembler::mov_stack32_freg64(
|
||||
buf,
|
||||
tmp_stack_offset,
|
||||
Self::FLOAT_RETURN_REGS[0],
|
||||
);
|
||||
tmp_stack_offset += 8;
|
||||
}
|
||||
}
|
||||
Layout::Builtin(Builtin::Str) => {
|
||||
// I think this just needs to be passed on the stack, so not a huge deal.
|
||||
todo!("Passing str args with Windows fast call");
|
||||
}
|
||||
Layout::Struct(&[]) => {}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x => {
|
||||
todo!("calling with arg type, {:?}", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
stack_offset as u32
|
||||
storage_manager.update_fn_call_stack_size(tmp_stack_offset as u32);
|
||||
}
|
||||
|
||||
fn return_struct<'a>(
|
||||
fn return_complex_symbol<'a>(
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
_struct_offset: i32,
|
||||
_struct_size: u32,
|
||||
_field_layouts: &[Layout<'a>],
|
||||
_ret_reg: Option<X86_64GeneralReg>,
|
||||
_storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
X86_64GeneralReg,
|
||||
X86_64FloatReg,
|
||||
X86_64Assembler,
|
||||
X86_64WindowsFastcall,
|
||||
>,
|
||||
_sym: &Symbol,
|
||||
_layout: &Layout<'a>,
|
||||
) {
|
||||
todo!("Returning structs for X86_64WindowsFastCall");
|
||||
todo!("Returning complex symbols for X86_64");
|
||||
}
|
||||
|
||||
fn load_returned_complex_symbol<'a>(
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
_storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
X86_64GeneralReg,
|
||||
X86_64FloatReg,
|
||||
X86_64Assembler,
|
||||
X86_64WindowsFastcall,
|
||||
>,
|
||||
_sym: &Symbol,
|
||||
_layout: &Layout<'a>,
|
||||
) {
|
||||
todo!("Loading returned complex symbols for X86_64");
|
||||
}
|
||||
}
|
||||
|
||||
impl X86_64WindowsFastcall {
|
||||
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
|
||||
|
@ -782,7 +724,8 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
#[inline(always)]
|
||||
fn x86_64_generic_setup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
saved_regs: &[X86_64GeneralReg],
|
||||
saved_general_regs: &[X86_64GeneralReg],
|
||||
saved_float_regs: &[X86_64FloatReg],
|
||||
requested_stack_size: i32,
|
||||
fn_call_stack_size: i32,
|
||||
) -> i32 {
|
||||
|
@ -790,7 +733,7 @@ fn x86_64_generic_setup_stack<'a>(
|
|||
X86_64Assembler::mov_reg64_reg64(buf, X86_64GeneralReg::RBP, X86_64GeneralReg::RSP);
|
||||
|
||||
let full_stack_size = match requested_stack_size
|
||||
.checked_add(8 * saved_regs.len() as i32)
|
||||
.checked_add(8 * (saved_general_regs.len() + saved_float_regs.len()) as i32)
|
||||
.and_then(|size| size.checked_add(fn_call_stack_size))
|
||||
{
|
||||
Some(size) => size,
|
||||
|
@ -817,10 +760,14 @@ fn x86_64_generic_setup_stack<'a>(
|
|||
|
||||
// Put values at the top of the stack to avoid conflicts with previously saved variables.
|
||||
let mut offset = aligned_stack_size - fn_call_stack_size;
|
||||
for reg in saved_regs {
|
||||
for reg in saved_general_regs {
|
||||
X86_64Assembler::mov_base32_reg64(buf, -offset, *reg);
|
||||
offset -= 8;
|
||||
}
|
||||
for reg in saved_float_regs {
|
||||
X86_64Assembler::mov_base32_freg64(buf, -offset, *reg);
|
||||
offset -= 8;
|
||||
}
|
||||
aligned_stack_size
|
||||
} else {
|
||||
0
|
||||
|
@ -834,16 +781,21 @@ fn x86_64_generic_setup_stack<'a>(
|
|||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn x86_64_generic_cleanup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
saved_regs: &[X86_64GeneralReg],
|
||||
saved_general_regs: &[X86_64GeneralReg],
|
||||
saved_float_regs: &[X86_64FloatReg],
|
||||
aligned_stack_size: i32,
|
||||
fn_call_stack_size: i32,
|
||||
) {
|
||||
if aligned_stack_size > 0 {
|
||||
let mut offset = aligned_stack_size - fn_call_stack_size;
|
||||
for reg in saved_regs {
|
||||
for reg in saved_general_regs {
|
||||
X86_64Assembler::mov_reg64_base32(buf, *reg, -offset);
|
||||
offset -= 8;
|
||||
}
|
||||
for reg in saved_float_regs {
|
||||
X86_64Assembler::mov_freg64_base32(buf, *reg, -offset);
|
||||
offset -= 8;
|
||||
}
|
||||
X86_64Assembler::add_reg64_reg64_imm32(
|
||||
buf,
|
||||
X86_64GeneralReg::RSP,
|
||||
|
@ -1429,6 +1381,9 @@ fn mov_reg64_base64_offset32(
|
|||
/// `MOVSD xmm1,xmm2` -> Move scalar double-precision floating-point value from xmm2 to xmm1 register.
|
||||
#[inline(always)]
|
||||
fn movsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
|
||||
if dst == src {
|
||||
return;
|
||||
}
|
||||
let dst_high = dst as u8 > 7;
|
||||
let dst_mod = dst as u8 % 8;
|
||||
let src_high = src as u8 > 7;
|
||||
|
@ -2161,10 +2116,7 @@ mod tests {
|
|||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for ((dst, src), expected) in &[
|
||||
(
|
||||
(X86_64FloatReg::XMM0, X86_64FloatReg::XMM0),
|
||||
vec![0xF2, 0x0F, 0x10, 0xC0],
|
||||
),
|
||||
((X86_64FloatReg::XMM0, X86_64FloatReg::XMM0), vec![]),
|
||||
(
|
||||
(X86_64FloatReg::XMM0, X86_64FloatReg::XMM15),
|
||||
vec![0xF2, 0x41, 0x0F, 0x10, 0xC7],
|
||||
|
@ -2173,10 +2125,7 @@ mod tests {
|
|||
(X86_64FloatReg::XMM15, X86_64FloatReg::XMM0),
|
||||
vec![0xF2, 0x44, 0x0F, 0x10, 0xF8],
|
||||
),
|
||||
(
|
||||
(X86_64FloatReg::XMM15, X86_64FloatReg::XMM15),
|
||||
vec![0xF2, 0x45, 0x0F, 0x10, 0xFF],
|
||||
),
|
||||
((X86_64FloatReg::XMM15, X86_64FloatReg::XMM15), vec![]),
|
||||
] {
|
||||
buf.clear();
|
||||
movsd_freg64_freg64(&mut buf, *dst, *src);
|
||||
|
|
|
@ -694,16 +694,8 @@ trait Backend<'a> {
|
|||
fn free_symbol(&mut self, sym: &Symbol);
|
||||
|
||||
/// set_last_seen sets the statement a symbol was last seen in.
|
||||
fn set_last_seen(
|
||||
&mut self,
|
||||
sym: Symbol,
|
||||
stmt: &Stmt<'a>,
|
||||
owning_symbol: &MutMap<Symbol, Symbol>,
|
||||
) {
|
||||
fn set_last_seen(&mut self, sym: Symbol, stmt: &Stmt<'a>) {
|
||||
self.last_seen_map().insert(sym, stmt);
|
||||
if let Some(parent) = owning_symbol.get(&sym) {
|
||||
self.last_seen_map().insert(*parent, stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/// last_seen_map gets the map from symbol to when it is last seen in the function.
|
||||
|
@ -749,45 +741,39 @@ trait Backend<'a> {
|
|||
/// scan_ast runs through the ast and fill the last seen map.
|
||||
/// This must iterate through the ast in the same way that build_stmt does. i.e. then before else.
|
||||
fn scan_ast(&mut self, stmt: &Stmt<'a>) {
|
||||
// This keeps track of symbols that depend on other symbols.
|
||||
// The main case of this is data in structures and tagged unions.
|
||||
// This data must extend the lifetime of the original structure or tagged union.
|
||||
// For arrays the loading is always done through low levels and does not depend on the underlying array's lifetime.
|
||||
let mut owning_symbol: MutMap<Symbol, Symbol> = MutMap::default();
|
||||
// Join map keeps track of join point parameters so that we can keep them around while they still might be jumped to.
|
||||
let mut join_map: MutMap<JoinPointId, &'a [Param<'a>]> = MutMap::default();
|
||||
match stmt {
|
||||
Stmt::Let(sym, expr, _, following) => {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
match expr {
|
||||
Expr::Literal(_) => {}
|
||||
|
||||
Expr::Call(call) => self.scan_ast_call(call, stmt, &owning_symbol),
|
||||
Expr::Call(call) => self.scan_ast_call(call, stmt),
|
||||
|
||||
Expr::Tag { arguments, .. } => {
|
||||
for sym in *arguments {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::Struct(syms) => {
|
||||
for sym in *syms {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::StructAtIndex { structure, .. } => {
|
||||
self.set_last_seen(*structure, stmt, &owning_symbol);
|
||||
owning_symbol.insert(*sym, *structure);
|
||||
self.set_last_seen(*structure, stmt);
|
||||
}
|
||||
Expr::GetTagId { structure, .. } => {
|
||||
self.set_last_seen(*structure, stmt, &owning_symbol);
|
||||
owning_symbol.insert(*sym, *structure);
|
||||
self.set_last_seen(*structure, stmt);
|
||||
}
|
||||
Expr::UnionAtIndex { structure, .. } => {
|
||||
self.set_last_seen(*structure, stmt, &owning_symbol);
|
||||
owning_symbol.insert(*sym, *structure);
|
||||
self.set_last_seen(*structure, stmt);
|
||||
}
|
||||
Expr::Array { elems, .. } => {
|
||||
for elem in *elems {
|
||||
if let ListLiteralElement::Symbol(sym) = elem {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -797,22 +783,22 @@ trait Backend<'a> {
|
|||
tag_name,
|
||||
..
|
||||
} => {
|
||||
self.set_last_seen(*symbol, stmt, &owning_symbol);
|
||||
self.set_last_seen(*symbol, stmt);
|
||||
match tag_name {
|
||||
TagName::Closure(sym) => {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
TagName::Private(sym) => {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
TagName::Global(_) => {}
|
||||
}
|
||||
for sym in *arguments {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::Reset { symbol, .. } => {
|
||||
self.set_last_seen(*symbol, stmt, &owning_symbol);
|
||||
self.set_last_seen(*symbol, stmt);
|
||||
}
|
||||
Expr::EmptyArray => {}
|
||||
Expr::RuntimeErrorFunction(_) => {}
|
||||
|
@ -826,56 +812,59 @@ trait Backend<'a> {
|
|||
default_branch,
|
||||
..
|
||||
} => {
|
||||
self.set_last_seen(*cond_symbol, stmt, &owning_symbol);
|
||||
self.set_last_seen(*cond_symbol, stmt);
|
||||
for (_, _, branch) in *branches {
|
||||
self.scan_ast(branch);
|
||||
}
|
||||
self.scan_ast(default_branch.1);
|
||||
}
|
||||
Stmt::Ret(sym) => {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
Stmt::Refcounting(modify, following) => {
|
||||
let sym = modify.get_symbol();
|
||||
|
||||
self.set_last_seen(sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(sym, stmt);
|
||||
self.scan_ast(following);
|
||||
}
|
||||
Stmt::Join {
|
||||
parameters,
|
||||
body: continuation,
|
||||
remainder,
|
||||
id,
|
||||
..
|
||||
} => {
|
||||
join_map.insert(*id, parameters);
|
||||
for param in *parameters {
|
||||
self.set_last_seen(param.symbol, stmt, &owning_symbol);
|
||||
self.set_last_seen(param.symbol, stmt);
|
||||
}
|
||||
self.scan_ast(continuation);
|
||||
self.scan_ast(remainder);
|
||||
}
|
||||
Stmt::Jump(JoinPointId(sym), symbols) => {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
if let Some(parameters) = join_map.get(&JoinPointId(*sym)) {
|
||||
// Keep the parameters around. They will be overwritten when jumping.
|
||||
for param in *parameters {
|
||||
self.set_last_seen(param.symbol, stmt);
|
||||
}
|
||||
}
|
||||
self.set_last_seen(*sym, stmt);
|
||||
for sym in *symbols {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Stmt::RuntimeError(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn scan_ast_call(
|
||||
&mut self,
|
||||
call: &roc_mono::ir::Call,
|
||||
stmt: &roc_mono::ir::Stmt<'a>,
|
||||
owning_symbol: &MutMap<Symbol, Symbol>,
|
||||
) {
|
||||
fn scan_ast_call(&mut self, call: &roc_mono::ir::Call, stmt: &roc_mono::ir::Stmt<'a>) {
|
||||
let roc_mono::ir::Call {
|
||||
call_type,
|
||||
arguments,
|
||||
} = call;
|
||||
|
||||
for sym in *arguments {
|
||||
self.set_last_seen(*sym, stmt, owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
|
||||
match call_type {
|
||||
|
|
|
@ -13,6 +13,7 @@ use roc_module::symbol;
|
|||
use roc_module::symbol::Interns;
|
||||
use roc_mono::ir::{Proc, ProcLayout};
|
||||
use roc_mono::layout::LayoutIds;
|
||||
use roc_target::TargetInfo;
|
||||
use target_lexicon::{Architecture as TargetArch, BinaryFormat as TargetBF, Triple};
|
||||
|
||||
// This is used by some code below which is currently commented out.
|
||||
|
@ -38,7 +39,7 @@ pub fn build_module<'a>(
|
|||
x86_64::X86_64FloatReg,
|
||||
x86_64::X86_64Assembler,
|
||||
x86_64::X86_64SystemV,
|
||||
>(env, interns);
|
||||
>(env, TargetInfo::default_x86_64(), interns);
|
||||
build_object(
|
||||
procedures,
|
||||
backend,
|
||||
|
@ -55,7 +56,7 @@ pub fn build_module<'a>(
|
|||
x86_64::X86_64FloatReg,
|
||||
x86_64::X86_64Assembler,
|
||||
x86_64::X86_64SystemV,
|
||||
>(env, interns);
|
||||
>(env, TargetInfo::default_x86_64(), interns);
|
||||
build_object(
|
||||
procedures,
|
||||
backend,
|
||||
|
@ -76,7 +77,7 @@ pub fn build_module<'a>(
|
|||
aarch64::AArch64FloatReg,
|
||||
aarch64::AArch64Assembler,
|
||||
aarch64::AArch64Call,
|
||||
>(env, interns);
|
||||
>(env, TargetInfo::default_aarch64(), interns);
|
||||
build_object(
|
||||
procedures,
|
||||
backend,
|
||||
|
@ -93,7 +94,7 @@ pub fn build_module<'a>(
|
|||
aarch64::AArch64FloatReg,
|
||||
aarch64::AArch64Assembler,
|
||||
aarch64::AArch64Call,
|
||||
>(env, interns);
|
||||
>(env, TargetInfo::default_aarch64(), interns);
|
||||
build_object(
|
||||
procedures,
|
||||
backend,
|
||||
|
|
|
@ -12,6 +12,12 @@ impl TargetInfo {
|
|||
self.architecture.ptr_width()
|
||||
}
|
||||
|
||||
pub const fn default_aarch64() -> Self {
|
||||
TargetInfo {
|
||||
architecture: Architecture::Aarch64,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn default_x86_64() -> Self {
|
||||
TargetInfo {
|
||||
architecture: Architecture::X86_64,
|
||||
|
|
|
@ -8,7 +8,17 @@
|
|||
<title>The Roc Programming Language</title>
|
||||
<!-- <meta name="description" content="A language for making delightful software."> -->
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
||||
<style type="text/css">
|
||||
html {
|
||||
max-width: 35em;
|
||||
margin: 0 auto;
|
||||
font-family: sans-serif;
|
||||
line-height: 145%;
|
||||
}
|
||||
li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
<!-- <link rel="icon" href="/favicon.svg"> -->
|
||||
</head>
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue