diff --git a/hbrt/ops_compare.go b/hbrt/ops_compare.go index da7a483..0889d90 100644 --- a/hbrt/ops_compare.go +++ b/hbrt/ops_compare.go @@ -58,13 +58,30 @@ func (t *Thread) Less() { // LessEqual pops two values, pushes boolean result. func (t *Thread) LessEqual() { - b := t.pop() - a := t.pop() + t.sp -= 2 + a := t.stack[t.sp] + b := t.stack[t.sp+1] + t.stack[t.sp+1] = cachedNil + // Fast path: Int <= Int (most common in FOR loops) + if a.Type() == tInt && b.Type() == tInt { + if int64(a.scalar) <= int64(b.scalar) { + t.stack[t.sp] = cachedTrue + } else { + t.stack[t.sp] = cachedFalse + } + t.sp++ + return + } cmp, ok := valueCompare(a, b) if !ok { panic(t.argError("<=", a, b)) } - t.push(MakeBool(cmp <= 0)) + if cmp <= 0 { + t.stack[t.sp] = cachedTrue + } else { + t.stack[t.sp] = cachedFalse + } + t.sp++ } // Greater pops two values, pushes boolean result. @@ -93,11 +110,19 @@ func (t *Thread) GreaterEqual() { // Not negates the boolean value on top of stack. func (t *Thread) Not() { - a := t.pop() - if !a.IsLogical() { - panic(t.argError(".NOT.", a)) + t.sp-- + a := t.stack[t.sp] + // Fast path: logical not (most common — DO WHILE !EOF()) + if a.Type() == tLogical { + if a.scalar != 0 { + t.stack[t.sp] = cachedFalse + } else { + t.stack[t.sp] = cachedTrue + } + t.sp++ + return } - t.push(MakeBool(!a.AsBool())) + panic(t.argError(".NOT.", a)) } // And pops two values, pushes logical AND. @@ -136,11 +161,15 @@ func (t *Thread) InString() { // Used by generated code for IF/WHILE conditions. // Harbour: hb_xvmPopLogical func (t *Thread) PopLogical() bool { - v := t.pop() - if !v.IsLogical() { - panic(t.argError("logical", v)) + t.sp-- + v := t.stack[t.sp] + t.stack[t.sp] = cachedNil + // Fast path: check type tag directly (avoid method call overhead) + if v.Type() == tLogical { + return v.scalar != 0 } - return v.AsBool() + // Harbour: non-logical values in condition → type error + panic(t.argError("logical", v)) } // --- Optimized comparison (used by generated code) --- diff --git a/hbrt/thread.go b/hbrt/thread.go index 14f58e7..624be00 100644 --- a/hbrt/thread.go +++ b/hbrt/thread.go @@ -123,12 +123,9 @@ func (t *Thread) push(v Value) { } func (t *Thread) pop() Value { - if t.sp <= 0 { - panic(t.runtimeError("stack underflow")) - } t.sp-- v := t.stack[t.sp] - t.stack[t.sp] = MakeNil() // help GC + t.stack[t.sp] = cachedNil // help GC (no alloc) return v } diff --git a/hbrt/value.go b/hbrt/value.go index 5e3563b..8b62dcc 100644 --- a/hbrt/value.go +++ b/hbrt/value.go @@ -96,20 +96,41 @@ func (v Value) IsDateTime() bool { t := v.Type(); return t == tDate || t == tTim // --- Scalar constructors (no heap allocation) --- -func MakeNil() Value { - return Value{info: makeInfo(tNil, 0, 0)} -} +// Pre-cached common values — zero allocation for hot paths. +var ( + cachedNil = Value{info: makeInfo(tNil, 0, 0)} + cachedTrue = Value{scalar: 1, info: makeInfo(tLogical, 0, 0)} + cachedFalse = Value{scalar: 0, info: makeInfo(tLogical, 0, 0)} + cachedInt0 = Value{scalar: 0, info: makeInfo(tInt, 1, 0)} + cachedInt1 = Value{scalar: 1, info: makeInfo(tInt, 1, 0)} +) + +func MakeNil() Value { return cachedNil } func MakeBool(b bool) Value { - var d uint64 if b { - d = 1 + return cachedTrue } - return Value{scalar: d, info: makeInfo(tLogical, 0, 0)} + return cachedFalse } // MakeInt creates an integer Value with display width. +// Pre-cached small integers 0-255 (avoids intExpLen loop + allocation per call). +var smallInts [256]Value + +func init() { + for i := range smallInts { + smallInts[i] = Value{ + scalar: uint64(int64(i)), + info: makeInfo(tInt, uint32(intExpLen(int64(i))), 0), + } + } +} + func MakeInt(v int) Value { + if v >= 0 && v < 256 { + return smallInts[v] + } return Value{ scalar: uint64(int64(v)), info: makeInfo(tInt, uint32(intExpLen(int64(v))), 0),