Skip to content

Commit

Permalink
remove lifetimes in favor of Rc/Arc
Browse files Browse the repository at this point in the history
introduce a threadsafe feature in exec to take either Rc/Arc
  • Loading branch information
vincenthz committed Feb 11, 2024
1 parent 7c23d93 commit a3b3654
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 69 deletions.
4 changes: 4 additions & 0 deletions werbolg-exec/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ edition = "2021"
[dependencies]
werbolg-core = { path = "../werbolg-core" }
werbolg-compile = { path = "../werbolg-compile" }

[features]
default = []
threadsafe = []
88 changes: 44 additions & 44 deletions werbolg-exec/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,29 +66,29 @@ use werbolg_core as ir;
use werbolg_core::{NifId, ValueFun};

/// Native Implemented Function
pub struct NIF<'m, 'e, A, L, T, V> {
pub struct NIF<A, L, T, V> {
/// name of the NIF
pub name: &'static str,
/// arity of the call
pub arity: CallArity,
/// the call itself
pub call: NIFCall<'m, 'e, A, L, T, V>,
pub call: NIFCall<A, L, T, V>,
}

/// 2 Variants of Native calls
///
/// * "Pure" function that don't have access to the execution machine
/// * "Mut" function that have access to the execution machine and have more power / responsability.
pub enum NIFCall<'m, 'e, A, L, T, V> {
pub enum NIFCall<A, L, T, V> {
/// "Pure" NIF call only takes the input parameter and return an output
Pure(fn(&A, &[V]) -> Result<V, ExecutionError>),
/// "Raw" NIF takes the execution machine in parameter and return an output
Raw(fn(&mut ExecutionMachine<'m, 'e, A, L, T, V>) -> Result<V, ExecutionError>),
Raw(fn(&mut ExecutionMachine<A, L, T, V>) -> Result<V, ExecutionError>),
}

impl<'m, 'e, A, L, T, V> NIFCall<'m, 'e, A, L, T, V> {
impl<A, L, T, V> NIFCall<A, L, T, V> {
/// Create a NIF from a NIFCall, by adding the necessary definition operations (name and arity)
pub fn info(self, name: &'static str, arity: CallArity) -> NIF<'m, 'e, A, L, T, V> {
pub fn info(self, name: &'static str, arity: CallArity) -> NIF<A, L, T, V> {
NIF {
name,
arity,
Expand All @@ -98,8 +98,8 @@ impl<'m, 'e, A, L, T, V> NIFCall<'m, 'e, A, L, T, V> {
}

/// Execute the module, calling function identified by FunId, with the arguments in parameters.
pub fn exec<'module, 'environ, A: WAllocator<Value = V>, L, T, V: Valuable>(
em: &mut ExecutionMachine<'module, 'environ, A, L, T, V>,
pub fn exec<A: WAllocator<Value = V>, L, T, V: Valuable>(
em: &mut ExecutionMachine<A, L, T, V>,
call: ir::FunId,
args: &[V],
) -> Result<V, ExecutionError> {
Expand All @@ -113,8 +113,8 @@ pub fn exec<'module, 'environ, A: WAllocator<Value = V>, L, T, V: Valuable>(

/// Initialize the execution machine with a call to the specified function (by FunId)
/// and the arguments to this function as values
pub fn initialize<'module, 'environ, A: WAllocator<Value = V>, L, T, V: Valuable>(
em: &mut ExecutionMachine<'module, 'environ, A, L, T, V>,
pub fn initialize<A: WAllocator<Value = V>, L, T, V: Valuable>(
em: &mut ExecutionMachine<A, L, T, V>,
call: ir::FunId,
args: &[V],
) -> Result<Option<V>, ExecutionError> {
Expand Down Expand Up @@ -143,17 +143,17 @@ pub fn initialize<'module, 'environ, A: WAllocator<Value = V>, L, T, V: Valuable
/// Resume execution
///
/// If the stack is empty (if the program is terminated already), then it returns an ExecutionFinished error
pub fn exec_continue<'m, 'e, A: WAllocator<Value = V>, L, T, V: Valuable>(
em: &mut ExecutionMachine<'m, 'e, A, L, T, V>,
pub fn exec_continue<A: WAllocator<Value = V>, L, T, V: Valuable>(
em: &mut ExecutionMachine<A, L, T, V>,
) -> Result<V, ExecutionError> {
if em.rets.is_empty() {
return Err(ExecutionError::ExecutionFinished);
}
exec_loop(em)
}

fn exec_loop<'m, 'e, A: WAllocator<Value = V>, L, T, V: Valuable>(
em: &mut ExecutionMachine<'m, 'e, A, L, T, V>,
fn exec_loop<A: WAllocator<Value = V>, L, T, V: Valuable>(
em: &mut ExecutionMachine<A, L, T, V>,
) -> Result<V, ExecutionError> {
loop {
match step(em)? {
Expand All @@ -171,37 +171,37 @@ type StepResult<V> = Result<Option<V>, ExecutionError>;
/// * not an error : Either no value or a value if the execution of the program is finished
///
/// The step function need to update the execution IP
pub fn step<'m, 'e, A: WAllocator<Value = V>, L, T, V: Valuable>(
em: &mut ExecutionMachine<'m, 'e, A, L, T, V>,
pub fn step<A: WAllocator<Value = V>, L, T, V: Valuable>(
em: &mut ExecutionMachine<A, L, T, V>,
) -> StepResult<V> {
// fetch the next instruction from ip, if ip points to a random place, raise an error
let Some(instr) = &em.module.code.get(em.ip) else {
let Some(instr) = em.module.code.get(em.ip).cloned() else {
return Err(ExecutionError::IpInvalid { ip: em.ip });
};
match instr {
Instruction::PushLiteral(lit) => {
let literal = &em.module.lits[*lit];
let literal = &em.module.lits[lit];
em.stack.push_value((em.params.literal_to_value)(literal));
em.ip_next();
}
Instruction::FetchGlobal(global_id) => {
em.sp_push_value_from_global(*global_id);
em.sp_push_value_from_global(global_id);
em.ip_next();
}
Instruction::FetchNif(nif_id) => {
em.stack.push_value(V::make_fun(ValueFun::Native(*nif_id)));
em.stack.push_value(V::make_fun(ValueFun::Native(nif_id)));
em.ip_next();
}
Instruction::FetchFun(fun_id) => {
em.stack.push_value(V::make_fun(ValueFun::Fun(*fun_id)));
em.stack.push_value(V::make_fun(ValueFun::Fun(fun_id)));
em.ip_next();
}
Instruction::FetchStackLocal(local_bind) => {
em.sp_push_value_from_local(*local_bind);
em.sp_push_value_from_local(local_bind);
em.ip_next()
}
Instruction::FetchStackParam(param_bind) => {
em.sp_push_value_from_param(*param_bind);
em.sp_push_value_from_param(param_bind);
em.ip_next()
}
Instruction::AccessField(expected_cid, idx) => {
Expand All @@ -212,16 +212,16 @@ pub fn step<'m, 'e, A: WAllocator<Value = V>, L, T, V: Valuable>(
});
};

if got_cid != *expected_cid {
if got_cid != expected_cid {
return Err(ExecutionError::StructMismatch {
constr_expected: *expected_cid,
constr_expected: expected_cid,
constr_got: got_cid,
});
}
if idx.0 as usize >= inner.len() {
return Err(ExecutionError::StructFieldOutOfBound {
constr: got_cid,
field_index: *idx,
field_index: idx,
struct_len: inner.len(),
});
}
Expand All @@ -230,45 +230,45 @@ pub fn step<'m, 'e, A: WAllocator<Value = V>, L, T, V: Valuable>(
}
Instruction::LocalBind(local_bind) => {
let val = em.stack.pop_value();
em.sp_set_local_value_at(*local_bind, val);
em.sp_set_local_value_at(local_bind, val);
em.ip_next();
}
Instruction::IgnoreOne => {
let _ = em.stack.pop_value();
em.ip_next();
}
Instruction::CallNif(nif, arity) => {
let nif_value = process_nif_call(em, *nif, *arity)?;
em.stack.pop_call_nofun(*arity);
let nif_value = process_nif_call(em, nif, arity)?;
em.stack.pop_call_nofun(arity);
em.stack.push_value(nif_value);
em.ip_next()
}
Instruction::Call(tc, arity) => {
let val = process_call(em, *arity)?;
let val = process_call(em, arity)?;
match val {
CallResult::Jump(fun_ip, local_stack_size) => {
if *tc == TailCall::Yes {
if tc == TailCall::Yes {
// if we have a tail call, we don't need to save the current call frame
// we just shift the values to replace the call stack and
// replace the current state (sp, ip, current_arity)
em.sp_move_rel(*arity, em.current_arity, local_stack_size);
em.current_arity = *arity;
em.sp_move_rel(arity, em.current_arity, local_stack_size);
em.current_arity = arity;
em.ip_set(fun_ip);
} else {
em.rets.push(CallSave {
ip: em.ip.next(),
sp: em.sp,
arity: em.current_arity,
});
em.current_arity = *arity;
em.current_arity = arity;
em.sp_set(local_stack_size);
em.ip_set(fun_ip);
}
}
CallResult::Value(nif_val) => {
em.stack.pop_call(*arity);
em.stack.pop_call(arity);

if *tc == TailCall::Yes {
if tc == TailCall::Yes {
match em.rets.pop() {
None => return Ok(Some(nif_val)),
Some(call_frame) => do_ret(em, call_frame, nif_val),
Expand All @@ -280,7 +280,7 @@ pub fn step<'m, 'e, A: WAllocator<Value = V>, L, T, V: Valuable>(
}
}
}
Instruction::Jump(d) => em.ip_jump(*d),
Instruction::Jump(d) => em.ip_jump(d),
Instruction::CondJump(d) => {
let val = em.stack.pop_value();
let Some(b) = val.conditional() else {
Expand All @@ -291,7 +291,7 @@ pub fn step<'m, 'e, A: WAllocator<Value = V>, L, T, V: Valuable>(
if b {
em.ip_next()
} else {
em.ip_jump(*d)
em.ip_jump(d)
}
}
Instruction::Ret => {
Expand All @@ -306,8 +306,8 @@ pub fn step<'m, 'e, A: WAllocator<Value = V>, L, T, V: Valuable>(
Ok(None)
}

fn do_ret<'m, 'e, A: WAllocator, L, T, V: Valuable>(
em: &mut ExecutionMachine<'m, 'e, A, L, T, V>,
fn do_ret<A: WAllocator, L, T, V: Valuable>(
em: &mut ExecutionMachine<A, L, T, V>,
CallSave { ip, sp, arity }: CallSave,
value: V,
) {
Expand All @@ -328,8 +328,8 @@ enum CallResult<V> {
Value(V),
}

fn process_call<'m, 'e, A: WAllocator, L, T, V: Valuable>(
em: &mut ExecutionMachine<'m, 'e, A, L, T, V>,
fn process_call<A: WAllocator, L, T, V: Valuable>(
em: &mut ExecutionMachine<A, L, T, V>,
arity: CallArity,
) -> Result<CallResult<V>, ExecutionError> {
let first = em.stack.get_call(arity);
Expand Down Expand Up @@ -359,8 +359,8 @@ fn process_call<'m, 'e, A: WAllocator, L, T, V: Valuable>(
}
}

fn process_nif_call<'m, 'e, A: WAllocator, L, T, V: Valuable>(
em: &mut ExecutionMachine<'m, 'e, A, L, T, V>,
fn process_nif_call<A: WAllocator, L, T, V: Valuable>(
em: &mut ExecutionMachine<A, L, T, V>,
nifid: NifId,
arity: CallArity,
) -> Result<V, ExecutionError> {
Expand Down
34 changes: 20 additions & 14 deletions werbolg-exec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

extern crate alloc;

#[cfg(feature = "threadsafe")]
extern crate std;

use ir::{ConstrId, GlobalId, NifId, ValueFun};
use werbolg_compile::{
CallArity, LocalBindIndex, LocalStackSize, ParamBindIndex, StructFieldIndex,
Expand All @@ -14,28 +17,31 @@ use werbolg_core::idvec::IdVec;

mod allocator;
mod exec;
mod refcount;
mod valuable;

use alloc::{string::String, vec::Vec};
pub use allocator::WAllocator;
pub use valuable::{Valuable, ValueKind};

pub use refcount::WerRefCount;

pub use exec::{exec, exec_continue, initialize, step, NIFCall, NIF};

/// Execution environment with index Nifs by their NifId, and global variable with their GlobalId
pub struct ExecutionEnviron<'m, 'e, A, L, T, V> {
pub struct ExecutionEnviron<A, L, T, V> {
/// Indexed NIFs
pub nifs: IdVec<NifId, NIF<'m, 'e, A, L, T, V>>,
pub nifs: IdVec<NifId, NIF<A, L, T, V>>,
/// Indexed Globals
pub globals: IdVec<GlobalId, V>,
}

impl<'m, 'e, A, L, T, V> ExecutionEnviron<'m, 'e, A, L, T, V> {
impl<A, L, T, V> ExecutionEnviron<A, L, T, V> {
/// Pack a streamlined compilation environment into an execution environment
///
/// this is the result of calling `Environment::finalize()`
pub fn from_compile_environment(
tuple: (IdVec<GlobalId, V>, IdVec<NifId, NIF<'m, 'e, A, L, T, V>>),
tuple: (IdVec<GlobalId, V>, IdVec<NifId, NIF<A, L, T, V>>),
) -> Self {
Self {
nifs: tuple.1,
Expand All @@ -52,11 +58,11 @@ pub struct ExecutionParams<L, V> {
}

/// Execution machine
pub struct ExecutionMachine<'m, 'e, A, L, T, V> {
pub struct ExecutionMachine<A, L, T, V> {
/// Environ
pub environ: &'e ExecutionEnviron<'m, 'e, A, L, T, V>,
pub environ: WerRefCount<ExecutionEnviron<A, L, T, V>>,
/// Module
pub module: &'m CompilationUnit<L>,
pub module: WerRefCount<CompilationUnit<L>>,
/// call frame return values
pub rets: Vec<CallSave>,
/// stack
Expand Down Expand Up @@ -209,11 +215,11 @@ impl<V: Valuable> ValueStack<V> {
}
}

impl<'m, 'e, A, L, T, V: Valuable> ExecutionMachine<'m, 'e, A, L, T, V> {
impl<A, L, T, V: Valuable> ExecutionMachine<A, L, T, V> {
/// Create a new execution machine
pub fn new(
module: &'m CompilationUnit<L>,
environ: &'e ExecutionEnviron<'m, 'e, A, L, T, V>,
module: WerRefCount<CompilationUnit<L>>,
environ: WerRefCount<ExecutionEnviron<A, L, T, V>>,
params: ExecutionParams<L, V>,
allocator: A,
userdata: T,
Expand Down Expand Up @@ -253,8 +259,8 @@ impl<'m, 'e, A, L, T, V: Valuable> ExecutionMachine<'m, 'e, A, L, T, V> {
}

/// Get the current instruction
pub fn get_current_instruction(&self) -> Option<&'m werbolg_compile::Instruction> {
self.module.code.get(self.ip)
pub fn get_current_instruction(&self) -> Option<werbolg_compile::Instruction> {
self.module.code.get(self.ip).cloned()
}

/// Set value at stack pointer + local bind to the value in parameter
Expand Down Expand Up @@ -315,7 +321,7 @@ impl<'m, 'e, A, L, T, V: Valuable> ExecutionMachine<'m, 'e, A, L, T, V> {
}
}

impl<'m, 'e, A, L, T, V: Valuable + core::fmt::Debug> ExecutionMachine<'m, 'e, A, L, T, V> {
impl<A, L, T, V: Valuable + core::fmt::Debug> ExecutionMachine<A, L, T, V> {
/// print the debug state of the execution machine in a writer
pub fn debug_state<W: core::fmt::Write>(&self, writer: &mut W) -> Result<(), core::fmt::Error> {
let instr = self.get_current_instruction();
Expand All @@ -324,7 +330,7 @@ impl<'m, 'e, A, L, T, V: Valuable + core::fmt::Debug> ExecutionMachine<'m, 'e, A
"ip={} sp={:?} instruction={:?}",
self.ip,
self.sp.0,
instr.unwrap_or(&werbolg_compile::Instruction::IgnoreOne)
instr.unwrap_or(werbolg_compile::Instruction::IgnoreOne)
)?;

for (stack_index, value) in self.stack.iter_pos() {
Expand Down
7 changes: 7 additions & 0 deletions werbolg-exec/src/refcount.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#[cfg(feature = "threadsafe")]
/// threadsafe refcount alias to Arc
pub type WerRefCount<A> = std::sync::Arc<A>;

#[cfg(not(feature = "threadsafe"))]
/// Not threadsafe refcount alias to Rc
pub type WerRefCount<A> = alloc::rc::Rc<A>;
3 changes: 1 addition & 2 deletions werbolg-tales/src/environ.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,7 @@ pub fn literal_mapper(span: Span, lit: Literal) -> Result<MyLiteral, Compilation
}
}

pub fn create_env<'m, 'e>(
) -> Environment<NIF<'m, 'e, crate::DummyAlloc, MyLiteral, (), Value>, Value> {
pub fn create_env() -> Environment<NIF<crate::DummyAlloc, MyLiteral, (), Value>, Value> {
macro_rules! add_pure_nif {
($env:ident, $i:literal, $arity:literal, $e:expr) => {
let nif = NIFCall::Pure($e).info($i, CallArity::try_from($arity as usize).unwrap());
Expand Down
Loading

0 comments on commit a3b3654

Please sign in to comment.