use std::cell::RefCell;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::num::NonZeroU32;
use std::rc::Rc;
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;
use crate::optimizer::settings::Settings as OptimizerSettings;
use crate::optimizer::Optimizer;
use crate::polkavm::DebugConfig;
use crate::target_machine::target::Target;
use crate::target_machine::TargetMachine;
use crate::PolkaVMLoadHeapWordFunction;
use crate::PolkaVMLoadHeapWordNativeFunction;
use crate::PolkaVMSbrkFunction;
use crate::PolkaVMStoreHeapWordFunction;
use crate::PolkaVMStoreHeapWordNativeFunction;
use self::address_space::AddressSpace;
use self::attribute::Attribute;
use self::attribute::MemoryEffect;
use self::build::Build;
use self::code_type::CodeType;
use self::debug_info::DebugInfo;
use self::function::declaration::Declaration as FunctionDeclaration;
use self::function::intrinsics::Intrinsics;
use self::function::llvm_runtime::LLVMRuntime;
use self::function::r#return::Return as FunctionReturn;
use self::function::runtime::revive::Exit;
use self::function::runtime::revive::WordToPointer;
use self::function::Function;
use self::global::Global;
use self::pointer::Pointer;
use self::r#loop::Loop;
use self::runtime::RuntimeFunction;
use self::solidity_data::SolidityData;
use self::yul_data::YulData;
pub mod address_space;
pub mod argument;
pub mod attribute;
pub mod build;
pub mod code_type;
pub mod debug_info;
pub mod function;
pub mod global;
pub mod r#loop;
pub mod pointer;
pub mod runtime;
pub mod solidity_data;
pub mod yul_data;
#[cfg(test)]
mod tests;
const MEMORY_READ_IMPORTS: &[&str] = &[
revive_runtime_api::polkavm_imports::CALL_DATA_SIZE,
revive_runtime_api::polkavm_imports::GAS_LIMIT,
revive_runtime_api::polkavm_imports::RETURNDATASIZE,
];
pub struct Context<'ctx> {
llvm: &'ctx inkwell::context::Context,
builder: inkwell::builder::Builder<'ctx>,
optimizer: Optimizer,
module: inkwell::module::Module<'ctx>,
code_type: Option<CodeType>,
globals: HashMap<String, Global<'ctx>>,
intrinsics: Intrinsics<'ctx>,
llvm_runtime: LLVMRuntime<'ctx>,
functions: HashMap<String, Rc<RefCell<Function<'ctx>>>>,
current_function: Option<Rc<RefCell<Function<'ctx>>>>,
loop_stack: Vec<Loop<'ctx>>,
function_counter: usize,
function_scope: Vec<BTreeMap<String, String>>,
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
debug_info: Option<DebugInfo<'ctx>>,
debug_config: DebugConfig,
solidity_data: Option<SolidityData>,
yul_data: Option<YulData>,
}
impl<'ctx> Context<'ctx> {
const FUNCTIONS_HASHMAP_INITIAL_CAPACITY: usize = 64;
const GLOBALS_HASHMAP_INITIAL_CAPACITY: usize = 4;
const LOOP_STACK_INITIAL_CAPACITY: usize = 16;
fn link_stdlib_module(
llvm: &'ctx inkwell::context::Context,
module: &inkwell::module::Module<'ctx>,
) {
module
.link_in_module(revive_stdlib::module(llvm, "revive_stdlib").unwrap())
.expect("the stdlib module should be linkable");
}
fn link_polkavm_imports(
llvm: &'ctx inkwell::context::Context,
module: &inkwell::module::Module<'ctx>,
) {
module
.link_in_module(
revive_runtime_api::polkavm_imports::module(llvm, "polkavm_imports").unwrap(),
)
.expect("the PolkaVM imports module should be linkable");
for import in revive_runtime_api::polkavm_imports::IMPORTS {
module
.get_function(import)
.unwrap_or_else(|| panic!("{import} import should be declared"))
.set_linkage(inkwell::module::Linkage::Internal);
}
let read_inaccessible_encoding = MemoryEffect::ReadInaccessible
.encoding()
.expect("ICE: ReadInaccessible always encodes to an integer");
let memory_read_attr =
llvm.create_enum_attribute(Attribute::Memory as u32, read_inaccessible_encoding);
for import in MEMORY_READ_IMPORTS {
if let Some(function) = module.get_function(import) {
function.add_attribute(
inkwell::attributes::AttributeLoc::Function,
memory_read_attr,
);
}
}
}
fn link_polkavm_exports(&self, contract_path: &str) -> anyhow::Result<()> {
let exports = revive_runtime_api::polkavm_exports::module(self.llvm(), "polkavm_exports")
.map_err(|error| {
anyhow::anyhow!(
"The contract `{}` exports module loading error: {}",
contract_path,
error
)
})?;
self.module.link_in_module(exports).map_err(|error| {
anyhow::anyhow!(
"The contract `{}` exports module linking error: {}",
contract_path,
error
)
})
}
fn link_immutable_data(&self, contract_path: &str) -> anyhow::Result<()> {
let size = self.solidity().immutables_size() as u32;
let immutables = revive_runtime_api::immutable_data::module(self.llvm(), size);
self.module.link_in_module(immutables).map_err(|error| {
anyhow::anyhow!(
"The contract `{}` immutable data module linking error: {}",
contract_path,
error
)
})
}
fn set_polkavm_stack_size(
llvm: &'ctx inkwell::context::Context,
module: &inkwell::module::Module<'ctx>,
size: u32,
) {
module
.link_in_module(revive_runtime_api::calling_convention::min_stack_size(
llvm,
"polkavm_stack_size",
size,
))
.expect("the PolkaVM minimum stack size module should be linkable");
}
fn set_module_flags(
llvm: &'ctx inkwell::context::Context,
module: &inkwell::module::Module<'ctx>,
) {
module.add_basic_value_flag(
"PIE Level",
inkwell::module::FlagBehavior::Override,
llvm.i32_type().const_int(2, false),
);
}
fn set_data_layout(
llvm: &'ctx inkwell::context::Context,
module: &inkwell::module::Module<'ctx>,
) {
let source_module = revive_stdlib::module(llvm, "revive_stdlib").unwrap();
let data_layout = source_module.get_data_layout();
module.set_data_layout(&data_layout);
}
pub fn new(
llvm: &'ctx inkwell::context::Context,
module: inkwell::module::Module<'ctx>,
optimizer: Optimizer,
debug_config: DebugConfig,
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
) -> Self {
Self::set_data_layout(llvm, &module);
Self::link_stdlib_module(llvm, &module);
Self::link_polkavm_imports(llvm, &module);
Self::set_polkavm_stack_size(
llvm,
&module,
memory_config
.stack_size
.unwrap_or(PolkaVMDefaultStackMemorySize),
);
Self::set_module_flags(llvm, &module);
let intrinsics = Intrinsics::new(llvm, &module);
let llvm_runtime = LLVMRuntime::new(llvm, &module, &optimizer);
let debug_info = debug_config.emit_debug_info.then(|| {
let debug_info = DebugInfo::new(&module, &debug_config);
debug_info.initialize_module(llvm, &module);
debug_info
});
Self {
llvm,
builder: llvm.create_builder(),
optimizer,
module,
code_type: None,
globals: HashMap::with_capacity(Self::GLOBALS_HASHMAP_INITIAL_CAPACITY),
intrinsics,
llvm_runtime,
functions: HashMap::with_capacity(Self::FUNCTIONS_HASHMAP_INITIAL_CAPACITY),
current_function: None,
loop_stack: Vec::with_capacity(Self::LOOP_STACK_INITIAL_CAPACITY),
function_counter: 0,
function_scope: Vec::new(),
memory_config,
debug_info,
debug_config,
solidity_data: None,
yul_data: None,
}
}
pub fn new_dummy(
llvm: &'ctx inkwell::context::Context,
optimizer_settings: OptimizerSettings,
) -> Self {
Self::new(
llvm,
llvm.create_module("dummy"),
Optimizer::new(optimizer_settings),
Default::default(),
Default::default(),
)
}
pub fn build(
self,
contract_path: &str,
metadata_hash: Option<revive_common::Keccak256>,
) -> anyhow::Result<Build> {
self.link_polkavm_exports(contract_path)?;
self.link_immutable_data(contract_path)?;
let target_machine = TargetMachine::new(
Target::PVM,
self.optimizer.settings(),
self.optimizer.is_newyork(),
)?;
self.module().set_triple(&target_machine.get_triple());
self.debug_config
.dump_llvm_ir_unoptimized(contract_path, self.module())?;
self.verify().map_err(|error| {
anyhow::anyhow!(
"The contract `{}` unoptimized LLVM IR verification error: {}",
contract_path,
error
)
})?;
self.optimizer
.run(&target_machine, self.module())
.map_err(|error| {
anyhow::anyhow!(
"The contract `{}` optimizing error: {}",
contract_path,
error
)
})?;
self.narrow_divrem_instructions();
self.debug_config
.dump_llvm_ir_optimized(contract_path, self.module())?;
self.verify().map_err(|error| {
anyhow::anyhow!(
"The contract `{}` optimized LLVM IR verification error: {}",
contract_path,
error
)
})?;
let buffer = target_machine
.write_to_memory_buffer(self.module())
.map_err(|error| {
anyhow::anyhow!(
"The contract `{}` assembly generating error: {}",
contract_path,
error
)
})?;
let object = buffer.as_slice().to_vec();
self.debug_config.dump_object(contract_path, &object)?;
crate::polkavm::build(
&object,
metadata_hash
.as_ref()
.map(|hash| hash.as_bytes().try_into().unwrap()),
)
}
pub fn verify(&self) -> anyhow::Result<()> {
self.module()
.verify()
.map_err(|error| anyhow::anyhow!(error.to_string()))
}
pub fn llvm(&self) -> &'ctx inkwell::context::Context {
self.llvm
}
pub fn builder(&self) -> &inkwell::builder::Builder<'ctx> {
&self.builder
}
pub fn module(&self) -> &inkwell::module::Module<'ctx> {
&self.module
}
pub fn set_code_type(&mut self, code_type: CodeType) {
self.code_type = Some(code_type);
}
pub fn code_type(&self) -> Option<CodeType> {
self.code_type.to_owned()
}
pub fn runtime_api_method(&self, name: &'static str) -> inkwell::values::FunctionValue<'ctx> {
self.module()
.get_function(name)
.unwrap_or_else(|| panic!("runtime API method {name} not declared"))
}
pub fn get_global(&self, name: &str) -> anyhow::Result<Global<'ctx>> {
match self.globals.get(name) {
Some(global) => Ok(*global),
None => anyhow::bail!("Global variable {} is not declared", name),
}
}
pub fn get_global_value(
&self,
name: &str,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
let global = self.get_global(name)?;
self.build_load(global.into(), name)
}
pub fn set_global<T, V>(&mut self, name: &str, r#type: T, address_space: AddressSpace, value: V)
where
T: BasicType<'ctx> + Clone + Copy,
V: BasicValue<'ctx> + Clone + Copy,
{
match self.globals.get(name) {
Some(global) => {
let global = *global;
self.build_store(global.into(), value).unwrap();
}
None => {
let global = Global::new(self, r#type, address_space, value, name);
self.globals.insert(name.to_owned(), global);
}
}
}
pub fn declare_global<T>(&mut self, name: &str, r#type: T, address_space: AddressSpace)
where
T: BasicType<'ctx> + Clone + Copy,
{
if self.globals.contains_key(name) {
return;
}
let global = Global::declare(self, r#type, address_space, name);
self.globals.insert(name.to_owned(), global);
}
pub fn intrinsics(&self) -> &Intrinsics<'ctx> {
&self.intrinsics
}
pub fn llvm_runtime(&self) -> &LLVMRuntime<'ctx> {
&self.llvm_runtime
}
pub fn get_current_scope(&mut self) -> &mut BTreeMap<String, String> {
self.function_scope
.last_mut()
.expect("ICE: function scope must be pushed before declaring frontend functions")
}
fn mangle_frontend_function_name(&mut self, name: &str) -> String {
assert!(
!self.get_current_scope().contains_key(name),
"ICE: function '{name}' declared subsequently in the same scope"
);
let counter = self.function_counter;
self.function_counter += 1;
let mangled = format!("{name}_{}__{counter}", self.code_type().unwrap());
self.get_current_scope()
.insert(name.to_string(), mangled.clone());
mangled
}
pub fn add_function(
&mut self,
name: &str,
r#type: inkwell::types::FunctionType<'ctx>,
return_values_length: usize,
linkage: Option<inkwell::module::Linkage>,
location: Option<(u32, u32)>,
is_frontend: bool,
) -> anyhow::Result<Rc<RefCell<Function<'ctx>>>> {
let name = if is_frontend {
self.mangle_frontend_function_name(name)
} else {
name.to_string()
};
let value = self.module().add_function(&name, r#type, linkage);
if self.debug_info().is_some() {
self.builder().unset_current_debug_location();
let func_scope = match value.get_subprogram() {
None => {
let fn_name = value.get_name().to_str()?;
let scp = self.build_function_debug_info(fn_name, 0)?;
value.set_subprogram(scp);
scp
}
Some(scp) => scp,
};
self.push_debug_scope(func_scope.as_debug_info_scope());
let (line, column) = location.unwrap_or((0, 0));
self.set_debug_location(line, column, Some(func_scope.as_debug_info_scope()))?;
}
let entry_block = self.llvm.append_basic_block(value, "entry");
let return_block = self.llvm.append_basic_block(value, "return");
let r#return = match return_values_length {
0 => FunctionReturn::none(),
1 => {
self.set_basic_block(entry_block);
let alloca_type = r#type
.get_return_type()
.unwrap_or_else(|| self.word_type().as_basic_type_enum());
let pointer = self.build_alloca(alloca_type, "return_pointer");
FunctionReturn::primitive(pointer)
}
size => {
self.set_basic_block(entry_block);
let alloca_type = r#type.get_return_type().unwrap_or_else(|| {
self.structure_type(
vec![self.word_type().as_basic_type_enum(); size].as_slice(),
)
.as_basic_type_enum()
});
let pointer = self.build_alloca(alloca_type, "return_pointer");
FunctionReturn::compound(pointer, size)
}
};
let function = Function::new(
name.clone(),
FunctionDeclaration::new(r#type, value),
r#return,
entry_block,
return_block,
);
Function::set_default_attributes(self.llvm, function.declaration(), &self.optimizer);
let function = Rc::new(RefCell::new(function));
self.functions.insert(name, function.clone());
self.pop_debug_scope();
Ok(function)
}
pub fn get_function(
&self,
name: &str,
is_frontend: bool,
) -> Option<Rc<RefCell<Function<'ctx>>>> {
if is_frontend {
let mangled = self
.function_scope
.iter()
.rev()
.find_map(|scope| scope.get(name))?;
self.functions.get(mangled).cloned()
} else {
self.functions.get(name).cloned()
}
}
pub fn current_function(&self) -> Rc<RefCell<Function<'ctx>>> {
self.current_function
.clone()
.expect("Must be declared before use")
}
pub fn set_current_function(
&mut self,
name: &str,
location: Option<(u32, u32)>,
frontend: bool,
) -> anyhow::Result<()> {
let function = self.get_function(name, frontend).ok_or_else(|| {
anyhow::anyhow!("Failed to activate an undeclared function `{}`", name)
})?;
self.current_function = Some(function);
if let Some(scope) = self.current_function().borrow().get_debug_scope() {
self.push_debug_scope(scope);
}
let (line, column) = location.unwrap_or_default();
self.set_debug_location(line, column, None)?;
Ok(())
}
pub fn build_function_debug_info(
&self,
name: &str,
line_no: u32,
) -> anyhow::Result<inkwell::debug_info::DISubprogram<'ctx>> {
let Some(debug_info) = self.debug_info() else {
anyhow::bail!("expected debug-info builders");
};
let builder = debug_info.builder();
let file = debug_info.compilation_unit().get_file();
let scope = file.as_debug_info_scope();
let flags = inkwell::debug_info::DIFlagsConstants::PUBLIC;
let return_type = debug_info.create_word_type(Some(flags))?.as_type();
let subroutine_type = builder.create_subroutine_type(file, Some(return_type), &[], flags);
Ok(builder.create_function(
scope,
name,
None,
file,
line_no,
subroutine_type,
false,
true,
1,
flags,
false,
))
}
pub fn set_debug_location(
&self,
line: u32,
column: u32,
scope: Option<DIScope<'ctx>>,
) -> anyhow::Result<()> {
let Some(debug_info) = self.debug_info() else {
return Ok(());
};
let scope = match scope {
Some(scp) => scp,
None => debug_info.top_scope().expect("expected a debug-info scope"),
};
let location =
debug_info
.builder()
.create_debug_location(self.llvm(), line, column, scope, None);
self.builder().set_current_debug_location(location);
Ok(())
}
pub fn set_debug_location_to_function_scope(&self) -> anyhow::Result<()> {
let scope = self.current_function().borrow().get_debug_scope();
self.set_debug_location(0, 0, scope)
}
pub fn push_debug_scope(&self, scope: DIScope<'ctx>) {
if let Some(debug_info) = self.debug_info() {
debug_info.push_scope(scope);
}
}
pub fn pop_debug_scope(&self) {
if let Some(debug_info) = self.debug_info() {
debug_info.pop_scope();
}
}
pub fn push_loop(
&mut self,
body_block: inkwell::basic_block::BasicBlock<'ctx>,
continue_block: inkwell::basic_block::BasicBlock<'ctx>,
join_block: inkwell::basic_block::BasicBlock<'ctx>,
) {
self.loop_stack
.push(Loop::new(body_block, continue_block, join_block));
}
pub fn pop_loop(&mut self) {
self.loop_stack.pop();
}
pub fn r#loop(&self) -> &Loop<'ctx> {
self.loop_stack
.last()
.expect("The current context is not in a loop")
}
pub fn push_function_scope(&mut self) {
self.function_scope.push(BTreeMap::new());
}
pub fn pop_function_scope(&mut self) {
self.function_scope
.pop()
.expect("ICE: tried to pop an empty function scope stack");
}
pub fn debug_info(&self) -> Option<&DebugInfo<'ctx>> {
self.debug_info.as_ref()
}
pub fn debug_config(&self) -> &DebugConfig {
&self.debug_config
}
pub fn append_basic_block(&self, name: &str) -> inkwell::basic_block::BasicBlock<'ctx> {
self.llvm
.append_basic_block(self.current_function().borrow().declaration().value, name)
}
pub fn set_basic_block(&self, block: inkwell::basic_block::BasicBlock<'ctx>) {
self.builder.position_at_end(block);
}
pub fn basic_block(&self) -> inkwell::basic_block::BasicBlock<'ctx> {
self.builder.get_insert_block().expect("Always exists")
}
pub fn build_alloca_at_entry<T: BasicType<'ctx> + Clone + Copy>(
&self,
r#type: T,
name: &str,
) -> Pointer<'ctx> {
let current_block = self.builder.get_insert_block().unwrap();
let function = current_block.get_parent().unwrap();
let entry_block = function.get_first_basic_block().unwrap();
if let Some(terminator) = entry_block.get_terminator() {
self.builder.position_before(&terminator);
} else {
self.builder.position_at_end(entry_block);
}
let pointer = self.builder.build_alloca(r#type, name).unwrap();
pointer
.as_instruction()
.unwrap()
.set_alignment(revive_common::BYTE_LENGTH_STACK_ALIGN as u32)
.expect("ICE: alignment is valid");
self.builder.position_at_end(current_block);
Pointer::new(r#type, AddressSpace::Stack, pointer)
}
pub fn build_alloca<T: BasicType<'ctx> + Clone + Copy>(
&self,
r#type: T,
name: &str,
) -> Pointer<'ctx> {
let pointer = self.builder.build_alloca(r#type, name).unwrap();
pointer
.as_instruction()
.unwrap()
.set_alignment(revive_common::BYTE_LENGTH_STACK_ALIGN as u32)
.expect("Alignment is valid");
Pointer::new(r#type, AddressSpace::Stack, pointer)
}
pub fn build_address_argument_store(
&self,
address: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<Pointer<'ctx>> {
let address_type = self.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS);
let address_pointer = self
.get_global(crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER)?
.into();
let address_truncated =
self.builder()
.build_int_truncate(address, address_type, "address_truncated")?;
let address_swapped = self.build_byte_swap(address_truncated.into())?;
self.build_store(address_pointer, address_swapped)?;
Ok(address_pointer)
}
pub fn build_load_address(
&self,
pointer: Pointer<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
let address = self.build_byte_swap(self.build_load(pointer, "address_value")?)?;
Ok(self
.builder()
.build_int_z_extend(address.into_int_value(), self.word_type(), "address_zext")?
.into())
}
pub fn build_load(
&self,
pointer: Pointer<'ctx>,
name: &str,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
match pointer.address_space {
AddressSpace::Heap => {
let name = <PolkaVMLoadHeapWordFunction as RuntimeFunction>::NAME;
let declaration =
<PolkaVMLoadHeapWordFunction as RuntimeFunction>::declaration(self);
let arguments = [self
.builder()
.build_ptr_to_int(pointer.value, self.xlen_type(), "offset_ptrtoint")?
.as_basic_value_enum()];
Ok(self
.build_call(declaration, &arguments, "heap_load")
.unwrap_or_else(|| {
panic!("revive runtime function {name} should return a value")
}))
}
AddressSpace::Stack => {
let value = self
.builder()
.build_load(pointer.r#type, pointer.value, name)?;
self.basic_block()
.get_last_instruction()
.expect("Always exists")
.set_alignment(revive_common::BYTE_LENGTH_STACK_ALIGN as u32)
.expect("Alignment is valid");
Ok(value)
}
}
}
pub fn build_store<V>(&self, pointer: Pointer<'ctx>, value: V) -> anyhow::Result<()>
where
V: BasicValue<'ctx>,
{
match pointer.address_space {
AddressSpace::Heap => {
let declaration =
<PolkaVMStoreHeapWordFunction as RuntimeFunction>::declaration(self);
let arguments = [
pointer.to_int(self).as_basic_value_enum(),
value.as_basic_value_enum(),
];
self.build_call(declaration, &arguments, "heap_store");
}
AddressSpace::Stack => {
let instruction = self.builder.build_store(pointer.value, value).unwrap();
instruction
.set_alignment(revive_common::BYTE_LENGTH_STACK_ALIGN as u32)
.expect("Alignment is valid");
}
};
Ok(())
}
pub fn build_load_native(
&self,
offset: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
let name = <PolkaVMLoadHeapWordNativeFunction as RuntimeFunction>::NAME;
let declaration = <PolkaVMLoadHeapWordNativeFunction as RuntimeFunction>::declaration(self);
let arguments = [offset.as_basic_value_enum()];
Ok(self
.build_call(declaration, &arguments, "heap_load_native")
.unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
}
pub fn build_store_native(
&self,
offset: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()> {
let declaration =
<PolkaVMStoreHeapWordNativeFunction as RuntimeFunction>::declaration(self);
let arguments = [offset.as_basic_value_enum(), value.as_basic_value_enum()];
self.build_call(declaration, &arguments, "heap_store_native");
Ok(())
}
pub fn build_byte_swap(
&self,
value: inkwell::values::BasicValueEnum<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
let intrinsic = match value.get_type().into_int_type().get_bit_width() as usize {
revive_common::BIT_LENGTH_WORD => self.intrinsics().byte_swap_word.value,
revive_common::BIT_LENGTH_ETH_ADDRESS => self.intrinsics().byte_swap_eth_address.value,
_ => panic!(
"invalid byte swap parameter: {:?} {}",
value.get_name(),
value.get_type()
),
};
Ok(self
.builder()
.build_call(intrinsic, &[value.into()], "call_byte_swap")?
.try_as_basic_value()
.unwrap_basic())
}
pub fn build_gep<T>(
&self,
pointer: Pointer<'ctx>,
indexes: &[inkwell::values::IntValue<'ctx>],
element_type: T,
name: &str,
) -> Pointer<'ctx>
where
T: BasicType<'ctx>,
{
let value = unsafe {
self.builder
.build_gep(pointer.r#type, pointer.value, indexes, name)
.unwrap()
};
Pointer::new(element_type, pointer.address_space, value)
}
pub fn build_conditional_branch(
&self,
comparison: inkwell::values::IntValue<'ctx>,
then_block: inkwell::basic_block::BasicBlock<'ctx>,
else_block: inkwell::basic_block::BasicBlock<'ctx>,
) -> anyhow::Result<()> {
if self.basic_block().get_terminator().is_some() {
return Ok(());
}
self.builder
.build_conditional_branch(comparison, then_block, else_block)?;
Ok(())
}
pub fn build_unconditional_branch(
&self,
destination_block: inkwell::basic_block::BasicBlock<'ctx>,
) {
if self.basic_block().get_terminator().is_some() {
return;
}
self.builder
.build_unconditional_branch(destination_block)
.unwrap();
}
pub fn build_runtime_call(
&self,
name: &'static str,
arguments: &[inkwell::values::BasicValueEnum<'ctx>],
) -> Option<inkwell::values::BasicValueEnum<'ctx>> {
self.builder
.build_direct_call(
self.runtime_api_method(name),
&arguments
.iter()
.copied()
.map(inkwell::values::BasicMetadataValueEnum::from)
.collect::<Vec<_>>(),
&format!("runtime_api_{name}_return_value"),
)
.unwrap()
.try_as_basic_value()
.basic()
}
pub fn build_runtime_call_to_getter(
&self,
import: &'static str,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
let pointer = self.build_alloca_at_entry(self.word_type(), &format!("{import}_output"));
self.build_runtime_call(import, &[pointer.to_int(self).into()]);
self.build_load(pointer, import)
}
pub fn build_call(
&self,
function: FunctionDeclaration<'ctx>,
arguments: &[inkwell::values::BasicValueEnum<'ctx>],
name: &str,
) -> Option<inkwell::values::BasicValueEnum<'ctx>> {
let arguments_wrapped: Vec<inkwell::values::BasicMetadataValueEnum> = arguments
.iter()
.copied()
.map(inkwell::values::BasicMetadataValueEnum::from)
.collect();
let call_site_value = self
.builder
.build_indirect_call(
function.r#type,
function.value.as_global_value().as_pointer_value(),
arguments_wrapped.as_slice(),
name,
)
.unwrap();
self.modify_call_site_value(arguments, call_site_value, function);
call_site_value.try_as_basic_value().basic()
}
pub fn build_memcpy(
&self,
destination: Pointer<'ctx>,
source: Pointer<'ctx>,
size: inkwell::values::IntValue<'ctx>,
name: &str,
) -> anyhow::Result<()> {
let size = self.safe_truncate_int_to_xlen(size)?;
let destination = if destination.address_space == AddressSpace::Heap {
self.build_heap_gep(
self.builder()
.build_ptr_to_int(destination.value, self.xlen_type(), name)?,
size,
)?
} else {
destination
};
let source = if source.address_space == AddressSpace::Heap {
self.build_heap_gep(
self.builder()
.build_ptr_to_int(source.value, self.xlen_type(), name)?,
size,
)?
} else {
source
};
self.builder()
.build_memmove(destination.value, 1, source.value, 1, size)?;
Ok(())
}
pub fn build_return(&self, value: Option<&dyn BasicValue<'ctx>>) {
if self.basic_block().get_terminator().is_some() {
return;
}
self.builder.build_return(value).unwrap();
}
pub fn build_unreachable(&self) {
if self.basic_block().get_terminator().is_some() {
return;
}
self.builder.build_unreachable().unwrap();
}
pub fn build_exit(
&self,
flags: inkwell::values::IntValue<'ctx>,
offset: inkwell::values::IntValue<'ctx>,
length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()> {
self.build_call(
<Exit as RuntimeFunction>::declaration(self),
&[flags.into(), offset.into(), length.into()],
"exit",
);
Ok(())
}
pub fn safe_truncate_int_to_xlen(
&self,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::IntValue<'ctx>> {
let value_width = value.get_type().get_bit_width();
let xlen_width = self.xlen_type().get_bit_width();
let word_width = self.word_type().get_bit_width();
if value_width == xlen_width {
return Ok(value);
}
if value_width < xlen_width {
return Ok(self.builder().build_int_z_extend(
value,
self.xlen_type(),
"narrow_to_xlen",
)?);
}
if value_width == word_width {
return Ok(self
.build_call(
<WordToPointer as RuntimeFunction>::declaration(self),
&[value.into()],
"word_to_pointer",
)
.unwrap_or_else(|| {
panic!(
"ICE: revive runtime function {} should return a value",
<WordToPointer as RuntimeFunction>::NAME,
)
})
.into_int_value());
}
let truncated =
self.builder()
.build_int_truncate(value, self.xlen_type(), "offset_truncated")?;
let extended =
self.builder()
.build_int_z_extend(truncated, value.get_type(), "offset_extended")?;
let is_overflow = self.builder().build_int_compare(
inkwell::IntPredicate::NE,
value,
extended,
"compare_truncated_extended",
)?;
let block_continue = self.append_basic_block("offset_pointer_ok");
let block_invalid = self.append_basic_block("offset_pointer_overflow");
self.build_conditional_branch(is_overflow, block_invalid, block_continue)?;
self.set_basic_block(block_invalid);
self.build_runtime_call(revive_runtime_api::polkavm_imports::INVALID, &[]);
self.build_unreachable();
self.set_basic_block(block_continue);
Ok(truncated)
}
pub fn clip_to_xlen(
&self,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::IntValue<'ctx>> {
let value_width = value.get_type().get_bit_width();
let xlen_width = self.xlen_type().get_bit_width();
let word_width = self.word_type().get_bit_width();
if value_width == xlen_width {
return Ok(value);
}
if value_width < xlen_width {
return Ok(self.builder().build_int_z_extend(
value,
self.xlen_type(),
"narrow_to_xlen",
)?);
}
let clipped = self.xlen_type().const_all_ones();
let comparison_type = if value_width == word_width {
self.word_type()
} else {
value.get_type()
};
let is_overflow = self.builder().build_int_compare(
inkwell::IntPredicate::UGT,
value,
self.builder()
.build_int_z_extend(clipped, comparison_type, "value_clipped")?,
"is_value_overflow",
)?;
let truncated =
self.builder()
.build_int_truncate(value, self.xlen_type(), "value_truncated")?;
Ok(self
.builder()
.build_select(is_overflow, clipped, truncated, "value")?
.into_int_value())
}
pub fn build_sbrk(
&self,
offset: inkwell::values::IntValue<'ctx>,
size: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::PointerValue<'ctx>> {
let call_site_value = self.builder().build_call(
<PolkaVMSbrkFunction as RuntimeFunction>::declaration(self).function_value(),
&[offset.into(), size.into()],
"alloc_start",
)?;
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Return,
self.llvm
.create_enum_attribute(Attribute::NonNull as u32, 0),
);
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Return,
self.llvm
.create_enum_attribute(Attribute::NoUndef as u32, 0),
);
Ok(call_site_value
.try_as_basic_value()
.unwrap_basic()
.into_pointer_value())
}
pub fn build_msize(&self) -> anyhow::Result<inkwell::values::IntValue<'ctx>> {
Ok(self
.get_global_value(crate::polkavm::GLOBAL_HEAP_SIZE)?
.into_int_value())
}
pub fn build_heap_gep(
&self,
offset: inkwell::values::IntValue<'ctx>,
length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<Pointer<'ctx>> {
assert_eq!(offset.get_type(), self.xlen_type());
assert_eq!(length.get_type(), self.xlen_type());
let pointer = self.build_sbrk(offset, length)?;
Ok(Pointer::new(self.byte_type(), AddressSpace::Stack, pointer))
}
pub fn build_heap_gep_unchecked(
&self,
offset: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<Pointer<'ctx>> {
assert_eq!(offset.get_type(), self.xlen_type());
let heap_global: Pointer<'ctx> =
self.get_global(crate::polkavm::GLOBAL_HEAP_MEMORY)?.into();
let pointer = self.build_gep(
heap_global,
&[self.xlen_type().const_zero(), offset],
self.byte_type(),
"heap_unchecked_ptr",
);
Ok(Pointer::new(
self.byte_type(),
AddressSpace::Stack,
pointer.value,
))
}
pub fn ensure_heap_size(
&self,
min_size: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()> {
let current = self
.get_global_value(crate::polkavm::GLOBAL_HEAP_SIZE)?
.into_int_value();
let needs_update = self.builder().build_int_compare(
inkwell::IntPredicate::UGT,
min_size,
current,
"msize_needs_update",
)?;
let new_size = self
.builder()
.build_select(needs_update, min_size, current, "msize_new")?
.into_int_value();
let heap_size_global = self.get_global(crate::polkavm::GLOBAL_HEAP_SIZE)?;
self.build_store(heap_size_global.into(), new_size)?;
Ok(())
}
pub fn bool_const(&self, value: bool) -> inkwell::values::IntValue<'ctx> {
self.bool_type().const_int(u64::from(value), false)
}
pub fn integer_const(&self, bit_length: usize, value: u64) -> inkwell::values::IntValue<'ctx> {
self.integer_type(bit_length).const_int(value, false)
}
pub fn word_const(&self, value: u64) -> inkwell::values::IntValue<'ctx> {
self.word_type().const_int(value, false)
}
pub fn word_undef(&self) -> inkwell::values::IntValue<'ctx> {
self.word_type().get_undef()
}
pub fn word_const_str_dec(&self, value: &str) -> inkwell::values::IntValue<'ctx> {
self.word_type()
.const_int_from_string(value, inkwell::types::StringRadix::Decimal)
.unwrap_or_else(|| panic!("Invalid string constant `{value}`"))
}
pub fn word_const_str_hex(&self, value: &str) -> inkwell::values::IntValue<'ctx> {
self.word_type()
.const_int_from_string(
value.strip_prefix("0x").unwrap_or(value),
inkwell::types::StringRadix::Hexadecimal,
)
.unwrap_or_else(|| panic!("Invalid string constant `{value}`"))
}
pub fn void_type(&self) -> inkwell::types::VoidType<'ctx> {
self.llvm.void_type()
}
pub fn bool_type(&self) -> inkwell::types::IntType<'ctx> {
self.llvm.bool_type()
}
pub fn byte_type(&self) -> inkwell::types::IntType<'ctx> {
self.llvm
.custom_width_int_type(
NonZeroU32::new(revive_common::BIT_LENGTH_BYTE as u32).expect("const is non-zero"),
)
.expect("valid integer width")
}
pub fn integer_type(&self, bit_length: usize) -> inkwell::types::IntType<'ctx> {
self.llvm
.custom_width_int_type(
NonZeroU32::new(bit_length as u32).expect("bit length is non-zero"),
)
.expect("valid integer width")
}
pub fn xlen_type(&self) -> inkwell::types::IntType<'ctx> {
self.llvm
.custom_width_int_type(
NonZeroU32::new(crate::polkavm::XLEN as u32).expect("const is non-zero"),
)
.expect("valid integer width")
}
pub fn register_type(&self) -> inkwell::types::IntType<'ctx> {
self.llvm
.custom_width_int_type(
NonZeroU32::new(revive_common::BIT_LENGTH_X64 as u32).expect("const is non-zero"),
)
.expect("valid integer width")
}
pub fn sentinel_pointer(&self) -> Pointer<'ctx> {
let sentinel_pointer = self
.xlen_type()
.const_all_ones()
.const_to_pointer(self.llvm().ptr_type(Default::default()));
Pointer::new(
sentinel_pointer.get_type(),
AddressSpace::Stack,
sentinel_pointer,
)
}
pub fn value_type(&self) -> inkwell::types::IntType<'ctx> {
self.llvm
.custom_width_int_type(
NonZeroU32::new(revive_common::BIT_LENGTH_VALUE as u32).expect("const is non-zero"),
)
.expect("valid integer width")
}
pub fn word_type(&self) -> inkwell::types::IntType<'ctx> {
self.llvm
.custom_width_int_type(
NonZeroU32::new(revive_common::BIT_LENGTH_WORD as u32).expect("const is non-zero"),
)
.expect("valid integer width")
}
pub fn array_type<T>(&self, element_type: T, length: usize) -> inkwell::types::ArrayType<'ctx>
where
T: BasicType<'ctx>,
{
element_type.array_type(length as u32)
}
pub fn structure_type<T>(&self, field_types: &[T]) -> inkwell::types::StructType<'ctx>
where
T: BasicType<'ctx>,
{
let field_types: Vec<inkwell::types::BasicTypeEnum<'ctx>> =
field_types.iter().map(T::as_basic_type_enum).collect();
self.llvm.struct_type(field_types.as_slice(), false)
}
pub fn function_type<T>(
&self,
argument_types: Vec<T>,
return_values_size: usize,
) -> inkwell::types::FunctionType<'ctx>
where
T: BasicType<'ctx>,
{
let argument_types: Vec<inkwell::types::BasicMetadataTypeEnum> = argument_types
.as_slice()
.iter()
.map(T::as_basic_type_enum)
.map(inkwell::types::BasicMetadataTypeEnum::from)
.collect();
match return_values_size {
0 => self
.llvm
.void_type()
.fn_type(argument_types.as_slice(), false),
1 => self.word_type().fn_type(argument_types.as_slice(), false),
size => self
.structure_type(vec![self.word_type().as_basic_type_enum(); size].as_slice())
.fn_type(argument_types.as_slice(), false),
}
}
pub fn function_type_with_returns<T>(
&self,
argument_types: Vec<T>,
return_types: &[inkwell::types::IntType<'ctx>],
) -> inkwell::types::FunctionType<'ctx>
where
T: BasicType<'ctx>,
{
let argument_types: Vec<inkwell::types::BasicMetadataTypeEnum> = argument_types
.as_slice()
.iter()
.map(T::as_basic_type_enum)
.map(inkwell::types::BasicMetadataTypeEnum::from)
.collect();
match return_types.len() {
0 => self
.llvm
.void_type()
.fn_type(argument_types.as_slice(), false),
1 => return_types[0].fn_type(argument_types.as_slice(), false),
_ => {
let field_types: Vec<inkwell::types::BasicTypeEnum> = return_types
.iter()
.map(|t| t.as_basic_type_enum())
.collect();
self.structure_type(&field_types)
.fn_type(argument_types.as_slice(), false)
}
}
}
pub fn modify_call_site_value(
&self,
arguments: &[inkwell::values::BasicValueEnum<'ctx>],
call_site_value: inkwell::values::CallSiteValue<'ctx>,
function: FunctionDeclaration<'ctx>,
) {
for (index, argument) in arguments.iter().enumerate() {
if argument.is_pointer_value() {
call_site_value.set_alignment_attribute(
inkwell::attributes::AttributeLoc::Param(index as u32),
revive_common::BYTE_LENGTH_STACK_ALIGN as u32,
);
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Param(index as u32),
self.llvm
.create_enum_attribute(Attribute::NoAlias as u32, 0),
);
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Param(index as u32),
self.llvm
.create_enum_attribute(Attribute::Captures as u32, 0), );
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Param(index as u32),
self.llvm.create_enum_attribute(Attribute::NoFree as u32, 0),
);
if Some(argument.get_type()) == function.r#type.get_return_type() {
if function
.r#type
.get_return_type()
.map(|r#type| {
r#type.into_pointer_type().get_address_space()
== AddressSpace::Stack.into()
})
.unwrap_or_default()
{
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Param(index as u32),
self.llvm
.create_enum_attribute(Attribute::Returned as u32, 0),
);
}
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Param(index as u32),
self.llvm.create_enum_attribute(
Attribute::Dereferenceable as u32,
(revive_common::BIT_LENGTH_WORD * 2) as u64,
),
);
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Return,
self.llvm.create_enum_attribute(
Attribute::Dereferenceable as u32,
(revive_common::BIT_LENGTH_WORD * 2) as u64,
),
);
}
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Param(index as u32),
self.llvm
.create_enum_attribute(Attribute::NonNull as u32, 0),
);
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Param(index as u32),
self.llvm
.create_enum_attribute(Attribute::NoUndef as u32, 0),
);
}
}
if function
.r#type
.get_return_type()
.map(|r#type| r#type.is_pointer_type())
.unwrap_or_default()
{
call_site_value.set_alignment_attribute(
inkwell::attributes::AttributeLoc::Return,
revive_common::BYTE_LENGTH_STACK_ALIGN as u32,
);
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Return,
self.llvm
.create_enum_attribute(Attribute::NoAlias as u32, 0),
);
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Return,
self.llvm
.create_enum_attribute(Attribute::NonNull as u32, 0),
);
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Return,
self.llvm
.create_enum_attribute(Attribute::NoUndef as u32, 0),
);
}
}
pub fn set_solidity_data(&mut self, data: SolidityData) {
self.solidity_data = Some(data);
}
pub fn solidity(&self) -> &SolidityData {
self.solidity_data
.as_ref()
.expect("The Solidity data must have been initialized")
}
pub fn solidity_mut(&mut self) -> &mut SolidityData {
self.solidity_data
.as_mut()
.expect("The Solidity data must have been initialized")
}
pub fn set_yul_data(&mut self, data: YulData) {
self.yul_data = Some(data);
}
pub fn yul(&self) -> Option<&YulData> {
self.yul_data.as_ref()
}
pub fn immutables_size(&self) -> anyhow::Result<usize> {
if let Some(solidity) = self.solidity_data.as_ref() {
Ok(solidity.immutables_size())
} else {
anyhow::bail!("The immutable size data is not available");
}
}
pub fn optimizer_settings(&self) -> &OptimizerSettings {
self.optimizer.settings()
}
pub fn is_newyork(&self) -> bool {
self.optimizer.is_newyork()
}
pub fn heap_size(&self) -> inkwell::values::IntValue<'ctx> {
self.xlen_type().const_int(
self.memory_config
.heap_size
.unwrap_or(PolkaVMDefaultHeapMemorySize) as u64,
false,
)
}
fn narrow_divrem_instructions(&self) {
let builder = self.llvm.create_builder();
for function in self.module().get_functions() {
let mut to_narrow = Vec::new();
for basic_block in function.get_basic_blocks() {
for instruction in basic_block.get_instructions() {
let is_divrem = matches!(
instruction.get_opcode(),
InstructionOpcode::UDiv
| InstructionOpcode::SDiv
| InstructionOpcode::URem
| InstructionOpcode::SRem
);
if !is_divrem {
continue;
}
if instruction.get_type().into_int_type().get_bit_width() < 256 {
continue;
}
let lhs = instruction.get_operand(0).and_then(|op| op.value());
let rhs = instruction.get_operand(1).and_then(|op| op.value());
if let (Some(lhs), Some(rhs)) = (lhs, rhs) {
let lhs_width = Self::provable_bit_width(lhs);
let rhs_width = Self::provable_bit_width(rhs);
if let (Some(lw), Some(rw)) = (lhs_width, rhs_width) {
let max_operand_width = lw.max(rw);
let narrow_width = Self::round_up_bit_width(max_operand_width);
let is_signed = matches!(
instruction.get_opcode(),
InstructionOpcode::SDiv | InstructionOpcode::SRem
);
let signed_sign_bit_safe =
!is_signed || narrow_width > max_operand_width;
if narrow_width < 256 && signed_sign_bit_safe {
to_narrow.push((instruction, narrow_width));
}
}
}
}
}
for (instruction, narrow_width) in to_narrow {
let lhs = instruction
.get_operand(0)
.unwrap()
.value()
.unwrap()
.into_int_value();
let rhs = instruction
.get_operand(1)
.unwrap()
.value()
.unwrap()
.into_int_value();
let wide_type = instruction.get_type().into_int_type();
let narrow_type = self
.llvm
.custom_width_int_type(
std::num::NonZeroU32::new(narrow_width).expect("narrow width is non-zero"),
)
.expect("valid integer width");
builder.position_before(&instruction);
let lhs_truncated = builder.build_int_truncate(lhs, narrow_type, "").unwrap();
let rhs_truncated = builder.build_int_truncate(rhs, narrow_type, "").unwrap();
let narrow_result = match instruction.get_opcode() {
InstructionOpcode::UDiv => builder
.build_int_unsigned_div(lhs_truncated, rhs_truncated, "")
.unwrap(),
InstructionOpcode::SDiv => builder
.build_int_signed_div(lhs_truncated, rhs_truncated, "")
.unwrap(),
InstructionOpcode::URem => builder
.build_int_unsigned_rem(lhs_truncated, rhs_truncated, "")
.unwrap(),
InstructionOpcode::SRem => builder
.build_int_signed_rem(lhs_truncated, rhs_truncated, "")
.unwrap(),
_ => unreachable!(),
};
let wide_result = if matches!(
instruction.get_opcode(),
InstructionOpcode::SDiv | InstructionOpcode::SRem
) {
builder
.build_int_s_extend(narrow_result, wide_type, "")
.unwrap()
} else {
builder
.build_int_z_extend(narrow_result, wide_type, "")
.unwrap()
};
let wide_instruction = wide_result.as_instruction().unwrap();
instruction.replace_all_uses_with(&wide_instruction);
instruction.erase_from_basic_block();
}
}
}
fn provable_bit_width(value: inkwell::values::BasicValueEnum) -> Option<u32> {
let integer_value = value.into_int_value();
if integer_value.is_const() {
return Self::constant_bit_width(integer_value);
}
let instruction = integer_value.as_instruction()?;
match instruction.get_opcode() {
InstructionOpcode::And => {
let operand_0 = instruction.get_operand(0)?.value()?.into_int_value();
let operand_1 = instruction.get_operand(1)?.value()?.into_int_value();
if operand_1.is_const() {
Self::constant_bit_width(operand_1)
} else if operand_0.is_const() {
Self::constant_bit_width(operand_0)
} else {
None
}
}
InstructionOpcode::ZExt => {
let source = instruction.get_operand(0)?.value()?.into_int_value();
Some(source.get_type().get_bit_width())
}
InstructionOpcode::Trunc => {
Some(instruction.get_type().into_int_type().get_bit_width())
}
_ => None,
}
}
fn constant_bit_width(integer_value: inkwell::values::IntValue) -> Option<u32> {
if let Some(value) = integer_value.get_zero_extended_constant() {
return Some(if value == 0 {
1
} else {
64 - value.leading_zeros()
});
}
let wide_type = integer_value.get_type();
if wide_type.get_bit_width() > 64 {
let i64_type = wide_type.get_context().i64_type();
let truncated = integer_value.const_truncate(i64_type);
if let Some(value) = truncated.get_zero_extended_constant() {
let reconstructed = wide_type.const_int(value, false);
if reconstructed == integer_value {
return Some(if value == 0 {
1
} else {
64 - value.leading_zeros()
});
}
}
}
None
}
fn round_up_bit_width(bits: u32) -> u32 {
if bits <= 8 {
8
} else if bits <= 16 {
16
} else if bits <= 32 {
32
} else if bits <= 64 {
64
} else if bits <= 128 {
128
} else {
256
}
}
}