Repository navigation
effects
Code: src/effects.rs · Tests: tests/functions/test_effects.rs, tests/functions/test_precomputed.rs, tests/control/test_run_time_blocks.rs · Design: DESIGN.md "Effects", "Effects as enforced capabilities" · Timing/folding: charged §3 / §5 · Related: pure, evaluation
- Closed set
Effect { State, Allocation, IO, FFI, Async, Unsafe, Div, Eval },EffectSetis a u8 bitset,Pure= empty set. - Trusted signatures:
TRUSTED_EXTERNALS(fetch/read/warning → Host/IO, print/puts/puti/putl/putf → Wasi/IO, fd_write → Wasi/IO+Unsafe, execute → Sql/IO, exec → Process/IO). FFI functions declared byimport f from lib/use libgetFFI(viaanalyzer::extract_ffi_imports), except libm which is Pure (capability Libm). The host wordrun_block(run-time!/interpret) getsEval(capability Host). - Resolution reuses
analyzer::extract_user_functions; every symbol naming a user function or external counts as a call. Nested definitions andimport/usestatements are skipped. Top-level code is the pseudo-functionmain, so defining an IO function is not IO; calling it is. - Effects = union over callees, iterated to a fixpoint (recursion safe).
- Inferred on the body itself (
own), beyond callees:-
State — the
globalkeyword, or reading a main-levelglobalthat is not a parameter (State-from-global, charged §3) -
Div — a
whilethat may run, self-recursion that does not shrink a measure toward a guarded base case, or mutual recursion -
Eval — only via the
run_blockcall (below); Allocation/Async are not inferred yet
-
State — the
A block known only at run time is compiled and run through the host import run_block (charged §5, src/lowering/run_time_blocks.rs). That call carries effect Eval: the function and every caller are not pure.
f(xs) := interpret(xs#1)
effects of f // Eval
Compile-time ! of a constant block is inlined (no Eval). Run-time ! / interpret of an unknown block is never folded or memoized.
Reading a variable declared global at the main level (or reading a free variable after global y in the body) is State: shared state at call time (charged §3). Such a function is not Pure, so it is neither folded nor memoized.
global y = 1
f() := y * 2
effects of f // State
y = 3
def z(){ global y; y * y }
effects of z // State
y = 1
f() := y * 2
effects of f // Pure — y is a constant free variable, not global
A parameter that shadows a global does not count as a State read (global y=1; f(y) := y*2 → Pure).
charged §3 is the meaning; effects decide what the compiler may precompute:
-
Pure (empty
EffectSet) parts with known inputs may be folded, hoisted, or memoized. The compiler decides alone — there is no user memoization syntax. -
Never precomputed: anything with effects (I/O,
now,random, mutation, task operations, run-time!/ Eval, State reads of a reassigned /globalvariable) runs at every call, in order. - Query check:
effects of freportsStatefor a global reader andEvalfor a function with run-time!; both stay out of the fold path (tests/functions/test_precomputed.rs).
-
EffectReport::of(&node)→effects_of(name),entry_effects(),needs(Capability),call_chain(f, effect),violations. -
effects::effects_of(code, "f")convenience. - Source query: last statement
effects of fevaluates toPure,IO,Eval,State, or(IO FFI).
f(x) := … ! Pure / ! IO, FFI (the comma form parses as [(def ! IO), FFI] and is re-joined).
Violation → Error("effect violation at L:C: f is declared ! Pure but performs IO via f → log → puts").
! Effects on a non-function (e.g. answer := 42 ! Pure) is a loud "misplaced effect constraint" error.
without_constraints strips them before emission.
WasmGcEmitter::emit_for_node calls derive_imports_from_effects: WASI/host imports only if such a call
resolves, FFI imports pruned to called functions (use m; 3 imports nothing). Explicit set_*_imports(true)
still wins. eval_parsed picks the linker from emitter.imports(Capability). The old code.contains("puts ")
heuristics are gone.
Consequence: WASI no longer changes main's type. main always returns a Node, WASI call results are boxed
with new_int, read_bytes_with_wasi returns a Node (shared val_to_node in wasm_reader.rs).
Before, puts('ok') (no space) silently skipped printing; now it really prints.
-
puts x/puti xwherexis a function parameter panics "Undefined variable" (WASI emitter looks up main's scope). Pre-existing, now reachable more often. - WASI output written with no trailing newline glues onto libtest's
test … okline, so test.sh can miss such tests (e.g. test_list_index_after_puts passes but isn't counted). - Once
src/semantic/exists: storeEffectSetonExpr/FunctionDecl, spans from the IR, and let the import manager emit per-function imports (fd_write only) instead of per-capability groups.