diff --git a/crates/llvm-context/src/polkavm/context/mod.rs b/crates/llvm-context/src/polkavm/context/mod.rs index 8da1dc3..4cc8eb9 100644 --- a/crates/llvm-context/src/polkavm/context/mod.rs +++ b/crates/llvm-context/src/polkavm/context/mod.rs @@ -8,6 +8,7 @@ use inkwell::debug_info::AsDIScope; use inkwell::debug_info::DIScope; use inkwell::types::BasicType; use inkwell::values::BasicValue; +use inkwell::values::InstructionOpcode; use revive_solc_json_interface::PolkaVMDefaultHeapMemorySize; use revive_solc_json_interface::PolkaVMDefaultStackMemorySize; use revive_solc_json_interface::SolcStandardJsonInputSettingsPolkaVMMemory; @@ -288,6 +289,17 @@ impl<'ctx> Context<'ctx> { ) })?; + // Remove MinSize on functions that perform large integer div/rem to + // avoid compiler crash that happens when large integer div/rem by + // power-of-2 are not being expanded by ExpandLargeIntDivRem pass as + // it expects peephole from DAGCombine, which doesn't happen due to the + // MinSize attribute being set on the function. + // NOTE: As soon as it strips attribute from a function where large + // integer div/rem is used, it's crucial to call it after inlining. + // TODO: Remove this once LLVM fix is backported to LLVM 21 and we + // switch to corresponding inkwell version. + self.strip_minsize_for_divrem(); + self.debug_config .dump_llvm_ir_optimized(contract_path, self.module())?; @@ -1394,4 +1406,38 @@ impl<'ctx> Context<'ctx> { name.to_string() } } + + /// Scans all functions in the module and removes the `MinSize` attribute + /// if the function contains any large sdiv, udiv, srem, urem instructions with either unknown + /// NOTE: The check here could be relaxed by checking denominator: if the denominator is + /// unknown or is a power-of-2 constant, then need to strip the `minsize` attribute; otherwise + /// instruction can be ignored as backend will expand it correctly. + fn strip_minsize_for_divrem(&self) { + self.module().get_functions().for_each(|func| { + let has_divrem = func.get_basic_block_iter().any(|b| { + b.get_instructions().any(|inst| match inst.get_opcode() { + InstructionOpcode::SDiv + | InstructionOpcode::UDiv + | InstructionOpcode::SRem + | InstructionOpcode::URem => { + inst.get_type().into_int_type().get_bit_width() >= 256 + } + _ => false, + }) + }); + if has_divrem + && func + .get_enum_attribute( + inkwell::attributes::AttributeLoc::Function, + Attribute::MinSize as u32, + ) + .is_some() + { + func.remove_enum_attribute( + inkwell::attributes::AttributeLoc::Function, + Attribute::MinSize as u32, + ); + } + }); + } } diff --git a/crates/resolc/src/tests/cli/optimization.rs b/crates/resolc/src/tests/cli/optimization.rs index 2e56749..29f6186 100644 --- a/crates/resolc/src/tests/cli/optimization.rs +++ b/crates/resolc/src/tests/cli/optimization.rs @@ -2,7 +2,8 @@ use crate::tests::cli::utils::{ self, assert_command_failure, assert_command_success, assert_equal_exit_codes, execute_resolc, - execute_solc, RESOLC_YUL_FLAG, SOLIDITY_CONTRACT_PATH, YUL_MEMSET_CONTRACT_PATH, + execute_solc, RESOLC_YUL_FLAG, SOLIDITY_CONTRACT_PATH, SOLIDITY_LARGE_DIV_REM_CONTRACT_PATH, + YUL_MEMSET_CONTRACT_PATH, }; const LEVELS: &[char] = &['0', '1', '2', '3', 's', 'z']; @@ -56,3 +57,26 @@ fn disable_solc_optimzer() { assert_ne!(enabled.stdout, disabled.stdout); } + +#[test] +fn test_large_div_rem_expansion() { + for level in LEVELS { + let optimization_argument = format!("-O{level}"); + let arguments = &[SOLIDITY_LARGE_DIV_REM_CONTRACT_PATH, &optimization_argument]; + let resolc_result = utils::execute_resolc(arguments); + assert!( + resolc_result.success, + "Providing the level `{optimization_argument}` should succeed with exit code {}, got {}.\nDetails: {}", + revive_common::EXIT_CODE_SUCCESS, + resolc_result.code, + resolc_result.stderr + ); + + assert!( + resolc_result + .stderr + .contains("Compiler run successful. No output requested"), + "Expected the output to contain a success message when providing the level `{optimization_argument}`." + ); + } +} diff --git a/crates/resolc/src/tests/cli/utils.rs b/crates/resolc/src/tests/cli/utils.rs index c5b970e..b9e594b 100644 --- a/crates/resolc/src/tests/cli/utils.rs +++ b/crates/resolc/src/tests/cli/utils.rs @@ -22,6 +22,10 @@ pub const YUL_MEMSET_CONTRACT_PATH: &str = "src/tests/data/yul/memset.yul"; pub const STANDARD_JSON_CONTRACTS_PATH: &str = "src/tests/data/standard_json/solidity_contracts.json"; +/// The simple Solidity contract containing i256 divisions and remains that should be compiled +/// correctly +pub const SOLIDITY_LARGE_DIV_REM_CONTRACT_PATH: &str = "src/tests/data/solidity/large_div_rem.sol"; + /// The `resolc` YUL mode flag. pub const RESOLC_YUL_FLAG: &str = "--yul"; /// The `--yul` option was deprecated in Solidity 0.8.27 in favor of `--strict-assembly`. diff --git a/crates/resolc/src/tests/data/solidity/large_div_rem.sol b/crates/resolc/src/tests/data/solidity/large_div_rem.sol new file mode 100644 index 0000000..6b8e680 --- /dev/null +++ b/crates/resolc/src/tests/data/solidity/large_div_rem.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8; + +contract LargeDivRem { + function rem_2(int n) public pure returns (int q) { + assembly { + q := smod(n, 2) + } + } + + function div_2(int n) public pure returns (int q) { + assembly { + q := sdiv(n, 2) + } + } + + function rem_7(int n) public pure returns (int q) { + assembly { + q := smod(n, 7) + } + } + + function div_7(int n) public pure returns (int q) { + assembly { + q := sdiv(n, 2) + } + } + + function rem_k(int n, int k) public pure returns (int q) { + assembly { + q := smod(n, k) + } + } + + function div_k(int n, int k) public pure returns (int q) { + assembly { + q := sdiv(n, k) + } + } +}