def create_gl(): prev_cxx = os.environ.get('EMMAKEN_CXX') if prev_cxx: os.environ['EMMAKEN_CXX'] = '' o = in_temp('gl.o') check_call([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'gl.c'), '-o', o]) if prev_cxx: os.environ['EMMAKEN_CXX'] = prev_cxx return o
def create_malloc(out_name): o = in_temp(out_name) cflags = ['-O2', '-fno-builtin'] if shared.Settings.USE_PTHREADS: cflags += ['-s', 'USE_PTHREADS=1'] if shared.Settings.EMSCRIPTEN_TRACING: cflags += ['--tracing'] if shared.Settings.SPLIT_MEMORY: cflags += ['-DMSPACES', '-DONLY_MSPACES'] if shared.Settings.DEBUG_LEVEL >= 3: cflags += ['-UNDEBUG', '-DDLMALLOC_DEBUG'] # TODO: consider adding -DEMMALLOC_DEBUG, but that is quite slow else: cflags += ['-DNDEBUG'] check_call([ shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', malloc_source()), '-o', o ] + cflags) if shared.Settings.SPLIT_MEMORY: split_malloc_o = in_temp('sm' + out_name) check_call([ shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'split_malloc.cpp'), '-o', split_malloc_o, '-O2' ]) lib = in_temp('lib' + out_name) shared.Building.link([o, split_malloc_o], lib) shutil.move(lib, o) return o
def build_native(subdir): shared.Building.ensure_no_emmake('We cannot build the native system library in "%s" when under the influence of emmake/emconfigure. To avoid this, create system dirs beforehand, so they are not auto-built on demand. For example, for binaryen, do "python embuilder.py build binaryen"' % subdir) old = os.getcwd() try: os.chdir(subdir) cmake_build_type = 'Release' # Configure check_call(['cmake', '-DCMAKE_BUILD_TYPE=' + cmake_build_type, '.']) # Check which CMake generator CMake used so we know which form to pass parameters to make/msbuild/etc. build tool. generator = re.search('CMAKE_GENERATOR:INTERNAL=(.*)$', open('CMakeCache.txt', 'r').read(), re.MULTILINE).group(1) # Make variants support '-jX' for number of cores to build, MSBuild does /maxcpucount:X num_cores = str(shared.Building.get_num_cores()) make_args = [] if 'Makefiles' in generator and 'NMake' not in generator: make_args = ['--', '-j', num_cores] elif 'Visual Studio' in generator: make_args = ['--config', cmake_build_type, '--', '/maxcpucount:' + num_cores] # Kick off the build. check_call(['cmake', '--build', '.'] + make_args) finally: os.chdir(old)
def run(): if len(sys.argv) < 2 or sys.argv[1] in ('--version', '--help'): print('''\ emconfigure is a helper for configure, setting various environment variables so that emcc etc. are used. Typical usage: emconfigure ./configure [FLAGS] (but you can run any command instead of configure)''', file=sys.stderr) return 1 args = sys.argv[1:] if 'cmake' in args: print( 'error: use `emcmake` rather then `emconfigure` for cmake projects', file=sys.stderr) return 1 env = building.get_building_env() # When we configure via a ./configure script, don't do config-time # compilation with emcc, but instead do builds natively with Clang. This # is a heuristic emulation that may or may not work. env['EMMAKEN_JUST_CONFIGURE'] = '1' print('configure: ' + shared.shlex_join(args), file=sys.stderr) try: shared.check_call(args, env=env) return 0 except CalledProcessError as e: return e.returncode
def create_dlmalloc(out_name, clflags): o = in_temp(out_name) check_call([ shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'dlmalloc.c'), '-o', o ] + clflags) return o
def create_gl(libname): # libname is ignored, this is just one .o file o = in_temp('gl.o') check_call([ shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'gl.c'), '-o', o ]) return o
def symbolize_address_dwarf(module, address): vma_adjust = get_codesec_offset(module) cmd = [ LLVM_SYMBOLIZER, '-e', module.filename, f'--adjust-vma={vma_adjust}', str(address) ] check_call(cmd)
def create_gl(): o = in_temp('gl.o') check_call([ shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'gl.c'), '-o', o ]) return o
def run(): if len(sys.argv) < 2 or sys.argv[1] in ('--version', '--help'): print('''\ emmake is a helper for make, setting various environment variables so that emcc etc. are used. Typical usage: emmake make [FLAGS] (but you can run any command instead of make)''', file=sys.stderr) return 1 args = sys.argv[1:] env = building.get_building_env() # On Windows prefer building with mingw32-make instead of make, if it exists. if utils.WINDOWS: if args[0] == 'make': mingw32_make = utils.which('mingw32-make') if mingw32_make: args[0] = mingw32_make if 'mingw32-make' in args[0]: env = building.remove_sh_exe_from_path(env) # On Windows, run the execution through shell to get PATH expansion and # executable extension lookup, e.g. 'sdl2-config' will match with # 'sdl2-config.bat' in PATH. print('make: ' + ' '.join(args), file=sys.stderr) try: shared.check_call(args, shell=utils.WINDOWS, env=env) return 0 except CalledProcessError as e: return e.returncode
def create_dlmalloc(out_name): o = in_temp(out_name) cflags = ['-O2'] if shared.Settings.USE_PTHREADS: cflags += ['-s', 'USE_PTHREADS=1'] if shared.Settings.EMSCRIPTEN_TRACING: cflags += ['--tracing'] if shared.Settings.SPLIT_MEMORY: cflags += ['-DMSPACES', '-DONLY_MSPACES'] if shared.Settings.DEBUG_LEVEL: cflags += ['-DDLMALLOC_DEBUG'] check_call([ shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'dlmalloc.c'), '-o', o ] + cflags) if shared.Settings.SPLIT_MEMORY: split_malloc_o = in_temp('sm' + out_name) check_call([ shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'split_malloc.cpp'), '-o', split_malloc_o, '-O2' ]) lib = in_temp('lib' + out_name) shared.Building.link([o, split_malloc_o], lib) shutil.move(lib, o) return o
def create_al(libname): # libname is ignored, this is just one .o file o = in_temp('al.o') check_call([ shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'al.c'), '-o', o, '-Os' ] + get_cflags()) return o
def create_dlmalloc_split(libname): dlmalloc_o = in_temp('dl' + libname) check_call([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'dlmalloc.c'), '-o', dlmalloc_o, '-O2', '-DMSPACES', '-DONLY_MSPACES']) split_malloc_o = in_temp('sm' + libname) check_call([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'split_malloc.cpp'), '-o', split_malloc_o, '-O2']) lib = in_temp(libname) shared.Building.link([dlmalloc_o, split_malloc_o], lib) return lib
def create_libc_extras( libname): # libname is ignored, this is just one .o file o = in_temp('libc_extras.o') check_call([ shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'libc', 'extras.c'), '-o', o ] + get_cflags()) return o
def run(): if len(sys.argv) < 2 or sys.argv[1] in ('--version', '--help'): print('''\ emcmake is a helper for cmake, setting various environment variables so that emcc etc. are used. Typical usage: emcmake cmake [FLAGS] ''', file=sys.stderr) return 1 args = sys.argv[1:] def has_substr(args, substr): return any(substr in s for s in args) # Append the Emscripten toolchain file if the user didn't specify one. if not has_substr(args, '-DCMAKE_TOOLCHAIN_FILE'): args.append('-DCMAKE_TOOLCHAIN_FILE=' + utils.path_from_root( 'cmake', 'Modules', 'Platform', 'Emscripten.cmake')) if not has_substr(args, '-DCMAKE_CROSSCOMPILING_EMULATOR'): node_js = config.NODE_JS[0] args.append(f'-DCMAKE_CROSSCOMPILING_EMULATOR={node_js}') # On Windows specify MinGW Makefiles or ninja if we have them and no other # toolchain was specified, to keep CMake from pulling in a native Visual # Studio, or Unix Makefiles. if utils.WINDOWS and '-G' not in args: if utils.which('mingw32-make'): args += ['-G', 'MinGW Makefiles'] elif utils.which('ninja'): args += ['-G', 'Ninja'] else: print( 'emcmake: no compatible cmake generator found; Please install ninja or mingw32-make, or specify a generator explicitly using -G', file=sys.stderr) return 1 # CMake has a requirement that it wants sh.exe off PATH if MinGW Makefiles # is being used. This happens quite often, so do this automatically on # behalf of the user. See # http://www.cmake.org/Wiki/CMake_MinGW_Compiler_Issues if utils.WINDOWS and 'MinGW Makefiles' in args: env = building.remove_sh_exe_from_path(os.environ) else: env = None print('configure: ' + shared.shlex_join(args), file=sys.stderr) try: shared.check_call(args, env=env) return 0 except CalledProcessError as e: return e.returncode
def create_malloc(out_name): o = in_temp(out_name) cflags = ['-O2', '-fno-builtin'] if shared.Settings.USE_PTHREADS: cflags += ['-s', 'USE_PTHREADS=1'] if shared.Settings.EMSCRIPTEN_TRACING: cflags += ['--tracing'] if shared.Settings.DEBUG_LEVEL >= 3: cflags += ['-UNDEBUG', '-DDLMALLOC_DEBUG'] # TODO: consider adding -DEMMALLOC_DEBUG, but that is quite slow else: cflags += ['-DNDEBUG'] check_call([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', malloc_source()), '-o', o] + cflags + get_cflags()) return o
def print_sizes(js_file): if not os.path.isfile(js_file): return error('Input JS file %s not foune' % js_file) if not js_file.endswith('.js'): return error('Input file %s does not have a JS extension' % js_file) basename = js_file[:-3] # Find the JS file size st = os.stat(js_file) js_size = st.st_size # Find the rest of the sizes wasm_file = basename + '.wasm' if not os.path.isfile(wasm_file): return error('Wasm file %s not found' % wasm_file) sizes = shared.check_call([LLVM_SIZE, '-format=sysv', wasm_file], stdout=subprocess.PIPE).stdout # llvm-size may emit some number of blank lines (after the total), ignore them lines = [line for line in sizes.splitlines() if line] # Last line is the total. Add the JS size. total = int(lines[-1].split()[-1]) total += js_size for line in lines[:-1]: print(line) print('JS\t\t%s\t0' % js_size) print('Total\t\t%s' % total)
def run(): if len(sys.argv) < 2 or sys.argv[1] in ('--version', '--help'): print('''\ emcmake is a helper for cmake, setting various environment variables so that emcc etc. are used. Typical usage: emcmake cmake [FLAGS] ''', file=sys.stderr) return 1 args = sys.argv[1:] def has_substr(args, substr): return any(substr in s for s in args) # Append the Emscripten toolchain file if the user didn't specify one. if not has_substr(args, '-DCMAKE_TOOLCHAIN_FILE'): args.append( '-DCMAKE_TOOLCHAIN_FILE=' + utils.path_from_root('cmake/Modules/Platform/Emscripten.cmake')) if not has_substr(args, '-DCMAKE_CROSSCOMPILING_EMULATOR'): node_js = config.NODE_JS[0] args.append(f'-DCMAKE_CROSSCOMPILING_EMULATOR={node_js}') # On Windows specify MinGW Makefiles or ninja if we have them and no other # toolchain was specified, to keep CMake from pulling in a native Visual # Studio, or Unix Makefiles. if utils.WINDOWS and '-G' not in args: if utils.which('mingw32-make'): args += ['-G', 'MinGW Makefiles'] elif utils.which('ninja'): args += ['-G', 'Ninja'] else: print( 'emcmake: no compatible cmake generator found; Please install ninja or mingw32-make, or specify a generator explicitly using -G', file=sys.stderr) return 1 print('configure: ' + shared.shlex_join(args), file=sys.stderr) try: shared.check_call(args) return 0 except CalledProcessError as e: return e.returncode
def run(): if len(sys.argv) < 2 or sys.argv[1] in ('--version', '--help'): print('''\ emcmake is a helper for cmake, setting various environment variables so that emcc etc. are used. Typical usage: emcmake cmake [FLAGS] ''', file=sys.stderr) return 1 args = sys.argv[1:] def has_substr(args, substr): return any(substr in s for s in args) # Append the Emscripten toolchain file if the user didn't specify one. if not has_substr(args, '-DCMAKE_TOOLCHAIN_FILE'): args.append('-DCMAKE_TOOLCHAIN_FILE=' + utils.path_from_root('cmake/Modules/Platform/Emscripten.cmake')) if not has_substr(args, '-DCMAKE_CROSSCOMPILING_EMULATOR'): node_js = config.NODE_JS[0] # In order to allow cmake to run code built with pthreads we need to pass some extra flags to node. # Note that we also need --experimental-wasm-bulk-memory which is true by default and hence not added here # See https://github.com/emscripten-core/emscripten/issues/15522 args.append(f'-DCMAKE_CROSSCOMPILING_EMULATOR={node_js};--experimental-wasm-threads') # On Windows specify MinGW Makefiles or ninja if we have them and no other # toolchain was specified, to keep CMake from pulling in a native Visual # Studio, or Unix Makefiles. if utils.WINDOWS and not any(arg.startswith('-G') for arg in args): if utils.which('mingw32-make'): args += ['-G', 'MinGW Makefiles'] elif utils.which('ninja'): args += ['-G', 'Ninja'] else: print('emcmake: no compatible cmake generator found; Please install ninja or mingw32-make, or specify a generator explicitly using -G', file=sys.stderr) return 1 print('configure: ' + shared.shlex_join(args), file=sys.stderr) try: shared.check_call(args) return 0 except CalledProcessError as e: return e.returncode
def create_malloc(out_name): o = in_temp(out_name) cflags = ['-O2', '-fno-builtin'] if shared.Settings.USE_PTHREADS: cflags += ['-s', 'USE_PTHREADS=1'] if shared.Settings.EMSCRIPTEN_TRACING: cflags += ['--tracing'] if shared.Settings.SPLIT_MEMORY: cflags += ['-DMSPACES', '-DONLY_MSPACES'] if shared.Settings.DEBUG_LEVEL >= 3: cflags += ['-UNDEBUG', '-DDLMALLOC_DEBUG'] # TODO: consider adding -DEMMALLOC_DEBUG, but that is quite slow else: cflags += ['-DNDEBUG'] check_call([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', malloc_source()), '-o', o] + cflags + get_cflags()) if shared.Settings.SPLIT_MEMORY: split_malloc_o = in_temp('sm' + out_name) check_call([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'split_malloc.cpp'), '-o', split_malloc_o, '-O2'] + get_cflags()) lib = in_temp('lib' + out_name) create_lib(lib, [o, split_malloc_o]) shutil.move(lib, o) return o
def run(): args = substitute_response_files(sys.argv) newargs = [shared.LLVM_AR] + args[1:] to_delete = [] # The 3 argmuent form of ar doesn't involve other files. For example # 'ar x libfoo.a'. if len(newargs) > 3: cmd = newargs[1] if 'r' in cmd or 'q' in cmd: # We are adding files to the archive. # Normally the output file is then arg 2, except in the case were the # a or b modifiers are used in which case its arg 3. if 'a' in cmd or 'b' in cmd: out_arg_index = 3 else: out_arg_index = 2 contents = set() if os.path.exists(newargs[out_arg_index]): cmd = [shared.LLVM_AR, 't', newargs[out_arg_index]] output = shared.check_call(cmd, stdout=shared.PIPE).stdout contents.update(output.split('\n')) # Add a hash to colliding basename, to make them unique. for j in range(out_arg_index + 1, len(newargs)): orig_name = newargs[j] full_name = os.path.abspath(orig_name) dirname = os.path.dirname(full_name) basename = os.path.basename(full_name) if basename not in contents: contents.add(basename) continue h = hashlib.md5(full_name.encode('utf-8')).hexdigest()[:8] parts = basename.split('.') parts[0] += '_' + h newname = '.'.join(parts) full_newname = os.path.join(dirname, newname) assert not os.path.exists(full_newname) try: shutil.copyfile(orig_name, full_newname) newargs[j] = full_newname to_delete.append(full_newname) contents.add(newname) except Exception: # it is ok to fail here, we just don't get hashing contents.add(basename) pass if shared.DEBUG: print('emar:', sys.argv, ' ==> ', newargs, file=sys.stderr) response_filename = create_response_file( newargs[3:], shared.get_emscripten_temp_dir()) to_delete += [response_filename] newargs = newargs[:3] + ['@' + response_filename] if shared.DEBUG: print('emar:', sys.argv, ' ==> ', newargs, file=sys.stderr) try: return shared.run_process(newargs, stdin=sys.stdin, check=False).returncode finally: for d in to_delete: shared.try_delete(d)
def create_al(libname): # libname is ignored, this is just one .o file o = in_temp('al.o') check_call([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'al.c'), '-o', o, '-Os'] + get_cflags()) return o
def create_libc_extras(libname): # libname is ignored, this is just one .o file o = in_temp('libc_extras.o') check_call([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'libc', 'extras.c'), '-o', o] + get_cflags()) return o
def create_gl(): o = in_temp('gl.o') check_call([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'gl.c'), '-o', o]) return o
def generate_object_file(data_files): embed_files = [f for f in data_files if f.mode == 'embed'] assert embed_files asm_file = shared.replace_suffix(options.obj_output, '.s') used = set() for f in embed_files: f.c_symbol_name = '__em_file_data_%s' % to_c_symbol(f.dstpath, used) with open(asm_file, 'w') as out: out.write( '# Emscripten embedded file data, generated by tools/file_packager.py\n' ) for f in embed_files: if DEBUG: err('embedding %s at %s' % (f.srcpath, f.dstpath)) size = os.path.getsize(f.srcpath) dstpath = to_asm_string(f.dstpath) srcpath = to_unix_path(f.srcpath) out.write( dedent(f''' .section .rodata.{f.c_symbol_name},"",@ # The name of file {f.c_symbol_name}_name: .asciz "{dstpath}" .size {f.c_symbol_name}_name, {len(dstpath)+1} # The size of the file followed by the content itself {f.c_symbol_name}: .incbin "{srcpath}" .size {f.c_symbol_name}, {size} ''')) out.write( dedent(''' # A list of triples of: # (file_name_ptr, file_data_size, file_data_ptr) # The list in null terminate with a single 0 .globl __emscripten_embedded_file_data .export_name __emscripten_embedded_file_data, __emscripten_embedded_file_data .section .rodata.__emscripten_embedded_file_data,"",@ __emscripten_embedded_file_data: .p2align 2 ''')) for f in embed_files: # The `.dc.a` directive gives us a pointer (address) sized entry. # See https://sourceware.org/binutils/docs/as/Dc.html out.write( dedent(f'''\ .dc.a {f.c_symbol_name}_name .int32 {os.path.getsize(f.srcpath)} .dc.a {f.c_symbol_name} ''')) ptr_size = 4 elem_size = (2 * ptr_size) + 4 total_size = len(embed_files) * elem_size + 4 out.write( dedent(f'''\ .dc.a 0 .size __emscripten_embedded_file_data, {total_size} ''')) shared.check_call([ shared.LLVM_MC, '-filetype=obj', '-triple=' + shared.get_llvm_target(), '-o', options.obj_output, asm_file ])
def create_dlmalloc(out_name, clflags): o = in_temp(out_name) check_call([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'dlmalloc.c'), '-o', o] + clflags) return o
def create_gl(libname): # libname is ignored, this is just one .o file o = in_temp('gl.o') check_call([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'gl.c'), '-o', o]) return o
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] 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 = 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. */ Z_stackRestoreZ_vi(sp); 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) # write out the final file with open(c_file, 'w') as out: out.write(total)
def generate_object_file(data_files): embed_files = [f for f in data_files if f.mode == 'embed'] assert embed_files asm_file = shared.replace_suffix(options.obj_output, '.s') used = set() for f in embed_files: f.c_symbol_name = '__em_file_data_%s' % to_c_symbol(f.dstpath, used) with open(asm_file, 'w') as out: out.write( '# Emscripten embedded file data, generated by tools/file_packager.py\n' ) for f in embed_files: if DEBUG: err('embedding %s at %s' % (f.srcpath, f.dstpath)) size = os.path.getsize(f.srcpath) dstpath = to_asm_string(f.dstpath) srcpath = to_unix_path(f.srcpath) out.write( dedent(f''' .section .rodata.{f.c_symbol_name},"",@ # The name of file {f.c_symbol_name}_name: .asciz "{dstpath}" .size {f.c_symbol_name}_name, {len(dstpath)+1} # The size of the file followed by the content itself {f.c_symbol_name}: .incbin "{srcpath}" .size {f.c_symbol_name}, {size} ''')) if options.wasm64: align = 3 ptr_type = 'i64' bits = 64 else: align = 2 ptr_type = 'i32' bits = 32 out.write( dedent(f''' .functype _emscripten_fs_load_embedded_files ({ptr_type}) -> () .section .text,"",@ init_file_data: .functype init_file_data () -> () global.get __emscripten_embedded_file_data@GOT call _emscripten_fs_load_embedded_files end_function # Run init_file_data on startup. # See system/lib/README.md for ordering of system constructors. .section .init_array.49,"",@ .p2align {align} .int{bits} init_file_data # A list of triples of: # (file_name_ptr, file_data_size, file_data_ptr) # The list in null terminate with a single 0 .globl __emscripten_embedded_file_data .export_name __emscripten_embedded_file_data, __emscripten_embedded_file_data .section .rodata.__emscripten_embedded_file_data,"",@ __emscripten_embedded_file_data: .p2align {align} ''')) for f in embed_files: # The `.dc.a` directive gives us a pointer (address) sized entry. # See https://sourceware.org/binutils/docs/as/Dc.html out.write( dedent(f'''\ .p2align %s .dc.a {f.c_symbol_name}_name .p2align %s .int32 {os.path.getsize(f.srcpath)} .p2align %s .dc.a {f.c_symbol_name} ''' % (align, align, align))) ptr_size = 4 elem_size = (2 * ptr_size) + 4 total_size = len(embed_files) * elem_size + 4 out.write( dedent(f'''\ .dc.a 0 .size __emscripten_embedded_file_data, {total_size} ''')) if options.wasm64: target = 'wasm64-unknown-emscripten' else: target = 'wasm32-unknown-emscripten' shared.check_call([ shared.LLVM_MC, '-filetype=obj', '-triple=' + target, '-o', options.obj_output, asm_file ])
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] 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): 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)
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(filename): nonlocal total with open(filename) as f: total += '// ' + filename + '\n' + f.read() + SEP # 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: bundle_file(os.path.join(header[0], header[1])) # add the wasm2c output bundle_file(c_file) # add the wasm2c runtime bundle_file(os.path.join(WASM2C_DIR, 'wasm-rt-impl.c')) # add the support code support_files = ['base.c'] if settings.AUTODEBUG: support_files.append('autodebug.c') 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.c') support_files.append('main.c') else: support_files.append('os_sandboxed.c') support_files.append('reactor.c') # 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: bundle_file(path_from_root(f'tools/wasm2c/{support_file}')) # 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)