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.