Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

disable sbrk and emulate EVM linear memory internally #76

Merged
merged 6 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions crates/integration/codesize.json
Original file line number Diff line number Diff line change
@@ -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
}
13 changes: 1 addition & 12 deletions crates/integration/contracts/MSize.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8;

/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
Expand All @@ -23,25 +24,13 @@ pragma solidity ^0.8;
"data": "f016832c"
}
},
{
"VerifyCall": {
"success": true,
"output": "0000000000000000000000000000000000000000000000000000000000000060"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "f4a63aa5"
}
},
{
"VerifyCall": {
"success": true,
"output": "0000000000000000000000000000000000000000000000000000000000000084"
}
}
]
}
Expand Down
11 changes: 7 additions & 4 deletions crates/llvm-context/src/polkavm/const/runtime_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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,
Expand All @@ -81,6 +87,3 @@ pub mod imports {
VALUE_TRANSFERRED,
];
}

/// PolkaVM __sbrk API symbol to extend the heap memory.
pub static SBRK: &str = "__sbrk";
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
67 changes: 29 additions & 38 deletions crates/llvm-context/src/polkavm/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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],
Expand Down
27 changes: 0 additions & 27 deletions crates/llvm-context/src/polkavm/evm/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
19 changes: 19 additions & 0 deletions crates/llvm-context/src/polkavm/evm/memory.rs
Original file line number Diff line number Diff line change
@@ -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>(
Expand Down
16 changes: 6 additions & 10 deletions crates/runner/src/specs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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(),
Expand Down
39 changes: 26 additions & 13 deletions crates/runtime-api/src/polkavm_imports.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
18 changes: 13 additions & 5 deletions crates/solidity/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading