-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
STANDALONE_WASM option #9461
STANDALONE_WASM option #9461
Changes from 20 commits
e11288c
479e297
8dd1aaa
b25ee39
fab6ceb
664e17b
40e2f58
90b1eaa
78db28a
2e0770a
1848451
6e5db08
f781f39
5994a4c
d129daf
5a0b65c
f89fa67
998aa4a
61c75eb
521686d
c347034
5e894da
8136e2b
67007b8
0f6e911
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/* | ||
* Copyright 2019 The Emscripten Authors. All rights reserved. | ||
* Emscripten is available under two separate licenses, the MIT license and the | ||
* University of Illinois/NCSA Open Source License. Both these licenses can be | ||
* found in the LICENSE file. | ||
*/ | ||
|
||
mergeInto(LibraryManager.library, { | ||
proc_exit__deps: ['exit'], | ||
proc_exit: function(code) { | ||
return _exit(code); | ||
}, | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -152,6 +152,10 @@ var LibraryManager = { | |
libraries.push('library_glemu.js'); | ||
} | ||
|
||
if (STANDALONE_WASM) { | ||
libraries.push('library_wasi.js'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why make this conditional? Aren't all the library functions only included on demand anyway? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it's all on demand, but we also include some libraries only based on their flags, like the GL stuff right above this. I think it's nice as it reflects the fact that nothing should be used from those libraries without the flag, the error is clearer that way. |
||
} | ||
|
||
libraries = libraries.concat(additionalLibraries); | ||
|
||
if (BOOTSTRAPPING_STRUCT_INFO) libraries = ['library_bootstrap_structInfo.js', 'library_formatString.js']; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -989,6 +989,12 @@ function createWasm() { | |
exports = Asyncify.instrumentWasmExports(exports); | ||
#endif | ||
Module['asm'] = exports; | ||
#if STANDALONE_WASM | ||
// In pure wasm mode the memory is created in the wasm (not imported), and | ||
// then exported. | ||
// TODO: do not create a Memory earlier in JS | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like a TODO we certainly want to fix. |
||
updateGlobalBufferAndViews(exports['memory'].buffer); | ||
#endif | ||
#if USE_PTHREADS | ||
// Keep a reference to the compiled module so we can post it to the workers. | ||
wasmModule = module; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/* | ||
* Copyright 2019 The Emscripten Authors. All rights reserved. | ||
* Emscripten is available under two separate licenses, the MIT license and the | ||
* University of Illinois/NCSA Open Source License. Both these licenses can be | ||
* found in the LICENSE file. | ||
*/ | ||
|
||
#include <emscripten.h> | ||
#include <errno.h> | ||
#include <stdio.h> | ||
#include <string.h> | ||
|
||
#include <wasi/wasi.h> | ||
|
||
/* | ||
* WASI support code. These are compiled with the program, and call out | ||
* using wasi APIs, which can be provided either by a wasi VM or by our | ||
* emitted JS. | ||
*/ | ||
|
||
// libc | ||
|
||
void exit(int status) { | ||
__wasi_proc_exit(status); | ||
__builtin_unreachable(); | ||
} | ||
|
||
void abort() { | ||
exit(1); | ||
} | ||
|
||
// Musl lock internals. As we assume wasi is single-threaded for now, these | ||
// are no-ops. | ||
|
||
void __lock(void* ptr) {} | ||
void __unlock(void* ptr) {} | ||
|
||
// Emscripten additions | ||
|
||
void *emscripten_memcpy_big(void *restrict dest, const void *restrict src, size_t n) { | ||
// This normally calls out into JS which can do a single fast operation, | ||
// but with wasi we can't do that. As this is called when n >= 8192, we | ||
// can just split into smaller calls. | ||
// TODO optimize, maybe build our memcpy with a wasi variant, maybe have | ||
// a SIMD variant, etc. | ||
const int CHUNK = 8192; | ||
unsigned char* d = (unsigned char*)dest; | ||
unsigned char* s = (unsigned char*)src; | ||
while (n > 0) { | ||
size_t curr_n = n; | ||
if (curr_n > CHUNK) curr_n = CHUNK; | ||
memcpy(d, s, curr_n); | ||
d += CHUNK; | ||
s += CHUNK; | ||
n -= curr_n; | ||
} | ||
return dest; | ||
} | ||
|
||
static const int WASM_PAGE_SIZE = 65536; | ||
|
||
// Note that this does not support memory growth in JS because we don't update the JS | ||
// heaps. Wasm and wasi lack a good API for that. | ||
int emscripten_resize_heap(size_t size) { | ||
size_t result = __builtin_wasm_memory_grow(0, (size + WASM_PAGE_SIZE - 1) / WASM_PAGE_SIZE); | ||
return result != (size_t)-1; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,7 +30,7 @@ | |
raise Exception('do not run this file directly; do something like: tests/runner.py other') | ||
|
||
from tools.shared import Building, PIPE, run_js, run_process, STDOUT, try_delete, listify | ||
from tools.shared import EMCC, EMXX, EMAR, EMRANLIB, PYTHON, FILE_PACKAGER, WINDOWS, MACOS, LLVM_ROOT, EMCONFIG, EM_BUILD_VERBOSE | ||
from tools.shared import EMCC, EMXX, EMAR, EMRANLIB, PYTHON, FILE_PACKAGER, WINDOWS, MACOS, LINUX, LLVM_ROOT, EMCONFIG, EM_BUILD_VERBOSE | ||
from tools.shared import CLANG, CLANG_CC, CLANG_CPP, LLVM_AR | ||
from tools.shared import COMPILER_ENGINE, NODE_JS, SPIDERMONKEY_ENGINE, JS_ENGINES, V8_ENGINE | ||
from tools.shared import WebAssembly | ||
|
@@ -8296,16 +8296,21 @@ def run(args, expected): | |
run(['-s', 'TOTAL_MEMORY=32MB', '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'BINARYEN=1'], (2 * 1024 * 1024 * 1024 - 65536) // 16384) | ||
run(['-s', 'TOTAL_MEMORY=32MB', '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'BINARYEN=1', '-s', 'WASM_MEM_MAX=128MB'], 2048 * 4) | ||
|
||
def test_wasm_targets(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it ok that we seems to have lost of test coverage for fastcomp here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, good point, I missed something here. I thought it was fine to remove this, but actually it hid a possible regression: building in fastcomp without SIDE_MODULE but with That never worked very well anyhow, it was a half-hearted attempt at standalone wasm files. So I am leaning towards showing a clear error in that case that the user should use the upstream wasm backend for standalone wasm? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like there is a reasonable fix actually, pushed now. |
||
@no_fastcomp('only upstream supports STANDALONE_WASM') | ||
def test_wasm_target_and_STANDALONE_WASM(self): | ||
# STANDALONE_WASM means we never minify imports and exports. | ||
for opts, potentially_expect_minified_exports_and_imports in ( | ||
([], False), | ||
(['-O2'], False), | ||
(['-O3'], True), | ||
(['-Os'], True), | ||
([], False), | ||
(['-O2'], False), | ||
(['-O3'], True), | ||
(['-O3', '-s', 'STANDALONE_WASM'], False), | ||
(['-Os'], True), | ||
): | ||
# targeting .wasm (without .js) means we enable STANDALONE_WASM automatically, and don't minify imports/exports | ||
for target in ('out.js', 'out.wasm'): | ||
expect_minified_exports_and_imports = potentially_expect_minified_exports_and_imports and target.endswith('.js') | ||
print(opts, potentially_expect_minified_exports_and_imports, target, ' => ', expect_minified_exports_and_imports) | ||
standalone = target.endswith('.wasm') or 'STANDALONE_WASM' in opts | ||
print(opts, potentially_expect_minified_exports_and_imports, target, ' => ', expect_minified_exports_and_imports, standalone) | ||
|
||
self.clear() | ||
run_process([PYTHON, EMCC, path_from_root('tests', 'hello_world.cpp'), '-o', target] + opts) | ||
|
@@ -8317,13 +8322,33 @@ def test_wasm_targets(self): | |
exports = [line.strip().split(' ')[1].replace('"', '') for line in wast_lines if "(export " in line] | ||
imports = [line.strip().split(' ')[2].replace('"', '') for line in wast_lines if "(import " in line] | ||
exports_and_imports = exports + imports | ||
print(exports) | ||
print(imports) | ||
print(' exports', exports) | ||
print(' imports', imports) | ||
if expect_minified_exports_and_imports: | ||
assert 'a' in exports_and_imports | ||
else: | ||
assert 'a' not in exports_and_imports | ||
assert 'memory' in exports_and_imports, 'some things are not minified anyhow' | ||
assert 'memory' in exports_and_imports or 'fd_write' in exports_and_imports, 'some things are not minified anyhow' | ||
# verify the wasm runs with the JS | ||
if target.endswith('.js'): | ||
self.assertContained('hello, world!', run_js('out.js')) | ||
# verify the wasm runs in a wasm VM, without the JS | ||
if LINUX: # TODO: other platforms | ||
if standalone: | ||
WASMER = os.path.expanduser(os.path.join('~', '.wasmer', 'bin', 'wasmer')) | ||
if os.path.isfile(WASMER): | ||
print(' running in wasmer') | ||
out = run_process([WASMER, 'run', 'out.wasm'], stdout=PIPE).stdout | ||
self.assertContained('hello, world!', out) | ||
else: | ||
print('[WARNING - no wasmer]') | ||
WASMTIME = os.path.expanduser(os.path.join('~', 'wasmtime')) | ||
if os.path.isfile(WASMTIME): | ||
print(' running in wasmtime') | ||
out = run_process([WASMTIME, 'out.wasm'], stdout=PIPE).stdout | ||
self.assertContained('hello, world!', out) | ||
else: | ||
print('[WARNING - no wasmtime]') | ||
|
||
def test_wasm_targets_side_module(self): | ||
# side modules do allow a wasm target | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -500,7 +500,7 @@ def get_emscripten_version(path): | |
# NB: major version 0 implies no compatibility | ||
# NB: when changing the metadata format, we should only append new fields, not | ||
# reorder, modify, or remove existing ones. | ||
(EMSCRIPTEN_METADATA_MAJOR, EMSCRIPTEN_METADATA_MINOR) = (0, 2) | ||
(EMSCRIPTEN_METADATA_MAJOR, EMSCRIPTEN_METADATA_MINOR) = (0, 3) | ||
# For the JS/WASM ABI, specifies the minimum ABI version required of | ||
# the WASM runtime implementation by the generated WASM binary. It follows | ||
# semver and changes whenever C types change size/signedness or | ||
|
@@ -509,7 +509,7 @@ def get_emscripten_version(path): | |
# change, increment EMSCRIPTEN_ABI_MINOR if EMSCRIPTEN_ABI_MAJOR == 0 | ||
# or the ABI change is backwards compatible, otherwise increment | ||
# EMSCRIPTEN_ABI_MAJOR and set EMSCRIPTEN_ABI_MINOR = 0. | ||
(EMSCRIPTEN_ABI_MAJOR, EMSCRIPTEN_ABI_MINOR) = (0, 6) | ||
(EMSCRIPTEN_ABI_MAJOR, EMSCRIPTEN_ABI_MINOR) = (0, 7) | ||
|
||
|
||
def generate_sanity(): | ||
|
@@ -1819,11 +1819,16 @@ def link_lld(args, target, opts=[], lto_level=0): | |
'-o', | ||
target, | ||
'--allow-undefined', | ||
'--import-memory', | ||
'--import-table', | ||
'--lto-O%d' % lto_level, | ||
] + args | ||
|
||
# wasi does not import the memory (but for JS it is efficient to do so, | ||
# as it allows us to set up memory, preload files, etc. even before the | ||
# wasm module arrives) | ||
if not Settings.STANDALONE_WASM: | ||
cmd.append('--import-memory') | ||
cmd.append('--import-table') | ||
|
||
if Settings.USE_PTHREADS: | ||
cmd.append('--shared-memory') | ||
|
||
|
@@ -2487,11 +2492,11 @@ def minify_wasm_js(js_file, wasm_file, expensive_optimizations, minify_whitespac | |
passes.append('minifyWhitespace') | ||
logger.debug('running post-meta-DCE cleanup on shell code: ' + ' '.join(passes)) | ||
js_file = Building.acorn_optimizer(js_file, passes) | ||
# also minify the names used between js and wasm, if we emitting JS (then the JS knows how to load the minified names) | ||
# Also minify the names used between js and wasm, if we are emitting an optimized JS+wasm combo (then the JS knows how to load the minified names). | ||
# If we are building with DECLARE_ASM_MODULE_EXPORTS=0, we must *not* minify the exports from the wasm module, since in DECLARE_ASM_MODULE_EXPORTS=0 mode, the code that | ||
# reads out the exports is compacted by design that it does not have a chance to unminify the functions. If we are building with DECLARE_ASM_MODULE_EXPORTS=1, we might | ||
# as well minify wasm exports to regain some of the code size loss that setting DECLARE_ASM_MODULE_EXPORTS=1 caused. | ||
if Settings.EMITTING_JS and not Settings.AUTODEBUG and not Settings.ASSERTIONS: | ||
if not Settings.STANDALONE_WASM and not Settings.AUTODEBUG and not Settings.ASSERTIONS and not Settings.SIDE_MODULE: | ||
js_file = Building.minify_wasm_imports_and_exports(js_file, wasm_file, minify_whitespace=minify_whitespace, minify_exports=Settings.DECLARE_ASM_MODULE_EXPORTS, debug_info=debug_info) | ||
return js_file | ||
|
||
|
@@ -2526,8 +2531,19 @@ def metadce(js_file, wasm_file, minify_whitespace, debug_info): | |
export = '_' + export | ||
if export in Building.user_requested_exports or Settings.EXPORT_ALL: | ||
item['root'] = True | ||
# in standalone wasm, always export the memory | ||
if Settings.STANDALONE_WASM: | ||
graph.append({ | ||
'export': 'memory', | ||
'name': 'emcc$export$memory', | ||
'reaches': [], | ||
'root': True | ||
}) | ||
# fix wasi imports TODO: support wasm stable with an option? | ||
WASI_IMPORTS = set(['fd_write']) | ||
WASI_IMPORTS = set([ | ||
'fd_write', | ||
'proc_exit', | ||
]) | ||
for item in graph: | ||
if 'import' in item and item['import'][1][1:] in WASI_IMPORTS: | ||
item['import'][0] = 'wasi_unstable' | ||
|
@@ -3090,7 +3106,8 @@ def add_emscripten_metadata(js_file, wasm_file): | |
WebAssembly.lebify(global_base) + | ||
WebAssembly.lebify(dynamic_base) + | ||
WebAssembly.lebify(dynamictop_ptr) + | ||
WebAssembly.lebify(tempdouble_ptr) | ||
WebAssembly.lebify(tempdouble_ptr) + | ||
WebAssembly.lebify(int(Settings.STANDALONE_WASM)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we should just not add this metadata for STANDALONE_WASM? Since the metadata was mostly to support for loading of emscripten-like wasm binaries outside of emscripten? Perhaps we can do that as a followup after consulting with the users of the metadata? Who are the users exactly I'm not sure. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can check with people if it's unnecessary, but we should either land this with the change (which is the safe thing) or block this until we decide whether to add this change or not. I lean towards the former since the latter may take a while. In general I think this may still be useful for the same people for the same reasons as before - these wasms are as standalone as we can make them, but still may contain e.g. WebGL calls if using OpenGL, etc. So they are not pure wasi in general. Which means it's useful to have metadata about the emscripten ABI, and this flag does affect the ABI. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure we can land this now.. I was just suggesting this as a followup. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I remembered that we have promised to only append to the metadata list. So if we land this we probably don't want to remove the field later. However, as I said earlier, I think we do want this field, so I'm not worried. Are you still concerned? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re-reading your posts, were you saying that we should not emit There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not talking about the field, but the entire metadata section. Its seems to be that the point of this section is so that runtimes and extract the information needs from the "emscripten-flavor" wasm file and run it without JS. My idea is that for PURE_WASM we may not need the section at all in the future. In which case .. this field would be zeor by definition... or we could bump the major version which allows is the change it however we like. My real long term hope is that the users of this metadata section are in fact that exact same users who want to use PURE_WASM instead and that PURE_WASM don't needs to self-describe in this way, and so we may be able to simply remove this completely? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see now, thanks. Yeah, maybe we eventually won't need this metadata at all, if we only use standard APIs. That seems like the very far future though, as we'll need nonstandard APIs to do many things for quite some time, if not forever (e.g. WebGL). |
||
|
||
# NB: more data can be appended here as long as you increase | ||
# the EMSCRIPTEN_METADATA_MINOR | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this. I really hope we can move fd_read in there and link it by default in the future.