From d74eb34497c051a01e0c3c4fd37709983a3646ce Mon Sep 17 00:00:00 2001 From: clearloop <26088946+clearloop@users.noreply.github.com> Date: Sun, 24 Dec 2023 09:00:38 +0800 Subject: [PATCH] refactor(codegen): introduce WASM environment (#209) * refactor(codegen): use FuncType in constructor * refactor(codegen): introduce function environment * refactor(codegen): use wasm env in dispatcher * feat(zinkc): nest config in cli --- codegen/src/codegen/constructor.rs | 17 +++------ codegen/src/codegen/dispatcher.rs | 40 ++++---------------- codegen/src/codegen/function.rs | 35 ++++++++--------- codegen/src/visitor/call.rs | 3 +- codegen/src/visitor/handlers.rs | 4 +- codegen/src/visitor/local.rs | 2 +- codegen/src/visitor/log.rs | 4 +- codegen/src/wasm/func.rs | 34 ++++++++--------- codegen/src/wasm/ie.rs | 31 --------------- codegen/src/wasm/mod.rs | 61 ++++++++++++++++++++++++------ compiler/src/cli.rs | 4 +- compiler/src/compiler.rs | 48 ++++++++++------------- compiler/src/config.rs | 20 +++++++++- compiler/src/parser.rs | 26 +++++++++++-- elko/src/build.rs | 19 +++++----- zint/src/contract.rs | 5 ++- 16 files changed, 178 insertions(+), 175 deletions(-) delete mode 100644 codegen/src/wasm/ie.rs diff --git a/codegen/src/codegen/constructor.rs b/codegen/src/codegen/constructor.rs index 16c50db56..0721d356c 100644 --- a/codegen/src/codegen/constructor.rs +++ b/codegen/src/codegen/constructor.rs @@ -1,9 +1,7 @@ //! Contract constructor. -use crate::{ - wasm::{self, ToLSBytes}, - Buffer, Function, JumpTable, MacroAssembler, Result, -}; +use crate::{wasm::ToLSBytes, Buffer, Function, JumpTable, MacroAssembler, Result}; +use wasmparser::FuncType; /// Contract constructor. /// @@ -16,25 +14,22 @@ use crate::{ /// /// TODO: introduce ABI for constructor pub struct Constructor { - /// Code buffer. - pub masm: MacroAssembler, - /// Code generator. + pub masm: MacroAssembler, + /// Code buffer. pub init_code: Buffer, - /// Runtime bytecode. pub runtime_bytecode: Buffer, } impl Constructor { /// Create a new constructor. - pub fn new(constructor: Option>, runtime_bytecode: Buffer) -> Result { + pub fn new(constructor: Option, runtime_bytecode: Buffer) -> Result { let mut init_code = Buffer::new(); if let Some(constructor) = constructor { let codegen = Function::new( - constructor.sig()?, - Default::default(), Default::default(), + constructor, // No `return` instruction in the generated code. false, )?; diff --git a/codegen/src/codegen/dispatcher.rs b/codegen/src/codegen/dispatcher.rs index 0ab85b1fc..f3ad8d239 100644 --- a/codegen/src/codegen/dispatcher.rs +++ b/codegen/src/codegen/dispatcher.rs @@ -2,7 +2,7 @@ use crate::{ codegen::code::ExtFunc, - wasm::{self, Data, Exports, Functions, Imports, ToLSBytes}, + wasm::{self, Env, Functions, ToLSBytes}, Error, JumpTable, MacroAssembler, Result, }; use wasmparser::{FuncType, Operator}; @@ -12,14 +12,10 @@ use zabi::Abi; pub struct Dispatcher<'d> { /// Code buffer pub asm: MacroAssembler, - /// Module exports - pub exports: Exports, + /// WASM environment + pub env: Env, /// Module functions pub funcs: &'d Functions<'d>, - /// Module imports - pub imports: Imports, - /// Module data - pub data: Data, /// Jump table pub table: JumpTable, /// ABI for the current function @@ -30,39 +26,19 @@ pub struct Dispatcher<'d> { impl<'d> Dispatcher<'d> { /// Create dispatcher with functions. - pub fn new(funcs: &'d Functions<'d>) -> Self { + pub fn new(env: Env, funcs: &'d Functions<'d>) -> Self { Self { asm: Default::default(), - exports: Default::default(), + env, funcs, - imports: Default::default(), - data: Default::default(), table: Default::default(), abi: Default::default(), } } - /// Set exports for the dispatcher. - pub fn exports(&mut self, exports: Exports) -> &mut Self { - self.exports = exports; - self - } - - /// Set imports for the dispatcher. - pub fn imports(&mut self, imports: Imports) -> &mut Self { - self.imports = imports; - self - } - - /// Set data for the dispatcher. - pub fn data(&mut self, data: Data) -> &mut Self { - self.data = data; - self - } - /// Query exported function from selector. fn query_func(&self, name: &str) -> Result { - for (index, export) in self.exports.iter() { + for (index, export) in self.env.exports.iter() { if export == name { return Ok(*index); } @@ -90,11 +66,11 @@ impl<'d> Dispatcher<'d> { return Err(Error::InvalidSelector); }; - if !self.imports.is_emit_abi(index) { + if !self.env.imports.is_emit_abi(index) { return Err(Error::FuncNotImported("emit_abi".into())); } - let abi = self.data.load(offset, length as usize)?; + let abi = self.env.data.load(offset, length as usize)?; Abi::from_hex(String::from_utf8_lossy(&abi)).map_err(Into::into) } diff --git a/codegen/src/codegen/function.rs b/codegen/src/codegen/function.rs index b876db278..054d28962 100644 --- a/codegen/src/codegen/function.rs +++ b/codegen/src/codegen/function.rs @@ -6,7 +6,7 @@ use crate::{ local::{LocalSlot, LocalSlotType, Locals}, masm::MacroAssembler, validator::ValidateThenVisit, - wasm::{Data, Imports}, + wasm::Env, Buffer, Error, Result, }; use wasmparser::{FuncType, FuncValidator, LocalsReader, OperatorsReader, ValidatorResources}; @@ -14,42 +14,39 @@ use wasmparser::{FuncType, FuncValidator, LocalsReader, OperatorsReader, Validat /// The code generation abstraction. pub struct Function { /// The backtrace. - pub(crate) backtrace: Backtrace, + pub backtrace: Backtrace, /// Control stack frames. - pub(crate) control: ControlStack, - /// Control stack frames. - pub(crate) dataset: Data, - /// The function environment. - pub(crate) env: FuncType, + pub control: ControlStack, + /// WASM environment. + pub env: Env, /// The defined locals for a function. - pub(crate) locals: Locals, + pub locals: Locals, /// The macro assembler. - pub(crate) masm: MacroAssembler, + pub masm: MacroAssembler, /// The jump table. - pub(crate) table: JumpTable, - /// The imported functions. - pub(crate) imports: Imports, + pub table: JumpTable, + /// The function type. + pub ty: FuncType, /// If this function is the main function. - pub(crate) is_main: bool, + pub is_main: bool, } impl Function { /// Create a new code generator. - pub fn new(env: FuncType, dataset: Data, imports: Imports, is_main: bool) -> Result { + pub fn new(env: Env, ty: FuncType, is_main: bool) -> Result { let mut params_count = 0; if !is_main { - params_count = env.params().len() as u8; + params_count = ty.params().len() as u8; } let mut codegen = Self { backtrace: Backtrace::default(), control: ControlStack::default(), - dataset, env, + ty, locals: Default::default(), masm: Default::default(), table: Default::default(), - imports, is_main, }; @@ -81,7 +78,7 @@ impl Function { let mut sp = if self.is_main { 0 } else { 1 }; // Define locals in function parameters. - for param in self.env.params() { + for param in self.ty.params() { self.locals .push(LocalSlot::new(*param, LocalSlotType::Parameter, sp)); sp += 1; @@ -128,7 +125,7 @@ impl Function { /// Finish code generation. pub fn finish(self, jump_table: &mut JumpTable, pc: u16) -> Result { let sp = self.masm.sp(); - if !self.is_main && self.masm.sp() != self.env.results().len() as u8 { + if !self.is_main && self.masm.sp() != self.ty.results().len() as u8 { return Err(Error::StackNotBalanced(sp)); } diff --git a/codegen/src/visitor/call.rs b/codegen/src/visitor/call.rs index 6c6b8c521..f2fb18d28 100644 --- a/codegen/src/visitor/call.rs +++ b/codegen/src/visitor/call.rs @@ -16,7 +16,7 @@ impl Function { /// The call instruction calls a function specified by its index. pub fn _call(&mut self, index: u32) -> Result<()> { - if self.imports.len() as u32 > index { + if self.env.imports.len() as u32 > index { self.call_imported(index) } else { self.call_internal(index) @@ -55,6 +55,7 @@ impl Function { // // register the imported function index to the jump table. let func = *self + .env .imports .get(&index) .ok_or(Error::ImportedFuncNotFound(index))?; diff --git a/codegen/src/visitor/handlers.rs b/codegen/src/visitor/handlers.rs index 01d41e8d2..2f1435c73 100644 --- a/codegen/src/visitor/handlers.rs +++ b/codegen/src/visitor/handlers.rs @@ -5,7 +5,7 @@ use crate::{ControlStackFrame, ControlStackFrameType, Function, Result}; impl Function { /// Handle the end of the function. pub(crate) fn handle_return(&mut self) -> Result<()> { - let results = self.env.results(); + let results = self.ty.results(); tracing::trace!("handle return, results: {results:?}"); self.masm.main_return(results) @@ -13,7 +13,7 @@ impl Function { /// Handle the return of a call. pub(crate) fn handle_call_return(&mut self) -> Result<()> { - let results = self.env.results(); + let results = self.ty.results(); tracing::trace!("handle call return: {:?}", results); self.masm.call_return(results) diff --git a/codegen/src/visitor/local.rs b/codegen/src/visitor/local.rs index 76dadda58..e02ede6c8 100644 --- a/codegen/src/visitor/local.rs +++ b/codegen/src/visitor/local.rs @@ -6,7 +6,7 @@ impl Function { /// This instruction gets the value of a variable. pub fn _local_get(&mut self, local_index: u32) -> Result<()> { let local_index = local_index as usize; - if self.is_main && local_index < self.env.params().len() { + if self.is_main && local_index < self.ty.params().len() { self._local_get_calldata(local_index) } else { self._local_get_var(local_index) diff --git a/codegen/src/visitor/log.rs b/codegen/src/visitor/log.rs index 3704df5f1..d0268db00 100644 --- a/codegen/src/visitor/log.rs +++ b/codegen/src/visitor/log.rs @@ -55,7 +55,7 @@ impl Function { for topic in (1..=count).rev() { let (offset, size) = self.log_data()?; let size = size as usize; - let data = self.dataset.load(offset, size)?; + let data = self.env.data.load(offset, size)?; tracing::debug!("log{count} topic{topic}: {:?}", data); topics.push(data); @@ -64,7 +64,7 @@ impl Function { let name = { let (offset, size) = self.log_data()?; let size = size as usize; - let data = self.dataset.load(offset, size)?; + let data = self.env.data.load(offset, size)?; tracing::debug!("log1 name: {:?}", data); data diff --git a/codegen/src/wasm/func.rs b/codegen/src/wasm/func.rs index 46c190e73..79085cb28 100644 --- a/codegen/src/wasm/func.rs +++ b/codegen/src/wasm/func.rs @@ -34,24 +34,10 @@ impl Function<'_> { } } -/// Functions with indexes. +/// WASM Functions by indexes. #[derive(Default)] pub struct Functions<'f>(BTreeMap>); -impl<'f> Deref for Functions<'f> { - type Target = BTreeMap>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'f> DerefMut for Functions<'f> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - impl<'f> Functions<'f> { /// Add function to the list. pub fn add( @@ -69,10 +55,10 @@ impl<'f> Functions<'f> { } /// Remove constructor function - pub fn remove_constructor(&mut self, exports: &Exports) -> Option> { + pub fn remove_constructor(&mut self, exports: &Exports) -> Option { for (index, export) in exports.iter() { if export.as_str() == "constructor" { - return self.remove(index); + return self.remove(index)?.sig().ok(); } } @@ -97,3 +83,17 @@ impl<'f> Functions<'f> { self.0.into_values().collect() } } + +impl<'f> Deref for Functions<'f> { + type Target = BTreeMap>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'f> DerefMut for Functions<'f> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/codegen/src/wasm/ie.rs b/codegen/src/wasm/ie.rs deleted file mode 100644 index 85536876a..000000000 --- a/codegen/src/wasm/ie.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Imports and exports - -use crate::wasm::HostFunc; -use std::collections::BTreeMap; - -impl_deref! { - ("WASM import section", Imports, BTreeMap), - ("WASM export section", Exports, BTreeMap) -} - -impl Imports { - /// If the function is `emit_abi`. - pub fn is_emit_abi(&self, index: u32) -> bool { - self.get(&index) == Some(&HostFunc::EmitABI) - } -} - -impl Exports { - /// Get all function selectors - pub fn selectors(&self) -> Vec { - self.iter() - .filter_map(|(index, export)| { - if export.ends_with("_selector") { - Some(*index) - } else { - None - } - }) - .collect::>() - } -} diff --git a/codegen/src/wasm/mod.rs b/codegen/src/wasm/mod.rs index 6d9614a4e..9bfaa4623 100644 --- a/codegen/src/wasm/mod.rs +++ b/codegen/src/wasm/mod.rs @@ -1,5 +1,18 @@ //! WASM related primitives. +mod abi; +mod data; +mod func; +mod host; + +pub use self::{ + abi::{ToLSBytes, Type}, + data::Data, + func::{Function, Functions}, + host::HostFunc, +}; +use std::collections::BTreeMap; + macro_rules! impl_deref { ($doc:literal, $name:ident, $target:ty) => { #[derive(Clone, Debug, Default)] @@ -25,16 +38,40 @@ macro_rules! impl_deref { }; } -mod abi; -mod data; -mod func; -mod host; -mod ie; +impl_deref! { + ("WASM import section", Imports, BTreeMap), + ("WASM export section", Exports, BTreeMap) +} -pub use self::{ - abi::{ToLSBytes, Type}, - data::Data, - func::{Function, Functions}, - host::HostFunc, - ie::{Exports, Imports}, -}; +/// A struct that holds the environment wasm module. +#[derive(Clone, Debug, Default)] +pub struct Env { + /// WASM imports + pub imports: Imports, + /// WASM exports + pub exports: Exports, + /// WASM data slots + pub data: Data, +} + +impl Imports { + /// If the function is `emit_abi`. + pub fn is_emit_abi(&self, index: u32) -> bool { + self.get(&index) == Some(&HostFunc::EmitABI) + } +} + +impl Exports { + /// Get all function selectors + pub fn selectors(&self) -> Vec { + self.iter() + .filter_map(|(index, export)| { + if export.ends_with("_selector") { + Some(*index) + } else { + None + } + }) + .collect::>() + } +} diff --git a/compiler/src/cli.rs b/compiler/src/cli.rs index 1551f9642..d4bbac03e 100644 --- a/compiler/src/cli.rs +++ b/compiler/src/cli.rs @@ -1,7 +1,7 @@ //! Zink compiler command line interface. #![cfg(feature = "cli")] -use crate::Compiler; +use crate::{Compiler, Config}; use ccli::{clap, Parser}; use std::{env, fs, path::PathBuf}; @@ -32,7 +32,7 @@ impl Compile { env::current_dir()?.join(self.input.with_extension("")) }; - let mut compiler = Compiler::default().dispatcher(self.dispatcher); + let mut compiler = Compiler::new(Config::default().dispatcher(self.dispatcher)); let bin = compiler.compile(&fs::read(&self.input)?)?; output.parent().map(fs::create_dir_all); diff --git a/compiler/src/compiler.rs b/compiler/src/compiler.rs index 4f9f91da3..5ccb8cd90 100644 --- a/compiler/src/compiler.rs +++ b/compiler/src/compiler.rs @@ -1,32 +1,33 @@ //! Zink compiler use crate::{parser::Parser, Config, Error, Result}; +use wasmparser::FuncType; use zabi::Abi; use zingen::{ - wasm::{self, Data, Imports}, + wasm::{self, Env}, Buffer, Constructor, Dispatcher, Function, JumpTable, BUFFER_LIMIT, }; /// Zink Compiler #[derive(Default)] pub struct Compiler { + /// ABIs of the compiled contract. abi: Vec, + /// EVM bytecode buffer. buffer: Buffer, + /// Global jump table. table: JumpTable, - config: Config, + /// Compiler configuration. + pub config: Config, } impl Compiler { - /// If embed constructor in bytecode. - pub fn constructor(mut self, constructor: bool) -> Self { - self.config.constructor = constructor; - self - } - - /// If embed dispatcher in bytecode. - pub fn dispatcher(mut self, dispatcher: bool) -> Self { - self.config.dispatcher = dispatcher; - self + /// Create a new compiler from config. + pub fn new(config: Config) -> Self { + Self { + config, + ..Default::default() + } } /// Compile wasm module to evm bytecode. @@ -37,8 +38,9 @@ impl Compiler { let constructor = parser.remove_constructor(); self.compile_dispatcher(&mut parser)?; + let env = parser.to_func_env(); for func in parser.funcs.into_funcs() { - self.compile_func(parser.data.clone(), parser.imports.clone(), func)?; + self.compile_func(env.clone(), func)?; } self.table.code_offset(self.buffer.len() as u16); @@ -61,12 +63,7 @@ impl Compiler { return Ok(()); } - let mut dispatcher = Dispatcher::new(&parser.funcs); - dispatcher - .data(parser.data.clone()) - .exports(parser.exports.clone()) - .imports(parser.imports.clone()); - + let mut dispatcher = Dispatcher::new(parser.to_env(), &parser.funcs); let buffer = dispatcher.finish(selectors, &mut self.table)?; self.buffer.extend_from_slice(&buffer); @@ -79,12 +76,7 @@ impl Compiler { } /// Compile WASM function. - pub fn compile_func( - &mut self, - dataset: Data, - imports: Imports, - mut func: wasm::Function<'_>, - ) -> Result<()> { + pub fn compile_func(&mut self, env: Env, mut func: wasm::Function<'_>) -> Result<()> { let func_index = func.index(); let sig = func.sig()?; @@ -92,10 +84,10 @@ impl Compiler { let is_main = if self.config.dispatcher { false } else { - func_index - (imports.len() as u32) == 0 + func_index - (env.imports.len() as u32) == 0 }; - let mut codegen = Function::new(sig, dataset, imports, is_main)?; + let mut codegen = Function::new(env, sig, is_main)?; let mut locals_reader = func.body.get_locals_reader()?; let mut ops_reader = func.body.get_operators_reader()?; @@ -126,7 +118,7 @@ impl Compiler { } /// Returns bytecode. - fn bytecode(&self, constructor: Option>) -> Result { + fn bytecode(&self, constructor: Option) -> Result { Constructor::new(constructor, self.buffer.clone())? .finish() .map_err(Into::into) diff --git a/compiler/src/config.rs b/compiler/src/config.rs index 43fdfe2fa..c3f73452c 100644 --- a/compiler/src/config.rs +++ b/compiler/src/config.rs @@ -1,10 +1,28 @@ //! Zink compiler configuration. +#[cfg(feature = "cli")] +use ccli::clap; + /// Zink compiler configuration. -#[derive(Default)] +#[derive(Debug, Default)] +#[cfg_attr(feature = "cli", derive(clap::Parser))] pub struct Config { /// If enable dispatcher. pub dispatcher: bool, /// If enable constructor. pub constructor: bool, } + +impl Config { + /// With dispatcher value. + pub fn dispatcher(mut self, dispatcher: bool) -> Self { + self.dispatcher = dispatcher; + self + } + + /// With constructor value. + pub fn constructor(mut self, constructor: bool) -> Self { + self.constructor = constructor; + self + } +} diff --git a/compiler/src/parser.rs b/compiler/src/parser.rs index baf5cc8c5..a1f281f30 100644 --- a/compiler/src/parser.rs +++ b/compiler/src/parser.rs @@ -3,10 +3,10 @@ use crate::{Error, Result}; use std::iter::IntoIterator; use wasmparser::{ - Data, DataKind, Export, ExternalKind, Import, Operator, Payload, SectionLimited, TypeRef, - ValidPayload, Validator, + Data, DataKind, Export, ExternalKind, FuncType, Import, Operator, Payload, SectionLimited, + TypeRef, ValidPayload, Validator, }; -use zingen::wasm::{self, Data as DataSet, Exports, Functions, HostFunc, Imports}; +use zingen::wasm::{Data as DataSet, Env, Exports, Functions, HostFunc, Imports}; /// WASM module parser #[derive(Default)] @@ -106,9 +106,27 @@ impl<'p> Parser<'p> { } /// Returns constructor if some. - pub fn remove_constructor(&mut self) -> Option> { + pub fn remove_constructor(&mut self) -> Option { self.funcs.remove_constructor(&self.exports) } + + /// Returns full environment. + pub fn to_env(&self) -> Env { + Env { + imports: self.imports.clone(), + data: self.data.clone(), + exports: self.exports.clone(), + } + } + + /// Returns function environment. + pub fn to_func_env(&self) -> Env { + Env { + imports: self.imports.clone(), + data: self.data.clone(), + exports: self.exports.clone(), + } + } } impl<'p> TryFrom<&'p [u8]> for Parser<'p> { diff --git a/elko/src/build.rs b/elko/src/build.rs index 4b13e8124..e5d2cc008 100644 --- a/elko/src/build.rs +++ b/elko/src/build.rs @@ -4,7 +4,7 @@ use anyhow::{anyhow, Result}; use ccli::clap::{self, Parser}; use etc::{Etc, FileSystem}; use std::{env, fs, path::PathBuf}; -use zinkc::Compiler; +use zinkc::{Compiler, Config}; /// Build zink project to EVM bytecode. #[derive(Debug, Parser)] @@ -12,15 +12,15 @@ use zinkc::Compiler; pub struct Build { /// The path of the cargo project. pub input: Option, - /// Write output to \ + /// Write output to #[clap(short, long, value_name = "filename")] pub output: Option, - /// Write output to compiler-chosen filename in \ + /// Write output to compiler-chosen filename in #[clap(long, value_name = "dir")] pub out_dir: Option, - /// If enable dispatcher. - #[clap(short, long)] - pub dispatcher: bool, + /// Compiler configuration + #[clap(flatten)] + pub config: Config, } impl Build { @@ -60,12 +60,11 @@ impl Build { // Compile the wasm to evm bytecode. let wasm = fs::read(builder.output()?)?; - let bin = Compiler::default() - .dispatcher(self.dispatcher) - .compile(&wasm)?; + let config = Config::default().dispatcher(self.config.dispatcher); + let bin = Compiler::new(config).compile(&wasm)?; let dst = builder.output()?.with_extension("bin"); - fs::write(dst, bin)?; + fs::write(dst, bin)?; Ok(()) } } diff --git a/zint/src/contract.rs b/zint/src/contract.rs index da886f7d7..2fa02e0a0 100644 --- a/zint/src/contract.rs +++ b/zint/src/contract.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, Result}; use serde::Deserialize; use std::{fs, path::PathBuf}; use zabi::Abi; -use zinkc::Compiler; +use zinkc::{Compiler, Config}; /// Cargo Manifest for parsing package. #[derive(Deserialize)] @@ -81,10 +81,11 @@ impl Contract { /// Compile WASM to EVM bytecode. pub fn compile(mut self) -> Result { - let mut compiler = Compiler::default() + let config = Config::default() .constructor(self.constructor) .dispatcher(self.dispatcher); + let mut compiler = Compiler::new(config); self.bytecode = compiler.compile(&self.wasm)?.to_vec(); self.abi = compiler.abi();