fn fixed_len_bytes_be_from_hex_number(expr: &Expression, fixed_len: u16) -> Option<Vec<u8>> {
let Expression::HexNumberLiteral(_, value, unit) = expr else {
return None;
};
if unit.is_some() {
return None;
}
let raw = value.trim().trim_start_matches("0x");
let mut hex: String = raw
.chars()
.filter(|c| !c.is_whitespace() && *c != '_')
.collect();
if hex.is_empty() {
return None;
}
if hex.len() % 2 == 1 {
hex.insert(0, '0');
}
let bytes = hex_decode(&hex).ok()?;
let fixed_len = fixed_len as usize;
if bytes.len() > fixed_len {
return None;
}
let mut out = vec![0u8; fixed_len - bytes.len()];
out.extend_from_slice(&bytes);
Some(out)
}
fn lower_bytes_eq_hex_number_literal(
left: &Expression,
right: &Expression,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
operator: BinaryOperator,
) -> Option<bool> {
if !matches!(operator, BinaryOperator::Eq | BinaryOperator::Ne) {
return None;
}
if let Some(ValueType::ByteArray {
fixed_len: Some(fixed_len),
}) = infer_type_from_expression(left, ctx)
{
let literal_expr = match right {
Expression::Parenthesis(_, inner) => inner.as_ref(),
other => other,
};
if let Some(bytes) = fixed_len_bytes_be_from_hex_number(literal_expr, fixed_len) {
if lower_expression(left, ctx, instructions) {
instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(bytes)));
instructions.push(Instruction::BinaryOp(operator));
return Some(true);
}
return Some(false);
}
}
if let Some(ValueType::ByteArray {
fixed_len: Some(fixed_len),
}) = infer_type_from_expression(right, ctx)
{
let literal_expr = match left {
Expression::Parenthesis(_, inner) => inner.as_ref(),
other => other,
};
if let Some(bytes) = fixed_len_bytes_be_from_hex_number(literal_expr, fixed_len) {
instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(bytes)));
if lower_expression(right, ctx, instructions) {
instructions.push(Instruction::BinaryOp(operator));
return Some(true);
}
return Some(false);
}
}
None
}
fn is_literal_number(expr: &Expression) -> bool {
matches!(
match expr {
Expression::Parenthesis(_, inner) => inner.as_ref(),
other => other,
},
Expression::NumberLiteral(..)
| Expression::HexNumberLiteral(..)
| Expression::RationalNumberLiteral(..)
)
}
fn is_uint256_operand(expr: &Expression, ctx: &LoweringContext) -> bool {
match infer_type_from_expression(expr, ctx) {
Some(ValueType::Integer {
signed: false,
bits: 256,
}) => true,
Some(_) => false,
None => is_literal_number(expr),
}
}
fn is_typed_uint256(expr: &Expression, ctx: &LoweringContext) -> bool {
!is_literal_number(expr)
&& matches!(
infer_type_from_expression(expr, ctx),
Some(ValueType::Integer {
signed: false,
bits: 256,
})
)
}
fn is_int256_operand(expr: &Expression, ctx: &LoweringContext) -> bool {
matches!(
infer_type_from_expression(expr, ctx),
Some(ValueType::Integer {
signed: true,
bits: 256,
})
)
}
fn is_narrow_result(left: &Expression, right: &Expression, ctx: &LoweringContext) -> bool {
if is_typed_uint256(left, ctx) || is_typed_uint256(right, ctx) {
return false;
}
if is_int256_operand(left, ctx) || is_int256_operand(right, ctx) {
return false;
}
narrow_unsigned_bits(left, right, ctx).is_some() || narrow_signed_bits(left, right, ctx).is_some()
}
fn should_emit_u256_arith_guard(
left: &Expression,
right: &Expression,
ctx: &LoweringContext,
operator: BinaryOperator,
) -> bool {
if ctx.in_unchecked_block() {
return false;
}
if !matches!(
operator,
BinaryOperator::Add | BinaryOperator::Sub | BinaryOperator::Mul
) {
return false;
}
if is_literal_number(left) && is_literal_number(right) {
return false;
}
if is_int256_operand(left, ctx) || is_int256_operand(right, ctx) {
return false;
}
if is_narrow_result(left, right, ctx) {
return false;
}
is_uint256_operand(left, ctx) || is_uint256_operand(right, ctx)
}
fn narrow_unsigned_bits(
left: &Expression,
right: &Expression,
ctx: &LoweringContext,
) -> Option<u16> {
fn narrow_bits(expr: &Expression, ctx: &LoweringContext) -> Option<u16> {
match infer_type_from_expression(expr, ctx) {
Some(ValueType::Integer {
signed: false,
bits,
}) if matches!(bits, 8 | 16 | 32 | 64 | 128) => Some(bits),
_ => None,
}
}
narrow_bits(left, ctx).or_else(|| narrow_bits(right, ctx))
}
fn should_emit_narrow_u_arith_guard(
left: &Expression,
right: &Expression,
ctx: &LoweringContext,
operator: BinaryOperator,
) -> Option<u16> {
if ctx.in_unchecked_block() {
return None;
}
if !matches!(
operator,
BinaryOperator::Add | BinaryOperator::Sub | BinaryOperator::Mul
) {
return None;
}
if is_literal_number(left) && is_literal_number(right) {
return None;
}
if is_typed_uint256(left, ctx) || is_typed_uint256(right, ctx) {
return None;
}
if is_int256_operand(left, ctx) || is_int256_operand(right, ctx) {
return None;
}
narrow_unsigned_bits(left, right, ctx)
}
fn narrow_signed_bits(
left: &Expression,
right: &Expression,
ctx: &LoweringContext,
) -> Option<u16> {
fn narrow_bits(expr: &Expression, ctx: &LoweringContext) -> Option<u16> {
match infer_type_from_expression(expr, ctx) {
Some(ValueType::Integer {
signed: true,
bits,
}) if matches!(bits, 8 | 16 | 32 | 64 | 128) => Some(bits),
_ => None,
}
}
narrow_bits(left, ctx).or_else(|| narrow_bits(right, ctx))
}
fn should_emit_narrow_i_arith_guard(
left: &Expression,
right: &Expression,
ctx: &LoweringContext,
operator: BinaryOperator,
) -> Option<u16> {
if ctx.in_unchecked_block() {
return None;
}
if !matches!(
operator,
BinaryOperator::Add | BinaryOperator::Sub | BinaryOperator::Mul
) {
return None;
}
if is_literal_number(left) && is_literal_number(right) {
return None;
}
if is_typed_uint256(left, ctx) || is_typed_uint256(right, ctx) {
return None;
}
if is_int256_operand(left, ctx) || is_int256_operand(right, ctx) {
return None;
}
narrow_signed_bits(left, right, ctx)
}
fn emit_checked_arith_guard_narrow_u(
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
operator: BinaryOperator,
bits: u16,
) {
let tmp_id = ctx.next_label();
let result_local = ctx.allocate_local(format!("__narith_res_{tmp_id}"), None);
let uint_max = (BigInt::one() << bits as usize) - BigInt::one();
instructions.push(Instruction::BinaryOp(operator));
instructions.push(Instruction::StoreLocal(result_local));
let after_max_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(result_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(uint_max)));
instructions.push(Instruction::BinaryOp(BinaryOperator::Gt));
instructions.push(Instruction::JumpIf {
target: after_max_label,
});
emit_panic(0x11, instructions);
instructions.push(Instruction::Label(after_max_label));
let done_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(result_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
instructions.push(Instruction::JumpIf { target: done_label });
emit_panic(0x11, instructions);
instructions.push(Instruction::Label(done_label));
instructions.push(Instruction::LoadLocal(result_local));
}
fn should_emit_i256_arith_guard(
left: &Expression,
right: &Expression,
ctx: &LoweringContext,
operator: BinaryOperator,
) -> bool {
if ctx.in_unchecked_block() {
return false;
}
if !matches!(
operator,
BinaryOperator::Add | BinaryOperator::Sub | BinaryOperator::Mul
) {
return false;
}
if is_literal_number(left) && is_literal_number(right) {
return false;
}
is_int256_operand(left, ctx) || is_int256_operand(right, ctx)
}
fn emit_widen_to_u256_unsigned(instructions: &mut Vec<Instruction>) {
instructions.push(Instruction::Convert {
target: ConvertTarget::ByteArray,
});
instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(vec![0u8])));
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::BytesConcat,
arg_count: 2,
});
}
fn emit_widen_both_u256_unsigned(instructions: &mut Vec<Instruction>) {
emit_widen_to_u256_unsigned(instructions);
instructions.push(Instruction::Swap);
emit_widen_to_u256_unsigned(instructions);
instructions.push(Instruction::Swap);
}
fn emit_truncate_u256(instructions: &mut Vec<Instruction>) {
instructions.push(Instruction::Convert {
target: ConvertTarget::ByteArray,
});
instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(vec![0u8; 32])));
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::BytesConcat,
arg_count: 2,
});
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(32u64))));
instructions.push(Instruction::Substr);
}
fn should_widen_unchecked_u256(
left: &Expression,
right: &Expression,
ctx: &LoweringContext,
operator: BinaryOperator,
) -> bool {
if !ctx.in_unchecked_block() {
return false;
}
if !matches!(
operator,
BinaryOperator::Add | BinaryOperator::Sub | BinaryOperator::Mul
) {
return false;
}
if is_literal_number(left) && is_literal_number(right) {
return false;
}
if is_int256_operand(left, ctx) || is_int256_operand(right, ctx) {
return false;
}
if is_narrow_result(left, right, ctx) {
return false;
}
is_uint256_operand(left, ctx) || is_uint256_operand(right, ctx)
}
fn emit_checked_arith_guard_i256(
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
operator: BinaryOperator,
) {
let tmp_id = ctx.next_label();
let result_local = ctx.allocate_local(format!("__sarith_res_{tmp_id}"), None);
let mut int256_max_bytes: Vec<u8> = vec![0xffu8; 32];
int256_max_bytes[31] = 0x7f;
let mut int256_min_bytes: Vec<u8> = vec![0u8; 32];
int256_min_bytes[31] = 0x80;
instructions.push(Instruction::BinaryOp(operator));
instructions.push(Instruction::StoreLocal(result_local));
let after_max_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(result_local));
instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(
int256_max_bytes,
)));
instructions.push(Instruction::BinaryOp(BinaryOperator::Gt));
instructions.push(Instruction::JumpIf {
target: after_max_label,
});
emit_panic(0x11, instructions);
instructions.push(Instruction::Label(after_max_label));
let done_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(result_local));
instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(
int256_min_bytes,
)));
instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
instructions.push(Instruction::JumpIf { target: done_label });
emit_panic(0x11, instructions);
instructions.push(Instruction::Label(done_label));
instructions.push(Instruction::LoadLocal(result_local));
}
fn emit_checked_arith_guard_narrow_i(
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
operator: BinaryOperator,
bits: u16,
) {
let tmp_id = ctx.next_label();
let result_local = ctx.allocate_local(format!("__sarith_n_res_{tmp_id}"), None);
let payload = (bits as usize) / 8;
let mut int_max_bytes: Vec<u8> = vec![0u8; 32];
for b in int_max_bytes.iter_mut().take(payload - 1) {
*b = 0xff;
}
int_max_bytes[payload - 1] = 0x7f;
let mut int_min_bytes: Vec<u8> = vec![0xffu8; 32];
for b in int_min_bytes.iter_mut().take(payload - 1) {
*b = 0x00;
}
int_min_bytes[payload - 1] = 0x80;
instructions.push(Instruction::BinaryOp(operator));
instructions.push(Instruction::StoreLocal(result_local));
let after_max_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(result_local));
instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(int_max_bytes)));
instructions.push(Instruction::BinaryOp(BinaryOperator::Gt));
instructions.push(Instruction::JumpIf {
target: after_max_label,
});
emit_panic(0x11, instructions);
instructions.push(Instruction::Label(after_max_label));
let done_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(result_local));
instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(int_min_bytes)));
instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
instructions.push(Instruction::JumpIf { target: done_label });
emit_panic(0x11, instructions);
instructions.push(Instruction::Label(done_label));
instructions.push(Instruction::LoadLocal(result_local));
}
fn u256_push(ins: &mut Vec<Instruction>, v: BigInt) {
ins.push(Instruction::PushLiteral(LiteralValue::Integer(v)));
}
fn u256_bop(ins: &mut Vec<Instruction>, op: BinaryOperator) {
ins.push(Instruction::BinaryOp(op));
}
fn u256_mask128() -> BigInt {
(BigInt::one() << 128usize) - BigInt::one()
}
fn u256_bias127() -> BigInt {
BigInt::one() << 127usize
}
fn u256_mask64() -> BigInt {
(BigInt::one() << 64usize) - BigInt::one()
}
fn emit_u256_unchecked_add_ir(ctx: &mut LoweringContext, ins: &mut Vec<Instruction>) {
let s = ctx.u256_scratch_locals(3);
let (al, bl, lo) = (s[0], s[1], s[2]);
ins.push(Instruction::StoreLocal(bl));
ins.push(Instruction::StoreLocal(al));
ins.push(Instruction::LoadLocal(al));
u256_push(ins, u256_mask128());
u256_bop(ins, BinaryOperator::BitAnd);
ins.push(Instruction::LoadLocal(bl));
u256_push(ins, u256_mask128());
u256_bop(ins, BinaryOperator::BitAnd);
u256_bop(ins, BinaryOperator::Add);
ins.push(Instruction::StoreLocal(lo));
emit_u256_hi_limb(ins, al);
emit_u256_hi_limb(ins, bl);
u256_bop(ins, BinaryOperator::Add);
ins.push(Instruction::LoadLocal(lo));
u256_push(ins, BigInt::from(128u32));
u256_bop(ins, BinaryOperator::Shr);
u256_bop(ins, BinaryOperator::Add);
emit_u256_combine_limbs(ins, lo);
}
fn emit_u256_unchecked_sub_ir(ctx: &mut LoweringContext, ins: &mut Vec<Instruction>) {
let s = ctx.u256_scratch_locals(3);
let (al, bl, lo) = (s[0], s[1], s[2]);
ins.push(Instruction::StoreLocal(bl));
ins.push(Instruction::StoreLocal(al));
ins.push(Instruction::LoadLocal(al));
u256_push(ins, u256_mask128());
u256_bop(ins, BinaryOperator::BitAnd);
ins.push(Instruction::LoadLocal(bl));
u256_push(ins, u256_mask128());
u256_bop(ins, BinaryOperator::BitAnd);
u256_bop(ins, BinaryOperator::Sub);
ins.push(Instruction::StoreLocal(lo));
emit_u256_hi_limb(ins, al);
emit_u256_hi_limb(ins, bl);
u256_bop(ins, BinaryOperator::Sub);
ins.push(Instruction::LoadLocal(lo));
u256_push(ins, BigInt::from(128u32));
u256_bop(ins, BinaryOperator::Shr);
u256_bop(ins, BinaryOperator::Add);
emit_u256_combine_limbs(ins, lo);
}
fn emit_u256_hi_limb(ins: &mut Vec<Instruction>, loc: usize) {
ins.push(Instruction::LoadLocal(loc));
u256_push(ins, BigInt::from(128u32));
u256_bop(ins, BinaryOperator::Shr);
u256_push(ins, u256_mask128());
u256_bop(ins, BinaryOperator::BitAnd);
}
fn emit_u256_combine_limbs(ins: &mut Vec<Instruction>, lo: usize) {
u256_push(ins, u256_mask128());
u256_bop(ins, BinaryOperator::BitAnd);
u256_push(ins, u256_bias127());
u256_bop(ins, BinaryOperator::BitXor);
u256_push(ins, u256_bias127());
u256_bop(ins, BinaryOperator::Sub);
u256_push(ins, BigInt::from(128u32));
u256_bop(ins, BinaryOperator::Shl);
ins.push(Instruction::LoadLocal(lo));
u256_push(ins, u256_mask128());
u256_bop(ins, BinaryOperator::BitAnd);
u256_bop(ins, BinaryOperator::Add);
}
fn emit_u256_mul_columns_ir(ctx: &mut LoweringContext, ins: &mut Vec<Instruction>) -> Vec<usize> {
let s = ctx.u256_scratch_locals(15);
ins.push(Instruction::StoreLocal(s[14]));
ins.push(Instruction::StoreLocal(s[13]));
for i in 0..4usize {
ins.push(Instruction::LoadLocal(s[13]));
if i > 0 {
u256_push(ins, BigInt::from(64u32 * i as u32));
u256_bop(ins, BinaryOperator::Shr);
}
u256_push(ins, u256_mask64());
u256_bop(ins, BinaryOperator::BitAnd);
ins.push(Instruction::StoreLocal(s[i]));
}
for j in 0..4usize {
ins.push(Instruction::LoadLocal(s[14]));
if j > 0 {
u256_push(ins, BigInt::from(64u32 * j as u32));
u256_bop(ins, BinaryOperator::Shr);
}
u256_push(ins, u256_mask64());
u256_bop(ins, BinaryOperator::BitAnd);
ins.push(Instruction::StoreLocal(s[4 + j]));
}
u256_push(ins, BigInt::zero());
ins.push(Instruction::StoreLocal(s[8]));
for k in 0..4usize {
ins.push(Instruction::LoadLocal(s[8]));
for i in 0..=k {
let j = k - i;
ins.push(Instruction::LoadLocal(s[i]));
ins.push(Instruction::LoadLocal(s[4 + j]));
u256_bop(ins, BinaryOperator::Mul);
u256_bop(ins, BinaryOperator::Add);
}
ins.push(Instruction::Dup);
u256_push(ins, u256_mask64());
u256_bop(ins, BinaryOperator::BitAnd);
ins.push(Instruction::StoreLocal(s[9 + k]));
u256_push(ins, BigInt::from(64u32));
u256_bop(ins, BinaryOperator::Shr);
ins.push(Instruction::StoreLocal(s[8]));
}
s
}
fn emit_u256_mul_build_result_ir(ins: &mut Vec<Instruction>, s: &[usize]) {
ins.push(Instruction::LoadLocal(s[9]));
ins.push(Instruction::LoadLocal(s[10]));
u256_push(ins, BigInt::from(64u32));
u256_bop(ins, BinaryOperator::Shl);
u256_bop(ins, BinaryOperator::Add);
ins.push(Instruction::StoreLocal(s[13]));
ins.push(Instruction::LoadLocal(s[11]));
ins.push(Instruction::LoadLocal(s[12]));
u256_push(ins, BigInt::from(64u32));
u256_bop(ins, BinaryOperator::Shl);
u256_bop(ins, BinaryOperator::Add);
u256_push(ins, u256_bias127());
u256_bop(ins, BinaryOperator::BitXor);
u256_push(ins, u256_bias127());
u256_bop(ins, BinaryOperator::Sub);
u256_push(ins, BigInt::from(128u32));
u256_bop(ins, BinaryOperator::Shl);
ins.push(Instruction::LoadLocal(s[13]));
u256_bop(ins, BinaryOperator::Add);
}
fn emit_u256_unchecked_mul_ir(ctx: &mut LoweringContext, ins: &mut Vec<Instruction>) {
let s = emit_u256_mul_columns_ir(ctx, ins);
emit_u256_mul_build_result_ir(ins, &s);
}
fn emit_u256_checked_arith(
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
operator: BinaryOperator,
) {
match operator {
BinaryOperator::Add => {
emit_u256_unchecked_add_ir(ctx, instructions); let s = ctx.u256_scratch_locals(3);
instructions.push(Instruction::StoreLocal(s[2])); instructions.push(Instruction::LoadLocal(s[2]));
instructions.push(Instruction::LoadLocal(s[0])); emit_u256_unsigned_compare(instructions, BinaryOperator::Lt); let done = ctx.next_label();
instructions.push(Instruction::JumpIf { target: done });
emit_panic(0x11, instructions);
instructions.push(Instruction::Label(done));
instructions.push(Instruction::LoadLocal(s[2]));
}
BinaryOperator::Sub => {
let s = ctx.u256_scratch_locals(3);
instructions.push(Instruction::StoreLocal(s[1])); instructions.push(Instruction::StoreLocal(s[0])); instructions.push(Instruction::LoadLocal(s[0]));
instructions.push(Instruction::LoadLocal(s[1]));
emit_u256_unsigned_compare(instructions, BinaryOperator::Lt); let safe = ctx.next_label();
instructions.push(Instruction::JumpIf { target: safe });
emit_panic(0x11, instructions);
instructions.push(Instruction::Label(safe));
instructions.push(Instruction::LoadLocal(s[0]));
instructions.push(Instruction::LoadLocal(s[1]));
emit_u256_unchecked_sub_ir(ctx, instructions); }
BinaryOperator::Mul => {
let s = emit_u256_mul_columns_ir(ctx, instructions);
instructions.push(Instruction::LoadLocal(s[8])); for (i, j) in [(1usize, 3usize), (2, 2), (3, 1), (2, 3), (3, 2), (3, 3)] {
instructions.push(Instruction::LoadLocal(s[i]));
instructions.push(Instruction::LoadLocal(s[4 + j]));
u256_bop(instructions, BinaryOperator::Mul);
u256_bop(instructions, BinaryOperator::Add);
}
let no_overflow = ctx.next_label();
instructions.push(Instruction::JumpIf { target: no_overflow }); emit_panic(0x11, instructions);
instructions.push(Instruction::Label(no_overflow));
emit_u256_mul_build_result_ir(instructions, &s);
}
_ => unreachable!("emit_u256_checked_arith only handles Add/Sub/Mul"),
}
}
fn emit_checked_arith_guard(
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
operator: BinaryOperator,
) {
if matches!(
operator,
BinaryOperator::Add | BinaryOperator::Sub | BinaryOperator::Mul
) {
emit_u256_checked_arith(ctx, instructions, operator);
return;
}
let tmp_id = ctx.next_label();
let lhs_local = ctx.allocate_local(format!("__arith_lhs_{tmp_id}"), None);
let rhs_local = ctx.allocate_local(format!("__arith_rhs_{tmp_id}"), None);
let result_local = ctx.allocate_local(format!("__arith_res_{tmp_id}"), None);
instructions.push(Instruction::StoreLocal(rhs_local));
instructions.push(Instruction::StoreLocal(lhs_local));
let done_label = ctx.next_label();
match operator {
BinaryOperator::Add => {
instructions.push(Instruction::LoadLocal(lhs_local));
instructions.push(Instruction::LoadLocal(rhs_local));
instructions.push(Instruction::BinaryOp(BinaryOperator::Add));
instructions.push(Instruction::StoreLocal(result_local));
instructions.push(Instruction::LoadLocal(result_local));
instructions.push(Instruction::Convert { target: ConvertTarget::ByteArray });
instructions.push(Instruction::GetSize);
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(32u64))));
instructions.push(Instruction::BinaryOp(BinaryOperator::Gt));
instructions.push(Instruction::JumpIf { target: done_label });
emit_panic(0x11, instructions);
}
BinaryOperator::Sub => {
instructions.push(Instruction::LoadLocal(rhs_local));
instructions.push(Instruction::LoadLocal(lhs_local));
instructions.push(Instruction::BinaryOp(BinaryOperator::Gt));
let safe_label = ctx.next_label();
instructions.push(Instruction::JumpIf {
target: safe_label,
});
emit_panic(0x11, instructions);
instructions.push(Instruction::Label(safe_label));
instructions.push(Instruction::LoadLocal(lhs_local));
instructions.push(Instruction::LoadLocal(rhs_local));
instructions.push(Instruction::BinaryOp(BinaryOperator::Sub));
instructions.push(Instruction::StoreLocal(result_local));
}
BinaryOperator::Mul => {
instructions.push(Instruction::LoadLocal(lhs_local));
instructions.push(Instruction::LoadLocal(rhs_local));
instructions.push(Instruction::BinaryOp(BinaryOperator::Mul));
instructions.push(Instruction::StoreLocal(result_local));
instructions.push(Instruction::LoadLocal(result_local));
instructions.push(Instruction::Convert { target: ConvertTarget::ByteArray });
instructions.push(Instruction::GetSize);
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(32u64))));
instructions.push(Instruction::BinaryOp(BinaryOperator::Gt));
instructions.push(Instruction::JumpIf { target: done_label });
emit_panic(0x11, instructions);
}
_ => {
instructions.push(Instruction::LoadLocal(lhs_local));
instructions.push(Instruction::LoadLocal(rhs_local));
instructions.push(Instruction::BinaryOp(operator));
instructions.push(Instruction::StoreLocal(result_local));
}
}
instructions.push(Instruction::Label(done_label));
instructions.push(Instruction::LoadLocal(result_local));
}
fn emit_truncate_narrow_unsigned(instructions: &mut Vec<Instruction>, bits: u16) {
let mask = (BigInt::one() << bits as usize) - BigInt::one();
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(mask)));
instructions.push(Instruction::BinaryOp(BinaryOperator::BitAnd));
}
fn emit_truncate_narrow_signed(
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
bits: u16,
) {
let bits = bits as usize;
let mask = (BigInt::one() << bits) - BigInt::one();
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(mask)));
instructions.push(Instruction::BinaryOp(BinaryOperator::BitAnd));
let tmp_id = ctx.next_label();
let value_local = ctx.allocate_local(format!("__narrow_trunc_{tmp_id}"), None);
instructions.push(Instruction::StoreLocal(value_local));
let sign_bit = BigInt::one() << (bits.saturating_sub(1));
let modulus = BigInt::one() << bits;
let positive_label = ctx.next_label();
let end_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(value_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(sign_bit)));
instructions.push(Instruction::BinaryOp(BinaryOperator::Ge));
instructions.push(Instruction::JumpIf {
target: positive_label,
});
instructions.push(Instruction::LoadLocal(value_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(modulus)));
instructions.push(Instruction::BinaryOp(BinaryOperator::Sub));
instructions.push(Instruction::Jump { target: end_label });
instructions.push(Instruction::Label(positive_label));
instructions.push(Instruction::LoadLocal(value_local));
instructions.push(Instruction::Label(end_label));
}
fn should_truncate_unchecked_narrow_u(
left: &Expression,
right: &Expression,
ctx: &LoweringContext,
operator: BinaryOperator,
) -> Option<u16> {
if !ctx.in_unchecked_block() {
return None;
}
if !matches!(
operator,
BinaryOperator::Add | BinaryOperator::Sub | BinaryOperator::Mul
) {
return None;
}
if is_literal_number(left) && is_literal_number(right) {
return None;
}
if is_typed_uint256(left, ctx) || is_typed_uint256(right, ctx) {
return None;
}
if is_int256_operand(left, ctx) || is_int256_operand(right, ctx) {
return None;
}
narrow_unsigned_bits(left, right, ctx)
}
fn should_truncate_unchecked_narrow_i(
left: &Expression,
right: &Expression,
ctx: &LoweringContext,
operator: BinaryOperator,
) -> Option<u16> {
if !ctx.in_unchecked_block() {
return None;
}
if !matches!(
operator,
BinaryOperator::Add | BinaryOperator::Sub | BinaryOperator::Mul
) {
return None;
}
if is_literal_number(left) && is_literal_number(right) {
return None;
}
if is_typed_uint256(left, ctx) || is_typed_uint256(right, ctx) {
return None;
}
if is_int256_operand(left, ctx) || is_int256_operand(right, ctx) {
return None;
}
narrow_signed_bits(left, right, ctx)
}
fn shl_narrow_truncation(
left: &Expression,
ctx: &LoweringContext,
operator: BinaryOperator,
) -> Option<(u16, bool)> {
if !matches!(operator, BinaryOperator::Shl) {
return None;
}
match infer_type_from_expression(left, ctx) {
Some(ValueType::Integer { signed, bits }) if matches!(bits, 8 | 16 | 32 | 64 | 128) => {
Some((bits, signed))
}
_ => None,
}
}
fn emit_u256_unsigned_compare(instructions: &mut Vec<Instruction>, operator: BinaryOperator) {
let sign_bit: BigInt = BigInt::one() << 255usize; instructions.push(Instruction::PushLiteral(LiteralValue::Integer(sign_bit.clone())));
instructions.push(Instruction::BinaryOp(BinaryOperator::BitXor)); instructions.push(Instruction::Swap); instructions.push(Instruction::PushLiteral(LiteralValue::Integer(sign_bit)));
instructions.push(Instruction::BinaryOp(BinaryOperator::BitXor)); instructions.push(Instruction::Swap); instructions.push(Instruction::BinaryOp(operator)); }
fn emit_u256_logical_shr_ir(ctx: &mut LoweringContext, instructions: &mut Vec<Instruction>) {
let scratch = ctx.u256_scratch_locals(2);
let n_local = scratch[0];
let a_local = scratch[1];
instructions.push(Instruction::StoreLocal(n_local)); instructions.push(Instruction::StoreLocal(a_local));
let nonzero_label = ctx.next_label();
let end_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(n_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::BinaryOp(BinaryOperator::Eq));
instructions.push(Instruction::JumpIf {
target: nonzero_label,
});
instructions.push(Instruction::LoadLocal(a_local));
instructions.push(Instruction::Jump { target: end_label });
instructions.push(Instruction::Label(nonzero_label));
let max_int256: BigInt = (BigInt::one() << 255usize) - BigInt::one(); instructions.push(Instruction::LoadLocal(a_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::one())));
instructions.push(Instruction::BinaryOp(BinaryOperator::Shr)); instructions.push(Instruction::PushLiteral(LiteralValue::Integer(max_int256)));
instructions.push(Instruction::BinaryOp(BinaryOperator::BitAnd)); instructions.push(Instruction::LoadLocal(n_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::one())));
instructions.push(Instruction::BinaryOp(BinaryOperator::Sub)); instructions.push(Instruction::BinaryOp(BinaryOperator::Shr)); instructions.push(Instruction::Label(end_label));
}
fn emit_u256_divmod_ir(
ctx: &mut LoweringContext,
ins: &mut Vec<Instruction>,
want_remainder: bool,
) {
let s = ctx.u256_scratch_locals(15);
let (a, b, q, r, m, t, rem) = (s[8], s[9], s[10], s[11], s[12], s[13], s[14]);
let max_int256 = (BigInt::one() << 255usize) - BigInt::one();
ins.push(Instruction::StoreLocal(b));
ins.push(Instruction::StoreLocal(a));
let big_b = ctx.next_label();
let done = ctx.next_label();
ins.push(Instruction::LoadLocal(b));
u256_push(ins, BigInt::zero());
u256_bop(ins, BinaryOperator::Ge);
ins.push(Instruction::JumpIf { target: big_b });
ins.push(Instruction::LoadLocal(a));
u256_push(ins, BigInt::one());
u256_bop(ins, BinaryOperator::Shr);
u256_push(ins, max_int256.clone());
u256_bop(ins, BinaryOperator::BitAnd);
ins.push(Instruction::StoreLocal(m));
ins.push(Instruction::LoadLocal(m));
ins.push(Instruction::LoadLocal(b));
u256_bop(ins, BinaryOperator::Div);
ins.push(Instruction::StoreLocal(t));
ins.push(Instruction::LoadLocal(m));
ins.push(Instruction::LoadLocal(b));
u256_bop(ins, BinaryOperator::Mod);
ins.push(Instruction::StoreLocal(rem));
ins.push(Instruction::LoadLocal(t));
ins.push(Instruction::LoadLocal(t));
emit_u256_unchecked_add_ir(ctx, ins);
ins.push(Instruction::StoreLocal(q));
ins.push(Instruction::LoadLocal(rem));
ins.push(Instruction::LoadLocal(rem));
emit_u256_unchecked_add_ir(ctx, ins);
ins.push(Instruction::LoadLocal(a));
u256_push(ins, BigInt::one());
u256_bop(ins, BinaryOperator::BitAnd);
u256_bop(ins, BinaryOperator::Add);
ins.push(Instruction::StoreLocal(r));
ins.push(Instruction::LoadLocal(r));
ins.push(Instruction::LoadLocal(b));
emit_u256_unsigned_compare(ins, BinaryOperator::Ge);
let skip_corr = ctx.next_label();
ins.push(Instruction::JumpIf { target: skip_corr });
ins.push(Instruction::LoadLocal(q));
u256_push(ins, BigInt::one());
u256_bop(ins, BinaryOperator::Add);
ins.push(Instruction::StoreLocal(q));
ins.push(Instruction::LoadLocal(r));
ins.push(Instruction::LoadLocal(b));
emit_u256_unchecked_sub_ir(ctx, ins);
ins.push(Instruction::StoreLocal(r));
ins.push(Instruction::Label(skip_corr));
ins.push(Instruction::Jump { target: done });
ins.push(Instruction::Label(big_b));
ins.push(Instruction::LoadLocal(a));
ins.push(Instruction::LoadLocal(b));
emit_u256_unsigned_compare(ins, BinaryOperator::Ge);
ins.push(Instruction::StoreLocal(q));
ins.push(Instruction::LoadLocal(q));
let q_zero = ctx.next_label();
let big_done = ctx.next_label();
ins.push(Instruction::JumpIf { target: q_zero });
ins.push(Instruction::LoadLocal(a));
ins.push(Instruction::LoadLocal(b));
emit_u256_unchecked_sub_ir(ctx, ins);
ins.push(Instruction::StoreLocal(r));
ins.push(Instruction::Jump { target: big_done });
ins.push(Instruction::Label(q_zero));
ins.push(Instruction::LoadLocal(a));
ins.push(Instruction::StoreLocal(r));
ins.push(Instruction::Label(big_done));
ins.push(Instruction::Label(done));
ins.push(Instruction::LoadLocal(if want_remainder { r } else { q }));
}
fn emit_arith_with_overflow_ladder(
left: &Expression,
right: &Expression,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
operator: BinaryOperator,
allow_unchecked_u256_widen: bool,
) {
if matches!(
operator,
BinaryOperator::Lt | BinaryOperator::Gt | BinaryOperator::Le | BinaryOperator::Ge
) && (is_typed_uint256(left, ctx) || is_typed_uint256(right, ctx))
&& !is_int256_operand(left, ctx)
&& !is_int256_operand(right, ctx)
{
emit_u256_unsigned_compare(instructions, operator);
return;
}
if matches!(operator, BinaryOperator::Div | BinaryOperator::Mod)
&& (is_typed_uint256(left, ctx) || is_typed_uint256(right, ctx))
&& !is_int256_operand(left, ctx)
&& !is_int256_operand(right, ctx)
{
emit_u256_divmod_ir(ctx, instructions, matches!(operator, BinaryOperator::Mod));
return;
}
if matches!(operator, BinaryOperator::Shr) && is_typed_uint256(left, ctx) {
emit_u256_logical_shr_ir(ctx, instructions);
return;
}
let emit_guard = should_emit_u256_arith_guard(left, right, ctx, operator);
let emit_i256_guard = !emit_guard && should_emit_i256_arith_guard(left, right, ctx, operator);
let emit_narrow_u_bits = if !emit_guard && !emit_i256_guard {
should_emit_narrow_u_arith_guard(left, right, ctx, operator)
} else {
None
};
let emit_narrow_i_bits = if !emit_guard && !emit_i256_guard && emit_narrow_u_bits.is_none() {
should_emit_narrow_i_arith_guard(left, right, ctx, operator)
} else {
None
};
let emit_unchecked_u256_widen = allow_unchecked_u256_widen
&& !emit_guard
&& !emit_i256_guard
&& emit_narrow_u_bits.is_none()
&& emit_narrow_i_bits.is_none()
&& should_widen_unchecked_u256(left, right, ctx, operator);
let unchecked_narrow_u = should_truncate_unchecked_narrow_u(left, right, ctx, operator);
let unchecked_narrow_i = if unchecked_narrow_u.is_none() {
should_truncate_unchecked_narrow_i(left, right, ctx, operator)
} else {
None
};
let shl_trunc = shl_narrow_truncation(left, ctx, operator);
let div_narrow_i = if matches!(operator, BinaryOperator::Div) && is_narrow_result(left, right, ctx)
{
narrow_signed_bits(left, right, ctx)
} else {
None
};
if emit_guard {
emit_checked_arith_guard(ctx, instructions, operator);
} else if emit_i256_guard {
emit_checked_arith_guard_i256(ctx, instructions, operator);
} else if let Some(bits) = emit_narrow_u_bits {
emit_checked_arith_guard_narrow_u(ctx, instructions, operator, bits);
} else if let Some(bits) = emit_narrow_i_bits {
emit_checked_arith_guard_narrow_i(ctx, instructions, operator, bits);
} else if emit_unchecked_u256_widen {
match operator {
BinaryOperator::Add => emit_u256_unchecked_add_ir(ctx, instructions),
BinaryOperator::Sub => emit_u256_unchecked_sub_ir(ctx, instructions),
BinaryOperator::Mul => emit_u256_unchecked_mul_ir(ctx, instructions),
_ => {
emit_widen_both_u256_unsigned(instructions);
instructions.push(Instruction::BinaryOp(operator));
emit_truncate_u256(instructions);
}
}
} else if let Some(bits) = unchecked_narrow_u {
instructions.push(Instruction::BinaryOp(operator));
emit_truncate_narrow_unsigned(instructions, bits);
} else if let Some(bits) = unchecked_narrow_i {
instructions.push(Instruction::BinaryOp(operator));
emit_truncate_narrow_signed(ctx, instructions, bits);
} else if let Some((bits, signed)) = shl_trunc {
instructions.push(Instruction::BinaryOp(operator));
if signed {
emit_truncate_narrow_signed(ctx, instructions, bits);
} else {
emit_truncate_narrow_unsigned(instructions, bits);
}
} else if let Some(bits) = div_narrow_i {
instructions.push(Instruction::BinaryOp(operator));
if ctx.in_unchecked_block() {
emit_truncate_narrow_signed(ctx, instructions, bits);
} else {
let int_max = (BigInt::one() << (bits as usize - 1)) - BigInt::one();
let tmp_id = ctx.next_label();
let result_local = ctx.allocate_local(format!("__divovf_{tmp_id}"), None);
instructions.push(Instruction::StoreLocal(result_local));
let done = ctx.next_label();
instructions.push(Instruction::LoadLocal(result_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(int_max)));
instructions.push(Instruction::BinaryOp(BinaryOperator::Gt));
instructions.push(Instruction::JumpIf { target: done });
emit_panic(0x11, instructions);
instructions.push(Instruction::Label(done));
instructions.push(Instruction::LoadLocal(result_local));
}
} else {
instructions.push(Instruction::BinaryOp(operator));
}
}
fn lower_binary_expr(
left: &Expression,
right: &Expression,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
operator: BinaryOperator,
) -> bool {
if let Some(result) =
lower_bytes_eq_hex_number_literal(left, right, ctx, instructions, operator)
{
return result;
}
if !lower_expression(left, ctx, instructions) {
return false;
}
if !lower_expression(right, ctx, instructions) {
return false;
}
if matches!(operator, BinaryOperator::Div | BinaryOperator::Mod) {
let tmp_id = ctx.next_label();
let rhs_local = ctx.allocate_local(format!("__div_rhs_{tmp_id}"), None);
instructions.push(Instruction::StoreLocal(rhs_local));
let ok_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(rhs_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::BinaryOp(BinaryOperator::Eq));
instructions.push(Instruction::JumpIf { target: ok_label });
emit_panic(0x12, instructions);
instructions.push(Instruction::Label(ok_label));
instructions.push(Instruction::LoadLocal(rhs_local));
}
emit_arith_with_overflow_ladder(left, right, ctx, instructions, operator, true);
true
}