Skip to content

Commit

Permalink
refactor(constructor): storage constructor instead of language feature (
Browse files Browse the repository at this point in the history
#229)

* fix(codegen): hardcoded creation bytecode

* chore(codegen): temp remove constructor

* feat(compiler): constructor with empty init code

* chore(clippy): make clippy happy

* chore(deps): remove depbot and update deps

* ci(dep): make depbot invalid

* feat(example): empty constructor

* feat(zink): generate wasm handler for constructor

* chore(codegen): get back stack balance check

* refactor(compiler): remove constructor handler

* feat(compiler): move constructor to artifact

* feat(compiler): construct preset storage just in time

* chore(clippy): make clippy happy

* chore(zink): remove language item constructor
  • Loading branch information
clearloop authored Sep 14, 2024
1 parent 84fa809 commit d096f5e
Show file tree
Hide file tree
Showing 21 changed files with 133 additions and 196 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ thiserror.workspace = true
tracing.workspace = true
wasmparser.workspace = true
zabi = { workspace = true, features = [ "hex", "selector", "syn" ] }
hex.workspace = true
2 changes: 1 addition & 1 deletion codegen/src/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{Buffer, Error, Result};
use opcodes::{for_each_shanghai_operator, OpCode as _, ShangHai as OpCode};

/// Low level assembler implementation for EVM.
#[derive(Default, Clone)]
#[derive(Default, Clone, Debug)]
pub struct Assembler {
/// Buffer of the assembler.
buffer: Buffer,
Expand Down
140 changes: 48 additions & 92 deletions codegen/src/codegen/constructor.rs
Original file line number Diff line number Diff line change
@@ -1,118 +1,74 @@
//! Contract constructor.
use crate::{wasm::ToLSBytes, Buffer, Function, JumpTable, MacroAssembler, Result};
use wasmparser::FuncType;
use crate::{wasm::ToLSBytes, Buffer, MacroAssembler, Result};
use smallvec::SmallVec;
use std::collections::HashMap;

/// Initial storage of contracts
pub type InitStorage = HashMap<SmallVec<[u8; 32]>, SmallVec<[u8; 32]>>;

/// Contract constructor.
///
/// # Bytecode
/// - `CREATE` instruction
/// - `INIT_CODE`
/// - `INIT_LOGIC`
/// - `RETURN RUNTIME_BYTECODE`
/// - `RUNTIME_BYTECODE`
///
/// TODO: introduce ABI for constructor
#[derive(Default, Debug, Clone)]
pub struct Constructor {
/// Code generator.
pub masm: MacroAssembler,
/// Code buffer.
pub init_code: Buffer,
/// Runtime bytecode.
pub runtime_bytecode: Buffer,
masm: MacroAssembler,
}

impl Constructor {
/// Create a new constructor.
pub fn new(constructor: Option<FuncType>, runtime_bytecode: Buffer) -> Result<Self> {
let mut init_code = Buffer::new();
if let Some(constructor) = constructor {
let codegen = Function::new(
Default::default(),
constructor,
// No `return` instruction in the generated code.
false,
)?;

let mut jump_table = JumpTable::default();
init_code = codegen.finish(&mut jump_table, 0)?;
jump_table.relocate(&mut init_code)?;
};
/// preset storage for the contract
pub fn storage(&mut self, mapping: InitStorage) -> Result<()> {
for (key, value) in mapping.into_iter() {
self.masm.push(&value)?;
self.masm.push(&key)?;
self.masm._sstore()?;
}

Ok(Self {
masm: MacroAssembler::default(),
init_code,
runtime_bytecode,
})
Ok(())
}

/// Concat the constructor code.
///
/// Here we override the memory totally with
/// the runtime bytecode.
pub fn finish(&mut self) -> Result<Buffer> {
let init_code_length = self.init_code.len();
let runtime_bytecode_length = self.runtime_bytecode.len();
let return_instr_length =
Self::return_instr_length(init_code_length, runtime_bytecode_length);

// Copy init code and runtime bytecode to memory from offset 0.
//
// 1. code size ( init_code + instr_return + runtime_bytecode )
// 2. byte offset of code which is fixed to N.
// 3. destination offset which is fixed to 0.
{
self.masm.push(
&(init_code_length + return_instr_length + runtime_bytecode_length).to_ls_bytes(),
)?;
// # SAFETY
//
// The length of the most significiant bytes of
// the bytecode offset is fixed to 1.
self.masm
.push(&((self.masm.pc_offset() as usize + 9).to_ls_bytes()))?;
self.masm._push0()?;
self.masm._codecopy()?;
}
pub fn finish(&self, runtime_bytecode: Buffer) -> Result<Buffer> {
let init_code = self.masm.buffer();
let init_code_len = init_code.len();
let runtime_bytecode_len = runtime_bytecode.len();
let runtime_bytecode_size = runtime_bytecode_len.to_ls_bytes();
let runtime_bytecode_offset =
Self::runtime_bytcode_offset(init_code_len, runtime_bytecode_size.len());

// Process instruction `CREATE`
{
self.masm._push0()?;
self.masm._push0()?;
self.masm._push0()?;
self.masm._calldataload()?;
self.masm._create()?;
}

self.masm.buffer_mut().extend_from_slice(&self.init_code);
let mut masm = self.masm.clone();

// Process `RETURN`.
//
// 1. size of the runtime bytecode
// 2. offset of the runtime bytecode in memory
{
self.masm.push(&runtime_bytecode_length.to_ls_bytes())?;
self.masm
.push(&(init_code_length + return_instr_length).to_ls_bytes())?;
self.masm.asm._return()?;
}
// 1. copy runtime bytecode to memory
masm.push(&runtime_bytecode_size)?; // code size
masm.push(&runtime_bytecode_offset.to_ls_bytes())?; // code offset
masm._push0()?; // dest offset in memory
masm._codecopy()?;

self.masm
.buffer_mut()
.extend_from_slice(&self.runtime_bytecode);
// 2. return runtime bytecode
masm.push(&runtime_bytecode_size)?; // code size
masm._push0()?; // memory offset
masm.asm._return()?;
masm.buffer_mut().extend_from_slice(&runtime_bytecode);

Ok(self.masm.buffer().into())
Ok(masm.buffer().into())
}

/// Returns the length of instructions.
fn return_instr_length(init_code_length: usize, runtime_bytecode_length: usize) -> usize {
let mut expected_length =
runtime_bytecode_length.to_ls_bytes().len() + init_code_length.to_ls_bytes().len() + 3;

if init_code_length < 0xff && init_code_length + expected_length > 0xff {
expected_length += 1;
/// Returns the offset of runtime bytecode.
///
/// [
/// init_code,
/// pushn, runtime_bytecode_size, pushn + <offset>, push0, code_copy
/// pushn, runtime_bytecode_size, push0, return,
/// <OFFSET>
/// ]
fn runtime_bytcode_offset(init_code_len: usize, runtime_bytecode_size_len: usize) -> usize {
let mut offset = init_code_len + runtime_bytecode_size_len * 2 + 8;
if (offset <= 0xff) && (offset + offset.to_ls_bytes().len() > 0xff) {
offset += 1;
}

expected_length
offset
}
}
4 changes: 2 additions & 2 deletions codegen/src/codegen/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl Dispatcher {
[jumpdest, ret].concat()
};

*self.asm = asm;
self.asm = asm;
let ret = ExtFunc {
bytecode,
stack_in: 0,
Expand Down Expand Up @@ -174,7 +174,7 @@ impl Dispatcher {
let ret = self.asm.buffer()[asm.buffer().len()..].to_vec();
[jumpdest, ret].concat()
};
*self.asm = asm;
self.asm = asm;
let ret = ExtFunc {
bytecode,
stack_in: len,
Expand Down
2 changes: 1 addition & 1 deletion codegen/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mod function;

pub use self::{
code::{Code, ExtFunc},
constructor::Constructor,
constructor::{Constructor, InitStorage},
dispatcher::Dispatcher,
function::Function,
};
2 changes: 1 addition & 1 deletion codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

pub use crate::{
asm::Assembler,
codegen::{Code, Constructor, Dispatcher, Function},
codegen::{Code, Constructor, Dispatcher, Function, InitStorage},
control::{ControlStack, ControlStackFrame, ControlStackFrameType},
jump::JumpTable,
local::{LocalSlot, Locals},
Expand Down
2 changes: 1 addition & 1 deletion codegen/src/masm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ mod ret;
mod stack;

/// EVM MacroAssembler.
#[derive(Default)]
#[derive(Default, Debug, Clone)]
pub struct MacroAssembler {
/// Low level assembler.
pub(crate) asm: Assembler,
Expand Down
11 changes: 0 additions & 11 deletions codegen/src/wasm/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,6 @@ impl<'f> Functions<'f> {
);
}

/// Remove constructor function
pub fn remove_constructor(&mut self, exports: &Exports) -> Option<FuncType> {
for (index, export) in exports.iter() {
if export.as_str() == "constructor" {
return self.remove(index)?.sig().ok();
}
}

None
}

/// Remove all selector functions
pub fn drain_selectors(&mut self, exports: &Exports) -> Self {
let mut functions = Self::default();
Expand Down
1 change: 1 addition & 0 deletions compiler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ tracing.workspace = true
wasmparser.workspace = true
zabi.workspace = true
zingen.workspace = true
hex.workspace = true

# Optional dependencies
ccli = { workspace = true, optional = true }
Expand Down
32 changes: 1 addition & 31 deletions compiler/src/artifact.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,16 @@
//! Zink compiler artifact
use crate::{Compiler, Config};
use wasmparser::FuncType;
use crate::Config;
use zabi::Abi;
use zingen::Constructor;

/// Zink compiler artifact
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Default, Debug)]
pub struct Artifact {
/// Contract ABIs
pub abi: Vec<Abi>,
/// Bytecode of the contract.
pub bytecode: Vec<u8>,
/// Compiler configuration.
pub config: Config,
/// Runtime bytecode of the contract.
pub runtime_bytecode: Vec<u8>,
}

impl TryFrom<(Compiler, Option<FuncType>)> for Artifact {
type Error = anyhow::Error;

fn try_from(
(compiler, constructor): (Compiler, Option<FuncType>),
) -> Result<Self, Self::Error> {
let Compiler {
abi,
buffer,
config,
..
} = compiler;

let bytecode = Constructor::new(constructor, buffer.clone())?
.finish()?
.to_vec();

Ok(Self {
abi,
bytecode,
config,
runtime_bytecode: buffer.to_vec(),
})
}
}
2 changes: 1 addition & 1 deletion compiler/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl Compile {
let artifact = compiler.compile(&fs::read(&self.input)?)?;

output.parent().map(fs::create_dir_all);
fs::write(&output, artifact.bytecode)?;
fs::write(&output, artifact.runtime_bytecode)?;

if !self.abi {
return Ok(());
Expand Down
23 changes: 20 additions & 3 deletions compiler/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,35 @@ impl Compiler {
/// Returns runtime bytecode.
pub fn compile(mut self, wasm: &[u8]) -> Result<Artifact> {
let mut parser = Parser::try_from(wasm)?;
let constructor = parser.remove_constructor();

self.compile_dispatcher(&mut parser)?;
let env = parser.to_func_env();
self.compile_dispatcher(&mut parser)?;
for func in parser.funcs.into_funcs() {
self.compile_func(env.clone(), func)?;
}

self.table.code_offset(self.buffer.len() as u16);
self.table.relocate(&mut self.buffer)?;

Artifact::try_from((self, constructor)).map_err(Into::into)
self.artifact()
}

/// Generate artifact
///
/// yields runtime bytecode and construct bytecode
fn artifact(self) -> Result<Artifact> {
let Compiler {
abi,
buffer,
config,
..
} = self;

Ok(Artifact {
abi,
config,
runtime_bytecode: buffer.to_vec(),
})
}

/// Compile EVM dispatcher.
Expand Down
1 change: 1 addition & 0 deletions compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub use crate::{
config::Config,
result::{Error, Result},
};
pub use zingen::{Constructor, InitStorage};

mod artifact;
pub mod cli;
Expand Down
9 changes: 2 additions & 7 deletions compiler/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
use crate::{Error, Result};
use std::iter::IntoIterator;
use wasmparser::{
Data, DataKind, Export, ExternalKind, FuncType, Import, Operator, Payload, SectionLimited,
TypeRef, ValidPayload, Validator,
Data, DataKind, Export, ExternalKind, Import, Operator, Payload, SectionLimited, TypeRef,
ValidPayload, Validator,
};
use zingen::wasm::{Data as DataSet, Env, Exports, Functions, HostFunc, Imports};

Expand Down Expand Up @@ -104,11 +104,6 @@ impl<'p> Parser<'p> {
Ok(imports)
}

/// Returns constructor if some.
pub fn remove_constructor(&mut self) -> Option<FuncType> {
self.funcs.remove_constructor(&self.exports)
}

/// Returns full environment.
pub fn to_env(&self) -> Env {
Env {
Expand Down
2 changes: 1 addition & 1 deletion elko/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl Build {
let artifact = Compiler::new(config).compile(&wasm)?;
let dst = builder.output()?.with_extension("bin");

fs::write(dst, artifact.bytecode)?;
fs::write(dst, artifact.runtime_bytecode)?;
Ok(())
}
}
Loading

0 comments on commit d096f5e

Please sign in to comment.