Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ pub use sonatina::{
pub use yul::{
EmitModuleError, ExpectedRevert, TestMetadata, TestModuleOutput, YulError, emit_ingot_yul,
emit_ingot_yul_with_layout, emit_module_yul, emit_module_yul_with_layout, emit_test_module_yul,
emit_test_module_yul_with_layout,
emit_test_module_yul_with_layout, parse_expected_revert,
};
90 changes: 70 additions & 20 deletions crates/codegen/src/sonatina/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@
//!
//! Contains all `lower_*` free functions that operate on `LowerCtx`.

/// Solidity `Panic(uint256)` selector: `keccak256("Panic(uint256)")[:4]`.
///
/// Stored as a 32-byte big-endian word with the selector left-aligned,
/// ready for an EVM `MSTORE` at offset 0.
const PANIC_SELECTOR_WORD: [u8; 32] = {
let mut bytes = [0u8; 32];
bytes[0] = 0x4e;
bytes[1] = 0x48;
bytes[2] = 0x7b;
bytes[3] = 0x71;
bytes
};

/// Panic code for arithmetic overflow / underflow.
const PANIC_CODE_OVERFLOW: u64 = 0x11;

/// Panic code for division or modulo by zero.
const PANIC_CODE_DIVISION_BY_ZERO: u64 = 0x12;

/// Panic code for array index out of bounds.
const PANIC_CODE_INDEX_OUT_OF_BOUNDS: u64 = 0x32;

use common::ingot::IngotKind;
use driver::DriverDataBase;
use hir::analysis::ty::adt_def::AdtRef;
Expand Down Expand Up @@ -3834,7 +3856,8 @@ fn lower_place_address_gep<'db, C: sonatina_ir::func_cursor::FuncCursor>(
if let Some(len) = arr_len
&& *idx >= len
{
let revert_block = ensure_overflow_revert_block(ctx)?;
let revert_block =
ensure_panic_revert_block(ctx, PANIC_CODE_INDEX_OUT_OF_BOUNDS)?;
ctx.fb
.insert_inst_no_result(Jump::new(ctx.is, revert_block));
let unreachable_block = ctx.fb.append_block();
Expand All @@ -3849,7 +3872,7 @@ fn lower_place_address_gep<'db, C: sonatina_ir::func_cursor::FuncCursor>(
let in_bounds =
ctx.fb.insert_inst(Lt::new(ctx.is, val, len_val), Type::I1);
let oob = ctx.fb.insert_inst(IsZero::new(ctx.is, in_bounds), Type::I1);
emit_overflow_revert(ctx, oob)?;
emit_panic_revert(ctx, oob, PANIC_CODE_INDEX_OUT_OF_BOUNDS)?;
}
val
}
Expand Down Expand Up @@ -3952,7 +3975,7 @@ fn lower_array_index_with_bounds_check<'db, C: sonatina_ir::func_cursor::FuncCur
match idx_source {
IndexSource::Constant(idx) => {
if *idx >= arr_len {
let revert_block = ensure_overflow_revert_block(ctx)?;
let revert_block = ensure_panic_revert_block(ctx, PANIC_CODE_INDEX_OUT_OF_BOUNDS)?;
ctx.fb
.insert_inst_no_result(Jump::new(ctx.is, revert_block));
let unreachable_block = ctx.fb.append_block();
Expand All @@ -3965,7 +3988,7 @@ fn lower_array_index_with_bounds_check<'db, C: sonatina_ir::func_cursor::FuncCur
let len_val = ctx.fb.make_imm_value(I256::from(arr_len as u64));
let in_bounds = ctx.fb.insert_inst(Lt::new(ctx.is, idx, len_val), Type::I1);
let oob = ctx.fb.insert_inst(IsZero::new(ctx.is, in_bounds), Type::I1);
emit_overflow_revert(ctx, oob)?;
emit_panic_revert(ctx, oob, PANIC_CODE_INDEX_OUT_OF_BOUNDS)?;
Ok(idx)
}
}
Expand Down Expand Up @@ -4047,7 +4070,8 @@ fn lower_place_address_arithmetic<'db, C: sonatina_ir::func_cursor::FuncCursor>(
if let Some(len) = arr_len
&& *idx >= len
{
let revert_block = ensure_overflow_revert_block(ctx)?;
let revert_block =
ensure_panic_revert_block(ctx, PANIC_CODE_INDEX_OUT_OF_BOUNDS)?;
ctx.fb
.insert_inst_no_result(Jump::new(ctx.is, revert_block));
let unreachable_block = ctx.fb.append_block();
Expand Down Expand Up @@ -4075,7 +4099,7 @@ fn lower_place_address_arithmetic<'db, C: sonatina_ir::func_cursor::FuncCursor>(
.fb
.insert_inst(Lt::new(ctx.is, idx_val, len_val), Type::I1);
let oob = ctx.fb.insert_inst(IsZero::new(ctx.is, in_bounds), Type::I1);
emit_overflow_revert(ctx, oob)?;
emit_panic_revert(ctx, oob, PANIC_CODE_INDEX_OUT_OF_BOUNDS)?;
}

// Compute dynamic index offset: idx * stride
Expand Down Expand Up @@ -5068,10 +5092,12 @@ fn bitcast_ptr<'db, C: sonatina_ir::func_cursor::FuncCursor>(
.insert_inst(Bitcast::new(ctx.is, ptr, ptr_ty), ptr_ty)
}

fn ensure_overflow_revert_block<'db, C: sonatina_ir::func_cursor::FuncCursor>(
/// Returns (or lazily creates) a revert block that emits `Panic(uint256)` with the given code.
fn ensure_panic_revert_block<'db, C: sonatina_ir::func_cursor::FuncCursor>(
ctx: &mut LowerCtx<'_, 'db, C>,
panic_code: u64,
) -> Result<BlockId, LowerError> {
if let Some(block) = *ctx.overflow_revert_block {
if let Some(&block) = ctx.panic_revert_blocks.get(&panic_code) {
return Ok(block);
}

Expand All @@ -5081,20 +5107,35 @@ fn ensure_overflow_revert_block<'db, C: sonatina_ir::func_cursor::FuncCursor>(
.ok_or_else(|| LowerError::Internal("missing current block".to_string()))?;
let revert_block = ctx.fb.append_block();
ctx.fb.switch_to_block(revert_block);

// Emit Panic(uint256) revert payload.
// Layout at memory offset 0: [selector 4 bytes][uint256 code 32 bytes] = 36 bytes.
let selector_val = ctx
.fb
.make_imm_value(I256::from_be_bytes(&PANIC_SELECTOR_WORD));
let zero = ctx.fb.make_imm_value(I256::zero());
ctx.fb
.insert_inst_no_result(EvmRevert::new(ctx.is, zero, zero));
.insert_inst_no_result(Mstore::new(ctx.is, zero, selector_val, Type::I256));
let four = ctx.fb.make_imm_value(I256::from(4u64));
let code_val = ctx.fb.make_imm_value(I256::from(panic_code));
ctx.fb
.insert_inst_no_result(Mstore::new(ctx.is, four, code_val, Type::I256));
let thirty_six = ctx.fb.make_imm_value(I256::from(36u64));
ctx.fb
.insert_inst_no_result(EvmRevert::new(ctx.is, zero, thirty_six));

ctx.fb.switch_to_block(origin_block);
*ctx.overflow_revert_block = Some(revert_block);
ctx.panic_revert_blocks.insert(panic_code, revert_block);
Ok(revert_block)
}

/// Emit a conditional branch to the shared overflow revert block if `overflow_flag` (I1) is true.
fn emit_overflow_revert<'db, C: sonatina_ir::func_cursor::FuncCursor>(
/// Emit a conditional branch to a Panic revert block if `overflow_flag` (I1) is true.
fn emit_panic_revert<'db, C: sonatina_ir::func_cursor::FuncCursor>(
ctx: &mut LowerCtx<'_, 'db, C>,
overflow_flag: ValueId,
panic_code: u64,
) -> Result<(), LowerError> {
let revert_block = ensure_overflow_revert_block(ctx)?;
let revert_block = ensure_panic_revert_block(ctx, panic_code)?;
let continue_block = ctx.fb.append_block();
ctx.fb
.insert_inst_no_result(Br::new(ctx.is, overflow_flag, revert_block, continue_block));
Expand Down Expand Up @@ -5244,7 +5285,7 @@ fn lower_checked_intrinsic<'db, C: sonatina_ir::func_cursor::FuncCursor>(
} else {
ctx.fb.insert_uaddo(lhs, rhs)
};
emit_overflow_revert(ctx, overflow)?;
emit_panic_revert(ctx, overflow, PANIC_CODE_OVERFLOW)?;
Ok(raw)
}
CheckedArithmeticOp::Sub => {
Expand All @@ -5263,7 +5304,7 @@ fn lower_checked_intrinsic<'db, C: sonatina_ir::func_cursor::FuncCursor>(
} else {
ctx.fb.insert_usubo(lhs, rhs)
};
emit_overflow_revert(ctx, overflow)?;
emit_panic_revert(ctx, overflow, PANIC_CODE_OVERFLOW)?;
Ok(raw)
}
CheckedArithmeticOp::Mul => {
Expand All @@ -5282,7 +5323,7 @@ fn lower_checked_intrinsic<'db, C: sonatina_ir::func_cursor::FuncCursor>(
} else {
ctx.fb.insert_umulo(lhs, rhs)
};
emit_overflow_revert(ctx, overflow)?;
emit_panic_revert(ctx, overflow, PANIC_CODE_OVERFLOW)?;
Ok(raw)
}
CheckedArithmeticOp::Div => {
Expand All @@ -5297,11 +5338,20 @@ fn lower_checked_intrinsic<'db, C: sonatina_ir::func_cursor::FuncCursor>(
let lhs = lower_checked_operand(ctx.fb, ctx.is, lhs_word, op_ty, signed);
let rhs = lower_checked_operand(ctx.fb, ctx.is, rhs_word, op_ty, signed);
let [raw, overflow] = if signed {
ctx.fb.insert_evm_sdivo(lhs, rhs)
let [raw, overflow] = ctx.fb.insert_evm_sdivo(lhs, rhs);
let zero = ctx.fb.make_imm_value(I256::zero());
let div_by_zero = ctx.fb.insert_inst(Eq::new(ctx.is, rhs, zero), Type::I1);
emit_panic_revert(ctx, div_by_zero, PANIC_CODE_DIVISION_BY_ZERO)?;
[raw, overflow]
} else {
ctx.fb.insert_evm_udivo(lhs, rhs)
};
emit_overflow_revert(ctx, overflow)?;
let panic_code = if signed {
PANIC_CODE_OVERFLOW
} else {
PANIC_CODE_DIVISION_BY_ZERO
};
emit_panic_revert(ctx, overflow, panic_code)?;
Ok(raw)
}
CheckedArithmeticOp::Rem => {
Expand All @@ -5320,7 +5370,7 @@ fn lower_checked_intrinsic<'db, C: sonatina_ir::func_cursor::FuncCursor>(
} else {
ctx.fb.insert_evm_umodo(lhs, rhs)
};
emit_overflow_revert(ctx, overflow)?;
emit_panic_revert(ctx, overflow, PANIC_CODE_DIVISION_BY_ZERO)?;
Ok(raw)
}
CheckedArithmeticOp::Neg => {
Expand All @@ -5339,7 +5389,7 @@ fn lower_checked_intrinsic<'db, C: sonatina_ir::func_cursor::FuncCursor>(
let val_word = lower_value(ctx, *arg)?;
let val = lower_checked_operand(ctx.fb, ctx.is, val_word, op_ty, true);
let [raw, overflow] = ctx.fb.insert_snego(val);
emit_overflow_revert(ctx, overflow)?;
emit_panic_revert(ctx, overflow, PANIC_CODE_OVERFLOW)?;
Ok(raw)
}
}
Expand Down
8 changes: 4 additions & 4 deletions crates/codegen/src/sonatina/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1264,7 +1264,7 @@ impl<'db, 'a> ModuleLowerer<'db, 'a> {

{
let mut const_data_globals = FxHashMap::default();
let mut overflow_revert_block = None;
let mut panic_revert_blocks = FxHashMap::default();
let mut ctx = LowerCtx {
fb: &mut fb,
db: self.db,
Expand All @@ -1286,7 +1286,7 @@ impl<'db, 'a> ModuleLowerer<'db, 'a> {
data_globals: &mut self.data_globals,
data_global_counter: &mut self.data_global_counter,
const_data_globals: &mut const_data_globals,
overflow_revert_block: &mut overflow_revert_block,
panic_revert_blocks: &mut panic_revert_blocks,
};
for (idx, block) in ctx.body.blocks.iter().enumerate() {
let block_id = mir::BasicBlockId(idx as u32);
Expand Down Expand Up @@ -1342,8 +1342,8 @@ pub(super) struct LowerCtx<'a, 'db, C: sonatina_ir::func_cursor::FuncCursor> {
pub(super) data_global_counter: &'a mut usize,
/// Per-function dedupe for constant aggregate payloads.
pub(super) const_data_globals: &'a mut FxHashMap<Vec<u8>, GlobalVariableRef>,
/// Lazily-created shared overflow trap block for checked arithmetic in this function.
pub(super) overflow_revert_block: &'a mut Option<BlockId>,
/// Lazily-created panic revert blocks, keyed by Panic(uint256) code.
pub(super) panic_revert_blocks: &'a mut FxHashMap<u64, BlockId>,
}

impl<'a, 'db, C: sonatina_ir::func_cursor::FuncCursor> LowerCtx<'a, 'db, C> {
Expand Down
13 changes: 5 additions & 8 deletions crates/codegen/src/sonatina/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use sonatina_ir::{
};
use sonatina_verifier::{VerificationLevel, VerifierConfig};

use crate::{ExpectedRevert, OptLevel, TestMetadata, TestModuleOutput};
use crate::{ExpectedRevert, OptLevel, TestMetadata, TestModuleOutput, parse_expected_revert};

use super::{ContractObjectSelection, LowerError, ModuleLowerer};

Expand Down Expand Up @@ -165,18 +165,15 @@ fn collect_tests(
};
let attrs = ItemKind::from(hir_func).attrs(db)?;
let test_attr = attrs.get_attr(db, "test")?;

let expected_revert = if test_attr.has_arg(db, "should_revert") {
Some(ExpectedRevert::Any)
} else {
None
};

let hir_name = hir_func
.name(db)
.to_opt()
.map(|n| n.data(db).to_string())
.unwrap_or_else(|| "<anonymous>".to_string());
let expected_revert = match parse_expected_revert(db, &hir_name, test_attr) {
Ok(expected_revert) => expected_revert,
Err(err) => return Some(Err(LowerError::Unsupported(err))),
};
let initial_balance = match parse_test_balance_arg(db, &hir_name, test_attr) {
Ok(balance) => balance,
Err(err) => return Some(Err(err)),
Expand Down
2 changes: 1 addition & 1 deletion crates/codegen/src/yul/emitter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::yul::errors::YulError;
pub use module::{
ExpectedRevert, TestMetadata, TestModuleOutput, emit_ingot_yul, emit_ingot_yul_with_layout,
emit_module_yul, emit_module_yul_with_layout, emit_test_module_yul,
emit_test_module_yul_with_layout,
emit_test_module_yul_with_layout, parse_expected_revert,
};

mod control_flow;
Expand Down
Loading
Loading