optimized native function calls

This commit is contained in:
trimill 2024-02-27 10:56:32 -05:00
parent 9996ca1574
commit 3e497d3ebc
Signed by: trimill
GPG key ID: 4F77A16E17E10BCB
4 changed files with 145 additions and 126 deletions

View file

@ -1,8 +1,4 @@
[workspace]
members = [
"talc-lang",
"talc-bin",
"talc-std",
"talc-macros",
]
members = ["talc-lang", "talc-bin", "talc-std", "talc-macros"]
resolver = "2"

View file

@ -1,11 +1,11 @@
use std::{collections::HashMap, sync::{Arc, Mutex, OnceLock}};
use std::{collections::HashMap, sync::{Mutex, OnceLock}};
use lazy_static::lazy_static;
#[derive(Default)]
struct SymbolTable {
names: Vec<Arc<str>>,
values: HashMap<Arc<str>, Symbol>
names: Vec<&'static str>,
values: HashMap<&'static str, Symbol>
}
lazy_static! {
@ -60,7 +60,7 @@ impl Symbol {
pub fn get(name: &str) -> Self {
let mut table = get_table().lock().expect("couldn't lock symbol table");
if let Some(sym) = table.values.get(name) {
if let Some(sym) = table.values.get(&name) {
return *sym
}
@ -68,9 +68,9 @@ impl Symbol {
assert!(symno <= MAX_SYMBOL, "too many symbols");
let sym = Symbol(symno as u32);
let name: Arc<str> = name.into();
let name = String::leak(name.to_owned());
table.names.push(name.clone());
table.names.push(name);
table.values.insert(name, sym);
sym
@ -99,7 +99,7 @@ impl Symbol {
/// # Panics
/// If the mutex storing the symbol table is poisoned
pub fn name(self) -> Arc<str> {
pub fn name(self) -> &'static str {
let table = get_table().lock().expect("couldn't lock symbol table");
table.names.get(self.0 as usize).cloned().expect("symbol does not exist")
}

View file

@ -404,9 +404,20 @@ impl Vm {
if let Value::NativeFunc(nf) = &args[0] {
let nf = nf.clone();
self.call_stack.push(std::mem::take(frame));
// safety: frame is restored immediately after
// function call ends
// ~25% performance improvement in for loop-heavy code
unsafe {
let f = std::ptr::read(frame);
self.call_stack.push(f);
}
let res = (nf.func)(self, args)?;
*frame = self.call_stack.pop().expect("no frame to pop");
unsafe {
let f = self.call_stack.pop().expect("no frame to pop");
std::ptr::write(frame, f);
}
self.stack.push(res);
} else if let Value::Function(func) = &args[0] {
@ -414,10 +425,10 @@ impl Vm {
if self.call_stack.len() + 1 >= self.stack_max {
throw!(*SYM_CALL_STACK_OVERFLOW, "call stack overflow")
}
self.call_stack.push(std::mem::take(frame));
let func = func.clone();
*frame = CallFrame::new(func, args);
let new_frame = CallFrame::new(func.clone(), args);
let old_frame = std::mem::replace(frame, new_frame);
self.call_stack.push(old_frame);
} else {
unreachable!("already verified by calling get_call_type");
}

View file

@ -6,12 +6,14 @@ use talc_macros::native_func;
use crate::{unpack_args, unpack_varargs};
pub fn load(vm: &mut Vm) {
// begin
vm.set_global_name("iter", iter().into());
vm.set_global_name("pairs", pairs().into());
vm.set_global_name("once", once().into());
vm.set_global_name("forever", forever().into());
vm.set_global_name("do_forever", forever().into());
// modify
vm.set_global_name("map", map().into());
vm.set_global_name("scan", scan().into());
vm.set_global_name("tee", tee().into());
@ -20,15 +22,18 @@ pub fn load(vm: &mut Vm) {
vm.set_global_name("take", take().into());
vm.set_global_name("skip", skip().into());
vm.set_global_name("enumerate", enumerate().into());
vm.set_global_name("cycle", cycle().into());
vm.set_global_name("step", step().into());
vm.set_global_name("rev", rev().into());
// join
vm.set_global_name("zip", zip().into());
vm.set_global_name("alternate", alternate().into());
vm.set_global_name("intersperse", intersperse().into());
vm.set_global_name("cartprod", cartprod().into());
vm.set_global_name("cycle", cycle().into());
vm.set_global_name("chain", chain().into());
vm.set_global_name("step", step().into());
vm.set_global_name("rev", rev().into());
// end
vm.set_global_name("list", list().into());
vm.set_global_name("table", table().into());
vm.set_global_name("len", len().into());
@ -288,6 +293,118 @@ pub fn enumerate(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(1)]
pub fn cycle(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let record = RefCell::new((Vec::<Rc<RefCell<Value>>>::new(), false, 0));
let f = move |vm: &mut Vm, _| {
if record.borrow().1 {
let mut r = record.borrow_mut();
if r.0.is_empty() {
return Ok(Value::Nil)
}
let val = r.0[r.2].clone();
r.2 = (r.2 + 1) % r.0.len();
return Ok(val.into())
}
match vmcalliter!(vm; iter.clone())? {
Some(v) => {
record.borrow_mut().0.push(v.clone());
Ok(v.into())
},
None => {
let mut r = record.borrow_mut();
r.1 = true;
if r.0.is_empty() {
Ok(Value::Nil)
} else {
r.2 = (r.2 + 1) % r.0.len();
Ok(r.0[0].clone().into())
}
}
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[derive(Clone, Copy, Default)]
enum Step {
First, Going, #[default] End,
}
#[native_func(2)]
pub fn step(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, by, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let Value::Int(by) = by else {
throw!(*SYM_TYPE_ERROR, "step expected integer")
};
if by <= 0 {
throw!(*SYM_TYPE_ERROR, "step expected positive integer")
}
let state = RefCell::new(Step::First);
let f = move |vm: &mut Vm, _| match state.take() {
Step::First => {
match vmcalliter!(vm; iter.clone())? {
Some(x) => {
*state.borrow_mut() = Step::Going;
Ok(x.into())
},
None => Ok(Value::Nil),
}
},
Step::Going => {
for _ in 0..(by-1) {
if vmcall!(vm; iter.clone())? == Value::Nil {
return Ok(Value::Nil)
}
}
let Some(x) = vmcalliter!(vm; iter.clone())? else {
return Ok(Value::Nil)
};
*state.borrow_mut() = Step::Going;
Ok(x.into())
},
Step::End => Ok(Value::Nil),
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(1)]
pub fn rev(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let lst = RefCell::new(None);
let f = move |vm: &mut Vm, _| {
if lst.borrow().is_none() {
let mut l = Vec::new();
while let Some(x) = vmcalliter!(vm; iter.clone())? {
l.push(cell_take(x))
}
l.reverse();
*lst.borrow_mut() = Some(l.into_iter());
}
match lst.borrow_mut().as_mut().unwrap().next() {
Some(v) => Ok(v.to_cell()),
None => Ok(Value::Nil),
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
//
// join
//
#[native_func(2..)]
pub fn zip(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let ([_, i1, i2], rest) = unpack_varargs!(args);
@ -476,111 +593,6 @@ pub fn cartprod(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
}
#[native_func(1)]
pub fn cycle(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let record = RefCell::new((Vec::<Rc<RefCell<Value>>>::new(), false, 0));
let f = move |vm: &mut Vm, _| {
if record.borrow().1 {
let mut r = record.borrow_mut();
if r.0.is_empty() {
return Ok(Value::Nil)
}
let val = r.0[r.2].clone();
r.2 = (r.2 + 1) % r.0.len();
return Ok(val.into())
}
match vmcalliter!(vm; iter.clone())? {
Some(v) => {
record.borrow_mut().0.push(v.clone());
Ok(v.into())
},
None => {
let mut r = record.borrow_mut();
r.1 = true;
if r.0.is_empty() {
Ok(Value::Nil)
} else {
r.2 = (r.2 + 1) % r.0.len();
Ok(r.0[0].clone().into())
}
}
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[derive(Clone, Copy, Default)]
enum Step {
First, Going, #[default] End,
}
#[native_func(2)]
pub fn step(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, by, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let Value::Int(by) = by else {
throw!(*SYM_TYPE_ERROR, "step expected integer")
};
if by <= 0 {
throw!(*SYM_TYPE_ERROR, "step expected positive integer")
}
let state = RefCell::new(Step::First);
let f = move |vm: &mut Vm, _| match state.take() {
Step::First => {
match vmcalliter!(vm; iter.clone())? {
Some(x) => {
*state.borrow_mut() = Step::Going;
Ok(x.into())
},
None => Ok(Value::Nil),
}
},
Step::Going => {
for _ in 0..(by-1) {
if vmcall!(vm; iter.clone())? == Value::Nil {
return Ok(Value::Nil)
}
}
let Some(x) = vmcalliter!(vm; iter.clone())? else {
return Ok(Value::Nil)
};
*state.borrow_mut() = Step::Going;
Ok(x.into())
},
Step::End => Ok(Value::Nil),
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(1)]
pub fn rev(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let lst = RefCell::new(None);
let f = move |vm: &mut Vm, _| {
if lst.borrow().is_none() {
let mut l = Vec::new();
while let Some(x) = vmcalliter!(vm; iter.clone())? {
l.push(cell_take(x))
}
l.reverse();
*lst.borrow_mut() = Some(l.into_iter());
}
match lst.borrow_mut().as_mut().unwrap().next() {
Some(v) => Ok(v.to_cell()),
None => Ok(Value::Nil),
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
//
// end iteration