use cranelift_codegen::{
binemit,
cursor::FuncCursor,
ir::{self, AbiParam, ArgumentPurpose, ExternalName, InstBuilder, Signature},
isa::{CallConv, TargetIsa},
settings, FinalizedMachReloc, FinalizedRelocTarget, MachTrap,
};
use cranelift_entity::PrimaryMap;
use cranelift_wasm::{FuncIndex, WasmFuncType, WasmHeapTopType, WasmHeapType, WasmValType};
use target_lexicon::Architecture;
use wasmtime_environ::{
BuiltinFunctionIndex, FlagValue, RelocationTarget, Trap, TrapInformation, Tunables,
};
pub use builder::builder;
pub mod isa_builder;
mod obj;
pub use obj::*;
mod compiled_function;
pub use compiled_function::*;
mod builder;
mod compiler;
mod debug;
mod func_environ;
mod gc;
const DEBUG_ASSERT_TRAP_CODE: u16 = u16::MAX;
fn blank_sig(isa: &dyn TargetIsa, call_conv: CallConv) -> ir::Signature {
let pointer_type = isa.pointer_type();
let mut sig = ir::Signature::new(call_conv);
sig.params.push(ir::AbiParam::special(
pointer_type,
ir::ArgumentPurpose::VMContext,
));
sig.params.push(ir::AbiParam::new(pointer_type));
return sig;
}
fn unbarriered_store_type_at_offset(
isa: &dyn TargetIsa,
pos: &mut FuncCursor,
ty: WasmValType,
flags: ir::MemFlags,
base: ir::Value,
offset: i32,
value: ir::Value,
) {
let ir_ty = value_type(isa, ty);
if ir_ty.is_ref() {
let value = pos
.ins()
.bitcast(ir_ty.as_int(), ir::MemFlags::new(), value);
let truncated = match isa.pointer_bytes() {
4 => value,
8 => pos.ins().ireduce(ir::types::I32, value),
_ => unreachable!(),
};
pos.ins().store(flags, truncated, base, offset);
} else {
pos.ins().store(flags, value, base, offset);
}
}
fn unbarriered_load_type_at_offset(
isa: &dyn TargetIsa,
pos: &mut FuncCursor,
ty: WasmValType,
flags: ir::MemFlags,
base: ir::Value,
offset: i32,
) -> ir::Value {
let ir_ty = value_type(isa, ty);
if ir_ty.is_ref() {
let gc_ref = pos.ins().load(ir::types::I32, flags, base, offset);
let extended = match isa.pointer_bytes() {
4 => gc_ref,
8 => pos.ins().uextend(ir::types::I64, gc_ref),
_ => unreachable!(),
};
pos.ins().bitcast(ir_ty, ir::MemFlags::new(), extended)
} else {
pos.ins().load(ir_ty, flags, base, offset)
}
}
fn value_type(isa: &dyn TargetIsa, ty: WasmValType) -> ir::types::Type {
match ty {
WasmValType::I32 => ir::types::I32,
WasmValType::I64 => ir::types::I64,
WasmValType::F32 => ir::types::F32,
WasmValType::F64 => ir::types::F64,
WasmValType::V128 => ir::types::I8X16,
WasmValType::Ref(rt) => reference_type(rt.heap_type, isa.pointer_type()),
}
}
fn array_call_signature(isa: &dyn TargetIsa) -> ir::Signature {
let mut sig = blank_sig(isa, CallConv::triple_default(isa.triple()));
sig.params.push(ir::AbiParam::new(isa.pointer_type()));
sig.params.push(ir::AbiParam::new(isa.pointer_type()));
sig
}
fn wasm_call_signature(
isa: &dyn TargetIsa,
wasm_func_ty: &WasmFuncType,
tunables: &Tunables,
) -> ir::Signature {
let call_conv = if tunables.winch_callable {
assert!(
matches!(
isa.triple().architecture,
Architecture::X86_64 | Architecture::Aarch64(_)
),
"The Winch calling convention is only implemented for x86_64 and aarch64"
);
CallConv::Winch
} else {
CallConv::Tail
};
let mut sig = blank_sig(isa, call_conv);
let cvt = |ty: &WasmValType| ir::AbiParam::new(value_type(isa, *ty));
sig.params.extend(wasm_func_ty.params().iter().map(&cvt));
sig.returns.extend(wasm_func_ty.returns().iter().map(&cvt));
sig
}
fn reference_type(wasm_ht: WasmHeapType, pointer_type: ir::Type) -> ir::Type {
match wasm_ht.top() {
WasmHeapTopType::Func => pointer_type,
WasmHeapTopType::Any | WasmHeapTopType::Extern => match pointer_type {
ir::types::I32 => ir::types::R32,
ir::types::I64 => ir::types::R64,
_ => panic!("unsupported pointer type"),
},
}
}
pub const NS_WASM_FUNC: u32 = 0;
pub const NS_WASMTIME_BUILTIN: u32 = 1;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Relocation {
pub reloc: binemit::Reloc,
pub reloc_target: RelocationTarget,
pub offset: binemit::CodeOffset,
pub addend: binemit::Addend,
}
pub fn clif_flags_to_wasmtime(
flags: impl IntoIterator<Item = settings::Value>,
) -> Vec<(&'static str, FlagValue<'static>)> {
flags
.into_iter()
.map(|val| (val.name, to_flag_value(&val)))
.collect()
}
fn to_flag_value(v: &settings::Value) -> FlagValue<'static> {
match v.kind() {
settings::SettingKind::Enum => FlagValue::Enum(v.as_enum().unwrap()),
settings::SettingKind::Num => FlagValue::Num(v.as_num().unwrap()),
settings::SettingKind::Bool => FlagValue::Bool(v.as_bool().unwrap()),
settings::SettingKind::Preset => unreachable!(),
}
}
pub const ALWAYS_TRAP_CODE: u16 = 100;
pub const CANNOT_ENTER_CODE: u16 = 101;
pub fn mach_trap_to_trap(trap: &MachTrap) -> Option<TrapInformation> {
let &MachTrap { offset, code } = trap;
Some(TrapInformation {
code_offset: offset,
trap_code: match code {
ir::TrapCode::StackOverflow => Trap::StackOverflow,
ir::TrapCode::HeapOutOfBounds => Trap::MemoryOutOfBounds,
ir::TrapCode::HeapMisaligned => Trap::HeapMisaligned,
ir::TrapCode::TableOutOfBounds => Trap::TableOutOfBounds,
ir::TrapCode::IndirectCallToNull => Trap::IndirectCallToNull,
ir::TrapCode::BadSignature => Trap::BadSignature,
ir::TrapCode::IntegerOverflow => Trap::IntegerOverflow,
ir::TrapCode::IntegerDivisionByZero => Trap::IntegerDivisionByZero,
ir::TrapCode::BadConversionToInteger => Trap::BadConversionToInteger,
ir::TrapCode::UnreachableCodeReached => Trap::UnreachableCodeReached,
ir::TrapCode::Interrupt => Trap::Interrupt,
ir::TrapCode::User(ALWAYS_TRAP_CODE) => Trap::AlwaysTrapAdapter,
ir::TrapCode::User(CANNOT_ENTER_CODE) => Trap::CannotEnterComponent,
ir::TrapCode::NullReference => Trap::NullReference,
ir::TrapCode::NullI31Ref => Trap::NullI31Ref,
ir::TrapCode::User(DEBUG_ASSERT_TRAP_CODE) => return None,
ir::TrapCode::User(_) => unreachable!(),
},
})
}
fn mach_reloc_to_reloc(
reloc: &FinalizedMachReloc,
name_map: &PrimaryMap<ir::UserExternalNameRef, ir::UserExternalName>,
) -> Relocation {
let &FinalizedMachReloc {
offset,
kind,
ref target,
addend,
} = reloc;
let reloc_target = match *target {
FinalizedRelocTarget::ExternalName(ExternalName::User(user_func_ref)) => {
let name = &name_map[user_func_ref];
match name.namespace {
NS_WASM_FUNC => RelocationTarget::Wasm(FuncIndex::from_u32(name.index)),
NS_WASMTIME_BUILTIN => {
RelocationTarget::Builtin(BuiltinFunctionIndex::from_u32(name.index))
}
_ => panic!("unknown namespace {}", name.namespace),
}
}
FinalizedRelocTarget::ExternalName(ExternalName::LibCall(libcall)) => {
let libcall = libcall_cranelift_to_wasmtime(libcall);
RelocationTarget::HostLibcall(libcall)
}
_ => panic!("unrecognized external name"),
};
Relocation {
reloc: kind,
reloc_target,
offset,
addend,
}
}
fn libcall_cranelift_to_wasmtime(call: ir::LibCall) -> wasmtime_environ::obj::LibCall {
use wasmtime_environ::obj::LibCall as LC;
match call {
ir::LibCall::FloorF32 => LC::FloorF32,
ir::LibCall::FloorF64 => LC::FloorF64,
ir::LibCall::NearestF32 => LC::NearestF32,
ir::LibCall::NearestF64 => LC::NearestF64,
ir::LibCall::CeilF32 => LC::CeilF32,
ir::LibCall::CeilF64 => LC::CeilF64,
ir::LibCall::TruncF32 => LC::TruncF32,
ir::LibCall::TruncF64 => LC::TruncF64,
ir::LibCall::FmaF32 => LC::FmaF32,
ir::LibCall::FmaF64 => LC::FmaF64,
ir::LibCall::X86Pshufb => LC::X86Pshufb,
_ => panic!("cranelift emitted a libcall wasmtime does not support: {call:?}"),
}
}
struct BuiltinFunctionSignatures {
pointer_type: ir::Type,
#[cfg(feature = "gc")]
reference_type: ir::Type,
call_conv: CallConv,
}
impl BuiltinFunctionSignatures {
fn new(isa: &dyn TargetIsa) -> Self {
Self {
pointer_type: isa.pointer_type(),
#[cfg(feature = "gc")]
reference_type: match isa.pointer_type() {
ir::types::I32 => ir::types::R32,
ir::types::I64 => ir::types::R64,
_ => panic!(),
},
call_conv: CallConv::triple_default(isa.triple()),
}
}
fn vmctx(&self) -> AbiParam {
AbiParam::special(self.pointer_type, ArgumentPurpose::VMContext)
}
#[cfg(feature = "gc")]
fn reference(&self) -> AbiParam {
AbiParam::new(self.reference_type)
}
fn pointer(&self) -> AbiParam {
AbiParam::new(self.pointer_type)
}
fn i32(&self) -> AbiParam {
AbiParam::new(ir::types::I32).uext()
}
fn i64(&self) -> AbiParam {
AbiParam::new(ir::types::I64)
}
fn signature(&self, builtin: BuiltinFunctionIndex) -> Signature {
let mut _cur = 0;
macro_rules! iter {
(
$(
$( #[$attr:meta] )*
$name:ident( $( $pname:ident: $param:ident ),* ) $( -> $result:ident )?;
)*
) => {
$(
$( #[$attr] )*
if _cur == builtin.index() {
return Signature {
params: vec![ $( self.$param() ),* ],
returns: vec![ $( self.$result() )? ],
call_conv: self.call_conv,
};
}
_cur += 1;
)*
};
}
wasmtime_environ::foreach_builtin_function!(iter);
unreachable!();
}
}
const I31_REF_DISCRIMINANT: u32 = 1;