feat: migrate to cranelift (WIP)

This commit is contained in:
2025-06-27 22:40:53 +08:00
parent e26789f3b7
commit 5625f28e9b
13 changed files with 720 additions and 732 deletions

View File

@@ -1,4 +1,5 @@
use std::cell::OnceCell;
use core::mem::MaybeUninit;
use std::rc::Rc;
use hashbrown::HashSet;
@@ -7,7 +8,7 @@ use priority_queue::PriorityQueue;
use crate::env::Env;
use crate::error::Result;
use crate::eval::jit::{JITContext, JITFunc};
use crate::eval::jit::{JITContext, JITFunc, JITValue};
use crate::eval::Evaluate;
use crate::ir::{Dep, Downgraded, Ir, SccNode};
use crate::ty::internal as i;
@@ -19,12 +20,12 @@ mod test;
type ThunkIdx = usize;
type EnvIdx = usize;
pub struct Engine<'ctx: 'exec, 'exec> {
pub struct Engine<'exec> {
pub thunks: Box<[Ir]>,
pub func_offset: usize,
pub func_deps: Vec<HashSet<Dep>>,
jit: &'exec JITContext<'ctx>,
compiled: Box<[OnceCell<JITFunc<'ctx, 'exec>>]>,
jit: &'exec JITContext,
compiled: Box<[OnceCell<JITFunc<'exec>>]>,
tasks: PriorityQueue<CompileTask, usize>,
}
@@ -79,8 +80,12 @@ impl<'ctx, 'exec> Engine<'ctx, 'exec> {
}
pub fn eval_thunk(&mut self, idx: usize, env: &mut Env) -> Result<i::Value> {
let func = self.compiled[idx].get_or_init(|| self.jit.compile(&self.thunks[idx]));
Ok(unsafe { func(self as *const Engine, env as *const Env).into() })
let func = self.compiled[idx].get_or_init(|| self.jit.compile(&self.thunks[idx], idx));
let mut ret: MaybeUninit<JITValue> = MaybeUninit::uninit();
unsafe {
func(self as *const Engine, env as *const Env, core::mem::transmute::<*mut MaybeUninit<JITValue>, *mut JITValue>(&mut ret as *mut _));
Ok(ret.assume_init().into())
}
}
pub fn eval_func_deps(&mut self, idx: usize, env: &mut Env) -> Result<()> {

View File

@@ -1,4 +1,4 @@
use std::fmt::Debug;
use core::fmt::Debug;
use std::rc::Rc;
use ecow::EcoString;
@@ -12,7 +12,6 @@ pub struct Env {
cache: Vec<HashMap<usize, Value>>,
with: Vec<Rc<HashMap<EcoString, Value>>>,
args: Vec<Value>,
pub args_len: usize,
}
impl Env {
@@ -21,7 +20,6 @@ impl Env {
cache: Vec::from([HashMap::new()]),
with: Vec::new(),
args: Vec::new(),
args_len: 0,
}
}
@@ -89,7 +87,7 @@ impl Env {
}
pub fn enter_arg(&mut self, arg: Value) {
self.args.push(arg)
self.args.push(arg);
}
pub fn pop_args(&mut self, len: usize) -> Vec<Value> {

View File

@@ -10,8 +10,8 @@ pub enum Error {
DowngradeError(String),
#[error("error occurred during evaluation stage: {0}")]
EvalError(String),
#[error("error occurred during JIT compile stage: {0}")]
CompileError(#[from] inkwell::builder::BuilderError),
// #[error("error occurred during JIT compile stage: {0}")]
// CompileError(#[from] inkwell::builder::BuilderError),
#[error("unknown error")]
Unknown,
}

View File

@@ -1,70 +1,71 @@
use std::{alloc::Layout, ffi::CStr};
use inkwell::values::{FunctionValue, StructValue};
use cranelift::prelude::*;
use cranelift::codegen::ir;
use crate::eval::jit::JITValue;
use crate::ir::*;
use crate::ty::common as c;
use crate::ty::internal::Value;
use crate::{eval::jit::JITValue, ir::*};
use super::{JITContext, ValueTag};
pub trait JITCompile {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc>;
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value;
}
impl JITCompile for Attrs {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
todo!()
}
}
impl JITCompile for List {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
todo!()
}
}
impl JITCompile for HasAttr {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
todo!()
}
}
impl JITCompile for BinOp {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
use BinOpKind::*;
use ValueTag::*;
let lhs = self.lhs.compile(ctx, func, values);
let rhs = self.rhs.compile(ctx, func, values);
let lhs = self.lhs.compile(ctx, builder, block);
let rhs = self.rhs.compile(ctx, builder, block);
let lhs_tag = ctx.get_tag(lhs);
let rhs_tag = ctx.get_tag(rhs);
let tag = ctx
.builder
.func_builder
.build_int_add(
lhs_tag.const_shl(ctx.helpers.const_int(8)),
rhs_tag,
@@ -73,7 +74,7 @@ impl JITCompile for BinOp {
.unwrap();
let ret = ctx.context.append_basic_block(func, "fallback");
let res = ctx
.builder
.func_builder
.build_alloca(ctx.helpers.value_type, "res_alloca")
.unwrap();
match self.kind {
@@ -83,7 +84,7 @@ impl JITCompile for BinOp {
let float_int = ctx.context.append_basic_block(func, "float_int");
let float_float = ctx.context.append_basic_block(func, "float_float");
let fallback = ctx.context.append_basic_block(func, "fallback");
ctx.builder
ctx.func_builder
.build_switch(
tag,
fallback,
@@ -107,19 +108,19 @@ impl JITCompile for BinOp {
],
)
.unwrap();
ctx.builder.position_at_end(int_int);
ctx.func_builder.position_at_end(int_int);
let val = ctx
.builder
.func_builder
.build_int_add(ctx.get_int(lhs), ctx.get_int(rhs), "add")
.unwrap();
ctx.builder
ctx.func_builder
.build_store(res, ctx.helpers.new_value(Int, val.into()))
.unwrap();
ctx.builder.position_at_end(int_float);
ctx.func_builder.position_at_end(int_float);
let val = ctx
.builder
.func_builder
.build_float_add(
ctx.builder
ctx.func_builder
.build_signed_int_to_float(
ctx.get_int(lhs),
ctx.helpers.float_type,
@@ -130,15 +131,15 @@ impl JITCompile for BinOp {
"add",
)
.unwrap();
ctx.builder
ctx.func_builder
.build_store(res, ctx.helpers.new_value(Float, val.into()))
.unwrap();
ctx.builder.position_at_end(float_int);
ctx.func_builder.position_at_end(float_int);
let val = ctx
.builder
.func_builder
.build_float_add(
ctx.get_float(lhs),
ctx.builder
ctx.func_builder
.build_signed_int_to_float(
ctx.get_int(rhs),
ctx.helpers.float_type,
@@ -148,23 +149,23 @@ impl JITCompile for BinOp {
"add",
)
.unwrap();
ctx.builder
ctx.func_builder
.build_store(res, ctx.helpers.new_value(Float, val.into()))
.unwrap();
ctx.builder.position_at_end(int_int);
ctx.func_builder.position_at_end(int_int);
let val = ctx
.builder
.func_builder
.build_float_add(ctx.get_float(lhs), ctx.get_float(rhs), "add")
.unwrap();
ctx.builder
ctx.func_builder
.build_store(res, ctx.helpers.new_value(Float, val.into()))
.unwrap();
ctx.builder.position_at_end(fallback);
ctx.func_builder.position_at_end(fallback);
}
Or => {
let bool_bool = ctx.context.append_basic_block(func, "int_int");
let fallback = ctx.context.append_basic_block(func, "fallback");
ctx.builder
ctx.func_builder
.build_switch(
tag,
fallback,
@@ -174,20 +175,20 @@ impl JITCompile for BinOp {
)],
)
.unwrap();
ctx.builder.position_at_end(bool_bool);
ctx.func_builder.position_at_end(bool_bool);
let val = ctx
.builder
.func_builder
.build_or(ctx.get_bool(lhs), ctx.get_bool(rhs), "or")
.unwrap();
ctx.builder
ctx.func_builder
.build_store(res, ctx.helpers.new_value(Bool, val.into()))
.unwrap();
ctx.builder.position_at_end(fallback);
ctx.func_builder.position_at_end(fallback);
}
_ => todo!(),
}
ctx.builder.position_at_end(ret);
ctx.builder
ctx.func_builder.position_at_end(ret);
ctx.func_builder
.build_load(ctx.helpers.value_type, res, "load_res")
.unwrap()
.try_into()
@@ -196,25 +197,25 @@ impl JITCompile for BinOp {
}
impl JITCompile for UnOp {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
todo!();
let rhs = self.rhs.compile(ctx, func, values);
let rhs = self.rhs.compile(ctx, builder, block);
let tag = ctx.get_tag(rhs);
let fallback = ctx.context.append_basic_block(func, "fallback");
let ret = ctx.context.append_basic_block(func, "fallback");
let res = ctx
.builder
.func_builder
.build_alloca(ctx.helpers.value_type, "res_alloca")
.unwrap();
ctx.builder.build_switch(tag, fallback, &[]).unwrap();
ctx.builder.position_at_end(fallback);
ctx.builder.position_at_end(ret);
ctx.builder
ctx.func_builder.build_switch(tag, fallback, &[]).unwrap();
ctx.func_builder.position_at_end(fallback);
ctx.func_builder.position_at_end(ret);
ctx.func_builder
.build_load(ctx.helpers.value_type, res, "load_res")
.unwrap()
.try_into()
@@ -223,34 +224,34 @@ impl JITCompile for UnOp {
}
impl JITCompile for Select {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
todo!()
}
}
impl JITCompile for If {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
todo!()
}
}
impl JITCompile for LoadFunc {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
_: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
ctx.helpers.new_value(
ValueTag::Function,
ctx.helpers.const_int(self.idx as i64).into(),
@@ -259,14 +260,18 @@ impl JITCompile for LoadFunc {
}
impl JITCompile for Call {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
let ret = ctx
.func_builder
.build_alloca(ctx.helpers.value_type, "ret")
.unwrap();
let args = ctx
.builder
.func_builder
.build_call(
ctx.helpers.alloc_array,
&[ctx.helpers.const_ptr_int(self.args.len()).into()],
@@ -277,89 +282,93 @@ impl JITCompile for Call {
.unwrap_left()
.into_pointer_value();
for (i, arg) in self.args.iter().enumerate() {
ctx.builder
.build_store(
unsafe {
args.const_in_bounds_gep(
ctx.helpers.value_type,
&[ctx.helpers.const_ptr_int(i)],
)
},
arg.compile(ctx, func, values),
)
let arg_ptr = unsafe {
ctx.func_builder
.build_gep(
ctx.helpers.value_type,
args,
&[ctx.helpers.const_ptr_int(i)],
"args_gep",
)
.unwrap()
};
ctx.func_builder
.build_store(arg_ptr, arg.compile(ctx, builder, block))
.unwrap();
}
ctx.builder
ctx.func_builder
.build_call(
ctx.helpers.call,
&[
self.func.compile(ctx, func, values).into(),
self.func.compile(ctx, builder, block).into(),
args.into(),
ctx.helpers.const_ptr_int(self.args.len()).into(),
func.get_first_param().unwrap().into(),
func.get_last_param().unwrap().into(),
func.get_nth_param(0).unwrap().into(),
func.get_nth_param(1).unwrap().into(),
ret.into(),
],
"call",
)
.unwrap();
ctx.func_builder
.build_load(ctx.helpers.value_type, ret, "load_ret")
.unwrap()
.try_as_basic_value()
.unwrap_left()
.try_into()
.unwrap()
.into_struct_value()
}
}
impl JITCompile for Let {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
unreachable!()
}
}
impl JITCompile for With {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
todo!()
todo!()
}
}
impl JITCompile for Assert {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
todo!()
}
}
impl JITCompile for ConcatStrings {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
todo!()
}
}
impl JITCompile for Const {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
use c::Const::*;
match self.val {
Bool(x) => ctx.helpers.new_bool(x),
@@ -371,28 +380,27 @@ impl JITCompile for Const {
}
impl JITCompile for String {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
todo!()
}
}
impl JITCompile for Var {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
let env = func.get_nth_param(1).unwrap();
let ptr = self.sym.as_ptr();
let len = self.sym.len();
// values.push(Value::String(self.sym.clone()));
ctx.builder
ctx.func_builder
.build_direct_call(
ctx.helpers.lookup,
&[
@@ -411,43 +419,54 @@ impl JITCompile for Var {
}
impl JITCompile for Arg {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
let env = func.get_last_param().unwrap();
let ret = ctx.builder.build_alloca(ctx.helpers.value_type, "alloca_ret").unwrap();
ctx.builder
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
let env = builder.block_params(block)[];
let env = func.get_nth_param(1).unwrap();
let arg = ctx
.func_builder
.build_alloca(ctx.helpers.value_type, "alloca_arg")
.unwrap();
ctx.func_builder
.build_direct_call(
ctx.helpers.lookup_arg,
&[env.into(), ctx.helpers.const_ptr_int(self.level).into(), ret.into()],
&[
env.into(),
ctx.helpers.const_ptr_int(self.level).into(),
arg.into(),
],
"lookup_arg",
)
.unwrap();
ctx.builder.build_load(ctx.helpers.value_type,ret, "load_ret").unwrap().into_struct_value()
ctx.func_builder
.build_load(ctx.helpers.value_type, arg, "load_arg")
.unwrap()
.into_struct_value()
}
}
impl JITCompile for LetVar {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
unreachable!()
}
}
impl JITCompile for Thunk {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
_: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
ctx.helpers.new_value(
ValueTag::Thunk,
ctx.helpers.const_int(self.idx as i64).into(),
@@ -456,12 +475,12 @@ impl JITCompile for Thunk {
}
impl JITCompile for Path {
fn compile<'gc>(
fn compile(
&self,
ctx: &JITContext<'gc>,
func: FunctionValue<'gc>,
values: &mut Vec<Value>,
) -> StructValue<'gc> {
ctx: &mut JITContext,
builder: &mut FunctionBuilder,
block: Block,
) -> ir::Value {
todo!()
}
}

View File

@@ -1,14 +1,14 @@
use std::alloc::Layout;
use std::ffi::CStr;
use std::ptr::NonNull;
use std::{slice, str};
use std::alloc::alloc;
use core::ptr::NonNull;
use core::{slice, str};
use inkwell::AddressSpace;
use inkwell::context::Context;
use inkwell::execution_engine::ExecutionEngine;
use inkwell::module::Module;
use inkwell::types::{FloatType, FunctionType, IntType, PointerType, StructType};
use inkwell::values::{BasicValueEnum, FloatValue, FunctionValue, IntValue, StructValue};
use cranelift::codegen::ir::ArgumentExtension;
use cranelift::codegen::ir::ArgumentPurpose;
use cranelift::prelude::*;
use cranelift_module::FuncId;
use cranelift_module::Linkage;
use cranelift_module::Module;
use crate::env::Env;
use crate::eval::Engine;
@@ -16,153 +16,71 @@ use crate::ty::internal::Value;
use super::{JITContext, JITValue, JITValueData, ValueTag};
pub struct Helpers<'ctx> {
pub int_type: IntType<'ctx>,
pub float_type: FloatType<'ctx>,
pub bool_type: IntType<'ctx>,
pub ptr_int_type: IntType<'ctx>,
pub ptr_type: PointerType<'ctx>,
pub value_type: StructType<'ctx>,
pub func_type: FunctionType<'ctx>,
pub struct Helpers {
pub int_type: Type,
pub float_type: Type,
pub bool_type: Type,
pub ptr_int_type: Type,
pub ptr_type: Type,
pub value_type: Type,
pub func_sig: Signature,
pub new_thunk: FunctionValue<'ctx>,
pub debug: FunctionValue<'ctx>,
pub capture_env: FunctionValue<'ctx>,
pub neg: FunctionValue<'ctx>,
pub not: FunctionValue<'ctx>,
pub add: FunctionValue<'ctx>,
pub sub: FunctionValue<'ctx>,
pub eq: FunctionValue<'ctx>,
pub or: FunctionValue<'ctx>,
pub call: FunctionValue<'ctx>,
pub lookup_arg: FunctionValue<'ctx>,
pub lookup: FunctionValue<'ctx>,
pub force: FunctionValue<'ctx>,
pub call: FuncId,
pub lookup_arg: FuncId,
pub lookup: FuncId,
pub force: FuncId,
pub alloc_array: FunctionValue<'ctx>,
pub alloc_array: FuncId,
}
impl<'ctx> Helpers<'ctx> {
impl Helpers {
pub fn new(
ctx: &'ctx Context,
module: &Module<'ctx>,
execution_engine: &ExecutionEngine<'ctx>,
ctx: &codegen::Context,
module: &mut dyn Module,
) -> Self {
let int_type = ctx.i64_type();
let float_type = ctx.f64_type();
let bool_type = ctx.bool_type();
let ptr_int_type = ctx.ptr_sized_int_type(execution_engine.get_target_data(), None);
let ptr_type = ctx.ptr_type(AddressSpace::default());
let value_type =
ctx.struct_type(&[int_type.into(), int_type.into(), int_type.into()], false);
let func_type = value_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
let int_type = types::I64;
let float_type = types::F64;
let bool_type = types::I8;
// let ptr_type = ctx.ptr_type(AddressSpace::default());
let ptr_type = module.target_config().pointer_type();
let ptr_int_type = ptr_type;
let value_type = types::I128;
// let func_sig = ctx.void_type().fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
let mut func_sig = Signature::new(isa::CallConv::SystemV);
func_sig.params.push(AbiParam { value_type: ptr_type, purpose: ArgumentPurpose::Normal, extension: ArgumentExtension::None });
func_sig.params.push(AbiParam { value_type: ptr_type, purpose: ArgumentPurpose::Normal, extension: ArgumentExtension::None });
func_sig.returns.push(AbiParam { value_type, purpose: ArgumentPurpose::StructReturn, extension: ArgumentExtension::None });
let new_thunk = module.add_function(
"new_thunk",
value_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
None,
);
let capture_env = module.add_function(
"capture_env",
ctx
.void_type()
.fn_type(&[value_type.into(), ptr_type.into()], false),
None,
);
let neg = module.add_function(
"neg",
value_type.fn_type(&[value_type.into(), ptr_type.into()], false),
None,
);
let not = module.add_function(
"not",
value_type.fn_type(&[value_type.into(), ptr_type.into()], false),
None,
);
let add = module.add_function(
"add",
value_type.fn_type(&[value_type.into(), value_type.into()], false),
None,
);
let sub = module.add_function(
"sub",
value_type.fn_type(&[value_type.into(), value_type.into()], false),
None,
);
let eq = module.add_function(
"eq",
value_type.fn_type(&[value_type.into(), value_type.into()], false),
None,
);
let or = module.add_function(
"or",
value_type.fn_type(&[value_type.into(), value_type.into()], false),
None,
);
let call = module.add_function(
"call",
value_type.fn_type(
&[
value_type.into(),
ptr_type.into(),
ptr_int_type.into(),
ptr_type.into(),
ptr_type.into(),
],
false,
),
None,
);
let debug = module.add_function(
"debug",
ctx
.void_type()
.fn_type(&[ptr_type.into(), int_type.into()], false),
None,
);
let lookup_arg = module.add_function(
"lookup_arg",
// value_type.fn_type(&[ptr_type.into(), int_type.into()], false),
ctx.void_type().fn_type(&[ptr_type.into(), int_type.into(), ptr_type.into()], false),
None,
);
let lookup = module.add_function(
"lookup",
value_type.fn_type(
&[ptr_int_type.into(), ptr_type.into(), ptr_int_type.into()],
false,
),
None,
);
let force = module.add_function(
"force",
value_type.fn_type(
&[value_type.into(), ptr_type.into(), ptr_type.into()],
false,
),
None,
);
let alloc_array = module.add_function(
"alloc_array",
value_type.fn_type(&[ptr_int_type.into()], false),
None,
);
let mut call_sig = Signature::new(isa::CallConv::SystemV);
call_sig.params.push(AbiParam { value_type, purpose: ArgumentPurpose::StructArgument(24), extension: ArgumentExtension::None });
call_sig.params.push(AbiParam { value_type: ptr_type, purpose: ArgumentPurpose::Normal, extension: ArgumentExtension::None });
call_sig.params.push(AbiParam { value_type: ptr_int_type, purpose: ArgumentPurpose::Normal, extension: ArgumentExtension::None });
call_sig.params.push(AbiParam { value_type: ptr_type, purpose: ArgumentPurpose::Normal, extension: ArgumentExtension::None });
call_sig.params.push(AbiParam { value_type: ptr_type, purpose: ArgumentPurpose::Normal, extension: ArgumentExtension::None });
let call = module.declare_function("helper_call", Linkage::Import, &call_sig).unwrap();
execution_engine.add_global_mapping(&new_thunk, helper_new_thunk as _);
execution_engine.add_global_mapping(&debug, helper_debug as _);
execution_engine.add_global_mapping(&capture_env, helper_capture_env as _);
execution_engine.add_global_mapping(&neg, helper_neg as _);
execution_engine.add_global_mapping(&not, helper_not as _);
execution_engine.add_global_mapping(&add, helper_add as _);
execution_engine.add_global_mapping(&sub, helper_sub as _);
execution_engine.add_global_mapping(&eq, helper_eq as _);
execution_engine.add_global_mapping(&or, helper_or as _);
execution_engine.add_global_mapping(&call, helper_call as _);
execution_engine.add_global_mapping(&lookup_arg, helper_lookup_arg as _);
execution_engine.add_global_mapping(&lookup, helper_lookup as _);
execution_engine.add_global_mapping(&force, helper_force as _);
execution_engine.add_global_mapping(&alloc_array, helper_alloc_array as _);
let mut lookup_arg_sig = Signature::new(isa::CallConv::SystemV);
lookup_arg_sig.params.push(AbiParam { value_type: ptr_type, purpose: ArgumentPurpose::Normal, extension: ArgumentExtension::None });
lookup_arg_sig.params.push(AbiParam { value_type: ptr_type, purpose: ArgumentPurpose::Normal, extension: ArgumentExtension::None });
let lookup_arg = module.declare_function("helper_lookup_arg", Linkage::Import, &call_sig).unwrap();
let mut lookup_sig = Signature::new(isa::CallConv::SystemV);
lookup_sig.params.push(AbiParam { value_type: ptr_type, purpose: ArgumentPurpose::Normal, extension: ArgumentExtension::None });
lookup_sig.params.push(AbiParam { value_type: ptr_type, purpose: ArgumentPurpose::Normal, extension: ArgumentExtension::None });
lookup_sig.params.push(AbiParam { value_type: ptr_type, purpose: ArgumentPurpose::Normal, extension: ArgumentExtension::None });
lookup_sig.params.push(AbiParam { value_type: ptr_int_type, purpose: ArgumentPurpose::Normal, extension: ArgumentExtension::None });
let lookup = module.declare_function("helper_lookup", Linkage::Import, &call_sig).unwrap();
let mut force_sig = Signature::new(isa::CallConv::SystemV);
force_sig.params.push(AbiParam { value_type, purpose: ArgumentPurpose::StructArgument(24), extension: ArgumentExtension::None });
force_sig.params.push(AbiParam { value_type: ptr_type, purpose: ArgumentPurpose::Normal, extension: ArgumentExtension::None });
force_sig.params.push(AbiParam { value_type: ptr_type, purpose: ArgumentPurpose::Normal, extension: ArgumentExtension::None });
let force = module.declare_function("helper_force", Linkage::Import, &call_sig).unwrap();
let mut alloc_array_sig = Signature::new(isa::CallConv::SystemV);
alloc_array_sig.params.push(AbiParam { value_type: ptr_int_type, purpose: ArgumentPurpose::Normal, extension: ArgumentExtension::None });
let alloc_array = module.declare_function("helper_alloc_array", Linkage::Import, &call_sig).unwrap();
Helpers {
int_type,
@@ -171,18 +89,8 @@ impl<'ctx> Helpers<'ctx> {
ptr_int_type,
ptr_type,
value_type,
func_type,
func_sig,
new_thunk,
debug,
capture_env,
neg,
not,
add,
sub,
eq,
or,
call,
lookup_arg,
lookup,
@@ -192,7 +100,7 @@ impl<'ctx> Helpers<'ctx> {
}
}
pub fn new_value(&self, tag: ValueTag, data: BasicValueEnum<'ctx>) -> StructValue<'ctx> {
pub fn new_value(&self, tag: ValueTag, data: BasicValueEnum) -> StructValue {
self.value_type.const_named_struct(&[
self.const_int(tag as i64).into(),
data,
@@ -200,143 +108,39 @@ impl<'ctx> Helpers<'ctx> {
])
}
pub fn const_ptr_int(&self, int: usize) -> IntValue<'ctx> {
pub fn const_ptr_int(&self, int: usize) -> IntValue {
self.ptr_int_type.const_int(int as _, false)
}
pub fn const_int(&self, int: i64) -> IntValue<'ctx> {
pub fn const_int(&self, int: i64) -> IntValue {
self.int_type.const_int(int as _, false)
}
pub fn new_int(&self, int: i64) -> StructValue<'ctx> {
pub fn new_int(&self, int: i64) -> StructValue {
self.new_value(ValueTag::Int, self.const_int(int).into())
}
pub fn const_float(&self, float: f64) -> FloatValue<'ctx> {
pub fn const_float(&self, float: f64) -> FloatValue {
self.float_type.const_float(float)
}
pub fn new_float(&self, float: f64) -> StructValue<'ctx> {
pub fn new_float(&self, float: f64) -> StructValue {
self.new_value(ValueTag::Float, self.const_float(float).into())
}
pub fn const_bool(&self, bool: bool) -> IntValue<'ctx> {
pub fn const_bool(&self, bool: bool) -> IntValue {
self.bool_type.const_int(bool as _, false)
}
pub fn new_bool(&self, bool: bool) -> StructValue<'ctx> {
pub fn new_bool(&self, bool: bool) -> StructValue {
self.new_value(ValueTag::Bool, self.const_bool(bool).into())
}
pub fn new_null(&self) -> StructValue<'ctx> {
pub fn new_null(&self) -> StructValue {
self.new_value(ValueTag::Null, self.int_type.const_zero().into())
}
}
extern "C" fn helper_capture_env(thunk: JITValue, env: *const Env) {
todo!()
}
extern "C" fn helper_neg(rhs: JITValue, _env: *const Env) -> JITValue {
use ValueTag::*;
match rhs.tag {
Int => JITValue {
tag: Int,
data: JITValueData {
int: -unsafe { rhs.data.int },
},
},
Float => JITValue {
tag: Float,
data: JITValueData {
float: -unsafe { rhs.data.float },
},
},
_ => todo!(),
}
}
extern "C" fn helper_not(rhs: JITValue, _env: *const Env) -> JITValue {
use ValueTag::*;
match rhs.tag {
Bool => JITValue {
tag: Bool,
data: JITValueData {
bool: !unsafe { rhs.data.bool },
},
},
_ => todo!(),
}
}
extern "C" fn helper_add(lhs: JITValue, rhs: JITValue) -> JITValue {
use ValueTag::*;
match (lhs.tag, rhs.tag) {
(Int, Int) => JITValue {
tag: Int,
data: JITValueData {
int: unsafe { lhs.data.int + rhs.data.int },
},
},
_ => todo!(
"Addition not implemented for {:?} and {:?}",
lhs.tag,
rhs.tag
),
}
}
extern "C" fn helper_sub(lhs: JITValue, rhs: JITValue) -> JITValue {
use ValueTag::*;
match (lhs.tag, rhs.tag) {
(Int, Int) => JITValue {
tag: Int,
data: JITValueData {
int: unsafe { lhs.data.int - rhs.data.int },
},
},
_ => todo!(
"Substruction not implemented for {:?} and {:?}",
lhs.tag,
rhs.tag
),
}
}
extern "C" fn helper_eq(lhs: JITValue, rhs: JITValue) -> JITValue {
use ValueTag::*;
match (lhs.tag, rhs.tag) {
(Int, Int) => JITValue {
tag: Bool,
data: JITValueData {
bool: unsafe { lhs.data.int == rhs.data.int },
},
},
_ => todo!(
"Equation not implemented for {:?} and {:?}",
lhs.tag,
rhs.tag
),
}
}
extern "C" fn helper_or(lhs: JITValue, rhs: JITValue) -> JITValue {
use ValueTag::*;
match (lhs.tag, rhs.tag) {
(Bool, Bool) => JITValue {
tag: Bool,
data: JITValueData {
bool: unsafe { lhs.data.bool || rhs.data.bool },
},
},
_ => todo!(
"Substraction not implemented for {:?} and {:?}",
lhs.tag,
rhs.tag
),
}
}
extern "C" fn helper_call(
func: JITValue,
args: *mut JITValue,
@@ -354,19 +158,12 @@ extern "C" fn helper_call(
unsafe { env.as_mut() },
)
.unwrap();
todo!()
func.into()
}
extern "C" fn helper_debug(env: NonNull<Env>, level: u64) {
dbg!(env, level);
dbg!(unsafe { env.as_ref() }.lookup_arg(level as usize));
}
extern "C" fn helper_lookup_arg(env_ptr: NonNull<Env>, level: u64, ret: NonNull<JITValue>) {
dbg!(env_ptr, level);
let env_ref = unsafe { env_ptr.as_ref() };
let val: JITValue = env_ref.lookup_arg(level as usize).into();
unsafe { ret.write(val) }
extern "C" fn helper_lookup_arg(env: NonNull<Env>, level: u64) -> JITValue {
let env_ref = unsafe { env.as_ref() };
env_ref.lookup_arg(level as usize).into()
}
extern "C" fn helper_lookup(env: NonNull<Env>, ptr: *const u8, len: usize) -> JITValue {
@@ -391,10 +188,6 @@ extern "C" fn helper_force(
todo!()
}
extern "C" fn helper_new_thunk(opcodes: *const ()) -> JITValue {
todo!()
}
unsafe extern "C" fn helper_alloc_array(len: usize) -> *mut u8 {
unsafe { std::alloc::alloc(Layout::array::<JITValue>(len).unwrap()) }
unsafe { alloc(Layout::array::<JITValue>(len).unwrap()) }
}

View File

@@ -2,14 +2,11 @@ use std::marker::PhantomData;
use std::ops::Deref;
use std::rc::Rc;
use inkwell::OptimizationLevel;
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::execution_engine::ExecutionEngine;
use inkwell::module::Module;
use inkwell::values::{
AnyValue, BasicMetadataValueEnum, FloatValue, IntValue, PointerValue, StructValue,
};
use cranelift::codegen::ir::Function;
use cranelift::prelude::*;
use cranelift::codegen::ir;
use cranelift_module::{DataDescription, Linkage, Module};
use cranelift_jit::{JITModule, JITBuilder};
use crate::engine::Engine;
use crate::env::Env;
@@ -22,19 +19,30 @@ mod helpers;
pub use compile::JITCompile;
use helpers::Helpers;
#[repr(u64)]
#[derive(Debug, Clone, Copy)]
pub enum ValueTag {
Null,
Int,
Float,
String,
Bool,
AttrSet,
List,
Function,
Thunk,
Path,
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ValueTag(u64);
#[allow(non_upper_case_globals)]
#[allow(non_snake_case)]
impl ValueTag {
const Null: Self = Self(0);
const Int: Self = Self(1);
const Float: Self = Self(2);
const Path: Self = Self(3);
const Bool: Self = Self(4);
const AttrSet: Self = Self(5);
const List: Self = Self(6);
const Function: Self = Self(7);
const Thunk: Self = Self(8);
pub fn String(len: usize) -> Self {
Self(len as u64 ^ (1 << 31))
}
pub fn is_str(&self) -> bool {
self.0 >> 31 != 0
}
}
#[repr(C)]
@@ -51,24 +59,15 @@ pub union JITValueData {
float: f64,
bool: bool,
ptr: *const (),
slice: Slice,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Slice {
ptr: *const (),
len: usize,
}
impl From<JITValue> for Value {
fn from(value: JITValue) -> Self {
use ValueTag::*;
match value.tag {
Int => Value::Int(unsafe { value.data.int }),
Null => Value::Null,
Function => Value::Func(unsafe { value.data.int as usize }),
Thunk => Value::Thunk(unsafe { value.data.int as usize }),
ValueTag::Int => Value::Int(unsafe { value.data.int }),
ValueTag::Null => Value::Null,
ValueTag::Function => Value::Func(unsafe { value.data.int as usize }),
ValueTag::Thunk => Value::Thunk(unsafe { value.data.int as usize }),
_ => todo!("not implemented for {:?}", value.tag),
}
}
@@ -84,10 +83,7 @@ impl From<Value> for JITValue {
Value::List(list) => JITValue {
tag: ValueTag::List,
data: JITValueData {
slice: Slice {
ptr: list.as_ptr() as *const (),
len: list.len(),
},
ptr: list.as_ptr() as *const (),
},
},
Value::Func(idx) => JITValue {
@@ -103,69 +99,82 @@ impl From<Value> for JITValue {
}
}
pub struct JITFunc<'ctx, 'exec>(F<'ctx, 'exec>, PhantomData<&'exec mut ()>);
type F<'ctx, 'exec> = unsafe extern "C" fn(*const Engine<'ctx, 'exec>, *const Env) -> JITValue;
pub struct JITFunc<'exec>(F<'exec>, PhantomData<&'exec mut ()>);
type F<'exec> = unsafe extern "C" fn(*const Engine<'exec>, *const Env, *mut JITValue);
impl<'ctx, 'exec> From<F<'ctx, 'exec>> for JITFunc<'ctx, 'exec> {
fn from(value: F<'ctx, 'exec>) -> Self {
impl<'exec> From<F<'exec>> for JITFunc<'exec> {
fn from(value: F<'exec>) -> Self {
Self(value, PhantomData)
}
}
impl<'ctx: 'exec, 'exec> Deref for JITFunc<'ctx, 'exec> {
type Target = F<'ctx, 'exec>;
impl<'exec> Deref for JITFunc<'exec> {
type Target = F<'exec>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct JITContext<'ctx> {
context: &'ctx Context,
module: Module<'ctx>,
builder: Builder<'ctx>,
execution_engine: ExecutionEngine<'ctx>,
pub struct JITContext {
func_builder: FunctionBuilderContext,
helpers: Helpers<'ctx>,
ctx: codegen::Context,
data_description: DataDescription,
module: JITModule,
func: Function,
helpers: Helpers,
}
impl<'ctx> JITContext<'ctx> {
pub fn new(context: &'ctx Context) -> Self {
// force linker to link JIT engine
unsafe {
inkwell::llvm_sys::execution_engine::LLVMLinkInMCJIT();
}
let module = context.create_module("nixjit");
let execution_engine = module
.create_jit_execution_engine(OptimizationLevel::Aggressive)
impl JITContext {
pub fn new() -> Self {
let mut flag_builder = settings::builder();
flag_builder.set("use_colocated_libcalls", "false").unwrap();
flag_builder.set("is_pic", "false").unwrap();
let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| {
panic!("host machine is not supported: {}", msg);
});
let isa = isa_builder
.finish(settings::Flags::new(flag_builder))
.unwrap();
let helpers = Helpers::new(context, &module, &execution_engine);
let builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
JITContext {
execution_engine,
builder: context.create_builder(),
context,
let mut module = JITModule::new(builder);
let ctx = module.make_context();
Self {
func_builder: FunctionBuilderContext::new(),
helpers: Helpers::new(&ctx, &mut module),
data_description: DataDescription::new(),
func: Function::new(),
ctx,
module,
helpers,
}
}
pub fn compile<'exec>(&'exec self, ir: &Ir) -> JITFunc<'ctx, 'exec> {
let func = self
.module
.add_function("nixjit_func", self.helpers.func_type, None);
let entry = self.context.append_basic_block(func, "entry");
self.builder.position_at_end(entry);
pub fn compile<'exec>(&'exec mut self, ir: &Ir, id: usize) -> JITFunc<'exec> {
let func_id = self.module.declare_function(format!("nixjit_thunk{id}").as_str(), Linkage::Local, &self.helpers.func_sig).unwrap();
let mut func = Function::new();
let builder = FunctionBuilder::new(&mut func, &mut self.func_builder);
let entry = builder.create_block();
builder.switch_to_block(entry);
// TODO:
let ret = ir.compile(self, func, &mut Vec::new());
self.builder.build_return(Some(&ret)).unwrap();
if func.verify(true) {
let ret = ir.compile(self, func_id, &mut Vec::new());
self.func_builder
.build_store(func_id.get_nth_param(2).unwrap().into_pointer_value(), ret)
.unwrap();
self.func_builder.build_return(None).unwrap();
self.module.print_to_stderr();
let _ = self.execution_engine.remove_module(&self.module);
let _ = self.execution_engine.add_module(&self.module);
if func_id.verify(true) {
unsafe {
JITFunc(
self.execution_engine
.get_function(func.get_name().to_str().unwrap())
.unwrap()
.into_raw(),
std::mem::transmute(
self.execution_engine
.get_function_address(func_id.get_name().to_str().unwrap())
.unwrap(),
),
PhantomData,
)
}
@@ -174,80 +183,18 @@ impl<'ctx> JITContext<'ctx> {
}
}
pub fn get_float(&self, val: StructValue<'ctx>) -> FloatValue<'ctx> {
let alloca = self
.builder
.build_alloca(self.helpers.int_type, "get_value_alloca")
.unwrap();
self.builder.build_store(alloca, val).unwrap();
self.builder
.build_load(
self.helpers.float_type,
self.builder
.build_struct_gep(self.helpers.value_type, alloca, 1, "get_value_gep")
.unwrap(),
"get_value",
)
.unwrap()
.into_float_value()
pub fn get_value(&self, builder: &mut FunctionBuilder, val: ir::Value) -> ir::Value {
let offset = builder.ins().iconst(types::I8, 64);
builder.ins().rotl(val, offset)
}
pub fn get_int(&self, val: StructValue<'ctx>) -> IntValue<'ctx> {
let alloca = self
.builder
.build_alloca(self.helpers.int_type, "get_value_alloca")
.unwrap();
self.builder.build_store(alloca, val).unwrap();
self.builder
.build_load(
self.helpers.int_type,
self.builder
.build_struct_gep(self.helpers.value_type, alloca, 1, "get_value_gep")
.unwrap(),
"get_value",
)
.unwrap()
.into_int_value()
pub fn get_tag(&self, builder: &mut FunctionBuilder, val: ir::Value) -> ir::Value {
let offset = builder.ins().iconst(types::I8, 64);
builder.ins().rotl(val, offset)
}
pub fn get_bool(&self, val: StructValue<'ctx>) -> IntValue<'ctx> {
let alloca = self
.builder
.build_alloca(self.helpers.bool_type, "get_value_alloca")
.unwrap();
self.builder.build_store(alloca, val).unwrap();
self.builder
.build_load(
self.helpers.bool_type,
self.builder
.build_struct_gep(self.helpers.value_type, alloca, 1, "get_value_gep")
.unwrap(),
"get_value",
)
.unwrap()
.into_int_value()
}
pub fn get_tag(&self, val: StructValue<'ctx>) -> IntValue<'ctx> {
let alloca = self
.builder
.build_alloca(self.helpers.value_type, "get_tag_alloca")
.unwrap();
self.builder.build_store(alloca, val).unwrap();
self.builder
.build_load(
self.helpers.int_type,
self.builder
.build_struct_gep(self.helpers.value_type, alloca, 0, "get_tag_gep")
.unwrap(),
"get_tag",
)
.unwrap()
.into_int_value()
}
pub fn const_ptr(&self, ptr: *const ()) -> PointerValue<'ctx> {
self.builder
pub fn const_ptr(&self, ptr: *const ()) -> PointerValue {
self.func_builder
.build_int_to_ptr(
self.helpers.int_type.const_int(ptr as _, false),
self.helpers.ptr_type,

View File

@@ -195,11 +195,11 @@ impl DowngradeContext {
|| unreachable!(),
|func| {
unsafe {
let old = std::ptr::read(func);
let old = core::ptr::read(func);
match old.resolve(Index::Func(idx), self_ptr.as_mut().unwrap(), env) {
Ok(ok) => std::ptr::write(func, ok),
Ok(ok) => core::ptr::write(func, ok),
Err(err) => {
std::ptr::write(
core::ptr::write(
func,
Func {
param: crate::ir::Param::Ident(EcoString::new()),
@@ -225,11 +225,11 @@ impl DowngradeContext {
|| unreachable!(),
|thunk| {
unsafe {
let (old, _) = std::ptr::read(thunk);
let (old, _) = core::ptr::read(thunk);
match old.resolve(Index::Thunk(idx), self_ptr.as_mut().unwrap(), env) {
Ok(ok) => std::ptr::write(&mut thunk.0, ok),
Ok(ok) => core::ptr::write(&mut thunk.0, ok),
Err(err) => {
std::ptr::write(
core::ptr::write(
&mut thunk.0,
Ir::Const(super::Const { val: Const::Null }),
);

View File

@@ -1,7 +1,7 @@
use derive_more::{IsVariant, TryUnwrap, Unwrap};
use ecow::EcoString;
use hashbrown::HashMap;
use inkwell::values::{FunctionValue, StructValue};
use cranelift::codegen;
use itertools::Itertools;
use rnix::ast::HasEntry;
use rnix::ast::{self, Expr};
@@ -90,9 +90,9 @@ macro_rules! ir {
}
impl JITCompile for Ir {
fn compile<'ctx>(&self, ctx: &JITContext<'ctx>, func: FunctionValue<'ctx>, values: &mut Vec<Value>) -> StructValue<'ctx>{
fn compile(&self, ctx: &mut JITContext, builder: &mut FunctionBuilder) -> StructValue{
match self {
$(Ir::$ty(ir) => ir.compile(ctx, func, values),)*
$(Ir::$ty(ir) => ir.compile(ctx, builder),)*
}
}
}

View File

@@ -1,5 +1,5 @@
use std::mem::{MaybeUninit, replace, transmute};
use std::ops::{Deref, DerefMut};
use core::mem::{MaybeUninit, replace, transmute};
use core::ops::{Deref, DerefMut};
use crate::error::*;

View File

@@ -1,5 +1,5 @@
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::hash::Hash;
use core::fmt::{Display, Formatter, Result as FmtResult};
use core::hash::Hash;
use derive_more::{Constructor, IsVariant, Unwrap};
use ecow::EcoString;
@@ -30,9 +30,9 @@ pub enum Const {
}
impl Hash for Const {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
use Const::*;
std::mem::discriminant(self).hash(state);
core::mem::discriminant(self).hash(state);
match self {
Int(x) => x.hash(state),
Float(x) => x.to_bits().hash(state),

View File

@@ -1,4 +1,4 @@
use std::ops::Deref;
use core::ops::Deref;
use std::rc::Rc;
use derive_more::Constructor;
@@ -99,7 +99,7 @@ impl AttrSet {
}
pub fn into_inner(self: Rc<Self>) -> Rc<HashMap<EcoString, Value>> {
unsafe { std::mem::transmute(self) }
unsafe { core::mem::transmute(self) }
}
pub fn from_inner(data: HashMap<EcoString, Value>) -> Self {