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

cawfree/memory #10

Merged
merged 9 commits into from
May 10, 2023
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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,19 @@ Here, we declare an `exceptionHandler` as `runtime` imports to the compiled modu

You can find a working implementation of this process in the [__Example App__](example/src/App.tsx).

### 🤔 Memory

Currently, `wasm3` [__only supports a single memory region__](https://github.com/wasm3/wasm3/blob/772f8f4648fcba75f77f894a6050db121e7651a2/source/wasm3.h#L214). This means that WebAssembly files which contain multiple `memory` allocations are not currently supported.

[`react-native-webassembly`](https://github.com/cawfree/react-native-webassembly) exposes access to the runtime memory element for allocated instances, which is represented using an `ArrayBuffer` named `memory`. This shares the same backing array as the native runtime.

It can accessed as follows:

```typescript
const module = WebAssembly.instantiate(...);

const memory: ArrayBuffer | undefined = module.instance.exports.memory;
```

### ✌️ License
[__MIT__](LICENSE)
82 changes: 67 additions & 15 deletions cpp/react-native-webassembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,22 @@ struct UserData {
std::map<std::string, wasm3::runtime> RUNTIMES;
std::map<std::string, UserData> USER_DATAS;

std::string ConvertM3FunctionToIdentifier(IM3Function f, std::string iid) {

std::string M3GetResolvedModuleName(IM3Function f) {
return f->import.moduleUtf8 ? std::string(f->import.moduleUtf8) : "*";
}

std::string* M3GetResolvedFunctionName(IM3Function f) {
return f->export_name
? new std::string(f->export_name)
: f->import.fieldUtf8
? new std::string(f->import.fieldUtf8)
: nullptr;
}

std::string ConvertM3ExportFunctionToIdentifier(IM3Function f, std::string iid) {
std::string signature = transform_signature(f);
return iid + std::string(f->import.moduleUtf8) + std::string(f->import.fieldUtf8) + signature;
return iid + M3GetResolvedModuleName(f) + *M3GetResolvedFunctionName(f) + signature;
}

facebook::jsi::Array ConvertStringArrayToJSIArray(
Expand Down Expand Up @@ -304,6 +317,32 @@ m3ApiRawFunction(_doSomethingWithFunction)
return m3Err_tooManyArgsRets;
}

class MutableMemoryHeaderBuffer : public MutableBuffer {
public:
MutableMemoryHeaderBuffer(IM3Runtime i_runtime) : m_runtime(i_runtime) {}
~MutableMemoryHeaderBuffer() override {}
size_t size() const override {
uint32_t memorySize;
uint8_t *memoryPtr = m3_GetMemory(m_runtime, &memorySize, 0);
return memorySize;
}
uint8_t* data() override {
uint32_t memorySize;
return m3_GetMemory(m_runtime, &memorySize, 0);
}
private:
IM3Runtime m_runtime;
};

facebook::jsi::ArrayBuffer M3MemoryHeaderToMutableBuffer(facebook::jsi::Runtime& runtime, IM3Runtime i_runtime) {

MutableMemoryHeaderBuffer* buffer = new MutableMemoryHeaderBuffer(i_runtime);

std::shared_ptr<MutableMemoryHeaderBuffer> shared_buffer = std::shared_ptr<MutableMemoryHeaderBuffer>(buffer);

return facebook::jsi::ArrayBuffer(runtime, shared_buffer);
}

namespace webassembly {

void install(Runtime &jsiRuntime) {
Expand All @@ -312,13 +351,16 @@ void install(Runtime &jsiRuntime) {
jsiRuntime,
PropNameID::forAscii(jsiRuntime, "RNWebassembly_instantiate"),
0,
[](Runtime &runtime, const Value &thisValue, const Value *arguments, size_t count) -> Value {
[&jsiRuntime](Runtime &runtime, const Value &thisValue, const Value *arguments, size_t count) -> Value {

auto responseObject = facebook::jsi::Object(runtime);

Object params = arguments->getObject(runtime);

String iid = params.getProperty(runtime, "iid").asString(runtime);
String bufferSource = params.getProperty(runtime, "bufferSource").asString(runtime);
uint32_t stackSizeInBytes = (uint32_t)params.getProperty(runtime, "stackSizeInBytes").asNumber();


Function callback = params.getProperty(runtime, "callback").asObject(runtime).asFunction(runtime);

Expand All @@ -340,21 +382,21 @@ void install(Runtime &jsiRuntime) {

IM3Module io_module = mod.m_module.get();

/* export initialization */
for (u32 i = 0; i < io_module->numFunctions; ++i)
{
const IM3Function f = & io_module->functions[i];

const char* moduleName = f->import.moduleUtf8;
const char* fieldName = f->import.fieldUtf8;

// TODO: is this valid?
if (!moduleName || !fieldName) continue;
std::string* functionName = M3GetResolvedFunctionName(f);

// Here we only care to connect to functions.
if (functionName == nullptr) continue;

// TODO: remove this in favour of wasm3::detail::m3_signature
// TODO: look at m3_type_to_sig
std::string signature = transform_signature(f);

std::shared_ptr<std::string> id = std::make_shared<std::string>(ConvertM3FunctionToIdentifier(f, std::string(iid.utf8(runtime))));
std::shared_ptr<std::string> id = std::make_shared<std::string>(ConvertM3ExportFunctionToIdentifier(f, std::string(iid.utf8(runtime))));

UserData userData;
userData.id = id;
Expand All @@ -366,18 +408,28 @@ void install(Runtime &jsiRuntime) {
// TODO: The callback implementation is erroneous?
// TODO: Generate signature.
// TODO: Remove raw function links.
m3_LinkRawFunctionEx(io_module, moduleName, fieldName, signature.data(), &_doSomethingWithFunction, static_cast<void*>(id->data()));
m3_LinkRawFunctionEx(io_module, M3GetResolvedModuleName(f).data(), functionName->data(), signature.data(), &_doSomethingWithFunction, static_cast<void*>(id->data()));
}

mod.compile();


// NOTICE: Only a single memory element may be provided at this time.
// TODO: m3_exec -> ResizeMemory()
// TODO: m3_env -> pub ResizeMemory
// TODO: Find the name of the memory call, if provided.
// TODO: How to tell if this was set? How to find the variable name?
// TODO: definitely increasing memorySize, we just need to find where?
facebook::jsi::ArrayBuffer arrayBuffer = M3MemoryHeaderToMutableBuffer(runtime, m3runtime.m_runtime.get());

RUNTIMES.insert(std::make_pair(iid.utf8(runtime), m3runtime));

return Value(0);

responseObject.setProperty(runtime, "result", 0);
responseObject.setProperty(runtime, "buffer", arrayBuffer);
} catch(wasm3::error &e) {
std::cerr << e.what() << std::endl;
return Value(1);
responseObject.setProperty(runtime, "result", 1);
}
return responseObject;
}
);

Expand Down Expand Up @@ -409,7 +461,7 @@ void install(Runtime &jsiRuntime) {
vec.push_back(args.getValueAtIndex(runtime, i).asString(runtime).utf8(runtime));

if (fn.GetRetCount() > 1) throw std::runtime_error("Unable to invoke.");

if (fn.GetRetCount() == 0) {
fn.call_argv<int>(vec);
return 0;
Expand Down
2 changes: 1 addition & 1 deletion cpp/wasm3_cpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ namespace wasm3 {
* @return function object
*/
function find_function(const char *name);
std::shared_ptr<M3Runtime> m_runtime;

protected:
friend class environment;
Expand All @@ -239,7 +240,6 @@ namespace wasm3 {

/* runtime extends the lifetime of the environment */
std::shared_ptr<M3Environment> m_env;
std::shared_ptr<M3Runtime> m_runtime;
};

/**
Expand Down
4 changes: 4 additions & 0 deletions example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### How to compile a `.wat` file:

1. Download and configure [__WebAssembly Binary Toolkit__](https://github.com/WebAssembly/wabt).
2. Then you can use something like: `wat2wasm test.wat -o test.wasm`.
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,7 @@ PODS:
- React-jsinspector (0.71.4)
- React-logger (0.71.4):
- glog
- react-native-webassembly (0.2.7):
- react-native-webassembly (0.3.2):
- RCT-Folly
- RCTRequired
- RCTTypeSafety
Expand Down Expand Up @@ -961,7 +961,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: d6b7fa9260aa3cb40afee0507e3bc1d17ecaa6f2
React-jsinspector: 1f51e775819199d3fe9410e69ee8d4c4161c7b06
React-logger: 0d58569ec51d30d1792c5e86a8e3b78d24b582c6
react-native-webassembly: 3aa2f51c7afc024ba36eb25742274ea8d51846ce
react-native-webassembly: b04d63800d8cc52e8702321448987555872602e2
React-perflogger: 0bb0522a12e058f6eb69d888bc16f40c16c4b907
React-RCTActionSheet: bfd675a10f06a18728ea15d82082d48f228a213a
React-RCTAnimation: 2fa220b2052ec75b733112aca39143d34546a941
Expand Down
49 changes: 48 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as WebAssembly from 'react-native-webassembly';

import LocalHello from './sources/Local.Hello.wasm';
import LocalCallback from './sources/Local.Callback.wasm';
import LocalSimpleMemory from './sources/Local.SimpleMemory.wasm';

import { useWasmCircomRuntime, useWasmHelloWorld } from './hooks';
import { fetchWasm } from './utils';
Expand All @@ -17,16 +18,19 @@ export default function App() {

const { calculateWTNSBin, error: circomError } = useWasmCircomRuntime();

/* Hook I/O. */
React.useEffect(
() => void (helloWorldError && console.error(helloWorldError)),
[helloWorldError]
);

/* ZK Snark */
React.useEffect(
() => void (circomError && console.error(circomError)),
[circomError]
);

/* Add. */
React.useEffect(() => {
if (!helloWorldResult) return;

Expand All @@ -35,6 +39,7 @@ export default function App() {
if (result !== 305) throw new Error('Failed to add.');
}, [helloWorldResult]);

/* Local imports. */
React.useEffect(
() =>
void (async () => {
Expand All @@ -53,6 +58,7 @@ export default function App() {
[]
);

/* complex allocation */
React.useEffect(
() =>
void (async () => {
Expand All @@ -71,6 +77,7 @@ export default function App() {
[]
);

/* callback e2e */
React.useEffect(
() =>
void (async () => {
Expand All @@ -85,7 +92,47 @@ export default function App() {

const result = localCallback.instance.exports.callBackFunction(25);

if (result !== 50) throw new Error('Simple callback failure.');
if (result !== 50) throw new Error('Callback failure.');
} catch (e) {
console.error(e);
}
})(),
[]
);

/* Simple memory. */
React.useEffect(
() =>
void (async () => {
try {
const localSimpleMemory = await WebAssembly.instantiate<{
readonly write_byte_to_memory: (value: number) => void;
readonly read_byte_from_memory: () => number;
}>(LocalSimpleMemory);

const testMemory = (withValue: number) => {
localSimpleMemory.instance.exports.write_byte_to_memory(withValue);

const wasmResult =
localSimpleMemory.instance.exports.read_byte_from_memory();

if (wasmResult !== withValue)
throw new Error(
`Expected ${withValue}, encountered wasm ${wasmResult}.`
);

const ab: ArrayBuffer = localSimpleMemory.instance.exports.memory!;

const jsResult = new Uint8Array(ab.slice(0, 1))[0];

// Ensure the JavaScript buffer is up-to-date.
if (jsResult !== withValue)
throw new Error(
`Expected ${withValue}, encountered js ${jsResult}.`
);
};

for (let i = 0; i < 255; i += 1) testMemory(i);
} catch (e) {
console.error(e);
}
Expand Down
Binary file added example/src/sources/Local.SimpleMemory.wasm
Binary file not shown.
17 changes: 17 additions & 0 deletions example/src/sources/Local.SimpleMemory.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
(module
;; Define a memory block with one page (64KB) of memory
(memory $my_memory 1)

;; Export the memory block so that it can be accessed from JavaScript
(export "memory" (memory $my_memory))

;; Define a function that writes a byte to the first byte of memory
(func (export "write_byte_to_memory") (param $value i32)
(i32.store8 (i32.const 0) (local.get $value))
)

;; Define a function that reads a byte from the first byte of memory
(func (export "read_byte_from_memory") (result i32)
(i32.load8_u (i32.const 0))
)
)
Loading