Skip to content
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

Merged
merged 1 commit into from
Apr 18, 2021
Merged

feat!: Static linking of Grain modules #584

merged 1 commit into from
Apr 18, 2021

Conversation

ospencer
Copy link
Member

@ospencer ospencer commented Apr 3, 2021

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 that grain 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 the grain 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 in compile.re. It's sort of an odd name now, but I can't quite think of anything better.

@ospencer ospencer requested a review from a team April 3, 2021 00:43
@ospencer ospencer self-assigned this Apr 3, 2021
compiler/esy.json Outdated Show resolved Hide resolved
Copy link
Member

@phated phated left a 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.

(reason (>= 3.6.0))
(ppx_sexp_conv (>= 0.14.0))
(sexplib (>= 0.14.0))
(binaryen (= 0.9.0))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(binaryen (= 0.9.0))
(binaryen (= 0.9.1))

compiler/esy.json Show resolved Hide resolved
compile_file(
~outfile,
~reset=false,
~hook=stop_after_object_file_emitted,
Copy link
Member

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?

Copy link
Member Author

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")
Copy link
Member

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

Copy link
Member Author

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 :)

compiler/src/linking/link.re Show resolved Hide resolved
Comment on lines +377 to +474
let new_name =
Hashtbl.find(
Hashtbl.find(exported_names, imported_module),
imported_name,
);
Hashtbl.add(local_names, internal_name, new_name);
Copy link
Member

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.

Copy link
Member Author

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.

Comment on lines +407 to +500
let new_name = gensym(internal_name);
Hashtbl.add(local_names, internal_name, new_name);
Copy link
Member

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.

Copy link
Member Author

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.

ignore @@
Table.add_active_element_segment(
linked_mod,
"tbl",
Copy link
Member

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?

[
Features.mvp,
Features.multivalue,
Features.tail_call,
Copy link
Member

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?

],
);
let _ = Module.set_low_memory_unused(1);
assert(Module.validate(linked_mod) == 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert, failwith or raise?

@@ -0,0 +1,3 @@

# Set eol to LF so files aren't converted to CRLF-eol on Windows.
* text eol=lf linguist-generated
Copy link
Member

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.

@ospencer ospencer force-pushed the static-linking branch 4 times, most recently from cc2e1c0 to 255c95f Compare April 18, 2021 03:45
@ospencer ospencer marked this pull request as ready for review April 18, 2021 03:46
@ospencer ospencer requested a review from a team April 18, 2021 03:47
Copy link
Member

@phated phated left a 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
@ospencer ospencer merged commit 3d4ac6e into main Apr 18, 2021
@ospencer ospencer deleted the static-linking branch April 18, 2021 20:13
@github-actions github-actions bot mentioned this pull request Apr 20, 2021
@github-actions github-actions bot mentioned this pull request May 31, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Multi-Module Packaging
2 participants