///|
/// Machine-code bytes for Wasm-ABI import trampolines that call the
/// `wasmoon_jit_hostcall` C bridge.
pub(all) struct HostcallImportTrampolineCode {
code : Array[Int]
}
///|
pub suberror HostcallImportTrampolineError {
UnsupportedHostcallImportTrampoline(message~ : String)
} derive(Debug, Eq)
///|
priv enum RuntimeValueBridge {
Hostcall(Int)
}
///|
priv enum HostcallRegClass {
Int
Float32
Float64
Vector
}
///|
priv struct HostcallPReg {
index : Int
}
///|
priv enum HostcallAbiValueLocation {
Reg(HostcallPReg)
Stack(offset~ : Int, size~ : Int)
ReturnArea(offset~ : Int, size~ : Int)
}
///|
fn hostcall_abi_location_stack_size(location : HostcallAbiValueLocation) -> Int {
match location {
Reg(_) => 0
Stack(size~, ..) | ReturnArea(size~, ..) => size
}
}
///|
fn RuntimeValueBridge::bridge_arg(self : RuntimeValueBridge) -> Int64 {
match self {
Hostcall(host_func_addr) => host_func_addr.to_int64()
}
}
///|
fn RuntimeValueBridge::x64_bridge_ptr(self : RuntimeValueBridge) -> Int64 {
match self {
Hostcall(_) => c_jit_get_hostcall_ptr()
}
}
///|
pub impl Show for HostcallImportTrampolineError with fn output(self, logger) {
logger.write_string(Repr(self).to_string())
}
///|
fn unsupported_hostcall_import(
message : String,
) -> Unit raise HostcallImportTrampolineError {
raise UnsupportedHostcallImportTrampoline(message~)
}
///|
fn hostcall_scalar_class(ty : @types.ValueType) -> HostcallRegClass {
match ty {
I32 | I64 => Int
F32 => Float32
F64 => Float64
V128 => Vector
_ => Int
}
}
///|
fn hostcall_abi_stack_align(ty : @types.ValueType) -> Int {
match ty {
V128 => 16
_ => 8
}
}
///|
fn hostcall_abi_stack_location(
ty : @types.ValueType,
offset : Int,
) -> HostcallAbiValueLocation {
let size = hostcall_value_byte_count(ty)
let align = hostcall_abi_stack_align(ty)
align |> ignore
Stack(offset~, size~)
}
///|
fn hostcall_param_locations(
param_types : Array[@types.ValueType],
int_arg_regs : Array[Int],
float_arg_reg_count : Int,
) -> Array[HostcallAbiValueLocation] {
let locations : Array[HostcallAbiValueLocation] = []
let mut int_seen = 0
let mut float_seen = 0
let mut stack_offset = 0
for ty in param_types {
match hostcall_scalar_class(ty) {
Int =>
if int_seen < int_arg_regs.length() {
locations.push(Reg({ index: int_arg_regs[int_seen] }))
int_seen = int_seen + 1
} else {
let align = hostcall_abi_stack_align(ty)
let offset = align_to(stack_offset, align)
let location = hostcall_abi_stack_location(ty, offset)
locations.push(location)
stack_offset = offset + hostcall_abi_location_stack_size(location)
int_seen = int_seen + 1
}
Float32 | Float64 | Vector =>
if float_seen < float_arg_reg_count {
locations.push(Reg({ index: float_seen }))
float_seen = float_seen + 1
} else {
let align = hostcall_abi_stack_align(ty)
let offset = align_to(stack_offset, align)
let location = hostcall_abi_stack_location(ty, offset)
locations.push(location)
stack_offset = offset + hostcall_abi_location_stack_size(location)
float_seen = float_seen + 1
}
}
}
locations
}
///|
fn hostcall_abi_return_area_location(
ty : @types.ValueType,
offset : Int,
) -> HostcallAbiValueLocation {
let size = hostcall_value_byte_count(ty)
let align = hostcall_abi_stack_align(ty)
align |> ignore
ReturnArea(offset~, size~)
}
///|
fn hostcall_result_locations(
result_types : Array[@types.ValueType],
int_result_regs : Array[Int],
float_result_reg_count : Int,
) -> Array[HostcallAbiValueLocation] {
let locations : Array[HostcallAbiValueLocation] = []
let mut int_seen = 0
let mut float_seen = 0
let mut return_area_offset = 0
for ty in result_types {
match hostcall_scalar_class(ty) {
Int =>
if int_seen < int_result_regs.length() {
locations.push(Reg({ index: int_result_regs[int_seen] }))
int_seen = int_seen + 1
} else {
let align = hostcall_abi_stack_align(ty)
let offset = align_to(return_area_offset, align)
let location = hostcall_abi_return_area_location(ty, offset)
locations.push(location)
return_area_offset = offset +
hostcall_abi_location_stack_size(location)
int_seen = int_seen + 1
}
Float32 | Float64 | Vector =>
if float_seen < float_result_reg_count {
locations.push(Reg({ index: float_seen }))
float_seen = float_seen + 1
} else {
let align = hostcall_abi_stack_align(ty)
let offset = align_to(return_area_offset, align)
let location = hostcall_abi_return_area_location(ty, offset)
locations.push(location)
return_area_offset = offset +
hostcall_abi_location_stack_size(location)
float_seen = float_seen + 1
}
}
}
locations
}
///|
fn hostcall_stack_arg_size(locations : Array[HostcallAbiValueLocation]) -> Int {
let mut size = 0
for location in locations {
if location is Stack(offset~, ..) {
let end = offset + hostcall_abi_location_stack_size(location)
if end > size {
size = end
}
}
}
align_to(size, 16)
}
///|
fn hostcall_return_area_size(
locations : Array[HostcallAbiValueLocation],
) -> Int {
let mut size = 0
for location in locations {
if location is ReturnArea(offset~, ..) {
let end = offset + hostcall_abi_location_stack_size(location)
if end > size {
size = end
}
}
}
align_to(size, 16)
}
///|
fn validate_hostcall_import_signature(
param_types : Array[@types.ValueType],
result_types : Array[@types.ValueType],
) -> Unit {
param_types |> ignore
result_types |> ignore
}
///|
fn hostcall_append_u32_le(code : Array[Int], word : Int) -> Unit {
code.push(word & 0xff)
code.push((word >> 8) & 0xff)
code.push((word >> 16) & 0xff)
code.push((word >> 24) & 0xff)
}
///|
fn hostcall_append_u64_le(code : Array[Int], word : Int64) -> Unit {
let mut x = word
for _ in 0..<8 {
code.push((x & 0xffL).to_int())
x = x >> 8
}
}
///|
fn align_to(value : Int, align : Int) -> Int {
if value == 0 {
0
} else {
(value + align - 1) / align * align
}
}
///|
fn a64_emit(code : Array[Int], word : Int) -> Unit {
hostcall_append_u32_le(code, word)
}
///|
fn a64_emit_add_imm(
code : Array[Int],
rd : Int,
rn : Int,
imm : Int,
) -> Unit raise HostcallImportTrampolineError {
if imm < 0 {
unsupported_hostcall_import(
"hostcall import trampoline stack adjustment must be non-negative",
)
}
if imm == 0 {
a64_emit(code, 0x91000000 | ((rn & 31) << 5) | (rd & 31))
return
}
let mut remaining = imm
while remaining > 0 {
let chunk = if remaining > 4095 { 4095 } else { remaining }
a64_emit(
code,
0x91000000 | ((chunk & 0xfff) << 10) | ((rn & 31) << 5) | (rd & 31),
)
remaining = remaining - chunk
}
}
///|
fn a64_emit_sub_imm(
code : Array[Int],
rd : Int,
rn : Int,
imm : Int,
) -> Unit raise HostcallImportTrampolineError {
if imm < 0 {
unsupported_hostcall_import(
"hostcall import trampoline stack adjustment must be non-negative",
)
}
if imm == 0 {
a64_emit(code, 0xd1000000 | ((rn & 31) << 5) | (rd & 31))
return
}
let mut remaining = imm
while remaining > 0 {
let chunk = if remaining > 4095 { 4095 } else { remaining }
a64_emit(
code,
0xd1000000 | ((chunk & 0xfff) << 10) | ((rn & 31) << 5) | (rd & 31),
)
remaining = remaining - chunk
}
}
///|
fn a64_emit_movz(code : Array[Int], rd : Int, imm16 : Int, shift : Int) -> Unit {
let hw = shift / 16
a64_emit(
code,
0xd2800000 | ((hw & 3) << 21) | ((imm16 & 0xffff) << 5) | (rd & 31),
)
}
///|
fn a64_emit_movk(code : Array[Int], rd : Int, imm16 : Int, shift : Int) -> Unit {
let hw = shift / 16
a64_emit(
code,
0xf2800000 | ((hw & 3) << 21) | ((imm16 & 0xffff) << 5) | (rd & 31),
)
}
///|
fn a64_emit_load_imm64_fixed(
code : Array[Int],
rd : Int,
value : Int64,
) -> Unit {
a64_emit_movz(code, rd, (value & 0xffffL).to_int(), 0)
a64_emit_movk(code, rd, ((value >> 16) & 0xffffL).to_int(), 16)
a64_emit_movk(code, rd, ((value >> 32) & 0xffffL).to_int(), 32)
a64_emit_movk(code, rd, ((value >> 48) & 0xffffL).to_int(), 48)
}
///|
fn a64_emit_add_byte_offset(
code : Array[Int],
rd : Int,
rn : Int,
offset : Int,
) -> Unit raise HostcallImportTrampolineError {
if offset < 0 {
unsupported_hostcall_import(
"hostcall import trampoline AArch64 memory offset must be non-negative",
)
}
a64_emit_add_imm(code, rd, rn, offset)
}
///|
fn a64_scratch_gpr_for(rt : Int) -> Int {
if rt == 16 {
17
} else {
16
}
}
///|
fn a64_emit_ldr_x(
code : Array[Int],
rt : Int,
rn : Int,
offset : Int,
) -> Unit raise HostcallImportTrampolineError {
if offset % 8 != 0 || offset < 0 || offset / 8 > 4095 {
let scratch = a64_scratch_gpr_for(rt)
a64_emit_add_byte_offset(code, scratch, rn, offset)
a64_emit_ldr_x(code, rt, scratch, 0)
return
}
a64_emit(
code,
0xf9400000 | ((offset / 8) << 10) | ((rn & 31) << 5) | (rt & 31),
)
}
///|
fn a64_emit_str_x(
code : Array[Int],
rt : Int,
rn : Int,
offset : Int,
) -> Unit raise HostcallImportTrampolineError {
if offset % 8 != 0 || offset < 0 || offset / 8 > 4095 {
let scratch = a64_scratch_gpr_for(rt)
a64_emit_add_byte_offset(code, scratch, rn, offset)
a64_emit_str_x(code, rt, scratch, 0)
return
}
a64_emit(
code,
0xf9000000 | ((offset / 8) << 10) | ((rn & 31) << 5) | (rt & 31),
)
}
///|
fn a64_emit_ldr_s(
code : Array[Int],
rt : Int,
rn : Int,
offset : Int,
) -> Unit raise HostcallImportTrampolineError {
if offset % 4 != 0 || offset < 0 || offset / 4 > 4095 {
a64_emit_add_byte_offset(code, 16, rn, offset)
a64_emit_ldr_s(code, rt, 16, 0)
return
}
a64_emit(
code,
0xbd400000 | ((offset / 4) << 10) | ((rn & 31) << 5) | (rt & 31),
)
}
///|
fn a64_emit_str_s(
code : Array[Int],
rt : Int,
rn : Int,
offset : Int,
) -> Unit raise HostcallImportTrampolineError {
if offset % 4 != 0 || offset < 0 || offset / 4 > 4095 {
a64_emit_add_byte_offset(code, 16, rn, offset)
a64_emit_str_s(code, rt, 16, 0)
return
}
a64_emit(
code,
0xbd000000 | ((offset / 4) << 10) | ((rn & 31) << 5) | (rt & 31),
)
}
///|
fn a64_emit_ldr_d(
code : Array[Int],
rt : Int,
rn : Int,
offset : Int,
) -> Unit raise HostcallImportTrampolineError {
if offset % 8 != 0 || offset < 0 || offset / 8 > 4095 {
a64_emit_add_byte_offset(code, 16, rn, offset)
a64_emit_ldr_d(code, rt, 16, 0)
return
}
a64_emit(
code,
0xfd400000 | ((offset / 8) << 10) | ((rn & 31) << 5) | (rt & 31),
)
}
///|
fn a64_emit_str_d(
code : Array[Int],
rt : Int,
rn : Int,
offset : Int,
) -> Unit raise HostcallImportTrampolineError {
if offset % 8 != 0 || offset < 0 || offset / 8 > 4095 {
a64_emit_add_byte_offset(code, 16, rn, offset)
a64_emit_str_d(code, rt, 16, 0)
return
}
a64_emit(
code,
0xfd000000 | ((offset / 8) << 10) | ((rn & 31) << 5) | (rt & 31),
)
}
///|
fn a64_emit_ldr_q(
code : Array[Int],
rt : Int,
rn : Int,
offset : Int,
) -> Unit raise HostcallImportTrampolineError {
if offset % 16 != 0 || offset < 0 || offset / 16 > 4095 {
a64_emit_add_byte_offset(code, 16, rn, offset)
a64_emit_ldr_q(code, rt, 16, 0)
return
}
a64_emit(
code,
0x3dc00000 | ((offset / 16) << 10) | ((rn & 31) << 5) | (rt & 31),
)
}
///|
fn a64_emit_str_q(
code : Array[Int],
rt : Int,
rn : Int,
offset : Int,
) -> Unit raise HostcallImportTrampolineError {
if offset % 16 != 0 || offset < 0 || offset / 16 > 4095 {
a64_emit_add_byte_offset(code, 16, rn, offset)
a64_emit_str_q(code, rt, 16, 0)
return
}
a64_emit(
code,
0x3d800000 | ((offset / 16) << 10) | ((rn & 31) << 5) | (rt & 31),
)
}
///|
fn a64_emit_blr(code : Array[Int], rn : Int) -> Unit {
a64_emit(code, 0xd63f0000 | ((rn & 31) << 5))
}
///|
fn a64_emit_ret(code : Array[Int]) -> Unit {
a64_emit(code, 0xd65f03c0)
}
///|
fn a64_emit_store_slot(
code : Array[Int],
ty : @types.ValueType,
reg : Int,
offset : Int,
) -> Unit raise HostcallImportTrampolineError {
a64_emit_store_slot_base(code, ty, reg, 31, offset)
}
///|
fn a64_emit_store_slot_base(
code : Array[Int],
ty : @types.ValueType,
reg : Int,
base : Int,
offset : Int,
) -> Unit raise HostcallImportTrampolineError {
match ty {
F32 => a64_emit_str_s(code, reg, base, offset)
F64 => a64_emit_str_d(code, reg, base, offset)
V128 => a64_emit_str_q(code, reg, base, offset)
_ => a64_emit_str_x(code, reg, base, offset)
}
}
///|
fn a64_emit_load_slot(
code : Array[Int],
ty : @types.ValueType,
reg : Int,
offset : Int,
) -> Unit raise HostcallImportTrampolineError {
a64_emit_load_slot_base(code, ty, reg, 31, offset)
}
///|
fn a64_emit_load_slot_base(
code : Array[Int],
ty : @types.ValueType,
reg : Int,
base : Int,
offset : Int,
) -> Unit raise HostcallImportTrampolineError {
match ty {
F32 => a64_emit_ldr_s(code, reg, base, offset)
F64 => a64_emit_ldr_d(code, reg, base, offset)
V128 => a64_emit_ldr_q(code, reg, base, offset)
_ => a64_emit_ldr_x(code, reg, base, offset)
}
}
///|
fn a64_emit_store_arg_to_values_vec(
code : Array[Int],
ty : @types.ValueType,
location : HostcallAbiValueLocation,
values_offset : Int,
caller_stack_base_offset : Int,
) -> Unit raise HostcallImportTrampolineError {
match location {
Reg(preg) => a64_emit_store_slot(code, ty, preg.index, values_offset)
Stack(offset~, ..) => {
let scratch = match hostcall_scalar_class(ty) {
Int => 16
Float32 | Float64 | Vector => 16
}
a64_emit_load_slot(code, ty, scratch, caller_stack_base_offset + offset)
a64_emit_store_slot(code, ty, scratch, values_offset)
}
ReturnArea(..) =>
unsupported_hostcall_import(
"hostcall import trampoline parameters cannot live in return areas",
)
}
}
///|
fn a64_emit_load_result_from_values_vec(
code : Array[Int],
ty : @types.ValueType,
location : HostcallAbiValueLocation,
values_offset : Int,
) -> Unit raise HostcallImportTrampolineError {
match location {
Reg(preg) => a64_emit_load_slot(code, ty, preg.index, values_offset)
ReturnArea(offset~, ..) => {
let scratch = match hostcall_scalar_class(ty) {
Int => 16
Float32 | Float64 | Vector => 16
}
a64_emit_load_slot(code, ty, scratch, values_offset)
a64_emit_store_slot_base(code, ty, scratch, 23, offset)
}
Stack(..) =>
unsupported_hostcall_import(
"hostcall import trampoline results cannot live in stack argument slots",
)
}
}
///|
fn a64_emit_store_result_to_values_vec(
code : Array[Int],
ty : @types.ValueType,
location : HostcallAbiValueLocation,
values_offset : Int,
return_area_offset : Int,
) -> Unit raise HostcallImportTrampolineError {
match location {
Reg(preg) =>
a64_emit_store_slot_base(code, ty, preg.index, 19, values_offset)
ReturnArea(offset~, ..) => {
let scratch = match hostcall_scalar_class(ty) {
Int => 16
Float32 | Float64 | Vector => 16
}
a64_emit_load_slot(code, ty, scratch, return_area_offset + offset)
a64_emit_store_slot_base(code, ty, scratch, 19, values_offset)
}
Stack(..) =>
unsupported_hostcall_import(
"entry trampoline results cannot live in stack argument slots",
)
}
}
///|
fn build_aarch64_hostcall_import_trampoline(
param_types : Array[@types.ValueType],
result_types : Array[@types.ValueType],
bridge : RuntimeValueBridge,
) -> HostcallImportTrampolineCode raise HostcallImportTrampolineError {
validate_hostcall_import_signature(param_types, result_types)
let code : Array[Int] = []
let param_locations = hostcall_param_locations(
param_types,
[1, 2, 3, 4, 5, 6, 7],
8,
)
let result_locations = hostcall_result_locations(
result_types,
[0, 1, 2, 3, 4, 5, 6, 7],
8,
)
let layout = plan_hostcall_import_trampoline_layout(
param_types, result_types, 7, 8,
)
// Preserve x23 while using it to keep the incoming x8 result-area pointer
// alive across the platform C call. LR occupies the final eight bytes.
let frame_size = align_to(layout.values_vec_bytes + 16, 16)
if frame_size > 0 {
a64_emit_sub_imm(code, 31, 31, frame_size)
a64_emit_str_x(code, 23, 31, frame_size - 16)
a64_emit_str_x(code, 30, 31, frame_size - 8)
}
a64_emit_add_imm(code, 23, 8, 0)
let mut offset = 0
for i, ty in param_types {
a64_emit_store_arg_to_values_vec(
code,
ty,
param_locations[i],
offset,
frame_size,
)
offset = offset + hostcall_value_byte_count(ty)
}
a64_emit_load_imm64_fixed(code, 1, bridge.bridge_arg())
a64_emit_add_imm(code, 2, 31, 0)
a64_emit_load_imm64_fixed(code, 3, layout.arg_slots.to_int64())
a64_emit_load_imm64_fixed(code, 4, layout.result_slots.to_int64())
a64_emit_load_imm64_fixed(code, 16, c_jit_get_hostcall_ptr())
a64_emit_blr(code, 16)
let mut result_offset = offset
for i, ty in result_types {
a64_emit_load_result_from_values_vec(
code,
ty,
result_locations[i],
result_offset,
)
result_offset = result_offset + hostcall_value_byte_count(ty)
}
if frame_size > 0 {
a64_emit_ldr_x(code, 23, 31, frame_size - 16)
a64_emit_ldr_x(code, 30, 31, frame_size - 8)
a64_emit_add_imm(code, 31, 31, frame_size)
}
a64_emit_ret(code)
{ code, }
}
///|
fn x64_rex(code : Array[Int], w : Bool, r : Int, b : Int) -> Unit {
code.push(
(if w { 0x48 } else { 0x40 }) | (((r >> 3) & 1) << 2) | ((b >> 3) & 1),
)
}
///|
fn x64_modrm(code : Array[Int], mod_ : Int, reg : Int, rm : Int) -> Unit {
code.push(((mod_ & 3) << 6) | ((reg & 7) << 3) | (rm & 7))
}
///|
fn x64_sib_base(code : Array[Int], base : Int) -> Unit {
code.push(0x20 | (base & 7))
}
///|
fn x64_modrm_base_disp32(code : Array[Int], reg : Int, base : Int) -> Unit {
x64_modrm(code, 2, reg, if (base & 7) == 4 { 4 } else { base })
if (base & 7) == 4 {
x64_sib_base(code, base)
}
}
///|
fn x64_mov_rr(code : Array[Int], dst : Int, src : Int) -> Unit {
x64_rex(code, true, src, dst)
code.push(0x89)
x64_modrm(code, 3, src, dst)
}
///|
fn x64_mov_imm64(code : Array[Int], dst : Int, value : Int64) -> Unit {
x64_rex(code, true, 0, dst)
code.push(0xb8 + (dst & 7))
hostcall_append_u64_le(code, value)
}
///|
fn x64_add_rsp(code : Array[Int], imm : Int) -> Unit {
code.push(0x48)
code.push(0x81)
code.push(0xc4)
hostcall_append_u32_le(code, imm)
}
///|
fn x64_sub_rsp(code : Array[Int], imm : Int) -> Unit {
code.push(0x48)
code.push(0x81)
code.push(0xec)
hostcall_append_u32_le(code, imm)
}
///|
fn x64_lea_rsp_disp32(code : Array[Int], dst : Int, offset : Int) -> Unit {
x64_rex(code, true, dst, 4)
code.push(0x8d)
x64_modrm_base_disp32(code, dst, 4)
hostcall_append_u32_le(code, offset)
}
///|
fn x64_mov_m64_r64_base(
code : Array[Int],
base : Int,
offset : Int,
src : Int,
) -> Unit {
x64_rex(code, true, src, base)
code.push(0x89)
x64_modrm_base_disp32(code, src, base)
hostcall_append_u32_le(code, offset)
}
///|
fn x64_mov_r64_m64_base(
code : Array[Int],
dst : Int,
base : Int,
offset : Int,
) -> Unit {
x64_rex(code, true, dst, base)
code.push(0x8b)
x64_modrm_base_disp32(code, dst, base)
hostcall_append_u32_le(code, offset)
}
///|
fn x64_movsd_m64_xmm_base(
code : Array[Int],
base : Int,
offset : Int,
src : Int,
) -> Unit {
code.push(0xf2)
x64_rex(code, false, src, base)
code.push(0x0f)
code.push(0x11)
x64_modrm_base_disp32(code, src, base)
hostcall_append_u32_le(code, offset)
}
///|
fn x64_movsd_xmm_m64_base(
code : Array[Int],
dst : Int,
base : Int,
offset : Int,
) -> Unit {
code.push(0xf2)
x64_rex(code, false, dst, base)
code.push(0x0f)
code.push(0x10)
x64_modrm_base_disp32(code, dst, base)
hostcall_append_u32_le(code, offset)
}
///|
fn x64_movdqu_m128_xmm_base(
code : Array[Int],
base : Int,
offset : Int,
src : Int,
) -> Unit {
code.push(0xf3)
x64_rex(code, false, src, base)
code.push(0x0f)
code.push(0x7f)
x64_modrm_base_disp32(code, src, base)
hostcall_append_u32_le(code, offset)
}
///|
fn x64_movdqu_xmm_m128_base(
code : Array[Int],
dst : Int,
base : Int,
offset : Int,
) -> Unit {
code.push(0xf3)
x64_rex(code, false, dst, base)
code.push(0x0f)
code.push(0x6f)
x64_modrm_base_disp32(code, dst, base)
hostcall_append_u32_le(code, offset)
}
///|
fn x64_call_reg(code : Array[Int], reg : Int) -> Unit {
x64_rex(code, false, 0, reg)
code.push(0xff)
x64_modrm(code, 3, 2, reg)
}
///|
fn x64_ret(code : Array[Int]) -> Unit {
code.push(0xc3)
}
///|
fn x64_emit_store_slot(
code : Array[Int],
ty : @types.ValueType,
reg : Int,
offset : Int,
) -> Unit {
x64_emit_store_slot_base(code, ty, reg, 4, offset)
}
///|
fn x64_emit_store_slot_base(
code : Array[Int],
ty : @types.ValueType,
reg : Int,
base : Int,
offset : Int,
) -> Unit {
match ty {
V128 => x64_movdqu_m128_xmm_base(code, base, offset, reg)
F32 | F64 => x64_movsd_m64_xmm_base(code, base, offset, reg)
_ => x64_mov_m64_r64_base(code, base, offset, reg)
}
}
///|
fn x64_emit_load_slot(
code : Array[Int],
ty : @types.ValueType,
reg : Int,
offset : Int,
) -> Unit {
x64_emit_load_slot_base(code, ty, reg, 4, offset)
}
///|
fn x64_emit_load_slot_base(
code : Array[Int],
ty : @types.ValueType,
reg : Int,
base : Int,
offset : Int,
) -> Unit {
match ty {
V128 => x64_movdqu_xmm_m128_base(code, reg, base, offset)
F32 | F64 => x64_movsd_xmm_m64_base(code, reg, base, offset)
_ => x64_mov_r64_m64_base(code, reg, base, offset)
}
}
///|
fn x64_emit_store_arg_to_values_vec(
code : Array[Int],
ty : @types.ValueType,
location : HostcallAbiValueLocation,
values_offset : Int,
caller_stack_base_offset : Int,
) -> Unit raise HostcallImportTrampolineError {
match location {
Reg(preg) => x64_emit_store_slot(code, ty, preg.index, values_offset)
Stack(offset~, ..) => {
let scratch = match hostcall_scalar_class(ty) {
Int => 10
Float32 | Float64 | Vector => 15
}
x64_emit_load_slot(code, ty, scratch, caller_stack_base_offset + offset)
x64_emit_store_slot(code, ty, scratch, values_offset)
}
ReturnArea(..) =>
unsupported_hostcall_import(
"hostcall import trampoline parameters cannot live in return areas",
)
}
}
///|
fn x64_emit_load_result_from_values_vec(
code : Array[Int],
ty : @types.ValueType,
location : HostcallAbiValueLocation,
values_offset : Int,
) -> Unit raise HostcallImportTrampolineError {
match location {
Reg(preg) => x64_emit_load_slot(code, ty, preg.index, values_offset)
ReturnArea(offset~, ..) => {
let scratch = match hostcall_scalar_class(ty) {
Int => 10
Float32 | Float64 | Vector => 15
}
x64_emit_load_slot(code, ty, scratch, values_offset)
x64_emit_store_slot_base(code, ty, scratch, 12, offset)
}
Stack(..) =>
unsupported_hostcall_import(
"hostcall import trampoline results cannot live in stack argument slots",
)
}
}
///|
fn x64_emit_store_result_to_values_vec(
code : Array[Int],
ty : @types.ValueType,
location : HostcallAbiValueLocation,
values_offset : Int,
return_area_offset : Int,
) -> Unit raise HostcallImportTrampolineError {
match location {
Reg(preg) =>
x64_emit_store_slot_base(code, ty, preg.index, 14, values_offset)
ReturnArea(offset~, ..) => {
let scratch = match hostcall_scalar_class(ty) {
Int => 10
Float32 | Float64 | Vector => 15
}
x64_emit_load_slot(code, ty, scratch, return_area_offset + offset)
x64_emit_store_slot_base(code, ty, scratch, 14, values_offset)
}
Stack(..) =>
unsupported_hostcall_import(
"entry trampoline results cannot live in stack argument slots",
)
}
}
///|
fn x64_hostcall_stack_adjust(values_vec_bytes : Int) -> Int {
// SysV x64 enters a function with rsp % 16 == 8. Keep the adjustment at
// 8 modulo 16 so rsp is 16-byte aligned before calling the C bridge.
align_to(values_vec_bytes, 16) + 8
}
///|
fn build_x64_hostcall_import_trampoline(
param_types : Array[@types.ValueType],
result_types : Array[@types.ValueType],
bridge : RuntimeValueBridge,
) -> HostcallImportTrampolineCode raise HostcallImportTrampolineError {
validate_hostcall_import_signature(param_types, result_types)
let code : Array[Int] = []
let int_arg_regs = [6, 2, 1, 8, 9]
let param_locations = hostcall_param_locations(param_types, int_arg_regs, 8)
let result_locations = hostcall_result_locations(
result_types,
[0, 2, 1, 8, 9, 6, 7, 10],
8,
)
let layout = plan_hostcall_import_trampoline_layout(
param_types, result_types, 5, 8,
)
let frame_size = x64_hostcall_stack_adjust(layout.values_vec_bytes)
x64_sub_rsp(code, frame_size)
let mut offset = 0
for i, ty in param_types {
x64_emit_store_arg_to_values_vec(
code,
ty,
param_locations[i],
offset,
frame_size + 8,
)
offset = offset + hostcall_value_byte_count(ty)
}
x64_mov_imm64(code, 6, bridge.bridge_arg())
x64_mov_rr(code, 2, 4)
x64_mov_imm64(code, 1, layout.arg_slots.to_int64())
x64_mov_imm64(code, 8, layout.result_slots.to_int64())
x64_mov_imm64(code, 11, bridge.x64_bridge_ptr())
x64_call_reg(code, 11)
let mut result_offset = offset
for i, ty in result_types {
x64_emit_load_result_from_values_vec(
code,
ty,
result_locations[i],
result_offset,
)
result_offset = result_offset + hostcall_value_byte_count(ty)
}
x64_add_rsp(code, frame_size)
x64_ret(code)
{ code, }
}
///|
pub fn build_hostcall_import_trampoline_for_target(
param_types : Array[@types.ValueType],
result_types : Array[@types.ValueType],
host_func_addr : Int,
target : NativeTarget,
) -> HostcallImportTrampolineCode raise HostcallImportTrampolineError {
match target {
AArch64 =>
build_aarch64_hostcall_import_trampoline(
param_types,
result_types,
Hostcall(host_func_addr),
)
X64 =>
build_x64_hostcall_import_trampoline(
param_types,
result_types,
Hostcall(host_func_addr),
)
}
}
///|
fn validate_entry_trampoline_signature(
param_types : Array[@types.ValueType],
result_types : Array[@types.ValueType],
) -> Unit {
validate_hostcall_import_signature(param_types, result_types)
}
///|
fn build_aarch64_entry_trampoline(
param_types : Array[@types.ValueType],
result_types : Array[@types.ValueType],
) -> Array[Int] raise HostcallImportTrampolineError {
validate_entry_trampoline_signature(param_types, result_types)
let code : Array[Int] = []
let param_locations = hostcall_param_locations(
param_types,
[1, 2, 3, 4, 5, 6, 7],
8,
)
let result_locations = hostcall_result_locations(
result_types,
[0, 1, 2, 3, 4, 5, 6, 7],
8,
)
let outgoing_stack_size = hostcall_stack_arg_size(param_locations)
let return_area_size = hostcall_return_area_size(result_locations)
let return_area_offset = outgoing_stack_size
let save_area_offset = return_area_offset + return_area_size
let frame_size = align_to(save_area_offset + 32, 16)
a64_emit_sub_imm(code, 31, 31, frame_size)
a64_emit_str_x(code, 19, 31, save_area_offset)
a64_emit_str_x(code, 20, 31, save_area_offset + 8)
a64_emit_str_x(code, 23, 31, save_area_offset + 16)
a64_emit_str_x(code, 30, 31, save_area_offset + 24)
a64_emit_add_imm(code, 19, 1, 0)
a64_emit_add_imm(code, 20, 2, 0)
if return_area_size > 0 {
a64_emit_add_imm(code, 23, 31, return_area_offset)
a64_emit_add_imm(code, 8, 23, 0)
}
let mut value_offset = 0
for i, ty in param_types {
match param_locations[i] {
Reg(preg) =>
a64_emit_load_slot_base(code, ty, preg.index, 19, value_offset)
Stack(offset~, ..) => {
let scratch = match hostcall_scalar_class(ty) {
Int => 16
Float32 | Float64 | Vector => 16
}
a64_emit_load_slot_base(code, ty, scratch, 19, value_offset)
a64_emit_store_slot(code, ty, scratch, offset)
}
ReturnArea(..) =>
unsupported_hostcall_import(
"entry trampoline parameters cannot live in return areas",
)
}
value_offset = value_offset + hostcall_value_byte_count(ty)
}
a64_emit_blr(code, 20)
let mut result_offset = value_offset
for i, ty in result_types {
a64_emit_store_result_to_values_vec(
code,
ty,
result_locations[i],
result_offset,
return_area_offset,
)
result_offset = result_offset + hostcall_value_byte_count(ty)
}
a64_emit_load_imm64_fixed(code, 0, 0L)
a64_emit_ldr_x(code, 19, 31, save_area_offset)
a64_emit_ldr_x(code, 20, 31, save_area_offset + 8)
a64_emit_ldr_x(code, 23, 31, save_area_offset + 16)
a64_emit_ldr_x(code, 30, 31, save_area_offset + 24)
a64_emit_add_imm(code, 31, 31, frame_size)
a64_emit_ret(code)
code
}
///|
fn build_x64_entry_trampoline(
param_types : Array[@types.ValueType],
result_types : Array[@types.ValueType],
) -> Array[Int] raise HostcallImportTrampolineError {
validate_entry_trampoline_signature(param_types, result_types)
let code : Array[Int] = []
let int_arg_regs = [6, 2, 1, 8, 9]
let param_locations = hostcall_param_locations(param_types, int_arg_regs, 8)
let result_locations = hostcall_result_locations(
result_types,
[0, 2, 1, 8, 9, 6, 7, 10],
8,
)
let outgoing_stack_size = hostcall_stack_arg_size(param_locations)
let return_area_size = hostcall_return_area_size(result_locations)
let return_area_offset = outgoing_stack_size
let stack_adjust = outgoing_stack_size + return_area_size
code.push(0x41)
code.push(0x54) // push r12
code.push(0x41)
code.push(0x55) // push r13
code.push(0x41)
code.push(0x56) // push r14
x64_sub_rsp(code, stack_adjust)
x64_mov_rr(code, 14, 6)
x64_mov_rr(code, 13, 2)
if return_area_size > 0 {
x64_lea_rsp_disp32(code, 12, return_area_offset)
}
let mut value_offset = 0
for i, ty in param_types {
match param_locations[i] {
Reg(preg) =>
match ty {
V128 => x64_movdqu_xmm_m128_base(code, preg.index, 14, value_offset)
F32 | F64 =>
x64_movsd_xmm_m64_base(code, preg.index, 14, value_offset)
_ => x64_mov_r64_m64_base(code, preg.index, 14, value_offset)
}
Stack(offset~, ..) => {
let scratch = match hostcall_scalar_class(ty) {
Int => 10
Float32 | Float64 | Vector => 15
}
match ty {
V128 => {
x64_movdqu_xmm_m128_base(code, scratch, 14, value_offset)
x64_emit_store_slot_base(code, ty, scratch, 4, offset)
}
F32 | F64 => {
x64_movsd_xmm_m64_base(code, scratch, 14, value_offset)
x64_emit_store_slot_base(code, ty, scratch, 4, offset)
}
_ => {
x64_mov_r64_m64_base(code, scratch, 14, value_offset)
x64_emit_store_slot_base(code, ty, scratch, 4, offset)
}
}
}
ReturnArea(..) =>
unsupported_hostcall_import(
"entry trampoline parameters cannot live in return areas",
)
}
value_offset = value_offset + hostcall_value_byte_count(ty)
}
x64_call_reg(code, 13)
let mut result_offset = value_offset
for i, ty in result_types {
x64_emit_store_result_to_values_vec(
code,
ty,
result_locations[i],
result_offset,
return_area_offset,
)
result_offset = result_offset + hostcall_value_byte_count(ty)
}
x64_mov_imm64(code, 0, 0L)
x64_add_rsp(code, stack_adjust)
code.push(0x41)
code.push(0x5e) // pop r14
code.push(0x41)
code.push(0x5d) // pop r13
code.push(0x41)
code.push(0x5c) // pop r12
x64_ret(code)
code
}
///|
pub fn build_entry_trampoline_for_target(
param_types : Array[@types.ValueType],
result_types : Array[@types.ValueType],
target : NativeTarget,
) -> Array[Int] raise HostcallImportTrampolineError {
match target {
AArch64 => build_aarch64_entry_trampoline(param_types, result_types)
X64 => build_x64_entry_trampoline(param_types, result_types)
}
}