diff --git a/src/ephapax-interp/src/lib.rs b/src/ephapax-interp/src/lib.rs index 05fab785..8505d838 100644 --- a/src/ephapax-interp/src/lib.rs +++ b/src/ephapax-interp/src/lib.rs @@ -85,6 +85,26 @@ impl Value { } } + /// Check if this value is linear (must be used exactly once) + pub fn is_linear(&self) -> bool { + match self { + // Strings are linear resources + Value::String { .. } => true, + // Pairs are linear if either component is linear + Value::Pair(l, r) => l.is_linear() || r.is_linear(), + // Sum types are linear if either variant type is linear + Value::Left(v) | Value::Right(v) => v.is_linear(), + // Closures that capture linear values are linear + // (Conservative: treat all closures as potentially linear) + Value::Closure { .. } => false, // Could be refined with capture analysis + // Base types are unrestricted + Value::Unit | Value::Bool(_) | Value::I32(_) | Value::I64(_) + | Value::F32(_) | Value::F64(_) => false, + // Borrows are second-class (not linear in the sense of consumption) + Value::Borrow(_) => false, + } + } + /// Get the inferred type of this value pub fn to_type(&self) -> Ty { match self { @@ -633,6 +653,13 @@ impl Interpreter { fn eval_copy(&mut self, inner: &Expr) -> Result { let val = self.eval(inner)?; + // Only unrestricted types can be copied - linear types (String, etc.) cannot + if val.is_linear() { + return Err(RuntimeError::TypeError { + expected: "unrestricted (copyable) type".to_string(), + found: format!("linear type {}", val.type_name()), + }); + } // For unrestricted types, return a pair of copies Ok(Value::Pair(Box::new(val.clone()), Box::new(val))) } diff --git a/src/ephapax-parser/src/lib.rs b/src/ephapax-parser/src/lib.rs index dd3acf83..55047bb0 100644 --- a/src/ephapax-parser/src/lib.rs +++ b/src/ephapax-parser/src/lib.rs @@ -298,7 +298,14 @@ fn literal_parser<'a>() -> impl Parser<'a, &'a [SpannedToken], (Literal, LexerSp TokenKind::Unit => Literal::Unit, TokenKind::True => Literal::Bool(true), TokenKind::False => Literal::Bool(false), - TokenKind::Integer(n) => Literal::I32(n as i32), + TokenKind::Integer(n) => { + // Check for overflow when converting i64 to i32 + if n > i32::MAX as i64 || n < i32::MIN as i64 { + Literal::I64(n) + } else { + Literal::I32(n as i32) + } + }, TokenKind::Float(n) => Literal::F64(n), TokenKind::String(s) => Literal::String(s), _ => unreachable!(), diff --git a/src/ephapax-runtime/src/lib.rs b/src/ephapax-runtime/src/lib.rs index c0329f01..28d52b62 100644 --- a/src/ephapax-runtime/src/lib.rs +++ b/src/ephapax-runtime/src/lib.rs @@ -134,12 +134,22 @@ pub extern "C" fn __ephapax_string_drop(_handle: StringHandle) { /// Enter a new region: save current bump pointer /// +/// Returns 1 on success, 0 if region stack is full. +/// /// # Safety /// /// Must not exceed maximum region depth. #[no_mangle] -pub unsafe extern "C" fn __ephapax_region_enter() { +pub unsafe extern "C" fn __ephapax_region_enter() -> u32 { let region_sp = core::ptr::read(layout::REGION_SP as *const u32); + + // Bounds check: ensure we don't exceed maximum region depth + let max_sp = (layout::REGION_STACK + layout::MAX_REGION_DEPTH * 4) as u32; + if region_sp >= max_sp { + // Region stack full - cannot nest deeper + return 0; + } + let bump_ptr = core::ptr::read(layout::BUMP_PTR as *const u32); // Push bump_ptr onto region stack @@ -147,17 +157,27 @@ pub unsafe extern "C" fn __ephapax_region_enter() { // Increment region stack pointer core::ptr::write(layout::REGION_SP as *mut u32, region_sp + 4); + + 1 // Success } /// Exit region: restore bump pointer, freeing all region allocations /// +/// Returns 1 on success, 0 if no region is active (underflow). +/// /// # Safety /// /// Must have a matching `__ephapax_region_enter` call. #[no_mangle] -pub unsafe extern "C" fn __ephapax_region_exit() { +pub unsafe extern "C" fn __ephapax_region_exit() -> u32 { let region_sp = core::ptr::read(layout::REGION_SP as *const u32); + // Bounds check: ensure we have a region to exit + if region_sp <= layout::REGION_STACK as u32 { + // No region to exit - stack underflow + return 0; + } + // Decrement and read saved bump_ptr let new_sp = region_sp - 4; let saved_bump = core::ptr::read(new_sp as *const u32); @@ -165,6 +185,8 @@ pub unsafe extern "C" fn __ephapax_region_exit() { // Restore bump pointer (effectively frees all region allocations) core::ptr::write(layout::BUMP_PTR as *mut u32, saved_bump); core::ptr::write(layout::REGION_SP as *mut u32, new_sp); + + 1 // Success } /// Check if we're in any region diff --git a/src/ephapax-wasm/src/lib.rs b/src/ephapax-wasm/src/lib.rs index b040a44f..d128e012 100644 --- a/src/ephapax-wasm/src/lib.rs +++ b/src/ephapax-wasm/src/lib.rs @@ -879,14 +879,16 @@ impl Codegen { } fn compile_unaryop(&mut self, func: &mut Function, op: UnaryOp, operand: &Expr) { - self.compile_expr(func, operand); - match op { UnaryOp::Not => { + self.compile_expr(func, operand); func.instruction(&Instruction::I32Eqz); } UnaryOp::Neg => { + // For negation: compute 0 - operand + // Push 0 first, then the operand, then subtract func.instruction(&Instruction::I32Const(0)); + self.compile_expr(func, operand); func.instruction(&Instruction::I32Sub); } }