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
+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(())
}