📖 Документация Qumir

← Вернуться в Playground

Array Representation

Kumir arrays are written with таб:

цел таб v[1:n]
вещ таб a[0:n-1, 0:m-1]

The AST type is TArrayType(elementType, arity). The bounds are not part of the type identity itself; they live on the declaration (TVarStmt / function parameter) as one [left:right] pair per dimension. In core/AST goldens this shows up as a type plus a separate bounds list:

(var a <array f64 2> [0 (- n 1)] [0 (- m 1)])

Runtime Value

At IR/LLVM level an array value is just a pointer to the first element:

FromAstType(TArrayType(T, n)) == Ptr(FromAstType(T))

There is no array header in the runtime allocation: no length, no bounds, no rank, and no stride table are stored next to the elements. Allocation is done through the System runtime:

void* array_create(size_t sizeInBytes);
void  array_destroy(void* ptr);
void  array_str_destroy(void* ptr, size_t arraySize);

array_create allocates an 8-byte-aligned zero-filled byte buffer. Ordinary arrays are destroyed with array_destroy. Arrays of лит are destroyed with array_str_destroy, which releases every stored string pointer before freeing the buffer.

Bounds and Layout Metadata

Because the runtime pointer has no header, the compiler creates separate hidden storage for array layout metadata. During IR lowering, LowerArrayLayout evaluates each declared bound and stores, per dimension:

Field Meaning
lower bound declared left bound, e.g. 1 in [1:n]
dim size right - left + 1
stride cumulative element count from this dimension

The same layout lowering runs for local/global array declarations and for array parameters inside the callee. For function parameters, the declared parameter bounds are expressions in the callee scope, commonly using scalar size parameters:

алг matvec(цел n, вещ таб x[0:n-1], рез вещ таб y[0:n-1])

Here n is passed separately, and the callee reconstructs the layout metadata for x and y from its parameter declarations.

Element Addressing

Arrays are flattened into one row-major allocation: the last dimension is contiguous. For an access a[i0, i1, ..., ik], lowering computes a byte offset equivalent to:

linear =
    (i0 - lb0) * size1 * size2 * ... * sizek
  + (i1 - lb1) * size2 * ... * sizek
  + ...
  + (ik - lbk)

byteOffset = linear * sizeof(element)
address    = basePointer + byteOffset

For example, вещ таб a[0:n-1, 0:m-1] stores a[i,j] at:

base + ((i * m) + j) * 8

The implementation emits normal pointer arithmetic in IR:

offset = LowerIndices(symbol, indices, elemByteSize)
addr   = arrayPtr + offset

Loads use lde; scalar stores use ste; struct elements are copied with copy because the element value may be address-backed.

Passing Arrays to Functions

Array arguments are passed by pointer. The callee receives the same backing allocation that the caller owns; no element copy is made at the call boundary. This is why procedures can fill output arrays efficiently:

алг fill(цел n, рез цел таб a[0:n-1])
нач
  цел i
  нц для i от 0 до n-1
    a[i] := i
  кц
кон

Parameter modes are enforced by semantic type flags:

Mode Effect for arrays
арг input array; elements may be read according to type flags
рез output array; reading elements is rejected
арг рез in/out array; elements may be read and written

The ABI is still pointer-based in all three cases. The difference is semantic checking and generated read/write permissions, not a different runtime representation.