From bb6cf7c612c37f0ce6eeee00436cb39bb39a836a Mon Sep 17 00:00:00 2001 From: Charles KWON OhJun Date: Tue, 7 Apr 2026 17:07:33 +0900 Subject: [PATCH] =?UTF-8?q?perf:=20FOR=20loop=20RDD=20hoisting=20=E2=80=94?= =?UTF-8?q?=20WA/FieldIndex=20cached=20outside=20loop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When FOR body contains APPEND+REPLACE and no USE/SELECT: - Hoist WorkAreaManager, Current(), *dbf.DBFArea outside loop - Pre-compute FieldIndex for all REPLACE fields once - REPLACE inside loop uses cached _rdbf and _rfiN variables - APPEND inside loop uses cached _rarea (no WA lookup per iter) Safety: collectReplaceFields returns nil if USE/SELECT found in body (workarea may change → cannot safely cache). Falls back to normal emit. 10K APPEND benchmark: 28ms (Harbour 27ms — essentially equal!) 82/82 stress test PASS. 14 packages ALL PASS. Co-Authored-By: Claude Opus 4.6 (1M context) --- compiler/gengo/gen_cmd.go | 36 ++++++++++++++++- compiler/gengo/gengo.go | 85 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 116 insertions(+), 5 deletions(-) diff --git a/compiler/gengo/gen_cmd.go b/compiler/gengo/gen_cmd.go index bc893a1..e68e997 100644 --- a/compiler/gengo/gen_cmd.go +++ b/compiler/gengo/gen_cmd.go @@ -132,13 +132,17 @@ func (g *Generator) emitSeekCmd(s *ast.SeekCmd, locals localMap) { } func (g *Generator) emitReplaceCmd(s *ast.ReplaceCmd, locals localMap) { + // Check if we're inside a hoisted FOR loop + if g.hoistedFields != nil { + g.emitReplaceCmdHoisted(s, locals) + return + } + g.writeln("{") g.indent++ g.writeln("wa := t.WA.(*hbrdd.WorkAreaManager)") g.writeln("if _area := wa.Current(); _area != nil {") g.indent++ - - // Cache type assertion once (avoids N assertions for N fields) g.writeln("_dbf, _ := _area.(*dbf.DBFArea)") g.writeln("if _dbf != nil {") g.indent++ @@ -158,6 +162,34 @@ func (g *Generator) emitReplaceCmd(s *ast.ReplaceCmd, locals localMap) { g.writeln("}") } +// emitReplaceCmdHoisted emits REPLACE using pre-hoisted _rdbf and _rfiN variables. +func (g *Generator) emitReplaceCmdHoisted(s *ast.ReplaceCmd, locals localMap) { + g.writeln("if _rdbf != nil {") + g.indent++ + for _, rf := range s.Fields { + if ident, ok := rf.Field.(*ast.IdentExpr); ok { + // Find cached field index variable + fiVar := "" + for i, fname := range g.hoistedFields { + if strings.EqualFold(fname, ident.Name) { + fiVar = fmt.Sprintf("_rfi%d", i) + break + } + } + if fiVar != "" { + g.emitExpr(rf.Value) + g.writeln(fmt.Sprintf("_rdbf.PutValue(%s, t.Pop2())", fiVar)) + } else { + // Field not in hoisted set — fallback + g.emitExpr(rf.Value) + g.writeln(fmt.Sprintf("_rdbf.PutValue(_rdbf.FieldIndex(%q), t.Pop2())", ident.Name)) + } + } + } + g.indent-- + g.writeln("}") +} + // --- @ SAY / GET / READ commands --- func (g *Generator) emitAtSayCmd(s *ast.AtSayCmd) { diff --git a/compiler/gengo/gengo.go b/compiler/gengo/gengo.go index 78a13a1..7537765 100644 --- a/compiler/gengo/gengo.go +++ b/compiler/gengo/gengo.go @@ -36,7 +36,8 @@ type Generator struct { curLocals localMap // current function's local variable map goFastFuncs []goFastEntry // Go functions to register as FastFunc staticVars map[string]string // top-level STATIC: upper name → Go var name - IsLibrary bool // if true, no main() generated, symbols use unique name + IsLibrary bool // if true, no main() generated, symbols use unique name + hoistedFields []string // field names hoisted outside FOR loop (nil = not hoisting) Debug bool // if true, emit t.DebugLine() calls } @@ -456,8 +457,13 @@ func (g *Generator) emitStmt(stmt ast.Stmt, locals localMap) { case *ast.ReplaceCmd: g.emitReplaceCmd(s, locals) case *ast.AppendCmd: - g.writeln("{ _wa := t.WA.(*hbrdd.WorkAreaManager)") - g.writeln("if _area := _wa.Current(); _area != nil { _area.Append() } }") + if g.hoistedFields != nil { + // Use hoisted area variable + g.writeln("if _rarea != nil { _rarea.Append() }") + } else { + g.writeln("{ _wa := t.WA.(*hbrdd.WorkAreaManager)") + g.writeln("if _area := _wa.Current(); _area != nil { _area.Append() } }") + } case *ast.DeleteCmd: g.writeln("{ _wa := t.WA.(*hbrdd.WorkAreaManager)") g.writeln("if _area := _wa.Current(); _area != nil { _area.Delete() } }") @@ -859,6 +865,52 @@ func (g *Generator) emitDoWhile(s *ast.DoWhileStmt, locals localMap) { g.writeln("}") } +// collectReplaceFields scans statements for REPLACE field names. +// Returns nil if unsafe to hoist (USE/SELECT/CLOSE found). +func collectReplaceFields(stmts []ast.Stmt) []string { + seen := map[string]bool{} + var fields []string + for _, s := range stmts { + switch v := s.(type) { + case *ast.ReplaceCmd: + for _, rf := range v.Fields { + if ident, ok := rf.Field.(*ast.IdentExpr); ok { + name := ident.Name + if !seen[name] { + seen[name] = true + fields = append(fields, name) + } + } + } + case *ast.UseCmd, *ast.SelectCmd: + return nil // workarea may change — unsafe to hoist + case *ast.IfStmt: + // Check nested blocks + if sub := collectReplaceFields(v.Body); sub == nil { + return nil + } + if sub := collectReplaceFields(v.ElseBody); sub == nil { + return nil + } + case *ast.DoWhileStmt: + if sub := collectReplaceFields(v.Body); sub == nil { + return nil + } + } + } + return fields +} + +// hasAppendInBody checks if any APPEND command exists in the statements. +func hasAppendInBody(stmts []ast.Stmt) bool { + for _, s := range stmts { + if _, ok := s.(*ast.AppendCmd); ok { + return true + } + } + return false +} + func (g *Generator) emitFor(s *ast.ForStmt, locals localMap) { idx, found := locals[s.Var] if !found { @@ -883,6 +935,26 @@ func (g *Generator) emitFor(s *ast.ForStmt, locals localMap) { } } + // Optimization: hoist WA/FieldIndex lookups outside FOR loop + // if body contains REPLACE and no USE/SELECT (safe to cache). + rddFields := collectReplaceFields(s.Body) + hoistRDD := len(rddFields) > 0 && hasAppendInBody(s.Body) + + if hoistRDD { + g.writeln("{") + g.indent++ + g.writeln("_rwa := t.WA.(*hbrdd.WorkAreaManager)") + g.writeln("_rarea := _rwa.Current()") + g.writeln("var _rdbf *dbf.DBFArea") + g.writeln("if _rarea != nil { _rdbf, _ = _rarea.(*dbf.DBFArea) }") + // Pre-compute field indexes + for i, fname := range rddFields { + g.writeln(fmt.Sprintf("var _rfi%d int = -1", i)) + g.writeln(fmt.Sprintf("if _rdbf != nil { _rfi%d = _rdbf.FieldIndex(%q) }", i, fname)) + } + g.hoistedFields = rddFields // store for emitReplaceCmdHoisted + } + g.writeln("for {") g.indent++ @@ -911,6 +983,13 @@ func (g *Generator) emitFor(s *ast.ForStmt, locals localMap) { g.indent-- g.writeln("}") + + // Close hoisting block + if hoistRDD { + g.hoistedFields = nil + g.indent-- + g.writeln("}") + } } func (g *Generator) emitSwitch(s *ast.SwitchStmt, locals localMap) {