diff --git a/emcc.py b/emcc.py index b13a1eb45689b..2f3abbd831982 100755 --- a/emcc.py +++ b/emcc.py @@ -47,6 +47,8 @@ from tools.minimal_runtime_shell import generate_minimal_runtime_html import tools.line_endings from tools.toolchain_profiler import ToolchainProfiler +from tools import wasm2c + if __name__ == '__main__': ToolchainProfiler.record_process_start() @@ -3356,6 +3358,9 @@ def run_closure_compiler(final): dwarf_target = wasm_binary_target + '.debug.wasm' building.emit_debug_on_side(wasm_binary_target, dwarf_target) + if shared.Settings.WASM2C: + wasm2c.do_wasm2c(wasm_binary_target) + # replace placeholder strings with correct subresource locations if shared.Settings.SINGLE_FILE: js = open(final).read() diff --git a/package-lock.json b/package-lock.json index b3819c01bf32a..b87b1e947af9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -361,6 +361,11 @@ "source-map": "^0.5.1" } }, + "wasm2c": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wasm2c/-/wasm2c-1.0.0.tgz", + "integrity": "sha512-4SIESF2JNxrry6XFa/UQcsQibn+bxPkQ/oqixiXz2o8fsMl8J4vtvhH/evgbi8vZajAlaukuihEcQTWb9tVLUA==" + }, "ws": { "version": "0.4.32", "resolved": "http://registry.npmjs.org/ws/-/ws-0.4.32.tgz", diff --git a/package.json b/package.json index 4bcb89aaf39e3..17de4cc07972a 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "dependencies": { "google-closure-compiler": "20200224.0.0", "html-minifier-terser": "5.0.2", - "source-map": "0.5.6" + "source-map": "0.5.6", + "wasm2c": "1.0.0" } } diff --git a/src/settings.js b/src/settings.js index 092d11f6fc7f1..dd6a629a850ff 100644 --- a/src/settings.js +++ b/src/settings.js @@ -1752,6 +1752,14 @@ var DEFAULT_TO_CXX = 1; // long double printing precision. var PRINTF_LONG_DOUBLE = 0; +// Run wabt's wasm2c tool on the final wasm, and combine that with a C runtime, +// resulting in a .c file that you can compile with a C compiler to get a +// native executable that works the same as the normal js+wasm. This will also +// emit the wasm2c .h file. The output filenames will be X.wasm.c, X.wasm.h +// if your output is X.js or X.wasm (note the added .wasm. we make sure to emit, +// which avoids trampling a C file). +var WASM2C = 0; + //=========================================== // Internal, used for testing only, from here //=========================================== diff --git a/tests/other/wasm2c/my-code.c b/tests/other/wasm2c/my-code.c new file mode 100644 index 0000000000000..ab263ab35405d --- /dev/null +++ b/tests/other/wasm2c/my-code.c @@ -0,0 +1,25 @@ +#include + +// We could +// +// #include +// +// for the externs declared here manually, but including that currently +// requires having wasm-rt.h in the include path, which may be annoying for +// users - needs to be thought about. + +extern void wasmbox_init(void); + +extern int (*Z_do_bad_thingZ_ii)(int); + +extern int (*Z_twiceZ_ii)(int); + +int main() { + puts("Initializing sandboxed unsafe library"); + wasmbox_init(); + printf("Calling twice on 21 returns %d\n", Z_twiceZ_ii(21)); + puts("Calling something bad now..."); + int num = Z_do_bad_thingZ_ii(1); + printf("The sandbox should not have been able to print anything.\n" + "It claims it printed %d chars but the test proves it didn't!\n", num); +} diff --git a/tests/other/wasm2c/output.txt b/tests/other/wasm2c/output.txt new file mode 100644 index 0000000000000..5c0c8b30b7cb9 --- /dev/null +++ b/tests/other/wasm2c/output.txt @@ -0,0 +1,5 @@ +Initializing sandboxed unsafe library +Calling twice on 21 returns 42 +Calling something bad now... +The sandbox should not have been able to print anything. +It claims it printed 55 chars but the test proves it didn't! diff --git a/tests/other/wasm2c/unsafe-library.c b/tests/other/wasm2c/unsafe-library.c new file mode 100644 index 0000000000000..28d4a85783180 --- /dev/null +++ b/tests/other/wasm2c/unsafe-library.c @@ -0,0 +1,12 @@ +#include +#include + +EMSCRIPTEN_KEEPALIVE +int twice(int x) { + return x + x; +} + +EMSCRIPTEN_KEEPALIVE +int do_bad_thing(int size) { + return printf("I am in a sandbox and should not be able to print this!"); +} diff --git a/tests/runner.py b/tests/runner.py index e5c4c55f91a8f..5e5c7c3d21c8d 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -58,8 +58,12 @@ sys.path.append(__rootpath__) import parallel_testsuite -from tools.shared import EM_CONFIG, TEMP_DIR, EMCC, EMXX, DEBUG, LLVM_TARGET, ASM_JS_TARGET, EMSCRIPTEN_TEMP_DIR, WASM_TARGET, SPIDERMONKEY_ENGINE, WINDOWS, EM_BUILD_VERBOSE -from tools.shared import asstr, get_canonical_temp_dir, run_process, try_delete, asbytes, safe_copy, Settings +from tools.shared import EM_CONFIG, TEMP_DIR, EMCC, EMXX, DEBUG +from tools.shared import LLVM_TARGET, ASM_JS_TARGET, EMSCRIPTEN_TEMP_DIR +from tools.shared import WASM_TARGET, SPIDERMONKEY_ENGINE, WINDOWS +from tools.shared import EM_BUILD_VERBOSE, CLANG_CC +from tools.shared import asstr, get_canonical_temp_dir, run_process, try_delete +from tools.shared import asbytes, safe_copy, Settings from tools import jsrun, shared, line_endings, building @@ -1231,6 +1235,9 @@ def do_run(self, src, expected_output, args=[], output_nicerizer=None, if len(wasm_engines) == 0: logger.warning('no wasm engine was found to run the standalone part of this test') js_engines += wasm_engines + if self.get_setting('WASM2C'): + # the "engine" to run wasm2c builds is clang that compiles the c + js_engines += [[CLANG_CC]] if len(js_engines) == 0: self.skipTest('No JS engine present to run this test with. Check %s and the paths therein.' % EM_CONFIG) for engine in js_engines: diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index a1fd8a44fe6fd..6b512787d99c5 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -239,6 +239,48 @@ def cleanup(self): building.clear() +class EmscriptenWasm2CBenchmarker(EmscriptenBenchmarker): + def __init__(self, name): + super(EmscriptenWasm2CBenchmarker, self).__init__(name, 'no engine needed') + + def build(self, parent, filename, args, shared_args, emcc_args, native_args, native_exec, lib_builder, has_output_parser): + # wasm2c doesn't want minimal runtime which the normal emscripten + # benchmarker defaults to, as we don't have any JS anyhow + emcc_args = emcc_args + [ + '-s', 'STANDALONE_WASM', + '-s', 'MINIMAL_RUNTIME=0', + '-s', 'WASM2C' + ] + + global LLVM_FEATURE_FLAGS + old_flags = LLVM_FEATURE_FLAGS + try: + # wasm2c does not support anything beyond MVP + LLVM_FEATURE_FLAGS = [] + super(EmscriptenWasm2CBenchmarker, self).build(parent, filename, args, shared_args, emcc_args, native_args, native_exec, lib_builder, has_output_parser) + finally: + LLVM_FEATURE_FLAGS = old_flags + + # move the JS away so there is no chance we run it by mistake + shutil.move(self.filename, self.filename + '.old.js') + + base = self.filename[:-3] + c = base + '.wasm.c' + native = base + '.exe' + + run_process(['clang', c, '-o', native, OPTIMIZATIONS, '-lm', + '-DWASM_RT_MAX_CALL_STACK_DEPTH=8000']) # for havlak + + self.filename = native + + def run(self, args): + return run_process([self.filename] + args, stdout=PIPE, stderr=PIPE, check=False).stdout + + def get_output_files(self): + # return the native code. c size may also be interesting. + return [self.filename] + + CHEERP_BIN = '/opt/cheerp/bin/' @@ -323,6 +365,7 @@ def cleanup(self): benchmarkers += [ EmscriptenBenchmarker(default_v8_name, aot_v8), EmscriptenBenchmarker(default_v8_name + '-lto', aot_v8, ['-flto']), + # EmscriptenWasm2CBenchmarker('wasm2c') ] if os.path.exists(CHEERP_BIN): benchmarkers += [ diff --git a/tests/test_core.py b/tests/test_core.py index 79183f13bc49d..b7f66b69e95ff 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -165,6 +165,28 @@ def decorated(self): return decorated +def also_with_standalone_wasm_and_wasm2c(func): + def decorated(self): + func(self) + # Standalone mode is only supported in the wasm backend, and not in all + # modes there. + if can_do_standalone(self): + print('standalone') + self.set_setting('STANDALONE_WASM', 1) + # we will not legalize the JS ffi interface, so we must use BigInt + # support in order for JS to have a chance to run this without trapping + # when it sees an i64 on the ffi. + self.set_setting('WASM_BIGINT', 1) + with js_engines_modify([NODE_JS + ['--experimental-wasm-bigint']]): + func(self) + print('wasm2c') + self.set_setting('WASM2C', 1) + with wasm_engines_modify([]): + func(self) + + return decorated + + # Similar to also_with_standalone_wasm, but suitable for tests that cannot # run in a wasm VM yet, as they are not 100% standalone. We can still # run them with the JS code though. @@ -187,6 +209,31 @@ def decorated(self): return decorated +def also_with_impure_standalone_wasm_and_wasm2c(func): + def decorated(self): + func(self) + # Standalone mode is only supported in the wasm backend, and not in all + # modes there. + if can_do_standalone(self): + print('standalone (impure; no wasm runtimes)') + with wasm_engines_modify([]): + self.set_setting('STANDALONE_WASM', 1) + # we will not legalize the JS ffi interface, so we must use BigInt + # support in order for JS to have a chance to run this without trapping + # when it sees an i64 on the ffi. + self.set_setting('WASM_BIGINT', 1) + with js_engines_modify([NODE_JS + ['--experimental-wasm-bigint']]): + func(self) + print('wasm2c') + self.set_setting('STANDALONE_WASM', 1) + self.set_setting('WASM2C', 1) + # disable js engines too, so we only run the c output + with js_engines_modify([]): + func(self) + + return decorated + + # Similar to also_with_standalone_wasm, but suitable for tests that can *only* # run in a wasm VM, or in non-standalone mode, but not in standalone mode with # our JS. @@ -521,7 +568,7 @@ def test_cube2md5(self): shutil.copyfile(path_from_root('tests', 'cube2md5.txt'), 'cube2md5.txt') self.do_run(open(path_from_root('tests', 'cube2md5.cpp')).read(), open(path_from_root('tests', 'cube2md5.ok')).read(), assert_returncode=None) - @also_with_standalone_wasm + @also_with_standalone_wasm_and_wasm2c @needs_make('make') def test_cube2hash(self): # A good test of i64 math @@ -1076,6 +1123,7 @@ def test_wcslen(self): def test_regex(self): self.do_run_in_out_file_test('tests', 'core', 'test_regex') + @also_with_impure_standalone_wasm_and_wasm2c def test_longjmp(self): self.do_run_in_out_file_test('tests', 'core', 'test_longjmp') @@ -5580,7 +5628,7 @@ def test_unistd_misc(self): # i64s in the API, which we'd need to legalize for JS, so in standalone mode # all we can test is wasm VMs - @also_with_standalone_wasm + @also_with_standalone_wasm_and_wasm2c def test_posixtime(self): test_path = path_from_root('tests', 'core', 'test_posixtime') src, output = (test_path + s for s in ('.c', '.out')) @@ -6608,7 +6656,7 @@ def do_autodebug(filename): @no_asan('autodebug logging interferes with asan') @no_fastcomp('autodebugging wasm is only supported in the wasm backend') @with_env_modify({'EMCC_AUTODEBUG': '1'}) - @also_with_impure_standalone_wasm + @also_with_impure_standalone_wasm_and_wasm2c def test_autodebug_wasm(self): # Autodebug does not work with too much shadow memory. # Memory consumed by autodebug depends on the size of the WASM linear memory. diff --git a/tests/test_other.py b/tests/test_other.py index 84e025b3916b3..af5dae51a888b 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -10248,6 +10248,27 @@ def test_standalone_syscalls(self): for engine in WASM_ENGINES: self.assertContained(expected, run_js('test.wasm', engine)) + @no_fastcomp("uses standalone mode") + def test_wasm2c_reactor(self): + # test compiling an unsafe library using wasm2c, then using it from a + # main program. this shows it is easy to use wasm2c as a sandboxing + # mechanism. + + # first compile the library with emcc, getting a .c and .h + run_process([EMCC, + path_from_root('tests', 'other', 'wasm2c', 'unsafe-library.c'), + '-O3', '-o', 'lib.wasm', '-s', 'WASM2C', '--no-entry']) + # compile that .c to a native object + run_process([CLANG_CC, 'lib.wasm.c', '-c', '-O3', '-o', 'lib.o']) + # compile the main program natively normally, and link with the + # unsafe library + run_process([CLANG_CC, + path_from_root('tests', 'other', 'wasm2c', 'my-code.c'), + '-O3', 'lib.o', '-o', 'program.exe']) + output = run_process([os.path.abspath('program.exe')], stdout=PIPE).stdout + with open(path_from_root('tests', 'other', 'wasm2c', 'output.txt')) as f: + self.assertEqual(output, f.read()) + @parameterized({ 'wasm2js': (['-s', 'WASM=0'], ''), 'modularize': (['-s', 'MODULARIZE'], 'Module()'), diff --git a/tools/jsrun.py b/tools/jsrun.py index e8c197e08355b..6da0461fb2a7c 100644 --- a/tools/jsrun.py +++ b/tools/jsrun.py @@ -47,6 +47,7 @@ def make_command(filename, engine=None, args=[]): is_jsc = 'jsc' in jsengine is_wasmer = 'wasmer' in jsengine is_wasmtime = 'wasmtime' in jsengine + is_clang = engine[0] == shared.CLANG_CC # Disable true async compilation (async apis will in fact be synchronous) for now # due to https://bugs.chromium.org/p/v8/issues/detail?id=6263 shell_option_flags = ['--no-wasm-async-compilation'] if is_d8 else [] @@ -56,6 +57,14 @@ def make_command(filename, engine=None, args=[]): if is_wasmer or is_wasmtime: # in a wasm runtime, run the wasm, not the js filename = shared.unsuffixed(filename) + '.wasm' + elif is_clang: + # with wasm2c, the input is a c file, which we must compile first + c = shared.unsuffixed(filename) + '.wasm.c' + executable = shared.unsuffixed(filename) + '.exe' + shared.run_process(engine + [c, '-o', executable]) + # we can now run the executable directly, without an engine + engine = [] + filename = os.path.abspath(executable) # Separates engine flags from script flags flag_separator = ['--'] if is_d8 or is_jsc else [] return engine + command_flags + [filename] + shell_option_flags + flag_separator + args @@ -83,10 +92,14 @@ def check_engine(engine): def require_engine(engine): engine_path = engine[0] + # if clang is the "engine", it means we compiled to a native executable; + # there is nothing to check here + if engine_path == shared.CLANG_CC: + return if engine_path not in WORKING_ENGINES: check_engine(engine) if not WORKING_ENGINES[engine_path]: - logging.critical('The JavaScript shell (%s) does not seem to work, check the paths in the config file' % engine) + logging.critical('The engine (%s) does not seem to work, check the paths in the config file' % engine) sys.exit(1) diff --git a/tools/wasm2c.py b/tools/wasm2c.py new file mode 100644 index 0000000000000..5b0a3c6cf63e5 --- /dev/null +++ b/tools/wasm2c.py @@ -0,0 +1,111 @@ +# Copyright 2020 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. + +import os +import re + +from tools.shared import Settings, path_from_root, unsuffixed, NODE_JS, run_process, exit_with_error + + +def do_wasm2c(infile): + assert Settings.STANDALONE_WASM + WASM2C = NODE_JS + [path_from_root('node_modules', 'wasm2c', 'wasm2c.js')] + WASM2C_DIR = path_from_root('node_modules', 'wasm2c') + c_file = unsuffixed(infile) + '.wasm.c' + h_file = unsuffixed(infile) + '.wasm.h' + cmd = WASM2C + [infile, '-o', c_file] + run_process(cmd) + total = '''\ +/* +* This file was generated by emcc+wasm2c. To compile it, use something like +* +* $CC FILE.c -O2 -lm -DWASM_RT_MAX_CALL_STACK_DEPTH=8000 +*/ +''' + SEP = '\n/* ==================================== */\n' + + def bundle_file(total, filename): + with open(filename) as f: + total += '// ' + filename + '\n' + f.read() + SEP + return total + + # hermeticize the C file, by bundling in the wasm2c/ includes + headers = [ + (WASM2C_DIR, 'wasm-rt.h'), + (WASM2C_DIR, 'wasm-rt-impl.h'), + ('', h_file) + ] + for header in headers: + total = bundle_file(total, os.path.join(header[0], header[1])) + # add the wasm2c output + with open(c_file) as read_c: + c = read_c.read() + total += c + SEP + # add the wasm2c runtime + total = bundle_file(total, os.path.join(WASM2C_DIR, 'wasm-rt-impl.c')) + # add the support code + support_files = ['base'] + if Settings.AUTODEBUG: + support_files.append('autodebug') + if Settings.EXPECT_MAIN: + # TODO: add an option for direct OS access. For now, do that when building + # an executable with main, as opposed to a library + support_files.append('os') + support_files.append('main') + else: + support_files.append('os_sandboxed') + support_files.append('reactor') + # for a reactor, also append wasmbox_* API definitions + with open(h_file, 'a') as f: + f.write(''' +// wasmbox_* API +// TODO: optional prefixing +extern void wasmbox_init(void); +''') + for support_file in support_files: + total = bundle_file(total, path_from_root('tools', 'wasm2c', support_file + '.c')) + # remove #includes of the headers we bundled + for header in headers: + total = total.replace('#include "%s"\n' % header[1], '/* include of %s */\n' % header[1]) + # generate the necessary invokes + invokes = [] + for sig in re.findall(r"\/\* import\: 'env' 'invoke_(\w+)' \*\/", total): + def s_to_c(s): + if s == 'v': + return 'void' + elif s == 'i': + return 'u32' + elif s == 'j': + return 'u64' + elif s == 'f': + return 'f32' + elif s == 'd': + return 'f64' + else: + exit_with_error('invalid sig element:' + str(s)) + + def name(i): + return 'a' + str(i) + + wabt_sig = sig[0] + 'i' + sig[1:] + typed_args = ['u32 fptr'] + [s_to_c(sig[i]) + ' ' + name(i) for i in range(1, len(sig))] + types = ['u32'] + [s_to_c(sig[i]) for i in range(1, len(sig))] + args = ['fptr'] + [name(i) for i in range(1, len(sig))] + invokes.append( + '%s_INVOKE_IMPL(%sZ_envZ_invoke_%sZ_%s, (%s), (%s), (%s), Z_dynCall_%sZ_%s);' % ( + 'VOID' if sig[0] == 'v' else 'RETURNING', + (s_to_c(sig[0]) + ', ') if sig[0] != 'v' else '', + sig, + wabt_sig, + ', '.join(typed_args), + ', '.join(types), + ', '.join(args), + sig, + wabt_sig + )) + total += '\n'.join(invokes) + # write out the final file + with open(c_file, 'w') as out: + out.write(total) diff --git a/tools/wasm2c/autodebug.c b/tools/wasm2c/autodebug.c new file mode 100644 index 0000000000000..d36c63e9eed10 --- /dev/null +++ b/tools/wasm2c/autodebug.c @@ -0,0 +1,87 @@ +IMPORT_IMPL(void, Z_envZ_log_executionZ_vi, (u32 loc), { + printf("log_execution %d\n", loc); +}); +IMPORT_IMPL(u32, Z_envZ_get_i32Z_iiii, (u32 loc, u32 index, u32 value), { + printf("get_i32 %d,%d,%d\n", loc, index, value); + return value; +}); +IMPORT_IMPL(u32, Z_envZ_get_i64Z_iiiii, (u32 loc, u32 index, u32 low, u32 high), { + printf("get_i64 %d,%d,%d,%d\n", loc, index, low, high); + tempRet0 = high; + return low; +}); +IMPORT_IMPL(f32, Z_envZ_get_f32Z_fiif, (u32 loc, u32 index, f32 value), { + printf("get_f32 %d,%d,%f\n", loc, index, value); + return value; +}); +IMPORT_IMPL(f64, Z_envZ_get_f64Z_diid, (u32 loc, u32 index, f64 value), { + printf("get_f64 %d,%d,%f\n", loc, index, value); + return value; +}); +IMPORT_IMPL(u32, Z_envZ_set_i32Z_iiii, (u32 loc, u32 index, u32 value), { + printf("set_i32 %d,%d,%d\n", loc, index, value); + return value; +}); +IMPORT_IMPL(u32, Z_envZ_set_i64Z_iiiii, (u32 loc, u32 index, u32 low, u32 high), { + printf("set_i64 %d,%d,%d,%d\n", loc, index, low, high); + tempRet0 = high; + return low; +}); +IMPORT_IMPL(f32, Z_envZ_set_f32Z_fiif, (u32 loc, u32 index, f32 value), { + printf("set_f32 %d,%d,%f\n", loc, index, value); + return value; +}); +IMPORT_IMPL(f64, Z_envZ_set_f64Z_diid, (u32 loc, u32 index, f64 value), { + printf("set_f64 %d,%d,%f\n", loc, index, value); + return value; +}); +IMPORT_IMPL(u32, Z_envZ_load_ptrZ_iiiii, (u32 loc, u32 bytes, u32 offset, u32 ptr), { + printf("load_ptr %d,%d,%d,%d\n", loc, bytes, offset, ptr); + return ptr; +}); +IMPORT_IMPL(u32, Z_envZ_load_val_i32Z_iii, (u32 loc, u32 value), { + printf("load_val_i32 %d,%d\n", loc, value); + return value; +}); +IMPORT_IMPL(u32, Z_envZ_load_val_i64Z_iiii, (u32 loc, u32 low, u32 high), { + printf("load_val_i64 %d,%d,%d\n", loc, low, high); + tempRet0 = high; + return low; +}); +IMPORT_IMPL(u64, Z_envZ_load_val_i64Z_jij, (u32 loc, u64 value), { + printf("load_val_i64 %d,%d,%d\n", loc, (u32)value, (u32)(value >> 32)); + return value; +}); +IMPORT_IMPL(f32, Z_envZ_load_val_f32Z_fif, (u32 loc, f32 value), { + printf("load_val_f32 %d,%f\n", loc, value); + return value; +}); +IMPORT_IMPL(f64, Z_envZ_load_val_f64Z_did, (u32 loc, f64 value), { + printf("load_val_f64 %d,%f\n", loc, value); + return value; +}); +IMPORT_IMPL(u32, Z_envZ_store_ptrZ_iiiii, (u32 loc, u32 bytes, u32 offset, u32 ptr), { + printf("store_ptr %d,%d,%d,%d\n", loc, bytes, offset, ptr); + return ptr; +}); +IMPORT_IMPL(u32, Z_envZ_store_val_i32Z_iii, (u32 loc, u32 value), { + printf("store_val_i32 %d,%d\n", loc, value); + return value; +}); +IMPORT_IMPL(u32, Z_envZ_store_val_i64Z_iiii, (u32 loc, u32 low, u32 high), { + printf("store_val_i64 %d,%d,%d\n", loc, low, high); + tempRet0 = high; + return low; +}); +IMPORT_IMPL(u64, Z_envZ_store_val_i64Z_jij, (u32 loc, u64 value), { + printf("store_val_i64 %d,%d,%d\n", loc, (u32)value, (u32)(value >> 32)); + return value; +}); +IMPORT_IMPL(f32, Z_envZ_store_val_f32Z_fif, (u32 loc, f32 value), { + printf("store_val_f32 %d,%f\n", loc, value); + return value; +}); +IMPORT_IMPL(f64, Z_envZ_store_val_f64Z_did, (u32 loc, f64 value), { + printf("store_val_f64 %d,%f\n", loc, value); + return value; +}); diff --git a/tools/wasm2c/base.c b/tools/wasm2c/base.c new file mode 100644 index 0000000000000..2d52dd32e2f1f --- /dev/null +++ b/tools/wasm2c/base.c @@ -0,0 +1,206 @@ +/* + * Base of all support for wasm2c code. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wasm-rt.h" +#include "wasm-rt-impl.h" + +#define UNLIKELY(x) __builtin_expect(!!(x), 0) +#define LIKELY(x) __builtin_expect(!!(x), 1) + +#define TRAP(x) (wasm_rt_trap(WASM_RT_TRAP_##x), 0) + +#define MEMACCESS(addr) ((void*)&Z_memory->data[addr]) + +#undef MEMCHECK +#define MEMCHECK(a, t) \ + if (UNLIKELY((a) + sizeof(t) > Z_memory->size)) TRAP(OOB) + +#undef DEFINE_LOAD +#define DEFINE_LOAD(name, t1, t2, t3) \ + static inline t3 name(u64 addr) { \ + MEMCHECK(addr, t1); \ + t1 result; \ + memcpy(&result, MEMACCESS(addr), sizeof(t1)); \ + return (t3)(t2)result; \ + } + +#undef DEFINE_STORE +#define DEFINE_STORE(name, t1, t2) \ + static inline void name(u64 addr, t2 value) { \ + MEMCHECK(addr, t1); \ + t1 wrapped = (t1)value; \ + memcpy(MEMACCESS(addr), &wrapped, sizeof(t1)); \ + } + +DEFINE_LOAD(wasm_i32_load, u32, u32, u32); +DEFINE_LOAD(wasm_i64_load, u64, u64, u64); +DEFINE_LOAD(wasm_f32_load, f32, f32, f32); +DEFINE_LOAD(wasm_f64_load, f64, f64, f64); +DEFINE_LOAD(wasm_i32_load8_s, s8, s32, u32); +DEFINE_LOAD(wasm_i64_load8_s, s8, s64, u64); +DEFINE_LOAD(wasm_i32_load8_u, u8, u32, u32); +DEFINE_LOAD(wasm_i64_load8_u, u8, u64, u64); +DEFINE_LOAD(wasm_i32_load16_s, s16, s32, u32); +DEFINE_LOAD(wasm_i64_load16_s, s16, s64, u64); +DEFINE_LOAD(wasm_i32_load16_u, u16, u32, u32); +DEFINE_LOAD(wasm_i64_load16_u, u16, u64, u64); +DEFINE_LOAD(wasm_i64_load32_s, s32, s64, u64); +DEFINE_LOAD(wasm_i64_load32_u, u32, u64, u64); +DEFINE_STORE(wasm_i32_store, u32, u32); +DEFINE_STORE(wasm_i64_store, u64, u64); +DEFINE_STORE(wasm_f32_store, f32, f32); +DEFINE_STORE(wasm_f64_store, f64, f64); +DEFINE_STORE(wasm_i32_store8, u8, u32); +DEFINE_STORE(wasm_i32_store16, u16, u32); +DEFINE_STORE(wasm_i64_store8, u8, u64); +DEFINE_STORE(wasm_i64_store16, u16, u64); +DEFINE_STORE(wasm_i64_store32, u32, u64); + +// Imports + +#ifdef VERBOSE_LOGGING +#define VERBOSE_LOG(...) { printf(__VA_ARGS__); } +#else +#define VERBOSE_LOG(...) +#endif + +#define IMPORT_IMPL(ret, name, params, body) \ +static ret _##name params { \ + VERBOSE_LOG("[import: " #name "]\n"); \ + body \ +} \ +ret (*name) params = _##name; + +#define STUB_IMPORT_IMPL(ret, name, params, returncode) IMPORT_IMPL(ret, name, params, { return returncode; }); + +// Generic abort method for a runtime error in the runtime. + +static void abort_with_message(const char* message) { + fprintf(stderr, "%s\n", message); + abort(); +} + +// Maintain a stack of setjmps, each jump taking us back to the last invoke. + +#define MAX_SETJMP_STACK 1024 + +static jmp_buf setjmp_stack[MAX_SETJMP_STACK]; + +static u32 next_setjmp = 0; + +#define VOID_INVOKE_IMPL(name, typed_args, types, args, dyncall) \ +IMPORT_IMPL(void, name, typed_args, { \ + VERBOSE_LOG("invoke " #name " " #dyncall "\n"); \ + u32 sp = Z_stackSaveZ_iv(); \ + if (next_setjmp >= MAX_SETJMP_STACK) { \ + abort_with_message("too many nested setjmps"); \ + } \ + u32 id = next_setjmp++; \ + int result = setjmp(setjmp_stack[id]); \ + if (result == 0) { \ + (* dyncall) args; \ + /* if we got here, no longjmp or exception happened, we returned normally */ \ + } else { \ + /* A longjmp or an exception took us here. */ \ + Z_stackRestoreZ_vi(sp); \ + Z_setThrewZ_vii(1, 0); \ + } \ + next_setjmp--; \ +}); + +#define RETURNING_INVOKE_IMPL(ret, name, typed_args, types, args, dyncall) \ +IMPORT_IMPL(ret, name, typed_args, { \ + VERBOSE_LOG("invoke " #name " " #dyncall "\n"); \ + u32 sp = Z_stackSaveZ_iv(); \ + if (next_setjmp >= MAX_SETJMP_STACK) { \ + abort_with_message("too many nested setjmps"); \ + } \ + u32 id = next_setjmp++; \ + int result = setjmp(setjmp_stack[id]); \ + ret returned_value = 0; \ + if (result == 0) { \ + returned_value = (* dyncall) args; \ + /* if we got here, no longjmp or exception happened, we returned normally */ \ + } else { \ + /* A longjmp or an exception took us here. */ \ + Z_stackRestoreZ_vi(sp); \ + Z_setThrewZ_vii(1, 0); \ + } \ + next_setjmp--; \ + return returned_value; \ +}); + +// Declare an export that may be needed and may not be. For example if longjmp +// is included then we need setThrew, but we must declare setThrew so that +// the C compiler can build us without error if longjmp is not actually used. + +#define DECLARE_WEAK_EXPORT(ret, name, args) \ +__attribute__((weak)) \ +ret (*WASM_RT_ADD_PREFIX(name)) args = NULL; + +DECLARE_WEAK_EXPORT(void, Z_setThrewZ_vii, (u32, u32)); + +IMPORT_IMPL(void, Z_envZ_emscripten_longjmpZ_vii, (u32 buf, u32 value), { + if (next_setjmp == 0) { + abort_with_message("longjmp without setjmp"); + } + Z_setThrewZ_vii(buf, value ? value : 1); + longjmp(setjmp_stack[next_setjmp - 1], 1); +}); + +IMPORT_IMPL(void, Z_envZ_emscripten_notify_memory_growthZ_vi, (u32 size), {}); + +static u32 tempRet0 = 0; + +IMPORT_IMPL(u32, Z_envZ_getTempRet0Z_iv, (), { + return tempRet0; +}); + +IMPORT_IMPL(void, Z_envZ_setTempRet0Z_vi, (u32 x), { + tempRet0 = x; +}); + +// Shared OS support in both sandboxed and unsandboxed mode + +#define WASI_DEFAULT_ERROR 63 /* __WASI_ERRNO_PERM */ +#define WASI_EINVAL 28 + +// Syscalls return a negative error code +#define EM_EACCES -2 + +STUB_IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_fd_fdstat_getZ_iii, (u32 a, u32 b), WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_fd_syncZ_ii, (u32 a), WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, Z_envZ_dlopenZ_iii, (u32 a, u32 b), 1); +STUB_IMPORT_IMPL(u32, Z_envZ_dlcloseZ_ii, (u32 a), 1); +STUB_IMPORT_IMPL(u32, Z_envZ_dlsymZ_iii, (u32 a, u32 b), 0); +STUB_IMPORT_IMPL(u32, Z_envZ_dlerrorZ_iv, (), 0); +STUB_IMPORT_IMPL(u32, Z_envZ_signalZ_iii, (u32 a, u32 b), -1); +STUB_IMPORT_IMPL(u32, Z_envZ_systemZ_ii, (u32 a), -1); +STUB_IMPORT_IMPL(u32, Z_envZ_utimesZ_iii, (u32 a, u32 b), -1); +STUB_IMPORT_IMPL(u32, Z_envZ___sys_rmdirZ_ii, (u32 a), EM_EACCES); +STUB_IMPORT_IMPL(u32, Z_envZ___sys_renameZ_iii, (u32 a, u32 b), EM_EACCES); +STUB_IMPORT_IMPL(u32, Z_envZ___sys_lstat64Z_iii, (u32 a, u32 b), EM_EACCES); +STUB_IMPORT_IMPL(u32, Z_envZ___sys_dup3Z_iiii, (u32 a, u32 b, u32 c), EM_EACCES); +STUB_IMPORT_IMPL(u32, Z_envZ___sys_dup2Z_iii, (u32 a, u32 b), EM_EACCES); +STUB_IMPORT_IMPL(u32, Z_envZ___sys_getcwdZ_iii, (u32 a, u32 b), EM_EACCES); +STUB_IMPORT_IMPL(u32, Z_envZ___sys_ftruncate64Z_iiiii, (u32 a, u32 b, u32 c, u32 d), EM_EACCES); +STUB_IMPORT_IMPL(u32, Z_envZ_pthread_mutexattr_initZ_ii, (u32 a), 0); +STUB_IMPORT_IMPL(u32, Z_envZ_pthread_mutexattr_settypeZ_iii, (u32 a, u32 b), 0); +STUB_IMPORT_IMPL(u32, Z_envZ_pthread_mutexattr_destroyZ_ii, (u32 a), 0); +STUB_IMPORT_IMPL(u32, Z_envZ_pthread_createZ_iiiii, (u32 a, u32 b, u32 c, u32 d), -1); +STUB_IMPORT_IMPL(u32, Z_envZ_pthread_joinZ_iii, (u32 a, u32 b), -1); +STUB_IMPORT_IMPL(u32, Z_envZ___cxa_thread_atexitZ_iiii, (u32 a, u32 b, u32 c), -1); diff --git a/tools/wasm2c/main.c b/tools/wasm2c/main.c new file mode 100644 index 0000000000000..1ff1108468e26 --- /dev/null +++ b/tools/wasm2c/main.c @@ -0,0 +1,42 @@ +static int main_argc; +static char** main_argv; + +IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_args_sizes_getZ_iii, (u32 pargc, u32 pargv_buf_size), { + wasm_i32_store(pargc, main_argc); + u32 buf_size = 0; + for (u32 i = 0; i < main_argc; i++) { + buf_size += strlen(main_argv[i]) + 1; + } + wasm_i32_store(pargv_buf_size, buf_size); + return 0; +}); + +IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_args_getZ_iii, (u32 argv, u32 argv_buf), { + u32 buf_size = 0; + for (u32 i = 0; i < main_argc; i++) { + u32 ptr = argv_buf + buf_size; + wasm_i32_store(argv + i * 4, ptr); + char* arg = main_argv[i]; + strcpy(MEMACCESS(ptr), arg); + buf_size += strlen(arg) + 1; + } + return 0; +}); + +int main(int argc, char** argv) { + main_argc = argc; + main_argv = argv; + + init_fds(); + + init(); + + int trap_code; + if ((trap_code = setjmp(g_jmp_buf))) { + printf("[wasm trap %d, halting]\n", trap_code); + return 1; + } else { + Z__startZ_vv(); + } + return 0; +} diff --git a/tools/wasm2c/os.c b/tools/wasm2c/os.c new file mode 100644 index 0000000000000..43ad41a8717df --- /dev/null +++ b/tools/wasm2c/os.c @@ -0,0 +1,280 @@ +IMPORT_IMPL(void, Z_wasi_snapshot_preview1Z_proc_exitZ_vi, (u32 x), { + exit(x); +}); + +#define MAX_FDS 1024 + +static int wasm_fd_to_native[MAX_FDS]; + +static u32 next_wasm_fd; + +static void init_fds() { + wasm_fd_to_native[0] = STDIN_FILENO; + wasm_fd_to_native[1] = STDOUT_FILENO; + wasm_fd_to_native[2] = STDERR_FILENO; + next_wasm_fd = 3; +} + +static u32 get_or_allocate_wasm_fd(int nfd) { + // If the native fd is already mapped, return the same wasm fd for it. + for (int i = 0; i < next_wasm_fd; i++) { + if (wasm_fd_to_native[i] == nfd) { + return i; + } + } + if (next_wasm_fd >= MAX_FDS) { + abort_with_message("ran out of fds"); + } + u32 fd = next_wasm_fd; + wasm_fd_to_native[fd] = nfd; + next_wasm_fd++; + return fd; +} + +static int get_native_fd(u32 fd) { + if (fd >= MAX_FDS || fd >= next_wasm_fd) { + return -1; + } + return wasm_fd_to_native[fd]; +} + +IMPORT_IMPL(u32, Z_envZ___sys_openZ_iiii, (u32 path, u32 flags, u32 varargs), { + VERBOSE_LOG(" open: %s %d %d\n", MEMACCESS(path), flags, wasm_i32_load(varargs)); + int nfd = open(MEMACCESS(path), flags, wasm_i32_load(varargs)); + VERBOSE_LOG(" => native %d\n", nfd); + if (nfd >= 0) { + u32 fd = get_or_allocate_wasm_fd(nfd); + VERBOSE_LOG(" => wasm %d\n", fd); + return fd; + } + return -1; +}); + +IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_fd_writeZ_iiiii, (u32 fd, u32 iov, u32 iovcnt, u32 pnum), { + int nfd = get_native_fd(fd); + VERBOSE_LOG(" fd_write wasm %d => native %d\n", fd, nfd); + if (nfd < 0) { + return WASI_DEFAULT_ERROR; + } + u32 num = 0; + for (u32 i = 0; i < iovcnt; i++) { + u32 ptr = wasm_i32_load(iov + i * 8); + u32 len = wasm_i32_load(iov + i * 8 + 4); + VERBOSE_LOG(" chunk %d %d\n", ptr, len); + ssize_t result; + // Use stdio for stdout/stderr to avoid mixing a low-level write() with + // other logging code, which can change the order from the expected. + if (nfd == STDOUT_FILENO) { + result = fwrite(MEMACCESS(ptr), 1, len, stdout); + } else if (nfd == STDERR_FILENO) { + result = fwrite(MEMACCESS(ptr), 1, len, stderr); + } else { + result = write(nfd, MEMACCESS(ptr), len); + } + if (result < 0) { + VERBOSE_LOG(" error, %d %s\n", errno, strerror(errno)); + return WASI_DEFAULT_ERROR; + } + if (result != len) { + VERBOSE_LOG(" amount error, %ld %d\n", result, len); + return WASI_DEFAULT_ERROR; + } + num += len; + } + VERBOSE_LOG(" success: %d\n", num); + wasm_i32_store(pnum, num); + return 0; +}); + +IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_fd_readZ_iiiii, (u32 fd, u32 iov, u32 iovcnt, u32 pnum), { + int nfd = get_native_fd(fd); + VERBOSE_LOG(" fd_read wasm %d => native %d\n", fd, nfd); + if (nfd < 0) { + return WASI_DEFAULT_ERROR; + } + u32 num = 0; + for (u32 i = 0; i < iovcnt; i++) { + u32 ptr = wasm_i32_load(iov + i * 8); + u32 len = wasm_i32_load(iov + i * 8 + 4); + VERBOSE_LOG(" chunk %d %d\n", ptr, len); + ssize_t result = read(nfd, MEMACCESS(ptr), len); + if (result < 0) { + VERBOSE_LOG(" error, %d %s\n", errno, strerror(errno)); + return WASI_DEFAULT_ERROR; + } + num += result; + if (result != len) { + break; // nothing more to read + } + } + VERBOSE_LOG(" success: %d\n", num); + wasm_i32_store(pnum, num); + return 0; +}); + +IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_fd_closeZ_ii, (u32 fd), { + // TODO full file support + int nfd = get_native_fd(fd); + VERBOSE_LOG(" close wasm %d => native %d\n", fd, nfd); + if (nfd < 0) { + return WASI_DEFAULT_ERROR; + } + close(nfd); + return 0; +}); + +IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_environ_sizes_getZ_iii, (u32 pcount, u32 pbuf_size), { + // TODO: connect to actual env? + wasm_i32_store(pcount, 0); + wasm_i32_store(pbuf_size, 0); + return 0; +}); + +IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_environ_getZ_iii, (u32 __environ, u32 environ_buf), { + // TODO: connect to actual env? + return 0; +}); + +static int whence_to_native(u32 whence) { + if (whence == 0) return SEEK_SET; + if (whence == 1) return SEEK_CUR; + if (whence == 2) return SEEK_END; + return -1; +} + +IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_fd_seekZ_iijii, (u32 fd, u64 offset, u32 whence, u32 new_offset), { + int nfd = get_native_fd(fd); + int nwhence = whence_to_native(whence); + VERBOSE_LOG(" seek %d (=> native %d) %ld %d (=> %d) %d\n", fd, nfd, offset, whence, nwhence, new_offset); + if (nfd < 0) { + return WASI_DEFAULT_ERROR; + } + off_t off = lseek(nfd, offset, nwhence); + VERBOSE_LOG(" off: %ld\n", off); + if (off == (off_t)-1) { + VERBOSE_LOG(" error, %d %s\n", errno, strerror(errno)); + return WASI_DEFAULT_ERROR; + } + wasm_i64_store(new_offset, off); + return 0; +}); +IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_fd_seekZ_iiiiii, (u32 a, u32 b, u32 c, u32 d, u32 e), { + return Z_wasi_snapshot_preview1Z_fd_seekZ_iijii(a, b + (((u64)c) << 32), d, e); +}); + +// TODO: set errno in wasm for things that need it + +IMPORT_IMPL(u32, Z_envZ___sys_unlinkZ_ii, (u32 path), { + VERBOSE_LOG(" unlink %s\n", MEMACCESS(path)); + if (unlink(MEMACCESS(path))) { + VERBOSE_LOG(" error, %d %s\n", errno, strerror(errno)); + return EM_EACCES; + } + return 0; +}); + +static u32 do_stat(int nfd, u32 buf) { + struct stat nbuf; + if (fstat(nfd, &nbuf)) { + VERBOSE_LOG(" error, %d %s\n", errno, strerror(errno)); + return EM_EACCES; + } + VERBOSE_LOG(" success, size=%ld\n", nbuf.st_size); + wasm_i32_store(buf + 0, nbuf.st_dev); + wasm_i32_store(buf + 4, 0); + wasm_i32_store(buf + 8, nbuf.st_ino); + wasm_i32_store(buf + 12, nbuf.st_mode); + wasm_i32_store(buf + 16, nbuf.st_nlink); + wasm_i32_store(buf + 20, nbuf.st_uid); + wasm_i32_store(buf + 24, nbuf.st_gid); + wasm_i32_store(buf + 28, nbuf.st_rdev); + wasm_i32_store(buf + 32, 0); + wasm_i64_store(buf + 40, nbuf.st_size); + wasm_i32_store(buf + 48, nbuf.st_blksize); + wasm_i32_store(buf + 52, nbuf.st_blocks); + wasm_i32_store(buf + 56, nbuf.st_atim.tv_sec); + wasm_i32_store(buf + 60, nbuf.st_atim.tv_nsec); + wasm_i32_store(buf + 64, nbuf.st_mtim.tv_sec); + wasm_i32_store(buf + 68, nbuf.st_mtim.tv_nsec); + wasm_i32_store(buf + 72, nbuf.st_ctim.tv_sec); + wasm_i32_store(buf + 76, nbuf.st_ctim.tv_nsec); + wasm_i64_store(buf + 80, nbuf.st_ino); + return 0; +} + +IMPORT_IMPL(u32, Z_envZ___sys_fstat64Z_iii, (u32 fd, u32 buf), { + int nfd = get_native_fd(fd); + VERBOSE_LOG(" fstat64 %d (=> %d) %d\n", fd, nfd, buf); + if (nfd < 0) { + return EM_EACCES; + } + return do_stat(nfd, buf); +}); + +IMPORT_IMPL(u32, Z_envZ___sys_stat64Z_iii, (u32 path, u32 buf), { + VERBOSE_LOG(" stat64: %s\n", MEMACCESS(path)); + int nfd = open(MEMACCESS(path), O_RDONLY); // could be O_PATH on linux... + if (nfd < 0) { + VERBOSE_LOG(" error, %d %s\n", errno, strerror(errno)); + return EM_EACCES; + } + return do_stat(nfd, buf); +}); + +IMPORT_IMPL(u32, Z_envZ___sys_readZ_iiii, (u32 fd, u32 buf, u32 count), { + int nfd = get_native_fd(fd); + VERBOSE_LOG(" read %d (=> %d) %d %d\n", fd, nfd, buf, count); + if (nfd < 0) { + VERBOSE_LOG(" bad fd\n"); + return EM_EACCES; + } + ssize_t ret = read(nfd, MEMACCESS(buf), count); + VERBOSE_LOG(" native read: %ld\n", ret); + if (ret < 0) { + VERBOSE_LOG(" read error %d %s\n", errno, strerror(errno)); + return EM_EACCES; + } + return ret; +}); + +IMPORT_IMPL(u32, Z_envZ___sys_accessZ_iii, (u32 pathname, u32 mode), { + VERBOSE_LOG(" access: %s 0x%x\n", MEMACCESS(pathname), mode); + // TODO: sandboxing, convert mode + int result = access(MEMACCESS(pathname), mode); + if (result < 0) { + VERBOSE_LOG(" access error: %d %s\n", errno, strerror(errno)); + return EM_EACCES; + } + return 0; +}); + +#define WASM_CLOCK_REALTIME 0 +#define WASM_CLOCK_MONOTONIC 1 +#define WASM_CLOCK_PROCESS_CPUTIME 2 +#define WASM_CLOCK_CLOCK_THREAD_CPUTIME_ID 3 + +static int check_clock(u32 clock_id) { + return clock_id == WASM_CLOCK_REALTIME || clock_id == WASM_CLOCK_MONOTONIC || + clock_id == WASM_CLOCK_PROCESS_CPUTIME || clock_id == CLOCK_THREAD_CPUTIME_ID; +} + +IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_clock_time_getZ_iiji, (u32 clock_id, u64 max_lag, u32 out), { + if (!check_clock(clock_id)) { + return WASI_EINVAL; + } + // TODO: handle realtime vs monotonic etc. + // wasi expects a result in nanoseconds, and we know how to convert clock() + // to seconds, so compute from there + const double NSEC_PER_SEC = 1000.0 * 1000.0 * 1000.0; + wasm_i64_store(out, (u64)(clock() / (CLOCKS_PER_SEC / NSEC_PER_SEC))); + return 0; +}); + +IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_clock_res_getZ_iii, (u32 clock_id, u32 out), { + if (!check_clock(clock_id)) { + return WASI_EINVAL; + } + // TODO: handle realtime vs monotonic etc. For now just report "milliseconds". + wasm_i64_store(out, 1000 * 1000); + return 0; +}); diff --git a/tools/wasm2c/os_sandboxed.c b/tools/wasm2c/os_sandboxed.c new file mode 100644 index 0000000000000..728d7130cb006 --- /dev/null +++ b/tools/wasm2c/os_sandboxed.c @@ -0,0 +1,22 @@ +// Stubs for OS functions, for a sandboxed environment. Nothing is allowed +// exit the sandbox, calls to printf will fail, etc. + +IMPORT_IMPL(void, Z_wasi_snapshot_preview1Z_proc_exitZ_vi, (u32 x), { + abort_with_message("exit() called"); +}); + +STUB_IMPORT_IMPL(u32, Z_envZ___sys_openZ_iiii, (u32 path, u32 flags, u32 varargs), -1); +STUB_IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_fd_writeZ_iiiii, (u32 fd, u32 iov, u32 iovcnt, u32 pnum), WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_fd_readZ_iiiii, (u32 fd, u32 iov, u32 iovcnt, u32 pnum), WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_fd_closeZ_ii, (u32 fd), WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_environ_sizes_getZ_iii, (u32 pcount, u32 pbuf_size), WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_environ_getZ_iii, (u32 __environ, u32 environ_buf), WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_fd_seekZ_iijii, (u32 fd, u64 offset, u32 whence, u32 new_offset), WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_fd_seekZ_iiiiii, (u32 a, u32 b, u32 c, u32 d, u32 e), WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, Z_envZ___sys_unlinkZ_ii, (u32 path), WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, Z_envZ___sys_fstat64Z_iii, (u32 fd, u32 buf), EM_EACCES); +STUB_IMPORT_IMPL(u32, Z_envZ___sys_stat64Z_iii, (u32 path, u32 buf), EM_EACCES); +STUB_IMPORT_IMPL(u32, Z_envZ___sys_readZ_iiii, (u32 fd, u32 buf, u32 count), EM_EACCES); +STUB_IMPORT_IMPL(u32, Z_envZ___sys_accessZ_iii, (u32 pathname, u32 mode), EM_EACCES); +STUB_IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_clock_time_getZ_iiji, (u32 clock_id, u64 max_lag, u32 out), WASI_EINVAL); +STUB_IMPORT_IMPL(u32, Z_wasi_snapshot_preview1Z_clock_res_getZ_iii, (u32 clock_id, u32 out), WASI_EINVAL); diff --git a/tools/wasm2c/reactor.c b/tools/wasm2c/reactor.c new file mode 100644 index 0000000000000..134f1a6a3eff2 --- /dev/null +++ b/tools/wasm2c/reactor.c @@ -0,0 +1,14 @@ +// TODO: optional prefixing +void wasmbox_init(void) { + // Initialize wasm2c runtime. + init(); + + // Set up handling for a trap + int trap_code; + if ((trap_code = setjmp(g_jmp_buf))) { + printf("[wasm trap %d, halting]\n", trap_code); + abort(); + } else { + Z__initializeZ_vv(); + } +}