mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-06-13 14:11:05 +00:00
Strip minsize attribute from functions with large div/rem (#390)
That's a workaround to avoid LLVM backend crash while selecting instruction for 256-bit integer division. The workaround needs to be removed after we switch to newest inkwell that has a fix in RISC-V backend --------- Signed-off-by: kvp <mammal_windier8j@icloud.com>
This commit is contained in:
@@ -8,6 +8,7 @@ use inkwell::debug_info::AsDIScope;
|
|||||||
use inkwell::debug_info::DIScope;
|
use inkwell::debug_info::DIScope;
|
||||||
use inkwell::types::BasicType;
|
use inkwell::types::BasicType;
|
||||||
use inkwell::values::BasicValue;
|
use inkwell::values::BasicValue;
|
||||||
|
use inkwell::values::InstructionOpcode;
|
||||||
use revive_solc_json_interface::PolkaVMDefaultHeapMemorySize;
|
use revive_solc_json_interface::PolkaVMDefaultHeapMemorySize;
|
||||||
use revive_solc_json_interface::PolkaVMDefaultStackMemorySize;
|
use revive_solc_json_interface::PolkaVMDefaultStackMemorySize;
|
||||||
use revive_solc_json_interface::SolcStandardJsonInputSettingsPolkaVMMemory;
|
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
|
self.debug_config
|
||||||
.dump_llvm_ir_optimized(contract_path, self.module())?;
|
.dump_llvm_ir_optimized(contract_path, self.module())?;
|
||||||
|
|
||||||
@@ -1394,4 +1406,38 @@ impl<'ctx> Context<'ctx> {
|
|||||||
name.to_string()
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
use crate::tests::cli::utils::{
|
use crate::tests::cli::utils::{
|
||||||
self, assert_command_failure, assert_command_success, assert_equal_exit_codes, execute_resolc,
|
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'];
|
const LEVELS: &[char] = &['0', '1', '2', '3', 's', 'z'];
|
||||||
@@ -56,3 +57,26 @@ fn disable_solc_optimzer() {
|
|||||||
|
|
||||||
assert_ne!(enabled.stdout, disabled.stdout);
|
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}`."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ pub const YUL_MEMSET_CONTRACT_PATH: &str = "src/tests/data/yul/memset.yul";
|
|||||||
pub const STANDARD_JSON_CONTRACTS_PATH: &str =
|
pub const STANDARD_JSON_CONTRACTS_PATH: &str =
|
||||||
"src/tests/data/standard_json/solidity_contracts.json";
|
"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.
|
/// The `resolc` YUL mode flag.
|
||||||
pub const RESOLC_YUL_FLAG: &str = "--yul";
|
pub const RESOLC_YUL_FLAG: &str = "--yul";
|
||||||
/// The `--yul` option was deprecated in Solidity 0.8.27 in favor of `--strict-assembly`.
|
/// The `--yul` option was deprecated in Solidity 0.8.27 in favor of `--strict-assembly`.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user