code wiki / (root) / effect.nx

effect.nx source

↩ module page · 114 lines · 3913 B

1// effect.nx -- typed effects tracking (phase A: runtime log). 2// 3// EFFICIENCY_ROADMAP ยง3.4 -- algebraic effects / typed IO so 4// function signatures disclose their side effects. Pure- 5// function reasoning becomes possible; unexpected I/O from a 6// callee no longer hides. 7// 8// Phase A: a process-wide effect log. Functions that touch 9// FS/NET/ALLOC call `effect_mark(EFF_*)` at entry; unit tests 10// assert the log is empty after a computation that claims 11// purity. Cheap to adopt incrementally; no compiler changes 12// needed. 13// 14// Phase B (parser + typechecker): `can [FS, ALLOC]` row-type 15// on function signatures. Caller inherits the UNION of callee 16// effects automatically. Koka / Eff / OCaml-effects style. 17// 18// Phase C: handler syntax. `try { ... } with FS_open -> ... ` 19// for mocking / redirection / sandboxing without globals. 20// 21// Invariants: 22// EF1 `effect_mark` is O(1) + non-allocating -- safe in 23// hot paths. 24// EF2 `effect_snapshot` captures the current effect set; 25// `effect_restore` rolls back. Enables nested purity 26// zones. 27// EF3 Effect constants are bitflags so multi-effect code 28// records a union with one OR. 29 30import "syscalls.nx" 31 32const EFF_NONE: i64 = 0 33const EFF_FS: i64 = 0x0001 // filesystem 34const EFF_NET: i64 = 0x0002 // network 35const EFF_ALLOC: i64 = 0x0004 // dynamic memory 36const EFF_PROC: i64 = 0x0008 // fork/exec 37const EFF_CLOCK: i64 = 0x0010 // read wall-clock 38const EFF_RANDOM: i64 = 0x0020 // entropy source 39const EFF_IO: i64 = 0x0040 // stdin/stdout/stderr 40const EFF_SYSCALL: i64 = 0x0080 // raw syscall 41const EFF_UNWIND: i64 = 0x0100 // may panic/throw 42const EFF_NONDET: i64 = 0x0200 // nondeterministic result 43 44// Process-wide effect bitmask. Single i64 = no allocation + no 45// locking (single-threaded runtime assumption). 46struct EffectState { 47 bits: i64, 48} 49 50// Lazy singleton. 51func effect_state() -> *EffectState { 52 // We intentionally leak a mmap'd 16-byte block on first call. 53 // The state lives for the whole process -- no free path 54 // needed. Multi-thread-safe version goes in phase B. 55 let raw: *u8 = sys_mmap(16) 56 let s: *EffectState = raw as *EffectState 57 s.bits = 0 58 return s 59} 60 61// Mark that `eff` was observed. Call at the entry of every 62// function that touches that effect domain. 63func effect_mark(s: *EffectState, eff: i64) -> i64 { 64 s.bits = s.bits | eff 65 return 0 66} 67 68// Snapshot + restore for bounded purity zones. 69func effect_snapshot(s: *EffectState) -> i64 { 70 return s.bits 71} 72 73func effect_restore(s: *EffectState, saved: i64) -> i64 { 74 s.bits = saved 75 return 0 76} 77 78// Assert the state only contains effects from a given allow-list. 79// Returns 0 on OK, the unexpected bitmask on failure. 80func effect_assert(s: *EffectState, allowed: i64) -> i64 { 81 let leaked: i64 = s.bits & (allowed ^ (0 - 1)) 82 if leaked != 0 { return leaked } 83 return 0 84} 85 86// Clear -- use between test cases so state doesn't leak. 87func effect_clear(s: *EffectState) -> i64 { 88 s.bits = 0 89 return 0 90} 91 92// Compile-only smoke. 93func main() -> i64 { 94 let s: *EffectState = effect_state() 95 if s.bits != 0 { return 1 } 96 97 // Log an FS + ALLOC effect. 98 effect_mark(s, EFF_FS) 99 effect_mark(s, EFF_ALLOC) 100 if s.bits != (EFF_FS | EFF_ALLOC) { return 2 } 101 102 // Assert we only leaked allowed effects. 103 if effect_assert(s, EFF_FS | EFF_ALLOC) != 0 { return 3 } 104 if effect_assert(s, EFF_FS) != EFF_ALLOC { return 4 } 105 106 // Snapshot / restore. 107 let save: i64 = effect_snapshot(s) 108 effect_mark(s, EFF_NET) 109 if (s.bits & EFF_NET) != EFF_NET { return 5 } 110 effect_restore(s, save) 111 if (s.bits & EFF_NET) != 0 { return 6 } 112 113 return 0 114}