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

  1. Re-check the stack offset defined for the opcode being emitted (net effect should be pushes - pops, e.g. binary ops are -1)
  2. Trace the compile path with asm.log enabled (the assembler logs stack transitions) to find where depth goes negative
  3. 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

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


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/649366215a383dd8. Report an issue: GitHub.