Skip to content

Conversation

@abrown
Copy link
Member

@abrown abrown commented Apr 15, 2020

This resolves the work started in bytecodealliance/cranelift#1231 and #1436. Cranelift filetests currently have the ability to run CLIF functions with a signature like () -> b* and check that the result is true under the test run directive. This PR adds the ability to call functions with arbitrary arguments and non-boolean returns and either print the result or check against a list of expected results:

  • run commands look like ; run: %add(2, 2) == 4 or ; run: %add(2, 2) != 5 and verify that the executed CLIF function returns the expected value
  • print commands look like ; print: %add(2, 2) and print the result of the function to stdout

To make this work, this PR compiles a single Cranelift Function into a CompiledFunction using a SingleFunctionCompiler. Because we will not know the signature of the function until runtime, we use a Trampoline to place the values in the appropriate location for the calling convention; this should look a lot like what @alexcrichton is doing with VMTrampoline in wasmtime (see

let mut values_vec = vec![0; max(params.len(), self.ty.results().len())];
// Store the argument values into `values_vec`.
let param_tys = self.ty.params().iter();
for ((arg, slot), ty) in params.iter().zip(&mut values_vec).zip(param_tys) {
if arg.ty() != *ty {
return Err(Trap::new("argument type mismatch"));
}
if !arg.comes_from_same_store(&self.store) {
return Err(Trap::new(
"cross-`Store` values are not currently supported",
));
}
unsafe {
arg.write_value_to(slot);
}
}
,
pub fn make_trampoline(
). To avoid re-compiling Trampolines for the same function signatures, Trampolines are cached in the SingleFunctionCompiler.

@abrown
Copy link
Member Author

abrown commented Apr 15, 2020

I expect this to fail because there are no encodings for booleans in load and store. @sunfishcode and others: should we add these encodings or should make_trampoline do something special for booleans?

@github-actions github-actions bot added the cranelift Issues related to the Cranelift code generator label Apr 15, 2020
@github-actions
Copy link

Subscribe to Label Action

cc @bnjbvr

Details This issue or pull request has been labeled: "cranelift"

Thus the following users have been cc'd because of the following labels:

  • bnjbvr: cranelift

To subscribe or unsubscribe from this label, edit the .github/subscribe-to-label.json configuration file.

Learn more.

@abrown abrown force-pushed the run-clif branch 2 times, most recently from 430fa31 to 6bec6d9 Compare April 15, 2020 21:34
@abrown
Copy link
Member Author

abrown commented Apr 16, 2020

@alexcrichton, here's the PR with the trampolines I was referring to. (You might notice they look pretty similar to the ones you created 😉).

@abrown abrown mentioned this pull request Apr 16, 2020
@alexcrichton
Copy link
Member

Nice! FWIW reading this over I think there's actually not a whole lot that we'd share between wasmtime and cranelift. Only a few small loops/etc would be shared, and otherwise it seems like we'd probably bend over backwards too much trying to cater to both use cases. In that sense I'd personally be ok if we just left this duplication in the two?

@abrown abrown force-pushed the run-clif branch 2 times, most recently from e1faf45 to 6c2e2b5 Compare April 18, 2020 22:15
@abrown abrown marked this pull request as ready for review April 18, 2020 22:16
@abrown abrown requested a review from bnjbvr April 18, 2020 22:16
@abrown
Copy link
Member Author

abrown commented Apr 18, 2020

Ok, I think this is ready: since the x86 backend does not have encodings for loading and storing booleans, I took @sunfishcode's suggestion and convert boolean values from (load + icmp_imm ne ..., 0) and to (bint + store).

@github-actions github-actions bot added the cranelift:meta Everything related to the meta-language. label Apr 18, 2020
@abrown abrown force-pushed the run-clif branch 2 times, most recently from ac158d3 to ede2aa2 Compare April 18, 2020 22:35
@abrown abrown force-pushed the run-clif branch 2 times, most recently from 8450ad2 to 9aa94a9 Compare April 22, 2020 18:04
Copy link
Member

@bnjbvr bnjbvr left a comment

Choose a reason for hiding this comment

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

Thanks! (and sorry about the long review times)
Looking good, nice jobs with the trampolines. A few suggestions below to harmonize the code a bit.

/// Check if a CLIF comment is potentially parseable as a [RunCommand].
pub fn is_potential_run_command(comment: &str) -> bool {
let trimmed = Self::trim_comment_chars(comment);
trimmed.starts_with("run") || trimmed.starts_with("print")
Copy link
Member

Choose a reason for hiding this comment

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

Instead, could this look if trimmed is exactly run or print? I don't see the value in the ambiguity. If you agree with this, then we could rename this function is_run_or_print_command (since otherwise, there's a "run_command" called "run" and another called "print", which is a bit confusing).

In addition to this, could this return the command it found (so an Option, with None equivalent to current's false), instead of letting the caller clean up comments again and analyze the value?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, this is a good direction. These weird trimmed_comment_chars and is_potential_run_command functions were trying to do some lookahead to see if we actually needed to really parse the comment. I moved that logic into parse_run_command (which is in the parser module, where this functionality should be) and made it return ParseResult<Option<RunCommand>> instead of ParseResult<RunCommand>. The signature is a bit more complex but I think it is headed more in the direction you are suggesting.

/// - compile a [Trampoline] for the [Function]'s signature (or used a cached [Trampoline];
/// this makes it possible to call functions when the signature is not known until runtime.
pub fn compile(&mut self, function: Function) -> Result<CompiledFunction, CompilationError> {
if function.signature.call_conv != self.isa.default_call_conv() {
Copy link
Member

Choose a reason for hiding this comment

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

Could it also check that the host architecture is equal to the requested ISA's architecture?

Copy link
Member Author

Choose a reason for hiding this comment

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

Those checks should already exist at a higher level, e.g. test_run.rs, and I didn't want to limit this code too much because there are cases (x86 32-bit vs 64-bit) where multiple Cranelift ISAs could run on the same machine.

Copy link
Contributor

Choose a reason for hiding this comment

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

32bit x86 doesn't work in 64bit processes.

panic!("argument type mismatch");
}
unsafe {
Trampoline::write_value_to(arg, slot);
Copy link
Member

Choose a reason for hiding this comment

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

Could we just do a write here, using a transmute? It avoids the indirection through Trampoline::write_value_to, which is a bit unsettling.

If we did the same for reads, then the need for the Trampoline data structure is much lower, and could probably be avoided. Or we could hide the writing and reading into a different new UnboxedValues struct type (that'd be a vec); then the slot_size could be an attribute of this struct, a const func returning the size of the values it's storing (and then we can remove a bunch of comments, and have more confidence in the size being sync'd with the storage type). What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

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

I like having the Trampoline structure around to know what we are talking about; otherwise we have an Mmap floating around that is implicitly a trampoline. But that could be a type alias, I guess. I think your UnboxedValues makes sense and I started down this road but I'm not too confident about the transmute stuff--I will ping you on Zulip to see what you think.

@abrown abrown force-pushed the run-clif branch 2 times, most recently from dc0bcb5 to 1f02a95 Compare April 29, 2020 00:29
@bnjbvr bnjbvr self-requested a review April 29, 2020 14:52
Copy link
Member

@bnjbvr bnjbvr left a comment

Choose a reason for hiding this comment

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

LGTM, thanks!

abrown added 2 commits April 30, 2020 10:39
This resolves the work started in bytecodealliance/cranelift#1231 and bytecodealliance#1436. Cranelift filetests currently have the ability to run CLIF functions with a signature like `() -> b*` and check that the result is true under the `test run` directive. This PR adds the ability to call functions with arbitrary arguments and non-boolean returns and either print the result or check against a list of expected results:
 - `run` commands look like `; run: %add(2, 2) == 4` or `; run: %add(2, 2) != 5` and verify that the executed CLIF function returns the expected value
 - `print` commands look like `; print: %add(2, 2)` and print the result of the function to stdout

To make this work, this PR compiles a single Cranelift `Function` into a `CompiledFunction` using a `SingleFunctionCompiler`. Because we will not know the signature of the function until runtime, we use a `Trampoline` to place the values in the appropriate location for the calling convention; this should look a lot like what @alexcrichton is doing with `VMTrampoline` in wasmtime (see https://github.com/bytecodealliance/wasmtime/blob/3b7cb6ee64469470fcdd68e185abca8eb2a1b20a/crates/api/src/func.rs#L510-L526, https://github.com/bytecodealliance/wasmtime/blob/3b7cb6ee64469470fcdd68e185abca8eb2a1b20a/crates/jit/src/compiler.rs#L260). To avoid re-compiling `Trampoline`s for the same function signatures, `Trampoline`s are cached in the `SingleFunctionCompiler`.
@abrown abrown merged commit 38dff29 into bytecodealliance:master Apr 30, 2020
@abrown abrown deleted the run-clif branch April 30, 2020 18:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cranelift:meta Everything related to the meta-language. cranelift Issues related to the Cranelift code generator

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants