Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
290222c
feat(parser): standardize struct literal layout
thiremani Jul 23, 2026
2f9765c
feat(compiler)!: finalize range streams at value roots
thiremani Jul 23, 2026
64360e3
refactor(compiler): remove redundant array-range arity check
thiremani Jul 24, 2026
5232ce3
refactor(compiler): trust solved array-range call types
thiremani Jul 24, 2026
19156cb
refactor(compiler): simplify array-range index lowering
thiremani Jul 24, 2026
1c56ad6
fix(compiler)!: preserve conditional scalar outputs
thiremani Jul 24, 2026
4c51094
docs(readme): restore concise memory wording
thiremani Jul 24, 2026
917ab11
refactor(compiler): derive direct seed from return mode
thiremani Jul 24, 2026
59d46ce
refactor(compiler): unify ranged call alias metadata
thiremani Jul 24, 2026
5b1e85f
fix(compiler): instantiate scalar callee variants for named range sel…
thiremani Jul 25, 2026
7a544b3
fix(compiler): treat skippable call outputs as conditional writes
thiremani Jul 25, 2026
e7f9882
fix(compiler): surface array-literal cell errors instead of panicking
thiremani Jul 25, 2026
c79a918
fix(compiler): correct heap-string ownership for comparison operands
thiremani Jul 25, 2026
ea82ea0
refactor(compiler): remove unreferenced helpers
thiremani Jul 25, 2026
61693ce
fix(compiler): skip type-incompatible outputs in parameter alias slots
thiremani Jul 25, 2026
3a6d988
test(compiler): cover alias selectors reached past a skipped output
thiremani Jul 25, 2026
7da9079
test(compiler): cover the pointer-promotion selector gap
thiremani Jul 25, 2026
333ebc1
test(compiler): pin the alias selector value and its target
thiremani Jul 25, 2026
635035f
refactor(compiler): carry the alias selector on the call argument
thiremani Jul 25, 2026
7d0fb6c
fix(compiler): document and cover the indirect alias refinement guard
thiremani Jul 25, 2026
29fed52
test(compiler): cover the ranged conditional reset slot flavor
thiremani Jul 25, 2026
b986fda
feat(compiler): copy bare range descriptors
thiremani Jul 26, 2026
62da7d7
refactor(compiler): localize array cell error recovery
thiremani Jul 26, 2026
fb5e436
fix(compiler): treat non-yielding values as conditional writes
thiremani Jul 26, 2026
542fbd2
fix(compiler)!: classify destination writes per value expression
thiremani Jul 26, 2026
a74e8f1
feat(compiler)!: print Range descriptors as values
thiremani Jul 26, 2026
cbb8614
fix(compiler): decide print descriptor arguments before rewriting
thiremani Jul 26, 2026
852572c
fix(compiler)!: classify collector writes as unconditional
thiremani Jul 26, 2026
a2730e8
docs(strings): describe Range markers as descriptor formatting
thiremani Jul 26, 2026
b53211b
fix(compiler)!: classify ranged-gate collector writes as unconditional
thiremani Jul 26, 2026
7d310c2
test(compiler): cover literal-driver siblings and driven numeric markers
thiremani Jul 26, 2026
2602e3e
docs(strings): define n and qualify the Range %d rule
thiremani Jul 26, 2026
d7610c7
fix(compiler): align gated collector analysis
thiremani Jul 26, 2026
72d7c0a
fix(compiler): unify ranged gate classification
thiremani Jul 27, 2026
2407480
fix(compiler): retain scalar guards in ranged gates
thiremani Jul 27, 2026
c322df6
fix(compiler): preserve collectors behind blocked gates
thiremani Jul 27, 2026
ffdd28b
refactor(compiler): simplify CFG yield classification
thiremani Jul 27, 2026
899fbeb
docs(ir-plan): record range domain ownership for write effects
thiremani Jul 27, 2026
718286f
docs(ir-plan): record structural output spans for template analysis
thiremani Jul 27, 2026
4616800
refactor(compiler): split typed and template CFG analysis
thiremani Jul 27, 2026
ce13031
refactor(compiler): share CFG statement event extraction
thiremani Jul 27, 2026
e0439e0
refactor(compiler): avoid redundant write initialization
thiremani Jul 27, 2026
f97991f
refactor(compiler): initialize CFG write kinds safely
thiremani Jul 28, 2026
934ede8
refactor(compiler): simplify call and output lowering
thiremani Jul 28, 2026
cdb2627
test(compiler): shorten alias test names
thiremani Jul 28, 2026
be081ae
test(compiler): clarify shared-driver fallback
thiremani Jul 28, 2026
cd06fef
refactor(compiler): simplify format marker scan
thiremani Jul 28, 2026
c668120
refactor(compiler): centralize range symbol invariant
thiremani Jul 28, 2026
c1d3ed3
refactor(compiler): reuse print argument range collection
thiremani Jul 28, 2026
6bfa822
refactor(compiler): centralize array range classification
thiremani Jul 28, 2026
b8dc925
refactor(compiler): simplify bare range resolution
thiremani Jul 29, 2026
fbf3055
refactor(compiler): simplify range driver checks
thiremani Jul 29, 2026
9564066
refactor(compiler): narrow expression cache guards
thiremani Jul 29, 2026
0541948
refactor(compiler): make print iteration ownership explicit
thiremani Jul 29, 2026
24e727b
refactor(compiler): simplify shared range detection
thiremani Jul 29, 2026
5861964
refactor(compiler): remove redundant array range arity guard
thiremani Jul 29, 2026
b4792f4
docs: align ABI entry point table
thiremani Jul 29, 2026
7f750f7
docs: consolidate range semantics
thiremani Jul 29, 2026
64bdf8b
test(compiler): strengthen blocked rank-N collector case
thiremani Jul 29, 2026
88cbcf2
test(compiler): consolidate range regression coverage
thiremani Jul 29, 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
2 changes: 1 addition & 1 deletion .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ nfpms:
- apk
file_name_template: '{{ .ProjectName }}_{{ .Version }}_linux_{{ .Arch }}'
homepage: 'https://github.com/{{ .Env.GITHUB_REPOSITORY }}'
maintainer: '{{ or .Env.NFPM_MAINTAINER "Pluto Contributors <opensource@pluto-lang.org>" }}'
maintainer: '{{ or .Env.NFPM_MAINTAINER "Tejas <tejasvph@gmail.com>" }}'
description: Pluto programming language compiler (requires LLVM 22 toolchain on target host).
license: 'Apache-2.0 WITH LLVM-exception'
bindir: /usr/bin
Expand Down
49 changes: 37 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Scope-based memory (no nulls, no out-of-bounds, no GC), and concurrency by const
- Range literals with auto-vectorized execution
- First-class rectangular arrays of any rank, columnar tables, and link semantics
- Scope-based memory: no nulls, no out-of-bounds, no garbage collector
- printf-style formatting; arrays/ranges printable
- printf-style formatting; arrays printable and range streams iterable
- Cross-platform (Linux/macOS/Windows)

## Command name
Expand Down Expand Up @@ -143,18 +143,18 @@ b = Square(2.2) # float specialization
c = Square(arr) # squares each array element
a, b, c

# Range, mask, and filter (non-accumulated)
# Range, mask, and filter (non-collected)
d = Square(1:3) # range: final iteration result
e = Square(arr[1:3]) # array-range: final iteration result
e = Square(arr[1:3]) # range-indexed array: final iteration result
f = Square(arr > 3) # element-wise mask: each element kept where > 3, else 0
g = Square(arr[1:3] > 3) # array-range filter: final iteration result
g = Square(arr[1:3] > 3) # range-indexed filter: final iteration result
d, e, f, g

# Accumulation forms
h = [Square(1:3)] # range accumulation
i = [Square(arr[1:3])] # array-range accumulation
j = [Square((1:3) > 1)] # conditional range accumulation
k = [Square(arr[1:4] > 2)] # conditional array-range accumulation
h = [Square(1:3)] # range collection
i = [Square(arr[1:3])] # range-indexed collection
j = [Square((1:3) > 1)] # conditional range collection
k = [Square(arr[1:4] > 2)] # conditional range-indexed collection
h, i, j, k
```

Expand All @@ -174,13 +174,34 @@ Generated code is equivalent to handwritten specialized code. There is no runtim

## Ranges as the execution primitive

Ranges are first-class values:
A range literal binds an execution domain:

```python
Square(1:5)
i = 0:5
copy = i # same bounds, independent named driver
values = [copy] # [0 1 2 3 4]
last = i + 0 # 4
lastSquare = Square(i) # 16
i # prints the descriptor: 0:5
```

Passing a range to a template executes it across all values. The compiler can map these operations to SIMD instructions — this is range-driven auto-vectorization.
A bare range is a value: assignment copies it and print shows its descriptor.
Using it in an operation creates a ranged computation: the assignment keeps
the final yield, brackets materialize all yields into an array, and passing it
to a template executes the call once for each value. The compiler can map
these operations to SIMD instructions — this is range-driven
auto-vectorization. Each named Range binding is a distinct driver: different
names form a cartesian domain when consumed together, even when their bounds
are equal.

Range-indexed array access follows the same rule:

```python
arr = [10 20 30 40]
i = 1:4
last = arr[i] # 40
selected = [arr[i]] # [20 30 40]
```

Data-parallel execution without explicit loop syntax.

Expand All @@ -195,7 +216,11 @@ x = [1 2 3 4 5]
y = [1.1 2.2 3.3]
```

Arrays are safe by construction — out-of-bounds access is not possible. Comparisons like `arr > 2` produce element-wise masks (each element kept where it holds, else 0) that work anywhere an array does.
Arrays are bounds-checked: an invalid index never reads outside array storage.
In a scalar range stream, invalid selection points yield nothing; inside `[]`,
they zero-fill to preserve shape. Comparisons like `arr > 2` produce
element-wise masks (each element kept where it holds, else 0) that work
anywhere an array does.

The same bracket syntax infers higher-rank arrays and tables without a type keyword:

Expand Down
8 changes: 4 additions & 4 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ type Func struct {
// For example:
//
// p = Person
// :name age
// : name age
// "Tejas" 35
type StructStatement struct {
Token token.Token // The token.ASSIGN token
Expand Down Expand Up @@ -360,8 +360,8 @@ func writeArrayRow(out *bytes.Buffer, row []Expression) {
// Example:
// p = Person
//
// :name age
// "Tejas" 35
// : name age
// "Tejas" 35
type StructLiteral struct {
Token token.Token // the type name token (e.g. "Person")
Headers []token.Token
Expand All @@ -375,7 +375,7 @@ func (sl *StructLiteral) String() string {
out.WriteString(sl.Token.Literal)

if len(sl.Headers) > 0 {
out.WriteString("\n :")
out.WriteString("\n : ")
for i, header := range sl.Headers {
if i > 0 {
out.WriteString(" ")
Expand Down
31 changes: 22 additions & 9 deletions compiler/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,15 @@ type ABIParam struct {
}

type ABIReturn struct {
Mode ABIReturnMode
DirectType Type
OutTypes []Type
HasSeedParam bool
Mode ABIReturnMode
DirectType Type
OutTypes []Type
}

// FuncABI captures the lowered function boundary for one mangled variant.
// Range-bearing variants may need hidden alias/seed state so direct scalar
// params and returns still preserve loop-carried accumulation and empty-range
// no-op semantics inside the callee.
// Direct scalar returns carry a hidden destination seed so a skipped write
// preserves the caller's value. Range-bearing variants may additionally need
// hidden alias state for loop-carried accumulation.
type FuncABI struct {
Params []ABIParam
Return ABIReturn
Expand All @@ -49,6 +48,18 @@ func isDirectScalarABIType(t Type) bool {
}
}

// aliasableOutput reports whether an output can back a parameter's alias slot.
// The hidden selector picks an output by position and the callee then reads that
// storage as the parameter's own type, so the two must lower identically. There
// is no numeric conversion anywhere on this path, and a pointer selected across
// mismatched types would be loaded as the wrong type.
func aliasableOutput(paramType, outputType Type) bool {
if ptr, ok := outputType.(Ptr); ok {
outputType = ptr.Elem
}
return TypeEqual(paramType, outputType)
}

func directScalarABIReturnType(outTypes []Type) (Type, bool) {
if len(outTypes) != 1 {
return nil, false
Expand Down Expand Up @@ -95,9 +106,11 @@ func classifyFuncABI(paramTypes []Type, outTypes []Type) FuncABI {
}

if directType, ok := directScalarABIReturnType(outTypes); ok {
// Whether a function body writes its output conditionally is not part
// of the type-based mangle. Keep the native C ABI stable across body
// changes: direct-return mode always implies a destination seed.
abi.Return.Mode = ABIReturnDirect
abi.Return.DirectType = directType
abi.Return.HasSeedParam = abi.HasRangeParams
}

return abi
Expand Down Expand Up @@ -141,7 +154,7 @@ func (abi FuncABI) AliasFunctionParamIndex(paramIndex int) int {
}

func (abi FuncABI) DirectReturnSeedParamIndex() int {
if abi.Return.Mode != ABIReturnDirect || !abi.Return.HasSeedParam {
if abi.Return.Mode != ABIReturnDirect {
return -1
}
return abi.AliasParamBaseIndex() + abi.NumAliasSlots()
Expand Down
98 changes: 45 additions & 53 deletions compiler/array.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,16 @@ func (c *Compiler) compileArrayLiteralCell(cell ast.Expression, elemType Type, c

c.withArrayLiteralCellMode(func() {
c.compileCondExprValue(cell, llvm.Value{}, func() {
errorsBefore := len(c.Errors)
vals := c.compileExpression(cell, nil)
if len(vals) == 0 {
// Only an error from this lowering explains the missing value;
// leave the seed in place so that diagnostic can surface.
if len(c.Errors) > errorsBefore {
return
}
panic("internal: array cell lowering produced no value and recorded no error")
}
c.storeArrayCellSlotWhenInBounds(cellSlot, vals, cell)
})
})
Expand Down Expand Up @@ -953,19 +962,6 @@ func (c *Compiler) arrayFormatArg(s *Symbol, info ArrayInfo, elementFormat strin
return c.builder.CreateCall(fnTy, fn, formatArgs, "arr_format")
}

func (c *Compiler) arrayRangeStrArgs(s *Symbol) (arrayStr llvm.Value, rangeStr llvm.Value) {
arrRange := s.Type.(ArrayRange)
agg := s.Val
arrPtr := c.builder.CreateExtractValue(agg, 0, "array_range_arr")
arrSym := &Symbol{Val: arrPtr, Type: arrRange.Array}
arrayStr = c.arrayStrArg(arrSym)

rangeVal := c.builder.CreateExtractValue(agg, 1, "array_range_rng")
rangeSym := &Symbol{Val: rangeVal, Type: arrRange.Range}
rangeStr = c.rangeStrArg(rangeSym)
return
}

func (c *Compiler) compileArrayRangeOperands(expr *ast.ArrayRangeExpression) (*Symbol, *Symbol, Array) {
arrayLoadName := ""
if arrayIdent, ok := expr.Array.(*ast.Identifier); ok {
Expand All @@ -982,6 +978,20 @@ func (c *Compiler) compileArrayRangeOperands(expr *ast.ArrayRangeExpression) (*S
return arraySym, idxSym, arrType
}

// compileArrayRangeCallArg builds the internal descriptor used only at an
// immediate call boundary. Ordinary array indexing still lowers to the final
// selected element/subarray and therefore cannot expose or retain ArrayRange.
func (c *Compiler) compileArrayRangeCallArg(expr *ast.ArrayRangeExpression, typ ArrayRange) *Symbol {
arraySym, rangeSym, _ := c.compileArrayRangeOperands(expr)

_, arrayIsIdent := expr.Array.(*ast.Identifier)
return &Symbol{
Val: c.CreateArrayRange(arraySym.Val, rangeSym.Val, typ),
Type: typ,
Borrowed: arrayIsIdent || arraySym.Borrowed,
}
}

func (c *Compiler) normalizeArrayIndex(idxSym *Symbol) llvm.Value {
idxVal := idxSym.Val
intType, ok := idxSym.Type.(Int)
Expand All @@ -994,9 +1004,14 @@ func (c *Compiler) normalizeArrayIndex(idxSym *Symbol) llvm.Value {
return c.builder.CreateIntCast(idxVal, c.Context.Int64Type(), "arr_idx_cast")
}

func (c *Compiler) storeArrayRangeOutput(output *Symbol, value llvm.Value, valueType Type) {
func (c *Compiler) storeRangedOutput(output *Symbol, value llvm.Value, valueType Type) {
c.freeSymbolValue(output, "old_output")
c.createStore(value, output.Val, valueType)
c.storeSymbolToSlot(
output,
&Symbol{Val: value, Type: valueType},
output.Type.(Ptr).Elem,
"range_output_store",
)
}

func (c *Compiler) compileArraySubarray(array *Symbol, index llvm.Value) *Symbol {
Expand Down Expand Up @@ -1049,19 +1064,12 @@ func (c *Compiler) checkedArraySubarray(array *Symbol, index llvm.Value, inBound
return c.createLoad(resultSlot, resultType, "array_subarray_checked")
}

// compileArrayRangeExpression compiles an array indexing expression.
// If the index is a range (e.g., arr[0:10]), returns an ArrayRange symbol.
// If the index is a scalar (e.g., arr[4]), returns the element.
// We check the actual compiled index type, not cached OutTypes, because
// inside a loop the index may be bound to a scalar even if originally a range.
// compileArrayRangeExpression compiles an array indexing expression. A ranged
// index is always finalized or collected through the surrounding loop; only a
// scalar element/subarray value reaches compileArrayRangeBasic.
func (c *Compiler) compileArrayRangeExpression(expr *ast.ArrayRangeExpression, dest []*ast.Identifier) []*Symbol {
info := c.ExprCache[key(c.FuncNameMangled, expr)]
pending := c.pendingLoopRanges(info.Ranges)
if len(pending) > 0 {
// Bare range indices (arr[i] where i is a range) remain ArrayRange views.
if len(info.OutTypes) > 0 && info.OutTypes[0].Kind() == ArrayRangeKind {
return c.compileArrayRangeBasic(expr)
}
if len(c.pendingLoopRanges(info.Ranges)) > 0 {
return c.compileArrayRangeRanges(info, dest)
}
return c.compileArrayRangeBasic(expr)
Expand All @@ -1070,21 +1078,8 @@ func (c *Compiler) compileArrayRangeExpression(expr *ast.ArrayRangeExpression, d
func (c *Compiler) compileArrayRangeBasic(expr *ast.ArrayRangeExpression) []*Symbol {
arraySym, idxSym, arrType := c.compileArrayRangeOperands(expr)

// Check actual compiled index type to determine ArrayRange vs element access
if idxSym.Type.Kind() == RangeKind {
// ArrayRange from an identifier is a borrowed view into existing storage.
_, arrayIsIdent := expr.Array.(*ast.Identifier)
borrowed := arrayIsIdent || arraySym.Borrowed

arrRange := ArrayRange{
Array: arrType,
Range: idxSym.Type.(Range),
}
return []*Symbol{{
Val: c.CreateArrayRange(arraySym.Val, idxSym.Val, arrRange),
Type: arrRange,
Borrowed: borrowed,
}}
panic("internal: ranged array access reached scalar lowering without an iterator loop")
}
// Scalar element access does not retain the source array pointer.
// Release temporary array sources on all scalar return paths; a consumed
Expand Down Expand Up @@ -1124,7 +1119,8 @@ func (c *Compiler) compileArrayRangeRanges(info *ExprInfo, dest []*ast.Identifie
PushScope(&c.Scopes, BlockScope)
defer c.popScope()

outputs := c.makeOutputs(dest, info.OutTypes, true)
outputs := c.makeSeededTempOutputs(dest, info.OutTypes)
c.bindRangedTempOutputs(dest, outputs)
output := outputs[0]

withCollectorPreparedLoopNest(c, info.Rewrite.(*ast.ArrayRangeExpression), info.Ranges, nil, nil, func(rew *ast.ArrayRangeExpression) {
Expand All @@ -1145,11 +1141,11 @@ func (c *Compiler) compileArrayRangeRanges(info *ExprInfo, dest []*ast.Identifie
if c.currentLoopBoundsMode() == loopBoundsModeAffineFast && c.isFastAffineAccess(rew) {
if arrType.Rank > 1 {
subarray := c.compileArraySubarray(arraySym, idxVal)
c.storeArrayRangeOutput(output, subarray.Val, resultType)
c.storeRangedOutput(output, subarray.Val, resultType)
return
}
elemVal := c.ArrayGet(arraySym, arrElemType, idxVal)
c.storeArrayRangeOutput(output, elemVal, resultType)
c.storeRangedOutput(output, elemVal, resultType)
return
}

Expand All @@ -1160,10 +1156,10 @@ func (c *Compiler) compileArrayRangeRanges(info *ExprInfo, dest []*ast.Identifie
c.builder.SetInsertPointAtEnd(storeBlock)
if arrType.Rank > 1 {
subarray := c.compileArraySubarray(arraySym, idxVal)
c.storeArrayRangeOutput(output, subarray.Val, resultType)
c.storeRangedOutput(output, subarray.Val, resultType)
} else {
elemVal := c.ArrayGet(arraySym, arrElemType, idxVal)
c.storeArrayRangeOutput(output, elemVal, resultType)
c.storeRangedOutput(output, elemVal, resultType)
}
c.builder.CreateBr(contBlock)

Expand All @@ -1172,7 +1168,7 @@ func (c *Compiler) compileArrayRangeRanges(info *ExprInfo, dest []*ast.Identifie
if arrType.Rank > 1 {
zeroVal = c.zeroArraySubarray(arraySym)
}
c.storeArrayRangeOutput(output, zeroVal.Val, zeroVal.Type)
c.storeRangedOutput(output, zeroVal.Val, zeroVal.Type)
c.builder.CreateBr(contBlock)

c.builder.SetInsertPointAtEnd(contBlock)
Expand All @@ -1184,20 +1180,16 @@ func (c *Compiler) compileArrayRangeRanges(info *ExprInfo, dest []*ast.Identifie
c.builder.SetInsertPointAtEnd(storeBlock)
if arrType.Rank > 1 {
subarray := c.compileArraySubarray(arraySym, idxVal)
c.storeArrayRangeOutput(output, subarray.Val, resultType)
c.storeRangedOutput(output, subarray.Val, resultType)
} else {
elemVal := c.ArrayGet(arraySym, arrElemType, idxVal)
c.storeArrayRangeOutput(output, elemVal, resultType)
c.storeRangedOutput(output, elemVal, resultType)
}
c.builder.CreateBr(contBlock)

c.builder.SetInsertPointAtEnd(contBlock)
})
})

elemType := output.Type.(Ptr).Elem
return []*Symbol{{
Val: c.createLoad(output.Val, elemType, "final"),
Type: elemType,
}}
return c.loadOutputValues(outputs, "final")
}
Loading
Loading