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

Workaround for floating point arguments and return values in DynamicFuncs. #1283

Merged
merged 17 commits into from
Mar 12, 2020

Conversation

losfair
Copy link
Contributor

@losfair losfair commented Mar 9, 2020

This PR makes floating point arguments and return values for DynamicFuncs work correctly in all three backends.

Previously Singlepass used integer registers for all arguments. This PR adds another thin trampoline layer just before control is transferred to the import function, so that arguments will be rearranged strictly according to the System V ABI.

The full fix would require singlepass to implement the SysV calling convention internally too: #1271 . This is just a workaround.

@losfair
Copy link
Contributor Author

losfair commented Mar 9, 2020

bors try

bors bot added a commit that referenced this pull request Mar 9, 2020
@bors
Copy link
Contributor

bors bot commented Mar 9, 2020

try

Build failed

let mut mcg_info_fed = false;

loop {
use wasmparser::ParserState;
let state = parser.read();

// Feed signature and namespace information as early as possible.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm confused about this change? These would have the same behaviour if they were cases in the match *state that follows, right? If this is about code reuse, maybe create a closure?

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it makes sense to use the Wasmparser types that let us be more specific about what exactly we want: https://docs.rs/wasmparser/0.51.4/wasmparser/struct.CodeSectionReader.html . They have a bunch of section-specific parsers, there aren't validating versions of these parsers though, so we'd have to do a validation pass first

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since this block borrows mcg, there would be lifetime issues if this is made into a closure. And yeah this is about code reuse. The same code was already duplicated twice for BeginFunctionBody and EndWasm, and this change will add one more duplication if done "in place".

Copy link
Contributor

Choose a reason for hiding this comment

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

mcg could be passed as an argument here to make closures work

lib/runtime-core/src/state.rs Outdated Show resolved Hide resolved
lib/runtime-core/src/state.rs Outdated Show resolved Hide resolved
@@ -246,44 +248,49 @@ impl TrampolineBufferBuilder {
&mut self,
target: unsafe extern "C" fn(*const CallContext, *const u64) -> u64,
context: *const CallContext,
num_params: u32,
params: &[Type],
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason this shouldn't just take the full function signature? As it happens it only used to need the number of parameters, now it needs the whole list of the types, but isn't that an implementation detail?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function does not prepend the WebAssembly-specific vm::Ctx argument and is just a general-purpose trampoline generator, therefore should not take a WebAssembly FuncSig. I added a returns argument just in case of future compatibility.

unsafe extern "C" fn inner(n: *const CallContext, args: *const u64) -> u64 {
let n = n as usize;
let mut result: u64 = 0;
for i in 0..n {
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry I'm confused – n was a pointer to a CallContext, so if it's at address 0x00005620d48999e8 then this loop will run 94699004729832 times?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The *const CallContext is casted from the length of the parameter list as an integer in the following call to add_callinfo_trampoline, so it's not really a pointer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will add a comment.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, but you can't just go around passing integers as pointers. The compiler makes the assumption that the pointer is sufficiently aligned to point to a CallContext, which could lead it to conclude that the bottom bits are zero. All you do here is use it as a usize which will cast it, but if you were to pass, say, a bitfield through, the compiler would actually delete the tests of the bottom bits.

I don't know about Rust, but in C and C++ the rule is that all pointers must actually point to a valid object when created (whenever an expression of pointer type is evaluated). You're guaranteed that casting a pointer to an appropriately sized int and back to the same pointer type gets you the original pointer back, but that's a different property from being able to take an int, cast it to a pointer, and put it back to an int. For a crazy example that no compiler I know of does, if there's a value in the range [0, 4) the compiler could pack it in the same storage as the int.

If this is a temporary part of the workaround that will go away with the permanent fix, let me know. Casting this way doesn't seem to go wrong in practice, much.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is part of the test code and does not go into the compiled crate, so the worst case is one more failing test and we know something is wrong here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh no, that's never the worst case. :-) The worst case is something like "not only does this test pass when it should actually fail, it causes the other tests running in other threads to incorrectly pass when they should be failing". Anyway, I don't think we need to change this right now.

lib/runtime-core/src/typed_func.rs Outdated Show resolved Hide resolved
lib/singlepass-backend/src/codegen_x64.rs Show resolved Hide resolved
}
match *state {
ParserState::BeginFunctionBody { .. } | ParserState::EndWasm => {
if !mcg_info_fed {
Copy link
Contributor

Choose a reason for hiding this comment

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

If we use separate match statements, moving the if !mcg_*_fed { checks outside the match *state will probably have better performance due to better branch prediction.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is a kind of simple optimization that can be left to the compiler?

Copy link
Contributor

@nlewycky nlewycky Mar 11, 2020

Choose a reason for hiding this comment

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

Depends whether the compiler can prove *state is side-effect free. I'm sorta torn on this one, I think we should write whatever is clearest to the reader, but I'm not sure that pulling this part of the match out in advance is actually clearer. I'm happy to let you and Mark decide.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will make it into a closure.

let mut mcg_info_fed = false;

loop {
use wasmparser::ParserState;
let state = parser.read();

// Feed signature and namespace information as early as possible.
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it makes sense to use the Wasmparser types that let us be more specific about what exactly we want: https://docs.rs/wasmparser/0.51.4/wasmparser/struct.CodeSectionReader.html . They have a bunch of section-specific parsers, there aren't validating versions of these parsers though, so we'd have to do a validation pass first

lib/spectests/tests/spectest.rs Outdated Show resolved Hide resolved
lib/runtime-core-tests/tests/imports.rs Outdated Show resolved Hide resolved
lib/runtime-core/src/trampoline_x64.rs Outdated Show resolved Hide resolved
losfair and others added 4 commits March 10, 2020 12:28
@losfair
Copy link
Contributor Author

losfair commented Mar 10, 2020

bors try

bors bot added a commit that referenced this pull request Mar 10, 2020
@bors
Copy link
Contributor

bors bot commented Mar 10, 2020

try

Build succeeded

@syrusakbary
Copy link
Member

bors r+

@bors
Copy link
Contributor

bors bot commented Mar 12, 2020

Build succeeded

@bors bors bot merged commit 18168fc into master Mar 12, 2020
@bors bors bot deleted the fix/fpcc-workaround branch March 12, 2020 05:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants