From 9e624258e3566cf6ca359f540c8bbc67e613829e Mon Sep 17 00:00:00 2001 From: Daniel Hoer Date: Mon, 1 Jun 2026 23:24:23 +0200 Subject: [PATCH 1/4] Fix dart2wasm standalone function exports --- example/web/main.dart | 1 + example_flutter/Makefile | 7 +- .../assets/emscripten/native_example.js | 23 +++++- .../assets/emscripten/native_example.wasm | Bin 11874 -> 11893 bytes .../assets/standalone/native_example.wasm | Bin 12075 -> 12105 bytes .../lib/example_library_wrapper.dart | 2 + example_flutter/lib/main.dart | 1 + .../lib/native_example_bindings.dart | 11 +++ example_flutter/src/native_example.c | 6 ++ example_flutter/src/native_example.h | 5 +- lib/src/ffi/extensions.dart | 6 +- lib/src/ffi/invoker_generated.dart | 10 +-- lib/src/ffi/js_function_apply.dart | 2 + lib/src/ffi/js_function_apply_stub.dart | 16 ++++ lib/src/ffi/js_function_apply_web.dart | 72 ++++++++++++++++++ lib/src/ffi/marshaller.dart | 24 ++++-- lib/src/ffi/modules/standalone_module.dart | 4 +- lib/src/js_utils/wasm_interop.dart | 5 +- 18 files changed, 169 insertions(+), 26 deletions(-) create mode 100644 lib/src/ffi/js_function_apply.dart create mode 100644 lib/src/ffi/js_function_apply_stub.dart create mode 100644 lib/src/ffi/js_function_apply_web.dart 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 28002a788203b4ce9f688aa23d370ae9174fb5e1..9e353c1f8b3ace83edb3330e76dd4d743bc03ac4 100755 GIT binary patch delta 2433 zcmZuzU2GIp6u#%q%@?+m51wG9o{LW_d$;;en){duIy>Op~2E zbMN{4&Ue1)wO#-2I^MEDFW?1*Aw%8^WQdR&VgTeGBJ;O*J;nkyqHlC^` zHdwr5^R6CBc)_vwlo*98=``WfHV66gVvJw1<>H3T+wm^GjSj&TvC7USug5UhR_TZ7 zDh|!i48IW*hwWy4F2?7?hxYrhAb4z`kx$g*e2V9pwi$DvOgd*^1D}uad2u#&B3yLj zwjoY6)ro1x?a|N`zUj9^d?Ch($|1O8o@&bvGXuTh1JmR-9$OG6oh?D|znsgrg_Q^I z3VXGByk{ZCq;JPU(3W6O0))u%<4(?_i0GWm!Phxf$+cHUeKL#~Vri2#J2&R}!#NKu zrpam=n3g5$X*B~*M`gL5P78=g0V|X)Lg)fUeX8u|#aj2PjIUmHf#DCT0wYNFT@qlcOn znwZ!c21o&PM8(=Ajf7h;_j@xBcA+{n=P!z7F~3+#i=4*x_r7IP9@`!U)_L~ z)8gx7(<4*T#5d4*Laj^&IX}Y8LF`Fn>xdn(XogR_orG{*VkM_V{&7WoWmpnksuwPb zU8xQ$a3^z_5F)i3X2n0Lb^5f+r$yU}_3I|wAPkP4uV!OX24zH+Ht8x#%Xoor4PTbT zJ1f={)XZYr@g$>45BT)}CkxRd{9~8tvU4R@frtF$1^N7z!xc1geJ&_l%P<@X$xh@B z5>0Ycb{m(I_(Cm_+L#_^{Wh^A;$mGo0Yx#8X8KWw|11usJ5oi5Y5Wk=@;p53lU&e{ z#TZ`{<#bDM>}opIpfYFlQ7mOSV{9M_kAbDR4V_RDnTEBnC|+quVTfZ5l@Omcbac(Q zZL~6*rC@3D8CMO5Ea=Xn&xxv)yZwp?keea^T#XTmKrqB};uEh~%&hDbOLd00wz9ra zPCcle1~YG<1sU~KyHVxp8$WLh7)V%X&?XWQT>YPe?YfSVEK*H7#CuJhX~{t)c1*uZ z>OO0FI*__3J0D0L3ImF#%>1cN>m== zNOL`^#6WR^UE zD1@?vB@g>a(Q9@O(hN)bsjL*Q>L+nC=_Qa=7zyc__YIa}sccozPh_jc4xgz+Ol6nw zItduoOQ~d4dMqJk%z+d}d7POem@jj@2nvT8jakfO$#QMvet?ANuj+)rBf$8iWx*_JjGf&Tyu9}gP< delta 2530 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 OI?rP(p6g5}`2PU7vu7v( delta 2862 zcmZ`*eP|rV6`z@%z1zFJ-94@Eba&Fpnq7UIP9L%CI#%mA_RSI7byQhS3PJ53L15g5 zQ%icXNE5Jq2US~>h7x%aLWpQo5Vq=ENI;?W9}P|*A1+ObOIn(Seo^RuhAOQIYB9L) z&7K<7B`2{nJNxFn_j@0|nO{8o(!);=4iGZS8}OrzSrf+wnPvE9bY@LL4MGT~v868! z7=NYi(!#-PI{CHvQ>Ug*ee==zCy2rEg>)>v+qd-bE*et-?WQ}_y>#cXOd?K+q&9U^ zG@WeCWbYwz&5?8;Bl4OnuRAzdab>f~aklKrzg*C>rYnbWW!04{&JZJNkv>P$s>s%9 zLS1LG)Ka^R0?a3k=h7WECz9iX+7=kN!0RLb&ah0wI44)E!0aQkQVV1KM8x2IA3FeJ zxfWz$DAj^a^(?=f&fV`M1mP7yL_+X%a(&vYQ);U>V{gOr`|&+7U+({g;Oc7p>olcG z)~{$mePUfS3SnNon8-Hrm!wx8Bh8dB=l2sL@>PC5%vZ^S4zX=vORIiZ6i&=LhTxJv zCYx3*$Ve8rDZ>_Cz(uTVjEA7?#9>}BDj-DXLM}jSye`Mv8jWold60;-I!st1J0Lj9 zb76h4JFrsBZY!*kF9WDpm59RGu%K$my;T8Ls!jN%zp71{Xv!4%unU|{HsNFP+}TTQ zqg~b8@OzOyq+w|9L|*+pc^u#$v~#qu{*e7SO7rT;)S%~RyOQiv)tEoNIQ~yXE zppM$*jAgwrA>y?#F8HKFA<~veq?4-d2>5=%ISFz7#F?R;YTDiZNN&>Mf=B_7Okt-T z5YGrVorJaVXbr38G=6{uOC%-HLJ5)7LhX&R`36Cu*bqAqm?>)8wj@CcqAu?Z;seHz(g+$OHw(Ny$h*_a|gjeJZ7m|BU7-}q&FEa^s zKm-$18uR-Y7gVrXNO2QX0IsB{Depl~zu0@kGxoLV+y_b8^xvBM-Q(&vpWg|H(y+tKQgBe<+N(e#m6w2(&j1xV)xvU88oS8~t2X z$Gf_6K2!rc>d=&FLj|)r5wC|l?W;!DUDFy#$OO>CTup-9Ts>rIA5}t02wnq2h zfSscbL~+fBe{e!_s1srfnvQ&cM)1|GuHHS%egq13hZ^z`b(itG&1h_HPjiqu6GJ(>NJ|bWBh34p%uNUQ0o@`)1pY|Z&U;yV=q97X;$Sw8) z)HikmRM@fyH4fv72cIC;(Bem-$uQYm4$Xx`{j0l!E-Jey_<2wMM(rw&78X6|$;X8; z2W%J;Xb`lq>B>#DP#lcJepDC3Y-iLGaC%KSh#Jyz zM^hlMKlYv&*O?R?Sq3Av6(n>Fc^5N-F0_dS&b3i-mvV=b>RBJd9{zXm7911RiORK?8J_C6C4&ePtg?C5b;XpD_&ws(QE%0O;cpbfk(A1&ZP_*xz z5;7-vgVYIuMQ=0=qP-DuU{d<+dKg!SMzV_auYHDRz{tRRpQfas{?=b9ZlwiU3q9l8 z)1u@+C-B?M!289})Gjb|!Mwu-RT+HS(j|Z#=alkr1Pqq;_5rmaog&p?9WxyC!PeiE`lA%wF1-N!`NGisy=~~mr_izP;jnBc!YD80qLk}{ zWqZoiOXWhSssE@0r-vI+UenW~XGf<3MD6)Cv3Py63C`$#1kPKL#7wR*|cCHw@{^g1lUfpQ&!bRi*bkXF{egE#3^4 z%%zYVA~L-&<-jq~a^yH@kPQgID%>1&IPDmuqo2V2u@7`VZ=?y_&g}Zwh**Dp?7KAi MW5 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..e5d2b81 --- /dev/null +++ b/lib/src/ffi/js_function_apply_stub.dart @@ -0,0 +1,16 @@ +import 'exceptions.dart'; +import 'memory.dart'; + +Object? applyJsFunction(Object base, List args) { + throw const MarshallingException( + 'JavaScript function invocation is only available in JavaScript runtimes!', + ); +} + +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..1268537 --- /dev/null +++ b/lib/src/ffi/js_function_apply_web.dart @@ -0,0 +1,72 @@ +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; + +import 'exceptions.dart'; +import 'memory.dart'; +import 'types.dart'; + +@JS('Number') +external JSNumber _number(JSAny? obj); + +Object? applyJsFunction(Object base, List args) { + final jsFunction = base as JSFunction; + final JSFunction apply = (jsFunction as JSObject).getProperty( + 'apply'.toJS, + ); + return apply.callAsFunction( + jsFunction, + null, + args.map(_toJsAny).toList().toJS, + ); +} + +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 _number(jsResult).toDartInt 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(_number(jsResult).toDartInt, memory); + } + + final Object? dartified = jsResult.dartify(); + if (dartified == null) { + return null as T; + } + return toDartType(dartified, memory); +} + +JSAny? _toJsAny(Object dartObject) { + if (dartObject is int || dartObject is double) { + return (dartObject as num).toJS; + } else if (dartObject is bool) { + return dartObject.toJS; + } else if (dartObject is Pointer) { + return 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..6fcfbd1 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'; @@ -78,21 +79,32 @@ 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) { if (T == DartVoidType) { - Function.apply(base, args.map(_toJsType).toList()); + if (base is Function) { + Function.apply(base, args.map(_toJsType).toList()); + } else { + applyJsFunction(base, args); + } return null as T; } else { - final Object? result = Function.apply(base, args.map(_toJsType).toList()); + if (base is Function) { + final Object? result = Function.apply(base, args.map(_toJsType).toList()); + if (result == null) { + return null as T; + } + return _toDartType(result, memory); + } + + final Object? result = applyJsFunction(base, args); if (result == null) { return null as T; } - return _toDartType(result, memory); + return jsResultToDartType(result, memory, _toDartType); } } -DF marshall( - Function base, Memory memory) { +DF marshall(Object base, Memory memory) { return _inferFromSignature(DF.toString()).copyWith(base, memory).run as DF; } 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)) { From 152224ed6eb3f57f54d55f266a2f5bd804210076 Mon Sep 17 00:00:00 2001 From: Daniel Hoer Date: Tue, 2 Jun 2026 00:07:12 +0200 Subject: [PATCH 2/4] Fix open PR bug reports --- lib/src/ffi/js_function_apply_stub.dart | 6 +- lib/src/ffi/js_function_apply_web.dart | 41 ++++--- lib/src/ffi/marshaller.dart | 137 +++++++++++++++++++----- test/marshaller_signature_test.dart | 22 ++++ 4 files changed, 167 insertions(+), 39 deletions(-) create mode 100644 test/marshaller_signature_test.dart diff --git a/lib/src/ffi/js_function_apply_stub.dart b/lib/src/ffi/js_function_apply_stub.dart index e5d2b81..a3172a6 100644 --- a/lib/src/ffi/js_function_apply_stub.dart +++ b/lib/src/ffi/js_function_apply_stub.dart @@ -1,7 +1,11 @@ import 'exceptions.dart'; import 'memory.dart'; -Object? applyJsFunction(Object base, List args) { +Object? applyJsFunction( + Object base, + List args, [ + Set jsBigIntArgumentIndexes = const {}, +]) { throw const MarshallingException( 'JavaScript function invocation is only available in JavaScript runtimes!', ); diff --git a/lib/src/ffi/js_function_apply_web.dart b/lib/src/ffi/js_function_apply_web.dart index 1268537..3653f4f 100644 --- a/lib/src/ffi/js_function_apply_web.dart +++ b/lib/src/ffi/js_function_apply_web.dart @@ -5,19 +5,28 @@ import 'exceptions.dart'; import 'memory.dart'; import 'types.dart'; -@JS('Number') -external JSNumber _number(JSAny? obj); +@JS('BigInt') +external JSBigInt _bigInt(JSAny? obj); -Object? applyJsFunction(Object base, List args) { +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, ); - return apply.callAsFunction( - jsFunction, - null, - args.map(_toJsAny).toList().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); } T jsResultToDartType( @@ -31,7 +40,7 @@ T jsResultToDartType( return (jsResult as JSNumber).toDartInt as T; } if (jsResult.typeofEquals('bigint')) { - return _number(jsResult).toDartInt as T; + return _jsBigIntToDartInt(jsResult) as T; } } else if (T == double) { if (jsResult.typeofEquals('number')) { @@ -47,7 +56,7 @@ T jsResultToDartType( } else if (jsResult.typeofEquals('number')) { return toDartType((jsResult as JSNumber).toDartInt, memory); } else if (jsResult.typeofEquals('bigint')) { - return toDartType(_number(jsResult).toDartInt, memory); + return toDartType(_jsBigIntToDartInt(jsResult), memory); } final Object? dartified = jsResult.dartify(); @@ -57,9 +66,15 @@ T jsResultToDartType( return toDartType(dartified, memory); } -JSAny? _toJsAny(Object dartObject) { - if (dartObject is int || dartObject is double) { - return (dartObject as num).toJS; +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) { diff --git a/lib/src/ffi/marshaller.dart b/lib/src/ffi/marshaller.dart index 6fcfbd1..9044f97 100644 --- a/lib/src/ffi/marshaller.dart +++ b/lib/src/ffi/marshaller.dart @@ -53,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; } @@ -80,23 +81,35 @@ void initTypes([int pointerSizeBytes = 4]) { // Called from the invokers 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) { - if (base is Function) { - Function.apply(base, args.map(_toJsType).toList()); + if (target is Function) { + Function.apply(target, args.map(_toJsType).toList()); } else { - applyJsFunction(base, args); + applyJsFunction(target, args, jsBigIntArgumentIndexes); } return null as T; } else { - if (base is Function) { - final Object? result = Function.apply(base, args.map(_toJsType).toList()); + if (target is Function) { + final Object? result = Function.apply( + target, + args.map(_toJsType).toList(), + ); if (result == null) { return null as T; } return _toDartType(result, memory); } - final Object? result = applyJsFunction(base, args); + final Object? result = applyJsFunction( + target, + args, + jsBigIntArgumentIndexes, + ); if (result == null) { return null as T; } @@ -104,8 +117,75 @@ T execute(Object base, List args, Memory memory) { } } -DF marshall(Object base, Memory memory) { - return _inferFromSignature(DF.toString()).copyWith(base, memory).run as DF; +DF marshall( + Object base, + Memory memory, +) { + final target = _NativeFunctionInvocation( + base, + _jsBigIntArgumentIndexes(typeString()), + ); + return _inferFromSignature(DF.toString()).copyWith(target, memory).run as DF; +} + +@visibleForTesting +List jsBigIntArgumentIndexesForTesting() => + List.unmodifiable(_jsBigIntArgumentIndexes(typeString())); + +final class _NativeFunctionInvocation { + const _NativeFunctionInvocation(this.base, this.jsBigIntArgumentIndexes); + + final Object base; + final Set jsBigIntArgumentIndexes; +} + +Set _jsBigIntArgumentIndexes(String nativeSignature) { + final indexes = {}; + final argTypes = _argumentTypesFromSignature(nativeSignature); + for (var i = 0; i < argTypes.length; i++) { + if (_usesJsBigInt(argTypes[i])) { + indexes.add(i); + } + } + return indexes; +} + +List _argumentTypesFromSignature(String signature) { + final argList = signature.split('=>').first.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 depth = 0; + var start = 0; + for (var i = 0; i < body.length; i++) { + final codeUnit = body.codeUnitAt(i); + if (codeUnit == 0x3c) { + depth++; + } else if (codeUnit == 0x3e) { + depth--; + } else if (codeUnit == 0x2c && depth == 0) { + result.add(body.substring(start, i).trim()); + start = i + 1; + } + } + result.add(body.substring(start).trim()); + return result; +} + +bool _usesJsBigInt(String nativeType) { + return nativeType == typeString() || + nativeType == typeString() || + registeredPointerSizeBytes == 8 && + (nativeType == typeString() || + nativeType == typeString() || + nativeType == typeString()); } Object _toJsType(Object dartObject) { @@ -115,7 +195,8 @@ Object _toJsType(Object dartObject) { return dartObject.address; } else { throw MarshallingException( - 'Could not convert dart type ${dartObject.runtimeType} to a JavaScript type!'); + 'Could not convert dart type ${dartObject.runtimeType} to a JavaScript type!', + ); } } @@ -123,8 +204,9 @@ 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) { @@ -132,11 +214,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()}', + ); } } } @@ -149,7 +233,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 = { @@ -163,18 +247,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>(); } @@ -428,7 +514,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/test/marshaller_signature_test.dart b/test/marshaller_signature_test.dart new file mode 100644 index 0000000..565cbca --- /dev/null +++ b/test/marshaller_signature_test.dart @@ -0,0 +1,22 @@ +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, + ); + }); +} From 7ff7b315a6f16797240eab28772143580442193b Mon Sep 17 00:00:00 2001 From: Daniel Hoer Date: Tue, 2 Jun 2026 00:52:31 +0200 Subject: [PATCH 3/4] Fix open PR bug reports --- lib/src/ffi/js_function_apply_web.dart | 4 ++- lib/src/ffi/marshaller.dart | 41 +++++++++++++++++++------- test/marshaller_signature_test.dart | 18 +++++++++++ 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/lib/src/ffi/js_function_apply_web.dart b/lib/src/ffi/js_function_apply_web.dart index 3653f4f..e3e85f3 100644 --- a/lib/src/ffi/js_function_apply_web.dart +++ b/lib/src/ffi/js_function_apply_web.dart @@ -78,7 +78,9 @@ JSAny? _toJsAny(Object dartObject, {bool asBigInt = false}) { } else if (dartObject is bool) { return dartObject.toJS; } else if (dartObject is Pointer) { - return dartObject.address.toJS; + 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 9044f97..567bf5e 100644 --- a/lib/src/ffi/marshaller.dart +++ b/lib/src/ffi/marshaller.dart @@ -129,8 +129,14 @@ DF marshall( } @visibleForTesting -List jsBigIntArgumentIndexesForTesting() => - List.unmodifiable(_jsBigIntArgumentIndexes(typeString())); +List jsBigIntArgumentIndexesForTesting({ + int? pointerSizeBytes, +}) => List.unmodifiable( + _jsBigIntArgumentIndexes( + typeString(), + pointerSizeBytes: pointerSizeBytes, + ), +); final class _NativeFunctionInvocation { const _NativeFunctionInvocation(this.base, this.jsBigIntArgumentIndexes); @@ -139,11 +145,14 @@ final class _NativeFunctionInvocation { final Set jsBigIntArgumentIndexes; } -Set _jsBigIntArgumentIndexes(String nativeSignature) { +Set _jsBigIntArgumentIndexes( + String nativeSignature, { + int? pointerSizeBytes, +}) { final indexes = {}; final argTypes = _argumentTypesFromSignature(nativeSignature); for (var i = 0; i < argTypes.length; i++) { - if (_usesJsBigInt(argTypes[i])) { + if (_usesJsBigInt(argTypes[i], pointerSizeBytes)) { indexes.add(i); } } @@ -179,13 +188,23 @@ List _argumentTypesFromSignature(String signature) { return result; } -bool _usesJsBigInt(String nativeType) { - return nativeType == typeString() || - nativeType == typeString() || - registeredPointerSizeBytes == 8 && - (nativeType == typeString() || - nativeType == typeString() || - nativeType == typeString()); +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); } Object _toJsType(Object dartObject) { diff --git a/test/marshaller_signature_test.dart b/test/marshaller_signature_test.dart index 565cbca..8f75ca4 100644 --- a/test/marshaller_signature_test.dart +++ b/test/marshaller_signature_test.dart @@ -19,4 +19,22 @@ void main() { 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, + ); + }, + ); } From ad0d974b595a19fae387ea2cdc4541b976eaa79e Mon Sep 17 00:00:00 2001 From: Daniel Hoer Date: Tue, 2 Jun 2026 06:08:48 +0200 Subject: [PATCH 4/4] Fix open PR bug reports --- lib/src/ffi/js_function_apply_stub.dart | 13 +++++ lib/src/ffi/js_function_apply_web.dart | 4 ++ lib/src/ffi/marshaller.dart | 74 +++++++++++++++++++------ test/marshaller_signature_test.dart | 14 +++++ 4 files changed, 87 insertions(+), 18 deletions(-) diff --git a/lib/src/ffi/js_function_apply_stub.dart b/lib/src/ffi/js_function_apply_stub.dart index a3172a6..2a31f33 100644 --- a/lib/src/ffi/js_function_apply_stub.dart +++ b/lib/src/ffi/js_function_apply_stub.dart @@ -1,5 +1,6 @@ import 'exceptions.dart'; import 'memory.dart'; +import 'types.dart'; Object? applyJsFunction( Object base, @@ -11,6 +12,18 @@ Object? applyJsFunction( ); } +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, diff --git a/lib/src/ffi/js_function_apply_web.dart b/lib/src/ffi/js_function_apply_web.dart index e3e85f3..d35a48c 100644 --- a/lib/src/ffi/js_function_apply_web.dart +++ b/lib/src/ffi/js_function_apply_web.dart @@ -29,6 +29,10 @@ Object? applyJsFunction( 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, diff --git a/lib/src/ffi/marshaller.dart b/lib/src/ffi/marshaller.dart index 567bf5e..3c8cfad 100644 --- a/lib/src/ffi/marshaller.dart +++ b/lib/src/ffi/marshaller.dart @@ -88,7 +88,10 @@ T execute(Object base, List args, Memory memory) { if (T == DartVoidType) { if (target is Function) { - Function.apply(target, args.map(_toJsType).toList()); + Function.apply( + target, + _toJsFunctionArguments(args, jsBigIntArgumentIndexes), + ); } else { applyJsFunction(target, args, jsBigIntArgumentIndexes); } @@ -97,7 +100,7 @@ T execute(Object base, List args, Memory memory) { if (target is Function) { final Object? result = Function.apply( target, - args.map(_toJsType).toList(), + _toJsFunctionArguments(args, jsBigIntArgumentIndexes), ); if (result == null) { return null as T; @@ -160,7 +163,9 @@ Set _jsBigIntArgumentIndexes( } List _argumentTypesFromSignature(String signature) { - final argList = signature.split('=>').first.trim(); + final separator = _topLevelReturnSeparator(signature); + final argList = + (separator == -1 ? signature : signature.substring(0, separator)).trim(); if (!argList.startsWith('(') || !argList.endsWith(')')) { return const []; } @@ -171,15 +176,24 @@ List _argumentTypesFromSignature(String signature) { } final result = []; - var depth = 0; + var angleDepth = 0; + var parenDepth = 0; var start = 0; for (var i = 0; i < body.length; i++) { final codeUnit = body.codeUnitAt(i); - if (codeUnit == 0x3c) { - depth++; + if (codeUnit == 0x3d && + i + 1 < body.length && + body.codeUnitAt(i + 1) == 0x3e) { + i++; + } else if (codeUnit == 0x3c) { + angleDepth++; } else if (codeUnit == 0x3e) { - depth--; - } else if (codeUnit == 0x2c && depth == 0) { + 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; } @@ -188,6 +202,29 @@ List _argumentTypesFromSignature(String signature) { 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; @@ -207,16 +244,17 @@ bool _usesJsBigInt(String nativeType, int? pointerSizeBytes) { nativeType.startsWith(_pointerTypePrefix); } -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!', - ); - } +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) { diff --git a/test/marshaller_signature_test.dart b/test/marshaller_signature_test.dart index 8f75ca4..6257499 100644 --- a/test/marshaller_signature_test.dart +++ b/test/marshaller_signature_test.dart @@ -37,4 +37,18 @@ void main() { ); }, ); + + test('handles nested function type arguments when parsing signatures', () { + expect( + marshaller + .jsBigIntArgumentIndexesForTesting< + Void Function( + Int64, + Pointer>, + Uint64, + ) + >(), + [0, 2], + ); + }); }