Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
506371d
Refactor interproc facts domain
wolfy-j May 18, 2026
97ee4c8
Refactor interproc fact domain
wolfy-j May 19, 2026
51d9a72
Document checker domain target
wolfy-j May 19, 2026
728c8ec
Refine abstract interpreter design
wolfy-j May 19, 2026
8ba2ab3
Extend checker inference design
wolfy-j May 19, 2026
79dfc21
Clarify checker dataflow contract
wolfy-j May 19, 2026
6b46fb3
Sharpen checker design doctrine
wolfy-j May 19, 2026
e63447e
Define checker semantic atoms
wolfy-j May 19, 2026
57d9dd0
Refine checker dataflow ownership design
wolfy-j May 19, 2026
f9f9500
Document checker Salsa wiring plan
wolfy-j May 19, 2026
88a4954
Define checker abstract machine model
wolfy-j May 19, 2026
3b4f8d8
Add checker dataflow ownership ledger
wolfy-j May 19, 2026
c81e896
Clarify checker evidence authority model
wolfy-j May 19, 2026
4f0587c
Define checker location memory calculus
wolfy-j May 19, 2026
ed897bb
Define checker relation effect calculus
wolfy-j May 19, 2026
6681700
Define checker function boundary calculus
wolfy-j May 19, 2026
2cfa142
Collapse parameter evidence into function facts
wolfy-j May 19, 2026
ca51917
Move parameter evidence into domain packages
wolfy-j May 19, 2026
777579e
Move value shape laws into domain
wolfy-j May 19, 2026
19ce145
Move return summaries into domain
wolfy-j May 19, 2026
cc5ebbf
Move function facts into domain
wolfy-j May 19, 2026
ebc1050
Move fact product into domain
wolfy-j May 19, 2026
78c1050
Move convergence laws into domains
wolfy-j May 19, 2026
1d602bc
Fix contextual narrowing false positives
wolfy-j May 19, 2026
97f222f
Rectify interproc function facts domain
wolfy-j May 19, 2026
19dfc17
Classify external lint replay reductions
wolfy-j May 19, 2026
82b6866
Close expression call evidence gaps
wolfy-j May 19, 2026
7eccaa7
Classify remaining external lint errors
wolfy-j May 19, 2026
ebb3639
Add advanced type system stress regressions
wolfy-j May 19, 2026
2c679fb
Add adversarial gradual typing regressions
wolfy-j May 19, 2026
92a8d7b
Add loop-carried gradual refinement regressions
wolfy-j May 19, 2026
b1b5aa1
Rectify checker convergence domains
wolfy-j May 19, 2026
736eee8
Rectify checker fact domains and regressions
wolfy-j May 19, 2026
873b5c3
Fix field path guard parent narrowing
wolfy-j May 19, 2026
0d00de4
Fix predicate branch effect inference
wolfy-j May 19, 2026
fb0238a
Fix table mutation facts and static empty keys
wolfy-j May 20, 2026
137037a
Fix contextual callback inference
wolfy-j May 20, 2026
6a39fce
Refactor checker facts into abstract interpreter product
wolfy-j May 20, 2026
63b744c
Make parameter demand canonical flow evidence
wolfy-j May 20, 2026
bf31847
Centralize abstract evidence materialization
wolfy-j May 20, 2026
b4d6fd3
Centralize keys collector callee classification
wolfy-j May 20, 2026
1b8840c
Cache graph evidence in checker store
wolfy-j May 20, 2026
b6c1676
Centralize structural value refinements
wolfy-j May 20, 2026
55e2a53
Require canonical evidence on graph providers
wolfy-j May 20, 2026
307adc6
Collapse graph evidence provider interface
wolfy-j May 20, 2026
c943f3e
Canonicalize checker domain ownership
wolfy-j May 20, 2026
e530802
Collapse abstract interpreter ownership
wolfy-j May 20, 2026
46b3f9e
Remove abstract transfer namespace
wolfy-j May 20, 2026
be38b4e
Clean abstract interpreter residue
wolfy-j May 20, 2026
3cb3272
Record external lint validation boundary
wolfy-j May 20, 2026
cd46937
Record current validation counts
wolfy-j May 20, 2026
ff27b72
Fix assertion-refined dynamic argument facts
wolfy-j May 20, 2026
231448e
Record assertion refinement validation
wolfy-j May 20, 2026
40c6cd0
Move call fact projection into function domain
wolfy-j May 20, 2026
3a2822c
Centralize function fact map construction
wolfy-j May 20, 2026
dee630d
Centralize function fact projection ownership
wolfy-j May 20, 2026
2be30ca
Use whole function fact projection cache
wolfy-j May 20, 2026
3467a89
Move parameter evidence signature projection
wolfy-j May 20, 2026
75e1dee
Run parameter evidence propagation to fixpoint
wolfy-j May 20, 2026
f07f888
Remove API function fact selectors
wolfy-j May 20, 2026
71542cf
Pass function facts as synth inputs
wolfy-j May 20, 2026
eb4748f
Remove API function fact projection
wolfy-j May 20, 2026
8b33ec0
Move nested call replay to calleffect domain
wolfy-j May 20, 2026
c2ffbb9
Restore checker revision boundary
wolfy-j May 20, 2026
7addbdf
Classify local replay diagnostics
wolfy-j May 20, 2026
bb8938b
Canonicalize interproc abstract facts domain
wolfy-j May 21, 2026
4c19dda
Remove residual canonical migration drift
wolfy-j May 21, 2026
83a5d53
Finish canonical naming cleanup
wolfy-j May 21, 2026
2db6644
Remove checker cleanup residue
wolfy-j May 21, 2026
33a50e1
Add local harness precision regressions
wolfy-j May 21, 2026
350cc04
Canonicalize empty root scope analysis
wolfy-j May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11,431 changes: 11,431 additions & 0 deletions INTERPROC_FACTS_DOMAIN_JOURNAL.md

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions compiler/ast/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ type FuncCallExpr struct {
AdjustRet bool // Whether return count should be adjusted
}

// CanProduceMultipleValues reports whether expr can expand to multiple Lua values
// when it appears in the final slot of an expression list.
func CanProduceMultipleValues(expr Expr) bool {
switch expr.(type) {
case *FuncCallExpr, *Comma3Expr:
return true
default:
return false
}
}

// LogicalOpExpr represents a logical operator (and, or).
type LogicalOpExpr struct {
ExprBase
Expand Down
16 changes: 16 additions & 0 deletions compiler/bind/fieldpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,19 @@ func displayFieldPathKey(path string) string {

return path
}

// DirectFieldNameFromKey returns the field name for a one-segment string-like
// field key. Numeric indexes and nested paths do not describe direct prototype
// fields and are rejected.
func DirectFieldNameFromKey(path string) (string, bool) {
segs := pathkey.ParseSuffix(path)
if len(segs) != 1 {
return "", false
}
switch segs[0].Kind {
case constraint.SegmentField, constraint.SegmentIndexString:
return segs[0].Name, segs[0].Name != ""
default:
return "", false
}
}
38 changes: 38 additions & 0 deletions compiler/bind/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ type fieldPathKey struct {
path string
}

// FieldSymbolRef identifies a direct field symbol rooted at a base symbol.
type FieldSymbolRef struct {
Name string
Symbol cfg.SymbolID
}

// NewBindingTable creates an empty binding table with all maps initialized.
func NewBindingTable() *BindingTable {
return NewBindingTableWithHint(0, 0)
Expand Down Expand Up @@ -409,6 +415,38 @@ func (t *BindingTable) FieldSymbol(baseSym cfg.SymbolID, path string) (cfg.Symbo
return sym, ok
}

// DirectFieldSymbols returns direct field symbols rooted at baseSym.
//
// Only one-segment string-like fields are returned; nested paths and numeric
// indexes are intentionally excluded because they are not fields on the base
// prototype itself.
func (t *BindingTable) DirectFieldSymbols(baseSym cfg.SymbolID) []FieldSymbolRef {
if t == nil || baseSym == 0 || len(t.fieldSymbols) == 0 {
return nil
}
out := make([]FieldSymbolRef, 0)
for key, sym := range t.fieldSymbols {
if key.base != baseSym || sym == 0 {
continue
}
name, ok := DirectFieldNameFromKey(key.path)
if !ok {
continue
}
out = append(out, FieldSymbolRef{Name: name, Symbol: sym})
}
if len(out) == 0 {
return nil
}
sort.Slice(out, func(i, j int) bool {
if out[i].Name == out[j].Name {
return out[i].Symbol < out[j].Symbol
}
return out[i].Name < out[j].Name
})
return out
}

// GetOrCreateFuncLitSymbol returns or creates a symbol for an anonymous function.
//
// Anonymous functions (function literals) need symbols for type assignment
Expand Down
45 changes: 45 additions & 0 deletions compiler/bind/table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,51 @@ func TestBindingTable_FieldSymbol_NormalizesLegacyBracketStringKey(t *testing.T)
}
}

func TestBindingTable_DirectFieldSymbols(t *testing.T) {
table := NewBindingTable()
baseSym := cfg.NextSymbolID()
otherSym := cfg.NextSymbolID()

beta := table.GetOrCreateFieldSymbol(baseSym, "beta")
alpha := table.GetOrCreateFieldSymbol(baseSym, "alpha")
nested := table.GetOrCreateFieldSymbol(baseSym, "alpha.deep")
indexKey, ok := FieldPathKeyFromSegments([]constraint.Segment{
{Kind: constraint.SegmentIndexString, Name: "quoted-key"},
})
if !ok {
t.Fatal("expected canonical string-index key")
}
quoted := table.GetOrCreateFieldSymbol(baseSym, indexKey)
numericKey, ok := FieldPathKeyFromSegments([]constraint.Segment{
{Kind: constraint.SegmentIndexInt, Index: 1},
})
if !ok {
t.Fatal("expected canonical int-index key")
}
_ = table.GetOrCreateFieldSymbol(baseSym, numericKey)
_ = table.GetOrCreateFieldSymbol(otherSym, "alpha")

got := table.DirectFieldSymbols(baseSym)
want := []FieldSymbolRef{
{Name: "alpha", Symbol: alpha},
{Name: "beta", Symbol: beta},
{Name: "quoted-key", Symbol: quoted},
}
if len(got) != len(want) {
t.Fatalf("DirectFieldSymbols length = %d, want %d; got %#v", len(got), len(want), got)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("DirectFieldSymbols[%d] = %#v, want %#v", i, got[i], want[i])
}
}
for _, ref := range got {
if ref.Symbol == nested {
t.Fatal("nested field path should not be returned as a direct field")
}
}
}

func TestBindingTable_GetOrCreateFieldSymbol_InvalidPathRejected(t *testing.T) {
table := NewBindingTable()
baseSym := cfg.NextSymbolID()
Expand Down
138 changes: 80 additions & 58 deletions compiler/cfg/analysis/dominators.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ type rpoReader interface {
RPOReadOnly() []basecfg.Point
}

type immediateDominatorData struct {
rpo []basecfg.Point
rpoNum []int
idomByPoint []basecfg.Point
hasIDom []bool
}

func predecessorsOf(g basecfg.Graph, point basecfg.Point) []basecfg.Point {
if direct, ok := g.(predecessorsReader); ok {
return direct.PredecessorsReadOnly(point)
Expand All @@ -57,130 +64,140 @@ func rpoOf(g basecfg.Graph) []basecfg.Point {
return g.RPO()
}

// ComputeDominators computes immediate dominators and the dominator tree.
// Uses the Cooper-Harvey-Kennedy algorithm with RPO iteration.
func ComputeDominators(g basecfg.Graph) (idom map[basecfg.Point]basecfg.Point, domTree map[basecfg.Point][]basecfg.Point) {
func computeImmediateDominatorData(g basecfg.Graph) immediateDominatorData {
rpo := rpoOf(g)
if len(rpo) == 0 {
return make(map[basecfg.Point]basecfg.Point), make(map[basecfg.Point][]basecfg.Point)
return immediateDominatorData{}
}

graphSize := g.Size()

if graphSize == 0 {
return make(map[basecfg.Point]basecfg.Point), make(map[basecfg.Point][]basecfg.Point)
return immediateDominatorData{}
}

// Build RPO number lookup for intersection and deterministic sorting.
rpoNum := make([]int, graphSize)
for i, p := range rpo {
if int(p) >= graphSize {
continue
}

rpoNum[p] = i
}

entry := g.Entry()
if int(entry) >= graphSize {
return make(map[basecfg.Point]basecfg.Point), make(map[basecfg.Point][]basecfg.Point)
return immediateDominatorData{}
}

idomByPoint := make([]basecfg.Point, graphSize)
hasIDom := make([]bool, graphSize)
idomByPoint[entry] = entry
hasIDom[entry] = true

// intersect finds the common dominator of two nodes
intersect := func(b1, b2 basecfg.Point) basecfg.Point {
finger1, finger2 := b1, b2

for finger1 != finger2 {
for rpoNum[finger1] > rpoNum[finger2] {
finger1 = idomByPoint[finger1]
intersect := func(pointA, pointB basecfg.Point) basecfg.Point {
fingerA, fingerB := pointA, pointB
for fingerA != fingerB {
for rpoNum[fingerA] > rpoNum[fingerB] {
fingerA = idomByPoint[fingerA]
}

for rpoNum[finger2] > rpoNum[finger1] {
finger2 = idomByPoint[finger2]
for rpoNum[fingerB] > rpoNum[fingerA] {
fingerB = idomByPoint[fingerB]
}
}

return finger1
return fingerA
}

// Iterate until fixed point
changed := true
for changed {
changed = false
for _, b := range rpo {
if b == entry {
continue
}

if int(b) >= graphSize {
for _, block := range rpo {
if block == entry || int(block) >= graphSize {
continue
}

preds := predecessorsOf(g, b)
preds := predecessorsOf(g, block)
if len(preds) == 0 {
continue
}

// Find first predecessor with defined idom
var newIdom basecfg.Point

var newIDom basecfg.Point
found := false

for _, p := range preds {
if int(p) >= graphSize {
for _, pred := range preds {
predIdx := int(pred)
if predIdx >= graphSize {
continue
}

if hasIDom[p] {
newIdom = p
if hasIDom[predIdx] {
newIDom = pred
found = true

break
}
}

if !found {
continue
}

// Intersect with other defined predecessors
for _, p := range preds {
if p == newIdom {
for _, pred := range preds {
if pred == newIDom {
continue
}

if int(p) >= graphSize {
predIdx := int(pred)
if predIdx >= graphSize {
continue
}

if hasIDom[p] {
newIdom = intersect(p, newIdom)
if hasIDom[predIdx] {
newIDom = intersect(pred, newIDom)
}
}

if !hasIDom[b] || idomByPoint[b] != newIdom {
idomByPoint[b] = newIdom
hasIDom[b] = true
blockIdx := int(block)
if !hasIDom[blockIdx] || idomByPoint[blockIdx] != newIDom {
idomByPoint[blockIdx] = newIDom
hasIDom[blockIdx] = true
changed = true
}
}
}

idom = make(map[basecfg.Point]basecfg.Point, len(rpo))
for _, point := range rpo {
if int(point) >= graphSize || !hasIDom[point] {
return immediateDominatorData{
rpo: rpo,
rpoNum: rpoNum,
idomByPoint: idomByPoint,
hasIDom: hasIDom,
}
}

func (d immediateDominatorData) asMap() map[basecfg.Point]basecfg.Point {
if len(d.rpo) == 0 {
return make(map[basecfg.Point]basecfg.Point)
}
idom := make(map[basecfg.Point]basecfg.Point, len(d.rpo))
for _, point := range d.rpo {
idx := int(point)
if idx >= len(d.hasIDom) || !d.hasIDom[idx] {
continue
}
idom[point] = d.idomByPoint[idx]
}
return idom
}

// ComputeImmediateDominators computes only the immediate-dominator map.
//
// Use this when callers only need dominance predicates. It avoids building the
// dominator tree, which is meaningful allocation in hot type-checking paths.
func ComputeImmediateDominators(g basecfg.Graph) map[basecfg.Point]basecfg.Point {
return computeImmediateDominatorData(g).asMap()
}

idom[point] = idomByPoint[point]
// ComputeDominators computes immediate dominators and the dominator tree.
// Uses the Cooper-Harvey-Kennedy algorithm with RPO iteration.
func ComputeDominators(g basecfg.Graph) (idom map[basecfg.Point]basecfg.Point, domTree map[basecfg.Point][]basecfg.Point) {
data := computeImmediateDominatorData(g)
if len(data.rpo) == 0 {
return make(map[basecfg.Point]basecfg.Point), make(map[basecfg.Point][]basecfg.Point)
}

// Build dominator tree from idom
idom = data.asMap()
domTree = make(map[basecfg.Point][]basecfg.Point, len(idom))

for n, dom := range idom {
Expand All @@ -192,10 +209,10 @@ func ComputeDominators(g basecfg.Graph) (idom map[basecfg.Point]basecfg.Point, d
// Sort children for deterministic order.
for p := range domTree {
slices.SortFunc(domTree[p], func(a, b basecfg.Point) int {
if rpoNum[a] < rpoNum[b] {
if data.rpoNum[a] < data.rpoNum[b] {
return -1
}
if rpoNum[a] > rpoNum[b] {
if data.rpoNum[a] > data.rpoNum[b] {
return 1
}
return 0
Expand Down Expand Up @@ -599,6 +616,11 @@ func ComputePostDominators(graph basecfg.Graph) (map[basecfg.Point]basecfg.Point
return ComputeDominators(&reversedGraph{g: graph})
}

// ComputeImmediatePostDominators computes only the immediate post-dominator map.
func ComputeImmediatePostDominators(graph basecfg.Graph) map[basecfg.Point]basecfg.Point {
return ComputeImmediateDominators(&reversedGraph{g: graph})
}

// PostDominates returns true if a post-dominates b (a is on every path from b to exit).
func PostDominates(postIdom map[basecfg.Point]basecfg.Point, pointA, pointB basecfg.Point) bool {
return Dominates(postIdom, pointA, pointB)
Expand Down
Loading