Closed Keno closed 5 years ago
I don't think we can call into libjulia functions like _jl_cpu_threads
unless they are exported. It looks like they are not exported in hello.wast
. Maybe JL_DLLEXPORT
could be defined to also include Emscripten's EMSCRIPTEN_KEEPALIVE
. That should make them all exported. I think there's also a way to export everything.
Yes, I've done that locally (sorry, should have mentioned) and it does add it to the export list, but that still doesn't help me get their fptrs. The two things I've contemplated doing if there's no solution in the wasm JS API is to post process the .wasm file to build this map and store it as a JS dict or to just generate the C code I write now manually.
For exported functions, I think you can do getCFunc("jl_initialize")
or Module["_jl_initialize"]
on the JS side.
Right the problem is that I don't know how to get the wasm function id from that. It is possible to insert it into the function table but that incurs another round trip that should be avoidable, since a call.indirect with the correct function index should just work.
If you don't want to round-trip through JS, then I think you have to generate the C code like you mentioned. If you want to use getCFunc
via JavaScript, I think that'll be something like the following in C:
extern int getCFunc(char *);
...
fn = getCFunc(name);
On the JS side, getCFunc
gets passed in with the environment. This is pretty easy with wasm-ffi. With just Emscripten, I think you can do the same thing.
If you don't want to round-trip through JS, then I think you have to generate the C code like you mentioned.
Well, it worked fine when I looked up the correct id manually in the binary. So either we need to automate that, which I feel like should be possible, I just don't know if it's exposed in javascript, or we need to generate C code.
Looks like what I may want to do is build a reverse dictionary from WebAssembly.Module.customSections(mod, "name");
. Let me try that.
I've learned some things about how wasm works. In particular it looks like to call a function it should either be first declared in exports or in element (the latter of which initializes the function table we jump through). I think the next think I'm gonna try is to parse the c source code to automatically generate the stuff I wrote manually. That shouldn't be too hard, and be both robust and fast. In the future, esp once we figure out jit and dynamic libraries, we can revisit this.
Leaving for myself an earlier attempt, in case I want to go back to it:
diff --git a/website/repl.js b/website/repl.js
index 566ace2..2647dac 100644
--- a/website/repl.js
+++ b/website/repl.js
@@ -671,6 +671,48 @@ function process_input(input) {
enable_prompt();
}
+function decode_leb128(buffer, idx) {
+ var result = 0;
+ var shift = 0;
+ while (true) {
+ if (shift > (32 - 7)) {
+ throw("Out of range");
+ }
+ b = buffer[idx]; idx += 1;
+ result |= (b & 0x7f) << shift;
+ if ((b & 0x80) == 0) {
+ return [result, idx];
+ }
+ shift += 7;
+ }
+}
+
+const NAME_TYPE_FUNCITON = 1;
+function decode_name_section(sect) {
+ idx = 0;
+ var function_mapping = {};
+ while (idx < sect.length) {
+ [name_type, idx] = decode_leb128(sect, idx);
+ [subsection_size, idx] = decode_leb128(sect, idx);
+ if (name_type == NAME_TYPE_FUNCITON) {
+ [count, idx] = decode_leb128(sect, idx);
+ n = 0;
+ while (n < count) {
+ [index, idx] = decode_leb128(sect, idx);
+ [name_len, idx] = decode_leb128(sect, idx);
+ name = UTF8Decoder.decode(sect.subarray(idx, idx+name_len));
+ function_mapping[name] = index;
+ idx += name_len;
+ n += 1;
+ }
+ return function_mapping;
+ } else {
+ idx += subsection_size;
+ }
+ }
+ throw("Name section not present");
+}
+
var Module = {
preRun: [],
postRun: [],
@@ -703,7 +745,23 @@ var Module = {
Module.stringToUTF8(input, ptr, input.length + 1);
Module._jl_eval_string(ptr);
enable_prompt();
- }]
+ }],
+ instantiateWasm: function(info, receiveInstance) {
+ WebAssembly.instantiateStreaming(fetch("hello.wasm", { credentials: 'same-origin' }), info)
+ .then(function(output) {
+ Module.wasmModule = output.module;
+ Module.fmap = decode_name_section(new Uint8Array(WebAssembly.Module.customSections(Module.wasmModule, "name")[0]));
+ receiveInstance(output.instance)
+ })
+ .catch(function(reason) {
+ // We expect the most common failure cause to be a bad MIME type for the binary,
+ // in which case falling back to ArrayBuffer instantiation should work.
+ err('wasm streaming compile failed: ' + reason);
+ err('falling back to ArrayBuffer instantiation');
+ instantiateArrayBuffer(receiveInstantiatedSource);
+ });
+ return {}; // Compiling asynchronously, no exports.
+ }
};
Module.setStatus('Downloading...');
window.onerror = function(event) {
This all currently relies on manually adding special case foreigncall support for all commonly called functions: https://github.com/JuliaLang/julia/blob/kf/wasm3/src/interpreter.c#L547-L1038. It's gotten us this far, but it's a very ugly hack. We should be able to look up the wasm function pointers, compute the wasm signature from the julia signature and just immediately jump to it. The only thing I couldn't figure out here is how to get that function pointer given the name of the function. I tried something like:
but that doesn't work properly (i.e. the number I get back is not the correct function pointer). If somebody can figure out how to take care of that, the rest should be simple.