Skip to content

Commit

Permalink
Merge pull request #23 from veewee/imports
Browse files Browse the repository at this point in the history
Introduce basic imports
  • Loading branch information
veewee authored Aug 3, 2023
2 parents f852032 + a139ffc commit df27dc4
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 11 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ var_dump($instance->get_some());
php examples/global.php
```

Currently, this package supports `global` access and function `exports`.
Check out the [examples](examples) folder for more examples.

## Stubs

```
cargo php stubs
make stubs
```

## Roadmap
Expand Down
21 changes: 21 additions & 0 deletions examples/imports.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

$instanceBuilder = Wasm\InstanceBuilder::fromWat(
<<<'EOWAT'
(module
(import "env" "global" (global $global (mut i32)))
(func (export "read_g") (result i32)
global.get $global)
(func (export "write_g") (param i32)
local.get 0
global.set $global))
EOWAT
);

$imports = Wasm\Imports::create();
$imports->define('env', 'global', \Wasm\Type\Global::mutable(32));
$instanceBuilder->import($imports);

$instance = $instanceBuilder->build();

var_dump($instance->read_g());
27 changes: 22 additions & 5 deletions ext-wasm.stubs.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,36 @@
// Stubs for ext-wasm

namespace Wasm {
class WasmInstance {
public static function fromBuilder(\Wasm\InstanceBuilder $builder): \Wasm\WasmInstance {}

public function __call(string $method, array $attributes): mixed {}

public function __get(string $accessor): mixed {}

public function __set(string $accessor, mixed $value): void {}
}

class InstanceBuilder {
public static function fromWat(string $wat): \Wasm\InstanceBuilder {}

public function import(array $imports): void {}

public function build(): \Wasm\WasmInstance {}
}

class WasmInstance {
public static function fromBuilder(\Wasm\InstanceBuilder $builder): \Wasm\WasmInstance {}
class Imports {
public static function create(): self {}

public function __call(string $method, array $attributes): mixed {}
public static function define(string $namespace, string $variable, \Wasm\Type\Global $value): void {}
}

public function __get(string $accessor): mixed {}
}

namespace Wasm\Type {
class Global {
public static function mutable(mixed $value): self {}

public function __set(string $accessor, mixed $value): mixed {}
public static function immutable(mixed $value): self {}
}
}
98 changes: 94 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

mod types;

use std::collections::HashMap;
use ext_php_rs::prelude::*;
use ext_php_rs::zend::ModuleEntry;
use ext_php_rs::types::ZendClassObject;
use ext_php_rs::types::{ZendClassObject};
use ext_php_rs::*;
use crate::types::Value;
use crate::types::value::Value;

#[php_class(name="Wasm\\WasmInstance")]
pub struct WasmInstance {
Expand Down Expand Up @@ -61,31 +62,120 @@ impl WasmInstance {
}

#[php_class(name="Wasm\\InstanceBuilder")]
#[derive(Default)]
pub struct InstanceBuilder {
pub wat: Box<String>,
pub imports : Option<Box<WasmImports>>,
}

#[php_impl]
impl InstanceBuilder {
pub fn from_wat(wat: String) -> InstanceBuilder {
InstanceBuilder {
wat: wat.clone().into()
wat: wat.clone().into(),
..Default::default()
}.into()
}

// TODO : Change return type to be fluent (see #28)
pub fn import<'a>(
#[this] this: &mut ZendClassObject<InstanceBuilder>,
imports: &mut ZendClassObject<WasmImports>
) -> () {
this.imports = Some((*imports).clone().into());
}

pub fn build(&mut self) -> PhpResult<WasmInstance> {
let mut store = wasmer::Store::default();
let module = wasmer::Module::new(&store, &*self.wat)
.map_err(|err| PhpException::default(err.to_string()))?;

// Build imports
let mut import_object = wasmer::Imports::new();
if self.imports.is_some() {
import_object = (self.imports.as_mut().unwrap()).into_wasmer_imports(&mut store)
}

let import_object = wasmer::imports! {};
let instance = wasmer::Instance::new(&mut store, &module, &import_object)
.map_err(|err| PhpException::default(err.to_string()))?;

Ok(WasmInstance {store, instance}.into())
}
}


#[php_class(name="Wasm\\Imports")]
#[derive(Clone)]
pub struct WasmImports {
pub registry : HashMap<(String, String), WasmGlobal>,
}

#[php_impl]
impl WasmImports {
pub fn create() -> WasmImports {
WasmImports {
registry: HashMap::new().into()
}.into()
}

// TODO : Change return type to be fluent (see #28)
pub fn define(
//&mut self,
#[this] this: &mut ZendClassObject<WasmImports>,
namespace : String,
variable : String,
value : &mut ZendClassObject<WasmGlobal>,
) -> () {
this.registry.insert((namespace.clone(), variable.clone()), (*value).clone().into());
}
}

impl WasmImports {
pub fn into_wasmer_imports(&mut self, store : &mut wasmer::Store) -> wasmer::Imports {
let mut import_object = wasmer::Imports::new();

for ((namespace, name), value) in (&self.registry).into_iter() {
let converted = value.clone().into_wasmer_global(store);
import_object.define(&namespace, &name, converted);
}

import_object
}
}

#[php_class(name="Wasm\\Type\\Global")]
#[derive(Clone)]
pub struct WasmGlobal {
pub value : Box<Value>,
pub mutable : bool,
}

#[php_impl]
impl WasmGlobal {
pub fn mutable(value: Value) -> WasmGlobal {
WasmGlobal {value: value.into(), mutable: true}.into()
}

pub fn immutable(value: Value) -> WasmGlobal {
WasmGlobal {value: value.into(), mutable: false}.into()
}
}

impl WasmGlobal {
pub fn into_wasmer_global(&mut self, store : &mut wasmer::Store) -> wasmer::Global {
match self.mutable {
true => wasmer::Global::new_mut(store, (*self.value).into()),
false => wasmer::Global::new(store, (*self.value).into()),
}
}

pub fn into_wasmer_extern(&mut self, store : &mut wasmer::Store) -> wasmer::Extern {
wasmer::Extern::Global(
self.into_wasmer_global(store)
)
}
}

/// Used by the `phpinfo()` function and when you run `php -i`.
pub extern "C" fn php_module_info(_module: *mut ModuleEntry) {
info_table_start!();
Expand Down
1 change: 1 addition & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod value;
File renamed without changes.
85 changes: 85 additions & 0 deletions tests/ImportsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

namespace Test;

use PHPUnit\Framework\TestCase;
use Wasm\InstanceBuilder;
use Wasm\Imports;
use Wasm\Type;

class ImportsTest extends TestCase
{
public function test_it_fails_on_missing_imports() {
$this->expectException(\Exception::class);
$this->createBuilderFromWat()->build();
}

public function test_it_fails_on_unkown_env() {
$imports = Imports::create();
$imports->define('unkown', 'global', Type\Global::mutable(32));

$builder = $this->createBuilderFromWat();
$builder->import($imports);

$this->expectException(\Exception::class);
$builder->build();
}

public function test_it_fails_on_unkown_import_key() {
$imports = Imports::create();
$imports->define('env', 'unkown', Type\Global::mutable(32));

$builder = $this->createBuilderFromWat();
$builder->import($imports);

$this->expectException(\Exception::class);
$builder->build();
}

public function test_it_fails_on_invalid_type() {
$imports = Imports::create();
$imports->define('env', 'global', Type\Global::mutable(1.1));

$builder = $this->createBuilderFromWat();
$builder->import($imports);

$this->expectException(\Exception::class);
$builder->build();
}

public function test_it_fails_on_invalid_mutability() {
$imports = Imports::create();
$imports->define('env', 'global', Type\Global::immutable(32));

$builder = $this->createBuilderFromWat();
$builder->import($imports);

$this->expectException(\Exception::class);
$builder->build();
}

public function test_it_imports_simple_globals() {
$imports = Imports::create();
$imports->define('env', 'global', Type\Global::mutable(32));

$builder = $this->createBuilderFromWat();
$builder->import($imports);

$instance = $builder->build();

self::assertSame(32, $instance->read_g());
}

private function createBuilderFromWat(?string $wat = null): InstanceBuilder
{
return InstanceBuilder::fromWat($wat ?? <<<'EOWAT'
(module
(import "env" "global" (global $global (mut i32)))
(func (export "read_g") (result i32)
global.get $global)
(func (export "write_g") (param i32)
local.get 0
global.set $global))
EOWAT);
}
}

0 comments on commit df27dc4

Please sign in to comment.