perf(gengo): elide dead-store inits for const-propagated LOCALs

When collectConstLocals proves a LOCAL is only ever read, not
written beyond its literal init, every read site gets the literal
substituted inline — which means the init itself has no live
reader. Skip emitting the PushXxx/PopLocalFast pair for those
LOCALs in both top-of-function and mid-body decls.

On a function with `LOCAL nBuf := 100, sTag := "x", bFlag := .T.`,
all three inits drop out (6 VM ops saved in the prologue), while
the still-written `LOCAL nSum := 0` init stays. Harbour compat
56/56, FiveSql2 43/43.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-18 13:06:43 +09:00
parent b829ed4996
commit 6974ff9473

View File

@@ -939,7 +939,9 @@ func (g *Generator) emitFuncDecl(fn *ast.FuncDecl) {
// so reads can be constant-propagated at emit time.
g.constLocals = collectConstLocals(fn)
// Emit LOCAL initializers
// Emit LOCAL initializers. LOCALs that were const-propagated have
// their reads substituted inline, so the init store has no live
// reader — skip it (dead-store elimination).
localIdx := nParams + 1 // 1-based, params come first
for _, d := range fn.Decls {
vd, ok := d.(*ast.VarDecl)
@@ -948,8 +950,10 @@ func (g *Generator) emitFuncDecl(fn *ast.FuncDecl) {
}
for _, v := range vd.Vars {
if v.Init != nil {
g.emitExpr(v.Init)
g.writeln(fmt.Sprintf("t.PopLocalFast(%d)", localIdx))
if _, isConst := g.constLocals[strings.ToUpper(v.Name)]; !isConst {
g.emitExpr(v.Init)
g.writeln(fmt.Sprintf("t.PopLocalFast(%d)", localIdx))
}
}
localIdx++
}
@@ -1330,8 +1334,10 @@ func (g *Generator) emitMidVarDecl(s *ast.VarDecl, locals localMap) {
locals[strings.ToUpper(v.Name)] = idx
}
if v.Init != nil {
g.emitExpr(v.Init)
g.writeln(fmt.Sprintf("t.PopLocalFast(%d)", idx))
if _, isConst := g.constLocals[strings.ToUpper(v.Name)]; !isConst {
g.emitExpr(v.Init)
g.writeln(fmt.Sprintf("t.PopLocalFast(%d)", idx))
}
}
}
}