←back to thread

180 points xnacly | 6 comments | | HN request time: 0s | source | bottom
Show context
sparkie ◴[] No.44562104[source]
As an alternative to the computed gotos, you can use regular functions with the `[[musttail]]` attribute in Clang or GCC to achieve basically the same thing - the call in the tail position is replaced with a `jmp` instruction to the next function rather than to the label, and stack usage remains constant because the current frame is reutililzed for the called function. `musttail` requires that the calling function and callee have the same signature, and a prototype.

You'd replace the JUMP_TARGET macro:

    #define JUMP_TARGET goto *jump_table[(int32_t)l->input.p[l->pos]]
With:

    #ifdef __clang__
    #define musttail [[clang::musttail]]
    #elif __GNUC__
    #define musttail [[gnu::musttail]]
    #else
    #define musttail
    #endif
    #define JUMP_TARGET return musttail jump_table[(int32_t)l->input.p[l->pos]](l, a, out)
Then move the jump table out to the top level and replace each `&&` with `&`.

See diff (untested): https://www.diffchecker.com/V4yH3EyF/

This approach has the advantage that it will work everywhere and not only on compilers that support the computed gotos - it just won't optimize it on compilers that don't support `musttail`. (Though it has been proposed to standardize it in a future version of C).

It might also work better with code navigation tools that show functions, but not labels, and enables modularity as we can split rules over multiple translation units.

Performance wise should basically be the same - though it's been argued that it may do better in some cases because the compiler's register allocator doesn't do a great job in large functions with computed gotos - whereas in musttail approach each function is a smaller unit and optimized separately.

replies(4): >>44563630 #>>44567877 #>>44569195 #>>44572729 #
bestouff ◴[] No.44563630[source]
Can't wait for mandatory TCO coming to Rust. But it's not there yet. https://github.com/phi-go/rfcs/blob/guaranteed-tco/text/0000...
replies(1): >>44564605 #
sparkie ◴[] No.44564605[source]
Not sure I like the `become` keyword. Seems bizarre - someone encountering this word in code for the first time would have no idea what it's doing.

Why don't they just use `tailcall`? That would make it's obvious what it's doing because we've been using the term for nearly half a century, and the entire literature on the subject uses the term "tail call".

Even better would be to just automatically insert a tail call - like every other language that has supported tail calls for decades - provided the callee has the same signature as the caller. If it's undesirable because we want a stack trace, then instead have some keyword or attribute to suppress the tail call - such as `no_tail`, `nontail` or `donttail`.

Requiring tail calls to be marked will basically mean the optimization will be underutilized. Other than having a stack trace for debugging, there's basically no reason not to have the optimization on by default.

replies(5): >>44565254 #>>44566663 #>>44567717 #>>44568467 #>>44587305 #
lmm ◴[] No.44568467{3}[source]
> Even better would be to just automatically insert a tail call - like every other language that has supported tail calls for decades - provided the callee has the same signature as the caller.

I've used Scala for many years and concluded this was a mistake. It makes it too easy to accidentally rely on an optimisation and then break your code much later with a seemingly innocuous change. Better to make it explicit.

replies(1): >>44570323 #
1. Quekid5 ◴[] No.44570323{4}[source]
Just add the @tailrec annotation -- then the compiler will complain loudly if you break tail calls.
replies(1): >>44570442 #
2. lmm ◴[] No.44570442[source]
Yes, the problem is if you didn't add it but the compiler was still silently TCOing your function, until one day it doesn't.
replies(1): >>44586326 #
3. Quekid5 ◴[] No.44586326[source]
Sure, but if you're relying on the TCO, you kind of have to actually state that, regardless? (At least if you want to avoid accidental regressions.)

I don't see any way around having to state your intent (as a programmer) for this. It's just a semantic difference in behavior unless your Abstract Machine allows for just ignoring the possibility of stack overflow entirely.

replies(1): >>44588679 #
4. lmm ◴[] No.44588679{3}[source]
> I don't see any way around having to state your intent (as a programmer) for this.

I want a way to state that my intent is to not rely on the TCO. Even a second explicit annotation for "do not TCO this function" would be better than what Scala currently does (but frankly TCO is surprising enough to debug that I think it should be opt-in rather than opt-out).

replies(1): >>44598924 #
5. Quekid5 ◴[] No.44598924{4}[source]
> I want a way to state that my intent is to not rely on the TCO.

How would that work? You want a nontail keyword to indicate that intent explicitly?

I guess I could imagine a scenario where you want to de-optimize a TCO into a non-TC... but I mean... that's got to be rare enough to just not bother with?

EDIT: Also, this is the exact opposite of your comment which I replied to. Make up your mind on what you want and we can maybe find a way to achieve it

replies(1): >>44642872 #
6. lmm ◴[] No.44642872{5}[source]
> How would that work? You want a nontail keyword to indicate that intent explicitly?

Either that, or to not have TCO applied if I don't set a keyword (which I'd prefer).

> I guess I could imagine a scenario where you want to de-optimize a TCO into a non-TC... but I mean... that's got to be rare enough to just not bother with?

Sometimes I know that a given function will eventually need to be non-TC, in which case I want a way to make it non-TCO now. More commonly a junior team member just hasn't thought about TC-ness at all, in which case I'd rather the function not be TCOed and fail-fast than be TCOed until it isn't.

> EDIT: Also, this is the exact opposite of your comment which I replied to.

No it isn't. It's the detail of how it happens. If you use Scala at scale with a team that includes non-experts it will happen to you.