Apply the sp-rewrite shape to the three binary arithmetic ops. The tInt==tInt fast branch reads scalar directly (skips the AsNumInt method) so the hot path is int64 ops + an overflow check; mixed-type branches keep AsNumDouble unchanged. PRG tight loops (FOR counter, SUM accumulators outside SQL aggregate path) skip one cachedNil store and two bounds-check sequences per op. Verification - go test ./... ALL PASS - FiveSql2 test_sql1999 43/43 - tests/compat_harbour 56/56 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
403 lines
8.8 KiB
Go
403 lines
8.8 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// Arithmetic operations for the Five runtime.
|
|
// Implements Harbour-compatible type promotion, overflow detection,
|
|
// and decimal precision propagation rules.
|
|
//
|
|
// See docs/harbour-type-system-analysis.md Section 5 for details.
|
|
package hbrt
|
|
|
|
import "math"
|
|
|
|
// Plus pops two values, pushes their sum.
|
|
// Harbour: hb_vmPlus (hvm.c:3285)
|
|
//
|
|
// Type rules:
|
|
// NumInt + NumInt -> NumInt (overflow -> Double)
|
|
// Numeric + Numeric -> Double
|
|
// String + String -> String (concatenation)
|
|
// Date + Numeric -> Date
|
|
// Timestamp + Numeric -> Timestamp
|
|
func (t *Thread) Plus() {
|
|
t.sp -= 2
|
|
a := t.stack[t.sp]
|
|
b := t.stack[t.sp+1]
|
|
t.stack[t.sp+1] = cachedNil
|
|
dst := &t.stack[t.sp]
|
|
|
|
// Fast path: Int + Int (tInt tag — skip tLong to stay branch-light)
|
|
if a.Type() == tInt && b.Type() == tInt {
|
|
an, bn := int64(a.scalar), int64(b.scalar)
|
|
r := an + bn
|
|
if (bn >= 0 && r >= an) || (bn < 0 && r < an) {
|
|
*dst = MakeNumInt(r)
|
|
} else {
|
|
*dst = MakeDoubleAuto(float64(an) + float64(bn))
|
|
}
|
|
t.sp++
|
|
return
|
|
}
|
|
|
|
if a.IsNumeric() && b.IsNumeric() {
|
|
ad, bd := a.AsNumDouble(), b.AsNumDouble()
|
|
dec := maxDec(a.Decimal(), b.Decimal())
|
|
*dst = MakeDouble(ad+bd, 255, dec)
|
|
t.sp++
|
|
return
|
|
}
|
|
|
|
if a.IsString() && b.IsString() {
|
|
*dst = MakeString(a.AsString() + b.AsString())
|
|
t.sp++
|
|
return
|
|
}
|
|
|
|
if a.IsDate() && b.IsNumeric() {
|
|
*dst = MakeDate(a.AsJulian() + int64(b.AsNumDouble()))
|
|
t.sp++
|
|
return
|
|
}
|
|
if a.IsNumeric() && b.IsDate() {
|
|
*dst = MakeDate(int64(a.AsNumDouble()) + b.AsJulian())
|
|
t.sp++
|
|
return
|
|
}
|
|
|
|
if a.IsTimestamp() && b.IsNumeric() {
|
|
days := int64(b.AsNumDouble())
|
|
frac := b.AsNumDouble() - float64(days)
|
|
ms := int32(frac * 86400000.0)
|
|
newJulian := a.AsJulian() + days
|
|
newTime := a.AsTimeMs() + ms
|
|
if newTime >= 86400000 {
|
|
newJulian++
|
|
newTime -= 86400000
|
|
} else if newTime < 0 {
|
|
newJulian--
|
|
newTime += 86400000
|
|
}
|
|
*dst = MakeTimestamp(newJulian, newTime)
|
|
t.sp++
|
|
return
|
|
}
|
|
|
|
panic(t.argError("+", a, b))
|
|
}
|
|
|
|
// Minus pops two values, pushes their difference.
|
|
// Harbour: hb_vmMinus (hvm.c:3401)
|
|
func (t *Thread) Minus() {
|
|
t.sp -= 2
|
|
a := t.stack[t.sp]
|
|
b := t.stack[t.sp+1]
|
|
t.stack[t.sp+1] = cachedNil
|
|
dst := &t.stack[t.sp]
|
|
|
|
if a.Type() == tInt && b.Type() == tInt {
|
|
an, bn := int64(a.scalar), int64(b.scalar)
|
|
r := an - bn
|
|
if (bn <= 0 && r >= an) || (bn > 0 && r < an) {
|
|
*dst = MakeNumInt(r)
|
|
} else {
|
|
*dst = MakeDoubleAuto(float64(an) - float64(bn))
|
|
}
|
|
t.sp++
|
|
return
|
|
}
|
|
|
|
if a.IsNumeric() && b.IsNumeric() {
|
|
ad, bd := a.AsNumDouble(), b.AsNumDouble()
|
|
dec := maxDec(a.Decimal(), b.Decimal())
|
|
*dst = MakeDouble(ad-bd, 255, dec)
|
|
t.sp++
|
|
return
|
|
}
|
|
|
|
if a.IsDate() && b.IsDate() {
|
|
*dst = MakeLong(a.AsJulian() - b.AsJulian())
|
|
t.sp++
|
|
return
|
|
}
|
|
|
|
if a.IsDate() && b.IsNumeric() {
|
|
*dst = MakeDate(a.AsJulian() - int64(b.AsNumDouble()))
|
|
t.sp++
|
|
return
|
|
}
|
|
|
|
if a.IsTimestamp() && b.IsTimestamp() {
|
|
dayDiff := a.AsJulian() - b.AsJulian()
|
|
timeDiff := a.AsTimeMs() - b.AsTimeMs()
|
|
if timeDiff != 0 {
|
|
*dst = MakeDoubleAuto(float64(dayDiff) + float64(timeDiff)/86400000.0)
|
|
} else {
|
|
*dst = MakeLong(dayDiff)
|
|
}
|
|
t.sp++
|
|
return
|
|
}
|
|
|
|
panic(t.argError("-", a, b))
|
|
}
|
|
|
|
// Mult pops two values, pushes their product.
|
|
// Harbour: hb_vmMult (hvm.c:3510)
|
|
// Decimal rule: dec = dec1 + dec2
|
|
func (t *Thread) Mult() {
|
|
t.sp -= 2
|
|
a := t.stack[t.sp]
|
|
b := t.stack[t.sp+1]
|
|
t.stack[t.sp+1] = cachedNil
|
|
dst := &t.stack[t.sp]
|
|
|
|
if a.Type() == tInt && b.Type() == tInt {
|
|
an, bn := int64(a.scalar), int64(b.scalar)
|
|
if an == 0 || bn == 0 {
|
|
*dst = MakeNumInt(0)
|
|
t.sp++
|
|
return
|
|
}
|
|
r := an * bn
|
|
if r/an == bn {
|
|
*dst = MakeNumInt(r)
|
|
} else {
|
|
*dst = MakeDoubleAuto(float64(an) * float64(bn))
|
|
}
|
|
t.sp++
|
|
return
|
|
}
|
|
|
|
if a.IsNumeric() && b.IsNumeric() {
|
|
ad, bd := a.AsNumDouble(), b.AsNumDouble()
|
|
dec := a.Decimal() + b.Decimal()
|
|
if dec > 255 {
|
|
dec = 255
|
|
}
|
|
*dst = MakeDouble(ad*bd, 255, dec)
|
|
t.sp++
|
|
return
|
|
}
|
|
|
|
panic(t.argError("*", a, b))
|
|
}
|
|
|
|
// Divide pops two values, pushes the quotient.
|
|
// Harbour: hb_vmDivide (hvm.c:3546)
|
|
// Always returns Double. Division by zero -> runtime error.
|
|
func (t *Thread) Divide() {
|
|
b := t.pop()
|
|
a := t.pop()
|
|
|
|
if a.IsNumeric() && b.IsNumeric() {
|
|
bd := b.AsNumDouble()
|
|
if bd == 0 {
|
|
panic(t.divisionByZero())
|
|
}
|
|
ad := a.AsNumDouble()
|
|
t.push(MakeDoubleAuto(ad / bd))
|
|
return
|
|
}
|
|
|
|
panic(t.argError("/", a, b))
|
|
}
|
|
|
|
// Modulus pops two values, pushes the remainder.
|
|
// Harbour: hb_vmModulus (hvm.c:3608)
|
|
// Always returns Double.
|
|
func (t *Thread) Modulus() {
|
|
b := t.pop()
|
|
a := t.pop()
|
|
|
|
if a.IsNumeric() && b.IsNumeric() {
|
|
bd := b.AsNumDouble()
|
|
if bd == 0 {
|
|
panic(t.divisionByZero())
|
|
}
|
|
ad := a.AsNumDouble()
|
|
t.push(MakeDoubleAuto(math.Mod(ad, bd)))
|
|
return
|
|
}
|
|
|
|
panic(t.argError("%", a, b))
|
|
}
|
|
|
|
// Power pops two values, pushes base^exponent.
|
|
// Harbour: hb_vmPower
|
|
// Always returns Double.
|
|
func (t *Thread) Power() {
|
|
b := t.pop()
|
|
a := t.pop()
|
|
|
|
if a.IsNumeric() && b.IsNumeric() {
|
|
ad, bd := a.AsNumDouble(), b.AsNumDouble()
|
|
t.push(MakeDoubleAuto(math.Pow(ad, bd)))
|
|
return
|
|
}
|
|
|
|
panic(t.argError("**", a, b))
|
|
}
|
|
|
|
// Negate negates the top of stack.
|
|
// Harbour: hb_vmNegate
|
|
func (t *Thread) Negate() {
|
|
a := t.pop()
|
|
|
|
if a.IsNumInt() {
|
|
t.push(MakeNumInt(-a.AsNumInt()))
|
|
return
|
|
}
|
|
if a.IsDouble() {
|
|
t.push(MakeDouble(-a.AsDouble(), a.Length(), a.Decimal()))
|
|
return
|
|
}
|
|
|
|
panic(t.argError("negate", a))
|
|
}
|
|
|
|
// Inc increments the top of stack by 1.
|
|
// Harbour: hb_vmInc
|
|
func (t *Thread) Inc() {
|
|
p := t.peekPtr()
|
|
if p.IsNumInt() {
|
|
*p = MakeNumInt(p.AsNumInt() + 1)
|
|
return
|
|
}
|
|
if p.IsDouble() {
|
|
*p = MakeDouble(p.AsDouble()+1, p.Length(), p.Decimal())
|
|
return
|
|
}
|
|
panic(t.argError("++", *p))
|
|
}
|
|
|
|
// Dec decrements the top of stack by 1.
|
|
// Harbour: hb_vmDec
|
|
func (t *Thread) Dec() {
|
|
p := t.peekPtr()
|
|
if p.IsNumInt() {
|
|
*p = MakeNumInt(p.AsNumInt() - 1)
|
|
return
|
|
}
|
|
if p.IsDouble() {
|
|
*p = MakeDouble(p.AsDouble()-1, p.Length(), p.Decimal())
|
|
return
|
|
}
|
|
panic(t.argError("--", *p))
|
|
}
|
|
|
|
// --- Optimized operations (used by generated code) ---
|
|
|
|
// AddInt adds an integer constant to the top of stack.
|
|
// Harbour: hb_xvmAddInt
|
|
func (t *Thread) AddInt(n int64) {
|
|
p := t.peekPtr()
|
|
if p.IsNumInt() {
|
|
an := p.AsNumInt()
|
|
r := an + n
|
|
if (n >= 0 && r >= an) || (n < 0 && r < an) {
|
|
*p = MakeNumInt(r)
|
|
} else {
|
|
*p = MakeDoubleAuto(float64(an) + float64(n))
|
|
}
|
|
return
|
|
}
|
|
if p.IsDouble() {
|
|
*p = MakeDouble(p.AsDouble()+float64(n), p.Length(), p.Decimal())
|
|
return
|
|
}
|
|
if p.IsDate() {
|
|
*p = MakeDate(p.AsJulian() + n)
|
|
return
|
|
}
|
|
panic(t.argError("+int", *p))
|
|
}
|
|
|
|
// LocalAdd adds the top of stack to a local variable, pops the value. Byref-aware.
|
|
// Harbour: hb_xvmLocalAdd
|
|
func (t *Thread) LocalAdd(n int) {
|
|
val := t.pop()
|
|
idx := t.localIndex(n)
|
|
loc := t.locals[idx]
|
|
isRef := loc.Type() == tByref
|
|
if isRef {
|
|
loc = (*HbRefCell)(loc.ptr).V
|
|
}
|
|
|
|
var result Value
|
|
if loc.IsNumInt() && val.IsNumInt() {
|
|
r := loc.AsNumInt() + val.AsNumInt()
|
|
if (val.AsNumInt() >= 0 && r >= loc.AsNumInt()) || (val.AsNumInt() < 0 && r < loc.AsNumInt()) {
|
|
result = MakeNumInt(r)
|
|
} else {
|
|
result = MakeDoubleAuto(float64(loc.AsNumInt()) + float64(val.AsNumInt()))
|
|
}
|
|
} else if loc.IsNumeric() && val.IsNumeric() {
|
|
dec := maxDec(loc.Decimal(), val.Decimal())
|
|
result = MakeDouble(loc.AsNumDouble()+val.AsNumDouble(), 255, dec)
|
|
} else if loc.IsString() && val.IsString() {
|
|
result = MakeString(loc.AsString() + val.AsString())
|
|
} else {
|
|
panic(t.argError("+=", loc, val))
|
|
}
|
|
|
|
if isRef {
|
|
(*HbRefCell)(t.locals[idx].ptr).V = result
|
|
} else {
|
|
t.locals[idx] = result
|
|
}
|
|
}
|
|
|
|
// LocalAddInt adds an integer constant directly to a local variable. Byref-aware.
|
|
// Harbour: hb_xvmLocalAddInt (fused PUSHINT + PLUS + POPLOCAL)
|
|
func (t *Thread) LocalAddInt(n int, val int64) {
|
|
idx := t.curFrame.localBase + n - 1 // inline index
|
|
loc := t.locals[idx]
|
|
isRef := loc.Type() == tByref
|
|
if isRef {
|
|
loc = (*HbRefCell)(loc.ptr).V
|
|
}
|
|
|
|
var result Value
|
|
if loc.IsNumInt() {
|
|
r := loc.AsNumInt() + val
|
|
if (val >= 0 && r >= loc.AsNumInt()) || (val < 0 && r < loc.AsNumInt()) {
|
|
result = MakeNumInt(r)
|
|
} else {
|
|
result = MakeDoubleAuto(float64(loc.AsNumInt()) + float64(val))
|
|
}
|
|
} else if loc.IsDouble() {
|
|
result = MakeDouble(loc.AsDouble()+float64(val), loc.Length(), loc.Decimal())
|
|
} else if loc.IsDate() {
|
|
result = MakeDate(loc.AsJulian() + val)
|
|
} else {
|
|
panic(t.argError("+int", loc))
|
|
}
|
|
|
|
if isRef {
|
|
(*HbRefCell)(t.locals[idx].ptr).V = result
|
|
} else {
|
|
t.locals[idx] = result
|
|
}
|
|
}
|
|
|
|
// --- Helpers ---
|
|
|
|
func maxDec(a, b uint16) uint16 {
|
|
if a == 255 || b == 255 {
|
|
return 255 // HB_DEFAULT_DECIMALS
|
|
}
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (t *Thread) divisionByZero() *HbError {
|
|
return &HbError{
|
|
Description: "division by zero",
|
|
Operation: "/",
|
|
SubSystem: "BASE",
|
|
GenCode: 1340, // EG_ZERODIV
|
|
}
|
|
}
|