ReferenceLimits and Errors

Sandbox limits

Two profiles via VM::with_limits: the same compiler.wasm runs unsandboxed in trusted contexts, clamped in untrusted.

Limitnone() (default)sandbox()What hitting it raises
Max call depth1,000256RecursionError
Max operationsunbounded100,000,000RuntimeError
Max live objects10,000,000100,000MemoryError

Integer width

Two-tier:

  • Inline (fast): 47-bit signed in a NaN-boxed Val. Range +/-2^47 (+/-140_737_488_355_327). One ALU op per arithmetic, no allocation.
  • Wide (slow): i128 in HeapObj::LongInt. Range +/-2^127 - 1. Auto-used when a literal exceeds 47-bit or inline arithmetic overflows.

Outside +/-2^127 raises OverflowError. Promotion is automatic; user code doesn’t see the boundary.

print(140737488355327) # inline, fast path
print(2 ** 47) # 140737488355328: auto-promotes to LongInt
print(2 ** 100) # 1267650600228229401496703205376
try:
  print(2 ** 127) # past the i128 cap
except OverflowError:
  print("overflow")
140737488355327
140737488355328
1267650600228229401496703205376
overflow

Caveats

  • pow(a, b, m) modular: modulus must be < 2^63 (larger overflows i128 in the multiply). Hard cap without arbitrary-precision arithmetic.
  • No CPython-style unbounded ints: by design, edge workloads don’t need wider than 128 bits; crypto-scale math is out of scope.
  • Float vs LongInt mixing: == works (LongInt -> f64), but dict/set hashing follows raw Val bits — {long_int: x} indexed by a same-magnitude float misses. Coerce explicitly.

Triggering limits

# Recursion depth
def loop(n):
  return loop(n + 1)
 
try:
  loop(0)
except RecursionError:
  print("hit max depth")
hit max depth
# Heap quota, a tight loop retaining new objects
try:
  xs = []
  while True:
    xs.append([])
except MemoryError:
  print("hit heap limit")

Source size

Source must be under 10 MiB; larger rejected at lex time.

Token limits

LimitValue
Max indent depth100
Max f-string depth200
Max expression depth200
Max instructions per chunk65,535

Prevent asymmetric DoS: small input producing an exponentially large parse tree.

Error types

Compile-time

Reported as Diagnostic { start, end, msg }, byte offsets into source; line/column computed lazily by render(). Caught before any code runs.

DiagnosticCause
expected X, got 'Y'Unexpected token
'(' was never closed (or '[' / '{')Bracket opened with no matching closer
')' does not match '[', expected ']'Wrong closer kind for innermost opener
unexpected ')', no matching openerCloser with no opener on the stack
unexpected ':' (missing 'if', 'while', 'for', ...)expr: at statement level
unterminated string literalString missing closing quote
unterminated triple-quoted string literalTriple-quoted string hit EOF
f-string was never closedF-string body hit EOF before close
inconsistent indentation: mixing tabs and spacesIndent mixes both whitespace kinds
'break' outside loopMisplaced control keyword
'continue' outside loopMisplaced control keyword
default 'except:' must be lastBare except not at end
expression too deeply nestedPast MAX_EXPR_DEPTH
program too large: exceeded maximum instruction limitPast MAX_INSTRUCTIONS

Runtime

Raised as VmErr; most catchable with try / except.

VariantClass nameWhen
TypeTypeErrorWrong operand type
TypeMsgTypeErrorWrong operand type (with context)
ValueValueErrorRight type, invalid value
AttributeAttributeErrorAttribute not found on object
NameNameErrorUndefined name
ZeroDivZeroDivisionErrorDivision or modulo by zero
OverflowOverflowErrorInteger arithmetic past ±2^127
Raised("KeyError")KeyErrorDict / set lookup miss
Raised("IndexError")IndexErrorSequence index out of range
Raised("StopIteration")StopIterationIterator exhausted
Raised("AssertionError")AssertionErrorFailed assert
Raised("TimeoutError")TimeoutErrorwith_timeout deadline expired
Raised("CancelledError")CancelledErrorUser-thrown cancellation
Raised("SystemExit")SystemExitraise SystemExit(code); uncaught = clean host exit with that code
CallDepthRecursionErrorPast max_calls, or comparing self-referential containers
HeapMemoryErrorPast heap limit
BudgetRuntimeErrorPast op limit
RuntimeRuntimeErrorInternal invariant or unsupported
Raised(custom)User raise X (X is a class or instance; raising a non-exception value such as a str or int gives TypeError)

Exception hierarchy

Flat tree rooted at BaseException -> Exception. except walks parent links — except Exception catches RuntimeError, ValueError, KeyError, AssertionError, etc.; except RuntimeError catches RecursionError, NotImplementedError. SystemExit sits directly under BaseException, so except Exception does not catch it (use except SystemExit or a bare except).

try:
  raise RuntimeError("oops")
except Exception as e:
  print("caught via parent:", e)
 
try:
  [][0]
except Exception:
  print("caught IndexError as Exception")
caught via parent: oops
caught IndexError as Exception

User-defined classes don’t auto-extend the built-in BaseException tree but support single-level inheritance among themselves: except UserBase catches a raised UserSub when UserSub inherits from UserBase. raise X from Y raises X; the cause is discarded (no __cause__ / __context__ chaining).

Exception arguments

Caught exceptions expose constructor args as e.args (tuple). raise X("msg") and raise X(a, b) carry through; runtime-raised errors carry their message as a single arg; bare raise X produces an empty tuple.

try:
  raise TypeError("bad input")
except TypeError as e:
  print(e.args)
 
try:
  1 / 0
except ZeroDivisionError as e:
  print(e.args)
 
try:
  raise ValueError
except ValueError as e:
  print(e.args)
('bad input',)
('division by zero',)
()

Catching errors

def safe(f, x):
  try:
    return f(x)
  except TypeError:
    return "type"
  except ValueError:
    return "value"
  except ZeroDivisionError:
    return "zero"
  except:
    return "other"
 
print(safe(lambda x: 1 / x, 0))
print(safe(lambda x: int(x), "abc"))
print(safe(lambda x: len(x), 42))
zero
value
type

Environmental errors

Failures surfaced before the source reaches the compiler: no line/column preview, no parsed code to anchor to. Emitted as plain text, uncatchable from Python.

ErrorWhenResolution
input rejected: invalid utf-8 at byte NHost input bytes not valid UTF-8Re-encode as UTF-8
source file exceeds maximum size (10 MiB)Source over the 10 MiB lex-time capSplit or trim the input

Handle at the embedder layer (path validation, encoding, size check) before invoking the compiler.

Unavailable modules

Unavailable modules (os, sys, asyncio, …) parse for syntactic compatibility but have no resolver entry, so they are rejected at compile time before any code runs — a parse-time diagnostic, not a catchable runtime exception. See Imports, Errors. For code reuse, use higher-order functions.

Determinism

Same source + input -> same output across runs and architectures (x86_64, aarch64, wasm32). No time, randomness, threading, or OS interaction. Heap-pool slot reuse is the only nondeterminism: observable through id(x) only, never ==, repr, or any other operation.