Skip to content

Conversation

@AndyAyersMS
Copy link
Member

On Wasm null checks must be explicit and exceptions raised via helper call.

Set up some of the mechanism we'll need for this:

  • Add null check special code kind
  • Track Wasm ACD entries by handler region only (instead of by try or handler). The code address of the helper cannot be used in Wasm to infer EH region containment; we will use use the virtual IP for that. So we need at most one throw helper (per kind) in each funclet and in the main method region.
  • Ensure throw helper blocks have Wasm labels that are always on the stack in their regions by putting the throw helpers at the end of the region RPO and pretending there is a branch from the region entry.
  • Add plausible codegen for GT_NULLCHECK

On Wasm null checks must be explicit and exceptions raised via helper call.

Set up some of the mechanism we'll need for this:
* Add null check special code kind
* Track Wasm ACD entries by handler region only (instead of by try or handler). The code
address of the helper cannot be used in Wasm to infer EH region containment; we will use
use the virtual IP for that. So we need at most one throw helper (per kind) in each funclet
and in the main method region.
* Ensure throw helper blocks have Wasm labels that are always on the stack in their regions
by putting the throw helpers at the end of the region RPO and pretending there is a branch
from the region entry.
* Add plausible codegen for GT_NULLCHECK
@github-actions github-actions bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jan 9, 2026
@AndyAyersMS
Copy link
Member Author

FYI @dotnet/jit-contrib (still WIP, need to see how much of this is testable)

See #123021 (comment) for some context.

@AndyAyersMS AndyAyersMS changed the title [Wasm RyjJit] throw helper / null check preliminaries [Wasm RyuJit] throw helper / null check preliminaries Jan 9, 2026
@AndyAyersMS
Copy link
Member Author

I don't have the code to make SCK_NULLCHECK demands in place yet. Seeing as nullchecks get generated in many places it seems less than ideal to track all these sites all down and add code to each one. I wonder if we can just do this in lower or similar.

Copy link
Contributor

@SingleAccretion SingleAccretion left a comment

Choose a reason for hiding this comment

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

I don't have the code to make SCK_NULLCHECK demands in place yet. Seeing as nullchecks get generated in many places it seems less than ideal to track all these sites all down and add code to each one. I wonder if we can just do this in lower or similar.

+1. In fact I don't see the point of this two-phase setup that exists. Why not add all the blocks late (in stack setter or lower) and remove the handling from morph?

bool Compiler::fgUseThrowHelperBlocks()
{
#if defined(TARGET_WASM)
return true;
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see a strong reason to make WASM special here. Unique "native" code addresses still matter for "native" stacks produced by engines. It is helpful to make them (more) useful with debug code.

Copy link
Member Author

@AndyAyersMS AndyAyersMS Jan 10, 2026

Choose a reason for hiding this comment

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

I suppose it is not so hard -- if we have say GT_NULLCHECK(x) we can produce

block
local.get x
i32.const null-limit-value
i32.gt
br_if 0
call <helper-idx> ; THROW NULL CHECK
unreachable       ; 
end

and this will properly nest wherever we happen to emit it, even if we've pended other operands already. The main difference being we need to know a bit earlier if there's a common helper block we can use.

{
assert(compiler->fgUseThrowHelperBlocks());
genConsumeOperand(tree->Addr());
GetEmitter()->Ins(INS_i32_eqz);
Copy link
Contributor

Choose a reason for hiding this comment

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

Ref #123053 (comment) for what value this should check against.

void CodeGen::genCodeForNullCheck(GenTreeIndir* tree)
{
assert(compiler->fgUseThrowHelperBlocks());
genConsumeOperand(tree->Addr());
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
genConsumeOperand(tree->Addr());
genConsumeAddress(tree->Addr());

Comment on lines +3720 to +3721
// For WASM we want one throw helper per funclet
// So we ignore any try region nesting.
Copy link
Contributor

Choose a reason for hiding this comment

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

This would also be hardcoding the "one catch per funclet (with nested trys)" scheme, right? Otherwise we still need the nesting for proper resumption.

Apart from that, the comment could be improved to talk about "why" we want this.

@AndyAyersMS
Copy link
Member Author

+1. In fact I don't see the point of this two-phase setup that exists.

I think this is just a long-standing practice that we've never reexamined. Perhaps it is time.

@am11 am11 added the arch-wasm WebAssembly architecture label Jan 10, 2026
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.

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

Labels

arch-wasm WebAssembly architecture area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants