Skip to content

gc_fuzz: Struct fields#13101

Open
khagankhan wants to merge 7 commits intobytecodealliance:mainfrom
khagankhan:struct-fields
Open

gc_fuzz: Struct fields#13101
khagankhan wants to merge 7 commits intobytecodealliance:mainfrom
khagankhan:struct-fields

Conversation

@khagankhan
Copy link
Copy Markdown
Contributor

Structs with fields

  • StructNew: Consumes ExternRef values from the operand stack for ref-typed fields instead of always synthesizing ref.null extern. POD fields still get default values. The consumed count is tracked in externref_from_stack and used during encoding via scratch locals to interleave stack values with defaults in field declaration order. POD constants do not need to come from the stack since we can always synthesize them, but externref (and concrete struct ref types in the future) should be consumed from the stack when available. This is implemented as a special case in the fixup loop rather than changing the #[operands()] attribute or introducing new stack value types, because the number of ref fields a struct will consume is not known statically since it depends on the struct type's field layout. The rest of the ops use static operand and result types so making them dynamic would be a larger refactor with little benefit. When the top of the abstract stack has consecutive ExternRef values, fixup pops them and records the count in externref_from_stack, letting the encoding know how many live values to retrieve via local.set/local.get instead of synthesizing ref.null extern.

  • StructSet: Consumes one ExternRef from the stack when the target mutable field is a ref type, using it as the field value instead of ref.null extern. During fixup, it finds the first mutable field starting from field_index (scanning forward, wrapping around) and sets it. if no mutable field exists, the struct ref is discarded.

  • StructGet/StructSet null guards: Both now check for null refs before accessing fields (ref.is_null + if/else), preventing null reference traps.

Other changes

@khagankhan khagankhan requested a review from a team as a code owner April 14, 2026 21:24
@khagankhan khagankhan requested review from fitzgen and removed request for a team April 14, 2026 21:24
@khagankhan
Copy link
Copy Markdown
Contributor Author

@fitzgen I know this maybe a lot here but what I need the most is to know whether this solution makes sense or do we need something else like maybe adding d PODs as GcOps and to Stack or something else.

@github-actions github-actions bot added the fuzzing Issues related to our fuzzing infrastructure label Apr 14, 2026
@github-actions
Copy link
Copy Markdown

Subscribe to Label Action

cc @fitzgen

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

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

  • fitzgen: fuzzing

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

Learn more.

Copy link
Copy Markdown
Member

@fitzgen fitzgen left a comment

Choose a reason for hiding this comment

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

Thanks! Sorry that it took a little while to review this one.

Comment on lines +438 to +439
let idx = usize::try_from(ctx.rng().gen_u32())
.expect("failed to convert rng output to usize")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This can just .unwrap(): usize::try_from(my_u32).unwrap() is common enough that it doesn't need a diagnostic message and it also shouldn't ever fail on our supported architectures anyway. Same with the other instances of this here.

Comment on lines +452 to +454
let idx = usize::try_from(ctx.rng().gen_u32())
.expect("failed to convert rng output to usize")
% FieldType::ALL.len();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also this pattern is probably a little more straight forward as:

let idx = ctx.rng().gen_index(FieldType::ALL.len());
let idx = u32::try_from(idx).unwrap();

And maybe even better deduplicated as a fn FieldType::random(rng: &mut mutatis::Rng) -> u32 helper function.

composite_type: wasm_encoder::CompositeType {
inner: wasm_encoder::CompositeInnerType::Struct(
wasm_encoder::StructType {
fields: fields.into_boxed_slice(),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You should be able to just collect the fields into a Box<[_]> in the first place rather than collect and then call into_boxed_slice().

GcOp::StructNew {
type_index,
externref_from_stack: consumed,
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm a bit confused here: why are we special-casing externref-typed fields here? Why are they different from i32-typed fields, for example? In either case, we need an operand of each field's type so I don't understand why we're not handling them uniformly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

My thinking was that reusing an externref or concretely typed references already on the stack may be more interesting. For example values from table.set or table.get. Since externref is GC related I wanted to distinguish it from something like i32.

P.S. I do not like special cases. If this is not useful we can synthesize externref too. We can also add them as StackTypes or GcOps. But this may just generate and discard values and waste mutation and execution cycles.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I will choose the first one and address the feedback. I can make PRs in the future if this turns out to be non-ideal.

Update Field mutation randomness

Update boxing in emitting binary
@khagankhan
Copy link
Copy Markdown
Contributor Author

Thanks @fitzgen ! Ready for the next round of reviews

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fuzzing Issues related to our fuzzing infrastructure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants