perf(gengo): unary-minus literal fold + x:=x+y → LocalAdd peephole
Two more leaf-level code-gen cleanups now that the const folder is in. - UnaryExpr MINUS over a LITERAL (INT/DOUBLE) emits the negated value directly, so `-42` becomes PushInt(-42) instead of PushInt(42) + Negate(). Guarded: MinInt64 passes through to the VM so the coerce-to-double path stays authoritative. Variables fall through to the normal Negate path — the LiteralExpr type assertion is the gate, so runtime-typed `-x` keeps its semantics. - `x := x + <expr>` / `x := x - <expr>` detected when the LHS ident resolves to the same local as the self-reference on the RHS, emits the same LocalAdd / Negate+LocalAdd shape that x += y already used. Non-matching locals (shadowing, module statics) fall through. 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>
This commit is contained in:
@@ -232,6 +232,45 @@ func (g *Generator) emitPushSymbol(name string) {
|
||||
g.writeln(fmt.Sprintf("t.PushSymbol(t.GetSym(&%s, %q))", v, name))
|
||||
}
|
||||
|
||||
// negateLiteral produces a new literal that represents -lit. Handles
|
||||
// INT and DOUBLE (as a textual prefix). Returns (nil, false) for
|
||||
// non-numeric literals or an already-negative INT whose negation would
|
||||
// overflow int64.
|
||||
func negateLiteral(lit *ast.LiteralExpr) (*ast.LiteralExpr, bool) {
|
||||
switch lit.Kind {
|
||||
case token.INT:
|
||||
n, err := strconv.ParseInt(lit.Value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
// Guard: math.MinInt64 has no positive twin — let the VM's
|
||||
// runtime coerce-to-double path handle it.
|
||||
if n == -1<<63 {
|
||||
return nil, false
|
||||
}
|
||||
return &ast.LiteralExpr{
|
||||
ValuePos: lit.ValuePos,
|
||||
Kind: token.INT,
|
||||
Value: strconv.FormatInt(-n, 10),
|
||||
}, true
|
||||
case token.DOUBLE:
|
||||
// Syntactically prefix `-` or flip an existing leading `-`.
|
||||
if strings.HasPrefix(lit.Value, "-") {
|
||||
return &ast.LiteralExpr{
|
||||
ValuePos: lit.ValuePos,
|
||||
Kind: token.DOUBLE,
|
||||
Value: lit.Value[1:],
|
||||
}, true
|
||||
}
|
||||
return &ast.LiteralExpr{
|
||||
ValuePos: lit.ValuePos,
|
||||
Kind: token.DOUBLE,
|
||||
Value: "-" + lit.Value,
|
||||
}, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// foldLiteralTree recursively folds BinaryExpr subtrees into LiteralExpr
|
||||
// where both operands eventually collapse to literals. Non-foldable
|
||||
// subtrees come back unchanged. Used as a preorder pre-pass so the
|
||||
@@ -1137,6 +1176,22 @@ func (g *Generator) emitAssign(a *ast.AssignExpr, locals localMap) {
|
||||
if idx, found := locals[strings.ToUpper(ident.Name)]; found {
|
||||
switch a.Op {
|
||||
case token.ASSIGN:
|
||||
// Peephole: `x := x + <expr>` / `x := x - <expr>` →
|
||||
// LocalAdd. Same result as `x += <expr>` but lets the
|
||||
// PRG side use the explicit form without penalty.
|
||||
if be, ok := a.Right.(*ast.BinaryExpr); ok &&
|
||||
(be.Op == token.PLUS || be.Op == token.MINUS) {
|
||||
if lid, isIdent := be.Left.(*ast.IdentExpr); isIdent {
|
||||
if selfIdx, found := locals[strings.ToUpper(lid.Name)]; found && selfIdx == idx {
|
||||
g.emitExpr(be.Right)
|
||||
if be.Op == token.MINUS {
|
||||
g.writeln("t.Negate()")
|
||||
}
|
||||
g.writeln(fmt.Sprintf("t.LocalAdd(%d)", idx))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
g.emitExpr(a.Right)
|
||||
g.writeln(fmt.Sprintf("t.PopLocalFast(%d)", idx))
|
||||
case token.PLUSEQ:
|
||||
@@ -1826,6 +1881,16 @@ func (g *Generator) emitExpr(expr ast.Expr) {
|
||||
g.emitBinaryOp(e.Op)
|
||||
}
|
||||
case *ast.UnaryExpr:
|
||||
// Fold `-<literal>` to a negative literal at compile time so
|
||||
// `-42` becomes PushInt(-42) instead of PushInt(42) + Negate().
|
||||
if e.Op == token.MINUS {
|
||||
if lit, ok := e.X.(*ast.LiteralExpr); ok {
|
||||
if neg, ok := negateLiteral(lit); ok {
|
||||
g.emitLiteral(neg)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
g.emitExpr(e.X)
|
||||
g.emitUnaryOp(e.Op)
|
||||
case *ast.AssignExpr:
|
||||
|
||||
Reference in New Issue
Block a user