use cranelift::codegen::ir::BlockArg;
use cranelift::prelude::*;
use cranelift_module::FuncId;
use qcode::{
context::Context,
space::MemorySpaceId,
value::{
BasicBlock, BlockId, ValueId, ValueRef,
insn::{
Binary, Binop, Carry, InstructionId, IntBinop, Load, Mnemonic, PopCount, Range,
SBorrow, SCarry, Sext, Store, Unary, Unop, Zext,
},
},
};
use qcode_vm::{PAGE_PERM_OFFSET, PAGE_SIZE, TLB_ENTRIES, TlbEntry, perm};
use rustc_hash::{FxHashMap, FxHashSet};
pub const BLOCK_OK: i64 = 0;
pub const BLOCK_FAULT: i64 = 1;
#[derive(Clone, Copy)]
enum Access {
Load,
Store,
}
impl Access {
fn required(self) -> u8 {
match self {
Self::Load => perm::MAP | perm::READ,
Self::Store => perm::MAP | perm::WRITE,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Unsupported {
Mnemonic(&'static str),
Width(usize),
Access(&'static str),
Terminator(&'static str),
Operand(&'static str),
Escapes(&'static str),
}
impl std::fmt::Display for Unsupported {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Mnemonic(what) => write!(f, "unsupported mnemonic `{what}`"),
Self::Width(size) => write!(f, "unsupported operand width {size}"),
Self::Access(what) => write!(f, "unsupported memory access: {what}"),
Self::Terminator(what) => write!(f, "unsupported terminator `{what}`"),
Self::Operand(what) => write!(f, "unsupported operand: {what}"),
Self::Escapes(what) => write!(f, "value escapes the block: {what}"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Division {
Unsigned,
UnsignedRem,
Signed,
SignedRem,
}
impl Division {
fn is_signed(self) -> bool {
matches!(self, Self::Signed | Self::SignedRem)
}
fn helper(self) -> usize {
match self {
Self::Unsigned => 0,
Self::UnsignedRem => 1,
Self::Signed => 2,
Self::SignedRem => 3,
}
}
}
#[derive(Clone, Copy)]
enum ShiftKind {
Logical,
Arithmetic,
}
pub(crate) fn int_type(size: usize) -> Result<Type, Unsupported> {
match size {
1 => Ok(types::I8),
2 => Ok(types::I16),
4 => Ok(types::I32),
8 => Ok(types::I64),
16 => Ok(types::I128),
other => Err(Unsupported::Width(other)),
}
}
#[derive(Debug, Default, Clone)]
pub struct SpaceTable {
entries: Vec<(MemorySpaceId, usize)>,
}
impl SpaceTable {
fn slot(&mut self, space: MemorySpaceId, required: usize) -> usize {
if let Some(index) = self.entries.iter().position(|(id, _)| *id == space) {
self.entries[index].1 = self.entries[index].1.max(required);
return index;
}
self.entries.push((space, required));
self.entries.len() - 1
}
pub fn entries(&self) -> &[(MemorySpaceId, usize)] {
&self.entries
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
pub(crate) struct BlockTranslator<'a, 'ctx> {
ctx: &'ctx Context<'ctx>,
builder: FunctionBuilder<'a>,
spaces_arg: Value,
exports_arg: Value,
tlb_arg: Value,
memory_arg: Value,
helpers: HelperRefs,
fault_block: Option<cranelift::prelude::Block>,
load_slot: Option<codegen::ir::StackSlot>,
bases: FxHashMap<usize, Value>,
values: FxHashMap<InstructionId, Value>,
own: FxHashSet<InstructionId>,
escaping: Vec<InstructionId>,
pub(crate) table: SpaceTable,
pub(crate) imports: Vec<Export>,
pub(crate) exports: Vec<Export>,
}
#[derive(Debug, Clone, Copy)]
pub struct Helpers {
pub load: FuncId,
pub store: FuncId,
pub divisions: [FuncId; 4],
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct HelperRefs {
pub(crate) load: codegen::ir::FuncRef,
pub(crate) store: codegen::ir::FuncRef,
pub(crate) divisions: [codegen::ir::FuncRef; 4],
}
#[derive(Debug, Clone, Copy)]
pub struct Export {
pub insn: InstructionId,
pub size: usize,
}
impl<'a, 'ctx> BlockTranslator<'a, 'ctx> {
pub(crate) fn new(
ctx: &'ctx Context<'ctx>,
builder: FunctionBuilder<'a>,
entry: cranelift::prelude::Block,
helpers: HelperRefs,
) -> Self {
let spaces_arg = builder.block_params(entry)[0];
let exports_arg = builder.block_params(entry)[1];
let tlb_arg = builder.block_params(entry)[2];
let memory_arg = builder.block_params(entry)[3];
Self {
ctx,
builder,
spaces_arg,
exports_arg,
tlb_arg,
memory_arg,
helpers,
fault_block: None,
load_slot: None,
bases: FxHashMap::default(),
values: FxHashMap::default(),
own: FxHashSet::default(),
escaping: Vec::new(),
table: SpaceTable::default(),
imports: Vec::new(),
exports: Vec::new(),
}
}
fn base(&mut self, slot: usize) -> Value {
if let Some(base) = self.bases.get(&slot) {
return *base;
}
let offset = (slot * std::mem::size_of::<*mut u8>()) as i32;
let base =
self.builder
.ins()
.load(types::I64, MemFlags::trusted(), self.spaces_arg, offset);
self.bases.insert(slot, base);
base
}
fn is_flat(&self, space: MemorySpaceId) -> bool {
space != MemorySpaceId::Shared(self.ctx.shared.default_space)
}
fn constant_address(&self, ptr: ValueId) -> Option<u64> {
match ValueRef::new(ptr, self.ctx) {
ValueRef::Literal(literal) => Some(literal.value()),
ValueRef::Temp(temp) => Some(temp.address() as u64),
ValueRef::Varnode(varnode) => Some(varnode.address() as u64),
_ => None,
}
}
fn operand(&mut self, id: ValueId, size: usize) -> Result<Value, Unsupported> {
let ty = int_type(size)?;
match id {
ValueId::Literal(_) => {
let ValueRef::Literal(literal) = ValueRef::new(id, self.ctx) else {
return Err(Unsupported::Operand("literal did not resolve"));
};
Ok(self.constant(ty, u128::from(literal.value())))
}
ValueId::Instruction(insn) => {
if let Some(&value) = self.values.get(&insn) {
return Ok(value);
}
self.import(insn, size)
}
_ => Err(Unsupported::Operand("not a literal or in-block value")),
}
}
fn width_of(&self, id: ValueId) -> Result<usize, Unsupported> {
let ty = self
.ctx
.stored_type_of(id)
.ok_or(Unsupported::Operand("operand has no type"))?;
Ok(self.ctx.shared.types.size_of(ty))
}
pub(crate) fn translate_body(
&mut self,
block: BlockId,
start: usize,
) -> Result<usize, Unsupported> {
let insns: Vec<InstructionId> = BasicBlock::from_id(self.ctx, block).instruction_ids();
if insns.is_empty() {
return Err(Unsupported::Terminator("block is empty"));
}
let body = &insns[..insns.len() - 1];
if start > body.len() {
return Err(Unsupported::Terminator("entry point past the body"));
}
let cut = body[start..]
.iter()
.position(|&insn| self.interrupts(insn))
.map_or(body.len(), |offset| start + offset);
self.own = insns.iter().copied().collect();
for &insn_id in &body[start..cut] {
self.translate_one(insn_id)?;
self.note_escapes(insn_id);
}
let own = std::mem::take(&mut self.own);
for &reader in &insns[cut..] {
self.export_operands(reader, &own)?;
}
for &def in &std::mem::take(&mut self.escaping) {
self.export(def)?;
}
Ok(cut)
}
fn interrupts(&self, insn_id: InstructionId) -> bool {
let insn = qcode::value::Instruction::from_id(self.ctx, insn_id);
let Mnemonic::PCodeOp(op) = insn.mnemonic() else {
return false;
};
let name = &self.ctx.shared.pcode_ops[op.id];
!matches!(
(name.as_ref(), op.args.as_slice()),
("undef", []) | ("LOCK" | "UNLOCK", [])
)
}
fn note_escapes(&mut self, insn_id: InstructionId) {
let value = ValueId::Instruction(insn_id);
if self
.ctx
.users_of(value)
.iter()
.any(|user| !self.own.contains(user))
{
self.escaping.push(insn_id);
}
}
fn export_operands(
&mut self,
reader: InstructionId,
own: &FxHashSet<InstructionId>,
) -> Result<(), Unsupported> {
let insn = qcode::value::Instruction::from_id(self.ctx, reader);
let operands: Vec<ValueId> = insn
.mnemonic()
.args()
.into_iter()
.map(|arg| arg.qualify(reader.func))
.collect();
for operand in operands {
let ValueId::Instruction(def) = operand else {
continue;
};
if !own.contains(&def) {
continue;
}
self.export(def)?;
}
Ok(())
}
fn export(&mut self, def: InstructionId) -> Result<(), Unsupported> {
if self.exports.iter().any(|export| export.insn == def) {
return Ok(());
}
let Some(&value) = self.values.get(&def) else {
return Ok(());
};
let size = self.width_of(ValueId::Instruction(def))?;
if size > std::mem::size_of::<u64>() {
return Err(Unsupported::Terminator("operand wider than an export slot"));
}
let slot = self.imports.len() + self.exports.len();
let widened = self.widen_to_u64(value);
self.builder.ins().store(
MemFlags::trusted(),
widened,
self.exports_arg,
(slot * std::mem::size_of::<u64>()) as i32,
);
self.exports.push(Export { insn: def, size });
Ok(())
}
fn widen_to_u64(&mut self, value: Value) -> Value {
if self.builder.func.dfg.value_type(value) == types::I64 {
value
} else {
self.builder.ins().uextend(types::I64, value)
}
}
fn import(&mut self, insn: InstructionId, size: usize) -> Result<Value, Unsupported> {
if size > std::mem::size_of::<u64>() {
return Err(Unsupported::Operand("import wider than a value slot"));
}
let slot = self.imports.len();
let wide = self.builder.ins().load(
types::I64,
MemFlags::trusted(),
self.exports_arg,
(slot * std::mem::size_of::<u64>()) as i32,
);
let ty = int_type(size)?;
let value = if ty == types::I64 {
wide
} else {
self.builder.ins().ireduce(ty, wide)
};
self.imports.push(Export { insn, size });
self.values.insert(insn, value);
Ok(value)
}
fn translate_one(&mut self, insn_id: InstructionId) -> Result<(), Unsupported> {
let insn = qcode::value::Instruction::from_id(self.ctx, insn_id);
let func = insn_id.func;
let result = match insn.mnemonic() {
&Mnemonic::Load(Load { space, ptr, size }) => {
let space = space.qualify(func);
if !self.is_flat(space) {
let addr = self.guest_address(ptr.qualify(func))?;
let value = self.ram_load(addr, size)?;
return self.record(insn_id, Some(value));
}
let addr = self
.constant_address(ptr.qualify(func))
.ok_or(Unsupported::Access("non-constant address"))?;
let ty = int_type(size)?;
let slot = self.table.slot(space, addr as usize + size);
let base = self.base(slot);
Some(self.builder.ins().load(
ty,
MemFlags::trusted(),
base,
i32::try_from(addr).map_err(|_| Unsupported::Access("address too large"))?,
))
}
&Mnemonic::Store(Store {
space,
ptr,
size,
src,
}) => {
let space = space.qualify(func);
if !self.is_flat(space) {
let addr = self.guest_address(ptr.qualify(func))?;
let value = self.operand(src.qualify(func), size)?;
self.ram_store(addr, value, size)?;
return Ok(());
}
let addr = self
.constant_address(ptr.qualify(func))
.ok_or(Unsupported::Access("non-constant address"))?;
int_type(size)?;
let value = self.operand(src.qualify(func), size)?;
let slot = self.table.slot(space, addr as usize + size);
let base = self.base(slot);
self.builder.ins().store(
MemFlags::trusted(),
value,
base,
i32::try_from(addr).map_err(|_| Unsupported::Access("address too large"))?,
);
None
}
Mnemonic::Binop(Binary { op, lhs, rhs }) => {
let lhs_id = lhs.qualify(func);
let rhs_id = rhs.qualify(func);
let width = self.width_of(lhs_id)?;
if self.width_of(rhs_id)? != width {
return Err(Unsupported::Operand("mismatched operand widths"));
}
let a = self.operand(lhs_id, width)?;
let b = self.operand(rhs_id, width)?;
Some(self.binop(*op, a, b)?)
}
Mnemonic::Unop(Unary { op, src }) => {
let src_id = src.qualify(func);
let width = self.width_of(src_id)?;
let value = self.operand(src_id, width)?;
match op {
Unop::IntNot => Some(self.builder.ins().bnot(value)),
Unop::IntNegate => Some(self.builder.ins().ineg(value)),
_ => return Err(Unsupported::Mnemonic("float unop")),
}
}
&Mnemonic::Zext(Zext { src, size }) => {
let src_id = src.qualify(func);
let from = self.width_of(src_id)?;
let value = self.operand(src_id, from)?;
let ty = int_type(size)?;
Some(match from.cmp(&size) {
std::cmp::Ordering::Less => self.builder.ins().uextend(ty, value),
std::cmp::Ordering::Equal => value,
std::cmp::Ordering::Greater => self.builder.ins().ireduce(ty, value),
})
}
&Mnemonic::Sext(Sext { src, size }) => {
let src_id = src.qualify(func);
let from = self.width_of(src_id)?;
let value = self.operand(src_id, from)?;
let ty = int_type(size)?;
Some(match from.cmp(&size) {
std::cmp::Ordering::Less => self.builder.ins().sextend(ty, value),
std::cmp::Ordering::Equal => value,
std::cmp::Ordering::Greater => self.builder.ins().ireduce(ty, value),
})
}
&Mnemonic::Range(Range { src, start, size }) => {
let src_id = src.qualify(func);
let from = self.width_of(src_id)?;
let value = self.operand(src_id, from)?;
let from_ty = int_type(from)?;
let ty = int_type(size)?;
let shifted = if start == 0 {
value
} else {
let amount = self.constant(from_ty, (start * 8) as u128);
self.builder.ins().ushr(value, amount)
};
Some(if from == size {
shifted
} else {
self.builder.ins().ireduce(ty, shifted)
})
}
&Mnemonic::PopCount(PopCount { src }) => {
let src_id = src.qualify(func);
let from = self.width_of(src_id)?;
if from > std::mem::size_of::<u64>() {
return Err(Unsupported::Width(from));
}
let value = self.operand(src_id, from)?;
let counted = self.builder.ins().popcnt(value);
let out = self.width_of(ValueId::Instruction(insn_id))?;
Some(self.resize(counted, from, out)?)
}
&Mnemonic::Carry(Carry { lhs, rhs }) => {
let (a, b) = self.pair(lhs.qualify(func), rhs.qualify(func))?;
let sum = self.builder.ins().iadd(a, b);
Some(self.builder.ins().icmp(IntCC::UnsignedLessThan, sum, a))
}
&Mnemonic::SCarry(SCarry { lhs, rhs }) => {
let (a, b) = self.pair(lhs.qualify(func), rhs.qualify(func))?;
let sum = self.builder.ins().iadd(a, b);
let a_differs = self.builder.ins().bxor(a, sum);
let b_differs = self.builder.ins().bxor(b, sum);
let both = self.builder.ins().band(a_differs, b_differs);
let ty = self.builder.func.dfg.value_type(both);
let zero = self.constant(ty, 0);
Some(self.builder.ins().icmp(IntCC::SignedLessThan, both, zero))
}
&Mnemonic::SBorrow(SBorrow { lhs, rhs }) => {
let (a, b) = self.pair(lhs.qualify(func), rhs.qualify(func))?;
let diff = self.builder.ins().isub(a, b);
let operands_differ = self.builder.ins().bxor(a, b);
let result_differs = self.builder.ins().bxor(a, diff);
let both = self.builder.ins().band(operands_differ, result_differs);
let ty = self.builder.func.dfg.value_type(both);
let zero = self.constant(ty, 0);
Some(self.builder.ins().icmp(IntCC::SignedLessThan, both, zero))
}
Mnemonic::PCodeOp(op) => {
let name = &self.ctx.shared.pcode_ops[op.id];
match (name.as_ref(), op.args.as_slice()) {
("undef", []) => {
let out = self.width_of(ValueId::Instruction(insn_id))?;
let ty = int_type(out)?;
Some(self.constant(ty, 0))
}
("LOCK" | "UNLOCK", []) => None,
_ => return Err(Unsupported::Mnemonic("user p-code op")),
}
}
other => return Err(Unsupported::Mnemonic(other.opcode())),
};
if let Some(value) = result {
self.values.insert(insn_id, value);
}
Ok(())
}
fn guest_flags() -> MemFlags {
MemFlags::new().with_notrap()
}
fn constant(&mut self, ty: Type, value: u128) -> Value {
if ty == types::I128 {
let low = self.builder.ins().iconst(types::I64, value as u64 as i64);
let high = self
.builder
.ins()
.iconst(types::I64, (value >> 64) as u64 as i64);
self.builder.ins().iconcat(low, high)
} else {
self.builder.ins().iconst(ty, value as u64 as i64)
}
}
fn splat(byte: u8, ty: Type) -> i64 {
let mut bits = [0u8; 8];
for slot in bits.iter_mut().take(ty.bytes() as usize) {
*slot = byte;
}
i64::from_le_bytes(bits)
}
fn fault_block(&mut self) -> cranelift::prelude::Block {
if let Some(block) = self.fault_block {
return block;
}
let block = self.builder.create_block();
self.builder.set_cold_block(block);
self.fault_block = Some(block);
block
}
fn bail_if(&mut self, cond: Value, target: cranelift::prelude::Block) {
let carry_on = self.builder.create_block();
self.builder.ins().brif(cond, target, &[], carry_on, &[]);
self.builder.switch_to_block(carry_on);
}
fn bail_unless(&mut self, cond: Value, target: cranelift::prelude::Block) {
let carry_on = self.builder.create_block();
self.builder.ins().brif(cond, carry_on, &[], target, &[]);
self.builder.switch_to_block(carry_on);
}
fn guest_address(&mut self, ptr: ValueId) -> Result<Value, Unsupported> {
let width = self.width_of(ptr)?;
if width > 8 {
return Err(Unsupported::Access("address wider than 64 bits"));
}
let value = self.operand(ptr, width)?;
Ok(match self.builder.func.dfg.value_type(value) {
types::I64 => value,
ty if ty.is_int() => self.builder.ins().uextend(types::I64, value),
_ => return Err(Unsupported::Access("address is not an integer")),
})
}
fn checked_width(&self, value: Value, size: usize) -> Result<(), Unsupported> {
if self.builder.func.dfg.value_type(value) == int_type(size)? {
Ok(())
} else {
Err(Unsupported::Operand("value is not its declared width"))
}
}
fn inline_access(
&mut self,
addr: Value,
size: usize,
kind: Access,
fallback: cranelift::prelude::Block,
) -> Result<(Value, Value), Unsupported> {
let ty = int_type(size)?;
let page_mask = (PAGE_SIZE - 1) as i64;
if size > 1 {
let offset = self.builder.ins().band_imm(addr, page_mask);
let last = self.builder.ins().iadd_imm(offset, size as i64 - 1);
let spills = self.builder.ins().band_imm(last, !page_mask);
self.bail_if(spills, fallback);
}
let entry_size = std::mem::size_of::<TlbEntry>() as i64;
debug_assert!(entry_size.count_ones() == 1);
let entry_bits = entry_size.trailing_zeros() as i64;
let index_shift = PAGE_SIZE.trailing_zeros() as i64 - entry_bits;
let shifted = self.builder.ins().ushr_imm(addr, index_shift);
let offset = self
.builder
.ins()
.band_imm(shifted, (TLB_ENTRIES as i64 - 1) << entry_bits);
let entry = self.builder.ins().iadd(self.tlb_arg, offset);
let flags = MemFlags::trusted();
let cached = self.builder.ins().load(types::I64, flags, entry, 0);
let tag = self.builder.ins().band_imm(addr, !page_mask);
let hit = self.builder.ins().icmp(IntCC::Equal, tag, cached);
self.bail_unless(hit, fallback);
let delta = self.builder.ins().load(types::I64, flags, entry, 8);
let host = self.builder.ins().iadd(addr, delta);
let held = self
.builder
.ins()
.load(ty, Self::guest_flags(), host, PAGE_PERM_OFFSET as i32);
let required = self
.builder
.ins()
.iconst(ty, Self::splat(kind.required(), ty));
let missing = self.builder.ins().band_not(required, held);
self.bail_if(missing, fallback);
Ok((host, held))
}
fn narrow_enough_for_ram(&self, size: usize) -> Result<(), Unsupported> {
if size > std::mem::size_of::<u64>() {
return Err(Unsupported::Access("guest RAM access wider than 8 bytes"));
}
Ok(())
}
fn load_slot(&mut self) -> codegen::ir::StackSlot {
if let Some(slot) = self.load_slot {
return slot;
}
let slot = self.builder.create_sized_stack_slot(StackSlotData::new(
StackSlotKind::ExplicitSlot,
8,
3,
));
self.load_slot = Some(slot);
slot
}
fn ram_load(&mut self, addr: Value, size: usize) -> Result<Value, Unsupported> {
let ty = int_type(size)?;
self.narrow_enough_for_ram(size)?;
let done = self.builder.create_block();
self.builder.append_block_param(done, ty);
let fallback = self.builder.create_block();
self.builder.set_cold_block(fallback);
let (host, _) = self.inline_access(addr, size, Access::Load, fallback)?;
let value = self.builder.ins().load(ty, Self::guest_flags(), host, 0);
self.builder.ins().jump(done, &[BlockArg::from(value)]);
self.builder.switch_to_block(fallback);
let slot = self.load_slot();
let out = self.builder.ins().stack_addr(types::I64, slot, 0);
let width = self.builder.ins().iconst(types::I32, size as i64);
let call = self
.builder
.ins()
.call(self.helpers.load, &[self.memory_arg, addr, width, out]);
let status = self.builder.inst_results(call)[0];
let faulted = self.fault_block();
self.bail_if(status, faulted);
let wide = self.builder.ins().stack_load(types::I64, slot, 0);
let narrowed = if ty == types::I64 {
wide
} else {
self.builder.ins().ireduce(ty, wide)
};
self.builder.ins().jump(done, &[BlockArg::from(narrowed)]);
self.builder.switch_to_block(done);
Ok(self.builder.block_params(done)[0])
}
fn ram_store(&mut self, addr: Value, value: Value, size: usize) -> Result<(), Unsupported> {
let ty = int_type(size)?;
self.narrow_enough_for_ram(size)?;
self.checked_width(value, size)?;
let done = self.builder.create_block();
let fallback = self.builder.create_block();
self.builder.set_cold_block(fallback);
let (host, held) = self.inline_access(addr, size, Access::Store, fallback)?;
self.builder
.ins()
.store(Self::guest_flags(), value, host, 0);
let init = self
.builder
.ins()
.bor_imm(held, Self::splat(perm::INIT, ty));
self.builder
.ins()
.store(Self::guest_flags(), init, host, PAGE_PERM_OFFSET as i32);
self.builder.ins().jump(done, &[]);
self.builder.switch_to_block(fallback);
let width = self.builder.ins().iconst(types::I32, size as i64);
let wide = self.widen_to_u64(value);
let call = self
.builder
.ins()
.call(self.helpers.store, &[self.memory_arg, addr, width, wide]);
let status = self.builder.inst_results(call)[0];
let faulted = self.fault_block();
self.bail_if(status, faulted);
self.builder.ins().jump(done, &[]);
self.builder.switch_to_block(done);
Ok(())
}
fn record(&mut self, insn_id: InstructionId, result: Option<Value>) -> Result<(), Unsupported> {
if let Some(value) = result {
self.values.insert(insn_id, value);
}
Ok(())
}
fn pair(&mut self, lhs: ValueId, rhs: ValueId) -> Result<(Value, Value), Unsupported> {
let width = self.width_of(lhs)?;
if self.width_of(rhs)? != width {
return Err(Unsupported::Operand("mismatched operand widths"));
}
Ok((self.operand(lhs, width)?, self.operand(rhs, width)?))
}
fn resize(&mut self, value: Value, from: usize, to: usize) -> Result<Value, Unsupported> {
let ty = int_type(to)?;
int_type(from)?;
Ok(match from.cmp(&to) {
std::cmp::Ordering::Less => self.builder.ins().uextend(ty, value),
std::cmp::Ordering::Equal => value,
std::cmp::Ordering::Greater => self.builder.ins().ireduce(ty, value),
})
}
fn guard_shift(&mut self, shifted: Value, a: Value, amount: Value, kind: ShiftKind) -> Value {
let ty = self.builder.func.dfg.value_type(a);
let bits = u128::from(ty.bits());
let width = self.constant(ty, bits);
let in_range = self
.builder
.ins()
.icmp(IntCC::UnsignedLessThan, amount, width);
let saturated = match kind {
ShiftKind::Logical => self.constant(ty, 0),
ShiftKind::Arithmetic => {
let all = self.constant(ty, bits - 1);
self.builder.ins().sshr(a, all)
}
};
self.builder.ins().select(in_range, shifted, saturated)
}
fn divide(&mut self, a: Value, b: Value, kind: Division) -> Result<Value, Unsupported> {
let ty = self.builder.func.dfg.value_type(a);
if ty == types::I128 {
return Ok(self.divide_wide(a, b, kind));
}
let zero = self.constant(ty, 0);
let one = self.constant(ty, 1);
let by_zero = self.builder.ins().icmp(IntCC::Equal, b, zero);
let mut avoid = by_zero;
if kind.is_signed() {
let most_negative = self.constant(ty, 1u128 << (ty.bits() - 1));
let minus_one = self.constant(ty, u128::MAX);
let a_is_min = self.builder.ins().icmp(IntCC::Equal, a, most_negative);
let b_is_minus_one = self.builder.ins().icmp(IntCC::Equal, b, minus_one);
let overflows = self.builder.ins().band(a_is_min, b_is_minus_one);
avoid = self.builder.ins().bor(by_zero, overflows);
}
let divisor = self.builder.ins().select(avoid, one, b);
let result = match kind {
Division::Unsigned => self.builder.ins().udiv(a, divisor),
Division::UnsignedRem => self.builder.ins().urem(a, divisor),
Division::Signed => self.builder.ins().sdiv(a, divisor),
Division::SignedRem => self.builder.ins().srem(a, divisor),
};
Ok(self.builder.ins().select(by_zero, zero, result))
}
fn divide_wide(&mut self, a: Value, b: Value, kind: Division) -> Value {
let slot = self.builder.create_sized_stack_slot(StackSlotData::new(
StackSlotKind::ExplicitSlot,
16,
4,
));
let out = self.builder.ins().stack_addr(types::I64, slot, 0);
let (a_low, a_high) = self.builder.ins().isplit(a);
let (b_low, b_high) = self.builder.ins().isplit(b);
let helper = self.helpers.divisions[kind.helper()];
self.builder
.ins()
.call(helper, &[a_low, a_high, b_low, b_high, out]);
let low = self.builder.ins().stack_load(types::I64, slot, 0);
let high = self.builder.ins().stack_load(types::I64, slot, 8);
self.builder.ins().iconcat(low, high)
}
fn binop(&mut self, op: Binop, a: Value, b: Value) -> Result<Value, Unsupported> {
let ins = self.builder.ins();
Ok(match op {
Binop::Int(int) => match int {
IntBinop::Add => ins.iadd(a, b),
IntBinop::Sub => ins.isub(a, b),
IntBinop::And => ins.band(a, b),
IntBinop::Or => ins.bor(a, b),
IntBinop::Xor => ins.bxor(a, b),
IntBinop::Mul => ins.imul(a, b),
IntBinop::ShiftLeft => {
let shifted = ins.ishl(a, b);
return Ok(self.guard_shift(shifted, a, b, ShiftKind::Logical));
}
IntBinop::ShiftRight => {
let shifted = ins.ushr(a, b);
return Ok(self.guard_shift(shifted, a, b, ShiftKind::Logical));
}
IntBinop::SShiftRight => {
let shifted = ins.sshr(a, b);
return Ok(self.guard_shift(shifted, a, b, ShiftKind::Arithmetic));
}
IntBinop::Equal => ins.icmp(IntCC::Equal, a, b),
IntBinop::NotEqual => ins.icmp(IntCC::NotEqual, a, b),
IntBinop::Less => ins.icmp(IntCC::UnsignedLessThan, a, b),
IntBinop::LessEqual => ins.icmp(IntCC::UnsignedLessThanOrEqual, a, b),
IntBinop::SLess => ins.icmp(IntCC::SignedLessThan, a, b),
IntBinop::SLessEqual => ins.icmp(IntCC::SignedLessThanOrEqual, a, b),
IntBinop::Div => return self.divide(a, b, Division::Unsigned),
IntBinop::Rem => return self.divide(a, b, Division::UnsignedRem),
IntBinop::Sdiv => return self.divide(a, b, Division::Signed),
IntBinop::Srem => return self.divide(a, b, Division::SignedRem),
_ => return Err(Unsupported::Mnemonic("integer binop")),
},
Binop::Float(_) => return Err(Unsupported::Mnemonic("float binop")),
_ => return Err(Unsupported::Mnemonic("binop")),
})
}
pub(crate) fn finish(mut self) {
let ok = self.builder.ins().iconst(types::I32, BLOCK_OK);
self.builder.ins().return_(&[ok]);
if let Some(block) = self.fault_block {
self.builder.switch_to_block(block);
let status = self.builder.ins().iconst(types::I32, BLOCK_FAULT);
self.builder.ins().return_(&[status]);
}
self.builder.seal_all_blocks();
self.builder.finalize();
}
}