llvm-context: lazy handling of function arguments and immutable data (#282)

- Lazily load function arguments so that they can be passed as pointers.
- Lazily call the immutable store function to avoid storing zero sized
immutable data.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
This commit is contained in:
xermicus
2025-04-14 15:54:59 +02:00
committed by GitHub
parent ad3315346c
commit 431b5a2ce5
18 changed files with 268 additions and 177 deletions
@@ -4,61 +4,98 @@
#[derive(Debug, Clone)]
pub struct Argument<'ctx> {
/// The actual LLVM operand.
pub value: inkwell::values::BasicValueEnum<'ctx>,
pub value: Value<'ctx>,
/// The original AST value. Used mostly for string literals.
pub original: Option<String>,
/// The preserved constant value, if available.
pub constant: Option<num::BigUint>,
}
/// The function argument can be either a pointer or a integer value.
/// This disambiguation allows for lazy loading of variables.
#[derive(Clone, Debug)]
pub enum Value<'ctx> {
Register(inkwell::values::BasicValueEnum<'ctx>),
Pointer {
pointer: crate::polkavm::context::Pointer<'ctx>,
id: String,
},
}
impl<'ctx> Argument<'ctx> {
/// The calldata offset argument index.
pub const ARGUMENT_INDEX_CALLDATA_OFFSET: usize = 0;
/// The calldata length argument index.
pub const ARGUMENT_INDEX_CALLDATA_LENGTH: usize = 1;
/// A shortcut constructor.
pub fn new(value: inkwell::values::BasicValueEnum<'ctx>) -> Self {
/// A shortcut constructor for register arguments.
pub fn value(value: inkwell::values::BasicValueEnum<'ctx>) -> Self {
Self {
value,
value: Value::Register(value),
original: None,
constant: None,
}
}
/// A shortcut constructor.
pub fn new_with_original(
value: inkwell::values::BasicValueEnum<'ctx>,
original: String,
) -> Self {
/// A shortcut constructor for stack arguments.
pub fn pointer(pointer: crate::polkavm::context::Pointer<'ctx>, id: String) -> Self {
Self {
value,
original: Some(original),
value: Value::Pointer { pointer, id },
original: None,
constant: None,
}
}
/// A shortcut constructor.
pub fn new_with_constant(
value: inkwell::values::BasicValueEnum<'ctx>,
constant: num::BigUint,
) -> Self {
Self {
value,
original: None,
constant: Some(constant),
}
/// Set the original decleratation value.
pub fn with_original(mut self, original: String) -> Self {
self.original = Some(original);
self
}
/// Set the constant value.
pub fn with_constant(mut self, constant: num::BigUint) -> Self {
self.constant = Some(constant);
self
}
/// Returns the inner LLVM value.
pub fn to_llvm(&self) -> inkwell::values::BasicValueEnum<'ctx> {
self.value
///
/// Panics if `self` is a pointer argument.
pub fn _to_llvm_value(&self) -> inkwell::values::BasicValueEnum<'ctx> {
match &self.value {
Value::Register(value) => *value,
Value::Pointer { .. } => unreachable!("invalid register value access"),
}
}
/// Access the underlying value.
///
/// Will emit a stack load if `self` is a pointer argument.
pub fn access<D: crate::polkavm::Dependency + Clone>(
&self,
context: &crate::polkavm::context::Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
match &self.value {
Value::Register(value) => Ok(*value),
Value::Pointer { pointer, id } => context.build_load(*pointer, id),
}
}
/// Access the underlying value.
///
/// Will emit a stack load if `self` is a pointer argument.
pub fn as_pointer<D: crate::polkavm::Dependency + Clone>(
&self,
context: &crate::polkavm::context::Context<'ctx, D>,
) -> anyhow::Result<crate::polkavm::context::Pointer<'ctx>> {
match &self.value {
Value::Register(value) => {
let pointer = context.build_alloca_at_entry(context.word_type(), "pvm_arg");
context.build_store(pointer, *value)?;
Ok(pointer)
}
Value::Pointer { pointer, .. } => Ok(*pointer),
}
}
}
impl<'ctx> From<inkwell::values::BasicValueEnum<'ctx>> for Argument<'ctx> {
fn from(value: inkwell::values::BasicValueEnum<'ctx>) -> Self {
Self::new(value)
Self::value(value)
}
}
@@ -101,6 +101,9 @@ where
solidity_data: Option<SolidityData>,
/// The Yul data.
yul_data: Option<YulData>,
/// Hints whether the contracts deploy function stores immutables.
immutables: bool,
}
impl<'ctx, D> Context<'ctx, D>
@@ -263,6 +266,8 @@ where
solidity_data: None,
yul_data: None,
immutables: false,
}
}
@@ -1426,4 +1431,14 @@ where
pub fn optimizer_settings(&self) -> &OptimizerSettings {
self.optimizer.settings()
}
/// Hint the deploy code exit routine to emit storing the immutables.
pub fn enable_immutables(&mut self) {
self.immutables = true;
}
/// Returns if the contract stores or loads immutables.
pub fn has_immutables(&self) -> bool {
self.immutables
}
}
@@ -19,7 +19,7 @@ where
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context
.word_type()
.fn_type(&[context.word_type().into()], false)
.fn_type(&[context.llvm().ptr_type(Default::default()).into()], false)
}
fn emit_body<'ctx>(
@@ -59,7 +59,7 @@ where
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context
.word_type()
.fn_type(&[context.word_type().into()], false)
.fn_type(&[context.llvm().ptr_type(Default::default()).into()], false)
}
fn emit_body<'ctx>(
@@ -94,7 +94,10 @@ where
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.void_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
&[
context.llvm().ptr_type(Default::default()).into(),
context.llvm().ptr_type(Default::default()).into(),
],
false,
)
}
@@ -138,7 +141,10 @@ where
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.void_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
&[
context.llvm().ptr_type(Default::default()).into(),
context.llvm().ptr_type(Default::default()).into(),
],
false,
)
}
@@ -173,9 +179,17 @@ where
fn emit_load<'ctx, D: Dependency + Clone>(
context: &mut Context<'ctx, D>,
mut key: BasicValueEnum<'ctx>,
key: BasicValueEnum<'ctx>,
transient: bool,
) -> anyhow::Result<BasicValueEnum<'ctx>> {
let mut key = context.build_load(
super::Pointer::new(
context.word_type(),
Default::default(),
key.into_pointer_value(),
),
"key",
)?;
if !transient {
key = context.build_byte_swap(key)?;
}
@@ -217,10 +231,26 @@ fn emit_load<'ctx, D: Dependency + Clone>(
fn emit_store<'ctx, D: Dependency + Clone>(
context: &mut Context<'ctx, D>,
mut key: BasicValueEnum<'ctx>,
mut value: BasicValueEnum<'ctx>,
key: BasicValueEnum<'ctx>,
value: BasicValueEnum<'ctx>,
transient: bool,
) -> anyhow::Result<()> {
let mut key = context.build_load(
super::Pointer::new(
context.word_type(),
Default::default(),
key.into_pointer_value(),
),
"key",
)?;
let mut value = context.build_load(
super::Pointer::new(
context.word_type(),
Default::default(),
value.into_pointer_value(),
),
"key",
)?;
if !transient {
key = context.build_byte_swap(key)?;
value = context.build_byte_swap(value)?;
+6 -10
View File
@@ -119,10 +119,8 @@ where
_ => error,
})?;
if contract_path.as_str() == parent {
return Ok(Argument::new_with_constant(
context.word_const(0).as_basic_value_enum(),
num::BigUint::zero(),
));
return Ok(Argument::value(context.word_const(0).as_basic_value_enum())
.with_constant(num::BigUint::zero()));
} else if identifier.ends_with("_deployed") && code_type == CodeType::Runtime {
anyhow::bail!("type({}).runtimeCode is not supported", identifier);
}
@@ -131,7 +129,7 @@ where
let hash_value = context
.word_const_str_hex(hash_string.as_str())
.as_basic_value_enum();
Ok(Argument::new_with_original(hash_value, hash_string))
Ok(Argument::value(hash_value).with_original(hash_string))
}
/// Translates the deploy call header size instruction. the header consists of
@@ -160,10 +158,8 @@ where
_ => error,
})?;
if contract_path.as_str() == parent {
return Ok(Argument::new_with_constant(
context.word_const(0).as_basic_value_enum(),
num::BigUint::zero(),
));
return Ok(Argument::value(context.word_const(0).as_basic_value_enum())
.with_constant(num::BigUint::zero()));
} else if identifier.ends_with("_deployed") && code_type == CodeType::Runtime {
anyhow::bail!("type({}).runtimeCode is not supported", identifier);
}
@@ -172,5 +168,5 @@ where
let size_value = context
.word_const(crate::polkavm::DEPLOYER_CALL_HEADER_SIZE as u64)
.as_basic_value_enum();
Ok(Argument::new_with_constant(size_value, size_bigint))
Ok(Argument::value(size_value).with_constant(size_bigint))
}
@@ -257,6 +257,7 @@ where
anyhow::bail!("Immutables are not available if the contract part is undefined");
}
Some(CodeType::Deploy) => {
context.enable_immutables();
let immutable_data_pointer = context
.get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_POINTER)?
.value
@@ -18,11 +18,13 @@ where
match context.code_type() {
None => anyhow::bail!("Return is not available if the contract part is undefined"),
Some(CodeType::Deploy) => {
context.build_call(
<Store as RuntimeFunction<D>>::declaration(context),
Default::default(),
"store_immutable_data",
);
if context.has_immutables() {
context.build_call(
<Store as RuntimeFunction<D>>::declaration(context),
Default::default(),
"store_immutable_data",
);
}
}
Some(CodeType::Runtime) => {}
}
+17 -10
View File
@@ -3,6 +3,7 @@
use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::PolkaVMArgument;
use crate::PolkaVMLoadStorageWordFunction;
use crate::PolkaVMLoadTransientStorageWordFunction;
use crate::PolkaVMStoreStorageWordFunction;
@@ -11,14 +12,14 @@ use crate::PolkaVMStoreTransientStorageWordFunction;
/// Translates the storage load.
pub fn load<'ctx, D>(
context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>,
position: &PolkaVMArgument<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let name = <PolkaVMLoadStorageWordFunction as RuntimeFunction<D>>::NAME;
let declaration = <PolkaVMLoadStorageWordFunction as RuntimeFunction<D>>::declaration(context);
let arguments = [position.into()];
let arguments = [position.as_pointer(context)?.value.into()];
Ok(context
.build_call(declaration, &arguments, "storage_load")
.unwrap_or_else(|| panic!("runtime function {name} should return a value")))
@@ -27,14 +28,17 @@ where
/// Translates the storage store.
pub fn store<'ctx, D>(
context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
position: &PolkaVMArgument<'ctx>,
value: &PolkaVMArgument<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let declaration = <PolkaVMStoreStorageWordFunction as RuntimeFunction<D>>::declaration(context);
let arguments = [position.into(), value.into()];
let arguments = [
position.as_pointer(context)?.value.into(),
value.as_pointer(context)?.value.into(),
];
context.build_call(declaration, &arguments, "storage_store");
Ok(())
}
@@ -42,13 +46,13 @@ where
/// Translates the transient storage load.
pub fn transient_load<'ctx, D>(
context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>,
position: &PolkaVMArgument<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let name = <PolkaVMLoadTransientStorageWordFunction as RuntimeFunction<D>>::NAME;
let arguments = [position.into()];
let arguments = [position.as_pointer(context)?.value.into()];
let declaration =
<PolkaVMLoadTransientStorageWordFunction as RuntimeFunction<D>>::declaration(context);
Ok(context
@@ -59,15 +63,18 @@ where
/// Translates the transient storage store.
pub fn transient_store<'ctx, D>(
context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
position: &PolkaVMArgument<'ctx>,
value: &PolkaVMArgument<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let declaration =
<PolkaVMStoreTransientStorageWordFunction as RuntimeFunction<D>>::declaration(context);
let arguments = [position.into(), value.into()];
let arguments = [
position.as_pointer(context)?.value.into(),
value.as_pointer(context)?.value.into(),
];
context.build_call(declaration, &arguments, "transient_storage_store");
Ok(())
}