Merge pull request #70 from iodide-project/chromedriver

Add testing with Chrome
This commit is contained in:
Michael Droettboom 2018-07-11 10:50:00 -04:00 committed by GitHub
commit b5e9630f0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 9495 additions and 293 deletions

View File

@ -17,7 +17,7 @@ jobs:
# Set up the Debian testing repo, and then install g++ from there...
sudo bash -c "echo \"deb http://ftp.us.debian.org/debian testing main contrib non-free\" >> /etc/apt/sources.list"
sudo apt-get update
sudo apt-get install node-less cmake build-essential clang-format-6.0 flake8 uglifyjs python3-yaml
sudo apt-get install node-less cmake build-essential clang-format-6.0 flake8 uglifyjs python3-yaml chromium
sudo apt-get install -t testing g++-8
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 60 --slave /usr/bin/g++ g++ /usr/bin/g++-6
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 80 --slave /usr/bin/g++ g++ /usr/bin/g++-8
@ -28,10 +28,14 @@ jobs:
# Get recent version of Firefox and geckodriver
wget -O firefox.tar.bz2 https://download.mozilla.org/\?product\=firefox-nightly-latest-ssl\&os\=linux64\&lang\=en-US
tar jxf firefox.tar.bz2
wget https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-linux64.tar.gz
tar zxf geckodriver-v0.20.1-linux64.tar.gz -C firefox
# Get recent version of chromedriver
wget https://chromedriver.storage.googleapis.com/2.40/chromedriver_linux64.zip
unzip chromedriver_linux64.zip
mv chromedriver firefox
# This Debian is so old, it doesn't know about wasm as a mime type, which then
# causes Firefox to complain when loading it. Let's just add the new mime type.
sudo bash -c "echo 'application/wasm wasm' >> /etc/mime.types"
@ -43,9 +47,7 @@ jobs:
- restore_cache:
keys:
- v1-emsdk-{{ checksum "emsdk/Makefile" }}-v2
# fallback to using the latest cache if no exact match is found
- v1-emsdk-
- v1-emsdk-{{ checksum "emsdk/Makefile" }}-v4
- run:
name: build
@ -56,7 +58,7 @@ jobs:
- save_cache:
paths:
- ./emsdk/emsdk
key: v1-emsdk-{{ checksum "emsdk/Makefile" }}-v2
key: v1-emsdk-{{ checksum "emsdk/Makefile" }}-v4
- run:
name: test

View File

@ -96,10 +96,14 @@ build/renderedhtml.css: src/renderedhtml.less
lessc $< $@
test: all build/test.html
test: all build/test.html build/test_data.txt
py.test test -v
build/test_data.txt: test/data.txt
cp test/data.txt build/test_data.txt
lint:
flake8 src
flake8 test

View File

@ -23,8 +23,8 @@ def run_native(hostpython, code):
return float(output.strip().split()[-1])
def run_wasm(code):
s = conftest.SeleniumWrapper()
def run_wasm(code, cls):
s = cls()
try:
s.load_package('numpy')
s.run(code)
@ -38,12 +38,18 @@ def run_wasm(code):
return runtime
def run_both(hostpython, code):
def run_all(hostpython, code):
a = run_native(hostpython, code)
print(a)
b = run_wasm(code)
print(b)
result = (a, b)
print("native:", a)
b = run_wasm(code, conftest.FirefoxWrapper)
print("firefox:", b)
c = run_wasm(code, conftest.ChromeWrapper)
print("chrome:", c)
result = {
'native': a,
'firefox': b,
'chrome': c
}
return result
@ -92,7 +98,7 @@ def main(hostpython):
results = {}
for k, v in get_benchmarks():
print(k)
results[k] = run_both(hostpython, v)
results[k] = run_all(hostpython, v)
return results

View File

@ -11,20 +11,24 @@ with open(sys.argv[-2]) as fp:
results = []
for k, v in content.items():
results.append((k, v[1] / v[0]))
results.append((k, v['firefox'] / v['native'], v['chrome'] / v['native']))
results.sort(key=lambda x: x[1], reverse=True)
names = [x[0] for x in results]
values = [x[1] for x in results]
firefox = [x[1] for x in results]
chrome = [x[2] for x in results]
width = 0.35
y_pos = np.arange(len(results))
ax.barh(y_pos, values, align='center')
ax.set_yticks(y_pos)
ax.barh(y_pos, firefox, width, color='#ff9400', label='firefox')
ax.barh(y_pos + width, chrome, width, color='#45a1ff', label='chrome')
ax.set_yticks(y_pos + width / 2)
ax.set_yticklabels(names)
ax.invert_yaxis()
ax.set_xlabel('Slowdown factor (WebAssembly:Native)')
ax.set_title('Python benchmarks')
ax.axvline(1.0, color='red')
ax.grid()
ax.legend(loc='lower right')
plt.savefig(sys.argv[-1])

View File

@ -11,9 +11,11 @@ emsdk/emsdk:
./emsdk install --build=Release sdk-tag-1.38.4-64bit binaryen-tag-1.38.4-64bit ; \
cd .. ; \
(cat patches/*.patch | patch -p1) ; \
cp files/* emsdk/emscripten/tag-1.38.4/src/ ; \
cd emsdk/binaryen/tag-1.38.4_64bit_binaryen/ ; \
make ; \
cd ../.. ; \
cp binaryen/tag-1.38.4/bin/binaryen.js binaryen/tag-1.38.4_64bit_binaryen/bin ; \
./emsdk activate --embedded --build=Release sdk-tag-1.38.4-64bit binaryen-tag-1.38.4-64bit \
)

4800
emsdk/files/library.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2482
emsdk/files/preamble.js Normal file

File diff suppressed because it is too large Load Diff

546
emsdk/files/support.js Normal file
View File

@ -0,0 +1,546 @@
// {{PREAMBLE_ADDITIONS}}
var STACK_ALIGN = {{{ STACK_ALIGN }}};
#if ASSERTIONS
// stack management, and other functionality that is provided by the compiled code,
// should not be used before it is ready
stackSave = stackRestore = stackAlloc = setTempRet0 = getTempRet0 = function() {
abort('cannot use the stack before compiled code is ready to run, and has provided stack access');
};
#endif
function staticAlloc(size) {
assert(!staticSealed);
var ret = STATICTOP;
STATICTOP = (STATICTOP + size + 15) & -16;
return ret;
}
function dynamicAlloc(size) {
assert(DYNAMICTOP_PTR);
var ret = HEAP32[DYNAMICTOP_PTR>>2];
var end = (ret + size + 15) & -16;
HEAP32[DYNAMICTOP_PTR>>2] = end;
if (end >= TOTAL_MEMORY) {
var success = enlargeMemory();
if (!success) {
HEAP32[DYNAMICTOP_PTR>>2] = ret;
return 0;
}
}
return ret;
}
{{{ alignMemory }}}
{{{ getNativeTypeSize }}}
function warnOnce(text) {
if (!warnOnce.shown) warnOnce.shown = {};
if (!warnOnce.shown[text]) {
warnOnce.shown[text] = 1;
Module.printErr(text);
}
}
var asm2wasmImports = { // special asm2wasm imports
"f64-rem": function(x, y) {
return x % y;
},
"debugger": function() {
debugger;
}
#if NEED_ALL_ASM2WASM_IMPORTS
,
"f64-to-int": function(x) {
return x | 0;
},
"i32s-div": function(x, y) {
return ((x | 0) / (y | 0)) | 0;
},
"i32u-div": function(x, y) {
return ((x >>> 0) / (y >>> 0)) >>> 0;
},
"i32s-rem": function(x, y) {
return ((x | 0) % (y | 0)) | 0;
},
"i32u-rem": function(x, y) {
return ((x >>> 0) % (y >>> 0)) >>> 0;
}
#endif // NEED_ALL_ASM2WASM_IMPORTS
};
#if RELOCATABLE
var loadedDynamicLibraries = [];
function loadDynamicLibrary(lib) {
var libModule;
#if WASM
var bin;
if (lib.buffer) {
// we were provided the binary, in a typed array
bin = lib;
} else {
// load the binary synchronously
bin = Module['readBinary'](lib);
}
libModule = loadWebAssemblyModule(bin);
#else
var src = Module['read'](lib);
libModule = eval(src)(
alignFunctionTables(),
Module
);
#endif
// add symbols into global namespace TODO: weak linking etc.
for (var sym in libModule) {
if (!Module.hasOwnProperty(sym)) {
Module[sym] = libModule[sym];
}
#if ASSERTIONS == 2
else if (sym[0] === '_') {
var curr = Module[sym], next = libModule[sym];
// don't warn on functions - might be odr, linkonce_odr, etc.
if (!(typeof curr === 'function' && typeof next === 'function')) {
Module.printErr("warning: trying to dynamically load symbol '" + sym + "' (from '" + lib + "') that already exists (duplicate symbol? or weak linking, which isn't supported yet?)"); // + [curr, ' vs ', next]);
}
}
#endif
}
loadedDynamicLibraries.push(libModule);
}
#if WASM
// Loads a side module from binary data
function loadWebAssemblyModule(binary, loadAsync) {
var int32View = new Uint32Array(new Uint8Array(binary.subarray(0, 24)).buffer);
assert(int32View[0] == 0x6d736100, 'need to see wasm magic number'); // \0wasm
// we should see the dylink section right after the magic number and wasm version
assert(binary[8] === 0, 'need the dylink section to be first')
var next = 9;
function getLEB() {
var ret = 0;
var mul = 1;
while (1) {
var byte = binary[next++];
ret += ((byte & 0x7f) * mul);
mul *= 0x80;
if (!(byte & 0x80)) break;
}
return ret;
}
var sectionSize = getLEB();
assert(binary[next] === 6); next++; // size of "dylink" string
assert(binary[next] === 'd'.charCodeAt(0)); next++;
assert(binary[next] === 'y'.charCodeAt(0)); next++;
assert(binary[next] === 'l'.charCodeAt(0)); next++;
assert(binary[next] === 'i'.charCodeAt(0)); next++;
assert(binary[next] === 'n'.charCodeAt(0)); next++;
assert(binary[next] === 'k'.charCodeAt(0)); next++;
var memorySize = getLEB();
var memoryAlign = getLEB();
var tableSize = getLEB();
var tableAlign = getLEB();
// alignments are powers of 2
memoryAlign = Math.pow(2, memoryAlign);
tableAlign = Math.pow(2, tableAlign);
// finalize alignments and verify them
memoryAlign = Math.max(memoryAlign, STACK_ALIGN); // we at least need stack alignment
assert(tableAlign === 1);
// prepare memory
var memoryStart = alignMemory(getMemory(memorySize + memoryAlign), memoryAlign); // TODO: add to cleanups
// The static area consists of explicitly initialized data, followed by zero-initialized data.
// The latter may need zeroing out if the MAIN_MODULE has already used this memory area before
// dlopen'ing the SIDE_MODULE. Since we don't know the size of the explicitly initialized data
// here, we just zero the whole thing, which is suboptimal, but should at least resolve bugs
// from uninitialized memory.
for (var i = memoryStart; i < memoryStart + memorySize; ++i) HEAP8[i] = 0;
// prepare env imports
var env = Module['asmLibraryArg'];
// TODO: use only memoryBase and tableBase, need to update asm.js backend
var table = Module['wasmTable'];
var oldTableSize = table.length;
env['memoryBase'] = env['gb'] = memoryStart;
env['tableBase'] = env['fb'] = oldTableSize;
var originalTable = table;
table.grow(tableSize);
assert(table === originalTable);
// zero-initialize memory and table TODO: in some cases we can tell it is already zero initialized
for (var i = env['memoryBase']; i < env['memoryBase'] + memorySize; i++) {
HEAP8[i] = 0;
}
for (var i = env['tableBase']; i < env['tableBase'] + tableSize; i++) {
table.set(i, null);
}
// copy currently exported symbols so the new module can import them
for (var x in Module) {
if (!(x in env)) {
env[x] = Module[x];
}
}
var info = {
global: {
'NaN': NaN,
'Infinity': Infinity,
},
'global.Math': Math,
env: env,
'asm2wasm': asm2wasmImports
};
#if ASSERTIONS
var oldTable = [];
for (var i = 0; i < oldTableSize; i++) {
oldTable.push(table.get(i));
}
#endif
function postInstantiation(instance) {
var exports = {};
#if ASSERTIONS
// the table should be unchanged
assert(table === originalTable);
assert(table === Module['wasmTable']);
if (instance.exports['table']) {
assert(table === instance.exports['table']);
}
// the old part of the table should be unchanged
for (var i = 0; i < oldTableSize; i++) {
assert(table.get(i) === oldTable[i], 'old table entries must remain the same');
}
// verify that the new table region was filled in
for (var i = 0; i < tableSize; i++) {
assert(table.get(oldTableSize + i) !== undefined, 'table entry was not filled in');
}
#endif
for (var e in instance.exports) {
var value = instance.exports[e];
if (typeof value === 'object') {
// a breaking change in the wasm spec, globals are now objects
// https://github.com/WebAssembly/mutable-global/issues/1
value = value.value;
}
if (typeof value === 'number') {
// relocate it - modules export the absolute value, they can't relocate before they export
#if EMULATED_FUNCTION_POINTERS
// it may be a function pointer
if (e.substr(0, 3) == 'fp$' && typeof instance.exports[e.substr(3)] === 'function') {
value = value + env['tableBase'];
} else {
#endif
value = value + env['memoryBase'];
#if EMULATED_FUNCTION_POINTERS
}
#endif
}
exports[e] = value;
}
// initialize the module
var init = exports['__post_instantiate'];
if (init) {
if (runtimeInitialized) {
init();
} else {
// we aren't ready to run compiled code yet
__ATINIT__.push(init);
}
}
return exports;
}
if (loadAsync) {
return WebAssembly.instantiate(binary, info).then(function(result) {
return postInstantiation(result.instance);
});
} else {
var instance = new WebAssembly.Instance(new WebAssembly.Module(binary), info);
return postInstantiation(instance);
}
}
Module['loadWebAssemblyModule'] = loadWebAssemblyModule;
#endif // WASM
#endif // RELOCATABLE
#if EMULATED_FUNCTION_POINTERS
function getFunctionTables(module) {
if (!module) module = Module;
var tables = {};
for (var t in module) {
if (/^FUNCTION_TABLE_.*/.test(t)) {
var table = module[t];
if (typeof table === 'object') tables[t.substr('FUNCTION_TABLE_'.length)] = table;
}
}
return tables;
}
function alignFunctionTables(module) {
var tables = getFunctionTables(module);
var maxx = 0;
for (var sig in tables) {
maxx = Math.max(maxx, tables[sig].length);
}
assert(maxx >= 0);
for (var sig in tables) {
var table = tables[sig];
while (table.length < maxx) table.push(0);
}
return maxx;
}
#if RELOCATABLE
// register functions from a new module being loaded
function registerFunctions(sigs, newModule) {
sigs.forEach(function(sig) {
if (!Module['FUNCTION_TABLE_' + sig]) {
Module['FUNCTION_TABLE_' + sig] = [];
}
});
var oldMaxx = alignFunctionTables(); // align the new tables we may have just added
var newMaxx = alignFunctionTables(newModule);
var maxx = oldMaxx + newMaxx;
sigs.forEach(function(sig) {
var newTable = newModule['FUNCTION_TABLE_' + sig];
var oldTable = Module['FUNCTION_TABLE_' + sig];
assert(newTable !== oldTable);
assert(oldTable.length === oldMaxx);
for (var i = 0; i < newTable.length; i++) {
oldTable.push(newTable[i]);
}
assert(oldTable.length === maxx);
});
assert(maxx === alignFunctionTables()); // align the ones we didn't touch
}
// export this so side modules can use it
Module['registerFunctions'] = registerFunctions;
#endif // RELOCATABLE
#endif // EMULATED_FUNCTION_POINTERS
#if WASM_BACKEND_WITH_RESERVED_FUNCTION_POINTERS
var jsCallStartIndex = {{{ JSCALL_START_INDEX }}};
var jsCallSigOrder = {{{ JSON.stringify(JSCALL_SIG_ORDER) }}};
var jsCallNumSigs = Object.keys(jsCallSigOrder).length;
var functionPointers = new Array(jsCallNumSigs * {{{ RESERVED_FUNCTION_POINTERS }}});
#else // WASM_BACKEND_WITH_RESERVED_FUNCTION_POINTERS == 0
var jsCallStartIndex = 1;
var functionPointers = new Array({{{ RESERVED_FUNCTION_POINTERS }}});
#endif // WASM_BACKEND_WITH_RESERVED_FUNCTION_POINTERS
// 'sig' parameter is only used on LLVM wasm backend
function addFunction(func, sig) {
#if WASM_BACKEND
assert(typeof sig !== 'undefined',
'Second argument of addFunction should be a wasm function signature ' +
'string');
#endif // WASM_BACKEND
#if ASSERTIONS
if (typeof sig === 'undefined') {
Module.printErr('warning: addFunction(): You should provide a wasm function signature string as a second argument. This is not necessary for asm.js and asm2wasm, but is required for the LLVM wasm backend, so it is recommended for full portability.');
}
#endif // ASSERTIONS
#if EMULATED_FUNCTION_POINTERS == 0
#if WASM_BACKEND_WITH_RESERVED_FUNCTION_POINTERS
var base = jsCallSigOrder[sig] * {{{ RESERVED_FUNCTION_POINTERS }}};
#else // WASM_BACKEND_WITH_RESERVED_FUNCTION_POINTERS == 0
var base = 0;
#endif // WASM_BACKEND_WITH_RESERVED_FUNCTION_POINTERS
for (var i = base; i < base + {{{ RESERVED_FUNCTION_POINTERS }}}; i++) {
if (!functionPointers[i]) {
functionPointers[i] = func;
return jsCallStartIndex + i;
}
}
throw 'Finished up all reserved function pointers. Use a higher value for RESERVED_FUNCTION_POINTERS.';
#else
#if WASM
// we can simply append to the wasm table
var table = Module['wasmTable'];
var ret = table.length;
table.grow(1);
table.set(ret, func);
return ret;
#else
alignFunctionTables(); // XXX we should rely on this being an invariant
var tables = getFunctionTables();
var ret = -1;
for (var sig in tables) {
var table = tables[sig];
if (ret < 0) ret = table.length;
else assert(ret === table.length);
table.push(func);
}
return ret;
#endif
#endif
}
function removeFunction(index) {
#if EMULATED_FUNCTION_POINTERS == 0
functionPointers[index-jsCallStartIndex] = null;
#else
alignFunctionTables(); // XXX we should rely on this being an invariant
var tables = getFunctionTables();
for (var sig in tables) {
tables[sig][index] = null;
}
#endif
}
var funcWrappers = {};
function getFuncWrapper(func, sig) {
if (!func) return; // on null pointer, return undefined
assert(sig);
if (!funcWrappers[sig]) {
funcWrappers[sig] = {};
}
var sigCache = funcWrappers[sig];
if (!sigCache[func]) {
// optimize away arguments usage in common cases
if (sig.length === 1) {
sigCache[func] = function dynCall_wrapper() {
return dynCall(sig, func);
};
} else if (sig.length === 2) {
sigCache[func] = function dynCall_wrapper(arg) {
return dynCall(sig, func, [arg]);
};
} else {
// general case
sigCache[func] = function dynCall_wrapper() {
return dynCall(sig, func, Array.prototype.slice.call(arguments));
};
}
}
return sigCache[func];
}
#if RUNTIME_DEBUG
var runtimeDebug = true; // Switch to false at runtime to disable logging at the right times
var printObjectList = [];
function prettyPrint(arg) {
if (typeof arg == 'undefined') return '!UNDEFINED!';
if (typeof arg == 'boolean') arg = arg + 0;
if (!arg) return arg;
var index = printObjectList.indexOf(arg);
if (index >= 0) return '<' + arg + '|' + index + '>';
if (arg.toString() == '[object HTMLImageElement]') {
return arg + '\n\n';
}
if (arg.byteLength) {
return '{' + Array.prototype.slice.call(arg, 0, Math.min(arg.length, 400)) + '}'; // Useful for correct arrays, less so for compiled arrays, see the code below for that
var buf = new ArrayBuffer(32);
var i8buf = new Int8Array(buf);
var i16buf = new Int16Array(buf);
var f32buf = new Float32Array(buf);
switch(arg.toString()) {
case '[object Uint8Array]':
i8buf.set(arg.subarray(0, 32));
break;
case '[object Float32Array]':
f32buf.set(arg.subarray(0, 5));
break;
case '[object Uint16Array]':
i16buf.set(arg.subarray(0, 16));
break;
default:
alert('unknown array for debugging: ' + arg);
throw 'see alert';
}
var ret = '{' + arg.byteLength + ':\n';
var arr = Array.prototype.slice.call(i8buf);
ret += 'i8:' + arr.toString().replace(/,/g, ',') + '\n';
arr = Array.prototype.slice.call(f32buf, 0, 8);
ret += 'f32:' + arr.toString().replace(/,/g, ',') + '}';
return ret;
}
if (typeof arg == 'object') {
printObjectList.push(arg);
return '<' + arg + '|' + (printObjectList.length-1) + '>';
}
if (typeof arg == 'number') {
if (arg > 0) return '0x' + arg.toString(16) + ' (' + arg + ')';
}
return arg;
}
#endif
function makeBigInt(low, high, unsigned) {
return unsigned ? ((+((low>>>0)))+((+((high>>>0)))*4294967296.0)) : ((+((low>>>0)))+((+((high|0)))*4294967296.0));
}
function dynCall(sig, ptr, args) {
if (args && args.length) {
#if ASSERTIONS
assert(args.length == sig.length-1);
#endif
#if ASSERTIONS
assert(('dynCall_' + sig) in Module, 'bad function pointer type - no table for sig \'' + sig + '\'');
#endif
return Module['dynCall_' + sig].apply(null, [ptr].concat(args));
} else {
#if ASSERTIONS
assert(sig.length == 1);
#endif
#if ASSERTIONS
assert(('dynCall_' + sig) in Module, 'bad function pointer type - no table for sig \'' + sig + '\'');
#endif
return Module['dynCall_' + sig].call(null, ptr);
}
}
#if RELOCATABLE
// tempRet0 is normally handled in the module. but in relocatable code,
// we need to share a single one among all the modules, so they all call
// out.
var tempRet0 = 0;
var setTempRet0 = function(value) {
tempRet0 = value;
}
var getTempRet0 = function() {
return tempRet0;
}
#endif // RELOCATABLE
#if RETAIN_COMPILER_SETTINGS
var compilerSettings = {{{ JSON.stringify(makeRetainedCompilerSettings()) }}} ;
function getCompilerSetting(name) {
if (!(name in compilerSettings)) return 'invalid compiler setting: ' + name;
return compilerSettings[name];
}
#else // RETAIN_COMPILER_SETTINGS
#if ASSERTIONS
function getCompilerSetting(name) {
throw 'You must build with -s RETAIN_COMPILER_SETTINGS=1 for getCompilerSetting or emscripten_get_compiler_setting to work';
}
#endif // ASSERTIONS
#endif // RETAIN_COMPILER_SETTINGS
var Runtime = {
// FIXME backwards compatibility layer for ports. Support some Runtime.*
// for now, fix it there, then remove it from here. That way we
// can minimize any period of breakage.
dynCall: dynCall, // for SDL2 port
#if ASSERTIONS
// helpful errors
getTempRet0: function() { abort('getTempRet0() is now a top-level function, after removing the Runtime object. Remove "Runtime."') },
staticAlloc: function() { abort('staticAlloc() is now a top-level function, after removing the Runtime object. Remove "Runtime."') },
stackAlloc: function() { abort('stackAlloc() is now a top-level function, after removing the Runtime object. Remove "Runtime."') },
#endif
};
// The address globals begin at. Very low in memory, for code size and optimization opportunities.
// Above 0 is static memory, starting with globals.
// Then the stack.
// Then 'dynamic' memory for sbrk.
var GLOBAL_BASE = {{{ GLOBAL_BASE }}};
#if RELOCATABLE
GLOBAL_BASE = alignMemory(GLOBAL_BASE, {{{ MAX_GLOBAL_ALIGN || 1 }}});
#endif

View File

@ -1,253 +0,0 @@
diff --git a/src/library.js b/src/library.js
index 5fc87ab16..b8ead8fc0 100644
--- a/src/library.js
+++ b/src/library.js
@@ -1755,39 +1755,44 @@ LibraryManager.library = {
return handle;
}
+ var lib_module;
if (filename === '__self__') {
var handle = -1;
- var lib_module = Module;
+ lib_module = Module;
} else {
- var target = FS.findObject(filename);
- if (!target || target.isFolder || target.isDevice) {
- DLFCN.errorMsg = 'Could not find dynamic lib: ' + filename;
- return 0;
- }
- FS.forceLoadFile(target);
+ if (Module['preloadedWasm'] !== undefined &&
+ Module['preloadedWasm'][filename] !== undefined) {
+ lib_module = Module['preloadedWasm'][filename];
+ } else {
+ var target = FS.findObject(filename);
+ if (!target || target.isFolder || target.isDevice) {
+ DLFCN.errorMsg = 'Could not find dynamic lib: ' + filename;
+ return 0;
+ }
+ FS.forceLoadFile(target);
- var lib_module;
- try {
+ try {
#if WASM
- // the shared library is a shared wasm library (see tools/shared.py WebAssembly.make_shared_library)
- var lib_data = FS.readFile(filename, { encoding: 'binary' });
- if (!(lib_data instanceof Uint8Array)) lib_data = new Uint8Array(lib_data);
- //Module.printErr('libfile ' + filename + ' size: ' + lib_data.length);
- lib_module = loadWebAssemblyModule(lib_data);
+ // the shared library is a shared wasm library (see tools/shared.py WebAssembly.make_shared_library)
+ var lib_data = FS.readFile(filename, { encoding: 'binary' });
+ if (!(lib_data instanceof Uint8Array)) lib_data = new Uint8Array(lib_data);
+ //Module.printErr('libfile ' + filename + ' size: ' + lib_data.length);
+ lib_module = loadWebAssemblyModule(lib_data);
#else
- // the shared library is a JS file, which we eval
- var lib_data = FS.readFile(filename, { encoding: 'utf8' });
- lib_module = eval(lib_data)(
- alignFunctionTables(),
- Module
- );
+ // the shared library is a JS file, which we eval
+ var lib_data = FS.readFile(filename, { encoding: 'utf8' });
+ lib_module = eval(lib_data)(
+ alignFunctionTables(),
+ Module
+ );
#endif
- } catch (e) {
+ } catch (e) {
#if ASSERTIONS
- Module.printErr('Error in loading dynamic library: ' + e);
+ Module.printErr('Error in loading dynamic library: ' + e);
#endif
- DLFCN.errorMsg = 'Could not evaluate dynamic lib: ' + filename + '\n' + e;
- return 0;
+ DLFCN.errorMsg = 'Could not evaluate dynamic lib: ' + filename + '\n' + e;
+ return 0;
+ }
}
// Not all browsers support Object.keys().
diff --git a/src/library_browser.js b/src/library_browser.js
index 36738391e..4258835ea 100644
--- a/src/library_browser.js
+++ b/src/library_browser.js
@@ -225,6 +225,33 @@ var LibraryBrowser = {
};
Module['preloadPlugins'].push(audioPlugin);
+#if (WASM != 0) && (MAIN_MODULE != 0)
+ var wasmPlugin = {};
+ wasmPlugin['asyncWasmLoadPromise'] = new Promise(
+ function(resolve, reject) { return resolve(); });
+ wasmPlugin['canHandle'] = function(name) {
+ return !Module.noWasmDecoding && (name.endsWith('.so') || name.endsWith('.wasm'));
+ };
+ wasmPlugin['handle'] = function(byteArray, name, onload, onerror) {
+ // loadWebAssemblyModule can not load modules out-of-order, so rather
+ // than just running the promises in parallel, this makes a chain of
+ // promises to run in series.
+ this.asyncWasmLoadPromise = this.asyncWasmLoadPromise.then(
+ function() {
+ return Module.loadWebAssemblyModule(byteArray, true)
+ }).then(
+ function(module) {
+ Module.preloadedWasm[name] = module;
+ onload();
+ },
+ function(err) {
+ console.warn("Couldn't instantiate wasm: " + name + " '" + err + "'");
+ onerror();
+ });
+ };
+ Module['preloadPlugins'].push(wasmPlugin);
+#endif
+
// Canvas event setup
function pointerLockChange() {
diff --git a/src/preamble.js b/src/preamble.js
index a757e8300..f529fe148 100644
--- a/src/preamble.js
+++ b/src/preamble.js
@@ -1822,6 +1822,9 @@ function removeRunDependency(id) {
Module["preloadedImages"] = {}; // maps url to image data
Module["preloadedAudios"] = {}; // maps url to audio data
+#if (WASM != 0) && (MAIN_MODULE != 0)
+Module["preloadedWasm"] = {}; // maps url to wasm instance exports
+#endif
#if PGO
var PGOMonitor = {
diff --git a/src/support.js b/src/support.js
index f6c9842ff..99367db70 100644
--- a/src/support.js
+++ b/src/support.js
@@ -86,7 +86,7 @@ function loadDynamicLibrary(lib) {
#if WASM
// Loads a side module from binary data
-function loadWebAssemblyModule(binary) {
+function loadWebAssemblyModule(binary, loadAsync) {
var int32View = new Uint32Array(new Uint8Array(binary.subarray(0, 24)).buffer);
assert(int32View[0] == 0x6d736100, 'need to see wasm magic number'); // \0wasm
// we should see the dylink section right after the magic number and wasm version
@@ -166,59 +166,71 @@ function loadWebAssemblyModule(binary) {
oldTable.push(table.get(i));
}
#endif
- // create a module from the instance
- var instance = new WebAssembly.Instance(new WebAssembly.Module(binary), info);
+
+ function postInstantiation(instance) {
+ var exports = {};
#if ASSERTIONS
- // the table should be unchanged
- assert(table === originalTable);
- assert(table === Module['wasmTable']);
- if (instance.exports['table']) {
- assert(table === instance.exports['table']);
- }
- // the old part of the table should be unchanged
- for (var i = 0; i < oldTableSize; i++) {
- assert(table.get(i) === oldTable[i], 'old table entries must remain the same');
- }
- // verify that the new table region was filled in
- for (var i = 0; i < tableSize; i++) {
- assert(table.get(oldTableSize + i) !== undefined, 'table entry was not filled in');
- }
-#endif
- var exports = {};
- for (var e in instance.exports) {
- var value = instance.exports[e];
- if (typeof value === 'object') {
- // a breaking change in the wasm spec, globals are now objects
- // https://github.com/WebAssembly/mutable-global/issues/1
- value = value.value;
+ // the table should be unchanged
+ assert(table === originalTable);
+ assert(table === Module['wasmTable']);
+ if (instance.exports['table']) {
+ assert(table === instance.exports['table']);
+ }
+ // the old part of the table should be unchanged
+ for (var i = 0; i < oldTableSize; i++) {
+ assert(table.get(i) === oldTable[i], 'old table entries must remain the same');
+ }
+ // verify that the new table region was filled in
+ for (var i = 0; i < tableSize; i++) {
+ assert(table.get(oldTableSize + i) !== undefined, 'table entry was not filled in');
}
- if (typeof value === 'number') {
- // relocate it - modules export the absolute value, they can't relocate before they export
+#endif
+ for (var e in instance.exports) {
+ var value = instance.exports[e];
+ if (typeof value === 'object') {
+ // a breaking change in the wasm spec, globals are now objects
+ // https://github.com/WebAssembly/mutable-global/issues/1
+ value = value.value;
+ }
+ if (typeof value === 'number') {
+ // relocate it - modules export the absolute value, they can't relocate before they export
#if EMULATED_FUNCTION_POINTERS
- // it may be a function pointer
- if (e.substr(0, 3) == 'fp$' && typeof instance.exports[e.substr(3)] === 'function') {
- value = value + env['tableBase'];
- } else {
+ // it may be a function pointer
+ if (e.substr(0, 3) == 'fp$' && typeof instance.exports[e.substr(3)] === 'function') {
+ value = value + env['tableBase'];
+ } else {
#endif
- value = value + env['memoryBase'];
+ value = value + env['memoryBase'];
#if EMULATED_FUNCTION_POINTERS
- }
+ }
#endif
+ }
+ exports[e] = value;
}
- exports[e] = value;
- }
- // initialize the module
- var init = exports['__post_instantiate'];
- if (init) {
- if (runtimeInitialized) {
- init();
- } else {
- // we aren't ready to run compiled code yet
- __ATINIT__.push(init);
+ // initialize the module
+ var init = exports['__post_instantiate'];
+ if (init) {
+ if (runtimeInitialized) {
+ init();
+ } else {
+ // we aren't ready to run compiled code yet
+ __ATINIT__.push(init);
+ }
}
+ return exports;
+ }
+
+ if (loadAsync) {
+ return WebAssembly.instantiate(binary, info).then(function(result) {
+ return postInstantiation(result.instance);
+ });
+ } else {
+ var instance = new WebAssembly.Instance(new WebAssembly.Module(binary), info);
+ return postInstantiation(instance);
}
- return exports;
}
+Module['loadWebAssemblyModule'] = loadWebAssemblyModule;
+
#endif // WASM
#endif // RELOCATABLE

View File

@ -79,6 +79,7 @@ var languagePluginLoader = new Promise((resolve, reject) => {
Module.noAudioDecoding = true;
let isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
if (isFirefox) {
console.log("Skipping wasm decoding");
Module.noWasmDecoding = true;
}

View File

@ -142,6 +142,12 @@ EM_JS(int, pyproxy_init, (), {
isPyProxy: function(jsobj) {
return jsobj['$$'] !== undefined && jsobj['$$']['type'] === 'PyProxy';
},
addExtraKeys: function(result) {
result.push('toString');
result.push('prototype');
result.push('arguments');
result.push('caller');
},
isExtensible: function() { return true },
has: function (jsobj, jskey) {
ptrobj = this.getPtr(jsobj);
@ -197,8 +203,7 @@ EM_JS(int, pyproxy_init, (), {
var idresult = __pyproxy_ownKeys(ptrobj);
var jsresult = Module.hiwire_get_value(idresult);
Module.hiwire_decref(idresult);
jsresult.push('toString');
jsresult.push('prototype');
this.addExtraKeys(jsresult);
return jsresult;
},
enumerate: function (jsobj) {
@ -206,8 +211,7 @@ EM_JS(int, pyproxy_init, (), {
var idresult = __pyproxy_enumerate(ptrobj);
var jsresult = Module.hiwire_get_value(idresult);
Module.hiwire_decref(idresult);
jsresult.push('toString');
jsresult.push('prototype');
this.addExtraKeys(jsresult);
return jsresult;
},
apply: function (jsobj, jsthis, jsargs) {

View File

@ -2,7 +2,12 @@
Various common utilities for testing.
"""
import atexit
import multiprocessing
import os
import pathlib
import queue
import sys
try:
import pytest
@ -30,16 +35,11 @@ class PackageLoaded:
class SeleniumWrapper:
def __init__(self):
from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.support.wait import WebDriverWait
options = Options()
options.add_argument('-headless')
driver = Firefox(
executable_path='geckodriver', firefox_options=options)
driver = self.get_driver()
wait = WebDriverWait(driver, timeout=20)
driver.get((BUILD_PATH / "test.html").as_uri())
driver.get(f'http://127.0.0.1:{PORT}/test.html')
wait.until(PyodideInited())
self.wait = wait
self.driver = driver
@ -69,12 +69,100 @@ class SeleniumWrapper:
yield self.driver.current_url
class FirefoxWrapper(SeleniumWrapper):
def get_driver(self):
from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options
from selenium.common.exceptions import JavascriptException
options = Options()
options.add_argument('-headless')
self.JavascriptException = JavascriptException
return Firefox(
executable_path='geckodriver', firefox_options=options)
class ChromeWrapper(SeleniumWrapper):
def get_driver(self):
from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options
from selenium.common.exceptions import WebDriverException
options = Options()
options.add_argument('--headless')
self.JavascriptException = WebDriverException
return Chrome(chrome_options=options)
if pytest is not None:
@pytest.fixture
def selenium():
selenium = SeleniumWrapper()
@pytest.fixture(params=['firefox', 'chrome'])
def selenium(request):
if request.param == 'firefox':
cls = FirefoxWrapper
elif request.param == 'chrome':
cls = ChromeWrapper
selenium = cls()
try:
yield selenium
finally:
print('\n'.join(str(x) for x in selenium.logs))
selenium.driver.quit()
PORT = 0
def spawn_web_server():
global PORT
print("Spawning webserver...")
q = multiprocessing.Queue()
p = multiprocessing.Process(target=run_web_server, args=(q,))
def shutdown_webserver():
q.put("TERMINATE")
p.join()
atexit.register(shutdown_webserver)
p.start()
PORT = q.get()
def run_web_server(q):
import http.server
import socketserver
print("Running webserver...")
os.chdir(BUILD_PATH)
Handler = http.server.SimpleHTTPRequestHandler
Handler.extensions_map['.wasm'] = 'application/wasm'
def dummy_log(*args, **kwargs):
pass
Handler.log_message = dummy_log
with socketserver.TCPServer(("", 0), Handler) as httpd:
host, port = httpd.server_address
print("serving at port", port)
q.put(port)
def service_actions():
try:
if q.get(False) == "TERMINATE":
sys.exit(0)
httpd.shutdown()
except queue.Empty:
pass
httpd.service_actions = service_actions
httpd.serve_forever()
if multiprocessing.current_process().name == 'MainProcess':
spawn_web_server()

View File

@ -2,8 +2,6 @@ import os
import pathlib
import time
from selenium.common.exceptions import JavascriptException
def test_init(selenium):
assert 'Python initialization complete' in selenium.logs
@ -50,7 +48,7 @@ def test_python2js(selenium):
def test_pythonexc2js(selenium):
try:
selenium.run_js('return pyodide.runPython("5 / 0")')
except JavascriptException as e:
except selenium.JavascriptException as e:
assert('ZeroDivisionError' in str(e))
else:
assert False, 'Expected exception'
@ -157,7 +155,7 @@ def test_pyproxy(selenium):
'__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__', 'bar', 'baz',
'get_value', 'toString', 'prototype'])
'get_value', 'toString', 'prototype', 'arguments', 'caller'])
assert selenium.run("hasattr(f, 'baz')")
selenium.run_js("delete pyodide.pyimport('f').baz")
assert not selenium.run("hasattr(f, 'baz')")
@ -179,7 +177,7 @@ def test_pyproxy_destroy(selenium):
"console.assert(f.get_value(1) === 64);\n"
"f.destroy();\n"
"f.get_value();\n")
except JavascriptException as e:
except selenium.JavascriptException as e:
assert 'Object has already been destroyed' in str(e)
else:
assert False, 'Expected exception'
@ -260,7 +258,7 @@ def test_jsproxy_iter(selenium):
def test_open_url(selenium):
assert selenium.run(
"import pyodide\n"
"pyodide.open_url('../test/data.txt').read()\n") == 'HELLO\n'
"pyodide.open_url('test_data.txt').read()\n") == 'HELLO\n'
def test_run_core_python_test(python_test, selenium):
@ -269,7 +267,7 @@ def test_run_core_python_test(python_test, selenium):
selenium.run(
"from test.libregrtest import main\n"
"main(['{}'], verbose=True, verbose3=True)".format(python_test))
except JavascriptException as e:
except selenium.JavascriptException as e:
assert str(e).strip().endswith('SystemExit: 0')