Skip to content

debug: XS stack-overflow trace instrumentation (ymax0 v320)#2

Draft
kriscendobot wants to merge 3 commits into
Agoricfrom
debug/xs-stack-overflow-trace
Draft

debug: XS stack-overflow trace instrumentation (ymax0 v320)#2
kriscendobot wants to merge 3 commits into
Agoricfrom
debug/xs-stack-overflow-trace

Conversation

@kriscendobot

Copy link
Copy Markdown
Owner

DRAFT — artifact preservation, not a merge candidate.

Captures the XS stack-overflow debug instrumentation developed live during the
ymax0 v320 value-stack-exhaustion investigation (garden issue agoric-labs#9), so the patch
never has to be re-derived from the issue thread again (it has been wiped by
redeploys twice).

What it instruments

The XS_STACK_OVERFLOW_EXIT arm of fxAbort() in
xsnap/sources/xsnapPlatform.c (plus #include <execinfo.h>). On a
stack-overflow abort it dumps, to inherited stderr:

  • the value-stack fill level (slots used of stackCount, free remaining);
  • the XS frame chain innermost-first — each frame's value-stack slot span and
    source line, capped at 600 frames;
  • an allocation-free native C backtrace (backtrace_symbols_fd; -rdynamic is
    already in the worker LINK_OPTIONS).

Why direct fprintf(stderr, …)

The xsnap worker compiles out the fxReport/console path via mxNoConsole, so
the usual report channel is unavailable at the abort point. The block is
allocation-free and therefore safe to run at exhaustion.

Key calibration result (from the investigation)

The real ymax0 importBundle overflows at only ~9 frames with the value stack
at 4096/4096 slots
(one anonymous activation ~2,588 slots, its callee ~1,238) —
wide value-stack exhaustion, not deep recursion. XS fixes stackCount at
4096 in xsnap-worker.c with no CLI override. A deep-recursion control
(f(100000)) hits ~394 frames at 4093/4096 slots.

Base is this fork's own Agoric branch (not agoric-labs). Companion build/run
methodology doc lives in kriscendobot/agoric-sdk.

Ref: garden issue agoric-labs#9.

Instrument the XS_STACK_OVERFLOW_EXIT arm of fxAbort() in xsnapPlatform.c so a
stack-overflow abort dumps, on inherited stderr:

  - the value-stack fill level (slots used of stackCount, and free),
  - the XS frame chain innermost-first, each frame's value-stack slot span and
    source line (capped at 600 frames), and
  - an allocation-free native C backtrace (backtrace_symbols_fd, requires the
    <execinfo.h> include added near the top; -rdynamic is already in the worker
    LINK_OPTIONS).

This was developed live during the ymax0 v320 investigation (garden issue agoric-labs#9) to
distinguish deep JS recursion from wide value-stack exhaustion. The conclusion:
the real ymax0 importBundle overflows at only ~9 frames with the value stack at
4096/4096 slots (one anonymous activation ~2,588 slots, its callee ~1,238) — wide
value-stack exhaustion, not deep recursion. XS fixes stackCount at 4096 in
xsnap-worker.c with no CLI override.

Output goes straight to stderr via fprintf because the worker compiles out the
fxReport/console path (mxNoConsole), so the usual report channel is unavailable
at this point. Debug-only; not intended to merge.
kriscendobot and others added 2 commits June 28, 2026 02:20
Evolve the XS_STACK_OVERFLOW_EXIT instrumentation (garden issue agoric-labs#9) so the
overflow trace names the responsible JavaScript calls and shows where the
JS stack crosses into native C, instead of leaving the widest frame
"(anonymous)".

Per-frame, the walk now prints:
  - kind: J (JS bytecode fn, runs inside fxRunID) vs C (host/native builtin,
    whose name resolves to a C symbol via dladdr) — the C rows are the literal
    JS<->C interleave points;
  - jsname: the qualified function name via fxBufferFrameName, the same routine
    the engine uses for Error.stack, so names corroborate with sources;
  - source: defining module path : current line.

The per-wide-frame (>200 slot) breakdown is likewise named, so slot
composition is attributable to a responsible call. The native C backtrace
header now explains the bytecode-VM interleave model (one fxRunID activation
per JS frame; the stacks meet at kind=C frames).

This commit also lands the value-stack kind histogram + per-wide-frame slot
breakdown from the prior investigation round, which had only ever lived in a
scratch worker and was never committed back to this branch.

Validated on-host: real ymax0 import overflows and the 2,588-slot wide frame
that was previously "(anonymous)" now names Array.prototype.flatMap (kind=C)
called from the SES compartmentImportNow/execute path; a trivial bundle still
imports clean (no spurious overflow). Allocation-free and stderr-only
(mxNoConsole), so still safe at exhaustion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The XS_STACK_OVERFLOW_EXIT frame walk printed env->value.environment.line
raw. XS keeps the line in 16 bits, so any line past 32767 (routine in a
large non-minified esbuild bundle — the ymax0 portfolio.contract bundle is
~43886 lines) read back as a negative txInteger, e.g. -27787 for line 37749.
Unwrap negatives by +65536 in both the per-frame walk and the wide-frame
breakdown so each overflow frame resolves to its real 1-based source line
(e.g. .../portfolio.contract.bundle.js:37749 = hex.js decodings flatMap).
Minified bundles stay small and are unaffected.

@kriskowal kriskowal left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests.

#include "xsAll.h"
#include "xsScript.h"
#include "xsSnapshot.h"
#include <execinfo.h> /* GARDEN INSTRUMENTATION (issue #9): native C backtrace at overflow */

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not need the comment.

source the defining module path : current line for that frame. */
fprintf(stderr, "depth\tslots\tkind\tjsname @ source:line\n");
while (aFrame) {
if (aDepth < 600) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Factor out magic number.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants