diff --git a/example/web/main.dart b/example/web/main.dart index 55e10e5..8602ecb 100644 --- a/example/web/main.dart +++ b/example/web/main.dart @@ -25,6 +25,7 @@ Future runTests(String source, String name) async { container.append( createKeyVal('Size of Pointer', runner.pointerSize().toString()), ); + container.append(createKeyVal('Sum4', runner.sum4().toString())); container.append( createKeyVal('Static Init Check', runner.staticInitCheck().toString()), ); diff --git a/example_flutter/Makefile b/example_flutter/Makefile index b566ee2..a711b52 100644 --- a/example_flutter/Makefile +++ b/example_flutter/Makefile @@ -6,6 +6,7 @@ build: assets/standalone/native_example.wasm assets/emscripten/native_example.js SRC=src/native_example.c src/native_example_cpp.cpp DEPS=$(SRC) $(SRC:.c=.h) +EMCC ?= emcc # Wasm options ifdef DEBUG @@ -22,16 +23,16 @@ clean: rm -rf assets/emscripten/* lib/native_example_bindings.dart: $(DEPS) - dart run ffigen --config ffigen.yaml && sed -i "s#'dart:ffi'#'package:wasm_ffi/ffi.dart'#g" $@ + dart run ffigen --config ffigen.yaml && perl -pi -e "s#(['\"])dart:ffi\\1#'package:wasm_ffi/ffi.dart'#g" $@ assets/standalone/native_example.wasm: $(DEPS) lib/native_example_bindings.dart - emcc -o assets/standalone/native_example.wasm $(COMPILER_OPTIONS) $(LINKER_OPTIONS) \ + $(EMCC) -o assets/standalone/native_example.wasm $(COMPILER_OPTIONS) $(LINKER_OPTIONS) \ $(SRC) \ -s STANDALONE_WASM=1 \ -s $(COMPILED_EXPORTS) assets/emscripten/native_example.js: $(DEPS) lib/native_example_bindings.dart - emcc -o assets/emscripten/native_example.js $(COMPILER_OPTIONS) $(LINKER_OPTIONS) \ + $(EMCC) -o assets/emscripten/native_example.js $(COMPILER_OPTIONS) $(LINKER_OPTIONS) \ $(SRC) \ -s MODULARIZE=1 -s 'EXPORT_NAME="native_example"' -s ALLOW_MEMORY_GROWTH=1 \ -s EXPORTED_RUNTIME_METHODS=HEAPU8 \ diff --git a/example_flutter/assets/emscripten/native_example.js b/example_flutter/assets/emscripten/native_example.js index a337b0f..8812243 100644 --- a/example_flutter/assets/emscripten/native_example.js +++ b/example_flutter/assets/emscripten/native_example.js @@ -1,2 +1,21 @@ -var native_example=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};if(typeof __filename!="undefined"){_scriptName=__filename}else if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var isFileURI=filename=>filename.startsWith("file://");var readyPromiseResolve,readyPromiseReject;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);HEAPU32=new Uint32Array(b);HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["c"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("native_example.wasm")}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}var _getLibraryName,_hello,_malloc,_freeMemory,_free,_intSize,_boolSize,_pointerSize,_static_init_check,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_getLibraryName=Module["_getLibraryName"]=wasmExports["d"];_hello=Module["_hello"]=wasmExports["e"];_malloc=Module["_malloc"]=wasmExports["f"];_freeMemory=Module["_freeMemory"]=wasmExports["g"];_free=Module["_free"]=wasmExports["h"];_intSize=Module["_intSize"]=wasmExports["i"];_boolSize=Module["_boolSize"]=wasmExports["j"];_pointerSize=Module["_pointerSize"]=wasmExports["k"];_static_init_check=Module["_static_init_check"]=wasmExports["l"];memory=wasmMemory=wasmExports["b"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={a:_emscripten_resize_heap};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} -;return moduleRtn}})();if(typeof exports==="object"&&typeof module==="object"){module.exports=native_example;module.exports.default=native_example}else if(typeof define==="function"&&define["amd"])define([],()=>native_example); +var native_example = (() => { + var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined; + if (typeof __filename != 'undefined') _scriptName = _scriptName || __filename; + return ( +async function(moduleArg = {}) { + var moduleRtn; + +var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;var readyPromise=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof WorkerGlobalScope!="undefined";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string"&&process.type!="renderer";if(ENVIRONMENT_IS_NODE){}var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("fs");var nodePath=require("path");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(!Module["thisProgram"]&&process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptName){scriptDirectory=_scriptName}if(scriptDirectory.startsWith("blob:")){scriptDirectory=""}else{scriptDirectory=scriptDirectory.slice(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var wasmBinary=Module["wasmBinary"];var wasmMemory;var ABORT=false;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAP64,HEAPU64,HEAPF64;var runtimeInitialized=false;var isFileURI=filename=>filename.startsWith("file://");function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b);Module["HEAP64"]=HEAP64=new BigInt64Array(b);Module["HEAPU64"]=HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["c"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}var runDependencies=0;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)}function removeRunDependency(id){runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("native_example.wasm")}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){return{a:wasmImports}}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;wasmMemory=wasmExports["b"];updateMemoryViews();removeRunDependency("wasm-instantiate");return wasmExports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(mod,inst)=>{receiveInstance(mod,inst);resolve(mod.exports)})})}wasmBinaryFile??=findWasmBinary();try{var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}catch(e){readyPromiseReject(e);return Promise.reject(e)}}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.unshift(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.unshift(cb);var noExitRuntime=Module["noExitRuntime"]||true;var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var b=wasmMemory.buffer;var pages=(size-b.byteLength+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var wasmImports={a:_emscripten_resize_heap};var wasmExports=await createWasm();var ___wasm_call_ctors=wasmExports["c"];var _getLibraryName=Module["_getLibraryName"]=wasmExports["d"];var _hello=Module["_hello"]=wasmExports["e"];var _malloc=Module["_malloc"]=wasmExports["f"];var _freeMemory=Module["_freeMemory"]=wasmExports["g"];var _free=Module["_free"]=wasmExports["h"];var _intSize=Module["_intSize"]=wasmExports["i"];var _boolSize=Module["_boolSize"]=wasmExports["j"];var _pointerSize=Module["_pointerSize"]=wasmExports["k"];var _sum4=Module["_sum4"]=wasmExports["l"];var _static_init_check=Module["_static_init_check"]=wasmExports["n"];function run(){if(runDependencies>0){dependenciesFulfilled=run;return}preRun();if(runDependencies>0){dependenciesFulfilled=run;return}function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run();moduleRtn=readyPromise; + + + return moduleRtn; +} +); +})(); +if (typeof exports === 'object' && typeof module === 'object') { + module.exports = native_example; + // This default export looks redundant, but it allows TS to import this + // commonjs style module. + module.exports.default = native_example; +} else if (typeof define === 'function' && define['amd']) + define([], () => native_example); diff --git a/example_flutter/assets/emscripten/native_example.wasm b/example_flutter/assets/emscripten/native_example.wasm index 28002a7..9e353c1 100755 Binary files a/example_flutter/assets/emscripten/native_example.wasm and b/example_flutter/assets/emscripten/native_example.wasm differ diff --git a/example_flutter/assets/standalone/native_example.wasm b/example_flutter/assets/standalone/native_example.wasm index 2330979..2423fef 100755 Binary files a/example_flutter/assets/standalone/native_example.wasm and b/example_flutter/assets/standalone/native_example.wasm differ diff --git a/example_flutter/lib/example_library_wrapper.dart b/example_flutter/lib/example_library_wrapper.dart index 5f7fb2c..d16d596 100644 --- a/example_flutter/lib/example_library_wrapper.dart +++ b/example_flutter/lib/example_library_wrapper.dart @@ -32,5 +32,7 @@ class ExampleLibraryWrapper { int pointerSize() => bindings.pointerSize(); + int sum4() => bindings.sum4(1, 2, 3, 4); + bool staticInitCheck() => bindings.static_init_check() != 0; } diff --git a/example_flutter/lib/main.dart b/example_flutter/lib/main.dart index 3af686d..ddb96e4 100644 --- a/example_flutter/lib/main.dart +++ b/example_flutter/lib/main.dart @@ -70,6 +70,7 @@ class _AsyncRunnerWidgetState extends State { 'Size of Int': runner.intSize().toString(), 'Size of Bool': runner.boolSize().toString(), 'Size of Pointer': runner.pointerSize().toString(), + 'Sum4': runner.sum4().toString(), 'Static Init Check': runner.staticInitCheck().toString(), }; } diff --git a/example_flutter/lib/native_example_bindings.dart b/example_flutter/lib/native_example_bindings.dart index a75dd14..76f16d3 100644 --- a/example_flutter/lib/native_example_bindings.dart +++ b/example_flutter/lib/native_example_bindings.dart @@ -87,6 +87,17 @@ class NativeExampleBindings { ); late final _pointerSize = _pointerSizePtr.asFunction(); + /// sum four numbers + int sum4(int a, int b, int c, int d) { + return _sum4(a, b, c, d); + } + + late final _sum4Ptr = + _lookup< + ffi.NativeFunction + >('sum4'); + late final _sum4 = _sum4Ptr.asFunction(); + /// check if static initialization happened int static_init_check() { return _static_init_check(); diff --git a/example_flutter/src/native_example.c b/example_flutter/src/native_example.c index 9007471..e22b420 100644 --- a/example_flutter/src/native_example.c +++ b/example_flutter/src/native_example.c @@ -42,3 +42,9 @@ EXPORT int pointerSize(void) { return (int)sizeof(void*); } + +/// sum four numbers +EXPORT int sum4(int a, int b, int c, int d) +{ + return a + b + c + d; +} diff --git a/example_flutter/src/native_example.h b/example_flutter/src/native_example.h index 3d10a2d..f7570eb 100644 --- a/example_flutter/src/native_example.h +++ b/example_flutter/src/native_example.h @@ -33,6 +33,9 @@ EXPORT int boolSize(void); /// size of a pointer EXPORT int pointerSize(void); +/// sum four numbers +EXPORT int sum4(int a, int b, int c, int d); + /// check if static initialization happened EXPORT int static_init_check(void); @@ -40,4 +43,4 @@ EXPORT int static_init_check(void); } #endif -#endif \ No newline at end of file +#endif diff --git a/lib/src/ffi/extensions.dart b/lib/src/ffi/extensions.dart index 00eeac6..167ddb5 100644 --- a/lib/src/ffi/extensions.dart +++ b/lib/src/ffi/extensions.dart @@ -1,4 +1,3 @@ -import 'dart:js_interop'; import 'dart:typed_data'; import 'annotations.dart'; import 'exceptions.dart'; @@ -21,9 +20,8 @@ extension NativeFunctionPointer DF asFunction() { // ignore: prefer_final_locals WasmSymbol symbol = symbolByAddress(boundMemory, address); - if (symbol is FunctionDescription && symbol.function.isA()) { - // ignore: invalid_runtime_check_with_js_interop_types - return marshall(symbol.function as Function, boundMemory); + if (symbol is FunctionDescription) { + return marshall(symbol.function, boundMemory); } else { throw ArgumentError( 'No function at address $address was found (but a global symbol)!', diff --git a/lib/src/ffi/invoker_generated.dart b/lib/src/ffi/invoker_generated.dart index 89e2eea..b340f28 100644 --- a/lib/src/ffi/invoker_generated.dart +++ b/lib/src/ffi/invoker_generated.dart @@ -12,7 +12,7 @@ class OpaqueInvokeHelper extends InvokeHelper> { OpaqueInvokeHelper(super.base, super.memory); @override - InvokeHelper> copyWith(Function base, Memory memory) { + InvokeHelper> copyWith(Object base, Memory memory) { return OpaqueInvokeHelper(base, memory); } @@ -282,7 +282,7 @@ class OpaqueInvokeHelperSquare OpaqueInvokeHelperSquare(super.base, super.memory); @override - InvokeHelper>> copyWith(Function base, Memory memory) { + InvokeHelper>> copyWith(Object base, Memory memory) { return OpaqueInvokeHelperSquare(base, memory); } @@ -549,11 +549,11 @@ class OpaqueInvokeHelperSquare class InvokeHelper { final Memory? _memory; - final Function? _base; + final Object? _base; const InvokeHelper(this._base, this._memory); - InvokeHelper copyWith(Function base, Memory memory) { + InvokeHelper copyWith(Object base, Memory memory) { return InvokeHelper(base, memory); } @@ -688,7 +688,7 @@ class InvokeHelper { if (_base == null || _memory == null) { throw StateError('Call copyWith first!'); } - final Function base = _base; + final Object base = _base; final Memory memory = _memory; final List args = [ arg0, diff --git a/lib/src/ffi/js_function_apply.dart b/lib/src/ffi/js_function_apply.dart new file mode 100644 index 0000000..c00eaa5 --- /dev/null +++ b/lib/src/ffi/js_function_apply.dart @@ -0,0 +1,2 @@ +export 'js_function_apply_stub.dart' + if (dart.library.js_interop) 'js_function_apply_web.dart'; diff --git a/lib/src/ffi/js_function_apply_stub.dart b/lib/src/ffi/js_function_apply_stub.dart new file mode 100644 index 0000000..2a31f33 --- /dev/null +++ b/lib/src/ffi/js_function_apply_stub.dart @@ -0,0 +1,33 @@ +import 'exceptions.dart'; +import 'memory.dart'; +import 'types.dart'; + +Object? applyJsFunction( + Object base, + List args, [ + Set jsBigIntArgumentIndexes = const {}, +]) { + throw const MarshallingException( + 'JavaScript function invocation is only available in JavaScript runtimes!', + ); +} + +Object? toJsFunctionArgument(Object dartObject, {bool asBigInt = false}) { + if (dartObject is int || dartObject is double || dartObject is bool) { + return dartObject; + } else if (dartObject is Pointer) { + return dartObject.address; + } + + throw MarshallingException( + 'Could not convert dart type ${dartObject.runtimeType} to a JavaScript type!', + ); +} + +T jsResultToDartType( + Object result, + Memory memory, + R Function(Object value, Memory memory) toDartType, +) { + return toDartType(result, memory); +} diff --git a/lib/src/ffi/js_function_apply_web.dart b/lib/src/ffi/js_function_apply_web.dart new file mode 100644 index 0000000..d35a48c --- /dev/null +++ b/lib/src/ffi/js_function_apply_web.dart @@ -0,0 +1,93 @@ +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; + +import 'exceptions.dart'; +import 'memory.dart'; +import 'types.dart'; + +@JS('BigInt') +external JSBigInt _bigInt(JSAny? obj); + +extension type _WrappedJSAny._(JSAny _) implements JSAny { + @JS('toString') + external JSString _toString(); +} + +Object? applyJsFunction( + Object base, + List args, [ + Set jsBigIntArgumentIndexes = const {}, +]) { + final jsFunction = base as JSFunction; + final JSFunction apply = (jsFunction as JSObject).getProperty( + 'apply'.toJS, + ); + final jsArgs = [ + for (var i = 0; i < args.length; i++) + _toJsAny(args[i], asBigInt: jsBigIntArgumentIndexes.contains(i)), + ]; + return apply.callAsFunction(jsFunction, null, jsArgs.toJS); +} + +Object? toJsFunctionArgument(Object dartObject, {bool asBigInt = false}) { + return _toJsAny(dartObject, asBigInt: asBigInt); +} + +T jsResultToDartType( + Object result, + Memory memory, + R Function(Object value, Memory memory) toDartType, +) { + final jsResult = result as JSAny; + if (T == int) { + if (jsResult.typeofEquals('number')) { + return (jsResult as JSNumber).toDartInt as T; + } + if (jsResult.typeofEquals('bigint')) { + return _jsBigIntToDartInt(jsResult) as T; + } + } else if (T == double) { + if (jsResult.typeofEquals('number')) { + return (jsResult as JSNumber).toDartDouble as T; + } + } else if (T == bool) { + if (jsResult.typeofEquals('boolean')) { + return (jsResult as JSBoolean).toDart as T; + } + if (jsResult.typeofEquals('number')) { + return toDartType((jsResult as JSNumber).toDartInt, memory); + } + } else if (jsResult.typeofEquals('number')) { + return toDartType((jsResult as JSNumber).toDartInt, memory); + } else if (jsResult.typeofEquals('bigint')) { + return toDartType(_jsBigIntToDartInt(jsResult), memory); + } + + final Object? dartified = jsResult.dartify(); + if (dartified == null) { + return null as T; + } + return toDartType(dartified, memory); +} + +int _jsBigIntToDartInt(JSAny jsBigInt) { + return BigInt.parse((jsBigInt as _WrappedJSAny)._toString().toDart).toInt(); +} + +JSAny? _toJsAny(Object dartObject, {bool asBigInt = false}) { + if (dartObject is int) { + return asBigInt ? _bigInt(dartObject.toString().toJS) : dartObject.toJS; + } else if (dartObject is double) { + return dartObject.toJS; + } else if (dartObject is bool) { + return dartObject.toJS; + } else if (dartObject is Pointer) { + return asBigInt + ? _bigInt(dartObject.address.toString().toJS) + : dartObject.address.toJS; + } else { + throw MarshallingException( + 'Could not convert dart type ${dartObject.runtimeType} to a JavaScript type!', + ); + } +} diff --git a/lib/src/ffi/marshaller.dart b/lib/src/ffi/marshaller.dart index 95faae9..3c8cfad 100644 --- a/lib/src/ffi/marshaller.dart +++ b/lib/src/ffi/marshaller.dart @@ -3,6 +3,7 @@ import 'package:meta/meta.dart'; import 'annotations.dart'; import 'exceptions.dart'; import 'invoker_generated.dart'; +import 'js_function_apply.dart'; import 'memory.dart'; import 'type_utils.dart'; import 'types.dart'; @@ -52,7 +53,8 @@ void initTypes([int pointerSizeBytes = 4]) { if (registeredPointerSizeBytes != null) { if (registeredPointerSizeBytes != pointerSizeBytes) { throw MarshallingException( - 'Can not change pointer size after it was set to $registeredPointerSizeBytes!'); + 'Can not change pointer size after it was set to $registeredPointerSizeBytes!', + ); } return; } @@ -78,41 +80,190 @@ void initTypes([int pointerSizeBytes = 4]) { } // Called from the invokers -T execute(Function base, List args, Memory memory) { +T execute(Object base, List args, Memory memory) { + final invocation = base is _NativeFunctionInvocation ? base : null; + final Object target = invocation?.base ?? base; + final jsBigIntArgumentIndexes = + invocation?.jsBigIntArgumentIndexes ?? const {}; + if (T == DartVoidType) { - Function.apply(base, args.map(_toJsType).toList()); + if (target is Function) { + Function.apply( + target, + _toJsFunctionArguments(args, jsBigIntArgumentIndexes), + ); + } else { + applyJsFunction(target, args, jsBigIntArgumentIndexes); + } return null as T; } else { - final Object? result = Function.apply(base, args.map(_toJsType).toList()); + if (target is Function) { + final Object? result = Function.apply( + target, + _toJsFunctionArguments(args, jsBigIntArgumentIndexes), + ); + if (result == null) { + return null as T; + } + return _toDartType(result, memory); + } + + final Object? result = applyJsFunction( + target, + args, + jsBigIntArgumentIndexes, + ); if (result == null) { return null as T; } - return _toDartType(result, memory); + return jsResultToDartType(result, memory, _toDartType); } } DF marshall( - Function base, Memory memory) { - return _inferFromSignature(DF.toString()).copyWith(base, memory).run as DF; + Object base, + Memory memory, +) { + final target = _NativeFunctionInvocation( + base, + _jsBigIntArgumentIndexes(typeString()), + ); + return _inferFromSignature(DF.toString()).copyWith(target, memory).run as DF; } -Object _toJsType(Object dartObject) { - if (dartObject is int || dartObject is double || dartObject is bool) { - return dartObject; - } else if (dartObject is Pointer) { - return dartObject.address; - } else { - throw MarshallingException( - 'Could not convert dart type ${dartObject.runtimeType} to a JavaScript type!'); +@visibleForTesting +List jsBigIntArgumentIndexesForTesting({ + int? pointerSizeBytes, +}) => List.unmodifiable( + _jsBigIntArgumentIndexes( + typeString(), + pointerSizeBytes: pointerSizeBytes, + ), +); + +final class _NativeFunctionInvocation { + const _NativeFunctionInvocation(this.base, this.jsBigIntArgumentIndexes); + + final Object base; + final Set jsBigIntArgumentIndexes; +} + +Set _jsBigIntArgumentIndexes( + String nativeSignature, { + int? pointerSizeBytes, +}) { + final indexes = {}; + final argTypes = _argumentTypesFromSignature(nativeSignature); + for (var i = 0; i < argTypes.length; i++) { + if (_usesJsBigInt(argTypes[i], pointerSizeBytes)) { + indexes.add(i); + } } + return indexes; +} + +List _argumentTypesFromSignature(String signature) { + final separator = _topLevelReturnSeparator(signature); + final argList = + (separator == -1 ? signature : signature.substring(0, separator)).trim(); + if (!argList.startsWith('(') || !argList.endsWith(')')) { + return const []; + } + + final body = argList.substring(1, argList.length - 1).trim(); + if (body.isEmpty) { + return const []; + } + + final result = []; + var angleDepth = 0; + var parenDepth = 0; + var start = 0; + for (var i = 0; i < body.length; i++) { + final codeUnit = body.codeUnitAt(i); + if (codeUnit == 0x3d && + i + 1 < body.length && + body.codeUnitAt(i + 1) == 0x3e) { + i++; + } else if (codeUnit == 0x3c) { + angleDepth++; + } else if (codeUnit == 0x3e) { + angleDepth--; + } else if (codeUnit == 0x28) { + parenDepth++; + } else if (codeUnit == 0x29) { + parenDepth--; + } else if (codeUnit == 0x2c && angleDepth == 0 && parenDepth == 0) { + result.add(body.substring(start, i).trim()); + start = i + 1; + } + } + result.add(body.substring(start).trim()); + return result; +} + +int _topLevelReturnSeparator(String signature) { + var angleDepth = 0; + var parenDepth = 0; + for (var i = 0; i < signature.length - 1; i++) { + final codeUnit = signature.codeUnitAt(i); + if (codeUnit == 0x3d && signature.codeUnitAt(i + 1) == 0x3e) { + if (angleDepth == 0 && parenDepth == 0) { + return i; + } + i++; + } else if (codeUnit == 0x3c) { + angleDepth++; + } else if (codeUnit == 0x3e) { + angleDepth--; + } else if (codeUnit == 0x28) { + parenDepth++; + } else if (codeUnit == 0x29) { + parenDepth--; + } + } + return -1; +} + +final String _pointerTypePrefix = typeString>() + .split(typeString()) + .first; + +bool _usesJsBigInt(String nativeType, int? pointerSizeBytes) { + if (nativeType == typeString() || nativeType == typeString()) { + return true; + } + + if ((pointerSizeBytes ?? registeredPointerSizeBytes) != 8) { + return false; + } + + return nativeType == typeString() || + nativeType == typeString() || + nativeType == typeString() || + nativeType.startsWith(_pointerTypePrefix); +} + +List _toJsFunctionArguments( + List args, + Set jsBigIntArgumentIndexes, +) { + return [ + for (var i = 0; i < args.length; i++) + toJsFunctionArgument( + args[i], + asBigInt: jsBigIntArgumentIndexes.contains(i), + ), + ]; } InvokeHelper _inferFromSignature(String signature) { final String returnType = signature.split('=>').last.trim(); if (returnType.startsWith(pointerPointerPointerPrefix)) { throw const MarshallingException( - 'Nesting pointers is only supported to a deepth of 2!' - '\nThis means that you can write Pointer> but not Pointer>>, ...'); + 'Nesting pointers is only supported to a deepth of 2!' + '\nThis means that you can write Pointer> but not Pointer>>, ...', + ); } final InvokeHelper? h = _knownTypes[returnType]; if (h != null) { @@ -120,11 +271,13 @@ InvokeHelper _inferFromSignature(String signature) { } else { if (returnType.startsWith(pointerNativeFunctionPrefix)) { throw const MarshallingException( - 'Using pointers to native functions as return type is only allowed if the type of the native function is dynamic!' - '\nThis means that only Pointer> is allowed!'); + 'Using pointers to native functions as return type is only allowed if the type of the native function is dynamic!' + '\nThis means that only Pointer> is allowed!', + ); } else { throw MarshallingException( - 'Unknown type $returnType (infered from $signature), all marshallable types: ${listKnownTypes()}'); + 'Unknown type $returnType (infered from $signature), all marshallable types: ${listKnownTypes()}', + ); } } } @@ -137,7 +290,7 @@ final Map _knownTypes = { typeString(): const InvokeHelper(null, null), typeString(): const InvokeHelper(null, null), typeString(): const InvokeHelper(null, null), - typeString(): const InvokeHelper(null, null) + typeString(): const InvokeHelper(null, null), }; final Map _knownTypes2 = { @@ -151,18 +304,20 @@ void _registerNativeMarshallerType() { _knownTypes[typeString>()] = InvokeHelper>(null, null); _knownTypes[typeString>>()] = InvokeHelper>>(null, null); - _knownTypes2[typeString>()] = - (Object o, Memory b) => _toDartType>(o, b); - _knownTypes2[typeString>>()] = - (Object o, Memory b) => _toDartType>>(o, b); + _knownTypes2[typeString>()] = (Object o, Memory b) => + _toDartType>(o, b); + _knownTypes2[typeString>>()] = (Object o, Memory b) => + _toDartType>>(o, b); } void _registerNativeMarshallerOpaque() { _knownTypes[typeString>()] = OpaqueInvokeHelper(null, null); - _knownTypes[typeString>>()] = - OpaqueInvokeHelperSquare(null, null); - _knownTypes2[typeString>()] = - (Object o, Memory b) => _toDartType>(o, b).cast(); + _knownTypes[typeString>>()] = OpaqueInvokeHelperSquare( + null, + null, + ); + _knownTypes2[typeString>()] = (Object o, Memory b) => + _toDartType>(o, b).cast(); _knownTypes2[typeString>>()] = (Object o, Memory b) => _toDartType>>(o, b).cast>(); } @@ -416,7 +571,8 @@ T _toDartType(Object o, Memory bind) { } } else { throw MarshallingException( - 'Can not back-marshall to type $T (object type is ${o.runtimeType})'); + 'Can not back-marshall to type $T (object type is ${o.runtimeType})', + ); } } } diff --git a/lib/src/ffi/modules/standalone_module.dart b/lib/src/ffi/modules/standalone_module.dart index 89ff8cf..518ab6b 100644 --- a/lib/src/ffi/modules/standalone_module.dart +++ b/lib/src/ffi/modules/standalone_module.dart @@ -3,6 +3,7 @@ import 'dart:typed_data'; import '../../js_utils/wasm_interop.dart'; import '../annotations.dart'; +import '../extensions.dart'; import '../memory.dart'; import '../type_utils.dart'; import '../types.dart'; @@ -113,7 +114,6 @@ class StandaloneWasmModule extends Module { String name, Memory memory, ) { - // ignore: invalid_runtime_check_with_js_interop_types - return _instance.functions[name]! as F; + return lookup>(name, memory).asFunction(); } } diff --git a/lib/src/js_utils/wasm_interop.dart b/lib/src/js_utils/wasm_interop.dart index de9650b..0154b1b 100644 --- a/lib/src/js_utils/wasm_interop.dart +++ b/lib/src/js_utils/wasm_interop.dart @@ -213,9 +213,8 @@ class Instance { // TODO throw StateError('Could not find an export named $key'); continue; } - // ignore: invalid_runtime_check_with_js_interop_types - if (value is Function) { - functions[key] = (value as JSFunction); + if (value.typeofEquals('function')) { + functions[key] = value as JSFunction; } else if (WasmGlobal.isInstance(value)) { globals[key] = (value as WasmGlobal); } else if (WasmMemory.isInstance(value)) { diff --git a/test/marshaller_signature_test.dart b/test/marshaller_signature_test.dart new file mode 100644 index 0000000..6257499 --- /dev/null +++ b/test/marshaller_signature_test.dart @@ -0,0 +1,54 @@ +import 'package:test/test.dart'; +import 'package:wasm_ffi/ffi.dart'; +import 'package:wasm_ffi/src/ffi/marshaller.dart' as marshaller; + +void main() { + test('identifies native i64 arguments that require JS BigInt', () { + expect( + marshaller + .jsBigIntArgumentIndexesForTesting< + Int Function(Int64, Uint64, Int, Pointer) + >(), + [0, 1], + ); + }); + + test('handles native functions without arguments', () { + expect( + marshaller.jsBigIntArgumentIndexesForTesting(), + isEmpty, + ); + }); + + test( + 'identifies pointer-sized arguments that require JS BigInt on wasm64', + () { + expect( + marshaller.jsBigIntArgumentIndexesForTesting< + Void Function(Pointer, IntPtr, UintPtr, Size, Int) + >(pointerSizeBytes: 8), + [0, 1, 2, 3], + ); + expect( + marshaller.jsBigIntArgumentIndexesForTesting< + Void Function(Pointer, IntPtr, UintPtr, Size, Int) + >(pointerSizeBytes: 4), + isEmpty, + ); + }, + ); + + test('handles nested function type arguments when parsing signatures', () { + expect( + marshaller + .jsBigIntArgumentIndexesForTesting< + Void Function( + Int64, + Pointer>, + Uint64, + ) + >(), + [0, 2], + ); + }); +}