r-near opened PR #12940 from r-near:winch-reference-types to bytecodealliance:main:
Implements the core reference type instructions for Winch (funcref only):
ref.null: pushes a null funcref (pointer-sized zero)ref.is_null: compares a ref to zero, produces i32 0/1ref.func: calls the existingref_funcbuiltin to get a funcref for a function index- typed
select: delegates to the existing untypedselectimplementationNon-func heap types (externref, etc.) bail with an unsupported error, consistent with how
table_get,table_set, and other table ops already handle them.Relates to #8088, #9924.
r-near requested cfallin for a review on PR #12940.
r-near requested Copilot for a review on PR #12940.
r-near requested wasmtime-compiler-reviewers for a review on PR #12940.
r-near updated PR #12940.
r-near requested wasmtime-core-reviewers for a review on PR #12940.
Copilot created PR review comment:
visit_ref_is_nullhardcodesRegImm::i64(0)andOperandSize::S64. Sinceref.is_nullis defined over pointer-sized references and this module already threadsenv.ptr_type()elsewhere (e.g.visit_ref_null,memory_growadjustments), it would be more robust to derive the operand size/immediate from the target pointer type (or from the popped value’sWasmValType) rather than assuming 64-bit.This avoids future miscompiles if/when Winch gains 32-bit pointer support or if pointer width becomes configurable in more places.
let (zero, size) = match self.env.ptr_type() { WasmValType::I64 => (RegImm::i64(0), OperandSize::S64), WasmValType::I32 => (RegImm::i32(0), OperandSize::S32), _ => bail!(CodeGenError::unsupported_wasm_type()), }; masm.cmp_with_set(writable!(reg), zero, IntCmpKind::Eq, size)?;
Copilot submitted PR review:
Pull request overview
Adds initial support for core WebAssembly reference-type operators (funcref-only) in Winch, enabling compilation of modules that use
ref.null,ref.is_null,ref.func, and typedselect, while rejecting unsupported heap types in line with existing table-op behavior.Changes:
- Implement
ref.null,ref.is_null, andref.funcvisitor lowering (funcref-only; other heap types error out).- Add support for typed
selectby delegating to the existingselectlowering.- Extend the “supported ops” list so these operators are no longer treated as unimplemented.
:light_bulb: <a href="/bytecodealliance/wasmtime/new/main?filename=.github/instructions/*.instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add Copilot custom instructions</a> for smarter, more guided reviews. <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn how to get started</a>.
Copilot created PR review comment:
visit_typed_selectignores the immediate result type and delegates tovisit_select, which pushesval2with its originalWasmValType. For typedselect, operands may have different (e.g. different ref nullability/subtypes) types while the immediate type defines the result; keepingval2's type can leave the value stack with an incorrect type for subsequent lowering decisions.Consider converting the
ValTypeimmediate to the correspondingWasmValType(e.g. via the existingTypeConverter) and updating the selected value's tracked type to that result type (and/or asserting it matches the operand widths) before pushing it back onto the stack.fn visit_typed_select(&mut self, ty: ValType) -> Self::Output { let cond = self.context.pop_to_reg(self.masm, None)?; let val2 = self.context.pop_to_reg(self.masm, None)?; let val1 = self.context.pop_to_reg(self.masm, None)?; // Determine the result machine type from the immediate. Numeric types // map directly; all ref-like types are represented using the pointer // type in this backend. let result_ty = match ty { ValType::I32 => WasmValType::I32, ValType::I64 => WasmValType::I64, ValType::F32 => WasmValType::F32, ValType::F64 => WasmValType::F64, ValType::V128 => WasmValType::V128, _ => self.env.ptr_type(), }; // In well-typed Wasm, the operands to a select have the same // representation type as the result. Assert this in debug builds. debug_assert_eq!(val1.ty, val2.ty); debug_assert_eq!(val1.ty, result_ty); self.masm .cmp(cond.reg, RegImm::i32(0), OperandSize::S32)?; // Conditionally move val1 to val2 if the comparison is not zero. self.masm.cmov( writable!(val2.into()), val1.into(), IntCmpKind::Ne, val1.ty.try_into()?, )?; // Push the selected value back on the stack using the result type // derived from the immediate, not the original type of `val2`. self.context.stack.push(Val::reg(val2.reg, result_ty)); self.context.free_reg(val1.reg); self.context.free_reg(cond); Ok(())
r-near converted PR #12940 winch: implement ref.null, ref.is_null, ref.func, and typed select to a draft.
r-near updated PR #12940.
r-near submitted PR review.
r-near created PR review comment:
Good catch, fixed to derive the size from
ptr_type()
r-near submitted PR review.
r-near created PR review comment:
In well-typed wasm the operands already have the same representation type as the result, the type annotation is purely for validation. The existing
visit_selecthandles this correctly since it derives the operand size from the stack values. Duplicating the logic to thread aValTypewe don't need seems like unnecessary complexity.
r-near has marked PR #12940 as ready for review.
r-near converted PR #12940 winch: implement ref.null, ref.is_null, ref.func, and typed select to a draft.
r-near updated PR #12940.
r-near updated PR #12940.
r-near has marked PR #12940 as ready for review.
github-actions[bot] added the label winch on PR #12940.
github-actions[bot] commented on PR #12940:
Subscribe to Label Action
cc @saulecabrera
<details>
This issue or pull request has been labeled: "winch"Thus the following users have been cc'd because of the following labels:
- saulecabrera: winch
To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.
Learn more.
</details>
cfallin submitted PR review:
This looks reasonable overall to me -- thanks!
A few testing requests:
- It doesn't look like
ref.func's return value is actually tested with a call; could you add a test case that does atable.setthen acall_indirectto make use of it?- It would be great to have disas tests (
tests/disas/winch/) for all of these operators as well.
r-near updated PR #12940.
r-near commented on PR #12940:
Thanks for the quick review @cfallin! Added the tests here:
43d2950(this PR)
cfallin submitted PR review:
Looks reasonable to me -- thanks!
cfallin added PR #12940 winch: implement ref.null, ref.is_null, ref.func, and typed select to the merge queue.
github-merge-queue[bot] removed PR #12940 winch: implement ref.null, ref.is_null, ref.func, and typed select from the merge queue.
cfallin commented on PR #12940:
@r-near there's a CI test failure (see status from the merge queue attempt) -- perhaps a test is running that shouldn't yet with the current implementation status. Could you take a look?
r-near requested alexcrichton for a review on PR #12940.
r-near requested wasmtime-fuzz-reviewers for a review on PR #12940.
r-near updated PR #12940.
cfallin submitted PR review:
Actually I believe @fitzgen added this
wasmtime::gctest hostcall recently -- Nick, could you verify this is added at the correct place in this case? Thanks!
fitzgen submitted PR review.
cfallin added PR #12940 winch: implement ref.null, ref.is_null, ref.func, and typed select to the merge queue.
cfallin merged PR #12940.
cfallin removed PR #12940 winch: implement ref.null, ref.is_null, ref.func, and typed select from the merge queue.
Last updated: Apr 12 2026 at 23:10 UTC