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

Christian Humer christian.humer at oracle.com
Fri Sep 11 15:33:51 UTC 2026


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/074faece/attachment-0001.htm>


More information about the GraalVM-Dev mailing list