mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-04-24 20:37:59 +00:00
disable sbrk and emulate EVM linear memory internally (#76)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com> Signed-off-by: xermicus <cyrill@parity.io>
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"Baseline": 962,
|
||||
"Computation": 4463,
|
||||
"DivisionArithmetics": 40756,
|
||||
"ERC20": 54427,
|
||||
"Events": 1792,
|
||||
"FibonacciIterative": 3065,
|
||||
"Flipper": 3665,
|
||||
"SHA1": 32923
|
||||
"Baseline": 983,
|
||||
"Computation": 4207,
|
||||
"DivisionArithmetics": 40509,
|
||||
"ERC20": 47068,
|
||||
"Events": 1791,
|
||||
"FibonacciIterative": 3044,
|
||||
"Flipper": 3405,
|
||||
"SHA1": 33583
|
||||
}
|
||||
@@ -4,6 +4,7 @@ pragma solidity ^0.8;
|
||||
|
||||
/* runner.json
|
||||
{
|
||||
"differential": true,
|
||||
"actions": [
|
||||
{
|
||||
"Instantiate": {
|
||||
@@ -23,12 +24,6 @@ pragma solidity ^0.8;
|
||||
"data": "f016832c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"VerifyCall": {
|
||||
"success": true,
|
||||
"output": "0000000000000000000000000000000000000000000000000000000000000060"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Call": {
|
||||
"dest": {
|
||||
@@ -36,12 +31,6 @@ pragma solidity ^0.8;
|
||||
},
|
||||
"data": "f4a63aa5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"VerifyCall": {
|
||||
"success": true,
|
||||
"output": "0000000000000000000000000000000000000000000000000000000000000084"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ pub mod exports {
|
||||
}
|
||||
|
||||
pub mod imports {
|
||||
pub static SBRK: &str = "__sbrk_internal";
|
||||
|
||||
pub static MEMORY_SIZE: &str = "__msize";
|
||||
|
||||
pub static ADDRESS: &str = "address";
|
||||
|
||||
pub static BALANCE: &str = "balance";
|
||||
@@ -57,7 +61,9 @@ pub mod imports {
|
||||
|
||||
/// All imported runtime API symbols.
|
||||
/// Useful for configuring common attributes and linkage.
|
||||
pub static IMPORTS: [&str; 21] = [
|
||||
pub static IMPORTS: [&str; 23] = [
|
||||
SBRK,
|
||||
MEMORY_SIZE,
|
||||
ADDRESS,
|
||||
BALANCE,
|
||||
BALANCE_OF,
|
||||
@@ -81,6 +87,3 @@ pub mod imports {
|
||||
VALUE_TRANSFERRED,
|
||||
];
|
||||
}
|
||||
|
||||
/// PolkaVM __sbrk API symbol to extend the heap memory.
|
||||
pub static SBRK: &str = "__sbrk";
|
||||
|
||||
@@ -61,7 +61,10 @@ impl Entry {
|
||||
context
|
||||
.get_global(crate::polkavm::GLOBAL_HEAP_MEMORY_POINTER)?
|
||||
.into(),
|
||||
context.build_sbrk(context.integer_const(crate::polkavm::XLEN, 0))?,
|
||||
context.build_sbrk(
|
||||
context.xlen_type().const_zero(),
|
||||
context.xlen_type().const_zero(),
|
||||
)?,
|
||||
)?;
|
||||
|
||||
context.set_global(
|
||||
|
||||
@@ -1088,16 +1088,20 @@ where
|
||||
Ok(truncated)
|
||||
}
|
||||
|
||||
/// Build a call to PolkaVM `sbrk` for extending the heap by `size`.
|
||||
/// Build a call to PolkaVM `sbrk` for extending the heap from offset by `size`.
|
||||
/// The allocation is aligned to 32 bytes.
|
||||
///
|
||||
/// This emulates the EVM linear memory until the runtime supports metered memory.
|
||||
pub fn build_sbrk(
|
||||
&self,
|
||||
offset: inkwell::values::IntValue<'ctx>,
|
||||
size: inkwell::values::IntValue<'ctx>,
|
||||
) -> anyhow::Result<inkwell::values::PointerValue<'ctx>> {
|
||||
Ok(self
|
||||
.builder()
|
||||
.build_call(
|
||||
self.runtime_api_method(runtime_api::SBRK),
|
||||
&[size.into()],
|
||||
self.runtime_api_method(runtime_api::imports::SBRK),
|
||||
&[offset.into(), size.into()],
|
||||
"call_sbrk",
|
||||
)?
|
||||
.try_as_basic_value()
|
||||
@@ -1106,14 +1110,29 @@ where
|
||||
.into_pointer_value())
|
||||
}
|
||||
|
||||
/// Call PolkaVM `sbrk` for extending the heap by `size`,
|
||||
/// Build a call to PolkaVM `msize` for querying the linear memory size.
|
||||
pub fn build_msize(&self) -> anyhow::Result<inkwell::values::IntValue<'ctx>> {
|
||||
Ok(self
|
||||
.builder()
|
||||
.build_call(
|
||||
self.runtime_api_method(runtime_api::imports::MEMORY_SIZE),
|
||||
&[],
|
||||
"call_msize",
|
||||
)?
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.expect("sbrk returns an int")
|
||||
.into_int_value())
|
||||
}
|
||||
|
||||
/// Call PolkaVM `sbrk` for extending the heap by `offset` + `size`,
|
||||
/// trapping the contract if the call failed.
|
||||
/// Returns the end of memory pointer.
|
||||
pub fn build_heap_alloc(
|
||||
&self,
|
||||
offset: inkwell::values::IntValue<'ctx>,
|
||||
size: inkwell::values::IntValue<'ctx>,
|
||||
) -> anyhow::Result<inkwell::values::PointerValue<'ctx>> {
|
||||
let end_of_memory = self.build_sbrk(size)?;
|
||||
) -> anyhow::Result<()> {
|
||||
let end_of_memory = self.build_sbrk(offset, size)?;
|
||||
let return_is_nil = self.builder().build_int_compare(
|
||||
inkwell::IntPredicate::EQ,
|
||||
end_of_memory,
|
||||
@@ -1131,7 +1150,7 @@ where
|
||||
|
||||
self.set_basic_block(continue_block);
|
||||
|
||||
Ok(end_of_memory)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a pointer to `offset` into the heap, allocating
|
||||
@@ -1146,40 +1165,12 @@ where
|
||||
assert_eq!(offset.get_type(), self.xlen_type());
|
||||
assert_eq!(length.get_type(), self.xlen_type());
|
||||
|
||||
self.build_heap_alloc(offset, length)?;
|
||||
|
||||
let heap_start = self
|
||||
.get_global(crate::polkavm::GLOBAL_HEAP_MEMORY_POINTER)?
|
||||
.value
|
||||
.as_pointer_value();
|
||||
let heap_end = self.build_sbrk(self.integer_const(crate::polkavm::XLEN, 0))?;
|
||||
let value_end = self.build_gep(
|
||||
Pointer::new(self.byte_type(), AddressSpace::Stack, heap_start),
|
||||
&[self.builder().build_int_nuw_add(offset, length, "end")?],
|
||||
self.byte_type(),
|
||||
"heap_end_gep",
|
||||
);
|
||||
let is_out_of_bounds = self.builder().build_int_compare(
|
||||
inkwell::IntPredicate::UGT,
|
||||
value_end.value,
|
||||
heap_end,
|
||||
"is_value_overflowing_heap",
|
||||
)?;
|
||||
|
||||
let out_of_bounds_block = self.append_basic_block("heap_offset_out_of_bounds");
|
||||
let heap_offset_block = self.append_basic_block("build_heap_pointer");
|
||||
self.build_conditional_branch(is_out_of_bounds, out_of_bounds_block, heap_offset_block)?;
|
||||
|
||||
self.set_basic_block(out_of_bounds_block);
|
||||
let size = self.builder().build_int_nuw_sub(
|
||||
self.builder()
|
||||
.build_ptr_to_int(value_end.value, self.xlen_type(), "value_end")?,
|
||||
self.builder()
|
||||
.build_ptr_to_int(heap_end, self.xlen_type(), "heap_end")?,
|
||||
"heap_alloc_size",
|
||||
)?;
|
||||
self.build_heap_alloc(size)?;
|
||||
self.build_unconditional_branch(heap_offset_block);
|
||||
|
||||
self.set_basic_block(heap_offset_block);
|
||||
Ok(self.build_gep(
|
||||
Pointer::new(self.byte_type(), AddressSpace::Stack, heap_start),
|
||||
&[offset],
|
||||
|
||||
@@ -107,33 +107,6 @@ where
|
||||
Ok(context.word_const(0).as_basic_value_enum())
|
||||
}
|
||||
|
||||
/// Translates the `msize` instruction.
|
||||
pub fn msize<'ctx, D>(
|
||||
context: &mut Context<'ctx, D>,
|
||||
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
let heap_end = context.build_sbrk(context.xlen_type().const_zero())?;
|
||||
let heap_start = context
|
||||
.get_global(crate::polkavm::GLOBAL_HEAP_MEMORY_POINTER)?
|
||||
.value
|
||||
.as_pointer_value();
|
||||
let heap_size = context.builder().build_int_nuw_sub(
|
||||
context
|
||||
.builder()
|
||||
.build_ptr_to_int(heap_end, context.xlen_type(), "heap_end")?,
|
||||
context
|
||||
.builder()
|
||||
.build_ptr_to_int(heap_start, context.xlen_type(), "heap_start")?,
|
||||
"heap_size",
|
||||
)?;
|
||||
Ok(context
|
||||
.builder()
|
||||
.build_int_z_extend(heap_size, context.word_type(), "heap_size_extended")?
|
||||
.as_basic_value_enum())
|
||||
}
|
||||
|
||||
/// Translates the `address` instruction.
|
||||
pub fn address<'ctx, D>(
|
||||
context: &mut Context<'ctx, D>,
|
||||
|
||||
@@ -1,10 +1,29 @@
|
||||
//! Translates the heap memory operations.
|
||||
|
||||
use inkwell::values::BasicValue;
|
||||
|
||||
use crate::polkavm::context::address_space::AddressSpace;
|
||||
use crate::polkavm::context::pointer::Pointer;
|
||||
use crate::polkavm::context::Context;
|
||||
use crate::polkavm::Dependency;
|
||||
|
||||
/// Translates the `msize` instruction.
|
||||
pub fn msize<'ctx, D>(
|
||||
context: &mut Context<'ctx, D>,
|
||||
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
Ok(context
|
||||
.builder()
|
||||
.build_int_z_extend(
|
||||
context.build_msize()?,
|
||||
context.word_type(),
|
||||
"heap_size_extended",
|
||||
)?
|
||||
.as_basic_value_enum())
|
||||
}
|
||||
|
||||
/// Translates the `mload` instruction.
|
||||
/// Uses the main heap.
|
||||
pub fn load<'ctx, D>(
|
||||
|
||||
@@ -289,16 +289,10 @@ impl Specs {
|
||||
else {
|
||||
panic!("the differential runner requires Code::Solidity source");
|
||||
};
|
||||
|
||||
assert_ne!(
|
||||
solc_optimizer,
|
||||
Some(false),
|
||||
"solc_optimizer must be enabled in differntial mode"
|
||||
);
|
||||
assert_ne!(
|
||||
pipeline,
|
||||
Some(revive_solidity::SolcPipeline::EVMLA),
|
||||
"yul pipeline must be enabled in differntial mode"
|
||||
"yul pipeline must be enabled in differential mode"
|
||||
);
|
||||
assert!(
|
||||
salt.0.is_none(),
|
||||
@@ -311,9 +305,11 @@ impl Specs {
|
||||
);
|
||||
|
||||
let deploy_code = match std::fs::read_to_string(&path) {
|
||||
Ok(solidity_source) => {
|
||||
hex::encode(compile_evm_deploy_code(&contract, &solidity_source))
|
||||
}
|
||||
Ok(solidity_source) => hex::encode(compile_evm_deploy_code(
|
||||
&contract,
|
||||
&solidity_source,
|
||||
solc_optimizer.unwrap_or(true),
|
||||
)),
|
||||
Err(err) => panic!(
|
||||
"failed to read solidity source\n . path: '{}'\n . error: {:?}",
|
||||
path.display(),
|
||||
|
||||
@@ -3,9 +3,34 @@
|
||||
|
||||
#include "polkavm_guest.h"
|
||||
|
||||
|
||||
// Missing builtins
|
||||
|
||||
#define EVM_WORD_SIZE 32
|
||||
#define ALIGN(size) ((size + EVM_WORD_SIZE - 1) & ~(EVM_WORD_SIZE - 1))
|
||||
#define MAX_MEMORY_SIZE (64 * 1024)
|
||||
static char __memory[MAX_MEMORY_SIZE];
|
||||
static uint32_t __memory_size = 0;
|
||||
|
||||
void * __sbrk_internal(uint32_t offset, uint32_t size) {
|
||||
if (offset >= MAX_MEMORY_SIZE || size > MAX_MEMORY_SIZE) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint32_t new_size = ALIGN(offset + size);
|
||||
if (new_size > MAX_MEMORY_SIZE) {
|
||||
return NULL;
|
||||
}
|
||||
if (new_size > __memory_size) {
|
||||
__memory_size = new_size;
|
||||
}
|
||||
|
||||
return (void *)&__memory[__memory_size];
|
||||
}
|
||||
|
||||
uint32_t __msize() {
|
||||
return __memory_size;
|
||||
}
|
||||
|
||||
void * memset(void *b, int c, size_t len) {
|
||||
uint8_t *dest = b;
|
||||
while (len-- > 0) *dest++ = c;
|
||||
@@ -37,18 +62,6 @@ void * memmove(void *dst, const void *src, size_t n) {
|
||||
return dst;
|
||||
}
|
||||
|
||||
void * __sbrk(uint32_t size) {
|
||||
uint32_t address;
|
||||
__asm__ __volatile__(
|
||||
".insn r 0xb, 1, 0, %[dst], %[sz], zero"
|
||||
: [dst] "=r" (address)
|
||||
: [sz] "ir" (size)
|
||||
:
|
||||
);
|
||||
return (void *)address;
|
||||
}
|
||||
|
||||
|
||||
// Imports
|
||||
|
||||
POLKAVM_IMPORT(void, input, uint32_t, uint32_t)
|
||||
|
||||
@@ -1206,7 +1206,7 @@ where
|
||||
anyhow::bail!("The `BLOBBASEFEE` instruction is not supported until zkVM v1.5.0");
|
||||
}
|
||||
InstructionName::MSIZE => {
|
||||
revive_llvm_context::polkavm_evm_contract_context::msize(context).map(Some)
|
||||
revive_llvm_context::polkavm_evm_memory::msize(context).map(Some)
|
||||
}
|
||||
|
||||
InstructionName::CALLCODE => {
|
||||
|
||||
@@ -276,18 +276,26 @@ pub fn compile_blob(contract_name: &str, source_code: &str) -> Vec<u8> {
|
||||
/// Compile the EVM bin-runtime of `contract_name` found in given `source_code`.
|
||||
/// The `solc` optimizer will be enabled
|
||||
pub fn compile_evm_bin_runtime(contract_name: &str, source_code: &str) -> Vec<u8> {
|
||||
compile_evm(contract_name, source_code, true)
|
||||
compile_evm(contract_name, source_code, true, true)
|
||||
}
|
||||
|
||||
/// Compile the EVM bin of `contract_name` found in given `source_code`.
|
||||
/// The `solc` optimizer will be enabled
|
||||
pub fn compile_evm_deploy_code(contract_name: &str, source_code: &str) -> Vec<u8> {
|
||||
compile_evm(contract_name, source_code, false)
|
||||
pub fn compile_evm_deploy_code(
|
||||
contract_name: &str,
|
||||
source_code: &str,
|
||||
solc_optimizer_enabled: bool,
|
||||
) -> Vec<u8> {
|
||||
compile_evm(contract_name, source_code, solc_optimizer_enabled, false)
|
||||
}
|
||||
|
||||
fn compile_evm(contract_name: &str, source_code: &str, runtime: bool) -> Vec<u8> {
|
||||
fn compile_evm(
|
||||
contract_name: &str,
|
||||
source_code: &str,
|
||||
solc_optimizer_enabled: bool,
|
||||
runtime: bool,
|
||||
) -> Vec<u8> {
|
||||
let pipeline = SolcPipeline::Yul;
|
||||
let solc_optimizer_enabled = true;
|
||||
let id = CachedBlob {
|
||||
contract_name: contract_name.to_owned(),
|
||||
pipeline,
|
||||
|
||||
@@ -963,9 +963,7 @@ impl FunctionCall {
|
||||
location
|
||||
);
|
||||
}
|
||||
Name::MSize => {
|
||||
revive_llvm_context::polkavm_evm_contract_context::msize(context).map(Some)
|
||||
}
|
||||
Name::MSize => revive_llvm_context::polkavm_evm_memory::msize(context).map(Some),
|
||||
|
||||
Name::Verbatim {
|
||||
input_size,
|
||||
|
||||
Reference in New Issue
Block a user