use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use inkwell::basic_block::BasicBlock;
use inkwell::builder::Builder;
use inkwell::module::Module;
use inkwell::values::{BasicValueEnum, FunctionValue, InstructionValue, IntValue, PointerValue};
use inkwell::AddressSpace;
use typelisp_mem::{Heap, Value};
use crate::eval::interp::{
expect_str, expect_struct_box, scope_clone_frames_heap, scope_pop_frame_heap, scope_push_frame_heap,
scope_set_raw, str_rt, EvalError,
};
use crate::types::Type;
use crate::types::Path;
pub(crate) fn eval_llvm_builtin_method(heap: &mut Heap, type_name: &Path, method: &str, args: &[Value]) -> Option<Result<Value, EvalError>> {
let _guard = crate::compile::COMPILE_LOCK.lock().unwrap();
if *type_name == Path::root("llvm-module") {
return match method {
"create" => Some(llvm_module_create(heap, args)),
"add-function" => Some(llvm_module_add_function(heap, args)),
"verify" => Some(llvm_module_verify(args)),
"to-string" => Some(llvm_module_to_string(heap, args)),
"get-function" => Some(llvm_module_get_function(heap, args)),
"add-function-with-env" => Some(llvm_module_add_function_with_env(heap, args)),
"add-coroutine-function" => Some(llvm_module_add_coroutine_function(heap, args)),
_ => None,
};
}
if *type_name == Path::root("llvm-function") {
return match method {
"append-block" => Some(llvm_function_append_block(heap, args)),
"function-param" => Some(llvm_function_param(args)),
_ => None,
};
}
if *type_name == Path::root("llvm-builder") {
return match method {
"create" => Some(llvm_builder_create()),
"position-at-end" => Some(llvm_builder_position_at_end(args)),
"const-word" => Some(llvm_builder_const_word(args)),
"build-ret" => Some(llvm_builder_build_ret(args)),
"load-arg" => Some(llvm_builder_load_arg(args)),
"build-add" => Some(llvm_builder_build_int_op(args, "add", Builder::build_int_add)),
"build-sub" => Some(llvm_builder_build_int_op(args, "sub", Builder::build_int_sub)),
"build-mul" => Some(llvm_builder_build_int_op(args, "mul", Builder::build_int_mul)),
"build-and" => Some(llvm_builder_build_int_op(args, "and", Builder::build_and)),
"build-or" => Some(llvm_builder_build_int_op(args, "or", Builder::build_or)),
"build-xor" => Some(llvm_builder_build_int_op(args, "xor", Builder::build_xor)),
"build-select" => Some(llvm_builder_build_select(args)),
"build-shl" => Some(llvm_builder_build_int_op(args, "shl", Builder::build_left_shift)),
"build-lshr" => Some(llvm_builder_build_int_op(args, "lshr", |b, lhs, rhs, name| b.build_right_shift(lhs, rhs, false, name))),
"build-ashr" => Some(llvm_builder_build_int_op(args, "ashr", |b, lhs, rhs, name| b.build_right_shift(lhs, rhs, true, name))),
"build-fadd" => Some(llvm_builder_build_float_op(args, "fadd", Builder::build_float_add)),
"build-fsub" => Some(llvm_builder_build_float_op(args, "fsub", Builder::build_float_sub)),
"build-fmul" => Some(llvm_builder_build_float_op(args, "fmul", Builder::build_float_mul)),
"build-fdiv" => Some(llvm_builder_build_float_op(args, "fdiv", Builder::build_float_div)),
"build-frem" => Some(llvm_builder_build_float_op(args, "frem", Builder::build_float_rem)),
"build-fcmp-lt" => Some(llvm_builder_build_fcmp(args, "fcmp_lt", inkwell::FloatPredicate::OLT)),
"build-fcmp-le" => Some(llvm_builder_build_fcmp(args, "fcmp_le", inkwell::FloatPredicate::OLE)),
"build-fcmp-gt" => Some(llvm_builder_build_fcmp(args, "fcmp_gt", inkwell::FloatPredicate::OGT)),
"build-fcmp-ge" => Some(llvm_builder_build_fcmp(args, "fcmp_ge", inkwell::FloatPredicate::OGE)),
"build-fcmp-eq" => Some(llvm_builder_build_fcmp(args, "fcmp_eq", inkwell::FloatPredicate::OEQ)),
"build-fcmp-ne" => Some(llvm_builder_build_fcmp(args, "fcmp_ne", inkwell::FloatPredicate::UNE)),
"build-fsqrt" => Some(llvm_builder_build_float_unary_intrinsic(args, "fsqrt", "llvm.sqrt.f64")),
"build-ffloor" => Some(llvm_builder_build_float_unary_intrinsic(args, "ffloor", "llvm.floor.f64")),
"build-fceil" => Some(llvm_builder_build_float_unary_intrinsic(args, "fceil", "llvm.ceil.f64")),
"build-fround" => Some(llvm_builder_build_float_unary_intrinsic(args, "fround", "llvm.round.f64")),
"build-ftrunc" => Some(llvm_builder_build_float_unary_intrinsic(args, "ftrunc", "llvm.trunc.f64")),
"build-fpow" => Some(llvm_builder_build_fpow(args)),
"build-fmaxnum" => Some(llvm_builder_build_float_binary_intrinsic(args, "fmaxnum", "llvm.maxnum.f64")),
"build-fminnum" => Some(llvm_builder_build_float_binary_intrinsic(args, "fminnum", "llvm.minnum.f64")),
"build-fsin" => Some(llvm_builder_build_float_unary_intrinsic(args, "fsin", "llvm.sin.f64")),
"build-fcos" => Some(llvm_builder_build_float_unary_intrinsic(args, "fcos", "llvm.cos.f64")),
"build-fexp" => Some(llvm_builder_build_float_unary_intrinsic(args, "fexp", "llvm.exp.f64")),
"build-flog" => Some(llvm_builder_build_float_unary_intrinsic(args, "flog", "llvm.log.f64")),
"build-fptosi" => Some(llvm_builder_build_fptosi(args)),
"build-sitofp" => Some(llvm_builder_build_sitofp(args)),
"build-fround32" => Some(llvm_builder_build_fround32(args)),
"build-sadd-overflow" => Some(llvm_builder_build_int_overflow(args, "sadd", "llvm.sadd.with.overflow")),
"build-ssub-overflow" => Some(llvm_builder_build_int_overflow(args, "ssub", "llvm.ssub.with.overflow")),
"build-smul-overflow" => Some(llvm_builder_build_int_overflow(args, "smul", "llvm.smul.with.overflow")),
"alloca-args" => Some(llvm_builder_alloca_args(args)),
"store-arg" => Some(llvm_builder_store_arg(args)),
"build-call" => Some(llvm_builder_build_call(args)),
"load-env" => Some(llvm_builder_load_env(args)),
"build-call-with-env" => Some(llvm_builder_build_call_with_env(args)),
"build-make-closure" => Some(llvm_builder_build_make_closure(args)),
"load-raw" => Some(llvm_builder_load_raw(args)),
"build-slot-ptr" => Some(llvm_builder_build_slot_ptr(args)),
"frame-begin" => Some(llvm_builder_frame_begin(args)),
"frame-value" => Some(llvm_builder_frame_value(args)),
"frame-slot" => Some(llvm_builder_frame_slot(args)),
"frame-slot-rooted" => Some(llvm_builder_frame_slot_rooted(args)),
"frame-end" => Some(llvm_builder_frame_end(args)),
"coroutine-begin" => Some(llvm_builder_coroutine_begin(args)),
"coroutine-call" => Some(llvm_builder_coroutine_call(args)),
"coroutine-suspend" => Some(llvm_builder_coroutine_suspend(args)),
"coroutine-unwind" => Some(llvm_builder_coroutine_unwind(args)),
"frame-set-handler" => Some(llvm_builder_frame_set_handler(args)),
"frame-clear-handler" => Some(llvm_builder_frame_clear_handler(args)),
"coroutine-call-env" => Some(llvm_builder_coroutine_call_env(args)),
"coroutine-apply" => Some(llvm_builder_coroutine_apply(args)),
"coroutine-dyn-call" => Some(llvm_builder_coroutine_dyn_call(args)),
"coroutine-end" => Some(llvm_builder_coroutine_end(args)),
"build-icmp-lt" => Some(llvm_builder_build_icmp(args, "icmp_lt", inkwell::IntPredicate::SLT)),
"build-icmp-le" => Some(llvm_builder_build_icmp(args, "icmp_le", inkwell::IntPredicate::SLE)),
"build-icmp-gt" => Some(llvm_builder_build_icmp(args, "icmp_gt", inkwell::IntPredicate::SGT)),
"build-icmp-ge" => Some(llvm_builder_build_icmp(args, "icmp_ge", inkwell::IntPredicate::SGE)),
"build-icmp-eq" => Some(llvm_builder_build_icmp(args, "icmp_eq", inkwell::IntPredicate::EQ)),
"build-icmp-ne" => Some(llvm_builder_build_icmp(args, "icmp_ne", inkwell::IntPredicate::NE)),
"build-cond-br" => Some(llvm_builder_build_cond_br(args)),
"build-br" => Some(llvm_builder_build_br(args)),
"block-terminated?" => Some(llvm_builder_block_terminated(args)),
"build-malloc" => Some(llvm_builder_build_malloc(args)),
"build-free" => Some(llvm_builder_build_free(args)),
"build-int-to-ptr" => Some(llvm_builder_build_int_to_ptr(args)),
"build-ptr-to-int" => Some(llvm_builder_build_ptr_to_int(args)),
"build-fn-address" => Some(llvm_builder_build_fn_address(args)),
_ => None,
};
}
None
}
fn expect_llvm_module(v: &Value) -> Result<Rc<RefCell<Module<'static>>>, EvalError> {
match expect_handle(v, "LlvmModule")? {
NativeHandle::Module(m) => Ok(m),
_ => Err(EvalError::Internal(format!("expected an LlvmModule, got {:?}", v))),
}
}
fn expect_llvm_function(v: &Value) -> Result<FunctionValue<'static>, EvalError> {
match expect_handle(v, "LlvmFunction")? {
NativeHandle::Function(f) => Ok(f),
_ => Err(EvalError::Internal(format!("expected an LlvmFunction, got {:?}", v))),
}
}
fn expect_llvm_builder(v: &Value) -> Result<Rc<RefCell<Builder<'static>>>, EvalError> {
match expect_handle(v, "LlvmBuilder")? {
NativeHandle::Builder(b) => Ok(b),
_ => Err(EvalError::Internal(format!("expected an LlvmBuilder, got {:?}", v))),
}
}
fn expect_llvm_basic_block(v: &Value) -> Result<BasicBlock<'static>, EvalError> {
match expect_handle(v, "LlvmBasicBlock")? {
NativeHandle::BasicBlock(b) => Ok(b),
_ => Err(EvalError::Internal(format!("expected an LlvmBasicBlock, got {:?}", v))),
}
}
fn expect_llvm_value(v: &Value) -> Result<BasicValueEnum<'static>, EvalError> {
match expect_handle(v, "LlvmValue")? {
NativeHandle::Value(x) => Ok(x),
_ => Err(EvalError::Internal(format!("expected an LlvmValue, got {:?}", v))),
}
}
fn expect_int(v: &Value) -> Result<i64, EvalError> {
match v {
Value::Int(n) => Ok(*n),
other => Err(EvalError::Internal(format!("expected an Int, got {:?}", other))),
}
}
fn i64_word_ptr(
b: &Builder<'static>,
array_ptr: PointerValue<'static>,
index: u64,
name: &str,
who: &str,
) -> Result<PointerValue<'static>, EvalError> {
let ctx = crate::compile::llvm_context();
let idx_val = ctx.i64_type().const_int(index, false);
unsafe { b.build_gep(ctx.i64_type(), array_ptr, &[idx_val], name) }
.map_err(|e| EvalError::Internal(format!("{}: {}", who, e)))
}
fn load_i64_word(
b: &Builder<'static>,
array_ptr: PointerValue<'static>,
index: u64,
names: (&str, &str),
who: &str,
) -> Result<BasicValueEnum<'static>, EvalError> {
let elem_ptr = i64_word_ptr(b, array_ptr, index, names.0, who)?;
let ctx = crate::compile::llvm_context();
b.build_load(ctx.i64_type(), elem_ptr, names.1).map_err(|e| EvalError::Internal(format!("{}: {}", who, e)))
}
fn llvm_module_create(heap: &Heap, args: &[Value]) -> Result<Value, EvalError> {
let name = expect_str(heap, &args[0])?;
let module = crate::compile::llvm_context().create_module(&name);
Ok(llvm_module_value(module))
}
pub(crate) fn compiled_fn_type() -> inkwell::types::FunctionType<'static> {
let ctx = crate::compile::llvm_context();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
ctx.i64_type().fn_type(&[ptr_ty.into(), ctx.i32_type().into()], false)
}
fn llvm_module_add_function(heap: &Heap, args: &[Value]) -> Result<Value, EvalError> {
let module = expect_llvm_module(&args[0])?;
let name = expect_str(heap, &args[1])?;
let existing = module.borrow().get_function(&name);
let function = existing.unwrap_or_else(|| module.borrow_mut().add_function(&name, compiled_fn_type(), None));
Ok(llvm_function_value(function))
}
pub(crate) fn coroutine_fn_type() -> inkwell::types::FunctionType<'static> {
let ctx = crate::compile::llvm_context();
ctx.i64_type().fn_type(&[ctx.i64_type().into()], false)
}
fn llvm_module_add_coroutine_function(heap: &Heap, args: &[Value]) -> Result<Value, EvalError> {
let module = expect_llvm_module(&args[0])?;
let name = expect_str(heap, &args[1])?;
let existing = module.borrow().get_function(&name);
let function = existing.unwrap_or_else(|| module.borrow_mut().add_function(&name, coroutine_fn_type(), None));
Ok(llvm_function_value(function))
}
fn llvm_function_param(args: &[Value]) -> Result<Value, EvalError> {
let function = expect_llvm_function(&args[0])?;
let idx = match &args[1] {
Value::Int(n) => *n as u32,
other => return Err(EvalError::Internal(format!("function-param: expected an Int, got {:?}", other))),
};
function
.get_nth_param(idx)
.map(llvm_value_value)
.ok_or_else(|| EvalError::Internal(format!("function-param: no parameter {}", idx)))
}
pub(crate) fn declare_external_function(module: &Rc<RefCell<Module<'static>>>, name: &str) {
module.borrow_mut().add_function(name, compiled_fn_type(), None);
}
pub(crate) fn declare_external_compiled_function(module: &Rc<RefCell<Module<'static>>>, name: &str) {
let ty = if crate::compile::EMITTED_BODY_ABI == typelisp_abi::BODY_ABI_COROUTINE {
coroutine_fn_type()
} else {
compiled_fn_type()
};
module.borrow_mut().add_function(name, ty, None);
}
fn compiled_fn_type_with_env() -> inkwell::types::FunctionType<'static> {
let ctx = crate::compile::llvm_context();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
ctx.i64_type().fn_type(&[ptr_ty.into(), ctx.i32_type().into(), ptr_ty.into(), ctx.i32_type().into()], false)
}
fn llvm_module_add_function_with_env(heap: &Heap, args: &[Value]) -> Result<Value, EvalError> {
let module = expect_llvm_module(&args[0])?;
let name = expect_str(heap, &args[1])?;
let function = module.borrow_mut().add_function(&name, compiled_fn_type_with_env(), None);
Ok(llvm_function_value(function))
}
fn llvm_module_verify(args: &[Value]) -> Result<Value, EvalError> {
let module = expect_llvm_module(&args[0])?;
let ok = module.borrow().verify().is_ok();
Ok(Value::Bool(ok))
}
fn llvm_module_to_string(heap: &mut Heap, args: &[Value]) -> Result<Value, EvalError> {
let module = expect_llvm_module(&args[0])?;
let text = module.borrow().print_to_string().to_string();
Ok(str_rt(heap, text))
}
fn llvm_module_get_function(heap: &Heap, args: &[Value]) -> Result<Value, EvalError> {
let module = expect_llvm_module(&args[0])?;
let name = expect_str(heap, &args[1])?;
let found = module.borrow().get_function(&name);
found
.map(llvm_function_value)
.ok_or_else(|| EvalError::Panic(format!("get-function: no function named \"{}\" in this module", name)))
}
fn llvm_function_append_block(heap: &Heap, args: &[Value]) -> Result<Value, EvalError> {
let function = expect_llvm_function(&args[0])?;
let name = expect_str(heap, &args[1])?;
let block = crate::compile::llvm_context().append_basic_block(function, &name);
Ok(llvm_block_value(block))
}
fn llvm_builder_create() -> Result<Value, EvalError> {
let builder = crate::compile::llvm_context().create_builder();
Ok(llvm_builder_value(builder))
}
fn llvm_builder_position_at_end(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let block = expect_llvm_basic_block(&args[1])?;
builder.borrow().position_at_end(block);
Ok(Value::Empty)
}
fn llvm_builder_const_word(args: &[Value]) -> Result<Value, EvalError> {
let _builder = expect_llvm_builder(&args[0])?;
let n = expect_int(&args[1])?;
let value = crate::compile::llvm_context().i64_type().const_int(n as u64, false);
Ok(llvm_value_value(value.into()))
}
fn llvm_builder_build_ret(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let value = expect_llvm_value(&args[1])?;
builder
.borrow()
.build_return(Some(&value))
.map_err(|e| EvalError::Internal(format!("build-ret: {}", e)))?;
Ok(Value::Empty)
}
fn llvm_builder_load_arg(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let function = expect_llvm_function(&args[1])?;
let index = expect_int(&args[2])? as u64;
let args_ptr = function
.get_nth_param(0)
.ok_or_else(|| EvalError::Internal("load-arg: function has no args parameter".into()))?
.into_pointer_value();
let loaded = load_i64_word(&builder.borrow(), args_ptr, index, ("arg_ptr", "arg_val"), "load-arg")?;
Ok(llvm_value_value(loaded))
}
fn llvm_builder_load_env(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let function = expect_llvm_function(&args[1])?;
let index = expect_int(&args[2])? as u64;
let env_ptr = function
.get_nth_param(2)
.ok_or_else(|| EvalError::Internal("load-env: function has no env parameter".into()))?
.into_pointer_value();
let loaded = load_i64_word(&builder.borrow(), env_ptr, index, ("env_ptr", "env_val"), "load-env")?;
Ok(llvm_value_value(loaded))
}
fn llvm_builder_build_int_op(
args: &[Value],
name: &str,
op: impl FnOnce(&Builder<'static>, inkwell::values::IntValue<'static>, inkwell::values::IntValue<'static>, &str) -> Result<inkwell::values::IntValue<'static>, inkwell::builder::BuilderError>,
) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let a = expect_llvm_value(&args[1])?.into_int_value();
let b = expect_llvm_value(&args[2])?.into_int_value();
let result = op(&builder.borrow(), a, b, name).map_err(|e| EvalError::Internal(format!("build-{}: {}", name, e)))?;
Ok(llvm_value_value(result.into()))
}
fn llvm_builder_build_float_op(
args: &[Value],
name: &str,
op: impl FnOnce(&Builder<'static>, inkwell::values::FloatValue<'static>, inkwell::values::FloatValue<'static>, &str) -> Result<inkwell::values::FloatValue<'static>, inkwell::builder::BuilderError>,
) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let a_bits = expect_llvm_value(&args[1])?.into_int_value();
let b_bits = expect_llvm_value(&args[2])?.into_int_value();
let bld = builder.borrow();
let ctx = crate::compile::llvm_context();
let f64_ty = ctx.f64_type();
let err = |e: inkwell::builder::BuilderError| EvalError::Internal(format!("build-{}: {}", name, e));
let a = bld.build_bit_cast(a_bits, f64_ty, "a_f").map_err(err)?.into_float_value();
let b = bld.build_bit_cast(b_bits, f64_ty, "b_f").map_err(err)?.into_float_value();
let result = op(&bld, a, b, name).map_err(err)?;
let bits = bld.build_bit_cast(result, ctx.i64_type(), name).map_err(err)?;
Ok(llvm_value_value(bits))
}
fn llvm_builder_build_fcmp(args: &[Value], name: &str, predicate: inkwell::FloatPredicate) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let a_bits = expect_llvm_value(&args[1])?.into_int_value();
let b_bits = expect_llvm_value(&args[2])?.into_int_value();
let bld = builder.borrow();
let ctx = crate::compile::llvm_context();
let f64_ty = ctx.f64_type();
let err = |e: inkwell::builder::BuilderError| EvalError::Internal(format!("{}: {}", name, e));
let a = bld.build_bit_cast(a_bits, f64_ty, "a_f").map_err(err)?.into_float_value();
let b = bld.build_bit_cast(b_bits, f64_ty, "b_f").map_err(err)?.into_float_value();
let cmp = bld.build_float_compare(predicate, a, b, name).map_err(err)?;
let widened = bld.build_int_z_extend(cmp, ctx.i64_type(), name).map_err(err)?;
Ok(llvm_value_value(widened.into()))
}
fn llvm_builder_build_float_unary_intrinsic(args: &[Value], name: &str, intrinsic_name: &str) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let module = expect_llvm_module(&args[1])?;
let x_bits = expect_llvm_value(&args[2])?.into_int_value();
let bld = builder.borrow();
let ctx = crate::compile::llvm_context();
let f64_ty = ctx.f64_type();
let err = |e: String| EvalError::Internal(format!("build-{}: {}", name, e));
let x = bld.build_bit_cast(x_bits, f64_ty, "x_f").map_err(|e| err(e.to_string()))?.into_float_value();
let intrinsic = inkwell::intrinsics::Intrinsic::find(intrinsic_name)
.ok_or_else(|| err(format!("no such LLVM intrinsic {}", intrinsic_name)))?;
let decl = intrinsic
.get_declaration(&module.borrow(), &[f64_ty.into()])
.ok_or_else(|| err(format!("failed to declare {}", intrinsic_name)))?;
let call = bld.build_call(decl, &[x.into()], name).map_err(|e| err(e.to_string()))?;
let result = match call.try_as_basic_value() {
inkwell::values::ValueKind::Basic(v) => v.into_float_value(),
inkwell::values::ValueKind::Instruction(_) => return Err(err(format!("{} produced no value", intrinsic_name))),
};
let bits = bld.build_bit_cast(result, ctx.i64_type(), name).map_err(|e| err(e.to_string()))?;
Ok(llvm_value_value(bits))
}
fn llvm_builder_build_float_binary_intrinsic(args: &[Value], name: &str, intrinsic_name: &str) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let module = expect_llvm_module(&args[1])?;
let a_bits = expect_llvm_value(&args[2])?.into_int_value();
let b_bits = expect_llvm_value(&args[3])?.into_int_value();
let bld = builder.borrow();
let ctx = crate::compile::llvm_context();
let f64_ty = ctx.f64_type();
let err = |e: String| EvalError::Internal(format!("build-{}: {}", name, e));
let a = bld.build_bit_cast(a_bits, f64_ty, "a_f").map_err(|e| err(e.to_string()))?.into_float_value();
let b = bld.build_bit_cast(b_bits, f64_ty, "b_f").map_err(|e| err(e.to_string()))?.into_float_value();
let intrinsic = inkwell::intrinsics::Intrinsic::find(intrinsic_name).ok_or_else(|| err(format!("no such LLVM intrinsic {}", intrinsic_name)))?;
let decl = intrinsic
.get_declaration(&module.borrow(), &[f64_ty.into()])
.ok_or_else(|| err(format!("failed to declare {}", intrinsic_name)))?;
let call = bld.build_call(decl, &[a.into(), b.into()], name).map_err(|e| err(e.to_string()))?;
let result = match call.try_as_basic_value() {
inkwell::values::ValueKind::Basic(v) => v.into_float_value(),
inkwell::values::ValueKind::Instruction(_) => return Err(err(format!("{} produced no value", intrinsic_name))),
};
let bits = bld.build_bit_cast(result, ctx.i64_type(), name).map_err(|e| err(e.to_string()))?;
Ok(llvm_value_value(bits))
}
fn llvm_builder_build_fpow(args: &[Value]) -> Result<Value, EvalError> {
llvm_builder_build_float_binary_intrinsic(args, "fpow", "llvm.pow.f64")
}
fn llvm_builder_build_select(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let cond = expect_llvm_value(&args[1])?.into_int_value();
let then_v = expect_llvm_value(&args[2])?.into_int_value();
let else_v = expect_llvm_value(&args[3])?.into_int_value();
let bld = builder.borrow();
let err = |e: String| EvalError::Internal(format!("build-select: {}", e));
let ctx = crate::compile::llvm_context();
let zero = ctx.i64_type().const_zero();
let is_nonzero = bld.build_int_compare(inkwell::IntPredicate::NE, cond, zero, "select_cond").map_err(|e| err(e.to_string()))?;
let result = bld.build_select(is_nonzero, then_v, else_v, "select").map_err(|e| err(e.to_string()))?;
Ok(llvm_value_value(result.into_int_value().into()))
}
fn llvm_builder_build_fptosi(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let module = expect_llvm_module(&args[1])?;
let x_bits = expect_llvm_value(&args[2])?.into_int_value();
let bld = builder.borrow();
let ctx = crate::compile::llvm_context();
let f64_ty = ctx.f64_type();
let i32_ty = ctx.i32_type();
let i64_ty = ctx.i64_type();
let err = |e: String| EvalError::Internal(format!("build-fptosi: {}", e));
let x = bld.build_bit_cast(x_bits, f64_ty, "x_f").map_err(|e| err(e.to_string()))?.into_float_value();
let intrinsic =
inkwell::intrinsics::Intrinsic::find("llvm.fptosi.sat").ok_or_else(|| err("no such LLVM intrinsic llvm.fptosi.sat".into()))?;
let decl = intrinsic
.get_declaration(&module.borrow(), &[i32_ty.into(), f64_ty.into()])
.ok_or_else(|| err("failed to declare llvm.fptosi.sat".into()))?;
let call = bld.build_call(decl, &[x.into()], "fptosi_sat").map_err(|e| err(e.to_string()))?;
let narrow = match call.try_as_basic_value() {
inkwell::values::ValueKind::Basic(v) => v.into_int_value(),
inkwell::values::ValueKind::Instruction(_) => return Err(err("llvm.fptosi.sat produced no value".into())),
};
let result = bld.build_int_s_extend(narrow, i64_ty, "fptosi_sext").map_err(|e| err(e.to_string()))?;
Ok(llvm_value_value(result.into()))
}
fn llvm_builder_build_sitofp(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let x = expect_llvm_value(&args[1])?.into_int_value();
let bld = builder.borrow();
let ctx = crate::compile::llvm_context();
let err = |e: String| EvalError::Internal(format!("build-sitofp: {}", e));
let f = bld.build_signed_int_to_float(x, ctx.f64_type(), "x_f").map_err(|e| err(e.to_string()))?;
let bits = bld.build_bit_cast(f, ctx.i64_type(), "x_bits").map_err(|e| err(e.to_string()))?;
Ok(llvm_value_value(bits.into_int_value().into()))
}
fn llvm_builder_build_fround32(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let x_bits = expect_llvm_value(&args[1])?.into_int_value();
let bld = builder.borrow();
let ctx = crate::compile::llvm_context();
let err = |e: String| EvalError::Internal(format!("build-fround32: {}", e));
let x = bld.build_bit_cast(x_bits, ctx.f64_type(), "x_f").map_err(|e| err(e.to_string()))?.into_float_value();
let narrow = bld.build_float_trunc(x, ctx.f32_type(), "x_f32").map_err(|e| err(e.to_string()))?;
let wide = bld.build_float_ext(narrow, ctx.f64_type(), "x_f64").map_err(|e| err(e.to_string()))?;
let bits = bld.build_bit_cast(wide, ctx.i64_type(), "x_bits").map_err(|e| err(e.to_string()))?;
Ok(llvm_value_value(bits.into_int_value().into()))
}
fn llvm_builder_build_int_overflow(args: &[Value], name: &str, intrinsic_name: &str) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let module = expect_llvm_module(&args[1])?;
let a = expect_llvm_value(&args[2])?.into_int_value();
let b = expect_llvm_value(&args[3])?.into_int_value();
let flag_slot = expect_llvm_value(&args[4])?.into_pointer_value();
let bld = builder.borrow();
let ctx = crate::compile::llvm_context();
let i64_ty = ctx.i64_type();
let err = |e: String| EvalError::Internal(format!("build-{}-overflow: {}", name, e));
let intrinsic = inkwell::intrinsics::Intrinsic::find(intrinsic_name).ok_or_else(|| err(format!("no such LLVM intrinsic {}", intrinsic_name)))?;
let decl = intrinsic
.get_declaration(&module.borrow(), &[i64_ty.into()])
.ok_or_else(|| err(format!("failed to declare {}", intrinsic_name)))?;
let call = bld.build_call(decl, &[a.into(), b.into()], name).map_err(|e| err(e.to_string()))?;
let pair = match call.try_as_basic_value() {
inkwell::values::ValueKind::Basic(v) => v.into_struct_value(),
inkwell::values::ValueKind::Instruction(_) => return Err(err(format!("{} produced no value", intrinsic_name))),
};
let result = bld.build_extract_value(pair, 0, "ov_result").map_err(|e| err(e.to_string()))?.into_int_value();
let flag = bld.build_extract_value(pair, 1, "ov_flag").map_err(|e| err(e.to_string()))?.into_int_value();
let flag_wide = bld.build_int_z_extend(flag, i64_ty, "ov_flag_i64").map_err(|e| err(e.to_string()))?;
let zero = i64_ty.const_zero();
let slot0 = unsafe { bld.build_gep(i64_ty, flag_slot, &[zero], "ov_flag_ptr").map_err(|e| err(e.to_string()))? };
bld.build_store(slot0, flag_wide).map_err(|e| err(e.to_string()))?;
Ok(llvm_value_value(result.into()))
}
fn llvm_builder_alloca_args(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let count = expect_int(&args[1])? as u32;
let ctx = crate::compile::llvm_context();
let array_ty = ctx.i64_type().array_type(count);
let b = builder.borrow();
let here = b.get_insert_block();
let entry = here
.and_then(|blk| blk.get_parent())
.and_then(|f| f.get_first_basic_block())
.ok_or_else(|| EvalError::Internal("alloca-args: the builder is not inside a function".to_string()))?;
match entry.get_terminator() {
Some(term) => b.position_before(&term),
None => b.position_at_end(entry),
}
let ptr =
b.build_alloca(array_ty, "call_args").map_err(|e| EvalError::Internal(format!("alloca-args: {}", e)))?;
if let Some(blk) = here {
b.position_at_end(blk);
}
Ok(llvm_value_value(ptr.into()))
}
fn llvm_builder_store_arg(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let array_ptr = expect_llvm_value(&args[1])?.into_pointer_value();
let index = expect_int(&args[2])? as u64;
let value = expect_llvm_value(&args[3])?.into_int_value();
let b = builder.borrow();
let elem_ptr = i64_word_ptr(&b, array_ptr, index, "store_arg_ptr", "store-arg")?;
b.build_store(elem_ptr, value).map_err(|e| EvalError::Internal(format!("store-arg: {}", e)))?;
Ok(Value::Empty)
}
fn llvm_builder_load_raw(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let array_ptr = expect_llvm_value(&args[1])?.into_pointer_value();
let index = expect_int(&args[2])? as u64;
let loaded = load_i64_word(&builder.borrow(), array_ptr, index, ("load_raw_ptr", "load_raw_val"), "load-raw")?;
Ok(llvm_value_value(loaded))
}
fn llvm_builder_build_slot_ptr(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let array_ptr = expect_llvm_value(&args[1])?.into_pointer_value();
let index = expect_int(&args[2])? as u64;
let elem_ptr = i64_word_ptr(&builder.borrow(), array_ptr, index, "slot_ptr", "build-slot-ptr")?;
Ok(llvm_value_value(elem_ptr.into()))
}
struct FrameCtx {
frame: BasicValueEnum<'static>,
data: PointerValue<'static>,
size_store: InstructionValue<'static>,
next: u64,
coro: Option<CoroCtx>,
}
struct CoroCtx {
function: FunctionValue<'static>,
body: BasicBlock<'static>,
dispatch: BasicBlock<'static>,
pc: IntValue<'static>,
resumes: Vec<(u64, BasicBlock<'static>)>,
handlers: Vec<(BasicBlock<'static>, u64)>,
fresh_end: InstructionValue<'static>,
fresh_frame: BasicValueEnum<'static>,
prologue_end: InstructionValue<'static>,
}
thread_local! {
static FRAME_CTXS: RefCell<HashMap<usize, FrameCtx>> = RefCell::new(HashMap::new());
}
fn builder_key(b: &Rc<RefCell<Builder<'static>>>) -> usize {
Rc::as_ptr(b) as usize
}
fn open_coroutine_frame(
builder: &Rc<RefCell<Builder<'static>>>,
what: &str,
) -> Result<(BasicValueEnum<'static>, PointerValue<'static>, FunctionValue<'static>, u64), EvalError> {
let key = builder_key(builder);
FRAME_CTXS.with(|c| {
let mut map = c.borrow_mut();
let f = map
.get_mut(&key)
.ok_or_else(|| EvalError::Internal(format!("{}: no frame is open on this builder", what)))?;
let coro = f
.coro
.as_mut()
.ok_or_else(|| EvalError::Internal(format!("{}: this function is not a coroutine", what)))?;
let id = coro.resumes.len() as u64 + 1;
Ok((f.frame, f.data, coro.function, id))
})
}
fn ensure_declared(module: &Rc<RefCell<Module<'static>>>, name: &str) -> FunctionValue<'static> {
let existing = module.borrow().get_function(name);
match existing {
Some(f) => f,
None => module.borrow_mut().add_function(name, compiled_fn_type(), None),
}
}
fn llvm_builder_frame_begin(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let module = expect_llvm_module(&args[1])?;
let ctx = crate::compile::llvm_context();
let i64t = ctx.i64_type();
let err = |what: &str, e: String| EvalError::Internal(format!("frame-begin: {}: {}", what, e));
let frame_new = ensure_declared(&module, "rt_frame_new");
let frame_data = ensure_declared(&module, "rt_frame_data");
let push_root = ensure_declared(&module, "rt_push_sexpr_root");
ensure_declared(&module, "rt_pop_sexpr_root");
ensure_declared(&module, "rt_frame_mask_bit");
let b = builder.borrow();
let size_args = b
.build_alloca(i64t.array_type(1), "frame_size_args")
.map_err(|e| err("alloca", e.to_string()))?;
let size_slot = unsafe {
b.build_gep(i64t, size_args, &[i64t.const_int(0, false)], "frame_size_slot")
.map_err(|e| err("gep", e.to_string()))?
};
let size_store = b
.build_store(size_slot, i64t.const_int(0, false))
.map_err(|e| err("store", e.to_string()))?;
let frame = match b
.build_call(frame_new, &[size_args.into(), ctx.i32_type().const_int(1, false).into()], "frame")
.map_err(|e| err("call rt_frame_new", e.to_string()))?
.try_as_basic_value()
{
inkwell::values::ValueKind::Basic(v) => v,
inkwell::values::ValueKind::Instruction(_) => {
return Err(EvalError::Internal("frame-begin: rt_frame_new returned no value".to_string()))
}
};
let data_args = b
.build_alloca(i64t.array_type(1), "frame_data_args")
.map_err(|e| err("alloca", e.to_string()))?;
let data_slot = unsafe {
b.build_gep(i64t, data_args, &[i64t.const_int(0, false)], "frame_data_slot")
.map_err(|e| err("gep", e.to_string()))?
};
b.build_store(data_slot, frame).map_err(|e| err("store", e.to_string()))?;
let data_word = match b
.build_call(frame_data, &[data_args.into(), ctx.i32_type().const_int(1, false).into()], "frame_data")
.map_err(|e| err("call rt_frame_data", e.to_string()))?
.try_as_basic_value()
{
inkwell::values::ValueKind::Basic(v) => v,
inkwell::values::ValueKind::Instruction(_) => {
return Err(EvalError::Internal("frame-begin: rt_frame_data returned no value".to_string()))
}
};
let data = b
.build_int_to_ptr(data_word.into_int_value(), ctx.ptr_type(inkwell::AddressSpace::default()), "frame_ptr")
.map_err(|e| err("inttoptr", e.to_string()))?;
let root_args = b.build_alloca(i64t.array_type(1), "frame_root_args").map_err(|e| err("alloca", e.to_string()))?;
let root_slot = unsafe {
b.build_gep(i64t, root_args, &[i64t.const_int(0, false)], "frame_root_slot")
.map_err(|e| err("gep", e.to_string()))?
};
b.build_store(root_slot, frame).map_err(|e| err("store", e.to_string()))?;
b.build_call(push_root, &[root_args.into(), ctx.i32_type().const_int(1, false).into()], "")
.map_err(|e| err("call rt_push_sexpr_root", e.to_string()))?;
FRAME_CTXS.with(|c| {
c.borrow_mut().insert(builder_key(&builder), FrameCtx { data, frame, size_store, next: 1, coro: None });
});
Ok(Value::Empty)
}
fn llvm_builder_frame_value(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let key = builder_key(&builder);
FRAME_CTXS.with(|c| {
let map = c.borrow();
let f = map
.get(&key)
.ok_or_else(|| EvalError::Internal("frame-value: no frame is open on this builder".to_string()))?;
Ok(llvm_value_value(f.frame))
})
}
fn llvm_builder_frame_slot(args: &[Value]) -> Result<Value, EvalError> {
frame_slot_impl(args, false)
}
fn llvm_builder_frame_slot_rooted(args: &[Value]) -> Result<Value, EvalError> {
frame_slot_impl(args, true)
}
fn frame_slot_impl(args: &[Value], rooted: bool) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let ctx = crate::compile::llvm_context();
let i64t = ctx.i64_type();
let key = builder_key(&builder);
let (frame, data, idx, coro) = FRAME_CTXS.with(|c| {
let mut map = c.borrow_mut();
let f = map
.get_mut(&key)
.ok_or_else(|| EvalError::Internal("frame-slot: no frame is open on this builder".to_string()))?;
let idx = f.next;
f.next += 1;
let coro = f.coro.as_ref().map(|c| (c.fresh_end, c.fresh_frame, c.prologue_end));
Ok::<_, EvalError>((f.frame, f.data, idx, coro))
})?;
if let Some((fresh_end, fresh_frame, prologue_end)) = coro {
{
let b = builder.borrow();
let here = b.get_insert_block();
if rooted {
let module = expect_llvm_module(&args[1])?;
let mask_fn = ensure_declared(&module, "rt_frame_mask_bit");
b.position_before(&fresh_end);
emit_rt_call(&b, mask_fn, &[fresh_frame, i64t.const_int(idx, false).into()], "mask", "frame-slot")?;
}
b.position_before(&prologue_end);
let elem_ptr = unsafe {
b.build_gep(i64t, data, &[i64t.const_int(idx, false)], "frame_slot")
.map_err(|e| EvalError::Internal(format!("frame-slot: gep: {}", e)))?
};
if let Some(block) = here {
b.position_at_end(block);
}
return Ok(llvm_value_value(elem_ptr.into()));
}
}
let b = builder.borrow();
if rooted {
let module = expect_llvm_module(&args[1])?;
let mask_fn = ensure_declared(&module, "rt_frame_mask_bit");
let err = |what: &str, e: String| EvalError::Internal(format!("frame-slot-rooted: {}: {}", what, e));
let mask_args = b.build_alloca(i64t.array_type(2), "mask_args").map_err(|e| err("alloca", e.to_string()))?;
let a0 = unsafe {
b.build_gep(i64t, mask_args, &[i64t.const_int(0, false)], "mask_a0").map_err(|e| err("gep", e.to_string()))?
};
b.build_store(a0, frame).map_err(|e| err("store", e.to_string()))?;
let a1 = unsafe {
b.build_gep(i64t, mask_args, &[i64t.const_int(1, false)], "mask_a1").map_err(|e| err("gep", e.to_string()))?
};
b.build_store(a1, i64t.const_int(idx, false)).map_err(|e| err("store", e.to_string()))?;
b.build_call(mask_fn, &[mask_args.into(), ctx.i32_type().const_int(2, false).into()], "")
.map_err(|e| err("call rt_frame_mask_bit", e.to_string()))?;
}
let elem_ptr = unsafe {
b.build_gep(i64t, data, &[i64t.const_int(idx, false)], "frame_slot")
.map_err(|e| EvalError::Internal(format!("frame-slot: gep: {}", e)))?
};
Ok(llvm_value_value(elem_ptr.into()))
}
fn llvm_builder_frame_end(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let module = expect_llvm_module(&args[1])?;
let ctx = crate::compile::llvm_context();
let key = builder_key(&builder);
let f = FRAME_CTXS.with(|c| {
c.borrow_mut()
.remove(&key)
.ok_or_else(|| EvalError::Internal("frame-end: no frame is open on this builder".to_string()))
})?;
if !f.size_store.set_operand(0, ctx.i64_type().const_int(f.next, false)) {
return Err(EvalError::Internal("frame-end: could not write the slot count into the prologue".to_string()));
}
let pop_root = ensure_declared(&module, "rt_pop_sexpr_root");
let b = builder.borrow();
let empty = b
.build_alloca(ctx.i64_type().array_type(0), "frame_unroot_args")
.map_err(|e| EvalError::Internal(format!("frame-end: alloca: {}", e)))?;
b.build_call(pop_root, &[empty.into(), ctx.i32_type().const_int(0, false).into()], "")
.map_err(|e| EvalError::Internal(format!("frame-end: call rt_pop_sexpr_root: {}", e)))?;
Ok(Value::Empty)
}
fn emit_rt_call(
b: &Builder<'static>,
f: FunctionValue<'static>,
vals: &[BasicValueEnum<'static>],
name: &str,
what: &str,
) -> Result<Option<BasicValueEnum<'static>>, EvalError> {
let ctx = crate::compile::llvm_context();
let i64t = ctx.i64_type();
let err = |step: &str, e: String| EvalError::Internal(format!("{}: {}: {}", what, step, e));
let arr = b
.build_alloca(i64t.array_type(vals.len().max(1) as u32), &format!("{}_args", name))
.map_err(|e| err("alloca", e.to_string()))?;
for (i, v) in vals.iter().enumerate() {
let slot = unsafe {
b.build_gep(i64t, arr, &[i64t.const_int(i as u64, false)], &format!("{}_a{}", name, i))
.map_err(|e| err("gep", e.to_string()))?
};
b.build_store(slot, *v).map_err(|e| err("store", e.to_string()))?;
}
let call = b
.build_call(f, &[arr.into(), ctx.i32_type().const_int(vals.len() as u64, false).into()], name)
.map_err(|e| err("call", e.to_string()))?;
Ok(match call.try_as_basic_value() {
inkwell::values::ValueKind::Basic(v) => Some(v),
inkwell::values::ValueKind::Instruction(_) => None,
})
}
fn llvm_builder_coroutine_begin(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let module = expect_llvm_module(&args[1])?;
let function = expect_llvm_function(&args[2])?;
let ctx = crate::compile::llvm_context();
let i64t = ctx.i64_type();
let what = "coroutine-begin";
let err = |step: &str, e: String| EvalError::Internal(format!("{}: {}: {}", what, step, e));
let frame_new = ensure_declared(&module, "rt_frame_new");
let frame_data = ensure_declared(&module, "rt_frame_data");
let frame_pc = ensure_declared(&module, "rt_frame_pc");
let entered = ensure_declared(&module, "rt_frame_entered");
let push_root = ensure_declared(&module, "rt_push_sexpr_root");
ensure_declared(&module, "rt_pop_sexpr_root");
ensure_declared(&module, "rt_frame_mask_bit");
ensure_declared(&module, "rt_frame_set_pc");
ensure_declared(&module, "rt_frame_call");
ensure_declared(&module, "rt_pending_arg");
ensure_declared(&module, "rt_pending_argc");
ensure_declared(&module, "rt_pending_env");
ensure_declared(&module, "rt_pending_envc");
ensure_declared(&module, "rt_frame_call_env");
let param = function
.get_nth_param(0)
.ok_or_else(|| EvalError::Internal(format!("{}: the function takes no frame parameter", what)))?
.into_int_value();
let fresh = ctx.append_basic_block(function, "coro.fresh");
let resumed = ctx.append_basic_block(function, "coro.resumed");
let prologue = ctx.append_basic_block(function, "coro.prologue");
let dispatch = ctx.append_basic_block(function, "coro.dispatch");
let body = ctx.append_basic_block(function, "coro.body");
let b = builder.borrow();
let cell = b.build_alloca(i64t, "coro_frame_cell").map_err(|e| err("alloca", e.to_string()))?;
let is_first = b
.build_int_compare(inkwell::IntPredicate::EQ, param, i64t.const_int(0, false), "coro_first")
.map_err(|e| err("icmp", e.to_string()))?;
b.build_conditional_branch(is_first, fresh, resumed).map_err(|e| err("br", e.to_string()))?;
b.position_at_end(fresh);
let size_args = b.build_alloca(i64t.array_type(1), "frame_size_args").map_err(|e| err("alloca", e.to_string()))?;
let size_slot = unsafe {
b.build_gep(i64t, size_args, &[i64t.const_int(0, false)], "frame_size_slot")
.map_err(|e| err("gep", e.to_string()))?
};
let size_store =
b.build_store(size_slot, i64t.const_int(0, false)).map_err(|e| err("store", e.to_string()))?;
let made = match b
.build_call(frame_new, &[size_args.into(), ctx.i32_type().const_int(1, false).into()], "frame")
.map_err(|e| err("call rt_frame_new", e.to_string()))?
.try_as_basic_value()
{
inkwell::values::ValueKind::Basic(v) => v,
inkwell::values::ValueKind::Instruction(_) => {
return Err(EvalError::Internal(format!("{}: rt_frame_new returned no value", what)))
}
};
b.build_store(cell, made).map_err(|e| err("store", e.to_string()))?;
emit_rt_call(&b, push_root, &[made], "frame_root", what)?;
emit_rt_call(&b, entered, &[made], "frame_entered", what)?;
let fresh_end = b.build_unconditional_branch(prologue).map_err(|e| err("br", e.to_string()))?;
b.position_at_end(resumed);
b.build_store(cell, param).map_err(|e| err("store", e.to_string()))?;
b.build_unconditional_branch(prologue).map_err(|e| err("br", e.to_string()))?;
b.position_at_end(prologue);
let frame = b.build_load(i64t, cell, "coro_frame").map_err(|e| err("load", e.to_string()))?;
let data_word = emit_rt_call(&b, frame_data, &[frame], "frame_data", what)?
.ok_or_else(|| EvalError::Internal(format!("{}: rt_frame_data returned no value", what)))?;
let data = b
.build_int_to_ptr(data_word.into_int_value(), ctx.ptr_type(inkwell::AddressSpace::default()), "frame_ptr")
.map_err(|e| err("inttoptr", e.to_string()))?;
let pc = emit_rt_call(&b, frame_pc, &[frame], "frame_pc", what)?
.ok_or_else(|| EvalError::Internal(format!("{}: rt_frame_pc returned no value", what)))?
.into_int_value();
let prologue_end = b.build_unconditional_branch(dispatch).map_err(|e| err("br", e.to_string()))?;
b.position_at_end(body);
FRAME_CTXS.with(|c| {
c.borrow_mut().insert(
builder_key(&builder),
FrameCtx {
data,
frame,
size_store,
next: typelisp_abi::FRAME_RESERVED_SLOTS as u64,
coro: Some(CoroCtx {
function,
body,
dispatch,
pc,
resumes: Vec::new(),
handlers: Vec::new(),
fresh_end,
fresh_frame: made,
prologue_end,
}),
},
);
});
Ok(Value::Empty)
}
fn llvm_builder_coroutine_call(args: &[Value]) -> Result<Value, EvalError> {
coroutine_call_impl(args, "rt_frame_call", "coroutine-call", 1, None)
}
fn llvm_builder_coroutine_apply(args: &[Value]) -> Result<Value, EvalError> {
coroutine_call_impl(args, "rt_frame_apply", "coroutine-apply", 1, None)
}
fn llvm_builder_coroutine_suspend(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let module = expect_llvm_module(&args[1])?;
let ctx = crate::compile::llvm_context();
let i64t = ctx.i64_type();
let what = "coroutine-suspend";
let err = |step: &str, e: String| EvalError::Internal(format!("{}: {}: {}", what, step, e));
let key = builder_key(&builder);
let (frame, data, function, id) = open_coroutine_frame(&builder, what)?;
let resume = ctx.append_basic_block(function, &format!("coro.resume{}", id));
let set_pc = ensure_declared(&module, "rt_frame_set_pc");
let b = builder.borrow();
emit_rt_call(&b, set_pc, &[frame, i64t.const_int(id, false).into()], "frame_set_pc", what)?;
b.build_return(Some(&i64t.const_int(typelisp_abi::STATUS_SUSPEND as u64, false)))
.map_err(|e| err("ret", e.to_string()))?;
b.position_at_end(resume);
let value_slot = unsafe {
b.build_gep(i64t, data, &[i64t.const_int(typelisp_abi::FRAME_VALUE_SLOT as u64, false)], "coro_wake_slot")
.map_err(|e| err("gep", e.to_string()))?
};
let result = b.build_load(i64t, value_slot, "coro_wake").map_err(|e| err("load", e.to_string()))?;
FRAME_CTXS.with(|c| {
let mut map = c.borrow_mut();
if let Some(coro) = map.get_mut(&key).and_then(|f| f.coro.as_mut()) {
coro.resumes.push((id, resume));
}
});
Ok(llvm_value_value(result))
}
fn llvm_builder_coroutine_unwind(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
expect_llvm_module(&args[1])?;
let ctx = crate::compile::llvm_context();
let b = builder.borrow();
b.build_return(Some(&ctx.i64_type().const_int(typelisp_abi::STATUS_UNWIND as u64, false)))
.map_err(|e| EvalError::Internal(format!("coroutine-unwind: ret: {}", e)))?;
Ok(Value::Empty)
}
fn llvm_builder_frame_set_handler(args: &[Value]) -> Result<Value, EvalError> {
let pad = expect_llvm_basic_block(&args[2])?;
frame_handler(args, Some(pad), "frame-set-handler")
}
fn llvm_builder_frame_clear_handler(args: &[Value]) -> Result<Value, EvalError> {
frame_handler(args, None, "frame-clear-handler")
}
fn frame_handler(args: &[Value], pad: Option<BasicBlock<'static>>, what: &str) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
expect_llvm_module(&args[1])?;
let ctx = crate::compile::llvm_context();
let i64t = ctx.i64_type();
let err = |step: &str, e: String| EvalError::Internal(format!("{}: {}: {}", what, step, e));
let key = builder_key(&builder);
let (data, id) = FRAME_CTXS.with(|c| {
let mut map = c.borrow_mut();
let f = map
.get_mut(&key)
.ok_or_else(|| EvalError::Internal(format!("{}: no frame is open on this builder", what)))?;
let coro = f
.coro
.as_mut()
.ok_or_else(|| EvalError::Internal(format!("{}: this function is not a coroutine", what)))?;
let id = match pad {
None => 0,
Some(block) => match coro.handlers.iter().find(|(b, _)| *b == block) {
Some((_, id)) => *id,
None => {
let id = coro.resumes.len() as u64 + 1;
coro.resumes.push((id, block));
coro.handlers.push((block, id));
id
}
},
};
Ok::<_, EvalError>((f.data, id))
})?;
let b = builder.borrow();
let slot = unsafe {
b.build_gep(i64t, data, &[i64t.const_int(typelisp_abi::FRAME_HANDLER_SLOT as u64, false)], "handler_slot")
.map_err(|e| err("gep", e.to_string()))?
};
b.build_store(slot, i64t.const_int(id, false)).map_err(|e| err("store", e.to_string()))?;
Ok(Value::Empty)
}
fn llvm_builder_coroutine_dyn_call(args: &[Value]) -> Result<Value, EvalError> {
coroutine_call_impl(args, "rt_frame_dyn_call", "coroutine-dyn-call", 2, None)
}
fn llvm_builder_coroutine_call_env(args: &[Value]) -> Result<Value, EvalError> {
let env_ptr = expect_llvm_value(&args[5])?.into_pointer_value();
let env_len = match &args[6] {
Value::Int(n) if *n >= 0 => *n as u64,
other => {
return Err(EvalError::Internal(format!("coroutine-call-env: {:?} is not a capture count", other)))
}
};
coroutine_call_impl(args, "rt_frame_call_env", "coroutine-call-env", 1, Some((env_ptr, env_len)))
}
fn coroutine_call_impl(
args: &[Value],
shim: &str,
what: &str,
head_len: usize,
env: Option<(PointerValue<'static>, u64)>,
) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let module = expect_llvm_module(&args[1])?;
let mut head: Vec<BasicValueEnum<'static>> = Vec::with_capacity(head_len + 2);
for a in &args[2..2 + head_len] {
head.push(expect_llvm_value(a)?);
}
let args_ptr = expect_llvm_value(&args[2 + head_len])?.into_pointer_value();
let argc = match &args[3 + head_len] {
Value::Int(n) if *n >= 0 => *n as u64,
other => return Err(EvalError::Internal(format!("{}: {:?} is not an argument count", what, other))),
};
let ctx = crate::compile::llvm_context();
let i64t = ctx.i64_type();
let err = |step: &str, e: String| EvalError::Internal(format!("{}: {}: {}", what, step, e));
let key = builder_key(&builder);
let (frame, data, function, id) = open_coroutine_frame(&builder, what)?;
let resume = ctx.append_basic_block(function, &format!("coro.resume{}", id));
let frame_call = ensure_declared(&module, shim);
let set_pc = ensure_declared(&module, "rt_frame_set_pc");
let b = builder.borrow();
let mut call_args: Vec<BasicValueEnum<'static>> = Vec::with_capacity(argc as usize + head_len + 2);
call_args.extend(head);
if let Some((env_ptr, env_len)) = env {
call_args.push(
b.build_ptr_to_int(env_ptr, i64t, "coro_env_word").map_err(|e| err("ptrtoint", e.to_string()))?.into(),
);
call_args.push(i64t.const_int(env_len, false).into());
}
for i in 0..argc {
let slot = unsafe {
b.build_gep(i64t, args_ptr, &[i64t.const_int(i, false)], "coro_arg")
.map_err(|e| err("gep", e.to_string()))?
};
call_args.push(b.build_load(i64t, slot, "coro_arg_val").map_err(|e| err("load", e.to_string()))?);
}
emit_rt_call(&b, frame_call, &call_args, "frame_call", what)?;
emit_rt_call(&b, set_pc, &[frame, i64t.const_int(id, false).into()], "frame_set_pc", what)?;
b.build_return(Some(&i64t.const_int(typelisp_abi::STATUS_CALL as u64, false)))
.map_err(|e| err("ret", e.to_string()))?;
b.position_at_end(resume);
let value_slot = unsafe {
b.build_gep(i64t, data, &[i64t.const_int(typelisp_abi::FRAME_VALUE_SLOT as u64, false)], "coro_result_slot")
.map_err(|e| err("gep", e.to_string()))?
};
let result = b.build_load(i64t, value_slot, "coro_result").map_err(|e| err("load", e.to_string()))?;
FRAME_CTXS.with(|c| {
let mut map = c.borrow_mut();
if let Some(coro) = map.get_mut(&key).and_then(|f| f.coro.as_mut()) {
coro.resumes.push((id, resume));
}
});
Ok(llvm_value_value(result))
}
fn llvm_builder_coroutine_end(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let module = expect_llvm_module(&args[1])?;
let value = expect_llvm_value(&args[2])?;
let ctx = crate::compile::llvm_context();
let i64t = ctx.i64_type();
let what = "coroutine-end";
let err = |step: &str, e: String| EvalError::Internal(format!("{}: {}: {}", what, step, e));
let key = builder_key(&builder);
let f = FRAME_CTXS.with(|c| {
c.borrow_mut()
.remove(&key)
.ok_or_else(|| EvalError::Internal(format!("{}: no frame is open on this builder", what)))
})?;
let coro = f
.coro
.ok_or_else(|| EvalError::Internal(format!("{}: this function is not a coroutine", what)))?;
let pop_root = ensure_declared(&module, "rt_pop_sexpr_root");
let b = builder.borrow();
let value_slot = unsafe {
b.build_gep(i64t, f.data, &[i64t.const_int(typelisp_abi::FRAME_VALUE_SLOT as u64, false)], "coro_ret_slot")
.map_err(|e| err("gep", e.to_string()))?
};
b.build_store(value_slot, value).map_err(|e| err("store", e.to_string()))?;
emit_rt_call(&b, pop_root, &[], "frame_unroot", what)?;
b.build_return(Some(&i64t.const_int(typelisp_abi::STATUS_RETURN as u64, false)))
.map_err(|e| err("ret", e.to_string()))?;
if !f.size_store.set_operand(0, i64t.const_int(f.next, false)) {
return Err(EvalError::Internal(format!("{}: could not write the slot count into the prologue", what)));
}
b.position_at_end(coro.dispatch);
let mut arms = std::iter::once((0u64, coro.body)).chain(coro.resumes.iter().copied()).peekable();
while let Some((id, target)) = arms.next() {
if arms.peek().is_none() {
b.build_unconditional_branch(target).map_err(|e| err("br", e.to_string()))?;
break;
}
let next = ctx.append_basic_block(coro.function, &format!("coro.dispatch{}", id + 1));
let hit = b
.build_int_compare(inkwell::IntPredicate::EQ, coro.pc, i64t.const_int(id, false), "coro_is")
.map_err(|e| err("icmp", e.to_string()))?;
b.build_conditional_branch(hit, target, next).map_err(|e| err("br", e.to_string()))?;
b.position_at_end(next);
}
Ok(Value::Empty)
}
fn llvm_builder_build_icmp(args: &[Value], name: &str, predicate: inkwell::IntPredicate) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let a = expect_llvm_value(&args[1])?.into_int_value();
let b = expect_llvm_value(&args[2])?.into_int_value();
let bld = builder.borrow();
let cmp = bld.build_int_compare(predicate, a, b, name).map_err(|e| EvalError::Internal(format!("{}: {}", name, e)))?;
let ctx = crate::compile::llvm_context();
let widened = bld.build_int_z_extend(cmp, ctx.i64_type(), name).map_err(|e| EvalError::Internal(format!("{}: {}", name, e)))?;
Ok(llvm_value_value(widened.into()))
}
fn llvm_builder_build_cond_br(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let cond = expect_llvm_value(&args[1])?.into_int_value();
let then_block = expect_llvm_basic_block(&args[2])?;
let else_block = expect_llvm_basic_block(&args[3])?;
let b = builder.borrow();
let ctx = crate::compile::llvm_context();
let zero = ctx.i64_type().const_zero();
let is_nonzero =
b.build_int_compare(inkwell::IntPredicate::NE, cond, zero, "if_cond_nz").map_err(|e| EvalError::Internal(format!("build-cond-br: {}", e)))?;
b.build_conditional_branch(is_nonzero, then_block, else_block).map_err(|e| EvalError::Internal(format!("build-cond-br: {}", e)))?;
Ok(Value::Empty)
}
fn llvm_builder_build_br(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let target = expect_llvm_basic_block(&args[1])?;
builder.borrow().build_unconditional_branch(target).map_err(|e| EvalError::Internal(format!("build-br: {}", e)))?;
Ok(Value::Empty)
}
fn llvm_builder_block_terminated(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let terminated = builder.borrow().get_insert_block().and_then(|bb| bb.get_terminator()).is_some();
Ok(Value::Bool(terminated))
}
fn llvm_builder_build_call(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let target = expect_llvm_function(&args[1])?;
let args_ptr = expect_llvm_value(&args[2])?;
let argc = expect_int(&args[3])? as u64;
let ctx = crate::compile::llvm_context();
let argc_val = ctx.i32_type().const_int(argc, false);
let call = builder
.borrow()
.build_call(target, &[args_ptr.into(), argc_val.into()], "call_result")
.map_err(|e| EvalError::Internal(format!("build-call: {}", e)))?;
match call.try_as_basic_value() {
inkwell::values::ValueKind::Basic(v) => Ok(llvm_value_value(v)),
inkwell::values::ValueKind::Instruction(_) => Err(EvalError::Internal("build-call: callee produced no value".into())),
}
}
fn llvm_builder_build_call_with_env(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let target = expect_llvm_function(&args[1])?;
let args_ptr = expect_llvm_value(&args[2])?;
let argc = expect_int(&args[3])? as u64;
let env_ptr = expect_llvm_value(&args[4])?;
let env_len = expect_int(&args[5])? as u64;
let ctx = crate::compile::llvm_context();
let argc_val = ctx.i32_type().const_int(argc, false);
let env_len_val = ctx.i32_type().const_int(env_len, false);
let call = builder
.borrow()
.build_call(target, &[args_ptr.into(), argc_val.into(), env_ptr.into(), env_len_val.into()], "call_result")
.map_err(|e| EvalError::Internal(format!("build-call-with-env: {}", e)))?;
match call.try_as_basic_value() {
inkwell::values::ValueKind::Basic(v) => Ok(llvm_value_value(v)),
inkwell::values::ValueKind::Instruction(_) => {
Err(EvalError::Internal("build-call-with-env: callee produced no value".into()))
}
}
}
fn llvm_builder_build_make_closure(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let module = expect_llvm_module(&args[1])?;
let target = expect_llvm_function(&args[2])?;
let env_ptr = expect_llvm_value(&args[3])?.into_pointer_value();
let env_len = expect_int(&args[4])? as u64;
let mask_half = |i: usize| -> Result<u64, EvalError> {
Ok(expect_int(&args[i])? as u64 & 0xFFFF_FFFF)
};
let sexpr_mask = mask_half(5)? | (mask_half(6)? << 32);
let ctx = crate::compile::llvm_context();
let b = builder.borrow();
let module = module.borrow();
let ctor_name = if target.get_type() == coroutine_fn_type() {
"rt_coroutine_closure_new"
} else {
"rt_closure_new"
};
let rt_closure_new = module.get_function(ctor_name).ok_or_else(|| {
EvalError::Internal(format!("build-make-closure: {} not declared in this module", ctor_name))
})?;
let i64_ty = ctx.i64_type();
let argc = 2 + env_len;
let ctor_args_ptr = b
.build_alloca(i64_ty.array_type(argc as u32), "closure_ctor_args")
.map_err(|e| EvalError::Internal(format!("build-make-closure: {}", e)))?;
let store_slot = |idx: u64, v: BasicValueEnum<'static>| -> Result<(), EvalError> {
let idx_val = i64_ty.const_int(idx, false);
let p = unsafe {
b.build_gep(i64_ty, ctor_args_ptr, &[idx_val], "closure_ctor_arg_ptr")
.map_err(|e| EvalError::Internal(format!("build-make-closure: {}", e)))?
};
b.build_store(p, v).map_err(|e| EvalError::Internal(format!("build-make-closure: {}", e)))?;
Ok(())
};
let fn_ptr_int = b
.build_ptr_to_int(target.as_global_value().as_pointer_value(), i64_ty, "closure_fn_ptr")
.map_err(|e| EvalError::Internal(format!("build-make-closure: {}", e)))?;
store_slot(0, fn_ptr_int.into())?;
store_slot(1, i64_ty.const_int(sexpr_mask, false).into())?;
for i in 0..env_len {
let idx_val = i64_ty.const_int(i, false);
let src_ptr = unsafe {
b.build_gep(i64_ty, env_ptr, &[idx_val], "closure_env_src")
.map_err(|e| EvalError::Internal(format!("build-make-closure: {}", e)))?
};
let v = b
.build_load(i64_ty, src_ptr, "closure_env_val")
.map_err(|e| EvalError::Internal(format!("build-make-closure: {}", e)))?;
store_slot(2 + i, v)?;
}
let argc_val = ctx.i32_type().const_int(argc, false);
let call = b
.build_call(rt_closure_new, &[ctor_args_ptr.into(), argc_val.into()], "closure_new_result")
.map_err(|e| EvalError::Internal(format!("build-make-closure: {}", e)))?;
match call.try_as_basic_value() {
inkwell::values::ValueKind::Basic(v) => Ok(llvm_value_value(v)),
inkwell::values::ValueKind::Instruction(_) => Err(EvalError::Internal("build-make-closure: rt_closure_new produced no value".into())),
}
}
fn llvm_builder_build_malloc(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let count = expect_int(&args[1])? as u64;
let ctx = crate::compile::llvm_context();
let count_val = ctx.i64_type().const_int(count, false);
let ptr = builder
.borrow()
.build_array_malloc(ctx.i64_type(), count_val, "box")
.map_err(|e| EvalError::Internal(format!("build-malloc: {}", e)))?;
Ok(llvm_value_value(ptr.into()))
}
fn llvm_builder_build_free(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let ptr = expect_llvm_value(&args[1])?.into_pointer_value();
builder.borrow().build_free(ptr).map_err(|e| EvalError::Internal(format!("build-free: {}", e)))?;
Ok(Value::Empty)
}
fn llvm_builder_build_int_to_ptr(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let v = expect_llvm_value(&args[1])?.into_int_value();
let ctx = crate::compile::llvm_context();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let ptr = builder
.borrow()
.build_int_to_ptr(v, ptr_ty, "int_to_ptr")
.map_err(|e| EvalError::Internal(format!("build-int-to-ptr: {}", e)))?;
Ok(llvm_value_value(ptr.into()))
}
fn llvm_builder_build_ptr_to_int(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let ptr = expect_llvm_value(&args[1])?.into_pointer_value();
let ctx = crate::compile::llvm_context();
let v = builder
.borrow()
.build_ptr_to_int(ptr, ctx.i64_type(), "ptr_to_int")
.map_err(|e| EvalError::Internal(format!("build-ptr-to-int: {}", e)))?;
Ok(llvm_value_value(v.into()))
}
fn llvm_builder_build_fn_address(args: &[Value]) -> Result<Value, EvalError> {
let builder = expect_llvm_builder(&args[0])?;
let f = expect_llvm_function(&args[1])?;
let ctx = crate::compile::llvm_context();
let v = builder
.borrow()
.build_ptr_to_int(f.as_global_value().as_pointer_value(), ctx.i64_type(), "fn_address")
.map_err(|e| EvalError::Internal(format!("build-fn-address: {}", e)))?;
Ok(llvm_value_value(v.into()))
}
#[derive(Clone)]
pub(crate) enum NativeHandle {
Module(Rc<RefCell<Module<'static>>>),
Builder(Rc<RefCell<Builder<'static>>>),
Function(FunctionValue<'static>),
BasicBlock(BasicBlock<'static>),
Value(BasicValueEnum<'static>),
}
struct LlvmHandles(Vec<NativeHandle>);
impl Drop for LlvmHandles {
fn drop(&mut self) {
let _guard = crate::compile::COMPILE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
self.0.clear();
}
}
thread_local! {
static LLVM_HANDLES: RefCell<LlvmHandles> = const { RefCell::new(LlvmHandles(Vec::new())) };
}
pub(crate) fn llvm_handle_register(h: NativeHandle) -> i64 {
LLVM_HANDLES.with(|t| {
let mut t = t.borrow_mut();
t.0.push(h);
(t.0.len() - 1) as i64
})
}
pub(crate) fn llvm_handle_get(h: i64) -> Option<NativeHandle> {
if h < 0 {
return None;
}
LLVM_HANDLES.with(|t| t.borrow().0.get(h as usize).cloned())
}
fn handle_value(h: NativeHandle) -> Value {
Value::Int(llvm_handle_register(h))
}
fn llvm_module_value(m: Module<'static>) -> Value {
handle_value(NativeHandle::Module(Rc::new(RefCell::new(m))))
}
pub(crate) fn llvm_module_value_rc(m: Rc<RefCell<Module<'static>>>) -> Value {
handle_value(NativeHandle::Module(m))
}
fn llvm_builder_value(b: Builder<'static>) -> Value {
handle_value(NativeHandle::Builder(Rc::new(RefCell::new(b))))
}
fn llvm_function_value(f: FunctionValue<'static>) -> Value {
handle_value(NativeHandle::Function(f))
}
fn llvm_block_value(b: BasicBlock<'static>) -> Value {
handle_value(NativeHandle::BasicBlock(b))
}
fn llvm_value_value(v: BasicValueEnum<'static>) -> Value {
handle_value(NativeHandle::Value(v))
}
pub fn llvm_module_of(v: &Value) -> Option<Rc<RefCell<Module<'static>>>> {
match llvm_handle_get(match v {
Value::Int(h) => *h,
_ => return None,
})? {
NativeHandle::Module(m) => Some(m),
_ => None,
}
}
pub fn llvm_value_of(v: &Value) -> Option<BasicValueEnum<'static>> {
match llvm_handle_get(match v {
Value::Int(h) => *h,
_ => return None,
})? {
NativeHandle::Value(x) => Some(x),
_ => None,
}
}
fn handle_of(v: &Value) -> Option<i64> {
match v {
Value::Int(h) => Some(*h),
_ => None,
}
}
fn value_of_handle(raw: i64) -> Option<Value> {
llvm_handle_get(raw).map(|_| Value::Int(raw))
}
pub(crate) fn llvm_handles_mark() -> usize {
LLVM_HANDLES.with(|t| t.borrow().0.len())
}
pub(crate) fn llvm_handles_release(mark: usize) {
let released: Vec<NativeHandle> = LLVM_HANDLES.with(|t| t.borrow_mut().0.split_off(mark));
let _guard = crate::compile::COMPILE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
drop(released);
}
fn expect_handle(v: &Value, want: &str) -> Result<NativeHandle, EvalError> {
let h = match v {
Value::Int(n) => *n,
other => return Err(EvalError::Internal(format!("expected an {}, got {:?}", want, other))),
};
llvm_handle_get(h).ok_or_else(|| EvalError::Internal(format!("expected an {}, got dangling handle {}", want, h)))
}
#[derive(Clone, Copy, Debug)]
enum LlvmArgK {
Handle,
Scope,
Str,
Int,
Bool,
}
#[derive(Clone, Copy, Debug)]
enum LlvmRetK {
Handle,
Scope,
Unit,
Bool,
Str,
OptHandle,
}
struct LlvmOp {
type_key: &'static str,
method: String,
args: Vec<LlvmArgK>,
ret: LlvmRetK,
}
fn llvm_arg_kind(ty: &Type) -> LlvmArgK {
if crate::check::repr::is_llvm_handle_ty(ty) {
LlvmArgK::Handle
} else if ty.is_int_family() {
LlvmArgK::Int
} else {
match ty {
Type::Str => LlvmArgK::Str,
Type::Bool => LlvmArgK::Bool,
other => panic!("llvm_op_table: parameter type {:?} has no rt_llvm_call marshaling", other),
}
}
}
fn llvm_ret_kind(ty: &Type) -> LlvmRetK {
if crate::check::repr::is_llvm_handle_ty(ty) {
LlvmRetK::Handle
} else {
match ty {
Type::Unit => LlvmRetK::Unit,
Type::Bool => LlvmRetK::Bool,
Type::Str => LlvmRetK::Str,
other => panic!("llvm_op_table: return type {:?} has no rt_llvm_call marshaling", other),
}
}
}
fn llvm_op_table() -> &'static HashMap<i64, LlvmOp> {
static TABLE: std::sync::OnceLock<HashMap<i64, LlvmOp>> = std::sync::OnceLock::new();
TABLE.get_or_init(|| {
let mut t: HashMap<i64, LlvmOp> = HashMap::new();
let insert = |t: &mut HashMap<i64, LlvmOp>, type_key: &'static str, method: String, args: Vec<LlvmArgK>, ret: LlvmRetK| {
let id = crate::compile::symbols::llvm_op_id(type_key, &method);
if t.insert(id, LlvmOp { type_key, method, args, ret }).is_some() {
panic!("llvm_op_table: op id collision on {}", id);
}
};
for (type_key, def) in [
("llvm-module", crate::check::registry::llvm_module_def()),
("llvm-function", crate::check::registry::llvm_function_def()),
("llvm-builder", crate::check::registry::llvm_builder_def()),
] {
for (method, af) in def.assoc {
let args = af.sig.params.iter().map(llvm_arg_kind).collect();
let ret = llvm_ret_kind(&af.sig.ret);
insert(&mut t, type_key, method, args, ret);
}
}
use LlvmArgK::{Handle as H, Str as S};
const SC: LlvmArgK = LlvmArgK::Scope;
insert(&mut t, "native-scope", "new".to_string(), vec![], LlvmRetK::Scope);
insert(&mut t, "native-scope", "clone-frames".to_string(), vec![SC], LlvmRetK::Scope);
insert(&mut t, "native-scope", "push-frame".to_string(), vec![SC], LlvmRetK::Unit);
insert(&mut t, "native-scope", "pop-frame".to_string(), vec![SC], LlvmRetK::Unit);
insert(&mut t, "native-scope", "get".to_string(), vec![SC, S], LlvmRetK::OptHandle);
insert(&mut t, "native-scope", "set".to_string(), vec![SC, S, H], LlvmRetK::Unit);
t
})
}
fn rt_llvm_fatal(msg: &str) -> ! {
eprintln!("typelisp runtime error: {}", msg);
std::process::abort();
}
pub(crate) unsafe extern "C" fn rt_llvm_call(args: *const i64, argc: u32) -> i64 {
let argv = std::slice::from_raw_parts(args, argc as usize);
let Some((&opid, raw_args)) = argv.split_first() else {
rt_llvm_fatal("rt_llvm_call: missing op id");
};
let Some(op) = llvm_op_table().get(&opid) else {
rt_llvm_fatal(&format!("rt_llvm_call: unknown op id {}", opid));
};
if raw_args.len() != op.args.len() {
rt_llvm_fatal(&format!(
"rt_llvm_call: {}::{} expects {} arguments, got {}",
op.type_key,
op.method,
op.args.len(),
raw_args.len()
));
}
let heap = crate::compile::runtime::active_heap();
let mut vals: Vec<Value> = Vec::with_capacity(raw_args.len());
for (raw, k) in raw_args.iter().zip(&op.args) {
vals.push(match k {
LlvmArgK::Handle => match value_of_handle(*raw) {
Some(v) => v,
None => rt_llvm_fatal(&format!(
"rt_llvm_call: {}::{}: dangling llvm handle {} (registry holds {}, args so far {:?})",
op.type_key,
op.method,
raw,
llvm_handles_mark(),
raw_args
)),
},
LlvmArgK::Str => match crate::compile::runtime::decode(*raw) {
v @ Value::Str(_) => v,
other => rt_llvm_fatal(&format!("rt_llvm_call: {}::{}: expected a Str argument, got {:?}", op.type_key, op.method, other)),
},
LlvmArgK::Scope => match crate::compile::runtime::decode(*raw) {
v @ Value::Boxed(_) => v,
other => rt_llvm_fatal(&format!("rt_llvm_call: {}::{}: expected a scope box, got {:?}", op.type_key, op.method, other)),
},
LlvmArgK::Int => Value::Int(*raw),
LlvmArgK::Bool => Value::Bool(*raw != 0),
});
}
if op.type_key == "native-scope" && op.method == "get" {
let found = match (expect_struct_box(&vals[0]), expect_str(heap, &vals[1])) {
(Ok(id), Ok(name)) => heap.scope_get(id, &name),
(Err(e), _) | (_, Err(e)) => rt_llvm_fatal(&format!("rt_llvm_call: native-scope::get: {:?}", e)),
};
let key = typelisp_front::type_key::type_key_of_type(&crate::types::Type::Named(
crate::Path::root("option"),
vec![crate::types::Type::Named(crate::Path::root("llvm-value"), vec![])],
));
let option = heap.alloc_option(&key, found);
return crate::compile::runtime::encode(option);
}
let result: Result<Value, EvalError> = if op.type_key == "native-scope" {
match op.method.as_str() {
"new" => Ok(heap.alloc_scope()),
"clone-frames" => scope_clone_frames_heap(heap, &vals),
"push-frame" => scope_push_frame_heap(heap, &vals),
"pop-frame" => scope_pop_frame_heap(heap, &vals),
"set" => scope_set_raw(heap, &vals),
other => rt_llvm_fatal(&format!("rt_llvm_call: unknown native-scope method {}", other)),
}
} else {
match eval_llvm_builtin_method(heap, &Path::root(op.type_key), &op.method, &vals) {
Some(r) => r,
None => rt_llvm_fatal(&format!("rt_llvm_call: {} has no builtin method {}", op.type_key, op.method)),
}
};
let v = match result {
Ok(v) => v,
Err(e) => rt_llvm_fatal(&format!("rt_llvm_call: {}::{}: {:?}", op.type_key, op.method, e)),
};
match op.ret {
LlvmRetK::Handle => match handle_of(&v) {
Some(h) => h,
None => rt_llvm_fatal(&format!("rt_llvm_call: {}::{}: expected a handle result, got {:?}", op.type_key, op.method, v)),
},
LlvmRetK::Scope => {
heap.push_session_root(v);
crate::compile::runtime::encode(v)
}
LlvmRetK::Unit => 0,
LlvmRetK::Bool => match v {
Value::Bool(b) => i64::from(b),
other => rt_llvm_fatal(&format!("rt_llvm_call: {}::{}: expected a Bool result, got {:?}", op.type_key, op.method, other)),
},
LlvmRetK::Str => match v {
sv @ Value::Str(_) => crate::compile::runtime::encode(sv),
other => rt_llvm_fatal(&format!("rt_llvm_call: {}::{}: expected a Str result, got {:?}", op.type_key, op.method, other)),
},
LlvmRetK::OptHandle => rt_llvm_fatal("rt_llvm_call: OptHandle result outside native-scope::get"),
}
}