Updated call semantics (#56)

- Update pallet-revive dependency
- Implement calls according to pallet-revive call semantics
- Switch to the new return data API in pallet revive and get rid of return data buffer
- Remove a bunch of resulting dead code
This commit is contained in:
Cyrill Leutwiler
2024-09-28 20:03:03 +02:00
committed by GitHub
parent 066acc4663
commit 6585973e99
24 changed files with 1001 additions and 886 deletions
@@ -21,12 +21,6 @@ pub static GLOBAL_CALLDATA_POINTER: &str = "ptr_calldata";
/// The calldata size global variable name.
pub static GLOBAL_CALLDATA_SIZE: &str = "calldatasize";
/// The return data pointer global variable name.
pub static GLOBAL_RETURN_DATA_POINTER: &str = "ptr_return_data";
/// The return data size pointer global variable name.
pub static GLOBAL_RETURN_DATA_SIZE: &str = "returndatasize";
/// The call flags global variable name.
pub static GLOBAL_CALL_FLAGS: &str = "call_flags";
@@ -36,15 +30,6 @@ pub static GLOBAL_CONST_ARRAY_PREFIX: &str = "const_array_";
/// The global verbatim getter identifier prefix.
pub static GLOBAL_VERBATIM_GETTER_PREFIX: &str = "get_global::";
/// The static word size.
pub static GLOBAL_I256_SIZE: &str = "i256_size";
/// The static value size.
pub static GLOBAL_I160_SIZE: &str = "i160_size";
/// The static i64 size.
pub static GLOBAL_I64_SIZE: &str = "i64_size";
/// The external call data offset in the auxiliary heap.
pub const HEAP_AUX_OFFSET_EXTERNAL_CALL: u64 = 0;
@@ -41,7 +41,9 @@ pub mod imports {
pub static RETURN: &str = "seal_return";
pub static RETURNDATACOPY: &str = "returndatacopy";
pub static RETURNDATACOPY: &str = "return_data_copy";
pub static RETURNDATASIZE: &str = "return_data_size";
pub static SET_STORAGE: &str = "set_storage";
@@ -49,7 +51,7 @@ pub mod imports {
/// All imported runtime API symbols.
/// Useful for configuring common attributes and linkage.
pub static IMPORTS: [&str; 17] = [
pub static IMPORTS: [&str; 18] = [
ADDRESS,
BALANCE,
BLOCK_NUMBER,
@@ -65,6 +67,7 @@ pub mod imports {
NOW,
RETURN,
RETURNDATACOPY,
RETURNDATASIZE,
SET_STORAGE,
VALUE_TRANSFERRED,
];
@@ -1,7 +1,6 @@
//! The entry function.
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::function::runtime;
@@ -26,9 +25,6 @@ impl Entry {
/// Reserve 1kb for calldata.
pub const MAX_CALLDATA_SIZE: usize = 1024;
/// Reserve 1kb for returndata.
pub const MAX_RETURNDATA_SIZE: usize = 1024;
/// Initializes the global variables.
/// The pointers are not initialized, because it's not possible to create a null pointer.
pub fn initialize_globals<D>(context: &mut Context<D>) -> anyhow::Result<()>
@@ -43,14 +39,6 @@ impl Entry {
calldata_type.get_undef(),
);
let returndata_type = context.array_type(context.byte_type(), Self::MAX_RETURNDATA_SIZE);
context.set_global(
crate::polkavm::GLOBAL_RETURN_DATA_POINTER,
returndata_type,
AddressSpace::Stack,
returndata_type.get_undef(),
);
context.set_global(
crate::polkavm::GLOBAL_HEAP_MEMORY_POINTER,
context.llvm().ptr_type(AddressSpace::Heap.into()),
@@ -70,12 +58,6 @@ impl Entry {
AddressSpace::Stack,
context.word_undef(),
);
context.set_global(
crate::polkavm::GLOBAL_RETURN_DATA_SIZE,
context.xlen_type(),
AddressSpace::Stack,
context.xlen_type().const_zero().as_basic_value_enum(),
);
context.set_global(
crate::polkavm::GLOBAL_CALL_FLAGS,
@@ -84,33 +66,6 @@ impl Entry {
context.word_const(0),
);
context.set_global(
crate::polkavm::GLOBAL_I256_SIZE,
context.xlen_type(),
AddressSpace::Stack,
context.integer_const(
crate::polkavm::XLEN,
revive_common::BYTE_LENGTH_X64 as u64 * 4,
),
);
context.set_global(
crate::polkavm::GLOBAL_I160_SIZE,
context.xlen_type(),
AddressSpace::Stack,
context.integer_const(
crate::polkavm::XLEN,
revive_common::BYTE_LENGTH_X64 as u64 * 2 + revive_common::BYTE_LENGTH_X32 as u64,
),
);
context.set_global(
crate::polkavm::GLOBAL_I64_SIZE,
context.xlen_type(),
AddressSpace::Stack,
context.integer_const(crate::polkavm::XLEN, revive_common::BYTE_LENGTH_X64 as u64),
);
Ok(())
}
+8 -36
View File
@@ -610,9 +610,8 @@ where
}
let pointer = self.build_alloca(r#type, name);
self.set_basic_block(current_block);
return pointer;
pointer
}
/// Builds an aligned stack allocation at the current position.
@@ -633,43 +632,16 @@ where
Pointer::new(r#type, AddressSpace::Stack, pointer)
}
/// Allocate an int of size `bit_length` on the stack.
/// Returns the allocation pointer and the length pointer.
///
/// Useful helper for passing runtime API parameters on the stack.
pub fn build_stack_parameter(
&self,
bit_length: usize,
name: &str,
) -> (Pointer<'ctx>, Pointer<'ctx>) {
let buffer_pointer = self.build_alloca(self.integer_type(bit_length), name);
let symbol = match bit_length {
revive_common::BIT_LENGTH_WORD => GLOBAL_I256_SIZE,
revive_common::BIT_LENGTH_ETH_ADDRESS => GLOBAL_I160_SIZE,
revive_common::BIT_LENGTH_BLOCK_NUMBER => GLOBAL_I64_SIZE,
_ => panic!("invalid stack parameter bit width: {bit_length}"),
};
let length_pointer = self.get_global(symbol).expect("should be declared");
(buffer_pointer, length_pointer.into())
}
/// Load the integer at given pointer and zero extend it to the VM word size.
pub fn build_load_word(
/// Load the address at given pointer and zero extend it to the VM word size.
pub fn build_load_address(
&self,
pointer: Pointer<'ctx>,
bit_length: usize,
name: &str,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
let value = self.build_load(
pointer.cast(self.integer_type(bit_length)),
&format!("load_{name}"),
)?;
let value_extended = self.builder().build_int_z_extend(
value.into_int_value(),
self.word_type(),
&format!("zext_{name}"),
)?;
Ok(value_extended.as_basic_value_enum())
let address = self.build_byte_swap(self.build_load(pointer, "address_pointer")?)?;
Ok(self
.builder()
.build_int_z_extend(address.into_int_value(), self.word_type(), "address_zext")?
.into())
}
/// Builds a stack load instruction.
+49 -39
View File
@@ -7,16 +7,14 @@ use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm_const::runtime_api;
static STATIC_CALL_FLAG: u32 = 0b0001_0000;
const STATIC_CALL_FLAG: u32 = 0b0001_0000;
const REENTRANT_CALL_FLAG: u32 = 0b0000_1000;
/// Translates a contract call.
///
/// If the `simulation_address` is specified, the call is
/// substituted with another instruction according to the specification.
#[allow(clippy::too_many_arguments)]
pub fn call<'ctx, D>(
context: &mut Context<'ctx, D>,
gas: inkwell::values::IntValue<'ctx>,
_gas: inkwell::values::IntValue<'ctx>,
address: inkwell::values::IntValue<'ctx>,
value: Option<inkwell::values::IntValue<'ctx>>,
input_offset: inkwell::values::IntValue<'ctx>,
@@ -29,59 +27,71 @@ pub fn call<'ctx, D>(
where
D: Dependency + Clone,
{
let address_pointer = context.build_alloca(context.word_type(), "address_ptr");
context.build_store(address_pointer, address)?;
let address_type = context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS);
let address_pointer = context.build_alloca_at_entry(address_type, "address_pointer");
let address_truncated =
context
.builder()
.build_int_truncate(address, address_type, "address_truncated")?;
let address_swapped = context.build_byte_swap(address_truncated.into())?;
context.build_store(address_pointer, address_swapped)?;
let value_pointer = if let Some(value) = value {
let value_pointer = context.build_alloca(context.value_type(), "value");
context.build_store(value_pointer, value)?;
value_pointer
} else {
context.sentinel_pointer()
};
let value = value.unwrap_or_else(|| context.word_const(0));
let value_pointer = context.build_alloca_at_entry(context.word_type(), "value_pointer");
context.build_store(value_pointer, value)?;
let input_offset = context.safe_truncate_int_to_xlen(input_offset)?;
let input_length = context.safe_truncate_int_to_xlen(input_length)?;
let output_offset = context.safe_truncate_int_to_xlen(output_offset)?;
let output_length = context.safe_truncate_int_to_xlen(output_length)?;
let gas = context
// TODO: What to supply here? Is there a weight to gas?
let _gas = context
.builder()
.build_int_truncate(gas, context.integer_type(64), "gas")?;
let flags = if static_call { STATIC_CALL_FLAG } else { 0 };
.build_int_truncate(_gas, context.integer_type(64), "gas")?;
let input_pointer = context.build_heap_gep(input_offset, input_length)?;
let output_pointer = context.build_heap_gep(output_offset, output_length)?;
let output_length_pointer = context.get_global(crate::polkavm::GLOBAL_RETURN_DATA_SIZE)?;
context.build_store(output_length_pointer.into(), output_length)?;
let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length");
context.build_store(output_length_pointer, output_length)?;
let argument_pointer = revive_runtime_api::calling_convention::Spill::new(
let flags = if static_call {
REENTRANT_CALL_FLAG | STATIC_CALL_FLAG
} else {
REENTRANT_CALL_FLAG
};
let flags = context.xlen_type().const_int(flags as u64, false);
let argument_type = revive_runtime_api::calling_convention::call(context.llvm());
let argument_pointer = context.build_alloca_at_entry(argument_type, "call_arguments");
let arguments = &[
flags.as_basic_value_enum(),
address_pointer.value.as_basic_value_enum(),
context.integer_const(64, 0).as_basic_value_enum(),
context.integer_const(64, 0).as_basic_value_enum(),
context.sentinel_pointer().value.as_basic_value_enum(),
value_pointer.value.as_basic_value_enum(),
input_pointer.value.as_basic_value_enum(),
input_length.as_basic_value_enum(),
output_pointer.value.as_basic_value_enum(),
output_length_pointer.value.as_basic_value_enum(),
];
revive_runtime_api::calling_convention::spill(
context.builder(),
revive_runtime_api::calling_convention::call(context.llvm()),
"call_arguments",
)?
.next(context.xlen_type().const_int(flags as u64, false))?
.next(address_pointer.value)?
.next(gas)?
.skip()
.next(context.sentinel_pointer().value)?
.next(value_pointer.value)?
.next(input_pointer.value)?
.next(input_length)?
.next(output_pointer.value)?
.next(output_length_pointer.value)?
.done();
argument_pointer.value,
argument_type,
arguments,
)?;
let name = runtime_api::imports::CALL;
let arguments = context.builder().build_ptr_to_int(
argument_pointer,
let argument_pointer = context.builder().build_ptr_to_int(
argument_pointer.value,
context.xlen_type(),
"argument_pointer",
"call_argument_pointer",
)?;
let success = context
.build_runtime_call(name, &[arguments.into()])
.build_runtime_call(name, &[argument_pointer.into()])
.unwrap_or_else(|| panic!("{name} should return a value"))
.into_int_value();
+10 -6
View File
@@ -149,11 +149,7 @@ where
runtime_api::imports::ADDRESS,
&[pointer.to_int(context).into()],
);
let value = context.build_byte_swap(context.build_load(pointer, "address")?)?;
Ok(context
.builder()
.build_int_z_extend(value.into_int_value(), context.word_type(), "address_zext")?
.into())
context.build_load_address(pointer)
}
/// Translates the `caller` instruction.
@@ -163,5 +159,13 @@ pub fn caller<'ctx, D>(
where
D: Dependency + Clone,
{
context.build_runtime_call_to_getter(runtime_api::imports::CALLER)
let pointer = context.build_alloca_at_entry(
context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS),
"address_output",
);
context.build_runtime_call(
runtime_api::imports::CALLER,
&[pointer.to_int(context).into()],
);
context.build_load_address(pointer)
}
+54 -54
View File
@@ -9,26 +9,14 @@ use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm_const::runtime_api;
/// Translates the contract `create` instruction.
/// The instruction is simulated by a call to a system contract.
/// Translates the contract `create` and `create2` instruction.
///
/// A `salt` value of `None` is equivalent to `create1`.
pub fn create<'ctx, D>(
context: &mut Context<'ctx, D>,
value: inkwell::values::IntValue<'ctx>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
self::create2(context, value, input_offset, input_length, None)
}
/// Translates the contract `create2` instruction.
pub fn create2<'ctx, D>(
context: &mut Context<'ctx, D>,
value: inkwell::values::IntValue<'ctx>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
salt: Option<inkwell::values::IntValue<'ctx>>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
@@ -37,9 +25,6 @@ where
let input_offset = context.safe_truncate_int_to_xlen(input_offset)?;
let input_length = context.safe_truncate_int_to_xlen(input_length)?;
let value_pointer = context.build_alloca(context.value_type(), "value");
context.build_store(value_pointer, value)?;
let code_hash_pointer = context.build_heap_gep(input_offset, input_length)?;
let input_data_pointer = context.build_gep(
@@ -48,53 +33,68 @@ where
.xlen_type()
.const_int(revive_common::BYTE_LENGTH_WORD as u64, false)],
context.byte_type(),
"value_ptr_parameter_offset",
"input_ptr_parameter_offset",
);
let salt_pointer = context.build_alloca(context.word_type(), "salt");
context.build_store(salt_pointer, salt.unwrap_or_else(|| context.word_const(0)))?;
let value_pointer = context.build_alloca_at_entry(context.value_type(), "transferred_value");
context.build_store(value_pointer, value)?;
let (address_pointer, address_length_pointer) =
context.build_stack_parameter(revive_common::BIT_LENGTH_ETH_ADDRESS, "address_pointer");
let salt_pointer = match salt {
Some(salt) => {
let salt_pointer = context.build_alloca_at_entry(context.word_type(), "salt_pointer");
context.build_store(salt_pointer, salt)?;
salt_pointer
}
None => context.sentinel_pointer(),
};
let address_pointer = context.build_alloca_at_entry(
context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS),
"address_pointer",
);
context.build_store(address_pointer, context.word_const(0))?;
let argument_pointer = revive_runtime_api::calling_convention::Spill::new(
let argument_type = revive_runtime_api::calling_convention::instantiate(context.llvm());
let argument_pointer = context.build_alloca_at_entry(argument_type, "instantiate_arguments");
let arguments = &[
code_hash_pointer.value.as_basic_value_enum(),
context.integer_const(64, 0).as_basic_value_enum(),
context.integer_const(64, 0).as_basic_value_enum(),
context.sentinel_pointer().value.as_basic_value_enum(),
value_pointer.value.as_basic_value_enum(),
input_data_pointer.value.as_basic_value_enum(),
input_length.as_basic_value_enum(),
address_pointer.value.as_basic_value_enum(),
context.sentinel_pointer().value.as_basic_value_enum(),
context.sentinel_pointer().value.as_basic_value_enum(),
salt_pointer.value.as_basic_value_enum(),
];
revive_runtime_api::calling_convention::spill(
context.builder(),
revive_runtime_api::calling_convention::instantiate(context.llvm()),
"create2_arguments",
)?
.next(code_hash_pointer.value)?
.skip()
.skip()
.next(context.sentinel_pointer().value)?
.next(value_pointer.value)?
.next(input_data_pointer.value)?
.next(input_length)?
.next(address_pointer.value)?
.next(address_length_pointer.value)?
.next(context.sentinel_pointer().value)?
.next(context.sentinel_pointer().value)?
.next(salt_pointer.value)?
.next(
context
.xlen_type()
.const_int(revive_common::BYTE_LENGTH_WORD as u64, false),
)?
.done();
argument_pointer.value,
argument_type,
arguments,
)?;
let argument_pointer = context.builder().build_ptr_to_int(
argument_pointer.value,
context.xlen_type(),
"instantiate_argument_pointer",
)?;
context.build_runtime_call(
runtime_api::imports::INSTANTIATE,
&[context
.builder()
.build_ptr_to_int(argument_pointer, context.xlen_type(), "argument_pointer")?
.into()],
&[argument_pointer.into()],
);
context.build_load_word(
address_pointer,
revive_common::BIT_LENGTH_ETH_ADDRESS,
"address",
)
let address = context.build_byte_swap(context.build_load(address_pointer, "address")?)?;
Ok(context
.builder()
.build_int_z_extend(
address.into_int_value(),
context.word_type(),
"address_zext",
)?
.into())
}
/// Translates the contract hash instruction, which is actually used to set the hash of the contract
@@ -29,11 +29,7 @@ where
runtime_api::imports::VALUE_TRANSFERRED,
&[output_pointer.to_int(context).into()],
);
context.build_load_word(
output_pointer,
revive_common::BIT_LENGTH_VALUE,
"value_transferred",
)
context.build_load(output_pointer, "value_transferred")
}
/// Translates the `balance` instructions.
@@ -1,7 +1,5 @@
//! Translates the return data instructions.
use inkwell::values::BasicValue;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm_const::runtime_api;
@@ -13,13 +11,17 @@ pub fn size<'ctx, D>(
where
D: Dependency + Clone,
{
let value = context
.get_global_value(crate::polkavm::GLOBAL_RETURN_DATA_SIZE)?
.into_int_value();
Ok(context
.builder()
.build_int_z_extend(value, context.word_type(), "calldatasize_extended")?
.as_basic_value_enum())
let output_pointer = context.build_alloca_at_entry(context.word_type(), "return_data_size");
let output_pointer_parameter = context.builder().build_ptr_to_int(
output_pointer.value,
context.xlen_type(),
"return_data_copy_output_pointer",
)?;
context.build_runtime_call(
runtime_api::imports::RETURNDATASIZE,
&[output_pointer_parameter.into()],
);
context.build_load(output_pointer, "return_data_size_load")
}
/// Translates the return data copy, trapping if
@@ -39,16 +41,49 @@ where
let destination_offset = context.safe_truncate_int_to_xlen(destination_offset)?;
let size = context.safe_truncate_int_to_xlen(size)?;
let destination_offset = context.builder().build_ptr_to_int(
let output_pointer = context.builder().build_ptr_to_int(
context.build_heap_gep(destination_offset, size)?.value,
context.xlen_type(),
"destination_offset",
"return_data_copy_output_pointer",
)?;
let output_length_pointer = context.build_alloca_at_entry(
context.xlen_type(),
"return_data_copy_output_length_pointer",
);
context.build_store(output_length_pointer, size)?;
let output_length_pointer_int = context.builder().build_ptr_to_int(
output_length_pointer.value,
context.xlen_type(),
"return_data_copy_output_length_pointer_int",
)?;
context.build_runtime_call(
runtime_api::imports::RETURNDATACOPY,
&[destination_offset.into(), source_offset.into(), size.into()],
&[
output_pointer.into(),
output_length_pointer_int.into(),
source_offset.into(),
],
);
// Trap on OOB (will be different in EOF code)
let overflow_block = context.append_basic_block("return_data_overflow");
let non_overflow_block = context.append_basic_block("return_data_non_overflow");
let is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
size,
context
.build_load(output_length_pointer, "bytes_written")?
.into_int_value(),
"is_overflow",
)?;
context.build_conditional_branch(is_overflow, overflow_block, non_overflow_block)?;
context.set_basic_block(overflow_block);
context.build_call(context.intrinsics().trap, &[], "invalid_trap");
context.build_unreachable();
context.set_basic_block(non_overflow_block);
Ok(())
}