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

feat(codegen): passing primitives in storage interfaces #218

Merged
merged 4 commits into from
Feb 5, 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
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
Loading