vitessio/vitess · critical
negative stack position
Error message
negative stack position
What it means
The evalengine compiler tracks the virtual operand stack depth while emitting bytecode. adjustStack(offset) applies an instruction's net stack effect and panics if the running depth would go negative, meaning the compiler emitted more pops than pushes — a compiler bug, not a query error. The stack.max is also updated here for bytecode sizing.
Source
Thrown at go/vt/vtgate/evalengine/compiler_asm.go:90
}
}
func (asm *assembler) jumpFrom() *jump {
return &jump{from: len(asm.ins)}
}
func (asm *assembler) jumpDestination(jumps ...*jump) {
for _, j := range jumps {
if j != nil {
j.to = len(asm.ins)
}
}
}
func (asm *assembler) adjustStack(offset int) {
asm.stack.cur += offset
if asm.stack.cur < 0 {
panic("negative stack position")
}
if asm.stack.cur > asm.stack.max {
asm.stack.max = asm.stack.cur
}
if asm.log != nil {
asm.log.Stack(asm.stack.cur-offset, asm.stack.cur)
}
}
func (asm *assembler) emit(f frame, instruction string, args ...any) {
if asm.log != nil {
asm.log.Instruction(instruction, args...)
}
asm.ins = append(asm.ins, f)
}
func (asm *assembler) Add_dd() {
asm.adjustStack(-1)View on GitHub (pinned to 01a25a7d17)
Solutions
- Re-check the stack offset defined for the opcode being emitted (net effect should be pushes - pops, e.g. binary ops are -1)
- Trace the compile path with asm.log enabled (the assembler logs stack transitions) to find where depth goes negative
- Add a compiler unit test that asserts final stack depth is exactly 1 for the compiled expression
Example fix
// before asm.emit(Add_ii) asm.adjustStack(-2) // wrong: net effect of binary add is -1 // after asm.emit(Add_ii) asm.adjustStack(-1)
Defensive patterns
Strategy: validation
Validate before calling
// Go: assert opcode stack effect matches emission
if want := pushCount(op) - popCount(op); want != declaredOffset(op) {
return fmt.Errorf("opcode %v stack offset mismatch: got %d, declared %d", op, want, declaredOffset(op))
} Try / catch
func safeCompile(expr string) (prog *program, err error) {
defer func() {
if r := recover(); r != nil {
err = vterrors.Errorf(vtrpcpb.Code_INTERNAL, "compiler error: %v", r)
}
}()
return compile(expr)
} Prevention
- Define each opcode's stack effect in one place and derive adjustStack offsets from it
- Assert compiled programs end with stack depth exactly 1 in a compile test over a corpus of expressions
- Enable the assembler's stack logging when debugging emission changes
When it happens
Trigger: Emitting an instruction (e.g. Add_dd, Add_ii, BitOp_and_bb) with a wrong stack offset in a new opcode definition, or mismatched push/pop accounting when compiling a nested expression so pops exceed pushes.
Common situations: Contributing a new opcode or rewriting instruction emission in compiler_asm.go/compiler.go; a wrongly defined pop count (e.g. -2 offset for an instruction that actually pops 1).
Related errors
- bad unsigned integer type
- bad type aggregation for signed/unsigned types
- unreachable
- did not compile?
- unexpected Numeric type
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/649366215a383dd8.
Report an issue: GitHub.