Skip to content

Commit

Permalink
feat(codegen): passing primitives in storage interfaces (#218)
Browse files Browse the repository at this point in the history
* chore(codegen): generate docs for storage

* feat(zink): returns ptr in storage get

* feat(codegen): mark no-op for push asm operations

* feat(zink): asm functions for sload
  • Loading branch information
clearloop authored Feb 5, 2024
1 parent d11abd2 commit b58839f
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 61 deletions.
2 changes: 1 addition & 1 deletion codegen/src/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down
4 changes: 3 additions & 1 deletion codegen/src/visitor/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -61,13 +60,16 @@ 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),
HostFunc::Evm(OpCode::LOG2) => self.log(2),
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))
Expand Down
9 changes: 9 additions & 0 deletions codegen/src/wasm/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -39,6 +41,13 @@ impl TryFrom<(&str, &str)> for HostFunc {
fn try_from(import: (&str, &str)) -> Result<Self> {
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())
Expand Down
15 changes: 7 additions & 8 deletions compiler/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?,
_ => {}
Expand Down Expand Up @@ -83,7 +83,7 @@ impl<'p> Parser<'p> {
}

/// Parse import section.
pub fn imports(reader: &SectionLimited<Import>) -> Imports {
pub fn imports(reader: &SectionLimited<Import>) -> Result<Imports> {
// TODO: use real index from WASM. (#122)
let mut index = 0;

Expand All @@ -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.
Expand Down
69 changes: 27 additions & 42 deletions zink/codegen/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,47 @@ 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::<Vec<String>>().join(", ");
// storage_key_string.insert(0, '[');
// storage_key_string.push(']');
// let storage_key_literal = syn::parse_str::<ExprArray>(&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();
}
}
}
};

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) ; } } }");
}
}
8 changes: 4 additions & 4 deletions zink/src/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::[<push_ $ty>](*self); }
paste! { ffi::asm::[<push_ $ty>](self); }
}
}
}
};
($len:expr) => {
impl Asm for [u8; $len] {
fn push(&self) {
fn push(self) {
unsafe {
paste! { ffi::evm::[<push $len>](self.as_ptr() as i32); }
}
Expand Down
26 changes: 25 additions & 1 deletion zink/src/ffi/asm.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
}
4 changes: 2 additions & 2 deletions zink/src/ffi/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down
2 changes: 1 addition & 1 deletion zink/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion zink/src/storage/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! Zink storage implementation.
use crate::Asm;

mod mapping;

/// Storage trait. Currently not for public use
pub trait Storage<T> {
pub trait Storage<T: Asm> {
const STORAGE_KEY: i32;

/// Get value from storage.
Expand Down

0 comments on commit b58839f

Please sign in to comment.