Skip to content

Commit

Permalink
lib(rust): Draft initial implementation of Rust binding
Browse files Browse the repository at this point in the history
TODO:

* Re-export function that compatible with C ABI without +multivalue return

* Implement buffer address allocation to store temporary computation
  + Buffer address should be reserved at the end of wasm linear memory on every computation
  + Computation should be eager so buffer always ready to be used (akin to __stack_pointer frame)
  • Loading branch information
DrSensor committed Sep 9, 2023
1 parent 8156d21 commit f6e683a
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 0 deletions.
8 changes: 8 additions & 0 deletions libs/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "libnusa"
version = "0.0.1-Jan.2023"
edition = "2021"
repository = "https://github.com/DrSensor/nusa/tree/main/libs/rust"
keywords = ["webdev", "wasm", "no_std", "nusa"]
categories = ["web-programming", "api-bindings", "no-std::no-alloc", "wasm"]
license = "MIT"
66 changes: 66 additions & 0 deletions libs/rust/src/iter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use crate::{host, number, types, Series};
use core::ops;
use std::primitive;
use types::number::{JSNumber, Type};

#[repr(transparent)]
pub struct All<T: Series>(T);

pub fn for_all<T, F, E, const N: usize>(from: F, exec: E)
where
T: Series,
F: Into<[T; N]>,
E: Fn([All<T>; N]),
{
exec(from.into().map(All))
}

macro_rules! bridge {
($ty:ident, $Ty:ident) => {
impl ops::AddAssign<primitive::$ty> for All<number::$ty> {
fn add_assign(&mut self, rhs: primitive::$ty) {
let All(data) = self;
unsafe {
host::num::mutate::addVAL(
Type::$Ty,
data.len(),
false,
data.ptr().into(),
rhs as JSNumber,
)
}
}
}

// TODO: is this correct?
impl ops::Add<primitive::$ty> for All<number::$ty> {
type Output = host::BufAddr;
fn add(self, rhs: primitive::$ty) -> Self::Output {
let All(data) = self;
unsafe {
host::num::compute::addVAL(
Type::$Ty,
data.len(),
false,
data.ptr().into(),
rhs as JSNumber,
) as Self::Output
}
}
}
};
}

bridge!(u8, Uint8);
bridge!(i8, Int8);

bridge!(u16, Uint16);
bridge!(i16, Int16);

bridge!(u32, Uint32);
bridge!(i32, Int32);
bridge!(f32, Float32);

bridge!(u64, Uint64);
bridge!(i64, Int64);
bridge!(f64, Float64);
81 changes: 81 additions & 0 deletions libs/rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
pub mod iter;
pub mod number;
mod types;

trait Accessor<T> {
fn set(self, value: T);
fn get(self) -> T;
}

pub trait Series {
type As;
fn ptr(&self) -> *const Self::As;
fn allocate(len: host::Size) -> (usize, usize);
fn len(&self) -> host::Size;
fn is_empty(&self) -> bool {
self.len() == 0
}
}

mod host {
pub type Size = u16;
pub type BufAddr = usize; // buffer to store temporary array from all `host::*::compute` module 🤔

pub mod scope {
#[link(wasm_import_module = "nusa")]
extern "C" {
#[link_name = "scope.size"]
pub fn size() -> u16;
}
}

pub mod num {
use crate::types::{number::Type, JSNumber, Null, Number};
pub type Setter = extern "C" fn(Number, JSNumber);
pub type Getter = extern "C" fn(Number) -> JSNumber;

#[link(wasm_import_module = "nusa")]
#[allow(improper_ctypes)]
extern "C" {
#[link_name = "num.allocate"]
pub fn allocate(ty: Type, len: u16, nullable: bool) -> (Number, Null);
#[link_name = "num.accessor"]
pub fn accessor(ty: Type) -> (Getter, Setter);
}

#[link(wasm_import_module = "nusa")]
extern "C" {
#[link_name = "num.set"]
pub fn set(setter: Setter, ptr: usize, value: JSNumber);
#[link_name = "num.get"]
pub fn get(getter: Getter, ptr: usize) -> JSNumber;
}

pub mod mutate {
use super::*;

#[link(wasm_import_module = "nusa")]
extern "C" {
#[link_name = "num.bulk.mut.addVAL"]
pub fn addVAL(ty: Type, len: u16, nullable: bool, this: Number, val: JSNumber);
}
}

pub mod compute {
use super::*;
use crate::host;

#[link(wasm_import_module = "nusa")]
extern "C" {
#[link_name = "num.bulk.calc.addVAL"] // TODO
pub fn addVAL(
ty: Type,
len: u16,
nullable: bool,
this: Number,
val: JSNumber,
) -> host::BufAddr;
}
}
}
}
68 changes: 68 additions & 0 deletions libs/rust/src/number.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use crate::{host, types, Accessor, Series};
use core::primitive;
use types::number::{JSNumber, Type};

macro_rules! bridge {
($ty:ident, $Ty:ident) => {
#[allow(non_camel_case_types)]
pub struct $ty {
len: host::Size,
addr: usize,
accr: (host::num::Getter, host::num::Setter),
}

impl Series for self::$ty {
type As = primitive::$ty;

fn ptr(&self) -> *const primitive::$ty {
self.addr as *const primitive::$ty
}

fn allocate(len: host::Size) -> (usize, usize) {
let (number, null) = unsafe { host::num::allocate(Type::$Ty, len, false) };
(number.addr, null.addr)
}

fn len(&self) -> host::Size {
self.len
}
}

impl self::$ty {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
let len = unsafe { host::scope::size() };
let (addr, _) = Self::allocate(len);
let (getter, setter) = unsafe { host::num::accessor(Type::$Ty) };
let accr = (getter, setter);
$ty { len, addr, accr }
}
}

impl Accessor<primitive::$ty> for self::$ty {
fn set(self, value: primitive::$ty) {
let (_, setter) = self.accr;
unsafe { host::num::set(setter, self.addr, value as JSNumber) }
}

fn get(self) -> primitive::$ty {
let (getter, _) = self.accr;
unsafe { host::num::get(getter, self.addr) as primitive::$ty }
}
}
};
}

bridge!(u8, Uint8);
bridge!(i8, Int8);

bridge!(u16, Uint16);
bridge!(i16, Int16);

bridge!(u32, Uint32);
bridge!(i32, Int32);
bridge!(f32, Float32);

bridge!(u64, Uint64);
bridge!(i64, Int64);
bridge!(f64, Float64);
1 change: 1 addition & 0 deletions libs/rust/src/types

0 comments on commit f6e683a

Please sign in to comment.