-
-
Notifications
You must be signed in to change notification settings - Fork 116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat!: Static linking of Grain modules #584
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some thoughts, mostly about the binaryen.ml API design and about how to handle errors in the linker.
compiler/dune-project
Outdated
(reason (>= 3.6.0)) | ||
(ppx_sexp_conv (>= 0.14.0)) | ||
(sexplib (>= 0.14.0)) | ||
(binaryen (= 0.9.0)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(binaryen (= 0.9.0)) | |
(binaryen (= 0.9.1)) |
compile_file( | ||
~outfile, | ||
~reset=false, | ||
~hook=stop_after_object_file_emitted, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this stop_after_object_file_emitted
now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously, it would just compile completely, but now since linking is a step that also happens, this skips linking when compiling dependencies (since they'll all be linked together at the end).
let rec globalize_names = (local_names, expr) => { | ||
let kind = Expression.get_kind(expr); | ||
switch (kind) { | ||
| Invalid => failwith("Invalid expression") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want this to failwith
or raise
? I feel like most of the compiler uses raise
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We typically use failwith
with things that are "impossible" and raise
for errors related to the user's program. The only way we'd ever hit this case is if we generated a wasm module with instructions that the linker didn't know about.
We can probably do a round up on errors, but that's probably for another PR :)
let new_name = | ||
Hashtbl.find( | ||
Hashtbl.find(exported_names, imported_module), | ||
imported_name, | ||
); | ||
Hashtbl.add(local_names, internal_name, new_name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is repeated a couple times.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's similar, but not quite the same. They essentially get to the same end result, but it takes different functions to get there.
let new_name = gensym(internal_name); | ||
Hashtbl.add(local_names, internal_name, new_name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whenever you gensym a new name, you need to add it to the local_names hashtbl, right? Maybe those should be bundled so it isn't missed during a code cleanup or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not always, no. I suppose we could have some utility that does this for where we do need to add it to the table.
compiler/src/linking/link.re
Outdated
ignore @@ | ||
Table.add_active_element_segment( | ||
linked_mod, | ||
"tbl", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've seen this string a couple times in this file. Should it be a constant at the top?
compiler/src/linking/link.re
Outdated
[ | ||
Features.mvp, | ||
Features.multivalue, | ||
Features.tail_call, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can always enable tail_call, even it not supported in a runtime?
compiler/src/linking/link.re
Outdated
], | ||
); | ||
let _ = Module.set_low_memory_unused(1); | ||
assert(Module.validate(linked_mod) == 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert
, failwith
or raise
?
esy.lock/.gitattributes
Outdated
@@ -0,0 +1,3 @@ | |||
|
|||
# Set eol to LF so files aren't converted to CRLF-eol on Windows. | |||
* text eol=lf linguist-generated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ospencer Oh, I just noticed that you accidentally ran esy
in the top of the monorepo and committed an extra esy.lock directory here.
cc2e1c0
to
255c95f
Compare
255c95f
to
eadb6ea
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ospencer you are an absolute genius. Amazing work on this!
It seems we both modified the esy.lock so you'll need to resolve that, but otherwise good to go 🎉
feat: Support for more WebAssembly runtimes, including Wasmtime and Wasmer fix!: Correct type signature for `_start` chore!: Introduce `_gmain` for old behavior of `_start` chore!: Tail calls must be enabled explicitly via `--experimental-wasm-tail-call` fix: Use proper return type for calls to external functions feat: Normalized wasm exports for linked modules
eadb6ea
to
63b29ea
Compare
Closes #28.
feat: Support for more WebAssembly runtimes, including Wasmtime and Wasmer
fix!: Correct type signature for
_start
chore!: Introduce
_gmain
for old behavior of_start
chore!: Tail calls must be enabled explicitly via
--experimental-wasm-tail-call
Still a draft as this depends on a number of PRs in https://github.com/grain-lang/binaryen.ml/pulls, but everything is finished.
Static Linking
This PR implements a static linker for Grain modules, which allows us to keep our separate compilation but produce a single wasm file that works with the existing runtime and more runtimes than ever before, including Wasmtime, Wasmer, and Wasm3. This PR may not be enormous, but it's riding on the back of more than two months of hard work! 🎉
Important info
grainc
will perform linking by default. This means thatgrain compile hello.gr && wasmtime hello.gr.wasm
will work without having to worry about flags / wondering why that doesn't work. Linking can be disabled via the--no-link
flag.Since most wasm runtimes don't support tail calls yet, they're now off by default, and can be enabled via
--experimental-wasm-tail-call
.-p
in thegrain
CLI is now deprecated, as it doesn't (and can't) work for linked modules. The tests still rely on it, but once we implement snapshot testing, we can remove it.Other info
Given this is implemented via Binaryen, I learned a ton and can probably contribute some info back to WebAssembly/binaryen#2767. The approach used isn't too far off from being able to work generically (and I even considered implementing the linker directly in Binaryen.ml), though modules would need to adhere to certain constraints.
Other thoughts
We may want to consider renaming the
Compiled
step incompile.re
. It's sort of an odd name now, but I can't quite think of anything better.