r/rust•
gopher9

The state of the procedural code generation in Rust

Long story short: it is impossible to generate reliable and fast code for a DFA recognizer in Rust.

The reason why it's impossible is simple: Rust supports neither goto nor tail call elimination. So it's impossible to represent a code with advanced control flow. And even though Rust has MIR which supports goto, it is unavailable from the procedural macros side.

The state of the art lexer for Rust, Logos uses tail calls to represent transitions between states (example). It quite fragile obviously, since there's no guarantee of tail call elimination in Rust (though LLVM can optimize it). But it seems to be the only way to achieve a reasonable performance.

It's quite unfortunate that I will probably have to go the same route for my own lexer generator as well.

Posted Jun 21, 2020, 12:22 PM UTC

310
dnew•

The normal solution to this is to have an enum with one alternative for each state. Then you write a loop that switches/matches on the enum value, and you goto by assigning the value matched upon on each loop, optionally with a continue to force the early restart. It's slightly less efficient in that you have an indirect vs direct branch for each time around the loop.

5
kennethuil•

It can be less efficient, but I fed some simple examples to godbolt and saw it generate direct jumps between states.

3
matu3ba•

https://github.com/rust-lang/rust/issues/28728#issuecomment-332581533 Shorter story: TCE does not work due to soundness problems in Rust (and LLVM). "this issue can only be solved when either rustc or LLVM can prove a pure function is total before using any relevant optimisations."

3
jschievinkrust•

Where is the connection between LLVM's issues around modeling termination and tail-call elimination?

2
matu3ba•

https://github.com/rust-lang/rfcs/issues/2691#issuecomment-486203569 LLVM can't emit tail calls for noreturn that enable TrapUnReachable.

https://releases.llvm.org/2.5/docs/LangRef.html#int_trap https://github.com/rust-lang/rust/pull/45920 The justification is to fix wrong loop behavior (removal of complete loops) while upholding optimisations.

However that breaks TCE.

1
jschievinkrust•

Ah, I see. Thanks for the explanation!

1
gopher9OP•

Interesting thread, thanks.

1
orangepantsman•

There are crates that can convert tail call functions into continuation passing form, that should be enough to ensure performance, right?

3
gopher9OP•

Don't these crates just use tramplining?

2
orangepantsman•

Yes

2