Skip to content

Builtins and Runtime Semantics

FunctionSignatureDescription
putchar(c: u32) -> u32Output single character
print(n: int)Print integer
println(n: int)Print integer with newline
puts(s: string)Print string
len(x) -> intLength of string, array, slice, or &[]T
cap(x) -> intCapacity of slice or &[]T
append(s: []T, elem: T) -> []TAppend to slice (Go semantics)
str(x) -> stringConvert int / bool / float to string
string(ptr: *T, len: int) -> stringConstruct string from pointer + length
string(s: []byte) -> stringConstruct string from byte slice
malloc(size: i64) -> *i8Allocate heap memory
free(ptr: *i8)Free heap memory
calloc(count: i64, size: i64) -> *i8Allocate zeroed memory
realloc(ptr: *i8, size: i64) -> *i8Resize allocation
sbrk(increment: i32) -> *i8Extend heap (POSIX)
panic(msg: string) -> unitHalt with message (trap 1, error code 4)
assert(cond: bool, msg: string) -> unitPanic with msg if cond is false
expect(actual: i64, expected: i64, msg: string) -> unitPanic with "msg: expected N, got M" if values differ
abort()Terminate execution (trap 1, error code 3)
sizeof(type-or-expr) -> intSize in bytes

User-defined functions shadow builtins of the same name.

wrapping_add / wrapping_sub / wrapping_mul / saturating_add / saturating_sub / saturating_mul — see the Integer Overflow section of Types.

  • Signed: i8i16i32i64
  • Unsigned: u8u16u32u64
  • Cross-sign: u8i16 (unsigned fits in wider signed)
  • Float: f32f64
  • Int to float: any integer → f32 or f64

Operations between signed and unsigned types are allowed when the unsigned value fits entirely within the signed type’s range:

var b: byte = 200 // u8
var x: int = b + 1 // OK: u8 fits in i32
if b == 0 then ... // OK: u8 compared with i32 literal
var big: u32 = 100
var y: int = big + 1 // ERROR: u32 doesn't fit in i32
  • boolint: use int(flag) or bool(n)
  • int ↔ pointer: use *i8(addr) or i64(ptr)
  • *TT (except self inside methods): write *ptr

Sysl has three allocation modes: stack-allocated values, ref-counted &T, and raw *T. Ref-counted values can trigger a user-defined Type.deinit() when the refcount reaches zero.

For heap-allocated byte buffers, use malloc / free. For ref-counted arrays, new [n]T returns an &[]T that is freed automatically.

Codegen emits trap 1 for runtime errors. On the OS, the trap handler terminates the faulting thread and outputs !N where N is the error code. On bare metal, execution halts.

Error codeCondition
1Array / slice index out of bounds
2Null pointer (malloc returned null)
3abort() called
4panic() or assert() failure

Contract violations (require / ensure) trap through the same path.

#if DEBUG
var verbose = true
#else
var verbose = false
#endif
#if !BARE_METAL
import posix.stdlib.*
#endif
#if TARGET == "trisc"
extern halt()
#endif
#if VERSION != "1.0"
import new_api.*
#endif

Condition forms:

  • #if SYMBOL — true if the symbol is defined and not "false", "0", or ""
  • #if !SYMBOL — negation
  • #if SYMBOL == "value" — string equality
  • #if SYMBOL != "value" — string inequality
RegisterPurpose
r0Zero register (hardwired to 0)
r1First argument / return value
r2 – r3Scratch (caller-saved)
r4Call address temp
r5Frame pointer
r6Link register (return address)
r7Stack pointer
  • At most one scalar argument in r1; additional arguments pushed right-to-left on the stack.
  • Struct / string return: caller allocates a return slot and passes a hidden pointer as the first arg in r1.
  • String arguments: 16 bytes {ptr, len} pushed on the stack.
  • Closures use r3 as the hidden env pointer.
  • mul Rd, Rs1, Rs2 writes high bits to r((d + 1) & 7) — never use mul r4 / r5 / r6 as destination.