-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Implement the struct.*
GC instructions
#9275
Implement the struct.*
GC instructions
#9275
Conversation
This commit implements the `struct.*` instructions for the GC proposal. These instructions allow allocating new structs and getting and setting their fields. The implemented instructions are: * `struct.new` * `struct.new_default` * `struct.get` * `struct.get_s` * `struct.get_u` * `struct.set` The `struct.new*` instructions are also allowed in constant expressions, but support for that is not yet implemented. Co-Authored-By: Trevor Elliott <[email protected]>
98daafd
to
57ae451
Compare
Subscribe to Label Actioncc @fitzgen
This issue or pull request has been labeled: "cranelift", "cranelift:wasm", "wasmtime:api", "wasmtime:ref-types"
Thus the following users have been cc'd because of the following labels:
To subscribe or unsubscribe from this label, edit the |
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.
Nice progress! 👍
@@ -48,12 +48,114 @@ pub fn gc_ref_table_fill_builtin( | |||
imp::gc_ref_table_fill_builtin(ty, func_env, func) | |||
} | |||
|
|||
/// Translate a `struct.new` instruction. | |||
pub fn translate_struct_new( |
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 you want I think it'd be fine to pub use
the inner module up to here. That loses the "type check" that it's exactly the same signature with/without gc but I think that's ok given all the CI checks we have.
} | ||
} | ||
|
||
fn translate_struct_new( |
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.
Another idea, if you're interested to cut down on the duplication here, change the spec trait to be like the component compiler where it has a method to return fn gc_compiler(&mut self) -> Option<&mut dyn GcCompiler>;
(in the cranelift-wasm crate) where the GcCompiler
trait has all these struct translation methods. That way when gc is disabled you don't even need to impl GcCompiler
, it just returns None
, and only in the gc enabled case are there all the methods.
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 a good idea -- I'll do a follow up to cut down on the boilerplate but leave it as-is for this PR.
// TODO: If we knew we have a `(ref i31)` here, instead of maybe a `(ref | ||
// null i31)`, we could omit the `trapz`. But plumbing that type info | ||
// from `wasmparser` and through to here is a bit funky. |
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.
Another alternative would be a cranelift pass perhaps? Optimizing away trapz
shouldn't be too too hard in theory for most simple cases. (I realize egraphs won't work for this at all, but we can always write a custom pass too)
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 it is a GC ref that this function allocated, or has already accessed, then we should be able to clean it up. In theory at least; in practice it might be a little tricky without introducing some new powers to the mid-end. But for an argument that we haven't accessed yet, then we can't really do anything without the type info, I don't think.
WasmStorageType::I8 => { | ||
builder | ||
.ins() | ||
.istore8(ir::MemFlags::trusted(), new_val, field_addr, 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.
Is ::trusted()
the right default here? I would have thought that this would be "can possibly trap" flags. We can stuff in a debug trap code which doesn't cause trap table information to get emitted but it should still hinder optimizations and such in Cranelift (which I think we want at least for 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.
I think we actually don't want to hinder optimizations. These aren't visible to Wasm semantics, or at least the whole point is they shouldn't be, so if the optimizer can prove that a load is redundant or whatever then I think we should let it.
/// read or write up to `size` bytes. Do NOT attempt accesses larger than | ||
/// `size` bytes; that may lead to unchecked out-of-bounds accesses. | ||
/// | ||
/// This method is collector-agnostic. | ||
fn prepare_gc_ref_access( |
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 method may want to be refactored to return (ir::Value, u32)
to enable optionally embedding the offset in the load/store instruction. For now it'd always return 0 but maybe a future refactoring to have to optimize this a bit in the future.
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.
Yeah, I was also thinking it may want to take a object_size: Option<u32>
argument as well to make bounds checks of repeated accesses dedupe-able by the mid-end. (Has to be an Option
because we don't statically know array lengths).
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.
But: simple, naive things first and we can clean up the code we generate in follow ups.
;; @002a v15 = uextend.i64 v11 | ||
;; @002a v16 = iconst.i64 16 | ||
;; @002a v17 = uadd_overflow_trap v15, v16, user65535 ; v16 = 16 |
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.
As a minor possible optimization (future PR) this should be pretty easy to optimize in the egraph pass to downgrade this to iadd
. It's pretty obvious by construction that this can't overflow, along with many other patterns too.
Whether it's worth it I'm not sure. Optimizing bounds checks is naturally going to be much more involved than just this b/c we want to rely on guard pages exclusively in theory.
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.
Ideally we'd have a mid-end ISLE rule something like
(rule (simplify (uadd_overflow_trap $i64 x y))
(if-let $true (add_cannot_overflow $I64 x y))
(iadd x y))
where add_cannot_overflow
handles constants and (uextend ...)
s and such.
But because uadd_overflow_trap
has side effects, we can't write such a rule in the mid-end today.
This commit implements the
struct.*
instructions for the GC proposal. These instructions allow allocating new structs and getting and setting their fields.The implemented instructions are:
struct.new
struct.new_default
struct.get
struct.get_s
struct.get_u
struct.set
The
struct.new*
instructions are also allowed in constant expressions, but support for that is not yet implemented.