code wiki / (root) / nx_effect.nx

nx_effect.nx source

↩ module page · 120 lines · 4053 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 30// nx_safety_envelope: 31// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 32// sil_target: SIL1 33// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 34// verdict: NOT_YET_EVALUATED 35 36import "nx_syscalls.nx" 37 38const EFF_NONE: i64 = 0 39const EFF_FS: i64 = 0x0001 // filesystem 40const EFF_NET: i64 = 0x0002 // network 41const EFF_ALLOC: i64 = 0x0004 // dynamic memory 42const EFF_PROC: i64 = 0x0008 // fork/exec 43const EFF_CLOCK: i64 = 0x0010 // read wall-clock 44const EFF_RANDOM: i64 = 0x0020 // entropy source 45const EFF_IO: i64 = 0x0040 // stdin/stdout/stderr 46const EFF_SYSCALL: i64 = 0x0080 // raw syscall 47const EFF_UNWIND: i64 = 0x0100 // may panic/throw 48const EFF_NONDET: i64 = 0x0200 // nondeterministic result 49 50// Process-wide effect bitmask. Single i64 = no allocation + no 51// locking (single-threaded runtime assumption). 52struct EffectState { 53 bits: i64, 54} 55 56// Lazy singleton. 57func effect_state() -> *EffectState { 58 // We intentionally leak a mmap'd 16-byte block on first call. 59 // The state lives for the whole process -- no free path 60 // needed. Multi-thread-safe version goes in phase B. 61 let raw: *u8 = sys_mmap(16) 62 let s: *EffectState = raw as *EffectState 63 s.bits = 0 64 return s 65} 66 67// Mark that `eff` was observed. Call at the entry of every 68// function that touches that effect domain. 69func effect_mark(s: *EffectState, eff: i64) -> i64 { 70 s.bits = s.bits | eff 71 return 0 72} 73 74// Snapshot + restore for bounded purity zones. 75func effect_snapshot(s: *EffectState) -> i64 { 76 return s.bits 77} 78 79func effect_restore(s: *EffectState, saved: i64) -> i64 { 80 s.bits = saved 81 return 0 82} 83 84// Assert the state only contains effects from a given allow-list. 85// Returns 0 on OK, the unexpected bitmask on failure. 86func effect_assert(s: *EffectState, allowed: i64) -> i64 { 87 let leaked: i64 = s.bits & (allowed ^ (0 - 1)) 88 if leaked != 0 { return leaked } 89 return 0 90} 91 92// Clear -- use between test cases so state doesn't leak. 93func effect_clear(s: *EffectState) -> i64 { 94 s.bits = 0 95 return 0 96} 97 98// Compile-only smoke. 99func main() -> i64 { 100 let s: *EffectState = effect_state() 101 if s.bits != 0 { return 1 } 102 103 // Log an FS + ALLOC effect. 104 effect_mark(s, EFF_FS) 105 effect_mark(s, EFF_ALLOC) 106 if s.bits != (EFF_FS | EFF_ALLOC) { return 2 } 107 108 // Assert we only leaked allowed effects. 109 if effect_assert(s, EFF_FS | EFF_ALLOC) != 0 { return 3 } 110 if effect_assert(s, EFF_FS) != EFF_ALLOC { return 4 } 111 112 // Snapshot / restore. 113 let save: i64 = effect_snapshot(s) 114 effect_mark(s, EFF_NET) 115 if (s.bits & EFF_NET) != EFF_NET { return 5 } 116 effect_restore(s, save) 117 if (s.bits & EFF_NET) != 0 { return 6 } 118 119 return 0 120}