Skip to content

interp: pass runtime values back to the caller - #5991

Draft
davecheney wants to merge 1 commit into
tinygo-org:devfrom
davecheney:interp-emit-more-at-runtime
Draft

davecheney wants to merge 1 commit into
tinygo-org:devfrom
davecheney:interp-emit-more-at-runtime

Conversation

@davecheney

@davecheney davecheney commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

When an instruction has an operand that is only known at runtime, interp emits it into runtime.initAll, but runAtRuntime can only emit a few opcodes. A ret of a runtime value rolls back the whole call, so a function returning an atomic load, a volatile register read or a global written at runtime is always called at runtime, even though everything else in it was evaluated. icmp, and, or, xor, the shifts, trunc and sext roll back in the same way.

Across stdlib.go and json.go on the host, cmd/maindec from github.com/davecheney/pdp11 on wasip1, and a net/http program on rp2350, ret caused 42% of 297 rollbacks and icmp another 25%.

This change returns a runtime value from ret to the caller, which already treats a runtime operand as a runtime instruction, and emits the other opcodes at runtime. The same programs roll back 151 times instead of 297. No binary grows, and cmd/maindec on wasip1 is 1.5% smaller.

A //go:noruntime function must still be evaluated completely, or it must survive as a call so that the noruntime check reports it. Interp now rolls back such a function if it would leave any instruction to run at runtime, including one emitted by a function it calls, instead of moving that work into runtime.initAll.

In the revert test, the first z.setArr call is now expanded as its comment says it can be, instead of becoming a call at runtime.

@davecheney davecheney added this to the 0.45 milestone Oct 22, 2026
@davecheney
davecheney force-pushed the interp-emit-more-at-runtime branch from a2e4ce3 to abc9e3a Compare October 22, 2026 13:16
When an instruction has an operand that is only known at runtime, interp emits it into runtime.initAll, but runAtRuntime can only emit a few opcodes. A ret of a runtime value rolls back the whole call, so a function returning an atomic load, a volatile register read or a global written at runtime is always called at runtime, even though everything else in it was evaluated. icmp, and, or, xor, the shifts, trunc and sext roll back in the same way.

Across stdlib.go and json.go on the host, cmd/maindec from github.com/davecheney/pdp11 on wasip1, and a net/http program on rp2350, ret caused 42% of 297 rollbacks and icmp another 25%.

This change returns a runtime value from ret to the caller, which already treats a runtime operand as a runtime instruction, and emits the other opcodes at runtime. The same programs roll back 151 times instead of 297. No binary grows, and cmd/maindec on wasip1 is 1.5% smaller.

A //go:noruntime function must still be evaluated completely, or it must survive as a call so that the noruntime check reports it. Interp now rolls back such a function if it would leave any instruction to run at runtime, including one emitted by a function it calls, instead of moving that work into runtime.initAll.

In the revert test, the first z.setArr call is now expanded as its comment says it can be, instead of becoming a call at runtime.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant