Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
e356b2b
perf(constraint): reduce normalization and conjunction merge overhead
wolfy-j Feb 20, 2026
e8c5fac
perf(cfg,constraint): speed up graph iteration and constraint ordering
wolfy-j Feb 20, 2026
8b50fb1
perf(cfg,assign): reduce SSA churn and assignment traversal overhead
wolfy-j Feb 20, 2026
c920d4a
perf(cfg): reduce assignment allocations and large-graph map growth
wolfy-j Feb 20, 2026
0a00d4a
perf(bind): size hot maps from declaration hints
wolfy-j Feb 20, 2026
f823b24
perf(types): cache manifest lookups and speed record field access
wolfy-j Feb 21, 2026
0601848
perf(flow): memoize NarrowedTypeAt queries after solve
wolfy-j Feb 21, 2026
110cd6c
perf(cfg): optimize graph construction; fix indexing and table.freeze…
wolfy-j Feb 21, 2026
5856769
perf(types,cfg): reduce recursion and SSA allocation overhead
wolfy-j Feb 21, 2026
76e5765
perf(db): remove closure deps from incremental query tracking
wolfy-j Feb 21, 2026
8f12cd8
perf(cfg): cache param slots for checker hot paths
wolfy-j Feb 21, 2026
cf3137d
perf(cfg): reduce assign and point-index allocations
wolfy-j Feb 21, 2026
c1802a7
perf(synth): avoid per-point scope map blowup
wolfy-j Feb 21, 2026
6f72b5e
perf(extract): skip redundant local inference expansion
wolfy-j Feb 21, 2026
de4e0f2
perf(extract): drop transient one-entry scope maps
wolfy-j Feb 21, 2026
791078f
perf(extract): fast-path single-target local inference
wolfy-j Feb 21, 2026
d2a50c8
perf(cfg): skip heavy visibility prepass on large graphs
wolfy-j Feb 21, 2026
07bf45f
perf(cfg): avoid parent map copy for invisible pushes
wolfy-j Feb 21, 2026
a24a26a
perf(cfg): trim SSA version stack preallocation
wolfy-j Feb 21, 2026
1ee466e
perf(extract): lazily init callback overlay synthesizer
wolfy-j Feb 21, 2026
732d521
perf(flow): batch dependency enqueues in solve worklist
wolfy-j Feb 21, 2026
c87f4d6
perf(flow): drop sorted edge conversion in propagation setup
wolfy-j Feb 21, 2026
f3d1e47
perf(cfg): raise map capacity hints for large graphs
wolfy-j Feb 21, 2026
60019eb
perf(flow): use read-only CFG adjacency in hot paths
wolfy-j Feb 21, 2026
a6e1959
perf(cfg): retune map cap ceilings for lower allocation
wolfy-j Feb 21, 2026
216e84a
perf(cfg): avoid adjacency slice copies in graph accessors
wolfy-j Feb 21, 2026
4e7210e
perf(flow): lazily allocate narrowed-type query cache
wolfy-j Feb 21, 2026
4ba9364
perf(flow): lazily initialize scratch maps
wolfy-j Feb 21, 2026
eb38c71
perf(cfg): lower map preallocation ceilings
wolfy-j Feb 21, 2026
81809a0
perf(cfg): use dense symbol index lookup in SSA rename
wolfy-j Feb 21, 2026
15edaf2
perf(cfg): limit SSA visibility cache prepass to small graphs
wolfy-j Feb 21, 2026
9696e6d
perf(cfg): trim SSA version-stack preallocation
wolfy-j Feb 21, 2026
817d613
perf(cfg): reduce SSA phi allocation overhead
wolfy-j Feb 21, 2026
1c58629
perf(cfg): tighten CFG capacity estimation cushions
wolfy-j Feb 21, 2026
2530bcc
perf(cfg): lower minimum CFG capacity floors
wolfy-j Feb 21, 2026
d2f0143
perf(cfg): tighten SSA symbol lookup and phi operand allocation
wolfy-j Feb 21, 2026
937f263
perf(cfg): skip persisting untouched SSA version counters
wolfy-j Feb 21, 2026
27da4f1
perf(db): avoid heap frame allocation in query tracker stack
wolfy-j Feb 21, 2026
a759c22
perf(db): reuse validation context wrappers in query path
wolfy-j Feb 21, 2026
9c9a76d
perf(callsite): avoid map allocation for tiny symbol candidate sets
wolfy-j Feb 21, 2026
1e741e7
perf(cfg): drop redundant visible-version preallocations
wolfy-j Feb 21, 2026
05e57ed
perf(cfg): trim eager scope-tracker map capacities
wolfy-j Feb 21, 2026
03cce2a
perf(assign): reuse SCC overlay map during inference fixpoint
wolfy-j Feb 21, 2026
35c0bd6
perf(db): compact dependency record payload
wolfy-j Feb 21, 2026
dbe6256
perf(assign): build extraction overlays in place
wolfy-j Feb 21, 2026
1cb3521
perf(assign): avoid map alloc for small call-ref dedupe
wolfy-j Feb 21, 2026
f94ed6a
perf(cfg): use typed slices.Sort in SSA symbol ordering
wolfy-j Feb 21, 2026
8dcfe3b
perf(cfg): use typed slices.SortFunc in dominator ordering
wolfy-j Feb 21, 2026
ac83140
perf(assign): reduce SCC dedupe map overhead
wolfy-j Feb 21, 2026
ef7a290
perf(cfg): simplify phi-site storage for SSA rename
wolfy-j Feb 21, 2026
55488d8
perf(assign): lazily expand RHS values during SCC inference
wolfy-j Feb 21, 2026
dbb917b
perf(assign): lazily expand assignment RHS values
wolfy-j Feb 21, 2026
55b8d34
perf(synth): lazily init prelim return-inference synthesizer
wolfy-j Feb 21, 2026
31c0664
perf(assign): rely on SCC marks for index dedupe
wolfy-j Feb 21, 2026
370fb4e
perf(cfg): single-pass visible-version map build
wolfy-j Feb 21, 2026
e9ee000
perf(synth): fast-path simple local return inference
wolfy-j Feb 21, 2026
e8418ae
perf(cfg): reduce minimum capacity hints for tiny graphs
wolfy-j Feb 21, 2026
fb61498
perf(synth): avoid graph scan for named function symbol lookup
wolfy-j Feb 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 188 additions & 9 deletions compiler/bind/binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,23 @@ type Binder struct {
// Globals are sorted before processing to ensure deterministic SymbolID
// assignment across multiple bindings of the same source.
func NewBinder(globals []string) *Binder {
return NewBinderWithDeclHint(globals, 0)
}

// NewBinderWithDeclHint creates a binder initialized with global names and an
// optional declaration-count hint for sizing internal binding maps.
func NewBinderWithDeclHint(globals []string, declHint int) *Binder {
if declHint < 0 {
declHint = 0
}

symbolHint := len(globals)
if declHint >= 32 {
symbolHint += declHint
}

b := &Binder{
table: NewBindingTable(),
table: NewBindingTableWithHint(symbolHint, declHint),
stack: []scopeFrame{{locals: make(map[string]cfg.SymbolID)}},
globals: make(map[string]cfg.SymbolID),
}
Expand Down Expand Up @@ -97,6 +112,12 @@ func NewBinder(globals []string) *Binder {
return b
}

// NewBinderWithStmtHint preserves the previous API surface; stmtHint is treated
// as a declaration-density hint.
func NewBinderWithStmtHint(globals []string, stmtHint int) *Binder {
return NewBinderWithDeclHint(globals, stmtHint)
}

// Bind performs complete name resolution on a function AST.
//
// This is the main entry point for the binding phase. It creates a binder
Expand All @@ -106,11 +127,164 @@ func NewBinder(globals []string) *Binder {
// The returned table maps all IdentExpr nodes to their resolved symbols
// and records parameter/local symbol lists for each function and declaration.
func Bind(fn *ast.FunctionExpr, globals []string) *BindingTable {
b := NewBinder(globals)
declHint := 0
if fn != nil {
declHint = countDeclHintsInFunction(fn, false)
}

b := NewBinderWithDeclHint(globals, declHint)
b.bindFunctionWithImplicitSelf(fn, false)
return b.table
}

func countDeclHintsInFunction(fn *ast.FunctionExpr, hasImplicitSelf bool) int {
if fn == nil {
return 0
}

count := 0

hasExplicitSelf := fn.ParList != nil && len(fn.ParList.Names) > 0 && fn.ParList.Names[0] == "self"
if hasImplicitSelf && !hasExplicitSelf {
count++
}

if fn.ParList != nil {
count += len(fn.ParList.Names)
}

count += countDeclHintsInStmts(fn.Stmts)

return count
}

func countDeclHintsInStmts(stmts []ast.Stmt) int {
count := 0

for _, stmt := range stmts {
count += countDeclHintsInStmt(stmt)
}

return count
}

func countDeclHintsInStmt(stmt ast.Stmt) int {
if stmt == nil {
return 0
}

switch s := stmt.(type) {
case *ast.LocalAssignStmt:
count := len(s.Names)
for _, expr := range s.Exprs {
count += countDeclHintsInExpr(expr)
}

return count
case *ast.AssignStmt:
count := 0
for _, expr := range s.Lhs {
count += countDeclHintsInExpr(expr)
}
for _, expr := range s.Rhs {
count += countDeclHintsInExpr(expr)
}

return count
case *ast.FuncCallStmt:
return countDeclHintsInExpr(s.Expr)
case *ast.DoBlockStmt:
return countDeclHintsInStmts(s.Stmts)
case *ast.WhileStmt:
return countDeclHintsInExpr(s.Condition) + countDeclHintsInStmts(s.Stmts)
case *ast.RepeatStmt:
return countDeclHintsInStmts(s.Stmts) + countDeclHintsInExpr(s.Condition)
case *ast.IfStmt:
return countDeclHintsInExpr(s.Condition) + countDeclHintsInStmts(s.Then) + countDeclHintsInStmts(s.Else)
case *ast.NumberForStmt:
return 1 + countDeclHintsInExpr(s.Init) + countDeclHintsInExpr(s.Limit) + countDeclHintsInExpr(s.Step) +
countDeclHintsInStmts(s.Stmts)
case *ast.GenericForStmt:
count := len(s.Names)
for _, expr := range s.Exprs {
count += countDeclHintsInExpr(expr)
}
count += countDeclHintsInStmts(s.Stmts)

return count
case *ast.FuncDefStmt:
isMethod := s.Name != nil && s.Name.Method != ""
count := countDeclHintsInFunction(s.Func, isMethod)
if s.Name != nil {
count += countDeclHintsInExpr(s.Name.Func) + countDeclHintsInExpr(s.Name.Receiver)
}

return count
case *ast.ReturnStmt:
count := 0
for _, expr := range s.Exprs {
count += countDeclHintsInExpr(expr)
}

return count
default:
return 0
}
}

func countDeclHintsInExpr(expr ast.Expr) int {
if expr == nil {
return 0
}

switch e := expr.(type) {
case *ast.FunctionExpr:
return countDeclHintsInFunction(e, false)
case *ast.AttrGetExpr:
return countDeclHintsInExpr(e.Object) + countDeclHintsInExpr(e.Key)
case *ast.TableExpr:
count := 0
for _, field := range e.Fields {
if field == nil {
continue
}
count += countDeclHintsInExpr(field.Key)
count += countDeclHintsInExpr(field.Value)
}

return count
case *ast.FuncCallExpr:
count := countDeclHintsInExpr(e.Func) + countDeclHintsInExpr(e.Receiver)
for _, arg := range e.Args {
count += countDeclHintsInExpr(arg)
}

return count
case *ast.LogicalOpExpr:
return countDeclHintsInExpr(e.Lhs) + countDeclHintsInExpr(e.Rhs)
case *ast.RelationalOpExpr:
return countDeclHintsInExpr(e.Lhs) + countDeclHintsInExpr(e.Rhs)
case *ast.StringConcatOpExpr:
return countDeclHintsInExpr(e.Lhs) + countDeclHintsInExpr(e.Rhs)
case *ast.ArithmeticOpExpr:
return countDeclHintsInExpr(e.Lhs) + countDeclHintsInExpr(e.Rhs)
case *ast.UnaryMinusOpExpr:
return countDeclHintsInExpr(e.Expr)
case *ast.UnaryNotOpExpr:
return countDeclHintsInExpr(e.Expr)
case *ast.UnaryLenOpExpr:
return countDeclHintsInExpr(e.Expr)
case *ast.UnaryBNotOpExpr:
return countDeclHintsInExpr(e.Expr)
case *ast.CastExpr:
return countDeclHintsInExpr(e.Expr)
case *ast.NonNilAssertExpr:
return countDeclHintsInExpr(e.Expr)
default:
return 0
}
}

// enterScope pushes a new empty scope frame onto the stack.
func (b *Binder) enterScope() {
b.stack = append(b.stack, scopeFrame{locals: make(map[string]cfg.SymbolID)})
Expand Down Expand Up @@ -214,12 +388,12 @@ func (b *Binder) predeclareLocalFunctions(stmts []ast.Stmt) {
continue
}
// Already declared (shouldn't happen, but be safe)
if syms := b.table.LocalSymbols(local); len(syms) > 0 {
if b.table.HasLocalSymbols(local) {
continue
}
// Declare the local function name now
sym := b.declareLocal(local.Names[0])
b.table.SetLocalSymbols(local, []cfg.SymbolID{sym})
b.table.SetLocalSymbol(local, sym)
}
}

Expand Down Expand Up @@ -329,7 +503,7 @@ func (b *Binder) bindAssignTarget(expr ast.Expr) {
// then the new local names are declared. This ensures 'local x = x' binds
// the RHS x to any outer declaration before shadowing it.
func (b *Binder) bindLocalAssignStmt(s *ast.LocalAssignStmt) {
if syms := b.table.LocalSymbols(s); len(syms) > 0 {
if b.table.HasLocalSymbols(s) {
for _, expr := range s.Exprs {
b.bindExpr(expr)
}
Expand All @@ -339,10 +513,15 @@ func (b *Binder) bindLocalAssignStmt(s *ast.LocalAssignStmt) {
for _, expr := range s.Exprs {
b.bindExpr(expr)
}
syms := make([]cfg.SymbolID, 0, len(s.Names))
for _, name := range s.Names {
sym := b.declareLocal(name)
syms = append(syms, sym)
if len(s.Names) == 1 {
b.table.SetLocalSymbol(s, b.declareLocal(s.Names[0]))

return
}

syms := make([]cfg.SymbolID, len(s.Names))
for i, name := range s.Names {
syms[i] = b.declareLocal(name)
}
b.table.SetLocalSymbols(s, syms)
}
Expand Down
Loading