Footgun in labelled switch

Today I changed the bytecode interpreter main loop of my toy language “moin" from a classic while true / switch to a labelled switch.

However, this introduced a bug.

Before, I had

while(true) {
    opcode = frame.readOpCode();
    switch(opcode) { … }
}

(sry for the formatting, doesn’t really work on my mobile)

Inside some of the switch prongs, I used opcode to decide between instructions with short operands (1 byte) and long operands (3 bytes).

Changing this construct to a labelled switch, basically means to add continue :vmloop frame.readOpCode() at the end of each switch prong.

However, the opcode variable which is the original switch expression is still there, but no longer valid inside the prongs, it still contains the very first opcode.

Maybe there shouldn’t be an expression at all in a labelled switch?

So it’s a bit like what I had here?
https://codeberg.org/ziglang/zig/issues/31002

You can kickstart the vmloop with something like vmloop: switch(frame.readOpCode())

Yes, ofc, that’s what I did after finding the reason for the bug.

BTW the labelled switch resulted in ~ 5% performance improvement.

1 Like

That would make many idioms not work, e.g grep the Zig compiler code base for : switch (

Yeah I noticed similar improvements, until the opcode set grew (or the prongs did) and I had to revert back to a mix of loop and labelled switch. It’s hard to please the LLVM opt pipeline, so I gate vmloop changes with performance regression tests.

The foot gun, when converting an existing conventional while/switch to a labelled switch, for me, was this:

After performing the obvious changes, I also had to change occurrences of `opcode` in the switch prongs.

The compiler does not (can not) help here.

The expression in parens after the switch keyword is only for the start of the loop.

Probably I wouldn’t have stumbled over this if I had created the labelled switch from scratch instead of adapting existing code.

See the changes in moin/src/vm.zig here:

https://codeberg.org/tumirnix/exla/commit/7b8395fafd97e5da218b9be28f98547d3c0afa07#diff-fac90981ba8e2a049d86d785f6eb28e22c93248b

I had to use |op|in the prongs instead of opcode.

A quick, powerful, and basically language-agnostic[1] protip for this kind of refactor:

Rename the variable above the switch statement to e.g. opcode_ temporarily. But not with the help of an LSP, just manually at the declaration site. This turns all references to it into compiler errors, so you can dig through them one-by-one and fix them as needed, without worrying that you’ll miss an occurrence. Footgun eliminated by making the compiler help you. :slight_smile:

Compiling with zig build --watch --prominent-compile-errors makes this sort of thing a breeze.


  1. Except for languages that don’t require variable declarations before use, like Python or non-strict JavaScript. ↩︎

1 Like