Skip to content

Commit 85b1395

Browse files
authored
do wasm/js glue in emscripten, in preparation for more advanced glue stuff like async compilation (#4859)
1 parent 62dfd0f commit 85b1395

File tree

3 files changed

+348
-15
lines changed

3 files changed

+348
-15
lines changed

emcc.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1255,6 +1255,11 @@ def check(input_file):
12551255
os.environ['EMCC_WASM_BACKEND_BINARYEN'] = '1'
12561256

12571257
if shared.Settings.BINARYEN:
1258+
# set file locations, so that JS glue can find what it needs
1259+
shared.Settings.WASM_TEXT_FILE = os.path.basename(wasm_text_target)
1260+
shared.Settings.WASM_BINARY_FILE = os.path.basename(wasm_binary_target)
1261+
shared.Settings.ASMJS_CODE_FILE = os.path.basename(asm_target)
1262+
12581263
shared.Settings.ASM_JS = 2 # when targeting wasm, we use a wasm Memory, but that is not compatible with asm.js opts
12591264
debug_level = max(1, debug_level) # keep whitespace readable, for asm.js parser simplicity
12601265
shared.Settings.GLOBAL_BASE = 1024 # leave some room for mapping global vars
@@ -1685,20 +1690,6 @@ def save_intermediate(name=None, suffix='js'):
16851690
file_code = execute([shared.PYTHON, shared.FILE_PACKAGER, unsuffixed(target) + '.data'] + file_args, stdout=PIPE)[0]
16861691
pre_js = file_code + pre_js
16871692

1688-
if shared.Settings.BINARYEN:
1689-
# add in the glue integration code as a pre-js, so it is optimized together with everything else
1690-
wasm_js_glue = open(os.path.join(shared.Settings.BINARYEN_ROOT, 'src', 'js', 'wasm.js-post.js')).read()
1691-
wasm_js_glue = wasm_js_glue.replace('{{{ asmjsCodeFile }}}', '"' + os.path.basename(asm_target) + '"')
1692-
wasm_js_glue = wasm_js_glue.replace('{{{ wasmTextFile }}}', '"' + os.path.basename(wasm_text_target) + '"')
1693-
wasm_js_glue = wasm_js_glue.replace('{{{ wasmBinaryFile }}}', '"' + os.path.basename(wasm_binary_target) + '"')
1694-
if shared.Settings.BINARYEN_METHOD:
1695-
wasm_js_glue = wasm_js_glue.replace('{{{ wasmJSMethod }}}', '(Module[\'wasmJSMethod\'] || "' + shared.Settings.BINARYEN_METHOD + '")')
1696-
else:
1697-
wasm_js_glue = wasm_js_glue.replace('{{{ wasmJSMethod }}}', 'null')
1698-
wasm_js_glue = wasm_js_glue.replace('{{{ WASM_BACKEND }}}', str(shared.Settings.WASM_BACKEND)) # if wasm backend, wasm contains memory segments
1699-
wasm_js_glue += '\nintegrateWasmJS(Module);\n' # add a call
1700-
post_module += str(wasm_js_glue) # we can set up the glue once we have the module
1701-
17021693
# Apply pre and postjs files
17031694
if pre_js or post_module or post_js:
17041695
logging.debug('applying pre/postjses')

src/preamble.js

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2032,4 +2032,343 @@ Module['FS_createPreloadedFile'] = FS.createPreloadedFile;
20322032
var cyberDWARFFile = '{{{ BUNDLED_CD_DEBUG_FILE }}}';
20332033
#endif
20342034

2035+
#if BINARYEN
2036+
function integrateWasmJS(Module) {
2037+
// wasm.js has several methods for creating the compiled code module here:
2038+
// * 'native-wasm' : use native WebAssembly support in the browser
2039+
// * 'interpret-s-expr': load s-expression code from a .wast and interpret
2040+
// * 'interpret-binary': load binary wasm and interpret
2041+
// * 'interpret-asm2wasm': load asm.js code, translate to wasm, and interpret
2042+
// * 'asmjs': no wasm, just load the asm.js code and use that (good for testing)
2043+
// The method can be set at compile time (BINARYEN_METHOD), or runtime by setting Module['wasmJSMethod'].
2044+
// The method can be a comma-separated list, in which case, we will try the
2045+
// options one by one. Some of them can fail gracefully, and then we can try
2046+
// the next.
2047+
2048+
// inputs
2049+
2050+
var method = Module['wasmJSMethod'] || '{{{ BINARYEN_METHOD }}}';
2051+
Module['wasmJSMethod'] = method;
2052+
2053+
var wasmTextFile = Module['wasmTextFile'] || '{{{ WASM_TEXT_FILE }}}';
2054+
var wasmBinaryFile = Module['wasmBinaryFile'] || '{{{ WASM_BINARY_FILE }}}';
2055+
var asmjsCodeFile = Module['asmjsCodeFile'] || '{{{ ASMJS_CODE_FILE }}}';
2056+
2057+
// utilities
2058+
2059+
var wasmPageSize = 64*1024;
2060+
2061+
var asm2wasmImports = { // special asm2wasm imports
2062+
"f64-rem": function(x, y) {
2063+
return x % y;
2064+
},
2065+
"f64-to-int": function(x) {
2066+
return x | 0;
2067+
},
2068+
"i32s-div": function(x, y) {
2069+
return ((x | 0) / (y | 0)) | 0;
2070+
},
2071+
"i32u-div": function(x, y) {
2072+
return ((x >>> 0) / (y >>> 0)) >>> 0;
2073+
},
2074+
"i32s-rem": function(x, y) {
2075+
return ((x | 0) % (y | 0)) | 0;
2076+
},
2077+
"i32u-rem": function(x, y) {
2078+
return ((x >>> 0) % (y >>> 0)) >>> 0;
2079+
},
2080+
"debugger": function() {
2081+
debugger;
2082+
},
2083+
};
2084+
2085+
var info = {
2086+
'global': null,
2087+
'env': null,
2088+
'asm2wasm': asm2wasmImports,
2089+
'parent': Module // Module inside wasm-js.cpp refers to wasm-js.cpp; this allows access to the outside program.
2090+
};
2091+
2092+
var exports = null;
2093+
2094+
function lookupImport(mod, base) {
2095+
var lookup = info;
2096+
if (mod.indexOf('.') < 0) {
2097+
lookup = (lookup || {})[mod];
2098+
} else {
2099+
var parts = mod.split('.');
2100+
lookup = (lookup || {})[parts[0]];
2101+
lookup = (lookup || {})[parts[1]];
2102+
}
2103+
if (base) {
2104+
lookup = (lookup || {})[base];
2105+
}
2106+
if (lookup === undefined) {
2107+
abort('bad lookupImport to (' + mod + ').' + base);
2108+
}
2109+
return lookup;
2110+
}
2111+
2112+
function mergeMemory(newBuffer) {
2113+
// The wasm instance creates its memory. But static init code might have written to
2114+
// buffer already, including the mem init file, and we must copy it over in a proper merge.
2115+
// TODO: avoid this copy, by avoiding such static init writes
2116+
// TODO: in shorter term, just copy up to the last static init write
2117+
var oldBuffer = Module['buffer'];
2118+
if (newBuffer.byteLength < oldBuffer.byteLength) {
2119+
Module['printErr']('the new buffer in mergeMemory is smaller than the previous one. in native wasm, we should grow memory here');
2120+
}
2121+
var oldView = new Int8Array(oldBuffer);
2122+
var newView = new Int8Array(newBuffer);
2123+
2124+
// If we have a mem init file, do not trample it
2125+
if (!memoryInitializer) {
2126+
oldView.set(newView.subarray(Module['STATIC_BASE'], Module['STATIC_BASE'] + Module['STATIC_BUMP']), Module['STATIC_BASE']);
2127+
}
2128+
2129+
newView.set(oldView);
2130+
updateGlobalBuffer(newBuffer);
2131+
updateGlobalBufferViews();
2132+
}
2133+
2134+
var WasmTypes = {
2135+
none: 0,
2136+
i32: 1,
2137+
i64: 2,
2138+
f32: 3,
2139+
f64: 4
2140+
};
2141+
2142+
function fixImports(imports) {
2143+
if (!{{{ WASM_BACKEND }}}) return imports;
2144+
var ret = {};
2145+
for (var i in imports) {
2146+
var fixed = i;
2147+
if (fixed[0] == '_') fixed = fixed.substr(1);
2148+
ret[fixed] = imports[i];
2149+
}
2150+
return ret;
2151+
}
2152+
2153+
function getBinary() {
2154+
var binary;
2155+
if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
2156+
binary = Module['wasmBinary'];
2157+
assert(binary, "on the web, we need the wasm binary to be preloaded and set on Module['wasmBinary']. emcc.py will do that for you when generating HTML (but not JS)");
2158+
binary = new Uint8Array(binary);
2159+
} else {
2160+
binary = Module['readBinary'](wasmBinaryFile);
2161+
}
2162+
return binary;
2163+
}
2164+
2165+
// do-method functions
2166+
2167+
function doJustAsm(global, env, providedBuffer) {
2168+
// if no Module.asm, or it's the method handler helper (see below), then apply
2169+
// the asmjs
2170+
if (typeof Module['asm'] !== 'function' || Module['asm'] === methodHandler) {
2171+
if (!Module['asmPreload']) {
2172+
// you can load the .asm.js file before this, to avoid this sync xhr and eval
2173+
eval(Module['read'](asmjsCodeFile)); // set Module.asm
2174+
} else {
2175+
Module['asm'] = Module['asmPreload'];
2176+
}
2177+
}
2178+
if (typeof Module['asm'] !== 'function') {
2179+
Module['printErr']('asm evalling did not set the module properly');
2180+
return false;
2181+
}
2182+
return Module['asm'](global, env, providedBuffer);
2183+
}
2184+
2185+
function doNativeWasm(global, env, providedBuffer) {
2186+
if (typeof WebAssembly !== 'object') {
2187+
Module['printErr']('no native wasm support detected');
2188+
return false;
2189+
}
2190+
// prepare memory import
2191+
if (!(Module['wasmMemory'] instanceof WebAssembly.Memory)) {
2192+
Module['printErr']('no native wasm Memory in use');
2193+
return false;
2194+
}
2195+
env['memory'] = Module['wasmMemory'];
2196+
// Load the wasm module and create an instance of using native support in the JS engine.
2197+
info['global'] = {
2198+
'NaN': NaN,
2199+
'Infinity': Infinity
2200+
};
2201+
info['global.Math'] = global.Math;
2202+
info['env'] = env;
2203+
var instance;
2204+
try {
2205+
instance = new WebAssembly.Instance(new WebAssembly.Module(getBinary()), info)
2206+
} catch (e) {
2207+
Module['printErr']('failed to compile wasm module: ' + e);
2208+
if (e.toString().indexOf('imported Memory with incompatible size') >= 0) {
2209+
Module['printErr']('Memory size incompatibility issues may be due to changing TOTAL_MEMORY at runtime to something too large. Use ALLOW_MEMORY_GROWTH to allow any size memory (and also make sure not to set TOTAL_MEMORY at runtime to something smaller than it was at compile time).');
2210+
}
2211+
return false;
2212+
}
2213+
exports = instance.exports;
2214+
if (exports.memory) mergeMemory(exports.memory);
2215+
2216+
Module["usingWasm"] = true;
2217+
2218+
return exports;
2219+
}
2220+
2221+
function doWasmPolyfill(global, env, providedBuffer, method) {
2222+
if (typeof WasmJS !== 'function') {
2223+
Module['printErr']('WasmJS not detected - polyfill not bundled?');
2224+
return false;
2225+
}
2226+
2227+
// Use wasm.js to polyfill and execute code in a wasm interpreter.
2228+
var wasmJS = WasmJS({});
2229+
2230+
// XXX don't be confused. Module here is in the outside program. wasmJS is the inner wasm-js.cpp.
2231+
wasmJS['outside'] = Module; // Inside wasm-js.cpp, Module['outside'] reaches the outside module.
2232+
2233+
// Information for the instance of the module.
2234+
wasmJS['info'] = info;
2235+
2236+
wasmJS['lookupImport'] = lookupImport;
2237+
2238+
assert(providedBuffer === Module['buffer']); // we should not even need to pass it as a 3rd arg for wasm, but that's the asm.js way.
2239+
2240+
info.global = global;
2241+
info.env = env;
2242+
2243+
// polyfill interpreter expects an ArrayBuffer
2244+
assert(providedBuffer === Module['buffer']);
2245+
env['memory'] = providedBuffer;
2246+
assert(env['memory'] instanceof ArrayBuffer);
2247+
2248+
wasmJS['providedTotalMemory'] = Module['buffer'].byteLength;
2249+
2250+
// Prepare to generate wasm, using either asm2wasm or s-exprs
2251+
var code;
2252+
if (method === 'interpret-binary') {
2253+
code = getBinary();
2254+
} else {
2255+
code = Module['read'](method == 'interpret-asm2wasm' ? asmjsCodeFile : wasmTextFile);
2256+
}
2257+
var temp;
2258+
if (method == 'interpret-asm2wasm') {
2259+
temp = wasmJS['_malloc'](code.length + 1);
2260+
wasmJS['writeAsciiToMemory'](code, temp);
2261+
wasmJS['_load_asm2wasm'](temp);
2262+
} else if (method === 'interpret-s-expr') {
2263+
temp = wasmJS['_malloc'](code.length + 1);
2264+
wasmJS['writeAsciiToMemory'](code, temp);
2265+
wasmJS['_load_s_expr2wasm'](temp);
2266+
} else if (method === 'interpret-binary') {
2267+
temp = wasmJS['_malloc'](code.length);
2268+
wasmJS['HEAPU8'].set(code, temp);
2269+
wasmJS['_load_binary2wasm'](temp, code.length);
2270+
} else {
2271+
throw 'what? ' + method;
2272+
}
2273+
wasmJS['_free'](temp);
2274+
2275+
wasmJS['_instantiate'](temp);
2276+
2277+
if (Module['newBuffer']) {
2278+
mergeMemory(Module['newBuffer']);
2279+
Module['newBuffer'] = null;
2280+
}
2281+
2282+
exports = wasmJS['asmExports'];
2283+
2284+
return exports;
2285+
}
2286+
2287+
// We may have a preloaded value in Module.asm, save it
2288+
Module['asmPreload'] = Module['asm'];
2289+
2290+
// Memory growth integration code
2291+
Module['reallocBuffer'] = function(size) {
2292+
size = Math.ceil(size / wasmPageSize) * wasmPageSize; // round up to wasm page size
2293+
var old = Module['buffer'];
2294+
var result = exports['__growWasmMemory'](size / wasmPageSize); // tiny wasm method that just does grow_memory
2295+
if (Module["usingWasm"]) {
2296+
if (result !== (-1 | 0)) {
2297+
// success in native wasm memory growth, get the buffer from the memory
2298+
return Module['buffer'] = Module['wasmMemory'].buffer;
2299+
} else {
2300+
return null;
2301+
}
2302+
} else {
2303+
// in interpreter, we replace Module.buffer if we allocate
2304+
return Module['buffer'] !== old ? Module['buffer'] : null; // if it was reallocated, it changed
2305+
}
2306+
};
2307+
2308+
// Provide an "asm.js function" for the application, called to "link" the asm.js module. We instantiate
2309+
// the wasm module at that time, and it receives imports and provides exports and so forth, the app
2310+
// doesn't need to care that it is wasm or olyfilled wasm or asm.js.
2311+
2312+
Module['asm'] = function(global, env, providedBuffer) {
2313+
global = fixImports(global);
2314+
env = fixImports(env);
2315+
2316+
// import table
2317+
if (!env['table']) {
2318+
var TABLE_SIZE = Module['wasmTableSize'];
2319+
if (TABLE_SIZE === undefined) TABLE_SIZE = 1024; // works in binaryen interpreter at least
2320+
var MAX_TABLE_SIZE = Module['wasmMaxTableSize'];
2321+
if (typeof WebAssembly === 'object' && typeof WebAssembly.Table === 'function') {
2322+
if (MAX_TABLE_SIZE !== undefined) {
2323+
env['table'] = new WebAssembly.Table({ initial: TABLE_SIZE, maximum: MAX_TABLE_SIZE, element: 'anyfunc' });
2324+
} else {
2325+
env['table'] = new WebAssembly.Table({ initial: TABLE_SIZE, element: 'anyfunc' });
2326+
}
2327+
} else {
2328+
env['table'] = new Array(TABLE_SIZE); // works in binaryen interpreter at least
2329+
}
2330+
Module['wasmTable'] = env['table'];
2331+
}
2332+
2333+
if (!env['memoryBase']) {
2334+
env['memoryBase'] = Module['STATIC_BASE']; // tell the memory segments where to place themselves
2335+
}
2336+
if (!env['tableBase']) {
2337+
env['tableBase'] = 0; // table starts at 0 by default, in dynamic linking this will change
2338+
}
2339+
2340+
// try the methods. each should return the exports if it succeeded
2341+
2342+
var exports;
2343+
var methods = method.split(',');
2344+
2345+
for (var i = 0; i < methods.length; i++) {
2346+
var curr = methods[i];
2347+
2348+
Module['printErr']('trying binaryen method: ' + curr);
2349+
2350+
if (curr === 'native-wasm') {
2351+
if (exports = doNativeWasm(global, env, providedBuffer)) break;
2352+
} else if (curr === 'asmjs') {
2353+
if (exports = doJustAsm(global, env, providedBuffer)) break;
2354+
} else if (curr === 'interpret-asm2wasm' || curr === 'interpret-s-expr' || curr === 'interpret-binary') {
2355+
if (exports = doWasmPolyfill(global, env, providedBuffer, curr)) break;
2356+
} else {
2357+
throw 'bad method: ' + curr;
2358+
}
2359+
}
2360+
2361+
if (!exports) throw 'no binaryen method succeeded. consider enabling more options, like interpreting, if you want that: https://github.com/kripken/emscripten/wiki/WebAssembly#binaryen-methods';
2362+
2363+
Module['printErr']('binaryen method succeeded.');
2364+
2365+
return exports;
2366+
};
2367+
2368+
var methodHandler = Module['asm']; // note our method handler, as we may modify Module['asm'] later
2369+
}
2370+
2371+
integrateWasmJS(Module);
2372+
#endif
2373+
20352374
// === Body ===

src/settings.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -817,4 +817,7 @@ var FETCH = 0; // If nonzero, enables emscripten_fetch API.
817817

818818
var ASMFS = 0; // If set to 1, uses the multithreaded filesystem that is implemented within the asm.js module, using emscripten_fetch. Implies -s FETCH=1.
819819

820-
// Reserved: variables containing POINTER_MASKING.
820+
var WASM_TEXT_FILE = ''; // name of the file containing wasm text, if relevant
821+
var WASM_BINARY_FILE = ''; // name of the file containing wasm binary, if relevant
822+
var ASMJS_CODE_FILE = ''; // name of the file containing asm.js, if relevant
823+

0 commit comments

Comments
 (0)