use cranelift::prelude::*;
use super::MirToIR;
use shape_vm::mir::types::BinOp;
impl<'a, 'b> MirToIR<'a, 'b> {
pub(crate) fn compile_binop_i32(
&mut self,
op: &BinOp,
lhs: Value,
rhs: Value,
) -> Result<Value, String> {
let l = self.builder.ins().ireduce(types::I32, lhs);
let r = self.builder.ins().ireduce(types::I32, rhs);
let result = match op {
BinOp::Add => self.builder.ins().iadd(l, r),
BinOp::Sub => self.builder.ins().isub(l, r),
BinOp::Mul => self.builder.ins().imul(l, r),
BinOp::Div => {
self.compile_int_divmod_guarded(l, r, types::I32, true, false)?
}
BinOp::Mod => {
self.compile_int_divmod_guarded(l, r, types::I32, true, true)?
}
_ => return Err(format!("unsupported i32 binop: {:?}", op)),
};
Ok(self.builder.ins().sextend(types::I64, result))
}
pub(crate) fn compile_cmp_i32(
&mut self,
op: &BinOp,
lhs: Value,
rhs: Value,
) -> Result<Value, String> {
let l = self.builder.ins().ireduce(types::I32, lhs);
let r = self.builder.ins().ireduce(types::I32, rhs);
let cc = match op {
BinOp::Eq => IntCC::Equal,
BinOp::Ne => IntCC::NotEqual,
BinOp::Lt => IntCC::SignedLessThan,
BinOp::Le => IntCC::SignedLessThanOrEqual,
BinOp::Gt => IntCC::SignedGreaterThan,
BinOp::Ge => IntCC::SignedGreaterThanOrEqual,
_ => return Err(format!("unsupported i32 cmp: {:?}", op)),
};
let cmp_result = self.builder.ins().icmp(cc, l, r);
let true_val = self
.builder
.ins()
.iconst(types::I64, 1i64);
let false_val = self
.builder
.ins()
.iconst(types::I64, 0i64);
Ok(self.builder.ins().select(cmp_result, true_val, false_val))
}
}
#[cfg(test)]
mod tests {
use cranelift::prelude::*;
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::Module;
fn jit_i32_binop(op: &str, a: i64, b: i64) -> i64 {
let mut flag_builder = settings::builder();
flag_builder.set("opt_level", "speed_and_size").unwrap();
let isa_builder = cranelift_native::builder().unwrap();
let isa = isa_builder
.finish(settings::Flags::new(flag_builder))
.unwrap();
let builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
let mut module = JITModule::new(builder);
let mut ctx = module.make_context();
let ptr_type = types::I64;
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(ptr_type));
sig.params.push(AbiParam::new(ptr_type));
sig.returns.push(AbiParam::new(ptr_type));
let func_id = module
.declare_function("test_fn", cranelift_module::Linkage::Local, &sig)
.unwrap();
ctx.func.signature = sig;
let mut fn_builder_ctx = FunctionBuilderContext::new();
let mut builder = FunctionBuilder::new(&mut ctx.func, &mut fn_builder_ctx);
let block = builder.create_block();
builder.append_block_params_for_function_params(block);
builder.switch_to_block(block);
builder.seal_block(block);
let lhs = builder.block_params(block)[0];
let rhs = builder.block_params(block)[1];
let l = builder.ins().ireduce(types::I32, lhs);
let r = builder.ins().ireduce(types::I32, rhs);
let result = match op {
"add" => builder.ins().iadd(l, r),
"sub" => builder.ins().isub(l, r),
"mul" => builder.ins().imul(l, r),
"div" => builder.ins().sdiv(l, r),
"mod" => builder.ins().srem(l, r),
_ => panic!("unknown op: {}", op),
};
let result_i64 = builder.ins().sextend(types::I64, result);
builder.ins().return_(&[result_i64]);
builder.finalize();
module.define_function(func_id, &mut ctx).unwrap();
module.clear_context(&mut ctx);
module.finalize_definitions().unwrap();
let code_ptr = module.get_finalized_function(func_id);
let func: fn(i64, i64) -> i64 = unsafe { std::mem::transmute(code_ptr) };
func(a, b)
}
fn jit_i32_cmp(op: &str, a: i64, b: i64) -> u64 {
let mut flag_builder = settings::builder();
flag_builder.set("opt_level", "speed_and_size").unwrap();
let isa_builder = cranelift_native::builder().unwrap();
let isa = isa_builder
.finish(settings::Flags::new(flag_builder))
.unwrap();
let builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
let mut module = JITModule::new(builder);
let mut ctx = module.make_context();
let ptr_type = types::I64;
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(ptr_type));
sig.params.push(AbiParam::new(ptr_type));
sig.returns.push(AbiParam::new(ptr_type));
let func_id = module
.declare_function("test_cmp", cranelift_module::Linkage::Local, &sig)
.unwrap();
ctx.func.signature = sig;
let mut fn_builder_ctx = FunctionBuilderContext::new();
let mut builder = FunctionBuilder::new(&mut ctx.func, &mut fn_builder_ctx);
let block = builder.create_block();
builder.append_block_params_for_function_params(block);
builder.switch_to_block(block);
builder.seal_block(block);
let lhs = builder.block_params(block)[0];
let rhs = builder.block_params(block)[1];
let l = builder.ins().ireduce(types::I32, lhs);
let r = builder.ins().ireduce(types::I32, rhs);
let cc = match op {
"eq" => IntCC::Equal,
"ne" => IntCC::NotEqual,
"lt" => IntCC::SignedLessThan,
"le" => IntCC::SignedLessThanOrEqual,
"gt" => IntCC::SignedGreaterThan,
"ge" => IntCC::SignedGreaterThanOrEqual,
_ => panic!("unknown cmp: {}", op),
};
let cmp_result = builder.ins().icmp(cc, l, r);
let true_val = builder
.ins()
.iconst(types::I64, 1i64);
let false_val = builder
.ins()
.iconst(types::I64, 0i64);
let result = builder.ins().select(cmp_result, true_val, false_val);
builder.ins().return_(&[result]);
builder.finalize();
module.define_function(func_id, &mut ctx).unwrap();
module.clear_context(&mut ctx);
module.finalize_definitions().unwrap();
let code_ptr = module.get_finalized_function(func_id);
let func: fn(i64, i64) -> u64 = unsafe { std::mem::transmute(code_ptr) };
func(a, b)
}
#[test]
fn test_i32_add_codegen() {
assert_eq!(jit_i32_binop("add", 100, 200), 300);
}
#[test]
fn test_i32_sub_codegen() {
assert_eq!(jit_i32_binop("sub", 500, 200), 300);
}
#[test]
fn test_i32_mul_codegen() {
assert_eq!(jit_i32_binop("mul", 7, 6), 42);
}
#[test]
fn test_i32_div_codegen() {
assert_eq!(jit_i32_binop("div", 17, 5), 3);
}
#[test]
fn test_i32_mod_codegen() {
assert_eq!(jit_i32_binop("mod", 17, 5), 2);
}
#[test]
fn test_i32_add_wrapping_overflow() {
let result = jit_i32_binop("add", i32::MAX as i64, 1);
assert_eq!(result, i32::MIN as i64);
}
#[test]
fn test_i32_mul_wrapping_overflow() {
let expected = (100000_i32).wrapping_mul(100000_i32) as i64;
assert_eq!(jit_i32_binop("mul", 100000, 100000), expected);
}
#[test]
fn test_i32_cmp_eq_true() {
assert_eq!(
jit_i32_cmp("eq", 42, 42),
1u64
);
}
#[test]
fn test_i32_cmp_eq_false() {
assert_eq!(
jit_i32_cmp("eq", 42, 43),
0u64
);
}
#[test]
fn test_i32_cmp_lt() {
assert_eq!(
jit_i32_cmp("lt", 10, 20),
1u64
);
assert_eq!(
jit_i32_cmp("lt", 20, 10),
0u64
);
}
#[test]
fn test_i32_cmp_gt() {
assert_eq!(
jit_i32_cmp("gt", 20, 10),
1u64
);
assert_eq!(
jit_i32_cmp("gt", 10, 20),
0u64
);
}
#[test]
fn test_i32_negative_values() {
assert_eq!(jit_i32_binop("add", -5, 3), -2);
assert_eq!(
jit_i32_cmp("lt", -10, 5),
1u64
);
}
fn jit_narrow_binop(
op: &str,
narrow: types::Type,
unsigned: bool,
a: i64,
b: i64,
) -> i64 {
let mut flag_builder = settings::builder();
flag_builder.set("opt_level", "speed_and_size").unwrap();
let isa = cranelift_native::builder()
.unwrap()
.finish(settings::Flags::new(flag_builder))
.unwrap();
let builder =
JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
let mut module = JITModule::new(builder);
let mut ctx = module.make_context();
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));
let func_id = module
.declare_function(
"narrow_fn",
cranelift_module::Linkage::Local,
&sig,
)
.unwrap();
ctx.func.signature = sig;
let mut fbc = FunctionBuilderContext::new();
let mut builder = FunctionBuilder::new(&mut ctx.func, &mut fbc);
let block = builder.create_block();
builder.append_block_params_for_function_params(block);
builder.switch_to_block(block);
builder.seal_block(block);
let lhs = builder.block_params(block)[0];
let rhs = builder.block_params(block)[1];
let l = builder.ins().ireduce(narrow, lhs);
let r = builder.ins().ireduce(narrow, rhs);
let result = match op {
"add" => builder.ins().iadd(l, r),
"sub" => builder.ins().isub(l, r),
"mul" => builder.ins().imul(l, r),
_ => panic!("unknown op: {}", op),
};
let widened = if unsigned {
builder.ins().uextend(types::I64, result)
} else {
builder.ins().sextend(types::I64, result)
};
builder.ins().return_(&[widened]);
builder.finalize();
module.define_function(func_id, &mut ctx).unwrap();
module.clear_context(&mut ctx);
module.finalize_definitions().unwrap();
let code_ptr = module.get_finalized_function(func_id);
let func: fn(i64, i64) -> i64 = unsafe { std::mem::transmute(code_ptr) };
func(a, b)
}
#[test]
fn narrow_i8_add_overflow_wraps() {
assert_eq!(
jit_narrow_binop("add", types::I8, false, 100, 100),
(100i8).wrapping_add(100) as i64,
);
assert_eq!(jit_narrow_binop("add", types::I8, false, 100, 100), -56);
}
#[test]
fn narrow_i8_sub_overflow_wraps() {
assert_eq!(
jit_narrow_binop("sub", types::I8, false, -100, 100),
(-100i8).wrapping_sub(100) as i64,
);
assert_eq!(jit_narrow_binop("sub", types::I8, false, -100, 100), 56);
}
#[test]
fn narrow_i8_mul_overflow_wraps() {
assert_eq!(
jit_narrow_binop("mul", types::I8, false, 20, 20),
(20i8).wrapping_mul(20) as i64,
);
assert_eq!(jit_narrow_binop("mul", types::I8, false, 20, 20), -112);
}
#[test]
fn narrow_i16_add_overflow_wraps() {
assert_eq!(
jit_narrow_binop("add", types::I16, false, 30000, 30000),
(30000i16).wrapping_add(30000) as i64,
);
assert_eq!(jit_narrow_binop("add", types::I16, false, 30000, 30000), -5536);
}
#[test]
fn narrow_i16_sub_overflow_wraps() {
assert_eq!(
jit_narrow_binop("sub", types::I16, false, -30000, 30000),
(-30000i16).wrapping_sub(30000) as i64,
);
assert_eq!(jit_narrow_binop("sub", types::I16, false, -30000, 30000), 5536);
}
#[test]
fn narrow_i16_mul_overflow_wraps() {
assert_eq!(
jit_narrow_binop("mul", types::I16, false, 1000, 1000),
(1000i16).wrapping_mul(1000) as i64,
);
assert_eq!(jit_narrow_binop("mul", types::I16, false, 1000, 1000), 16960);
}
#[test]
fn narrow_i32_add_overflow_wraps() {
assert_eq!(
jit_narrow_binop("add", types::I32, false, 2_000_000_000, 2_000_000_000),
(2_000_000_000i32).wrapping_add(2_000_000_000) as i64,
);
assert_eq!(
jit_narrow_binop("add", types::I32, false, 2_000_000_000, 2_000_000_000),
-294_967_296,
);
}
#[test]
fn narrow_i32_sub_overflow_wraps() {
assert_eq!(
jit_narrow_binop("sub", types::I32, false, -2_000_000_000, 2_000_000_000),
(-2_000_000_000i32).wrapping_sub(2_000_000_000) as i64,
);
assert_eq!(
jit_narrow_binop("sub", types::I32, false, -2_000_000_000, 2_000_000_000),
294_967_296,
);
}
#[test]
fn narrow_i32_mul_overflow_wraps() {
assert_eq!(
jit_narrow_binop("mul", types::I32, false, 100_000, 100_000),
(100_000i32).wrapping_mul(100_000) as i64,
);
assert_eq!(
jit_narrow_binop("mul", types::I32, false, 100_000, 100_000),
1_410_065_408,
);
}
#[test]
fn narrow_u8_add_overflow_wraps() {
assert_eq!(
jit_narrow_binop("add", types::I8, true, 200, 200),
(200u8).wrapping_add(200) as i64,
);
assert_eq!(jit_narrow_binop("add", types::I8, true, 200, 200), 144);
}
#[test]
fn narrow_u8_sub_overflow_wraps() {
assert_eq!(
jit_narrow_binop("sub", types::I8, true, 50, 200),
(50u8).wrapping_sub(200) as i64,
);
assert_eq!(jit_narrow_binop("sub", types::I8, true, 50, 200), 106);
}
#[test]
fn narrow_u8_mul_overflow_wraps() {
assert_eq!(
jit_narrow_binop("mul", types::I8, true, 30, 30),
(30u8).wrapping_mul(30) as i64,
);
assert_eq!(jit_narrow_binop("mul", types::I8, true, 30, 30), 132);
}
#[test]
fn narrow_u16_add_overflow_wraps() {
assert_eq!(
jit_narrow_binop("add", types::I16, true, 60000, 60000),
(60000u16).wrapping_add(60000) as i64,
);
assert_eq!(jit_narrow_binop("add", types::I16, true, 60000, 60000), 54464);
}
#[test]
fn narrow_u16_sub_overflow_wraps() {
assert_eq!(
jit_narrow_binop("sub", types::I16, true, 10000, 60000),
(10000u16).wrapping_sub(60000) as i64,
);
assert_eq!(jit_narrow_binop("sub", types::I16, true, 10000, 60000), 15536);
}
#[test]
fn narrow_u16_mul_overflow_wraps() {
assert_eq!(
jit_narrow_binop("mul", types::I16, true, 1000, 1000),
(1000u16).wrapping_mul(1000) as i64,
);
assert_eq!(jit_narrow_binop("mul", types::I16, true, 1000, 1000), 16960);
}
#[test]
fn narrow_u32_add_overflow_wraps() {
assert_eq!(
jit_narrow_binop("add", types::I32, true, 4_000_000_000, 4_000_000_000),
(4_000_000_000u32).wrapping_add(4_000_000_000) as i64,
);
assert_eq!(
jit_narrow_binop("add", types::I32, true, 4_000_000_000, 4_000_000_000),
3_705_032_704,
);
}
#[test]
fn narrow_u32_sub_overflow_wraps() {
assert_eq!(
jit_narrow_binop("sub", types::I32, true, 1_000_000_000, 4_000_000_000),
(1_000_000_000u32).wrapping_sub(4_000_000_000) as i64,
);
assert_eq!(
jit_narrow_binop("sub", types::I32, true, 1_000_000_000, 4_000_000_000),
1_294_967_296,
);
}
#[test]
fn narrow_u32_mul_overflow_wraps() {
assert_eq!(
jit_narrow_binop("mul", types::I32, true, 100_000, 100_000),
(100_000u32).wrapping_mul(100_000) as i64,
);
assert_eq!(
jit_narrow_binop("mul", types::I32, true, 100_000, 100_000),
1_410_065_408,
);
}
#[test]
fn narrow_no_overflow_is_exact() {
assert_eq!(jit_narrow_binop("add", types::I8, false, 5, 7), 12);
assert_eq!(jit_narrow_binop("sub", types::I16, false, 100, 40), 60);
assert_eq!(jit_narrow_binop("mul", types::I32, false, 7, 6), 42);
assert_eq!(jit_narrow_binop("add", types::I8, true, 100, 50), 150);
}
fn jit_u64_binop(op: &str, a: u64, b: u64) -> u64 {
let mut flag_builder = settings::builder();
flag_builder.set("opt_level", "speed_and_size").unwrap();
let isa = cranelift_native::builder()
.unwrap()
.finish(settings::Flags::new(flag_builder))
.unwrap();
let builder =
JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
let mut module = JITModule::new(builder);
let mut ctx = module.make_context();
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));
let func_id = module
.declare_function("u64_fn", cranelift_module::Linkage::Local, &sig)
.unwrap();
ctx.func.signature = sig;
let mut fbc = FunctionBuilderContext::new();
let mut builder = FunctionBuilder::new(&mut ctx.func, &mut fbc);
let block = builder.create_block();
builder.append_block_params_for_function_params(block);
builder.switch_to_block(block);
builder.seal_block(block);
let l = builder.block_params(block)[0];
let r = builder.block_params(block)[1];
let result = match op {
"add" => builder.ins().iadd(l, r),
"sub" => builder.ins().isub(l, r),
"mul" => builder.ins().imul(l, r),
"div" => builder.ins().udiv(l, r),
"mod" => builder.ins().urem(l, r),
_ => panic!("unknown op: {}", op),
};
builder.ins().return_(&[result]);
builder.finalize();
module.define_function(func_id, &mut ctx).unwrap();
module.clear_context(&mut ctx);
module.finalize_definitions().unwrap();
let code_ptr = module.get_finalized_function(func_id);
let func: fn(u64, u64) -> u64 = unsafe { std::mem::transmute(code_ptr) };
func(a, b)
}
fn jit_u64_cmp(op: &str, a: u64, b: u64) -> bool {
let mut flag_builder = settings::builder();
flag_builder.set("opt_level", "speed_and_size").unwrap();
let isa = cranelift_native::builder()
.unwrap()
.finish(settings::Flags::new(flag_builder))
.unwrap();
let builder =
JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
let mut module = JITModule::new(builder);
let mut ctx = module.make_context();
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));
let func_id = module
.declare_function("u64_cmp", cranelift_module::Linkage::Local, &sig)
.unwrap();
ctx.func.signature = sig;
let mut fbc = FunctionBuilderContext::new();
let mut builder = FunctionBuilder::new(&mut ctx.func, &mut fbc);
let block = builder.create_block();
builder.append_block_params_for_function_params(block);
builder.switch_to_block(block);
builder.seal_block(block);
let l = builder.block_params(block)[0];
let r = builder.block_params(block)[1];
let cc = match op {
"lt" => IntCC::UnsignedLessThan,
"le" => IntCC::UnsignedLessThanOrEqual,
"gt" => IntCC::UnsignedGreaterThan,
"ge" => IntCC::UnsignedGreaterThanOrEqual,
"eq" => IntCC::Equal,
"ne" => IntCC::NotEqual,
_ => panic!("unknown cmp: {}", op),
};
let cmp = builder.ins().icmp(cc, l, r);
let t = builder.ins().iconst(types::I64, 1);
let f = builder.ins().iconst(types::I64, 0);
let result = builder.ins().select(cmp, t, f);
builder.ins().return_(&[result]);
builder.finalize();
module.define_function(func_id, &mut ctx).unwrap();
module.clear_context(&mut ctx);
module.finalize_definitions().unwrap();
let code_ptr = module.get_finalized_function(func_id);
let func: fn(u64, u64) -> u64 = unsafe { std::mem::transmute(code_ptr) };
func(a, b) != 0
}
#[test]
fn u64_jit_add_exact() {
assert_eq!(
jit_u64_binop("add", 10_000_000_000_000_000_000, 5),
10_000_000_000_000_000_005
);
}
#[test]
fn u64_jit_add_wraps_at_2_pow_64() {
assert_eq!(jit_u64_binop("add", u64::MAX, 1), 0);
}
#[test]
fn u64_jit_sub_wraps_below_zero() {
assert_eq!(jit_u64_binop("sub", 0, 1), u64::MAX);
}
#[test]
fn u64_jit_mul_wraps_at_2_pow_64() {
assert_eq!(
jit_u64_binop("mul", 10_000_000_000_000_000_000, 10_000_000_000_000_000_000),
10_000_000_000_000_000_000u64.wrapping_mul(10_000_000_000_000_000_000),
);
}
#[test]
fn u64_jit_div_is_unsigned() {
assert_eq!(jit_u64_binop("div", u64::MAX, 2), 9_223_372_036_854_775_807);
}
#[test]
fn u64_jit_mod_is_unsigned() {
assert_eq!(jit_u64_binop("mod", u64::MAX, 10), 5);
}
#[test]
fn u64_jit_div_full_range_operands() {
assert_eq!(jit_u64_binop("div", u64::MAX - 1, 1u64 << 63), 1);
}
#[test]
fn u64_jit_gt_above_i64_max_is_greater() {
assert!(jit_u64_cmp("gt", u64::MAX, 2));
assert!(!jit_u64_cmp("lt", u64::MAX, 2));
}
#[test]
fn u64_jit_cmp_full_range() {
assert!(jit_u64_cmp("ge", u64::MAX, u64::MAX));
assert!(jit_u64_cmp("le", 1u64 << 63, u64::MAX));
assert!(jit_u64_cmp("eq", u64::MAX, u64::MAX));
assert!(jit_u64_cmp("ne", u64::MAX, u64::MAX - 1));
}
const SENTINEL_DIVZERO: i64 = 0x7EAD_BEEF_DEAD_BEEFu64 as i64;
fn jit_guarded_divmod(
narrow: types::Type,
is_signed: bool,
is_mod: bool,
dividend: i64,
divisor: i64,
) -> i64 {
let mut flag_builder = settings::builder();
flag_builder.set("opt_level", "speed_and_size").unwrap();
let isa = cranelift_native::builder()
.unwrap()
.finish(settings::Flags::new(flag_builder))
.unwrap();
let builder =
JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
let mut module = JITModule::new(builder);
let mut ctx = module.make_context();
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));
let func_id = module
.declare_function("guarded_divmod", cranelift_module::Linkage::Local, &sig)
.unwrap();
ctx.func.signature = sig;
let mut fbc = FunctionBuilderContext::new();
let mut builder = FunctionBuilder::new(&mut ctx.func, &mut fbc);
let entry = builder.create_block();
builder.append_block_params_for_function_params(entry);
builder.switch_to_block(entry);
builder.seal_block(entry);
let narrow_imm = |value: i64| -> i64 {
let bits = narrow.bits();
if bits >= 64 {
value
} else {
(value as u64 & ((1u64 << bits) - 1)) as i64
}
};
let lhs_i64 = builder.block_params(entry)[0];
let rhs_i64 = builder.block_params(entry)[1];
let l = if narrow == types::I64 {
lhs_i64
} else {
builder.ins().ireduce(narrow, lhs_i64)
};
let r = if narrow == types::I64 {
rhs_i64
} else {
builder.ins().ireduce(narrow, rhs_i64)
};
let zero = builder.ins().iconst(narrow, 0);
let is_zero = builder.ins().icmp(IntCC::Equal, r, zero);
let div_by_zero_block = builder.create_block();
let continue_block = builder.create_block();
builder
.ins()
.brif(is_zero, div_by_zero_block, &[], continue_block, &[]);
builder.switch_to_block(div_by_zero_block);
builder.seal_block(div_by_zero_block);
let sentinel = builder.ins().iconst(types::I64, SENTINEL_DIVZERO);
builder.ins().return_(&[sentinel]);
builder.switch_to_block(continue_block);
builder.seal_block(continue_block);
let narrow_result = if !is_signed {
if is_mod {
builder.ins().urem(l, r)
} else {
builder.ins().udiv(l, r)
}
} else {
let neg_one = builder.ins().iconst(narrow, narrow_imm(-1));
let int_min = builder
.ins()
.iconst(narrow, narrow_imm(i64::MIN >> (64 - narrow.bits())));
let div_is_neg_one = builder.ins().icmp(IntCC::Equal, r, neg_one);
let dividend_is_min = builder.ins().icmp(IntCC::Equal, l, int_min);
let is_overflow = builder.ins().band(div_is_neg_one, dividend_is_min);
let one = builder.ins().iconst(narrow, narrow_imm(1));
let safe_divisor = builder.ins().select(is_overflow, one, r);
if is_mod {
builder.ins().srem(l, safe_divisor)
} else {
builder.ins().sdiv(l, safe_divisor)
}
};
let widened = if narrow == types::I64 {
narrow_result
} else if is_signed {
builder.ins().sextend(types::I64, narrow_result)
} else {
builder.ins().uextend(types::I64, narrow_result)
};
builder.ins().return_(&[widened]);
builder.finalize();
module.define_function(func_id, &mut ctx).unwrap();
module.clear_context(&mut ctx);
module.finalize_definitions().unwrap();
let code_ptr = module.get_finalized_function(func_id);
let func: fn(i64, i64) -> i64 = unsafe { std::mem::transmute(code_ptr) };
func(dividend, divisor)
}
#[test]
fn i64_div_by_zero_is_guarded_no_sigill() {
assert_eq!(jit_guarded_divmod(types::I64, true, false, 1, 0), SENTINEL_DIVZERO);
assert_eq!(jit_guarded_divmod(types::I64, true, false, -42, 0), SENTINEL_DIVZERO);
}
#[test]
fn i64_mod_by_zero_is_guarded_no_sigill() {
assert_eq!(jit_guarded_divmod(types::I64, true, true, 10, 0), SENTINEL_DIVZERO);
}
#[test]
fn i64_div_int_min_by_neg_one_wraps_no_sigfpe() {
assert_eq!(
jit_guarded_divmod(types::I64, true, false, i64::MIN, -1),
i64::MIN,
);
assert_eq!(
jit_guarded_divmod(types::I64, true, false, i64::MIN, -1),
i64::MIN.wrapping_div(-1),
);
}
#[test]
fn i64_mod_int_min_by_neg_one_is_zero_no_sigfpe() {
assert_eq!(jit_guarded_divmod(types::I64, true, true, i64::MIN, -1), 0);
assert_eq!(
jit_guarded_divmod(types::I64, true, true, i64::MIN, -1),
i64::MIN.wrapping_rem(-1),
);
}
#[test]
fn i64_ordinary_div_mod_unaffected() {
assert_eq!(jit_guarded_divmod(types::I64, true, false, 17, 5), 3);
assert_eq!(jit_guarded_divmod(types::I64, true, true, 17, 5), 2);
assert_eq!(jit_guarded_divmod(types::I64, true, false, -5, -1), 5);
assert_eq!(jit_guarded_divmod(types::I64, true, false, -20, 4), -5);
assert_eq!(jit_guarded_divmod(types::I64, true, true, -20, 6), -2);
}
#[test]
fn i32_div_by_zero_is_guarded_no_sigill() {
assert_eq!(jit_guarded_divmod(types::I32, true, false, 7, 0), SENTINEL_DIVZERO);
assert_eq!(jit_guarded_divmod(types::I32, true, true, 7, 0), SENTINEL_DIVZERO);
}
#[test]
fn i32_div_int_min_by_neg_one_wraps_no_sigfpe() {
assert_eq!(
jit_guarded_divmod(types::I32, true, false, i32::MIN as i64, -1),
i32::MIN as i64,
);
assert_eq!(
jit_guarded_divmod(types::I32, true, true, i32::MIN as i64, -1),
0,
);
}
#[test]
fn i32_ordinary_div_mod_unaffected() {
assert_eq!(jit_guarded_divmod(types::I32, true, false, 17, 5), 3);
assert_eq!(jit_guarded_divmod(types::I32, true, true, 17, 5), 2);
assert_eq!(jit_guarded_divmod(types::I32, true, false, -100, -1), 100);
}
#[test]
fn narrow_i8_div_by_zero_is_guarded_no_sigill() {
assert_eq!(jit_guarded_divmod(types::I8, true, false, 9, 0), SENTINEL_DIVZERO);
assert_eq!(jit_guarded_divmod(types::I16, true, true, 9, 0), SENTINEL_DIVZERO);
}
#[test]
fn narrow_i8_div_int_min_by_neg_one_wraps_no_sigfpe() {
assert_eq!(
jit_guarded_divmod(types::I8, true, false, i8::MIN as i64, -1),
i8::MIN as i64,
);
assert_eq!(
jit_guarded_divmod(types::I8, true, true, i8::MIN as i64, -1),
0,
);
}
#[test]
fn narrow_i16_div_int_min_by_neg_one_wraps_no_sigfpe() {
assert_eq!(
jit_guarded_divmod(types::I16, true, false, i16::MIN as i64, -1),
i16::MIN as i64,
);
assert_eq!(
jit_guarded_divmod(types::I16, true, true, i16::MIN as i64, -1),
0,
);
}
#[test]
fn narrow_signed_ordinary_div_mod_unaffected() {
assert_eq!(jit_guarded_divmod(types::I8, true, false, 100, 7), 14);
assert_eq!(jit_guarded_divmod(types::I8, true, true, 100, 7), 2);
assert_eq!(jit_guarded_divmod(types::I8, true, false, -50, -1), 50);
assert_eq!(jit_guarded_divmod(types::I16, true, false, 30000, 3), 10000);
}
#[test]
fn u64_div_by_zero_is_guarded_no_sigill() {
assert_eq!(jit_guarded_divmod(types::I64, false, false, 100, 0), SENTINEL_DIVZERO);
assert_eq!(jit_guarded_divmod(types::I64, false, true, 100, 0), SENTINEL_DIVZERO);
}
#[test]
fn u64_div_full_range_is_unsigned() {
let q = jit_guarded_divmod(types::I64, false, false, u64::MAX as i64, 2) as u64;
assert_eq!(q, 9_223_372_036_854_775_807);
let r = jit_guarded_divmod(types::I64, false, true, u64::MAX as i64, 10) as u64;
assert_eq!(r, 5);
}
#[test]
fn narrow_unsigned_div_by_zero_is_guarded_no_sigill() {
assert_eq!(jit_guarded_divmod(types::I8, false, false, 200, 0), SENTINEL_DIVZERO);
assert_eq!(jit_guarded_divmod(types::I32, false, true, 4_000_000_000, 0), SENTINEL_DIVZERO);
}
#[test]
fn narrow_unsigned_ordinary_div_mod_unaffected() {
assert_eq!(jit_guarded_divmod(types::I8, false, false, 200, 3), 66);
assert_eq!(jit_guarded_divmod(types::I8, false, true, 200, 3), 2);
assert_eq!(
jit_guarded_divmod(types::I32, false, false, 4_000_000_000, 7),
(4_000_000_000u32 / 7) as i64,
);
}
fn jit_narrow_cmp(
op: &str,
narrow: types::Type,
unsigned: bool,
a: i64,
b: i64,
) -> u64 {
let mut flag_builder = settings::builder();
flag_builder.set("opt_level", "speed_and_size").unwrap();
let isa = cranelift_native::builder()
.unwrap()
.finish(settings::Flags::new(flag_builder))
.unwrap();
let builder =
JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
let mut module = JITModule::new(builder);
let mut ctx = module.make_context();
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));
let func_id = module
.declare_function("narrow_cmp_fn", cranelift_module::Linkage::Local, &sig)
.unwrap();
ctx.func.signature = sig;
let mut fbc = FunctionBuilderContext::new();
let mut builder = FunctionBuilder::new(&mut ctx.func, &mut fbc);
let block = builder.create_block();
builder.append_block_params_for_function_params(block);
builder.switch_to_block(block);
builder.seal_block(block);
let lhs = builder.block_params(block)[0];
let rhs = builder.block_params(block)[1];
let l_narrow = builder.ins().ireduce(narrow, lhs);
let l = if unsigned {
builder.ins().uextend(types::I64, l_narrow)
} else {
builder.ins().sextend(types::I64, l_narrow)
};
let r = rhs;
let cc = match (op, unsigned) {
("eq", _) => IntCC::Equal,
("ne", _) => IntCC::NotEqual,
("lt", false) => IntCC::SignedLessThan,
("lt", true) => IntCC::UnsignedLessThan,
("le", false) => IntCC::SignedLessThanOrEqual,
("le", true) => IntCC::UnsignedLessThanOrEqual,
("gt", false) => IntCC::SignedGreaterThan,
("gt", true) => IntCC::UnsignedGreaterThan,
("ge", false) => IntCC::SignedGreaterThanOrEqual,
("ge", true) => IntCC::UnsignedGreaterThanOrEqual,
_ => panic!("unknown cmp: {}", op),
};
let cmp = builder.ins().icmp(cc, l, r);
let true_val = builder.ins().iconst(types::I64, 1);
let false_val = builder.ins().iconst(types::I64, 0);
let result = builder.ins().select(cmp, true_val, false_val);
builder.ins().return_(&[result]);
builder.finalize();
module.define_function(func_id, &mut ctx).unwrap();
module.clear_context(&mut ctx);
module.finalize_definitions().unwrap();
let code_ptr = module.get_finalized_function(func_id);
let func: fn(i64, i64) -> u64 = unsafe { std::mem::transmute(code_ptr) };
func(a, b)
}
#[test]
fn narrow_i8_eq_negative_literal_canonical_repro() {
let sum: i64 = (100i8).wrapping_add(100) as i64; assert_eq!(jit_narrow_cmp("eq", types::I8, false, sum, -56), 1);
assert_eq!(jit_narrow_cmp("eq", types::I8, false, -56, -56), 1);
assert_eq!(jit_narrow_cmp("eq", types::I8, false, 56, 56), 1);
}
#[test]
fn narrow_i8_eq_ne_negative_and_positive_literal() {
assert_eq!(jit_narrow_cmp("eq", types::I8, false, -56, -56), 1);
assert_eq!(jit_narrow_cmp("eq", types::I8, false, -56, -10), 0);
assert_eq!(jit_narrow_cmp("ne", types::I8, false, -56, -10), 1);
assert_eq!(jit_narrow_cmp("ne", types::I8, false, -56, -56), 0);
assert_eq!(jit_narrow_cmp("eq", types::I8, false, 42, 42), 1);
assert_eq!(jit_narrow_cmp("ne", types::I8, false, 42, 7), 1);
}
#[test]
fn narrow_i8_ordered_cmp_negative_literal() {
assert_eq!(jit_narrow_cmp("lt", types::I8, false, -56, -10), 1);
assert_eq!(jit_narrow_cmp("le", types::I8, false, -56, -56), 1);
assert_eq!(jit_narrow_cmp("gt", types::I8, false, -56, -100), 1);
assert_eq!(jit_narrow_cmp("ge", types::I8, false, -56, -10), 0);
assert_eq!(jit_narrow_cmp("lt", types::I8, false, -56, 10), 1);
assert_eq!(jit_narrow_cmp("gt", types::I8, false, -56, 10), 0);
}
#[test]
fn narrow_i16_cmp_negative_and_positive_literal() {
let v: i64 = -5536; assert_eq!(jit_narrow_cmp("eq", types::I16, false, v, -5536), 1);
assert_eq!(jit_narrow_cmp("ne", types::I16, false, v, -5536), 0);
assert_eq!(jit_narrow_cmp("lt", types::I16, false, v, -100), 1);
assert_eq!(jit_narrow_cmp("le", types::I16, false, v, v), 1);
assert_eq!(jit_narrow_cmp("gt", types::I16, false, v, -10000), 1);
assert_eq!(jit_narrow_cmp("ge", types::I16, false, v, -10000), 1);
assert_eq!(jit_narrow_cmp("eq", types::I16, false, 12345, 12345), 1);
}
#[test]
fn narrow_i32_cmp_negative_and_positive_literal() {
let v: i64 = (2_000_000_000i32).wrapping_add(2_000_000_000) as i64; assert_eq!(jit_narrow_cmp("eq", types::I32, false, v, -294_967_296), 1);
assert_eq!(jit_narrow_cmp("ne", types::I32, false, v, 0), 1);
assert_eq!(jit_narrow_cmp("lt", types::I32, false, v, -1), 1);
assert_eq!(jit_narrow_cmp("le", types::I32, false, v, v), 1);
assert_eq!(jit_narrow_cmp("gt", types::I32, false, v, -1_000_000_000), 1);
assert_eq!(jit_narrow_cmp("ge", types::I32, false, v, v), 1);
assert_eq!(jit_narrow_cmp("eq", types::I32, false, 123_456, 123_456), 1);
}
#[test]
fn narrow_unsigned_cmp_uses_uextend_not_sextend() {
assert_eq!(jit_narrow_cmp("eq", types::I8, true, 200, 200), 1);
assert_eq!(jit_narrow_cmp("ne", types::I8, true, 200, 100), 1);
assert_eq!(jit_narrow_cmp("gt", types::I8, true, 200, 100), 1);
assert_eq!(jit_narrow_cmp("lt", types::I8, true, 200, 100), 0);
assert_eq!(jit_narrow_cmp("ge", types::I8, true, 200, 200), 1);
assert_eq!(jit_narrow_cmp("le", types::I8, true, 100, 200), 1);
assert_eq!(jit_narrow_cmp("eq", types::I16, true, 60_000, 60_000), 1);
assert_eq!(jit_narrow_cmp("gt", types::I16, true, 60_000, 1_000), 1);
assert_eq!(jit_narrow_cmp("eq", types::I32, true, 4_000_000_000, 4_000_000_000), 1);
assert_eq!(jit_narrow_cmp("gt", types::I32, true, 4_000_000_000, 1), 1);
}
#[test]
fn narrow_cmp_out_of_range_literal_not_truncated() {
assert_eq!(jit_narrow_cmp("eq", types::I8, false, 44, 300), 0);
assert_eq!(jit_narrow_cmp("ne", types::I8, false, 44, 300), 1);
assert_eq!(jit_narrow_cmp("lt", types::I8, false, 44, 300), 1);
assert_eq!(jit_narrow_cmp("eq", types::I16, false, 100, 100_000), 0);
assert_eq!(jit_narrow_cmp("lt", types::I16, false, 100, 100_000), 1);
}
#[test]
fn narrow_cmp_against_int_variable_signed_extends() {
assert_eq!(jit_narrow_cmp("lt", types::I8, false, -1, 5), 1);
assert_eq!(jit_narrow_cmp("eq", types::I8, false, -1, 5), 0);
assert_eq!(jit_narrow_cmp("gt", types::I8, false, -1, -100), 1);
assert_eq!(jit_narrow_cmp("lt", types::I32, false, -1, 9_000_000_000), 1);
}
}