Skip to content

cranelift: handle interrupt_poll in inliner and emit stack map at faulting load PC - #14947

Open
Shoryamishra61 wants to merge 2 commits into
bytecodealliance:mainfrom
Shoryamishra61:fix/interrupt-poll-14926
Open

Shoryamishra61 wants to merge 2 commits into
bytecodealliance:mainfrom
Shoryamishra61:fix/interrupt-poll-14926

Conversation

@Shoryamishra61

@Shoryamishra61 Shoryamishra61 commented Oct 22, 2026 •

Copy link
Copy Markdown

Fixes #14926

Why this change is needed

When inlining functions that contain interrupt_poll or compiling functions where an interrupt_poll possesses user stack maps, Cranelift encountered several issues:

  1. Calling Convention Invariant & Safepoint Mapping: interrupt_poll is a safepoint at which stack maps must be emitted at the faulting load instruction PC for signal handler inspection.
  2. Opcode Classification: Following review feedback from @cfallin, interrupt_poll does not have a callee, arguments, or calling convention (signal handlers are transparent to control flow). It is therefore classified without .call(), but retains safepoint handling (is_safepoint()).
  3. Inlining Logic: Because interrupt_poll is not classified as a call, the inliner copies it directly in the instruction-copying loop, and stack map entries are cleanly copied using opcode.is_safepoint().
  4. Backend Safepoints: Both x64 and AArch64 backends classify InterruptPoll as a safepoint (is_safepoint()), emitting user stack maps at the address of the faulting load without artificial NOP padding.

Implementation Highlights

  • Instruction Definition (cranelift/codegen/meta/src/shared/instructions.rs):
    • Removed .call() classification from interrupt_poll.
  • Inliner (cranelift/codegen/src/inline.rs):
    • Simplified inlining loop: uses opcode.is_safepoint() for copying user stack map entries, while reserving call/try_call exception table fixups strictly for actual call instructions.
  • ISA Backends (cranelift/codegen/src/isa/{x64, aarch64}/inst/):
    • is_safepoint() returns true for Inst::InterruptPoll.
    • User stack maps emitted at the exact faulting load address without artificial NOP insertion.
  • Regression Tests (cranelift/filetests/):
    • Updated CLIF filetests to reflect revised inlining and emission behavior.

AI Tool Usage Disclosure

In compliance with the Bytecode Alliance AI Tool Use Policy: An AI assistant was used locally to assist with code inspection, running formatting/syntax checks, and drafting this summary. All code changes were reviewed and verified.

…lting load PC

- Treat interrupt_poll as a call-like non-inlined instruction in the inliner.
- Inhibit inlining of callees with interrupt_poll into non-tail callers.
- Fixup exception tables only for actual callable targets, skipping interrupt_poll.
- Mark InterruptPoll as a safepoint on x64 and aarch64.
- Emit user stack maps at the faulting load PC.
- Insert architecture-specific nop when consecutive call and poll stack maps share the same PC.
- Generalize write.rs to display user stack maps for all safepoints.

Fixes bytecodealliance#14926
@Shoryamishra61
Shoryamishra61 requested a review from a team as a code owner October 22, 2026 18:09
@Shoryamishra61
Shoryamishra61 requested review from alexcrichton and removed request for a team October 22, 2026 18:09
@github-actions github-actions Bot added cranelift Issues related to the Cranelift code generator cranelift:area:machinst Issues related to instruction selection and the new MachInst backend. cranelift:area:aarch64 Issues related to AArch64 backend. cranelift:area:x64 Issues related to x64 codegen labels Oct 22, 2026
Comment thread cranelift/codegen/src/inline.rs Outdated
/// `interrupt_poll` is only valid in functions that use the `tail` calling
/// convention (see the verifier), so inlining a callee that contains one into a
/// function with any other convention would produce invalid CLIF.
fn can_inline(caller: &ir::Function, callee: &ir::Function) -> bool {

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.

IIRC we've already got inlining heuristics elsewhere in Wasmtime which would cover things like this. Subjectively I'd say it's ok to drop this and leave this up to callers, and if the heuristics from Wasmtime are ever extracted we could perhaps consolidate with this.

Comment thread cranelift/codegen/src/inline.rs Outdated
Comment on lines +282 to +288
// `interrupt_poll` is classified as `is_call()` because it
// "continues execution elsewhere" (see its definition), but
// it is not an inlineable function call — it has no `FuncRef`
// and no external callee body. When a function containing a
// poll is inlined by `inline_one`, the instruction is cloned
// into the caller verbatim by the generic instruction-copying
// loop; no special action is needed here.

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.

It's ok to trim down verbose LLM comments like this. Basically this just needs to convey "InterruptPoll returns is_call(), but it doesn't participate in inlining"

// in release builds where the buffer's ordering assertion is
// compiled out.
if stack_map.is_some() && sink.last_user_stack_map_pc() == Some(sink.cur_offset()) {
sink.put4(0xd503201f);

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.

IIRC we've already got infrastructure in other places for whether or not metadata applies to after an instruction or that instruction itself (or something like that). For example I think MachDebugTagPos serves that purpose. Personally I think it would be better to improve stack map lookup to handle that as opposed to injecting otherwise-not-useful nops.

@cfallin

cfallin commented Oct 22, 2026

Copy link
Copy Markdown
Member

interrupt_poll has Opcode::is_call() == true

My intuition here is that this is probably the wrong decision, in hindsight: yes it can "do a call" in a sense, if a signal handler runs, but that's completely transparent. The "can call" opcode characteristic is really more relevant for things like inlining, as we see here, and in most ways the opcode is not a call (with args, with a callee, etc). So we should probably flip the condition here and then work through the implications, IMHO.

(I will take a more detailed look next week when I am fully back at the keyboard; I just wanted to leave this thought now as the discussion is open)

@alexcrichton
alexcrichton requested a review from cfallin October 23, 2026 15:38
@cfallin

cfallin commented Oct 26, 2026

Copy link
Copy Markdown
Member

Looking at this a little more, it remains my opinion that interrupt_poll should not be a "call". Some of the other fixes here are good though. @Shoryamishra61 can you address that concern by removing the "callness" of the opcode and then working through the implications?

(Also cc @erikrose)

@erikrose

Copy link
Copy Markdown
Contributor

Yes, we should hold loosely my choice to put .call() on there; it may be less transgressive without. This might also be an opportunity to enrich the documentation of call(), beyond "a call instruction". I struggled with what the right choice was. Thanks for having a second look!

@cfallin

cfallin commented Oct 29, 2026

Copy link
Copy Markdown
Member

@erikrose would you mind taking on the fix here, since the original creator of this PR hasn't responded for a week?

@Shoryamishra61

Copy link
Copy Markdown
Author

Apologies for the delayed response, @cfallin and @alexcrichton . I've been occupied with other commitments over the past week and wasn't able to follow up on the review discussions.

I'm still interested in seeing this through and addressing the feedback.

I understand the concern about treating interrupt_poll as a call, and I agree that removing its call classification is worth investigating. I'll revisit the affected inliner paths and verifier assumptions, simplify the comments highlighted in review, and look into the existing metadata infrastructure for handling stack map PCs without introducing unnecessary NOPs.

If the work hasn't already been handed over, I'd appreciate the opportunity to make these revisions. If @erikrose has already started, I'm happy to coordinate to avoid duplicating effort.

Thanks for the detailed feedback and your patience.

@cfallin

cfallin commented Oct 29, 2026

Copy link
Copy Markdown
Member

Ah, no worries @Shoryamishra61, I just wanted to make sure things were moving forward and didn't want to put any pressure on an external contributor (you don't owe us anything!) -- if you want to keep going on this, please do. I guess if you feel like you need to hand it off at any point, it would make sense to pass off to Erik. Either way we (as Cranelift maintainers) do want to see it resolved before declaring the MMU-interruption work done, since this is a bug, but that's not on you to ensure :-)

@Shoryamishra61

Copy link
Copy Markdown
Author

Pushed commit \920636a214\ addressing @cfallin's review:

  • Removed \Opcode::is_call\ classification from \interrupt_poll\ in \instructions.rs.
  • Simplified the inliner to copy \interrupt_poll\ as a regular instruction while attaching user stack maps via \opcode.is_safepoint().
  • Removed the artificial NOP padding from x64 and aarch64 code emission.
  • Updated the CLIF filetests to reflect the revised inlining and emission behavior.

@github-actions github-actions Bot added the cranelift:meta Everything related to the meta-language. label Nov 1, 2026

This branch has not been deployed

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

Labels

cranelift:area:aarch64 Issues related to AArch64 backend. cranelift:area:machinst Issues related to instruction selection and the new MachInst backend. cranelift:area:x64 Issues related to x64 codegen cranelift:meta Everything related to the meta-language. cranelift Issues related to the Cranelift code generator

Projects

None yet

Development

Successfully merging this pull request may close these issues.

interrupt_poll isn't handled by the inliner or by stack maps

4 participants