That was my intended interpretation. Not code size in terms of the size of the final executable, but code size in terms of the actual density of instructions emitted, which affects everything from pressure on instruction-cache density, to branch-predictor-table exhaustion, to the number of actual instructions executed.
I have to respectfully disagree. Well, maybe I shouldn’t have used non-local returns for returning tokens in my example (I was just being a bit too clever there). But if we keep this restricted to non-local returns on end-of-incomplete-buffer, and bad-utf8-sequence, then these are rare enough that the perf gain of removing numerous branches on the common path, and increasing code density, should yield significant benefits.
In a typical parse, a bad-utf8-sequence should NEVER happen, and typically halts the entire parse - it’s both an error condition and expected to happen only once per parse. With the end-of-incomplete-buffer situation, that should only happen once per buffer “update”, which can be tuned, but is typically several K in size. Typically with incremental parsing, re-entering the parser/tokenizer frequently is a bad idea, and we wait until we’ve reached end-of-stream or several kb of additional data before trying again.
Performance quibbles aside, though… the main reason I want this is because it expesses the intent of the code far better in the circumstances where it applies. Conceptually, I’m thinking to myself “ok, I’m writing next_char(), I see a bad UTF-8 sequence, I want to break to the start of next_token() and return an error”. Currently, I’m forced to implement this with disjoint-value wrapers and bubbling-up by adding wiring code at every call point leading up to the point-of-resume.