perf: inline RTL + symbol cache infrastructure + EndProcFast

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) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 22:01:24 +09:00
parent 77562d4645
commit ad1bc23e36

View File

@@ -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)
}