[graalvm-dev] [External] : Truffle Bytecode DSL production cutover with continuations and structured control flow

protos at guillermomolina.com protos at guillermomolina.com
Fri Sep 11 16:32:15 UTC 2026


Hi Christian,

Thanks, this is very helpful.

On the ControlFlowException point: no, Protos does not need stack-frame 
capture for those bridged internal transfers.

Guest Error is a separate lane and remains an AbstractTruffleException. 
The transfers currently going through the Bytecode EH bridge are only 
non-local return and cooperative cancellation. In both cases we preserve 
the exact original ControlFlowException and unwrap it again at the 
owning call/resume boundary.

So the AbstractTruffleException envelope currently exists only to let 
those transfers cross Bytecode EH regions so that structured cleanup can 
run. We do not otherwise need guest exception stack information for it.

An opt-in mechanism allowing ControlFlowException to participate 
directly in the relevant Bytecode EH machinery would therefore fit 
Protos very well, and I would prefer that over the current envelope when 
available.

Your comments about continuation locals are also particularly useful. We 
will add explicit IGV inspection for:

- ordinary hot continuation-enabled roots that do not suspend;
- resumed Future.value() paths;
- ensure cleanup that itself suspends and resumes;
- continuation-local usage and live operand-stack values at resume 
points;
- whether continuation targets are being inlined as expected.

The cancellation point also matches our intent. Cancellation is 
cooperative and language-controlled rather than host-thread 
cancellation; it participates in structured unwind and cleanup, so we 
need to retain that semantic control.

I'll also join the #truffle Slack channel. As we complete the production 
cutover and get optimizing-runtime measurements, I'm happy to share any 
Bytecode DSL feedback or minimized cases that look useful.

Thanks again,

Guillermo Molina
Protos
https://github.com/guillermomolina/protos

On 2026-09-11 17:33, Christian Humer wrote:

> Hi Guillermo,
> 
> Always glad to learn about a new bytecode DSL user. If you also have 
> some general feedback, that is always appreciated.
> 
> The separation between suspension and unwinding sounds sensible. 
> Generally, the intended direction is to avoid ControlFlowException for 
> ordinary control flow: it was primarily a workaround for AST 
> interpreters, whereas bytecode can usually express that control flow 
> directly. From you docs I read that you explicitly decided to not use 
> Truffle's safe point mechanism for cancellation. That can be a sensible 
> decision if you want your users to recover from cancellation.
> 
> Your cross-call non-local returns are one of the rarer cases where CFE 
> can still make sense. Desugaring them into ordinary returns would 
> require propagating an explicit transfer through intermediate calls. 
> The conversion to AbstractTruffleException to run ensure cleanup is not 
> ideal, though we should probably support CFEs directly in the relevant 
> Bytecode exception-handling machinery as an opt-in. This would allow 
> you handle CFEs like truffle exceptions but don't pay the cost for 
> capturing stack frames. Or is this something you need anyway?
> 
> For continuation-enabled roots, the main performance caveat is that 
> operand-stack values are virtualized after runtime compilation, but 
> continuation locals are not, making local accesses slower. Entering a 
> continuation with many live stack values also has a cost to bring those 
> values into compiled execution and virtualize them. That of course only 
> applies if the continuation target is not inlined.
> 
> Getting a continuation-enabled bytecode interpreter right for partial 
> evaluation is tricky, but the Bytecode DSL handles that machinery. So 
> no problems are expected here. I would still inspect IGV graphs, 
> particularly for resumed paths and cleanup that itself suspends to 
> double check everything is as expected.
> 
> You're also welcome to also join the community Slack to discuss this 
> further directly. (#truffle channel)
> https://www.graalvm.org/slack-invitation/
> 
> Hope this was helpful.
> 
> Christian
> 
>> On 11 Sep 2026, at 13:26, Guillermo Molina 
>> <protos at guillermomolina.com> wrote:
>> 
>> Hi,
>> 
>> I'm Guillermo Adri?n Molina, author of Protos, a prototype-based
>> language implemented on Truffle 25.3.4.1.
>> 
>> I originally intended to ask about how to represent dynamic guest
>> control flow around Bytecode DSL continuations, but the implementation
>> has progressed quite a bit since then, so I thought a more useful
>> question would be about the final production cutover and any pitfalls 
>> we
>> may be missing.
>> 
>> Protos is migrating from an AST-based cooperative replay mechanism to 
>> a
>> Bytecode DSL backend.
>> 
>> The Bytecode path now supports:
>> 
>> * source-backed closure calls composed across Bytecode roots;
>> * Task/Future suspension and resumption through ContinuationResult,
>> without replaying completed guest effects;
>> * non-local return, guest Error propagation/handlers, suspendible
>> ensure, cancellation and while;
>> * debugger scopes through TagTreeNode / NodeLibrary;
>> * StatementTag/CallTag source identity and logical BytecodeLocation
>> preservation across suspension/resume.
>> 
>> The control-flow model we ended up with is:
>> 
>> * normal guest values remain ordinary values;
>> * suspension is distinct from unwind and is represented by the
>> Bytecode continuation mechanism;
>> * guest Error remains a guest-exception lane;
>> * non-local return and cancellation are rare internal control-flow
>> transfers;
>> * a narrow interceptControlFlowException bridge allows those internal
>> transfers to cross Bytecode EH regions so that structured cleanup can
>> run, while retaining the exact original transfer;
>> * pending transfers live in resumable Bytecode/frame state while
>> cleanup executes, including when cleanup itself suspends;
>> * there is no replay tape, global continuation registry, semantic
>> ThreadLocal, or parked thread on the Bytecode path.
>> 
>> At this point the remaining work is mostly production cutover: 
>> complete
>> a couple of hosting/native boundaries, switch normal
>> TruffleLanguage.parse() execution to the Bytecode backend, retire the
>> old replay path, and then enable/measure the optimizing Truffle 
>> runtime
>> in ordinary execution.
>> 
>> I would be interested in feedback on three points:
>> 
>> * Does the separation above -- Bytecode continuations for suspension,
>> structured Bytecode EH/finally for unwind, and a narrow
>> ControlFlowException bridge for internal transfers -- match the 
>> intended
>> direction of the Bytecode DSL, or is there another pattern you would
>> recommend for a production language?
>> * Are there known caveats when making a continuation-enabled Bytecode
>> DSL root the normal production backend, particularly for runtime
>> compilation / partial evaluation? For example, does enabling yield 
>> have
>> meaningful cost for roots that never actually suspend?
>> * Are there specific diagnostics or invariants you recommend checking
>> during the final AST-to-Bytecode cutover to make sure resumed paths,
>> structured control flow, source metadata and runtime compilation 
>> remain
>> optimization-friendly?
>> 
>> The project is guillermomolina/protos on GitHub if concrete
>> implementation details are useful.
>> 
>> Thanks,
>> 
>> Guillermo Adri?n Molina
>> Protos
>> -------------- next part --------------
>> An HTML attachment was scrubbed...
>> URL: 
>> <http://oss.oracle.com/pipermail/graalvm-dev/attachments/20260911/90d5f0f2/attachment.htm>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://oss.oracle.com/pipermail/graalvm-dev/attachments/20260911/5587462b/attachment.htm>


More information about the GraalVM-Dev mailing list