blob: 74085c521afd4d5f3af7234479b8f25f0056e783 [file] [log] [blame]
# 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)