Fix stack overflow in no-musttail fallback for direct continuations

The no-musttail trampoline (063872e) only rerouted keepGoing dispatch.
Continuations that tail-call each other directly still recursed on the C
stack when MUSTTAIL is a no-op, so deep input overflowed it -- nested
arrays via n_value <-> n_array2, and runs of string escapes via
n_string2 -> n_string2 (the latter isn't even bounded by stackSize).

Add a TAILCALL(fn) macro: a direct musttail tail call when available,
otherwise a bounce to the parse() trampoline (which re-dispatches
continuations[top()] == fn, valid because fn's symbol is already on top
of the stack at every site). Route the 10 direct continuation tail calls
through it. The musttail build is unchanged; the fallback no longer
recurses without bound.
This commit is contained in:
2026-06-15 00:53:50 -04:00
parent 4bb13a23ff
commit 4301351042
+24 -10
View File
@@ -161,6 +161,18 @@ struct Parser3 {
#endif #endif
}; };
// Transfer control to continuation `fn`, whose Symbol must already be on top of
// the stack. With musttail this is a direct tail call (fast path). Without it,
// a direct call would recurse on the C stack (e.g. n_value -> n_array2 ->
// n_value for nested arrays, or n_string2 -> n_string2 for runs of escapes) and
// overflow on deep input; instead we bounce to the parse() trampoline, which
// re-dispatches continuations[top()] == fn without growing the stack.
#ifdef HAS_MUSTTAIL
#define TAILCALL(fn) MUSTTAIL return fn(self, buf, bufEnd)
#else
#define TAILCALL(fn) return Parser3::keepGoing(self, buf, bufEnd)
#endif
inline PRESERVE_NONE ContinuationStatus skipWhitespace(char *&buf, inline PRESERVE_NONE ContinuationStatus skipWhitespace(char *&buf,
char *bufEnd) { char *bufEnd) {
constexpr int kStride = 4; constexpr int kStride = 4;
@@ -306,7 +318,7 @@ inline PRESERVE_NONE ContinuationStatus n_value(Parser3 *self, char *buf,
if (buf == bufEnd) { if (buf == bufEnd) {
return WeaselJson_AGAIN; return WeaselJson_AGAIN;
} }
MUSTTAIL return n_object2(self, buf, bufEnd); TAILCALL(n_object2);
case '[': case '[':
self->callbacks->on_begin_array(self->userdata); self->callbacks->on_begin_array(self->userdata);
++buf; ++buf;
@@ -315,7 +327,7 @@ inline PRESERVE_NONE ContinuationStatus n_value(Parser3 *self, char *buf,
if (buf == bufEnd) { if (buf == bufEnd) {
return WeaselJson_AGAIN; return WeaselJson_AGAIN;
} }
MUSTTAIL return n_array2(self, buf, bufEnd); TAILCALL(n_array2);
case '"': case '"':
++buf; ++buf;
self->dataBegin = self->writeBuf = buf; self->dataBegin = self->writeBuf = buf;
@@ -325,7 +337,7 @@ inline PRESERVE_NONE ContinuationStatus n_value(Parser3 *self, char *buf,
if (buf == bufEnd) { if (buf == bufEnd) {
return WeaselJson_AGAIN; return WeaselJson_AGAIN;
} }
MUSTTAIL return n_string2(self, buf, bufEnd); TAILCALL(n_string2);
case '0': case '0':
case '1': case '1':
case '2': case '2':
@@ -344,7 +356,7 @@ inline PRESERVE_NONE ContinuationStatus n_value(Parser3 *self, char *buf,
if (buf == bufEnd) { if (buf == bufEnd) {
return WeaselJson_AGAIN; return WeaselJson_AGAIN;
} }
MUSTTAIL return n_number(self, buf, bufEnd); TAILCALL(n_number);
case 't': case 't':
++buf; ++buf;
self->pop(); self->pop();
@@ -432,7 +444,7 @@ inline PRESERVE_NONE ContinuationStatus n_object2(Parser3 *self, char *buf,
if (buf == bufEnd) { if (buf == bufEnd) {
return WeaselJson_AGAIN; return WeaselJson_AGAIN;
} }
MUSTTAIL return n_string2(self, buf, bufEnd); TAILCALL(n_string2);
default: default:
[[unlikely]] return WeaselJson_REJECT; [[unlikely]] return WeaselJson_REJECT;
} }
@@ -478,7 +490,7 @@ inline PRESERVE_NONE ContinuationStatus n_object3(Parser3 *self, char *buf,
if (buf == bufEnd) { if (buf == bufEnd) {
return WeaselJson_AGAIN; return WeaselJson_AGAIN;
} }
MUSTTAIL return n_string2(self, buf, bufEnd); TAILCALL(n_string2);
default: default:
[[unlikely]] return WeaselJson_REJECT; [[unlikely]] return WeaselJson_REJECT;
} }
@@ -506,7 +518,7 @@ inline PRESERVE_NONE ContinuationStatus n_array2(Parser3 *self, char *buf,
if (auto s = self->push({N_VALUE, N_ARRAY3})) { if (auto s = self->push({N_VALUE, N_ARRAY3})) {
return s; return s;
} }
MUSTTAIL return n_value(self, buf, bufEnd); TAILCALL(n_value);
} }
} }
@@ -536,7 +548,7 @@ inline PRESERVE_NONE ContinuationStatus n_array3(Parser3 *self, char *buf,
if (buf == bufEnd) { if (buf == bufEnd) {
return WeaselJson_AGAIN; return WeaselJson_AGAIN;
} }
MUSTTAIL return n_value(self, buf, bufEnd); TAILCALL(n_value);
default: default:
[[unlikely]] return WeaselJson_REJECT; [[unlikely]] return WeaselJson_REJECT;
} }
@@ -561,7 +573,7 @@ inline PRESERVE_NONE ContinuationStatus n_string(Parser3 *self, char *buf,
if (buf == bufEnd) { if (buf == bufEnd) {
return WeaselJson_AGAIN; return WeaselJson_AGAIN;
} }
MUSTTAIL return n_string2(self, buf, bufEnd); TAILCALL(n_string2);
} }
inline int32_t read4_hex(const char *buf) { inline int32_t read4_hex(const char *buf) {
@@ -664,7 +676,7 @@ inline PRESERVE_NONE ContinuationStatus n_string2(Parser3 *self, char *buf,
self->flushString(false, buf); self->flushString(false, buf);
return WeaselJson_AGAIN; return WeaselJson_AGAIN;
} }
MUSTTAIL return n_string2(self, buf, bufEnd); TAILCALL(n_string2);
} }
default: default:
[[unlikely]] return WeaselJson_REJECT; [[unlikely]] return WeaselJson_REJECT;
@@ -1072,4 +1084,6 @@ inline PRESERVE_NONE ContinuationStatus Parser3::keepGoing(Parser3 *self,
#endif #endif
} }
#undef TAILCALL
} // namespace parser3 } // namespace parser3