diff --git a/codegen/src/asm.rs b/codegen/src/asm.rs index 01c52e3c9..f0e1d7211 100644 --- a/codegen/src/asm.rs +++ b/codegen/src/asm.rs @@ -128,7 +128,7 @@ impl Assembler { macro_rules! impl_opcodes { ($($name:ident => $opcode:ident),+) => { $( - #[doc = concat!(" Emit", stringify!($opcode))] + #[doc = concat!(" Emit ", stringify!($opcode))] pub fn $name(&mut self) -> Result<()> { self.emit_op(OpCode::$opcode)?; Ok(()) diff --git a/codegen/src/visitor/call.rs b/codegen/src/visitor/call.rs index 199831bf9..0dd986853 100644 --- a/codegen/src/visitor/call.rs +++ b/codegen/src/visitor/call.rs @@ -51,7 +51,6 @@ impl Function { /// Call imported functions fn call_imported(&mut self, index: u32) -> Result<()> { - tracing::trace!("call imported function: index={index}"); // call an imported function. // // register the imported function index to the jump table. @@ -61,6 +60,8 @@ impl Function { .get(&index) .ok_or(Error::ImportedFuncNotFound(index))?; + tracing::trace!("call imported function, index={index}, func={func:?}"); + match func { HostFunc::Evm(OpCode::LOG0) => self.log(0), HostFunc::Evm(OpCode::LOG1) => self.log(1), @@ -68,6 +69,7 @@ impl Function { HostFunc::Evm(OpCode::LOG3) => self.log(3), HostFunc::Evm(OpCode::LOG4) => self.log(4), HostFunc::Evm(op) => self.masm.emit_op(op), + HostFunc::NoOp => Ok(()), _ => { tracing::error!("unsupported host function {func:?}"); Err(Error::UnsupportedHostFunc(func)) diff --git a/codegen/src/wasm/host.rs b/codegen/src/wasm/host.rs index 1d074c90d..8ab2e1f2d 100644 --- a/codegen/src/wasm/host.rs +++ b/codegen/src/wasm/host.rs @@ -9,6 +9,8 @@ use opcodes::{OpCode as _, ShangHai as OpCode}; pub enum HostFunc { /// EVM assemble operations. Evm(OpCode), + /// No operations, this only covers `push_$ty` at the moment. + NoOp, // Zinkc helper functions // /// Emit ABI to the compiler. @@ -39,6 +41,13 @@ impl TryFrom<(&str, &str)> for HostFunc { fn try_from(import: (&str, &str)) -> Result { let (module, name) = import; match import { + ("asm", name) => { + if name.starts_with("sload") { + Ok(Self::Evm(OpCode::SLOAD)) + } else { + Ok(Self::NoOp) + } + } ("evm", name) => { Ok(Self::Evm(OpCode::from_str(name).map_err(|_| { Error::HostFuncNotFound(module.into(), name.into()) diff --git a/compiler/src/parser.rs b/compiler/src/parser.rs index a1f281f30..d3fac7471 100644 --- a/compiler/src/parser.rs +++ b/compiler/src/parser.rs @@ -28,7 +28,7 @@ impl<'p> Parser<'p> { let valid_payload = validator.payload(&payload)?; match &payload { - Payload::ImportSection(reader) => self.imports = Self::imports(reader), + Payload::ImportSection(reader) => self.imports = Self::imports(reader)?, Payload::DataSection(reader) => self.data = Self::data(reader)?, Payload::ExportSection(reader) => self.exports = Self::exports(reader)?, _ => {} @@ -83,7 +83,7 @@ impl<'p> Parser<'p> { } /// Parse import section. - pub fn imports(reader: &SectionLimited) -> Imports { + pub fn imports(reader: &SectionLimited) -> Result { // TODO: use real index from WASM. (#122) let mut index = 0; @@ -95,14 +95,13 @@ impl<'p> Parser<'p> { ty: TypeRef::Func(_), })) = iter.next() { - if let Ok(func) = HostFunc::try_from((module, name)) { - tracing::trace!("imported function: {}::{} at {index}", module, name); - imports.insert(index, func); - index += 1; - } + let func = HostFunc::try_from((module, name))?; + tracing::trace!("imported function: {}::{} at {index}", module, name); + imports.insert(index, func); + index += 1; } - imports + Ok(imports) } /// Returns constructor if some. diff --git a/zink/codegen/src/storage.rs b/zink/codegen/src/storage.rs index 17629612b..43549287d 100644 --- a/zink/codegen/src/storage.rs +++ b/zink/codegen/src/storage.rs @@ -5,47 +5,43 @@ use quote::{quote, ToTokens}; use std::sync::atomic::{AtomicI32, Ordering::Relaxed}; use syn::ItemType; +static IOTA: AtomicI32 = AtomicI32::new(0); + +/// Parse storage attribute. +/// +/// Method `get` unwraps the ptr as the original type, mainly +/// mainly for passing the compilation checks at the moment, +/// and it works for WASM in real cases as well. +/// +/// For the cases in EVM, it doesn't matter it returns pointer +/// since the value will be left on stack anyway. pub fn parse(input: ItemType) -> TokenStream { - let variable_name = input.ident; - let variable_type = input.ty.to_token_stream(); - - match variable_type.to_string().as_str() { - "i32" => (), - _ => unimplemented!("Only support i32 as storage key for now."), - }; - - // hash-based storage key derivation (we decided that order-based is better) - - // let mut h = Keccak256::new(); - // h.update(variable_name.to_string().as_bytes()); - // let storage_key = h.finalize(); - // - // // lmfao i'm sure there's a better way to do this but i don't know how - // let mut storage_key_string = storage_key.as_slice().into_iter().map(|i| i.to_string()).collect::>().join(", "); - // storage_key_string.insert(0, '['); - // storage_key_string.push(']'); - // let storage_key_literal = syn::parse_str::(&storage_key_string).unwrap(); - - static IOTA: AtomicI32 = AtomicI32::new(0); - // temporary solution, we'll switch to 32 byte storage keys later - let storage_key = IOTA.fetch_add(1, Relaxed); + let name = input.ident; + let ty = input.ty.to_token_stream(); + // Temporary solution, we'll switch to 32 byte storage keys later + let key = IOTA.fetch_add(1, Relaxed); let expanded = quote! { - // TODO: derive documents (#137) - struct #variable_name; + #[doc = concat!(" Storage ", stringify!($variable_name))] + struct #name; - impl zink::Storage<#variable_type> for #variable_name { - const STORAGE_KEY: i32 = #storage_key; + impl zink::Storage<#ty> for #name { + const STORAGE_KEY: i32 = #key; - fn get() -> #variable_type { + fn get() -> #ty { + zink::Asm::push(Self::STORAGE_KEY); unsafe { - zink::ffi::evm::sload(Self::STORAGE_KEY) + paste::paste! { + zink::ffi::asm::[< sload_ #ty >]() + } } } - fn set(value: #variable_type) { + fn set(value: #ty) { + zink::Asm::push(value); + zink::Asm::push(Self::STORAGE_KEY); unsafe { - zink::ffi::evm::sstore(value, Self::STORAGE_KEY); + zink::ffi::evm::sstore(); } } } @@ -53,14 +49,3 @@ pub fn parse(input: ItemType) -> TokenStream { expanded } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_test() { - let expr: ItemType = syn::parse_str("pub type Counter = i32;").unwrap(); - assert_eq!(parse(expr).to_string().as_str(), "struct Counter ; impl zink :: Storage < i32 > for Counter { const STORAGE_KEY : i32 = 0i32 ; fn get () -> i32 { unsafe { zink :: ffi :: evm :: sload (Self :: STORAGE_KEY) } } fn set (value : i32) { unsafe { zink :: ffi :: evm :: sstore (value , Self :: STORAGE_KEY) ; } } }"); - } -} diff --git a/zink/src/asm.rs b/zink/src/asm.rs index fae4f1e8e..b26b94bdf 100644 --- a/zink/src/asm.rs +++ b/zink/src/asm.rs @@ -6,22 +6,22 @@ use paste::paste; /// Types implemented this trait are able to be pushed on stack. pub trait Asm { /// Push self on the stack. - fn push(&self); + fn push(self); } macro_rules! impl_asm { ($ty:ident) => { impl Asm for $ty { - fn push(&self) { + fn push(self) { unsafe { - paste! { ffi::asm::[](*self); } + paste! { ffi::asm::[](self); } } } } }; ($len:expr) => { impl Asm for [u8; $len] { - fn push(&self) { + fn push(self) { unsafe { paste! { ffi::evm::[](self.as_ptr() as i32); } } diff --git a/zink/src/ffi/asm.rs b/zink/src/ffi/asm.rs index 95935825d..6bec21db3 100644 --- a/zink/src/ffi/asm.rs +++ b/zink/src/ffi/asm.rs @@ -1,6 +1,6 @@ //! Assembly FFI. -#[link(wasm_import_module = "zinkc")] +#[link(wasm_import_module = "asm")] #[allow(improper_ctypes)] extern "C" { /// Push a 8-bit signed integer to the stack. @@ -26,4 +26,28 @@ extern "C" { /// Push a 64-bit unsigned integer to the stack. pub fn push_u64(val: u64); + + /// Load a 8-bit signed integer from the storage. + pub fn sload_i8() -> i8; + + /// Load a 8-bit unsigned integer from the storage. + pub fn sload_u8() -> u8; + + /// Load a 16-bit signed integer from the storage. + pub fn sload_i16() -> i16; + + /// Load a 16-bit unsigned integer from the storage. + pub fn sload_u16() -> u16; + + /// Load a 32-bit signed integer from the storage. + pub fn sload_i32() -> i32; + + /// Load a 32-bit unsigned integer from the storage. + pub fn sload_u32() -> u32; + + /// Load a 64-bit signed integer from the storage. + pub fn sload_i64() -> i64; + + /// Load a 64-bit unsigned integer from the storage. + pub fn sload_u64() -> u64; } diff --git a/zink/src/ffi/evm.rs b/zink/src/ffi/evm.rs index 25180741a..ffa2ffcc9 100644 --- a/zink/src/ffi/evm.rs +++ b/zink/src/ffi/evm.rs @@ -103,10 +103,10 @@ extern "C" { pub fn push32(val: i32); /// Store a value in the storage - pub fn sstore(value: i32, key: i32); + pub fn sstore(); /// Load a value from the storage - pub fn sload(key: i32) -> i32; + pub fn sload(); /// Append log record with no topics pub fn log0(name: &'static [u8]); diff --git a/zink/src/lib.rs b/zink/src/lib.rs index 4a7ae6082..a1fd75fba 100644 --- a/zink/src/lib.rs +++ b/zink/src/lib.rs @@ -7,7 +7,7 @@ mod event; pub mod ffi; mod storage; -pub use self::{event::Event, storage::Storage}; +pub use self::{asm::Asm, event::Event, storage::Storage}; pub use zink_codegen::{constructor, external, storage, Event}; // Panic hook implementation diff --git a/zink/src/storage/mod.rs b/zink/src/storage/mod.rs index 8f383c3e8..c8f034449 100644 --- a/zink/src/storage/mod.rs +++ b/zink/src/storage/mod.rs @@ -1,9 +1,11 @@ //! Zink storage implementation. +use crate::Asm; + mod mapping; /// Storage trait. Currently not for public use -pub trait Storage { +pub trait Storage { const STORAGE_KEY: i32; /// Get value from storage.