1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
|
# 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 = ['wasm2c']
WASM2C_DIR = '/usr/src/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)
|