Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/ephapax-interp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -633,6 +653,13 @@ impl Interpreter {

fn eval_copy(&mut self, inner: &Expr) -> Result<Value, RuntimeError> {
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)))
}
Expand Down
9 changes: 8 additions & 1 deletion src/ephapax-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(),
Expand Down
26 changes: 24 additions & 2 deletions src/ephapax-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,37 +134,59 @@ 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
core::ptr::write(region_sp as *mut u32, bump_ptr);

// 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);

// 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
Expand Down
6 changes: 4 additions & 2 deletions src/ephapax-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
Loading