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

Implement the struct.* GC instructions #9275

Merged
merged 4 commits into from
Sep 18, 2024

Conversation

fitzgen
Copy link
Member

@fitzgen fitzgen commented Sep 18, 2024

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.

@fitzgen fitzgen requested review from a team as code owners September 18, 2024 01:15
@fitzgen fitzgen requested review from elliottt and alexcrichton and removed request for a team and elliottt September 18, 2024 01:15
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]>
@github-actions github-actions bot added cranelift Issues related to the Cranelift code generator cranelift:wasm wasmtime:api Related to the API of the `wasmtime` crate itself wasmtime:ref-types Issues related to reference types and GC in Wasmtime labels Sep 18, 2024
Copy link

Subscribe to Label Action

cc @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:

  • fitzgen: wasmtime:ref-types

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

Learn more.

Copy link
Member

@alexcrichton alexcrichton left a comment

Choose a reason for hiding this comment

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

Nice progress! 👍

crates/environ/src/gc.rs Outdated Show resolved Hide resolved
crates/cranelift/src/gc/disabled.rs Outdated Show resolved Hide resolved
@@ -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(
Copy link
Member

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(
Copy link
Member

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.

Copy link
Member Author

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.

Comment on lines +1863 to +1865
// 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.
Copy link
Member

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)

Copy link
Member Author

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

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

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 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(
Copy link
Member

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.

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, 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).

Copy link
Member Author

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.

Comment on lines +32 to +34
;; @002a v15 = uextend.i64 v11
;; @002a v16 = iconst.i64 16
;; @002a v17 = uadd_overflow_trap v15, v16, user65535 ; v16 = 16
Copy link
Member

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.

Copy link
Member Author

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.

crates/cranelift/src/gc/enabled.rs Show resolved Hide resolved
@fitzgen fitzgen added this pull request to the merge queue Sep 18, 2024
Merged via the queue into bytecodealliance:main with commit 37ed724 Sep 18, 2024
39 checks passed
@fitzgen fitzgen deleted the gc-struct-instructions branch September 18, 2024 17:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cranelift:wasm cranelift Issues related to the Cranelift code generator wasmtime:api Related to the API of the `wasmtime` crate itself wasmtime:ref-types Issues related to reference types and GC in Wasmtime
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants