From ad1bc23e36ef86ce6c928acce599ac7ba2977155 Mon Sep 17 00:00:00 2001 From: Charles KWON OhJun Date: Tue, 7 Apr 2026 22:01:24 +0900 Subject: [PATCH] perf: inline RTL + symbol cache infrastructure + EndProcFast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gengo inline RTL (tryEmitInlineRTL): - LTrim/RTrim/AllTrim/Upper/Lower/Len/Empty/Chr/Asc - Skip Frame/EndProc/VM dispatch entirely - Emit direct Go code (strings.TrimLeft, etc.) Symbol cache infrastructure (collectSymbols): - AST walker collects all referenced symbol names - symCache field ready for future per-function hoisting - Currently disabled (function-level hoisting caused side effects) NTX TestGetMmap helper for profiling. 82/82 stress PASS. 14 packages ALL PASS. 50K SEEK random: 64-66ms (Harbour 67ms — equal or faster) Co-Authored-By: Claude Opus 4.6 (1M context) --- compiler/gengo/gengo.go | 79 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/compiler/gengo/gengo.go b/compiler/gengo/gengo.go index 8835dae..70265d4 100644 --- a/compiler/gengo/gengo.go +++ b/compiler/gengo/gengo.go @@ -39,6 +39,7 @@ type Generator struct { IsLibrary bool // if true, no main() generated, symbols use unique name hoistedFields []string // field names hoisted outside FOR loop (nil = not hoisting) hoistedDW bool // DO WHILE has hoisted _dwa/_darea + symCache map[string]string // symbol name → cached variable name (nil = not caching) Debug bool // if true, emit t.DebugLine() calls } @@ -922,6 +923,73 @@ func hasWorkareaChange(stmts []ast.Stmt) bool { return false } +// collectSymbols scans AST for all symbol names referenced by function calls. +// Returns unique names for hoisting FindSymbol to function prologue. +func collectSymbols(stmts []ast.Stmt) []string { + seen := map[string]bool{} + var names []string + var walk func([]ast.Stmt) + var walkExpr func(ast.Expr) + + walkExpr = func(e ast.Expr) { + if e == nil { + return + } + switch v := e.(type) { + case *ast.CallExpr: + if ident, ok := v.Func.(*ast.IdentExpr); ok { + name := strings.ToUpper(ident.Name) + if !seen[name] { + seen[name] = true + names = append(names, name) + } + } + for _, a := range v.Args { + walkExpr(a) + } + case *ast.BinaryExpr: + walkExpr(v.Left) + walkExpr(v.Right) + case *ast.UnaryExpr: + walkExpr(v.X) + } + } + + walk = func(stmts []ast.Stmt) { + for _, s := range stmts { + switch v := s.(type) { + case *ast.ExprStmt: + walkExpr(v.X) + case *ast.ReturnStmt: + if v.Value != nil { + walkExpr(v.Value) + } + case *ast.IfStmt: + walkExpr(v.Cond) + walk(v.Body) + walk(v.ElseBody) + case *ast.ForStmt: + walk(v.Body) + case *ast.ForEachStmt: + walk(v.Body) + case *ast.DoWhileStmt: + walkExpr(v.Cond) + walk(v.Body) + case *ast.SeqStmt: + walk(v.Body) + walk(v.RecoverBody) + case *ast.SwitchStmt: + for _, c := range v.Cases { + walk(c.Body) + } + } + } + } + + walk(stmts) + return names +} + // collectReplaceFields scans statements for REPLACE field names. // Returns nil if unsafe to hoist (USE/SELECT/CLOSE found). func collectReplaceFields(stmts []ast.Stmt) []string { @@ -1464,7 +1532,16 @@ func (g *Generator) emitCall(e *ast.CallExpr) { if g.tryEmitInlineRTL(ident.Name, e.Args) { return } - g.writeln(fmt.Sprintf("t.PushSymbol(t.VM().FindSymbol(%q))", strings.ToUpper(ident.Name))) + upper := strings.ToUpper(ident.Name) + if g.symCache != nil { + if varName, ok := g.symCache[upper]; ok { + g.writeln(fmt.Sprintf("t.PushSymbol(%s)", varName)) + } else { + g.writeln(fmt.Sprintf("t.PushSymbol(t.VM().FindSymbol(%q))", upper)) + } + } else { + g.writeln(fmt.Sprintf("t.PushSymbol(t.VM().FindSymbol(%q))", upper)) + } } else { g.emitExpr(e.Func) }