|  | # 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 unsuffixed, check_call | 
|  | from tools.settings import settings | 
|  | from tools.utils import path_from_root, exit_with_error | 
|  | from tools import config | 
|  |  | 
|  |  | 
|  | # map an emscripten-style signature letter to a wasm2c C type | 
|  | 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)) | 
|  |  | 
|  |  | 
|  | # map a wasm2c C type to an emscripten-style signature letter | 
|  | def c_to_s(c): | 
|  | if c == 'WASM_RT_I32': | 
|  | return 'i' | 
|  | elif c == 'WASM_RT_I64': | 
|  | return 'j' | 
|  | elif c == 'WASM_RT_F32': | 
|  | return 'f' | 
|  | elif c == 'WASM_RT_F64': | 
|  | return 'd' | 
|  | else: | 
|  | exit_with_error('invalid wasm2c type element:' + str(c)) | 
|  |  | 
|  |  | 
|  | def get_func_types(code): | 
|  | ''' | 
|  | We look for this pattern: | 
|  |  | 
|  | static void init_func_types(void) { | 
|  | func_types[0] = wasm_rt_register_func_type(3, 1, WASM_RT_I32, WASM_RT_I32, WASM_RT_I32, WASM_RT_I32); | 
|  | func_types[1] = wasm_rt_register_func_type(1, 1, WASM_RT_I32, WASM_RT_I32); | 
|  | func_types[2] = wasm_rt_register_func_type(0, 0); | 
|  | } | 
|  |  | 
|  | We return a map of signatures names to their index. | 
|  | ''' | 
|  | init_func_types = re.search(r'static void init_func_types\(void\) {([^}]*)}', code) | 
|  | if not init_func_types: | 
|  | return {} | 
|  | ret = {} | 
|  | for entry in re.findall(r'func_types\[(\d+)\] = wasm_rt_register_func_type\((\d+), (\d+),? ?([^)]+)?\);', init_func_types[0]): | 
|  | index, params, results, types = entry | 
|  | index = int(index) | 
|  | params = int(params) | 
|  | results = int(results) | 
|  | types = types.split(', ') | 
|  | sig = '' | 
|  | for i in range(params): | 
|  | sig += c_to_s(types[i]) | 
|  | if results == 0: | 
|  | sig = 'v' + sig | 
|  | else: | 
|  | assert results == 1, 'no multivalue support' | 
|  | sig = c_to_s(types[-1]) + sig | 
|  | ret[sig] = index | 
|  | return ret | 
|  |  | 
|  |  | 
|  | def do_wasm2c(infile): | 
|  | assert settings.STANDALONE_WASM | 
|  | WASM2C = config.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] | 
|  | check_call(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): | 
|  | all_func_types = get_func_types(total) | 
|  |  | 
|  | def name(i): | 
|  | return 'a' + str(i) | 
|  |  | 
|  | wabt_sig = sig[0] + 'i' + sig[1:] | 
|  | typed_args = [s_to_c(sig[i]) + ' ' + name(i) for i in range(1, len(sig))] | 
|  | full_typed_args = ['u32 fptr'] + typed_args | 
|  | types = [s_to_c(sig[i]) for i in range(1, len(sig))] | 
|  | args = [name(i) for i in range(1, len(sig))] | 
|  | c_func_type = s_to_c(sig[0]) + ' (*)(' + (', '.join(types) if types else 'void') + ')' | 
|  | if sig not in all_func_types: | 
|  | exit_with_error('could not find signature ' + sig + ' in function types ' + str(all_func_types)) | 
|  | type_index = all_func_types[sig] | 
|  |  | 
|  | invokes.append(r''' | 
|  | IMPORT_IMPL(%(return_type)s, Z_envZ_invoke_%(sig)sZ_%(wabt_sig)s, (%(full_typed_args)s), { | 
|  | VERBOSE_LOG("invoke\n"); // waka | 
|  | u32 sp = WASM_RT_ADD_PREFIX(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]); | 
|  | %(declare_return)s | 
|  | if (result == 0) { | 
|  | %(receive)sCALL_INDIRECT(w2c___indirect_function_table, %(c_func_type)s, %(type_index)s, fptr %(args)s); | 
|  | /* if we got here, no longjmp or exception happened, we returned normally */ | 
|  | } else { | 
|  | /* A longjmp or an exception took us here. */ | 
|  | WASM_RT_ADD_PREFIX(Z_stackRestoreZ_vi)(sp); | 
|  | WASM_RT_ADD_PREFIX(Z_setThrewZ_vii)(1, 0); | 
|  | } | 
|  | next_setjmp--; | 
|  | %(return)s | 
|  | }); | 
|  | ''' % { | 
|  | 'return_type': s_to_c(sig[0]) if sig[0] != 'v' else 'void', | 
|  | 'sig': sig, | 
|  | 'wabt_sig': wabt_sig, | 
|  | 'full_typed_args': ', '.join(full_typed_args), | 
|  | 'type_index': type_index, | 
|  | 'c_func_type': c_func_type, | 
|  | 'args': (', ' + ', '.join(args)) if args else '', | 
|  | 'declare_return': (s_to_c(sig[0]) + ' returned_value = 0;') if sig[0] != 'v' else '', | 
|  | 'receive': 'returned_value = ' if sig[0] != 'v' else '', | 
|  | 'return': 'return returned_value;' if sig[0] != 'v' else '' | 
|  | }) | 
|  |  | 
|  | total += '\n'.join(invokes) | 
|  |  | 
|  | # adjust sandboxing | 
|  | TRAP_OOB = 'TRAP(OOB)' | 
|  | assert total.count(TRAP_OOB) == 2 | 
|  | if settings.WASM2C_SANDBOXING == 'full': | 
|  | pass # keep it | 
|  | elif settings.WASM2C_SANDBOXING == 'none': | 
|  | total = total.replace(TRAP_OOB, '{}') | 
|  | elif settings.WASM2C_SANDBOXING == 'mask': | 
|  | assert not settings.ALLOW_MEMORY_GROWTH | 
|  | assert (settings.INITIAL_MEMORY & (settings.INITIAL_MEMORY - 1)) == 0, 'poewr of 2' | 
|  | total = total.replace(TRAP_OOB, '{}') | 
|  | MEM_ACCESS = '[addr]' | 
|  | assert total.count(MEM_ACCESS) == 3, '2 from wasm2c, 1 from runtime' | 
|  | total = total.replace(MEM_ACCESS, '[addr & %d]' % (settings.INITIAL_MEMORY - 1)) | 
|  | else: | 
|  | exit_with_error('bad sandboxing') | 
|  |  | 
|  | # adjust prefixing: emit simple output that works with multiple libraries, | 
|  | # each compiled into its own single .c file, by adding 'static' in some places | 
|  | # TODO: decide on the proper pattern for this in an upstream discussion in | 
|  | #       wasm2c; another option would be to prefix all these things. | 
|  | for rep in [ | 
|  | 'uint32_t wasm_rt_register_func_type(', | 
|  | 'void wasm_rt_trap(', | 
|  | 'void wasm_rt_allocate_memory(', | 
|  | 'uint32_t wasm_rt_grow_memory(', | 
|  | 'void wasm_rt_allocate_table(', | 
|  | 'jmp_buf g_jmp_buf', | 
|  | 'uint32_t g_func_type_count', | 
|  | 'FuncType* g_func_types', | 
|  | 'uint32_t wasm_rt_call_stack_depth', | 
|  | 'uint32_t g_saved_call_stack_depth', | 
|  | ]: | 
|  | # remove 'extern' from declaration | 
|  | total = total.replace('extern ' + rep, rep) | 
|  | # add 'static' to implementation | 
|  | old = total | 
|  | total = total.replace(rep, 'static ' + rep) | 
|  | assert old != total, f'did not find "{rep}"' | 
|  |  | 
|  | # write out the final file | 
|  | with open(c_file, 'w') as out: | 
|  | out.write(total) |