Skip to main content

Tensors

A tensor is a rectangular block of values of one element type, with a shape known at type-check time. esque tensors are first-class values.

Shape grammar​

TensorType = ElemType "[" Shape "]"
Shape = ShapeExpr ("," ShapeExpr)*
ShapeExpr = ShapeTerm (("+" | "-") ShapeTerm)*
ShapeTerm = ShapeFactor (("*" | "/") ShapeFactor)*
ShapeFactor = nat_literal | shape_var | "(" ShapeExpr ")"

Shape arithmetic in type position is parsed today. Evaluation of non-trivial shape expressions awaits the planned const-eval work.

Tensor literals​

[1.0, 2.0, 3.0] # f32[3]
[1, 2, 3, 4] # i32[4]
[[1.0, 2.0], [3.0, 4.0]] # f32[2, 2]

The element type is inferred from the first element. The shape is inferred from the literal's structure. Non-rectangular nested literals are a type error.

Range expressions​

0..5 # i32[5] = [0, 1, 2, 3, 4] (exclusive)
1..=5 # i32[5] = [1, 2, 3, 4, 5] (inclusive)

Both bounds must be integer literals today. The element type is i32. The size is capped at 1 << 20 to avoid runaway .rodata growth.

Element-wise operators​

OpEffect
.+element-wise add
.-element-wise sub
.*element-wise multiply (Hadamard)
./element-wise divide
.%element-wise modulo

Both operands must have the same type, including shape. There is no broadcasting today; matched shapes only.

Reductions​

<op>/(v) folds op across v's elements:

+/(v) # sum
-/(v) # running difference (left fold)
*/(v) # product
//(v) # running quotient (left fold)

Reductions take a tensor and return a scalar of the element type. Reductions on rank-2+ tensors reduce all elements (not "along an axis"); axis-aware reductions are planned.

Codegen for +/​

Element typeShapeStrategy
f32mult of 8AVX2 vaddps + vhaddps
f32mult of 4SSE addps + SSE3 haddps
f32irregular[8] + [4] + scalar tail
i32anyscalar add chain

Matrix multiply​

a @ b

a: f32[M, K], b: f32[K, N], result f32[M, N]. The token is parsed at precedence 35, but the codegen path is not yet wired. Until it is, write the matmul explicitly with tabulate/+/.

Indexing​

There is no element-access syntax today. To get one element, use the loop primitives or destructure (when records arrive). For debugging, each(v, print_i32) walks a tensor.

Stride / layout​

Tensors are densely packed in row-major order in memory. Constant tensors live in .rodata aligned to 16 bytes (so the SIMD loads are aligned). Local tensors live in stack slots.

Lifetime​

All tensors today are stack- or rodata-bound. Heap allocation arrives with dynamic shape support.