Skip to content

Commit

Permalink
feat(wasm) Set up a minimal working example.
Browse files Browse the repository at this point in the history
  • Loading branch information
Hywan committed Nov 17, 2018
0 parents commit 4051d04
Show file tree
Hide file tree
Showing 13 changed files with 277 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
target/
Cargo.lock
13 changes: 13 additions & 0 deletions Cargo.toml
@@ -0,0 +1,13 @@
[package]
name = "php-ext-wasm"
version = "0.1.0"
authors = ["Ivan Enderlin <ivan.enderlin@hoa-project.net>"]

[lib]
crate-type = ["dylib", "staticlib"]

[dependencies]
wasmi = "^0.4.2"

[build-dependencies]
cbindgen = "^0.6.0"
6 changes: 6 additions & 0 deletions README.md
@@ -0,0 +1,6 @@
# PHP `ext-wasm`

This is only experimental right now.

The goal of the project is to be able to run WebAssembly binaries from
PHP directly. So much fun coming!
11 changes: 11 additions & 0 deletions build.rs
@@ -0,0 +1,11 @@
extern crate cbindgen;

use std::env;

fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();

cbindgen::generate(crate_dir)
.expect("Unable to generate C bindings.")
.write_to_file("headers/php-ext-wasm.h");
}
9 changes: 9 additions & 0 deletions cbindgen.toml
@@ -0,0 +1,9 @@
header = """
/*
php-ext-wasm
Warning, this file is autogenerated by `cbindgen`.
Do not modify this manually.
*/"""
tab_width = 4
language = "C"
Empty file added extension/.keep
Empty file.
1 change: 1 addition & 0 deletions headers/.gitignore
@@ -0,0 +1 @@
*.h
21 changes: 21 additions & 0 deletions justfile
@@ -0,0 +1,21 @@
compile-toy:
cd tests && \
rustc --target wasm32-unknown-unknown -O --crate-type=cdylib toy.rs -o toy.raw.wasm && \
wasm-gc toy.raw.wasm toy.wasm && \
rm toy.raw.wasm

c:
clang \
-Wall \
-o test-c \
test.c \
-L target/release/ \
-l php_ext_wasm \
-l System \
-l pthread \
-l c \
-l m

# Local Variables:
# mode: makefile
# End:
173 changes: 173 additions & 0 deletions src/lib.rs
@@ -0,0 +1,173 @@
extern crate wasmi;

use wasmi::{
Error,
ImportsBuilder,
Module,
ModuleInstance,
ModuleRef,
NopExternals,
RuntimeValue,
};
use std::fs::File;
use std::io::{self, Read};
use std::path::PathBuf;

pub fn read_wasm_binary(path: &PathBuf) -> io::Result<Vec<u8>> {
let mut file = File::open(path)?;
let mut buffer = Vec::new();

file.read_to_end(&mut buffer).unwrap();

Ok(buffer)
}

pub struct WASMInstance {
pub file_path: String,
instance: ModuleRef,
}

impl WASMInstance {
pub fn new(path: &PathBuf, wasm_binary: Vec<u8>) -> Result<Self, Error> {
let module = Module::from_buffer(&wasm_binary)?;

Ok(
WASMInstance {
file_path: path.to_string_lossy().into_owned(),
instance: ModuleInstance::new(&module, &ImportsBuilder::default())?.assert_no_start(),
}
)
}

pub fn invoke(&self, function_name: &str, arguments: &[RuntimeValue]) -> Option<RuntimeValue> {
self.instance.invoke_export(function_name, arguments, &mut NopExternals).expect("foo")
}
}

pub mod ffi {
use super::WASMInstance;
use wasmi::{
RuntimeValue,
nan_preserving_float::{F32, F64},
};
use std::{
ffi::CStr,
os::raw::c_char,
path::PathBuf,
str,
ptr
};

macro_rules! check_and_deref {
($variable:ident) => {
{
assert!(!$variable.is_null());

unsafe { &*$variable }
}
};
}

macro_rules! check_and_deref_mut {
($variable:ident) => {
{
assert!(!$variable.is_null());

unsafe { &mut *$variable }
}
};
}

macro_rules! check_and_deref_to_pathbuf {
($variable:ident) => {
{
assert!(!$variable.is_null());

PathBuf::from(
unsafe {
String::from_utf8_unchecked(
CStr::from_ptr($variable).to_bytes().to_vec()
)
}
)
}
}
}

#[no_mangle]
pub extern "C" fn wasm_read_binary(file_path: *const c_char) -> *const Vec<u8> {
Box::into_raw(
Box::new(
super::read_wasm_binary(&check_and_deref_to_pathbuf!(file_path)).unwrap_or_else(|_| vec![])
)
)
}

#[no_mangle]
pub extern "C" fn wasm_new_instance(file_path: *const c_char, wasm_binary: *const Vec<u8>) -> *mut WASMInstance {
let file_path = check_and_deref_to_pathbuf!(file_path);
let wasm_binary = check_and_deref!(wasm_binary).to_vec();

match super::WASMInstance::new(&file_path, wasm_binary) {
Ok(wasm_instance) => Box::into_raw(Box::new(wasm_instance)),
Err(_) => ptr::null_mut(),
}
}

#[no_mangle]
pub extern "C" fn wasm_invoke_function(
wasm_instance: *const WASMInstance,
function_name: *const c_char,
arguments: *const Vec<RuntimeValue>
) {
let wasm_instance = check_and_deref!(wasm_instance);
let function_name = unsafe {
assert!(!function_name.is_null());
str::from_utf8_unchecked(CStr::from_ptr(function_name).to_bytes())
};
let arguments = check_and_deref!(arguments).as_slice();

println!("{:?}", wasm_instance.invoke(function_name, arguments));
}

#[no_mangle]
pub extern "C" fn wasm_invoke_arguments_builder() -> *mut Vec<RuntimeValue> {
Box::into_raw(Box::new(Vec::new()))
}

#[no_mangle]
pub extern "C" fn wasm_invoke_arguments_builder_add_i32(arguments: *mut Vec<RuntimeValue>, argument: i32) {
check_and_deref_mut!(arguments).push(RuntimeValue::I32(argument));
}

#[no_mangle]
pub extern "C" fn wasm_invoke_arguments_builder_add_i64(arguments: *mut Vec<RuntimeValue>, argument: i64) {
check_and_deref_mut!(arguments).push(RuntimeValue::I64(argument));
}

#[no_mangle]
pub extern "C" fn wasm_invoke_arguments_builder_add_f32(arguments: *mut Vec<RuntimeValue>, argument: f32) {
check_and_deref_mut!(arguments).push(RuntimeValue::F32(F32::from_float(argument)));
}

#[no_mangle]
pub extern "C" fn wasm_invoke_arguments_builder_add_f64(arguments: *mut Vec<RuntimeValue>, argument: f64) {
check_and_deref_mut!(arguments).push(RuntimeValue::F64(F64::from_float(argument)));
}
}


#[cfg(test)]
mod tests {
use super::*;

#[test]
fn case_invoke() {
let file_path = PathBuf::from("./tests/toy.wasm");
let wasm_binary = read_wasm_binary(&file_path).unwrap();
let wasm_instance = WASMInstance::new(&file_path, wasm_binary).unwrap();
let result = wasm_instance.invoke("sum", &[1.into(), 2.into()]);

assert_eq!(Some(3.into()), result);
}
}
29 changes: 29 additions & 0 deletions test.c
@@ -0,0 +1,29 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "headers/php-ext-wasm.h"

int main(int argc, char **argv) {
const Vec_u8 *wasm_binary = wasm_read_binary("./tests/toy.wasm");

WASMInstance *wasm_instance = wasm_new_instance("./tests/toy.wasm", wasm_binary);

if (NULL == wasm_instance) {
printf("Cannot instanciate the WASM binary.");

return 1;
}

Vec_RuntimeValue *arguments = wasm_invoke_arguments_builder();
wasm_invoke_arguments_builder_add_i32(arguments, 1);
wasm_invoke_arguments_builder_add_i32(arguments, 2);

wasm_invoke_function(wasm_instance, "sum", arguments);
}

/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
*/
8 changes: 8 additions & 0 deletions tests/toy.js
@@ -0,0 +1,8 @@
#!/usr/bin/env node

const fs = require('fs');

WebAssembly
.instantiate(fs.readFileSync('toy.wasm'), {})
.then(result => result.instance.exports.sum(1, 2))
.then(console.log);
4 changes: 4 additions & 0 deletions tests/toy.rs
@@ -0,0 +1,4 @@
#[no_mangle]
pub extern "C" fn sum(x: i32, y: i32) -> i32 {
x + y
}
Binary file added tests/toy.wasm
Binary file not shown.

0 comments on commit 4051d04

Please sign in to comment.