Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
81 changes: 20 additions & 61 deletions compiler/array.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,9 @@ func (c *Compiler) ArraySetCells(vec llvm.Value, cells []*Symbol, exprs []ast.Ex
func (c *Compiler) compileArrayExpression(e *ast.ArrayLiteral, _ []*ast.Identifier) (res []*Symbol) {
lit, info := c.resolveArrayLiteralRewrite(e)

// If ArrayLiteral has ranges, use with-loops path which creates accumulator
// pendingLoopRanges will filter already-bound ranges to prevent double-looping
if len(info.Ranges) == 0 {
// Array literals materialize their own collection domain instead of
// inheriting the parent expression's outer loop shape.
if len(info.CollectRanges) == 0 {
return c.compileArrayLiteralImmediate(lit, info)
}

Expand Down Expand Up @@ -384,7 +384,7 @@ func (c *Compiler) compileArrayLiteralWithLoops(lit *ast.ArrayLiteral, info *Exp
elemType := arr.ColTypes[0]
acc := c.NewArrayAccumulator(arr)

c.withLoopNestVersioned(info.Ranges, lit, func() {
c.withCollectLoopNest(info.CollectRanges, func() {
for _, cell := range lit.Rows[0] {
c.compileAccumCell(acc, cell, elemType)
}
Expand All @@ -393,16 +393,17 @@ func (c *Compiler) compileArrayLiteralWithLoops(lit *ast.ArrayLiteral, info *Exp
return []*Symbol{c.ArrayAccResult(acc)}
}

// withValueRanges resolves the solver rewrite on a literal, then either
// wraps body in a loop nest (when the literal has value-level ranges) or
// calls it directly. The resolved literal is passed to body.
func (c *Compiler) withValueRanges(lit *ast.ArrayLiteral, body func(*ast.ArrayLiteral)) {
resolved, valueInfo := c.resolveArrayLiteralRewrite(lit)
if len(valueInfo.Ranges) == 0 {
// withPendingLiteralRanges resolves the solver rewrite on a literal, then
// iterates only the literal ranges still pending in the current outer context.
// This is used by top-level ranged accumulation, not by internal collection
// materialization.
func (c *Compiler) withPendingLiteralRanges(lit *ast.ArrayLiteral, body func(*ast.ArrayLiteral)) {
resolved, literalInfo := c.resolveArrayLiteralRewrite(lit)
if len(literalInfo.CollectRanges) == 0 {
body(resolved)
return
}
c.withLoopNest(valueInfo.Ranges, func() { body(resolved) })
c.withLoopNest(literalInfo.CollectRanges, func() { body(resolved) })
}

// compileAccumCell compiles one cell under a fresh bounds guard and pushes
Expand Down Expand Up @@ -435,17 +436,17 @@ func (c *Compiler) compileArrayLiteralCellExpr(cell ast.Expression) []*Symbol {
// accumulator with per-cell bounds guards.
func (c *Compiler) appendArrayLiteral(acc *ArrayAccumulator, lit *ast.ArrayLiteral) {
elemType := acc.ElemType
c.withValueRanges(lit, func(resolved *ast.ArrayLiteral) {
c.withPendingLiteralRanges(lit, func(resolved *ast.ArrayLiteral) {
for _, cell := range resolved.Rows[0] {
c.compileAccumCell(acc, cell, elemType)
}
})
}

// appendArrayLiterals dispatches to the appropriate accumulation strategy for
// top-level 1D array-literal outputs from ranged conditional lowering. Single
// values use the direct per-cell path; tuples share one bounds guard so
// remaining guarded write/skip decisions stay synchronized across outputs.
// top-level 1D array-literal outputs from ranged conditional lowering.
// Each collector owns its own local value-range domain and appends
// independently of sibling array outputs.
func (c *Compiler) appendArrayLiterals(accs []*ArrayAccumulator, values []*ast.ArrayLiteral) {
if len(values) == 1 {
c.appendArrayLiteral(accs[0], values[0])
Expand All @@ -454,54 +455,12 @@ func (c *Compiler) appendArrayLiterals(accs []*ArrayAccumulator, values []*ast.A
c.appendTupleArrayLiterals(accs, values)
}

// appendTupleArrayLiterals compiles cells from multiple accumulating
// array-literal outputs under a single shared bounds guard. Literal cells
// preserve shape via zero-fill, while any remaining guarded failures still
// keep the tuple outputs synchronized per iteration.
// appendTupleArrayLiterals appends each accumulating array-literal output
// independently. Local ranges belong to the collector that mentions them and
// do not leak across sibling array outputs in the same tuple assignment.
func (c *Compiler) appendTupleArrayLiterals(accs []*ArrayAccumulator, values []*ast.ArrayLiteral) {
c.pushBoundsGuard("tuple_bounds_guard")

accSyms := make([][]*Symbol, len(accs))
accCells := make([][]ast.Expression, len(accs))
for i, lit := range values {
c.withValueRanges(lit, func(resolved *ast.ArrayLiteral) {
for _, cell := range resolved.Rows[0] {
vals := c.compileArrayLiteralCellExpr(cell)
accSyms[i] = append(accSyms[i], c.derefIfPointer(vals[0], ""))
accCells[i] = append(accCells[i], cell)
}
})
}

if !c.withActiveBoundsGuard(
"tuple_ok",
"tuple_push",
"tuple_skip",
"tuple_cont",
func() { c.pushTupleCells(accs, accSyms, accCells) },
func() { c.freeTupleCells(accSyms, accCells) },
) {
c.pushTupleCells(accs, accSyms, accCells)
}
c.popBoundsGuard()
}

// pushTupleCells pushes all compiled tuple cells into their accumulators.
func (c *Compiler) pushTupleCells(accs []*ArrayAccumulator, accSyms [][]*Symbol, accCells [][]ast.Expression) {
for i, acc := range accs {
for j, sym := range accSyms[i] {
_, isIdent := accCells[i][j].(*ast.Identifier)
c.pushAccumCellValue(acc, sym, !isIdent, acc.ElemType)
}
}
}

// freeTupleCells frees all compiled tuple cell temporaries.
func (c *Compiler) freeTupleCells(accSyms [][]*Symbol, accCells [][]ast.Expression) {
for i := range accSyms {
for j := range accSyms[i] {
c.freeTemporary(accCells[i][j], []*Symbol{accSyms[i][j]})
}
c.appendArrayLiteral(accs[i], lit)
}
}

Expand Down
40 changes: 19 additions & 21 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,8 @@ type Symbol struct {
// - Borrowed tracks lifetime/ownership (cleanup must skip when true).

type FuncArgs struct {
Inputs []*Symbol // lowered function inputs (range iterators remain pointer-backed)
IterIndices []int // Indices of iterator params
Iters map[string]*Symbol // Current iterator values during loop
Inputs []*Symbol // lowered function inputs (range iterators remain pointer-backed)
IterIndices []int // Indices of iterator params
}

type callArg struct {
Expand Down Expand Up @@ -2344,7 +2343,6 @@ func (c *Compiler) compileFuncIter(template *ast.FuncStatement, inputs []*Symbol
fa := &FuncArgs{
Inputs: inputs,
IterIndices: iterIndices,
Iters: make(map[string]*Symbol),
}
return c.funcLoopNest(template, fa, 0, currentOutput)
}
Expand All @@ -2366,7 +2364,7 @@ func (c *Compiler) compileFuncBlock(template *ast.FuncStatement, sig *callSignat
c.bindFuncOutputs(template, outputs)

if len(iterIndices) == 0 {
c.compileBlockWithArgs(template, map[string]*Symbol{}, map[string]*Symbol{})
c.compileBlockWithArgs(template, nil)
} else if sig.ABI.Return.Mode == ABIReturnDirect {
// ABIReturnDirect is currently a single scalar output, so the seeded
// binding above becomes the initial loop-carried SSA state here.
Expand Down Expand Up @@ -2483,25 +2481,33 @@ func (c *Compiler) iterOverArrayRangeState(arrRangeSym *Symbol, currentOutput *S

func (c *Compiler) funcLoopNest(fn *ast.FuncStatement, fa *FuncArgs, level int, currentOutput *Symbol) *Symbol {
if level == len(fa.IterIndices) {
PushScope(&c.Scopes, BlockScope)
defer c.popScope()
if currentOutput == nil {
c.compileBlockWithArgs(fn, map[string]*Symbol{}, fa.Iters)
c.compileBlockWithArgs(fn, nil)
return nil
}
return c.compileDirectOutputIterBody(fn, fa.Iters, currentOutput)
return c.compileDirectOutputIterBody(fn, currentOutput)
}

paramIdx := fa.IterIndices[level]
input := fa.Inputs[paramIdx]
name := fn.Parameters[paramIdx].Value

next := func(iterVal llvm.Value, iterType Type, current *Symbol) *Symbol {
fa.Iters[name] = &Symbol{
iterSym := &Symbol{
Val: iterVal,
Type: iterType,
FuncArg: true,
Borrowed: true,
ReadOnly: false,
}
// Function iterator params mirror statement loops: each nested iterator
// level gets its own shadow scope, so pre-iteration lookup can peel back
// one level at a time structurally.
PushIterScope(&c.Scopes)
Put(c.Scopes, name, iterSym)
defer c.popScope()
return c.funcLoopNest(fn, fa, level+1, current)
}

Expand All @@ -2528,30 +2534,22 @@ func (c *Compiler) funcLoopNest(fn *ast.FuncStatement, fa *FuncArgs, level int,
default:
panic("unsupported iterator kind in funcLoopNest")
}
delete(fa.Iters, name)
return result
}

func (c *Compiler) compileDirectOutputIterBody(fn *ast.FuncStatement, iters map[string]*Symbol, currentOutput *Symbol) *Symbol {
PushScope(&c.Scopes, BlockScope)
defer c.popScope()

PutBulk(c.Scopes, iters)
func (c *Compiler) compileDirectOutputIterBody(fn *ast.FuncStatement, currentOutput *Symbol) *Symbol {
// Direct-return ABI is single-output today, so the loop body only needs the
// current scalar output binding for fn.Outputs[0].
Put(c.Scopes, fn.Outputs[0].Value, currentOutput)

for _, stmt := range fn.Body.Statements {
c.compileStatement(stmt)
}
c.compileBlockWithArgs(fn, map[string]*Symbol{fn.Outputs[0].Value: currentOutput})

output, _ := c.localValSymbol(fn.Outputs[0].Value, fn.Outputs[0].Value+"_iter_out")
return output
}

func (c *Compiler) compileBlockWithArgs(fn *ast.FuncStatement, scalars map[string]*Symbol, iters map[string]*Symbol) {
// compileBlockWithArgs executes a function body in the writable scope prepared
// by the caller, seeding any extra scalar bindings needed for that body entry.
func (c *Compiler) compileBlockWithArgs(fn *ast.FuncStatement, scalars map[string]*Symbol) {
PutBulk(c.Scopes, scalars)
PutBulk(c.Scopes, iters)

for _, stmt := range fn.Body.Statements {
c.compileStatement(stmt)
Expand Down
Loading
Loading