Skip to content
Execution

Execution

The language page says what is allowed. This page says what a program does when it runs, in the detail needed to predict it exactly.

Order of evaluation

Evaluation is strict and goes left to right.

  • The operands of an operator are evaluated left first, then right.
  • The arguments of a call are evaluated in the order written, before the call.
  • The fields of a record or union case are evaluated in the order written, which is the declared order.
  • The lines of a block run in order.
  • and and or evaluate the right side only when the left side has not already decided the result.
  • An if evaluates its condition, then exactly one branch.
  • A match evaluates its value once, then exactly one arm.

Since nothing can be changed after it is made, the order is visible only through input, output, a raw_ function, or a program that stops.

Values in memory

Every value is one 64-bit word.

TypeThe word holds
intThe number itself.
bool0 for false, 1 for true.
bytesThe address of a block: a word holding the length, the bytes, then one zero byte.
a recordThe address of a block with one word per field, in declared order.
a unionThe address of a block: a word holding the case’s position in the declaration, counted from 0, then one word per field.

Passing a record, a union value or bytes to a function passes the address. Nothing is copied.

The heap

Blocks for records, union values and new bytes come from a heap that grows upward, one memory page at a time, as the program asks for more. Nothing is returned to it while the program runs. A program that keeps making values keeps growing.

Bytes literals are not on the heap. They are stored inside the executable, which the kernel loads as memory that cannot be written. Writing to a literal’s address with raw_poke ends the program.

raw_heap_mark and raw_heap_reset are the only way to give heap memory back. See built-ins.

Calls and the stack

A call pushes its arguments on the stack in order, runs the function, and takes them off again. Inside a function, each let and each name bound by a match arm takes one more word of stack, released at the end of its block.

A function that calls itself in last position stores the new arguments over its own and jumps back to its start. No stack is used, however many times it repeats.

Any other call uses stack until it returns. The stack is the one the operating system gives the process, commonly eight megabytes. A chain of calls deep enough to exceed it ends the program without a message.

Numbers

+, - and * keep the lowest 64 bits of the true result, so going past either end of the range wraps around.

/ rounds toward zero: 7 / 2 is 3 and -7 / 2 is -3. % is what remains, with the sign of the left side: -7 % 2 is -1.

A divisor of zero stops the program with a message. The smallest int divided by -1 gives the smallest int, and its remainder is 0.

Start and end

A program starts by calling main. When main returns, the program ends with its result as the exit status. The operating system keeps only the lowest eight bits, so a result of 256 is seen as 0.

halt ends the program at once with the status given. A division by zero and panic both end it with status 1 after writing to standard error.

The executable

The compiler writes one file: a static, position-independent 64-bit executable for Linux on x86-64. It has a single segment holding the machine code and the bytes literals, and no symbol or section tables. It uses no shared library and no C library. It talks to the kernel directly.