-
Notifications
You must be signed in to change notification settings - Fork 824
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 DynamicFunc
s.
#1283
Conversation
…t-functions' into fix/fpcc-workaround
bors try |
tryBuild failed |
lib/runtime-core/src/parse.rs
Outdated
let mut mcg_info_fed = false; | ||
|
||
loop { | ||
use wasmparser::ParserState; | ||
let state = parser.read(); | ||
|
||
// Feed signature and namespace information as early as possible. |
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'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?
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.
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
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.
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".
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.
mcg
could be passed as an argument here to make closures work
@@ -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], |
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.
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?
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 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 { |
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.
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?
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.
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.
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.
Will add a comment.
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.
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.
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 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.
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.
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/parse.rs
Outdated
} | ||
match *state { | ||
ParserState::BeginFunctionBody { .. } | ParserState::EndWasm => { | ||
if !mcg_info_fed { |
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.
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.
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 think this is a kind of simple optimization that can be left to the compiler?
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.
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.
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.
Will make it into a closure.
lib/runtime-core/src/parse.rs
Outdated
let mut mcg_info_fed = false; | ||
|
||
loop { | ||
use wasmparser::ParserState; | ||
let state = parser.read(); | ||
|
||
// Feed signature and namespace information as early as possible. |
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.
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
Co-Authored-By: nlewycky <[email protected]>
Co-Authored-By: nlewycky <[email protected]>
bors try |
tryBuild succeeded |
bors r+ |
Build succeeded
|
This PR makes floating point arguments and return values for
DynamicFunc
s 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.