New upstream version 2.0.25~dfsg
Jonas Smedegaard
2 years ago
174 | 174 | - run: |
175 | 175 | name: download firefox |
176 | 176 | command: | |
177 | # Temporarily pinned to beta rather then dev. | |
178 | # See https://github.com/emscripten-core/emscripten/issues/14401 | |
179 | wget -O ~/ff.tar.bz2 "https://download.mozilla.org/?product=firefox-beta-latest-ssl&os=linux64&lang=en-US" | |
177 | wget -O ~/ff.tar.bz2 "https://download.mozilla.org/?product=firefox-devedition-latest-ssl&os=linux64&lang=en-US" | |
180 | 178 | tar -C ~ -xf ~/ff.tar.bz2 |
181 | 179 | - run: |
182 | 180 | name: configure firefox |
186 | 184 | user_pref("gfx.offscreencanvas.enabled", true); |
187 | 185 | user_pref("javascript.options.shared_memory", true); |
188 | 186 | user_pref("dom.postMessage.sharedArrayBuffer.bypassCOOP_COEP.insecure.enabled", true); |
189 | EOF | |
190 | - run: | |
191 | name: configure openbox | |
192 | command: | | |
193 | # Set up X and Openbox config (if we move to a headless browser, this may not be needed). | |
194 | mkdir -p ~/.config/X11 | |
195 | cat > ~/.config/X11/xorg.conf \<<EOF | |
196 | Section "ServerLayout" | |
197 | Identifier "X.org Configured" | |
198 | Screen 0 "Screen0" 0 0 | |
199 | EndSection | |
200 | ||
201 | Section "Monitor" | |
202 | Identifier "Monitor0" | |
203 | HorizSync 72 | |
204 | Modeline "1920x1080@60" 144 1920 1920 1960 2000 1080 1080 1140 1200 | |
205 | EndSection | |
206 | ||
207 | Section "Device" | |
208 | Identifier "Card0" | |
209 | Driver "dummy" | |
210 | VideoRam 1048576 | |
211 | EndSection | |
212 | ||
213 | Section "Screen" | |
214 | Identifier "Screen0" | |
215 | Device "Card0" | |
216 | Monitor "Monitor0" | |
217 | DefaultDepth 24 | |
218 | SubSection "Display" | |
219 | Depth 24 | |
220 | Modes "1920x1080@60" | |
221 | EndSubSection | |
222 | EndSection | |
223 | EOF | |
224 | mkdir -p ~/.config/openbox | |
225 | echo "[ -f \"\$EXTRA_AUTOSTART\" ] && sh \"\$EXTRA_AUTOSTART\"" > ~/.config/openbox/autostart | |
226 | mkdir -p ~/.config/autostart | |
227 | cat > ~/.config/autostart/at-spi-dbus-bus.desktop \<<EOF | |
228 | [Desktop Entry] | |
229 | Type=Application | |
230 | Name=AT-SPI D-Bus Bus | |
231 | Hidden=true # do not auto-start AT-SPI to suppress one warning | |
232 | 187 | EOF |
233 | 188 | - run: |
234 | 189 | # browser.test_sdl2_mouse and/or SDL2 should be fixed. The case happens |
240 | 195 | # browser.test_webgl_offscreen_canvas_in_mainthread_after_pthread |
241 | 196 | # are crashing Firefox (bugzil.la/1281796). The former case is |
242 | 197 | # further blocked by issue #6897. |
243 | # TODO: use Firefox headless mode when https://bugzil.la/1375585 resolves | |
198 | # browser.test_sdl2_misc_main_module: Requires PIC version of libSDL | |
199 | # which is not included in emsdk (not compatible with FROZEN_CACHE). | |
244 | 200 | name: run tests |
245 | 201 | environment: |
246 | 202 | GALLIUM_DRIVER: softpipe # TODO: use the default llvmpipe when it supports more extensions |
203 | # TODO: Do GL testing when https://bugzil.la/1375585 (lack of WebGL | |
204 | # support in headless mode) resolves | |
205 | EMTEST_LACKS_GRAPHICS_HARDWARE: "1" | |
247 | 206 | EMTEST_LACKS_SOUND_HARDWARE: "1" |
248 | 207 | # OffscreenCanvas support is not yet done in Firefox. |
249 | 208 | EMTEST_LACKS_OFFSCREEN_CANVAS: "1" |
250 | 209 | EMTEST_DETECT_TEMPFILE_LEAKS: "0" |
251 | 210 | DISPLAY: ":0" |
252 | 211 | command: | |
253 | export EMTEST_BROWSER="$HOME/firefox/firefox -profile $HOME/tmp-firefox-profile/" | |
254 | # Start an X session. Openbox might be optional for now, but | |
255 | # an ICCCM/EWMH compliant window manager is potentially needed | |
256 | # for tests with fullscreen toggling (if we move to a headless | |
257 | # browser, this may not be needed eventually). | |
258 | TMPDIR=`mktemp -d` | |
259 | mkfifo $TMPDIR/fifo | |
260 | echo "echo -n > $TMPDIR/fifo" > $TMPDIR/autostart | |
261 | EXTRA_AUTOSTART=$TMPDIR/autostart startx /usr/bin/openbox-session -- $DISPLAY -config ~/.config/X11/xorg.conf -nolisten tcp & | |
262 | cat $TMPDIR/fifo > /dev/null # wait until $EXTRA_AUTOSTART is spawned, which indicates the end of Openbox initialization | |
263 | rm -r $TMPDIR | |
264 | tests/runner browser posixtest_browser.test_pthread_create_1_1 skip:browser.test_sdl2_mouse skip:browser.test_html5_webgl_create_context skip:browser.test_webgl_offscreen_canvas_in_pthread skip:browser.test_webgl_offscreen_canvas_in_mainthread_after_pthread skip:browser.test_glut_glutget | |
212 | export EMTEST_BROWSER="$HOME/firefox/firefox -headless -profile $HOME/tmp-firefox-profile/" | |
265 | 213 | echo "-----" |
266 | echo "Running emrun tests" | |
214 | echo "Running browser tests" | |
267 | 215 | echo "-----" |
268 | # test_no_browser seems to cause the xsession to go down when run in github CI. | |
269 | tests/runner emrun skip:emrun.test_no_browser | |
270 | openbox --exit | |
271 | wait || true # wait for startx to shutdown cleanly, or not | |
216 | tests/runner browser skip:browser.test_sdl2_mouse skip:browser.test_html5_webgl_create_context skip:browser.test_webgl_offscreen_canvas_in_pthread skip:browser.test_webgl_offscreen_canvas_in_mainthread_after_pthread skip:browser.test_glut_glutget skip:browser.test_sdl2_misc_main_module | |
217 | # posix and emrun suites are disabled because firefox errors on | |
218 | # "Firefox is already running, but is not responding." | |
219 | # TODO: find out a way to shut down and restart firefox | |
272 | 220 | test-chrome: |
273 | 221 | description: "Runs emscripten browser tests under chrome" |
274 | 222 | steps: |
302 | 250 | command: | |
303 | 251 | export EMTEST_BROWSER="/usr/bin/google-chrome $CHROME_FLAGS_BASE $CHROME_FLAGS_HEADLESS $CHROME_FLAGS_WASM $CHROME_FLAGS_NOCACHE" |
304 | 252 | # skip test_zzz_zzz_4gb_fail as it OOMs on the current bot |
305 | tests/runner browser posixtest_browser.test_pthread_create_1_1 skip:browser.test_zzz_zzz_4gb_fail | |
253 | # browser.test_sdl2_misc_main_module: Requires PIC version of libSDL | |
254 | # which is not included in emsdk (not compatible with FROZEN_CACHE). | |
255 | tests/runner browser posixtest_browser.test_pthread_create_1_1 skip:browser.test_zzz_zzz_4gb_fail skip:browser.test_sdl2_misc_main_module | |
306 | 256 | tests/runner emrun |
307 | 257 | test-sockets-chrome: |
308 | 258 | description: "Runs emscripten sockets tests under chrome" |
569 | 569 | * Bobbie Chen <bobbie.chen75@gmail.com> |
570 | 570 | * Nicholas Hollander <nhollander98@gmail.com> |
571 | 571 | * Camillo Lugaresi <camillol@google.com> (copyright owned by Google LLC) |
572 | * Chris Craig <emscripten@goldwave.com> | |
573 | * Le Yao <le.yao@intel.com> (copyright owned by Intel Corporation) | |
574 | * José Cadete <crudelios@gmail.com> |
17 | 17 | |
18 | 18 | See docs/process.md for more on how version tagging works. |
19 | 19 | |
20 | 2.0.24 | |
20 | 2.0.25 | |
21 | 21 | ------ |
22 | - Support for the 'shell' environment is now disabled by default. Running under | |
23 | `d8`, `js`, or `jsc` is not something that most emscripten users ever want to | |
24 | do, so including the support code is, more often than not, unnecessary. Users | |
25 | who want shell support can enable it by including 'shell' in `-s ENVIRONMENT` | |
26 | (#14535). | |
27 | - A new setting called `ALLOW_UNIMPLEMENTED_SYSCALLS` was added. This setting | |
28 | is enabled by default but, if disabled, will generate link-time errors if | |
29 | a program references an unimplemented syscall. This setting is disabled | |
30 | by default in `STRICT` mode. | |
31 | - By default (unless `EXIT_RUNTIME=1` is specified) emscripten programs running | |
32 | under node will no longer call `process.exit()` on `exit()`. Instead they | |
33 | will simply unwind the stack and return to the event loop, much like they do | |
34 | on the web. In many cases the node process will then exit naturally if there | |
35 | is nothing keeping the event loop going. | |
36 | Note for users of node + pthreads: Because of the way that threads are | |
37 | implemented under node multi-threaded programs now require `EXIT_RUNTIME=1` | |
38 | (or call `emscripten_force_exit`) in order to actually bring down the process. | |
39 | - Drop support for node versions older than v5.10.0. We now assume the | |
40 | existence of `Buffer.from` which was added in v5.10.0. If it turns out | |
41 | there is still a need to support these older node versions we can | |
42 | add a polyfil under LEGACY_VM_SUPPORT (#14447). | |
43 | ||
44 | 2.0.24 - 06/10/2021 | |
45 | ------------------- | |
22 | 46 | - Support `--preload-file` in Node.js. (#11785) |
23 | 47 | - System libraries are now passed to the linker internally via `-lfoo` rather |
24 | 48 | than using their full path. This is in line with how gcc and clang pass system |
48 | 72 | setting its value in `ENV` to `undefined`. This is useful for variables, such |
49 | 73 | as `LANG`, for which Emscripten normally provides a default value. |
50 | 74 | |
51 | 2.0.23 | |
52 | ------ | |
75 | 2.0.23 - 05/26/2021 | |
76 | ------------------- | |
53 | 77 | - libcxxabi updated to llvm-12. (#14288) |
54 | 78 | - libcxx updated to llvm-12. (#14249) |
55 | 79 | - compiler-rt updated to llvm-12. (#14280) |
102 | 102 | 3. Run `make install EMSCRIPTEN_SITE=\[path-to-a-checkout-of-the-site-repo\]` |
103 | 103 | 3. Go to the site repo, commit the changes, and push. |
104 | 104 | |
105 | You will need the specific sphinx version installed, which you can do using | |
106 | `pip3 install -r requirements-dev.txt` (depending on your system, you may then | |
107 | need to add `~/.local/bin` to your path, if pip installs to there). | |
105 | 108 | |
106 | 109 | |
107 | 110 | Updating the `emcc.py` help text |
114 | 117 | 1. In your emscripten repo checkout, enter `site`. |
115 | 118 | 2. Run `make clean` (without this, it may not emit the right output). |
116 | 119 | 2. Run `make text`. |
117 | 3. Add the changes to your PR. | |
120 | 3. Copy the output `build/text/docs/tools_reference/emcc.txt` to | |
121 | `../docs/emcc.txt` (both paths relative to the `site/` directory in | |
122 | emscripten that you entered in step 1), and add that change to your PR. | |
123 | ||
124 | See notes above on installing sphinx. | |
118 | 125 | |
119 | 126 | |
120 | 127 | [site_repo]: https://github.com/kripken/emscripten-site |
50 | 50 | from tools import webassembly |
51 | 51 | from tools import config |
52 | 52 | from tools.settings import settings, MEM_SIZE_SETTINGS, COMPILE_TIME_SETTINGS |
53 | from tools.utils import read_file, write_file, read_binary | |
53 | 54 | |
54 | 55 | logger = logging.getLogger('emcc') |
55 | 56 | |
157 | 158 | if not DEBUG: |
158 | 159 | return |
159 | 160 | if not final_js: |
160 | logger.debug('(not saving intermediate %s because not generating JS)' % name) | |
161 | logger.debug(f'(not saving intermediate {name} because not generating JS)') | |
161 | 162 | return |
162 | building.save_intermediate(final_js, name + '.' + suffix) | |
163 | building.save_intermediate(final_js, f'{name}.{suffix}') | |
163 | 164 | |
164 | 165 | |
165 | 166 | def save_intermediate_with_wasm(name, wasm_binary): |
269 | 270 | # Environment setting based on user input |
270 | 271 | environments = settings.ENVIRONMENT.split(',') |
271 | 272 | if any([x for x in environments if x not in VALID_ENVIRONMENTS]): |
272 | exit_with_error('Invalid environment specified in "ENVIRONMENT": ' + settings.ENVIRONMENT + '. Should be one of: ' + ','.join(VALID_ENVIRONMENTS)) | |
273 | exit_with_error(f'Invalid environment specified in "ENVIRONMENT": {settings.ENVIRONMENT}. Should be one of: {",".join(VALID_ENVIRONMENTS)}') | |
273 | 274 | |
274 | 275 | settings.ENVIRONMENT_MAY_BE_WEB = not settings.ENVIRONMENT or 'web' in environments |
275 | 276 | settings.ENVIRONMENT_MAY_BE_WEBVIEW = not settings.ENVIRONMENT or 'webview' in environments |
356 | 357 | filename = strip_prefix(value, '@') |
357 | 358 | if not os.path.exists(filename): |
358 | 359 | exit_with_error('%s: file not found parsing argument: %s=%s' % (filename, key, value)) |
359 | value = open(filename).read().strip() | |
360 | value = read_file(filename).strip() | |
360 | 361 | else: |
361 | 362 | value = value.replace('\\', '\\\\') |
362 | 363 | |
552 | 553 | def check_human_readable_list(items): |
553 | 554 | for item in items: |
554 | 555 | if item.count('(') != item.count(')'): |
555 | logger.warning('''emcc: ASYNCIFY list contains an item without balanced parentheses ("(", ")"):''') | |
556 | logger.warning(''' ''' + item) | |
557 | logger.warning('''This may indicate improper escaping that led to splitting inside your names.''') | |
558 | logger.warning('''Try to quote the entire argument, like this: -s 'ASYNCIFY_ONLY=["foo(int, char)", "bar"]' ''') | |
556 | logger.warning('emcc: ASYNCIFY list contains an item without balanced parentheses ("(", ")"):') | |
557 | logger.warning(' ' + item) | |
558 | logger.warning('This may indicate improper escaping that led to splitting inside your names.') | |
559 | logger.warning('Try using a response file. e.g: -sASYNCIFY_ONLY=@funcs.txt. The format is a simple') | |
560 | logger.warning('text file, one line per function.') | |
559 | 561 | break |
560 | 562 | |
561 | 563 | if settings.ASYNCIFY_REMOVE: |
588 | 590 | |
589 | 591 | |
590 | 592 | def make_js_executable(script): |
591 | src = open(script).read() | |
593 | src = read_file(script) | |
592 | 594 | cmd = shared.shlex_join(config.JS_ENGINE) |
593 | 595 | if not os.path.isabs(config.JS_ENGINE[0]): |
594 | 596 | # TODO: use whereis etc. And how about non-*NIX? |
945 | 947 | cmd = shared.shlex_join(args) |
946 | 948 | if EMCC_CFLAGS: |
947 | 949 | cmd += ' + ' + EMCC_CFLAGS |
948 | logger.warning('invocation: ' + cmd + ' (in ' + os.getcwd() + ')') | |
950 | logger.warning(f'invocation: {cmd} (in {os.getcwd()})') | |
949 | 951 | if EMCC_CFLAGS: |
950 | 952 | args.extend(shlex.split(EMCC_CFLAGS)) |
951 | 953 | |
1071 | 1073 | settings.limit_settings(None) |
1072 | 1074 | |
1073 | 1075 | if options.output_file and options.output_file.startswith('-'): |
1074 | exit_with_error('invalid output filename: `%s`' % options.output_file) | |
1076 | exit_with_error(f'invalid output filename: `{options.output_file}`') | |
1075 | 1077 | |
1076 | 1078 | target, wasm_target = phase_linker_setup(options, state, newargs, settings_map) |
1077 | 1079 | |
1079 | 1081 | linker_arguments = phase_calculate_linker_inputs(options, state, linker_inputs) |
1080 | 1082 | |
1081 | 1083 | if options.oformat == OFormat.OBJECT: |
1082 | with ToolchainProfiler.profile_block('linking to object file'): | |
1083 | logger.debug('link_to_object: ' + str(linker_arguments) + ' -> ' + target) | |
1084 | building.link_to_object(linker_arguments, target) | |
1085 | logger.debug('stopping after linking to object file') | |
1086 | return 0 | |
1084 | logger.debug(f'link_to_object: {linker_arguments} -> {target}') | |
1085 | building.link_to_object(linker_arguments, target) | |
1086 | logger.debug('stopping after linking to object file') | |
1087 | return 0 | |
1087 | 1088 | |
1088 | 1089 | phase_calculate_system_libraries(state, linker_arguments, linker_inputs, newargs) |
1089 | 1090 | |
1230 | 1231 | has_header_inputs = True |
1231 | 1232 | if file_suffix in STATICLIB_ENDINGS and not building.is_ar(arg): |
1232 | 1233 | if building.is_bitcode(arg): |
1233 | message = arg + ': File has a suffix of a static library ' + str(STATICLIB_ENDINGS) + ', but instead is an LLVM bitcode file! When linking LLVM bitcode files use .bc or .o.' | |
1234 | message = f'{arg}: File has a suffix of a static library {STATICLIB_ENDINGS}, but instead is an LLVM bitcode file! When linking LLVM bitcode files use .bc or .o.' | |
1234 | 1235 | else: |
1235 | 1236 | message = arg + ': Unknown format, not a static library!' |
1236 | 1237 | exit_with_error(message) |
1349 | 1350 | add_link_flag(state, sys.maxsize, f) |
1350 | 1351 | |
1351 | 1352 | if options.emrun: |
1352 | options.pre_js += open(shared.path_from_root('src', 'emrun_prejs.js')).read() + '\n' | |
1353 | options.post_js += open(shared.path_from_root('src', 'emrun_postjs.js')).read() + '\n' | |
1353 | options.pre_js += read_file(shared.path_from_root('src', 'emrun_prejs.js')) + '\n' | |
1354 | options.post_js += read_file(shared.path_from_root('src', 'emrun_postjs.js')) + '\n' | |
1354 | 1355 | # emrun mode waits on program exit |
1355 | 1356 | settings.EXIT_RUNTIME = 1 |
1356 | 1357 | |
1357 | 1358 | if options.cpu_profiler: |
1358 | options.post_js += open(shared.path_from_root('src', 'cpuprofiler.js')).read() + '\n' | |
1359 | options.post_js += read_file(shared.path_from_root('src', 'cpuprofiler.js')) + '\n' | |
1359 | 1360 | |
1360 | 1361 | if options.memory_profiler: |
1361 | 1362 | settings.MEMORYPROFILER = 1 |
1362 | 1363 | |
1363 | 1364 | if options.thread_profiler: |
1364 | options.post_js += open(shared.path_from_root('src', 'threadprofiler.js')).read() + '\n' | |
1365 | options.post_js += read_file(shared.path_from_root('src', 'threadprofiler.js')) + '\n' | |
1365 | 1366 | |
1366 | 1367 | if options.memory_init_file is None: |
1367 | 1368 | options.memory_init_file = settings.OPT_LEVEL >= 2 |
1535 | 1536 | default_setting('AUTO_ARCHIVE_INDEXES', 0) |
1536 | 1537 | default_setting('IGNORE_MISSING_MAIN', 0) |
1537 | 1538 | default_setting('DEFAULT_TO_CXX', 0) |
1539 | default_setting('ALLOW_UNIMPLEMENTED_SYSCALLS', 0) | |
1538 | 1540 | |
1539 | 1541 | # Default to TEXTDECODER=2 (always use TextDecoder to decode UTF-8 strings) |
1540 | 1542 | # in -Oz builds, since custom decoder for UTF-8 takes up space. |
1800 | 1802 | # In non-MINIMAL_RUNTIME, the core runtime depends on these functions to be present. (In MINIMAL_RUNTIME, they are |
1801 | 1803 | # no longer always bundled in) |
1802 | 1804 | settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [ |
1803 | '$keepRuntimeAlive', | |
1804 | 1805 | '$demangle', |
1805 | 1806 | '$demangleAll', |
1806 | 1807 | '$jsStackTrace', |
1918 | 1919 | include_and_export('establishStackSpace') |
1919 | 1920 | include_and_export('invokeEntryPoint') |
1920 | 1921 | if not settings.MINIMAL_RUNTIME: |
1921 | # noExitRuntime does not apply to MINIMAL_RUNTIME. | |
1922 | include_and_export('keepRuntimeAlive') | |
1922 | # keepRuntimeAlive does not apply to MINIMAL_RUNTIME. | |
1923 | settings.EXPORTED_RUNTIME_METHODS += ['keepRuntimeAlive'] | |
1923 | 1924 | |
1924 | 1925 | if settings.MODULARIZE: |
1925 | 1926 | if not settings.EXPORT_ES6 and settings.EXPORT_NAME == 'Module': |
2018 | 2019 | if settings.MODULARIZE and not (settings.EXPORT_ES6 and not settings.SINGLE_FILE) and \ |
2019 | 2020 | settings.EXPORT_NAME == 'Module' and options.oformat == OFormat.HTML and \ |
2020 | 2021 | (options.shell_path == shared.path_from_root('src', 'shell.html') or options.shell_path == shared.path_from_root('src', 'shell_minimal.html')): |
2021 | exit_with_error('Due to collision in variable name "Module", the shell file "' + options.shell_path + '" is not compatible with build options "-s MODULARIZE=1 -s EXPORT_NAME=Module". Either provide your own shell file, change the name of the export to something else to avoid the name collision. (see https://github.com/emscripten-core/emscripten/issues/7950 for details)') | |
2022 | exit_with_error(f'Due to collision in variable name "Module", the shell file "{options.shell_path}" is not compatible with build options "-s MODULARIZE=1 -s EXPORT_NAME=Module". Either provide your own shell file, change the name of the export to something else to avoid the name collision. (see https://github.com/emscripten-core/emscripten/issues/7950 for details)') | |
2022 | 2023 | |
2023 | 2024 | if settings.STANDALONE_WASM: |
2024 | 2025 | if settings.USE_PTHREADS: |
2338 | 2339 | headers = [header for _, header in input_files] |
2339 | 2340 | for header in headers: |
2340 | 2341 | if not header.endswith(HEADER_ENDINGS): |
2341 | exit_with_error('cannot mix precompile headers with non-header inputs: ' + str(headers) + ' : ' + header) | |
2342 | exit_with_error(f'cannot mix precompiled headers with non-header inputs: {headers} : {header}') | |
2342 | 2343 | cmd = get_clang_command(header) |
2343 | 2344 | if options.output_file: |
2344 | 2345 | cmd += ['-o', options.output_file] |
2345 | logger.debug("running (for precompiled headers): " + cmd[0] + ' ' + ' '.join(cmd[1:])) | |
2346 | logger.debug(f"running (for precompiled headers): {cmd[0]} {' '.join(cmd[1:])}") | |
2346 | 2347 | shared.check_call(cmd) |
2347 | 2348 | return [] |
2348 | 2349 | |
2378 | 2379 | if not state.has_dash_c: |
2379 | 2380 | cmd += ['-c'] |
2380 | 2381 | cmd += ['-o', output_file] |
2382 | if state.mode == Mode.COMPILE_AND_LINK and '-gsplit-dwarf' in newargs: | |
2383 | # When running in COMPILE_AND_LINK mode we compile to temporary location | |
2384 | # but we want the `.dwo` file to be generated in the current working directory, | |
2385 | # like it is under clang. We could avoid this hack if we use the clang driver | |
2386 | # to generate the temporary files, but that would also involve using the clang | |
2387 | # driver to perform linking which would be big change. | |
2388 | cmd += ['-Xclang', '-split-dwarf-file', '-Xclang', unsuffixed_basename(input_file) + '.dwo'] | |
2389 | cmd += ['-Xclang', '-split-dwarf-output', '-Xclang', unsuffixed_basename(input_file) + '.dwo'] | |
2381 | 2390 | shared.check_call(cmd) |
2382 | 2391 | if output_file not in ('-', os.devnull): |
2383 | 2392 | assert os.path.exists(output_file) |
2425 | 2434 | |
2426 | 2435 | @ToolchainProfiler.profile_block('link') |
2427 | 2436 | def phase_link(linker_arguments, wasm_target): |
2428 | logger.debug('linking: ' + str(linker_arguments)) | |
2437 | logger.debug(f'linking: {linker_arguments}') | |
2429 | 2438 | |
2430 | 2439 | # Make a final pass over settings.EXPORTED_FUNCTIONS to remove any |
2431 | 2440 | # duplication between functions added by the driver/libraries and function |
2520 | 2529 | file_args.append('--lz4') |
2521 | 2530 | if options.use_preload_plugins: |
2522 | 2531 | file_args.append('--use-preload-plugins') |
2532 | if not settings.ENVIRONMENT_MAY_BE_NODE: | |
2533 | file_args.append('--no-node') | |
2523 | 2534 | file_code = shared.check_call([shared.FILE_PACKAGER, unsuffixed(target) + '.data'] + file_args, stdout=PIPE).stdout |
2524 | 2535 | options.pre_js = js_manipulation.add_files_pre_js(options.pre_js, file_code) |
2525 | 2536 | |
2526 | 2537 | # Apply pre and postjs files |
2527 | 2538 | if final_js and (options.pre_js or options.post_js): |
2528 | 2539 | logger.debug('applying pre/postjses') |
2529 | src = open(final_js).read() | |
2540 | src = read_file(final_js) | |
2530 | 2541 | final_js += '.pp.js' |
2531 | 2542 | with open(final_js, 'w') as f: |
2532 | 2543 | # pre-js code goes right after the Module integration code (so it |
2552 | 2563 | # is set the memory initializer url. |
2553 | 2564 | global final_js |
2554 | 2565 | |
2555 | src = open(final_js).read() | |
2566 | src = read_file(final_js) | |
2556 | 2567 | src = do_replace(src, '// {{MEM_INITIALIZER}}', 'var memoryInitializer = "%s";' % os.path.basename(memfile)) |
2557 | open(final_js + '.mem.js', 'w').write(src) | |
2568 | write_file(final_js + '.mem.js', src) | |
2558 | 2569 | final_js += '.mem.js' |
2559 | 2570 | |
2560 | 2571 | |
2564 | 2575 | |
2565 | 2576 | # Remove some trivial whitespace |
2566 | 2577 | # TODO: do not run when compress has already been done on all parts of the code |
2567 | # src = open(final_js).read() | |
2578 | # src = read_file(final_js) | |
2568 | 2579 | # src = re.sub(r'\n+[ \n]*\n+', '\n', src) |
2569 | # open(final_js, 'w').write(src) | |
2580 | # write_file(final_js, src) | |
2570 | 2581 | |
2571 | 2582 | if settings.USE_PTHREADS: |
2572 | 2583 | target_dir = os.path.dirname(os.path.abspath(target)) |
2577 | 2588 | # Minify the worker.js file in optimized builds |
2578 | 2589 | if (settings.OPT_LEVEL >= 1 or settings.SHRINK_LEVEL >= 1) and not settings.DEBUG_LEVEL: |
2579 | 2590 | minified_worker = building.acorn_optimizer(worker_output, ['minifyWhitespace'], return_output=True) |
2580 | open(worker_output, 'w').write(minified_worker) | |
2591 | write_file(worker_output, minified_worker) | |
2581 | 2592 | |
2582 | 2593 | # track files that will need native eols |
2583 | 2594 | generated_text_files_with_native_eols = [] |
2598 | 2609 | # Unmangle previously mangled `import.meta` references in both main code and libraries. |
2599 | 2610 | # See also: `preprocess` in parseTools.js. |
2600 | 2611 | if settings.EXPORT_ES6 and settings.USE_ES6_IMPORT_META: |
2601 | src = open(final_js).read() | |
2612 | src = read_file(final_js) | |
2602 | 2613 | final_js += '.esmeta.js' |
2603 | with open(final_js, 'w') as f: | |
2604 | f.write(src.replace('EMSCRIPTEN$IMPORT$META', 'import.meta')) | |
2614 | write_file(final_js, src.replace('EMSCRIPTEN$IMPORT$META', 'import.meta')) | |
2605 | 2615 | save_intermediate('es6-import-meta') |
2606 | 2616 | |
2607 | 2617 | # Apply pre and postjs files |
2608 | 2618 | if options.extern_pre_js or options.extern_post_js: |
2609 | 2619 | logger.debug('applying extern pre/postjses') |
2610 | src = open(final_js).read() | |
2620 | src = read_file(final_js) | |
2611 | 2621 | final_js += '.epp.js' |
2612 | 2622 | with open(final_js, 'w') as f: |
2613 | 2623 | f.write(fix_windows_newlines(options.extern_pre_js)) |
2752 | 2762 | elif check_arg('--js-transform'): |
2753 | 2763 | options.js_transform = consume_arg() |
2754 | 2764 | elif check_arg('--pre-js'): |
2755 | options.pre_js += open(consume_arg_file()).read() + '\n' | |
2765 | options.pre_js += read_file(consume_arg_file()) + '\n' | |
2756 | 2766 | elif check_arg('--post-js'): |
2757 | options.post_js += open(consume_arg_file()).read() + '\n' | |
2767 | options.post_js += read_file(consume_arg_file()) + '\n' | |
2758 | 2768 | elif check_arg('--extern-pre-js'): |
2759 | options.extern_pre_js += open(consume_arg_file()).read() + '\n' | |
2769 | options.extern_pre_js += read_file(consume_arg_file()) + '\n' | |
2760 | 2770 | elif check_arg('--extern-post-js'): |
2761 | options.extern_post_js += open(consume_arg_file()).read() + '\n' | |
2771 | options.extern_post_js += read_file(consume_arg_file()) + '\n' | |
2762 | 2772 | elif check_arg('--compiler-wrapper'): |
2763 | 2773 | config.COMPILER_WRAPPER = consume_arg() |
2764 | 2774 | elif check_flag('--post-link'): |
2897 | 2907 | # that are e.g. x86 specific and non-portable. The emscripten bundled |
2898 | 2908 | # headers are modified to be portable, local system ones are generally not. |
2899 | 2909 | diagnostics.warning( |
2900 | 'absolute-paths', '-I or -L of an absolute path "' + arg + | |
2901 | '" encountered. If this is to a local system header/library, it may ' | |
2910 | 'absolute-paths', f'-I or -L of an absolute path "{arg}" ' | |
2911 | 'encountered. If this is to a local system header/library, it may ' | |
2902 | 2912 | 'cause problems (local system files make sense for compiling natively ' |
2903 | 2913 | 'on your system, but not necessarily to JavaScript).') |
2904 | 2914 | elif check_flag('--emrun'): |
2931 | 2941 | elif style.lower() == 'linux': |
2932 | 2942 | options.output_eol = '\n' |
2933 | 2943 | else: |
2934 | exit_with_error('Invalid value "' + style + '" to --output_eol!') | |
2944 | exit_with_error(f'Invalid value "{style}" to --output_eol!') | |
2935 | 2945 | elif check_arg('--generate-config'): |
2936 | 2946 | optarg = consume_arg() |
2937 | 2947 | path = os.path.expanduser(optarg) |
2938 | 2948 | if os.path.exists(path): |
2939 | exit_with_error('File ' + optarg + ' passed to --generate-config already exists!') | |
2949 | exit_with_error(f'File {optarg} passed to --generate-config already exists!') | |
2940 | 2950 | else: |
2941 | 2951 | config.generate_config(optarg) |
2942 | 2952 | should_exit = True |
2957 | 2967 | else: |
2958 | 2968 | value = '1' |
2959 | 2969 | if key in settings.keys(): |
2960 | exit_with_error(arg + ': cannot change built-in settings values with a -jsD directive. Pass -s ' + key + '=' + value + ' instead!') | |
2970 | exit_with_error(f'{arg}: cannot change built-in settings values with a -jsD directive. Pass -s {key}={value} instead!') | |
2961 | 2971 | user_js_defines += [(key, value)] |
2962 | 2972 | newargs[i] = '' |
2963 | 2973 | elif check_flag('-shared'): |
3176 | 3186 | |
3177 | 3187 | # replace placeholder strings with correct subresource locations |
3178 | 3188 | if final_js and settings.SINGLE_FILE and not settings.WASM2JS: |
3179 | js = open(final_js).read() | |
3189 | js = read_file(final_js) | |
3180 | 3190 | |
3181 | 3191 | if settings.MINIMAL_RUNTIME: |
3182 | js = do_replace(js, '<<< WASM_BINARY_DATA >>>', base64_encode(open(wasm_target, 'rb').read())) | |
3192 | js = do_replace(js, '<<< WASM_BINARY_DATA >>>', base64_encode(read_binary(wasm_target))) | |
3183 | 3193 | else: |
3184 | 3194 | js = do_replace(js, '<<< WASM_BINARY_FILE >>>', shared.JS.get_subresource_location(wasm_target)) |
3185 | 3195 | shared.try_delete(wasm_target) |
3189 | 3199 | |
3190 | 3200 | def modularize(): |
3191 | 3201 | global final_js |
3192 | logger.debug('Modularizing, assigning to var ' + settings.EXPORT_NAME) | |
3193 | src = open(final_js).read() | |
3202 | logger.debug(f'Modularizing, assigning to var {settings.EXPORT_NAME}') | |
3203 | src = read_file(final_js) | |
3194 | 3204 | |
3195 | 3205 | return_value = settings.EXPORT_NAME |
3196 | 3206 | if settings.WASM_ASYNC_COMPILATION: |
3265 | 3275 | |
3266 | 3276 | def module_export_name_substitution(): |
3267 | 3277 | global final_js |
3268 | logger.debug('Private module export name substitution with ' + settings.EXPORT_NAME) | |
3278 | logger.debug(f'Private module export name substitution with {settings.EXPORT_NAME}') | |
3269 | 3279 | with open(final_js) as f: |
3270 | 3280 | src = f.read() |
3271 | 3281 | final_js += '.module_export_name_substitution.js' |
3385 | 3395 | if settings.SINGLE_FILE: |
3386 | 3396 | js_contents = script.inline or '' |
3387 | 3397 | if script.src: |
3388 | js_contents += open(js_target).read() | |
3398 | js_contents += read_file(js_target) | |
3389 | 3399 | shared.try_delete(js_target) |
3390 | 3400 | script.src = None |
3391 | 3401 | script.inline = js_contents |
3434 | 3444 | # '--remove-empty-elements': removes all elements with empty contents. |
3435 | 3445 | # (Breaks at least browser.test_asm_swapping) |
3436 | 3446 | |
3437 | logger.debug('minifying HTML file ' + filename) | |
3447 | logger.debug(f'minifying HTML file {filename}') | |
3438 | 3448 | size_before = os.path.getsize(filename) |
3439 | 3449 | start_time = time.time() |
3440 | 3450 | shared.check_call(shared.get_npm_cmd('html-minifier-terser') + [filename, '-o', filename] + opts, env=shared.env_with_node_in_path()) |
3442 | 3452 | elapsed_time = time.time() - start_time |
3443 | 3453 | size_after = os.path.getsize(filename) |
3444 | 3454 | delta = size_after - size_before |
3445 | logger.debug('HTML minification took {:.2f}'.format(elapsed_time) + ' seconds, and shrunk size of ' + filename + ' from ' + str(size_before) + ' to ' + str(size_after) + ' bytes, delta=' + str(delta) + ' ({:+.2f}%)'.format(delta * 100.0 / size_before)) | |
3455 | logger.debug(f'HTML minification took {elapsed_time:.2f} seconds, and shrunk size of {filename} from {size_before} to {size_after} bytes, delta={delta} ({delta * 100.0 / size_before:+.2f}%)') | |
3446 | 3456 | |
3447 | 3457 | |
3448 | 3458 | def generate_html(target, options, js_target, target_basename, |
3478 | 3488 | proxy_worker_filename = (settings.PROXY_TO_WORKER_FILENAME or worker_target_basename) + '.js' |
3479 | 3489 | |
3480 | 3490 | target_contents = worker_js_script(proxy_worker_filename) |
3481 | open(target, 'w').write(target_contents) | |
3491 | write_file(target, target_contents) | |
3482 | 3492 | |
3483 | 3493 | |
3484 | 3494 | def worker_js_script(proxy_worker_filename): |
3485 | web_gl_client_src = open(shared.path_from_root('src', 'webGLClient.js')).read() | |
3486 | idb_store_src = open(shared.path_from_root('src', 'IDBStore.js')).read() | |
3487 | proxy_client_src = open(shared.path_from_root('src', 'proxyClient.js')).read() | |
3495 | web_gl_client_src = read_file(shared.path_from_root('src', 'webGLClient.js')) | |
3496 | idb_store_src = read_file(shared.path_from_root('src', 'IDBStore.js')) | |
3497 | proxy_client_src = read_file(shared.path_from_root('src', 'proxyClient.js')) | |
3488 | 3498 | proxy_client_src = do_replace(proxy_client_src, '{{{ filename }}}', proxy_worker_filename) |
3489 | 3499 | proxy_client_src = do_replace(proxy_client_src, '{{{ IDBStore.js }}}', idb_store_src) |
3490 | 3500 | return web_gl_client_src + '\n' + proxy_client_src |
690 | 690 | except OSError: |
691 | 691 | pass |
692 | 692 | filename = os.path.join(dump_out_directory, os.path.normpath(filename)) |
693 | open(filename, 'wb').write(data) | |
693 | with open(filename, 'wb') as fh: | |
694 | fh.write(data) | |
694 | 695 | logi('Wrote ' + str(len(data)) + ' bytes to file "' + filename + '".') |
695 | 696 | have_received_messages = True |
696 | 697 | elif path == '/system_info': |
1059 | 1060 | model = check_output(cmd) |
1060 | 1061 | model = re.search('<configCode>(.*)</configCode>', model) |
1061 | 1062 | model = model.group(1).strip() |
1062 | open(os.path.join(os.getenv("HOME"), '.emrun.hwmodel.cached'), 'w').write(model) # Cache the hardware model to disk | |
1063 | with open(os.path.join(os.getenv("HOME"), '.emrun.hwmodel.cached'), 'w') as fh: | |
1064 | fh.write(model) # Cache the hardware model to disk | |
1063 | 1065 | return model |
1064 | 1066 | except Exception: |
1065 | 1067 | hwmodel = check_output(['sysctl', 'hw.model']) |
1384 | 1386 | return info.strip() |
1385 | 1387 | else: |
1386 | 1388 | try: |
1387 | unique_system_id = open(os.path.expanduser('~/.emrun.generated.guid'), 'r').read().strip() | |
1389 | with open(os.path.expanduser('~/.emrun.generated.guid')) as fh: | |
1390 | unique_system_id = fh.read().strip() | |
1388 | 1391 | except Exception: |
1389 | 1392 | import uuid |
1390 | 1393 | unique_system_id = str(uuid.uuid4()) |
203 | 203 | # standalone mode doesn't use main, and it always reports missing entry point at link time. |
204 | 204 | # In this mode we never expect _main in the export list. |
205 | 205 | return |
206 | ||
207 | # PROXY_TO_PTHREAD only makes sense with a main(), so error if one is | |
208 | # missing. note that when main() might arrive from another module we cannot | |
209 | # error here. | |
210 | if settings.PROXY_TO_PTHREAD and '_main' not in defined_symbols and \ | |
211 | not settings.RELOCATABLE: | |
212 | exit_with_error('PROXY_TO_PTHREAD proxies main() for you, but no main exists') | |
206 | 213 | |
207 | 214 | if settings.IGNORE_MISSING_MAIN: |
208 | 215 | # The default mode for emscripten is to ignore the missing main function allowing |
17 | 17 | |
18 | 18 | import sys |
19 | 19 | import os |
20 | from pathlib import Path | |
20 | 21 | |
21 | 22 | |
22 | 23 | # At the top. #HamishW https://pypi.python.org/pypi/sphinx-bootstrap-theme/ ... |
92 | 93 | # |version| and |release|, also used in various other places throughout the |
93 | 94 | # built documents. |
94 | 95 | # |
95 | ||
96 | emscripten_version = open(os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'emscripten-version.txt'))).read().strip().replace('"', '') | |
96 | emscripten_version = Path(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'emscripten-version.txt').resolve().read_text().strip().replace('"', '') | |
97 | 97 | |
98 | 98 | # The short X.Y version. |
99 | 99 | version = emscripten_version[:emscripten_version.rindex('.')] |
11 | 11 | |
12 | 12 | .. note:: |
13 | 13 | |
14 | In order to use the Fetch API, you would need to compile your code with **-s | |
15 | FETCH=1**. | |
14 | In order to use the Fetch API, you would need to compile your code with ``-s | |
15 | FETCH=1``. | |
16 | 16 | |
17 | 17 | Introduction |
18 | 18 | ============ |
192 | 192 | restrictions, depending on which Emscripten build mode (linker flags) is used: |
193 | 193 | |
194 | 194 | - **No flags**: Only asynchronous Fetch operations are available. |
195 | - **--proxy-to-worker**: Synchronous Fetch operations are allowed for fetches | |
195 | - ``--proxy-to-worker``: Synchronous Fetch operations are allowed for fetches | |
196 | 196 | that only do an XHR but do not interact with IndexedDB. |
197 | - **-s USE_PTHREADS=1**: Synchronous Fetch operations are available on | |
197 | - ``-s USE_PTHREADS=1``: Synchronous Fetch operations are available on | |
198 | 198 | pthreads, but not on the main thread. |
199 | - **--proxy-to-worker** + **-s USE_PTHREADS=1**: Synchronous Fetch operations | |
199 | - ``--proxy-to-worker`` + ``-s USE_PTHREADS=1``: Synchronous Fetch operations | |
200 | 200 | are available both on the main thread and pthreads. |
201 | 201 | |
202 | 202 | Waitable Fetches |
240 | 240 | |
241 | 241 | Waitable fetches are available only in certain build modes: |
242 | 242 | |
243 | - **No flags** or **--proxy-to-worker**: Waitable fetches are not available. | |
244 | - **-s USE_PTHREADS=1**: Waitable fetches are available on pthreads, but not | |
243 | - **No flags** or ``--proxy-to-worker``: Waitable fetches are not available. | |
244 | - ``-s USE_PTHREADS=1``: Waitable fetches are available on pthreads, but not | |
245 | 245 | on the main thread. |
246 | - **--proxy-to-worker** + **-s USE_PTHREADS=1**: Waitable fetches are | |
246 | - ``--proxy-to-worker`` + ``-s USE_PTHREADS=1``: Waitable fetches are | |
247 | 247 | available on all threads. |
248 | 248 | |
249 | 249 | Tracking Progress |
158 | 158 | |
159 | 159 | - Simulate lack of any special APIs that the page might need, e.g. Gamepad, Acceleration or Touch Events, and make sure that appropriate error flow is handled in those cases as well. |
160 | 160 | |
161 | If you have good tips or suggestsions to share, please help improve this guide by posting feedback to the `Emscripten bug tracker <https://github.com/emscripten-core/emscripten/issues>`_ or the `emscripten-discuss <https://groups.google.com/forum/#!forum/emscripten-discuss>`_ mailing list. | |
161 | If you have good tips or suggestions to share, please help improve this guide by posting feedback to the `Emscripten bug tracker <https://github.com/emscripten-core/emscripten/issues>`_ or the `emscripten-discuss <https://groups.google.com/forum/#!forum/emscripten-discuss>`_ mailing list. |
176 | 176 | EMCC_DEBUG=2 tests/runner test_hello_world |
177 | 177 | |
178 | 178 | |
179 | You can also specify ``EMTEST_SAVE_DIR=1`` in the environment to save the | |
180 | temporary directory that the test runner uses into **/tmp/emscripten_test/**. | |
181 | This is a test suite-specific feature, and is useful for inspecting test | |
182 | outputs as well as temporary files generated by the test. By default, | |
183 | the temporary directory will be cleaned between each test run, but setting | |
184 | ``EMTEST_SAVE_DIR=2`` will preserve the directory even when a new test is | |
185 | started. | |
179 | You can also specify ``--save-dir`` to save the temporary directory that the | |
180 | test runner uses into **/tmp/emscripten_test/**. This is a test suite-specific | |
181 | feature, and is useful for inspecting test outputs as well as temporary files | |
182 | generated by the test. By default, the temporary directory will be cleaned | |
183 | between each test run, but you can add ``--no-clean`` to avoid this. | |
186 | 184 | |
187 | 185 | |
188 | 186 | The :ref:`Debugging` topic provides more guidance on how to debug Emscripten-generated code. |
234 | 234 | `this MDN example <https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#Basic_example>`_ |
235 | 235 | ). |
236 | 236 | |
237 | Note that we must propagate the value returned from ``handleSleep()``. The calling C code then | |
237 | Note that we must propagate the value returned from ``handleAsync()``. The calling C code then | |
238 | 238 | gets it normally, after the Promise completes. |
239 | 239 | |
240 | 240 | If you're using the ``handleSleep`` API, the value needs to be also passed to the ``wakeUp`` callback, instead of being returned from our handler: |
59 | 59 | `Binaryen <https://github.com/WebAssembly/binaryen/>`_ is a WebAssembly compiler toolkit, which Emscripten uses to modify and optimize wasm. |
60 | 60 | |
61 | 61 | node.js |
62 | **Node.js** is a cross-platform runtime environment for server-side and networking applications written in JavaScript. Essentially it allows you to run JavaScript applications outside of a browser context. | |
62 | `Node.js <https://nodejs.org/en/>`_ is a cross-platform runtime environment for server-side and networking applications written in JavaScript. Essentially it allows you to run JavaScript applications outside of a browser context. | |
63 | 63 | |
64 | 64 | Python |
65 | 65 | Python is a scripting language used to write many of Emscripten's tools. The required version is listed in the :ref:`toolchain requirements <central-list-of-emscripten-tools-and-dependencies>`. |
21 | 21 | import stat |
22 | 22 | import sys |
23 | 23 | import time |
24 | from pathlib import Path | |
24 | 25 | |
25 | 26 | import api_items |
26 | 27 | |
90 | 91 | continue |
91 | 92 | |
92 | 93 | inputfilename = wiki_checkout + file |
93 | markdown = open(inputfilename).read() | |
94 | markdown = Path(inputfilename).read_text() | |
94 | 95 | if 'This article has moved from the wiki to the new site' in markdown: |
95 | 96 | continue |
96 | 97 | if 'This page has been migrated to the main site' in markdown: |
37 | 37 | function base64Decode(b64) { |
38 | 38 | #if ENVIRONMENT_MAY_BE_NODE |
39 | 39 | if (typeof ENVIRONMENT_IS_NODE !== 'undefined' && ENVIRONMENT_IS_NODE) { |
40 | try { | |
41 | var buf = Buffer.from(b64, 'base64'); | |
42 | } catch (_) { | |
43 | var buf = new Buffer(b64, 'base64'); | |
44 | } | |
40 | var buf = Buffer.from(b64, 'base64'); | |
45 | 41 | return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); |
46 | 42 | } |
47 | 43 | #endif |
43 | 43 | function intArrayFromBase64(s) { |
44 | 44 | #if ENVIRONMENT_MAY_BE_NODE |
45 | 45 | if (typeof ENVIRONMENT_IS_NODE === 'boolean' && ENVIRONMENT_IS_NODE) { |
46 | var buf; | |
47 | try { | |
48 | // TODO: Update Node.js externs, Closure does not recognize the following Buffer.from() | |
49 | /**@suppress{checkTypes}*/ | |
50 | buf = Buffer.from(s, 'base64'); | |
51 | } catch (_) { | |
52 | buf = new Buffer(s, 'base64'); | |
53 | } | |
46 | var buf = Buffer.from(s, 'base64'); | |
54 | 47 | return new Uint8Array(buf['buffer'], buf['byteOffset'], buf['byteLength']); |
55 | 48 | } |
56 | 49 | #endif |
108 | 108 | } |
109 | 109 | }, |
110 | 110 | |
111 | _emval_run_destructors__sig: 'vi', | |
111 | 112 | _emval_run_destructors__deps: ['_emval_decref', '$emval_handle_array', '$runDestructors'], |
112 | 113 | _emval_run_destructors: function(handle) { |
113 | 114 | var destructors = emval_handle_array[handle].value; |
125 | 126 | return __emval_register({}); |
126 | 127 | }, |
127 | 128 | |
129 | _emval_new_cstring__sig: 'ii', | |
128 | 130 | _emval_new_cstring__deps: ['$getStringOrSymbol', '_emval_register'], |
129 | 131 | _emval_new_cstring: function(v) { |
130 | 132 | return __emval_register(getStringOrSymbol(v)); |
240 | 242 | })()('return this')(); |
241 | 243 | }, |
242 | 244 | #endif |
245 | _emval_get_global__sig: 'ii', | |
243 | 246 | _emval_get_global__deps: ['_emval_register', '$getStringOrSymbol', '$emval_get_global'], |
244 | 247 | _emval_get_global: function(name) { |
245 | 248 | if (name===0) { |
256 | 259 | return __emval_register(Module[name]); |
257 | 260 | }, |
258 | 261 | |
262 | _emval_get_property__sig: 'iii', | |
259 | 263 | _emval_get_property__deps: ['_emval_register', '$requireHandle'], |
260 | 264 | _emval_get_property: function(handle, key) { |
261 | 265 | handle = requireHandle(handle); |
263 | 267 | return __emval_register(handle[key]); |
264 | 268 | }, |
265 | 269 | |
270 | _emval_set_property__sig: 'viii', | |
266 | 271 | _emval_set_property__deps: ['$requireHandle'], |
267 | 272 | _emval_set_property: function(handle, key, value) { |
268 | 273 | handle = requireHandle(handle); |
270 | 275 | value = requireHandle(value); |
271 | 276 | handle[key] = value; |
272 | 277 | }, |
273 | ||
278 | ||
279 | _emval_as__sig: 'iiii', | |
274 | 280 | _emval_as__deps: ['_emval_register', '$requireHandle', '$requireRegisteredType'], |
275 | 281 | _emval_as: function(handle, returnType, destructorsRef) { |
276 | 282 | handle = requireHandle(handle); |
96 | 96 | // It is possible that when printing the function as a string on Windows, the js interpreter we are in returns the string with Windows |
97 | 97 | // line endings \r\n. This is undesirable, since line endings are managed in the form \n in the output for binary file writes, so |
98 | 98 | // make sure the endings are uniform. |
99 | snippet = snippet.toString().replace(/\r\n/gm,"\n"); | |
99 | snippet = snippet.toString().replace(/\r\n/gm,'\n'); | |
100 | 100 | |
101 | 101 | // name the function; overwrite if it's already named |
102 | 102 | snippet = snippet.replace(/function(?:\s+([^(]+))?\s*\(/, 'function ' + finalName + '('); |
103 | 103 | |
104 | 104 | // apply LIBRARY_DEBUG if relevant |
105 | if (LIBRARY_DEBUG) { | |
105 | if (LIBRARY_DEBUG && !isJsOnlyIdentifier(ident)) { | |
106 | 106 | snippet = modifyFunction(snippet, (name, args, body) => { |
107 | 107 | return 'function ' + name + '(' + args + ') {\n' + |
108 | 108 | 'var ret = (function() { if (runtimeDebug) err("[library call:' + finalName + ': " + Array.prototype.slice.call(arguments).map(prettyPrint) + "]");\n' + |
191 | 191 | LibraryManager.library[ident] = new Function(functionBody); |
192 | 192 | noExport = true; |
193 | 193 | } |
194 | } | |
195 | ||
196 | if (!ALLOW_UNIMPLEMENTED_SYSCALLS && LibraryManager.library[ident + '__unimplemented']) { | |
197 | error(`attempt to link unsupport syscall: ${ident} (use -s ALLOW_UNIMPLEMENTED_SYSCALLS (the default) to allow linking with a stub version`); | |
194 | 198 | } |
195 | 199 | |
196 | 200 | var original = LibraryManager.library[ident]; |
393 | 397 | print(preprocess(read('arrayUtils.js'))); |
394 | 398 | } |
395 | 399 | |
396 | if (SUPPORT_BASE64_EMBEDDING && !MINIMAL_RUNTIME) { | |
400 | if ((SUPPORT_BASE64_EMBEDDING || FORCE_FILESYSTEM) && !MINIMAL_RUNTIME) { | |
397 | 401 | print(preprocess(read('base64Utils.js'))); |
398 | 402 | } |
399 | 403 |
35 | 35 | setTempRet0__sig: 'vi', |
36 | 36 | setTempRet0: function(val) { |
37 | 37 | setTempRet0(val); |
38 | }, | |
39 | ||
40 | $zeroMemory: function(address, size) { | |
41 | #if LEGACY_VM_SUPPORT | |
42 | if (!HEAPU8.fill) { | |
43 | for (var i = 0; i < size; i++) { | |
44 | HEAPU8[address + i] = 0; | |
45 | } | |
46 | return; | |
47 | } | |
48 | #endif | |
49 | HEAPU8.fill(0, address, address + size); | |
38 | 50 | }, |
39 | 51 | |
40 | 52 | #if SAFE_HEAP |
112 | 124 | // sys/file.h |
113 | 125 | // ========================================================================== |
114 | 126 | |
127 | flock__unimplemented: true, | |
115 | 128 | flock: function(fd, operation) { |
116 | 129 | // int flock(int fd, int operation); |
117 | 130 | // Pretend to succeed |
121 | 134 | chroot__deps: ['$setErrNo'], |
122 | 135 | chroot__proxy: 'sync', |
123 | 136 | chroot__sig: 'ii', |
137 | chroot__unimplemented: true, | |
124 | 138 | chroot: function(path) { |
125 | 139 | // int chroot(const char *path); |
126 | 140 | // http://pubs.opengroup.org/onlinepubs/7908799/xsh/chroot.html |
130 | 144 | |
131 | 145 | execve__deps: ['$setErrNo'], |
132 | 146 | execve__sig: 'iiii', |
147 | execve__unimplemented: true, | |
133 | 148 | execve: function(path, argv, envp) { |
134 | 149 | // int execve(const char *pathname, char *const argv[], |
135 | 150 | // char *const envp[]); |
150 | 165 | #endif |
151 | 166 | }, |
152 | 167 | |
153 | _exit__sig: 'vi', | |
154 | _exit: 'exit', | |
155 | ||
156 | _Exit__sig: 'vi', | |
157 | _Exit: 'exit', | |
158 | ||
159 | 168 | #if MINIMAL_RUNTIME |
160 | 169 | $exit: function(status) { |
161 | 170 | throw 'exit(' + status + ')'; |
166 | 175 | // processes. |
167 | 176 | fork__deps: ['$setErrNo'], |
168 | 177 | fork__sig: 'i', |
178 | fork__unimplemented: true, | |
169 | 179 | fork: function() { |
170 | 180 | // pid_t fork(void); |
171 | 181 | // http://pubs.opengroup.org/onlinepubs/000095399/functions/fork.html |
177 | 187 | posix_spawn: 'fork', |
178 | 188 | |
179 | 189 | setgroups__deps: ['$setErrNo', 'sysconf'], |
190 | setgroups__unimplemented: true, | |
180 | 191 | setgroups: function(ngroups, gidset) { |
181 | 192 | // int setgroups(int ngroups, const gid_t *gidset); |
182 | 193 | // https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man2/setgroups.2.html |
183 | 194 | if (ngroups < 1 || ngroups > _sysconf({{{ cDefine('_SC_NGROUPS_MAX') }}})) { |
184 | 195 | setErrNo({{{ cDefine('EINVAL') }}}); |
185 | 196 | return -1; |
186 | } else { | |
187 | // We have just one process/user/group, so it makes no sense to set groups. | |
188 | setErrNo({{{ cDefine('EPERM') }}}); | |
189 | return -1; | |
190 | } | |
197 | } | |
198 | // We have just one process/user/group, so it makes no sense to set groups. | |
199 | setErrNo({{{ cDefine('EPERM') }}}); | |
200 | return -1; | |
191 | 201 | }, |
192 | 202 | |
193 | 203 | emscripten_get_heap_max: function() { |
418 | 428 | // stdlib.h |
419 | 429 | // ========================================================================== |
420 | 430 | |
421 | _ZSt9terminatev__deps: ['exit'], | |
422 | _ZSt9terminatev: function() { | |
423 | _exit(-1234); | |
424 | }, | |
425 | ||
426 | 431 | #if MINIMAL_RUNTIME && !EXIT_RUNTIME |
427 | 432 | atexit__sig: 'v', // atexit unsupported in MINIMAL_RUNTIME |
428 | 433 | atexit: function(){}, |
437 | 442 | }, |
438 | 443 | __cxa_atexit: 'atexit', |
439 | 444 | |
440 | #endif | |
441 | ||
442 | // used in rust, clang when doing thread_local statics | |
443 | #if USE_PTHREADS | |
444 | __cxa_thread_atexit: 'pthread_cleanup_push', | |
445 | __cxa_thread_atexit_impl: 'pthread_cleanup_push', | |
446 | #else | |
447 | __cxa_thread_atexit: 'atexit', | |
448 | __cxa_thread_atexit_impl: 'atexit', | |
449 | 445 | #endif |
450 | 446 | |
451 | 447 | // TODO: There are currently two abort() functions that get imported to asm module scope: the built-in runtime function abort(), |
511 | 507 | __assert_fail: function(condition, filename, line, func) { |
512 | 508 | abort('Assertion failed: ' + UTF8ToString(condition) + ', at: ' + [filename ? UTF8ToString(filename) : 'unknown filename', line, func ? UTF8ToString(func) : 'unknown function']); |
513 | 509 | }, |
514 | ||
515 | // ========================================================================== | |
516 | // pwd.h | |
517 | // ========================================================================== | |
518 | ||
519 | // TODO: Implement. | |
520 | // http://pubs.opengroup.org/onlinepubs/009695399/basedefs/pwd.h.html | |
521 | getpwuid: function(uid) { | |
522 | return 0; // NULL | |
523 | }, | |
524 | ||
525 | 510 | |
526 | 511 | // ========================================================================== |
527 | 512 | // time.h |
766 | 751 | }, |
767 | 752 | |
768 | 753 | stime__deps: ['$setErrNo'], |
754 | stime__unimplemented: true, | |
769 | 755 | stime: function(when) { |
770 | 756 | setErrNo({{{ cDefine('EPERM') }}}); |
771 | 757 | return -1; |
1400 | 1386 | return _strptime(buf, format, tm); // no locale support yet |
1401 | 1387 | }, |
1402 | 1388 | |
1389 | getdate__unimplemented: true, | |
1403 | 1390 | getdate: function(string) { |
1404 | 1391 | // struct tm *getdate(const char *string); |
1405 | 1392 | // http://pubs.opengroup.org/onlinepubs/009695399/functions/getdate.html |
1489 | 1476 | // sys/times.h |
1490 | 1477 | // ========================================================================== |
1491 | 1478 | |
1479 | times__deps: ['$zeroMemory'], | |
1480 | times__unimplemented: true, | |
1492 | 1481 | times: function(buffer) { |
1493 | 1482 | // clock_t times(struct tms *buffer); |
1494 | 1483 | // http://pubs.opengroup.org/onlinepubs/009695399/functions/times.html |
1495 | 1484 | // NOTE: This is fake, since we can't calculate real CPU time usage in JS. |
1496 | 1485 | if (buffer !== 0) { |
1497 | _memset(buffer, 0, {{{ C_STRUCTS.tms.__size__ }}}); | |
1486 | zeroMemory(buffer, {{{ C_STRUCTS.tms.__size__ }}}); | |
1498 | 1487 | } |
1499 | 1488 | return 0; |
1500 | 1489 | }, |
1501 | ||
1502 | // ========================================================================== | |
1503 | // sys/types.h | |
1504 | // ========================================================================== | |
1505 | // http://www.kernel.org/doc/man-pages/online/pages/man3/minor.3.html | |
1506 | makedev__sig: 'iii', | |
1507 | makedev: function(maj, min) { | |
1508 | return ((maj) << 8 | (min)); | |
1509 | }, | |
1510 | gnu_dev_makedev: 'makedev', | |
1511 | major__sig: 'ii', | |
1512 | major: function(dev) { | |
1513 | return ((dev) >> 8); | |
1514 | }, | |
1515 | gnu_dev_major: 'major', | |
1516 | minor__sig: 'ii', | |
1517 | minor: function(dev) { | |
1518 | return ((dev) & 0xff); | |
1519 | }, | |
1520 | gnu_dev_minor: 'minor', | |
1521 | 1490 | |
1522 | 1491 | // ========================================================================== |
1523 | 1492 | // setjmp.h |
1543 | 1512 | return this.longjmp__deps; |
1544 | 1513 | }, |
1545 | 1514 | // will never be emitted, as the dep errors at compile time |
1515 | longjmp__unimplemented: true, | |
1546 | 1516 | longjmp: function(env, value) { |
1547 | 1517 | abort('longjmp not supported'); |
1548 | 1518 | }, |
1519 | setjmp__unimplemented: true, | |
1549 | 1520 | setjmp: function(env, value) { |
1550 | 1521 | abort('setjmp not supported'); |
1551 | 1522 | }, |
1557 | 1528 | |
1558 | 1529 | wait__deps: ['$setErrNo'], |
1559 | 1530 | wait__sig: 'ii', |
1531 | wait__unimplemented: true, | |
1560 | 1532 | wait: function(stat_loc) { |
1561 | 1533 | // pid_t wait(int *stat_loc); |
1562 | 1534 | // http://pubs.opengroup.org/onlinepubs/009695399/functions/wait.html |
2039 | 2011 | |
2040 | 2012 | return { family: family, addr: addr, port: port }; |
2041 | 2013 | }, |
2042 | $writeSockaddr__deps: ['$Sockets', '$inetPton4', '$inetPton6'], | |
2014 | $writeSockaddr__deps: ['$Sockets', '$inetPton4', '$inetPton6', '$zeroMemory'], | |
2043 | 2015 | $writeSockaddr: function (sa, family, addr, port, addrlen) { |
2044 | 2016 | switch (family) { |
2045 | 2017 | case {{{ cDefine('AF_INET') }}}: |
2046 | 2018 | addr = inetPton4(addr); |
2019 | zeroMemory(sa, {{{ C_STRUCTS.sockaddr_in.__size__ }}}); | |
2047 | 2020 | if (addrlen) { |
2048 | 2021 | {{{ makeSetValue('addrlen', 0, C_STRUCTS.sockaddr_in.__size__, 'i32') }}}; |
2049 | 2022 | } |
2050 | 2023 | {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in.sin_family, 'family', 'i16') }}}; |
2051 | 2024 | {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in.sin_addr.s_addr, 'addr', 'i32') }}}; |
2052 | 2025 | {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in.sin_port, '_htons(port)', 'i16') }}}; |
2053 | /* Use makeSetValue instead of memset to avoid adding memset dependency for all users of writeSockaddr. */ | |
2054 | {{{ assert(C_STRUCTS.sockaddr_in.__size__ - C_STRUCTS.sockaddr_in.sin_zero == 8), '' }}} | |
2055 | {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in.sin_zero, '0', 'i64') }}}; | |
2056 | 2026 | break; |
2057 | 2027 | case {{{ cDefine('AF_INET6') }}}: |
2058 | 2028 | addr = inetPton6(addr); |
2029 | zeroMemory(sa, {{{ C_STRUCTS.sockaddr_in6.__size__ }}}); | |
2059 | 2030 | if (addrlen) { |
2060 | 2031 | {{{ makeSetValue('addrlen', 0, C_STRUCTS.sockaddr_in6.__size__, 'i32') }}}; |
2061 | 2032 | } |
2065 | 2036 | {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+8, 'addr[2]', 'i32') }}}; |
2066 | 2037 | {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+12, 'addr[3]', 'i32') }}}; |
2067 | 2038 | {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_port, '_htons(port)', 'i16') }}}; |
2068 | {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_flowinfo, '0', 'i32') }}}; | |
2069 | {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_scope_id, '0', 'i32') }}}; | |
2070 | 2039 | break; |
2071 | 2040 | default: |
2072 | 2041 | return {{{ cDefine('EAFNOSUPPORT') }}}; |
2565 | 2534 | |
2566 | 2535 | // pwd.h |
2567 | 2536 | |
2537 | getpwnam__unimplemented: true, | |
2568 | 2538 | getpwnam: function() { throw 'getpwnam: TODO' }, |
2539 | getpwnam_r__unimplemented: true, | |
2569 | 2540 | getpwnam_r: function() { throw 'getpwnam_r: TODO' }, |
2541 | getpwuid__unimplemented: true, | |
2570 | 2542 | getpwuid: function() { throw 'getpwuid: TODO' }, |
2543 | getpwuid_r__unimplemented: true, | |
2571 | 2544 | getpwuid_r: function() { throw 'getpwuid_r: TODO' }, |
2545 | setpwent__unimplemented: true, | |
2572 | 2546 | setpwent: function() { throw 'setpwent: TODO' }, |
2547 | getpwent__unimplemented: true, | |
2573 | 2548 | getpwent: function() { throw 'getpwent: TODO' }, |
2549 | endpwent__unimplemented: true, | |
2574 | 2550 | endpwent: function() { throw 'endpwent: TODO' }, |
2575 | 2551 | |
2576 | 2552 | // grp.h |
2577 | 2553 | |
2554 | getgrgid__unimplemented: true, | |
2578 | 2555 | getgrgid: function() { throw 'getgrgid: TODO' }, |
2556 | getgrgid_r__unimplemented: true, | |
2579 | 2557 | getgrgid_r: function() { throw 'getgrgid_r: TODO' }, |
2558 | getgrnam__unimplemented: true, | |
2580 | 2559 | getgrnam: function() { throw 'getgrnam: TODO' }, |
2560 | getgrnam_r__unimplemented: true, | |
2581 | 2561 | getgrnam_r: function() { throw 'getgrnam_r: TODO' }, |
2562 | getgrent__unimplemented: true, | |
2582 | 2563 | getgrent: function() { throw 'getgrent: TODO' }, |
2564 | endgrent__unimplemented: true, | |
2583 | 2565 | endgrent: function() { throw 'endgrent: TODO' }, |
2566 | setgrent__unimplemented: true, | |
2584 | 2567 | setgrent: function() { throw 'setgrent: TODO' }, |
2585 | 2568 | |
2586 | 2569 | // random.h |
3558 | 3541 | throw 'unwind'; |
3559 | 3542 | }, |
3560 | 3543 | |
3561 | emscripten_force_exit__deps: ['$runtimeKeepaliveCounter'], | |
3562 | 3544 | emscripten_force_exit__proxy: 'sync', |
3563 | 3545 | emscripten_force_exit__sig: 'vi', |
3564 | 3546 | emscripten_force_exit: function(status) { |
3575 | 3557 | }, |
3576 | 3558 | |
3577 | 3559 | #if !MINIMAL_RUNTIME |
3578 | $runtimeKeepaliveCounter: 0, | |
3579 | ||
3580 | $keepRuntimeAlive__deps: ['$runtimeKeepaliveCounter'], | |
3581 | $keepRuntimeAlive: function() { | |
3582 | return noExitRuntime || runtimeKeepaliveCounter > 0; | |
3583 | }, | |
3584 | ||
3585 | 3560 | // Callable in pthread without __proxy needed. |
3586 | 3561 | $runtimeKeepalivePush__sig: 'v', |
3587 | $runtimeKeepalivePush__deps: ['$runtimeKeepaliveCounter'], | |
3588 | 3562 | $runtimeKeepalivePush: function() { |
3589 | 3563 | runtimeKeepaliveCounter += 1; |
3590 | 3564 | #if RUNTIME_DEBUG |
3593 | 3567 | }, |
3594 | 3568 | |
3595 | 3569 | $runtimeKeepalivePop__sig: 'v', |
3596 | $runtimeKeepalivePop__deps: ['$runtimeKeepaliveCounter'], | |
3597 | 3570 | $runtimeKeepalivePop: function() { |
3598 | 3571 | #if ASSERTIONS |
3599 | 3572 | assert(runtimeKeepaliveCounter > 0); |
3603 | 3576 | err('runtimeKeepalivePop -> counter=' + runtimeKeepaliveCounter); |
3604 | 3577 | #endif |
3605 | 3578 | }, |
3606 | ||
3607 | 3579 | |
3608 | 3580 | // Used to call user callbacks from the embedder / event loop. For example |
3609 | 3581 | // setTimeout or any other kind of event handler that calls into user case |
3680 | 3652 | }, |
3681 | 3653 | #endif |
3682 | 3654 | |
3655 | $safeSetTimeout: function(func, timeout) { | |
3656 | {{{ runtimeKeepalivePush() }}} | |
3657 | return setTimeout(function() { | |
3658 | {{{ runtimeKeepalivePop() }}} | |
3659 | callUserCallback(func); | |
3660 | }, timeout); | |
3661 | }, | |
3662 | ||
3683 | 3663 | $asmjsMangle: function(x) { |
3684 | 3664 | var unmangledSymbols = {{{ buildStringArray(WASM_SYSTEM_EXPORTS) }}}; |
3685 | 3665 | return x.indexOf('dynCall_') == 0 || unmangledSymbols.includes(x) ? x : '_' + x; |
3666 | }, | |
3667 | ||
3668 | $asyncLoad: function(url, onload, onerror, noRunDep) { | |
3669 | var dep = !noRunDep ? getUniqueRunDependency('al ' + url) : ''; | |
3670 | readAsync(url, function(arrayBuffer) { | |
3671 | assert(arrayBuffer, 'Loading data file "' + url + '" failed (no arrayBuffer).'); | |
3672 | onload(new Uint8Array(arrayBuffer)); | |
3673 | if (dep) removeRunDependency(dep); | |
3674 | }, function(event) { | |
3675 | if (onerror) { | |
3676 | onerror(); | |
3677 | } else { | |
3678 | throw 'Loading data file "' + url + '" failed.'; | |
3679 | } | |
3680 | }); | |
3681 | if (dep) addRunDependency(dep); | |
3682 | }, | |
3683 | ||
3684 | // Allocate memory for an mmap operation. This allocates space of the right | |
3685 | // page-aligned size, and clears the allocated space. | |
3686 | $mmapAlloc__deps: ['$zeroMemory'], | |
3687 | $mmapAlloc: function(size) { | |
3688 | #if hasExportedFunction('_memalign') | |
3689 | size = alignMemory(size, {{{ WASM_PAGE_SIZE }}}); | |
3690 | var ptr = _memalign({{{ WASM_PAGE_SIZE }}}, size); | |
3691 | if (!ptr) return 0; | |
3692 | zeroMemory(ptr, size); | |
3693 | return ptr; | |
3694 | #elif ASSERTIONS | |
3695 | abort('internal error: mmapAlloc called but `memalign` native symbol not exported'); | |
3696 | #else | |
3697 | abort(); | |
3698 | #endif | |
3686 | 3699 | }, |
3687 | 3700 | |
3688 | 3701 | #if RELOCATABLE |
19 | 19 | }, |
20 | 20 | |
21 | 21 | #if ASYNCIFY |
22 | $Asyncify__deps: ['$runAndAbortIfError'], | |
22 | $Asyncify__deps: ['$runAndAbortIfError', '$callUserCallback', | |
23 | #if !MINIMAL_RUNTIME | |
24 | '$runtimeKeepalivePush', '$runtimeKeepalivePop' | |
25 | #endif | |
26 | ], | |
23 | 27 | $Asyncify: { |
24 | 28 | State: { |
25 | 29 | Normal: 0, |
129 | 133 | #if ASYNCIFY_DEBUG |
130 | 134 | err('ASYNCIFY: stop unwind'); |
131 | 135 | #endif |
136 | {{{ runtimeKeepalivePush(); }}} | |
132 | 137 | Asyncify.state = Asyncify.State.Normal; |
138 | // Keep the runtime alive so that a re-wind can be done later. | |
133 | 139 | runAndAbortIfError(Module['_asyncify_stop_unwind']); |
134 | 140 | if (typeof Fibers !== 'undefined') { |
135 | 141 | Fibers.trampoline(); |
177 | 183 | return func; |
178 | 184 | }, |
179 | 185 | |
186 | doRewind: function(ptr) { | |
187 | var start = Asyncify.getDataRewindFunc(ptr); | |
188 | #if ASYNCIFY_DEBUG | |
189 | err('ASYNCIFY: start:', start); | |
190 | #endif | |
191 | // Once we have rewound and the stack we no longer need to artificially keep | |
192 | // the runtime alive. | |
193 | {{{ runtimeKeepalivePop(); }}} | |
194 | return start(); | |
195 | }, | |
196 | ||
180 | 197 | handleSleep: function(startAsync) { |
181 | 198 | #if ASSERTIONS |
182 | 199 | assert(Asyncify.state !== Asyncify.State.Disabled, 'Asyncify cannot be done during or after the runtime exits'); |
183 | 200 | #endif |
184 | 201 | if (ABORT) return; |
185 | #if !MINIMAL_RUNTIME | |
186 | noExitRuntime = true; | |
187 | #endif | |
188 | 202 | #if ASYNCIFY_DEBUG |
189 | 203 | err('ASYNCIFY: handleSleep ' + Asyncify.state); |
190 | 204 | #endif |
222 | 236 | if (typeof Browser !== 'undefined' && Browser.mainLoop.func) { |
223 | 237 | Browser.mainLoop.resume(); |
224 | 238 | } |
225 | var start = Asyncify.getDataRewindFunc(Asyncify.currData); | |
226 | #if ASYNCIFY_DEBUG | |
227 | err('ASYNCIFY: start:', start); | |
228 | #endif | |
229 | var asyncWasmReturnValue = start(); | |
239 | var asyncWasmReturnValue = Asyncify.doRewind(Asyncify.currData); | |
230 | 240 | if (!Asyncify.currData) { |
231 | 241 | // All asynchronous execution has finished. |
232 | 242 | // `asyncWasmReturnValue` now contains the final |
272 | 282 | Asyncify.currData = null; |
273 | 283 | // Call all sleep callbacks now that the sleep-resume is all done. |
274 | 284 | Asyncify.sleepCallbacks.forEach(function(func) { |
275 | func(); | |
285 | callUserCallback(func); | |
276 | 286 | }); |
277 | 287 | } else { |
278 | 288 | abort('invalid state: ' + Asyncify.state); |
293 | 303 | }, |
294 | 304 | }, |
295 | 305 | |
296 | emscripten_sleep__deps: ['$Browser'], | |
306 | emscripten_sleep__deps: ['$safeSetTimeout'], | |
297 | 307 | emscripten_sleep: function(ms) { |
298 | 308 | Asyncify.handleSleep(function(wakeUp) { |
299 | Browser.safeSetTimeout(wakeUp, ms); | |
309 | safeSetTimeout(wakeUp, ms); | |
300 | 310 | }); |
301 | 311 | }, |
302 | 312 | |
323 | 333 | }); |
324 | 334 | }, |
325 | 335 | |
326 | emscripten_wget_data__deps: ['$Browser'], | |
336 | emscripten_wget_data__deps: ['$asyncLoad', 'malloc'], | |
327 | 337 | emscripten_wget_data: function(url, pbuffer, pnum, perror) { |
328 | 338 | Asyncify.handleSleep(function(wakeUp) { |
329 | Browser.asyncLoad(UTF8ToString(url), function(byteArray) { | |
339 | asyncLoad(UTF8ToString(url), function(byteArray) { | |
330 | 340 | // can only allocate the buffer after the wakeUp, not during an asyncing |
331 | 341 | var buffer = _malloc(byteArray.length); // must be freed by caller! |
332 | 342 | HEAPU8.set(byteArray, buffer); |
420 | 430 | #endif |
421 | 431 | Asyncify.state = Asyncify.State.Rewinding; |
422 | 432 | Module['_asyncify_start_rewind'](asyncifyData); |
423 | var start = Asyncify.getDataRewindFunc(asyncifyData); | |
424 | #if ASYNCIFY_DEBUG | |
425 | err('ASYNCIFY/FIBER: start: ' + start); | |
426 | #endif | |
427 | start(); | |
433 | Asyncify.doRewind(asyncifyData); | |
428 | 434 | } |
429 | 435 | }, |
430 | 436 | }, |
459 | 465 | emscripten_fiber_swap__deps: ["$Asyncify", "$Fibers"], |
460 | 466 | emscripten_fiber_swap: function(oldFiber, newFiber) { |
461 | 467 | if (ABORT) return; |
462 | #if !MINIMAL_RUNTIME | |
463 | noExitRuntime = true; | |
464 | #endif | |
465 | 468 | #if ASYNCIFY_DEBUG |
466 | 469 | err('ASYNCIFY/FIBER: swap', oldFiber, '->', newFiber, 'state:', Asyncify.state); |
467 | 470 | #endif |
13 | 13 | assert(!LibraryManager.library); |
14 | 14 | LibraryManager.library = { |
15 | 15 | $callRuntimeCallbacks: function() {}, |
16 | $keepRuntimeAlive: function() { return false } | |
17 | 16 | }; |
8 | 8 | $Browser__deps: [ |
9 | 9 | '$setMainLoop', |
10 | 10 | '$callUserCallback', |
11 | '$safeSetTimeout', | |
11 | 12 | 'emscripten_set_main_loop_timing', |
12 | 13 | #if !MINIMAL_RUNTIME |
13 | 14 | '$runtimeKeepalivePush', |
229 | 230 | }; |
230 | 231 | audio.src = url; |
231 | 232 | // workaround for chrome bug 124926 - we do not always get oncanplaythrough or onerror |
232 | Browser.safeSetTimeout(function() { | |
233 | safeSetTimeout(function() { | |
233 | 234 | finish(audio); // try to use it even though it is not necessarily ready to play |
234 | 235 | }, 10000); |
235 | 236 | } else { |
487 | 488 | |
488 | 489 | // abort and pause-aware versions TODO: build main loop on top of this? |
489 | 490 | |
491 | safeSetTimeout: function(func) { | |
492 | // Legacy function, this is used by the SDL2 port so we need to keep it | |
493 | // around at least until that is updated. | |
494 | return safeSetTimeout(func); | |
495 | }, | |
490 | 496 | safeRequestAnimationFrame: function(func) { |
491 | 497 | {{{ runtimeKeepalivePush() }}} |
492 | 498 | return Browser.requestAnimationFrame(function() { |
493 | 499 | {{{ runtimeKeepalivePop() }}} |
494 | 500 | callUserCallback(func); |
495 | 501 | }); |
496 | }, | |
497 | safeSetTimeout: function(func, timeout) { | |
498 | {{{ runtimeKeepalivePush() }}} | |
499 | return setTimeout(function() { | |
500 | {{{ runtimeKeepalivePop() }}} | |
501 | callUserCallback(func); | |
502 | }, timeout); | |
503 | 502 | }, |
504 | 503 | |
505 | 504 | getMimetype: function(name) { |
672 | 671 | } |
673 | 672 | }, |
674 | 673 | |
675 | asyncLoad: function(url, onload, onerror, noRunDep) { | |
676 | var dep = !noRunDep ? getUniqueRunDependency('al ' + url) : ''; | |
677 | readAsync(url, function(arrayBuffer) { | |
678 | assert(arrayBuffer, 'Loading data file "' + url + '" failed (no arrayBuffer).'); | |
679 | onload(new Uint8Array(arrayBuffer)); | |
680 | if (dep) removeRunDependency(dep); | |
681 | }, function(event) { | |
682 | if (onerror) { | |
683 | onerror(); | |
684 | } else { | |
685 | throw 'Loading data file "' + url + '" failed.'; | |
686 | } | |
687 | }); | |
688 | if (dep) addRunDependency(dep); | |
689 | }, | |
690 | ||
691 | 674 | resizeListeners: [], |
692 | 675 | |
693 | 676 | updateResizeListeners: function() { |
772 | 755 | } |
773 | 756 | } |
774 | 757 | }, |
775 | ||
776 | wgetRequests: {}, | |
777 | nextWgetRequestHandle: 0, | |
778 | ||
779 | getNextWgetRequestHandle: function() { | |
780 | var handle = Browser.nextWgetRequestHandle; | |
781 | Browser.nextWgetRequestHandle++; | |
782 | return handle; | |
783 | } | |
784 | }, | |
785 | ||
786 | emscripten_async_wget__deps: ['$PATH_FS', | |
787 | #if !MINIMAL_RUNTIME | |
788 | '$runtimeKeepalivePush', '$runtimeKeepalivePop', | |
789 | #endif | |
790 | ], | |
791 | emscripten_async_wget__proxy: 'sync', | |
792 | emscripten_async_wget__sig: 'viiii', | |
793 | emscripten_async_wget: function(url, file, onload, onerror) { | |
794 | {{{ runtimeKeepalivePush() }}} | |
795 | ||
796 | var _url = UTF8ToString(url); | |
797 | var _file = UTF8ToString(file); | |
798 | _file = PATH_FS.resolve(_file); | |
799 | function doCallback(callback) { | |
800 | if (callback) { | |
801 | {{{ runtimeKeepalivePop() }}} | |
802 | callUserCallback(function() { | |
803 | var stack = stackSave(); | |
804 | {{{ makeDynCall('vi', 'callback') }}}(allocate(intArrayFromString(_file), ALLOC_STACK)); | |
805 | stackRestore(stack); | |
806 | }); | |
807 | } | |
808 | } | |
809 | var destinationDirectory = PATH.dirname(_file); | |
810 | FS.createPreloadedFile( | |
811 | destinationDirectory, | |
812 | PATH.basename(_file), | |
813 | _url, true, true, | |
814 | function() { | |
815 | doCallback(onload); | |
816 | }, | |
817 | function() { | |
818 | doCallback(onerror); | |
819 | }, | |
820 | false, // dontCreateFile | |
821 | false, // canOwn | |
822 | function() { // preFinish | |
823 | // if a file exists there, we overwrite it | |
824 | try { | |
825 | FS.unlink(_file); | |
826 | } catch (e) {} | |
827 | // if the destination directory does not yet exist, create it | |
828 | FS.mkdirTree(destinationDirectory); | |
829 | } | |
830 | ); | |
831 | 758 | }, |
832 | 759 | |
833 | 760 | $funcWrappers: {}, |
858 | 785 | } |
859 | 786 | } |
860 | 787 | return sigCache[func]; |
861 | }, | |
862 | ||
863 | emscripten_async_wget_data__proxy: 'sync', | |
864 | emscripten_async_wget_data__sig: 'viiii', | |
865 | emscripten_async_wget_data: function(url, arg, onload, onerror) { | |
866 | {{{ runtimeKeepalivePush() }}} | |
867 | Browser.asyncLoad(UTF8ToString(url), function(byteArray) { | |
868 | {{{ runtimeKeepalivePop() }}} | |
869 | callUserCallback(function() { | |
870 | var buffer = _malloc(byteArray.length); | |
871 | HEAPU8.set(byteArray, buffer); | |
872 | {{{ makeDynCall('viii', 'onload') }}}(arg, buffer, byteArray.length); | |
873 | _free(buffer); | |
874 | }); | |
875 | }, function() { | |
876 | if (onerror) { | |
877 | {{{ runtimeKeepalivePop() }}} | |
878 | callUserCallback(function() { | |
879 | {{{ makeDynCall('vi', 'onerror') }}}(arg); | |
880 | }); | |
881 | } | |
882 | }, true /* no need for run dependency, this is async but will not do any prepare etc. step */ ); | |
883 | }, | |
884 | ||
885 | emscripten_async_wget2__deps: ['$PATH_FS', | |
886 | #if !MINIMAL_RUNTIME | |
887 | '$runtimeKeepalivePush', '$runtimeKeepalivePop', | |
888 | #endif | |
889 | ], | |
890 | emscripten_async_wget2__proxy: 'sync', | |
891 | emscripten_async_wget2__sig: 'iiiiiiiii', | |
892 | emscripten_async_wget2: function(url, file, request, param, arg, onload, onerror, onprogress) { | |
893 | {{{ runtimeKeepalivePush() }}} | |
894 | ||
895 | var _url = UTF8ToString(url); | |
896 | var _file = UTF8ToString(file); | |
897 | _file = PATH_FS.resolve(_file); | |
898 | var _request = UTF8ToString(request); | |
899 | var _param = UTF8ToString(param); | |
900 | var index = _file.lastIndexOf('/'); | |
901 | ||
902 | var http = new XMLHttpRequest(); | |
903 | http.open(_request, _url, true); | |
904 | http.responseType = 'arraybuffer'; | |
905 | ||
906 | var handle = Browser.getNextWgetRequestHandle(); | |
907 | ||
908 | var destinationDirectory = PATH.dirname(_file); | |
909 | ||
910 | // LOAD | |
911 | http.onload = function http_onload(e) { | |
912 | {{{ runtimeKeepalivePop() }}} | |
913 | if (http.status >= 200 && http.status < 300) { | |
914 | // if a file exists there, we overwrite it | |
915 | try { | |
916 | FS.unlink(_file); | |
917 | } catch (e) {} | |
918 | // if the destination directory does not yet exist, create it | |
919 | FS.mkdirTree(destinationDirectory); | |
920 | ||
921 | FS.createDataFile( _file.substr(0, index), _file.substr(index + 1), new Uint8Array(/** @type{ArrayBuffer}*/(http.response)), true, true, false); | |
922 | if (onload) { | |
923 | var stack = stackSave(); | |
924 | {{{ makeDynCall('viii', 'onload') }}}(handle, arg, allocate(intArrayFromString(_file), ALLOC_STACK)); | |
925 | stackRestore(stack); | |
926 | } | |
927 | } else { | |
928 | if (onerror) {{{ makeDynCall('viii', 'onerror') }}}(handle, arg, http.status); | |
929 | } | |
930 | ||
931 | delete Browser.wgetRequests[handle]; | |
932 | }; | |
933 | ||
934 | // ERROR | |
935 | http.onerror = function http_onerror(e) { | |
936 | {{{ runtimeKeepalivePop() }}} | |
937 | if (onerror) {{{ makeDynCall('viii', 'onerror') }}}(handle, arg, http.status); | |
938 | delete Browser.wgetRequests[handle]; | |
939 | }; | |
940 | ||
941 | // PROGRESS | |
942 | http.onprogress = function http_onprogress(e) { | |
943 | if (e.lengthComputable || (e.lengthComputable === undefined && e.total != 0)) { | |
944 | var percentComplete = (e.loaded / e.total)*100; | |
945 | if (onprogress) {{{ makeDynCall('viii', 'onprogress') }}}(handle, arg, percentComplete); | |
946 | } | |
947 | }; | |
948 | ||
949 | // ABORT | |
950 | http.onabort = function http_onabort(e) { | |
951 | {{{ runtimeKeepalivePop() }}} | |
952 | delete Browser.wgetRequests[handle]; | |
953 | }; | |
954 | ||
955 | if (_request == "POST") { | |
956 | //Send the proper header information along with the request | |
957 | http.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); | |
958 | http.send(_param); | |
959 | } else { | |
960 | http.send(null); | |
961 | } | |
962 | ||
963 | Browser.wgetRequests[handle] = http; | |
964 | ||
965 | return handle; | |
966 | }, | |
967 | ||
968 | emscripten_async_wget2_data__proxy: 'sync', | |
969 | emscripten_async_wget2_data__sig: 'iiiiiiiii', | |
970 | emscripten_async_wget2_data: function(url, request, param, arg, free, onload, onerror, onprogress) { | |
971 | var _url = UTF8ToString(url); | |
972 | var _request = UTF8ToString(request); | |
973 | var _param = UTF8ToString(param); | |
974 | ||
975 | var http = new XMLHttpRequest(); | |
976 | http.open(_request, _url, true); | |
977 | http.responseType = 'arraybuffer'; | |
978 | ||
979 | var handle = Browser.getNextWgetRequestHandle(); | |
980 | ||
981 | function onerrorjs() { | |
982 | if (onerror) { | |
983 | var statusText = 0; | |
984 | if (http.statusText) { | |
985 | var len = lengthBytesUTF8(http.statusText) + 1; | |
986 | statusText = stackAlloc(len); | |
987 | stringToUTF8(http.statusText, statusText, len); | |
988 | } | |
989 | {{{ makeDynCall('viiii', 'onerror') }}}(handle, arg, http.status, statusText); | |
990 | } | |
991 | } | |
992 | ||
993 | // LOAD | |
994 | http.onload = function http_onload(e) { | |
995 | if (http.status >= 200 && http.status < 300 || (http.status === 0 && _url.substr(0,4).toLowerCase() != "http")) { | |
996 | var byteArray = new Uint8Array(/** @type{ArrayBuffer} */(http.response)); | |
997 | var buffer = _malloc(byteArray.length); | |
998 | HEAPU8.set(byteArray, buffer); | |
999 | if (onload) {{{ makeDynCall('viiii', 'onload') }}}(handle, arg, buffer, byteArray.length); | |
1000 | if (free) _free(buffer); | |
1001 | } else { | |
1002 | onerrorjs(); | |
1003 | } | |
1004 | delete Browser.wgetRequests[handle]; | |
1005 | }; | |
1006 | ||
1007 | // ERROR | |
1008 | http.onerror = function http_onerror(e) { | |
1009 | onerrorjs(); | |
1010 | delete Browser.wgetRequests[handle]; | |
1011 | }; | |
1012 | ||
1013 | // PROGRESS | |
1014 | http.onprogress = function http_onprogress(e) { | |
1015 | if (onprogress) {{{ makeDynCall('viiii', 'onprogress') }}}(handle, arg, e.loaded, e.lengthComputable || e.lengthComputable === undefined ? e.total : 0); | |
1016 | }; | |
1017 | ||
1018 | // ABORT | |
1019 | http.onabort = function http_onabort(e) { | |
1020 | delete Browser.wgetRequests[handle]; | |
1021 | }; | |
1022 | ||
1023 | if (_request == "POST") { | |
1024 | //Send the proper header information along with the request | |
1025 | http.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); | |
1026 | http.send(_param); | |
1027 | } else { | |
1028 | http.send(null); | |
1029 | } | |
1030 | ||
1031 | Browser.wgetRequests[handle] = http; | |
1032 | ||
1033 | return handle; | |
1034 | }, | |
1035 | ||
1036 | emscripten_async_wget2_abort__proxy: 'sync', | |
1037 | emscripten_async_wget2_abort__sig: 'vi', | |
1038 | emscripten_async_wget2_abort: function(handle) { | |
1039 | var http = Browser.wgetRequests[handle]; | |
1040 | if (http) { | |
1041 | http.abort(); | |
1042 | } | |
1043 | 788 | }, |
1044 | 789 | |
1045 | 790 | emscripten_run_preload_plugins__deps: ['$PATH', |
1104 | 849 | }, |
1105 | 850 | |
1106 | 851 | // Callable from pthread, executes in pthread context. |
1107 | emscripten_async_run_script__deps: ['emscripten_run_script'], | |
852 | emscripten_async_run_script__deps: ['emscripten_run_script', '$safeSetTimeout'], | |
1108 | 853 | emscripten_async_run_script: function(script, millis) { |
1109 | 854 | // TODO: cache these to avoid generating garbage |
1110 | Browser.safeSetTimeout(function() { | |
855 | safeSetTimeout(function() { | |
1111 | 856 | _emscripten_run_script(script); |
1112 | 857 | }, millis); |
1113 | 858 | }, |
1420 | 1165 | |
1421 | 1166 | // Runs natively in pthread, no __proxy needed. |
1422 | 1167 | emscripten_async_call__sig: 'viii', |
1168 | emscripten_async_call__deps: ['$safeSetTimeout'], | |
1423 | 1169 | emscripten_async_call: function(func, arg, millis) { |
1424 | 1170 | function wrapper() { |
1425 | 1171 | {{{ makeDynCall('vi', 'func') }}}(arg); |
1426 | 1172 | } |
1427 | 1173 | |
1428 | if (millis >= 0) { | |
1429 | Browser.safeSetTimeout(wrapper, millis); | |
1174 | if (millis >= 0 | |
1175 | #if ENVIRONMENT_MAY_BE_NODE | |
1176 | // node does not support requestAnimationFrame | |
1177 | || ENVIRONMENT_IS_NODE | |
1178 | #endif | |
1179 | ) { | |
1180 | safeSetTimeout(wrapper, millis); | |
1430 | 1181 | } else { |
1431 | 1182 | Browser.safeRequestAnimationFrame(wrapper); |
1432 | 1183 | } |
247 | 247 | ___heap_base = end; |
248 | 248 | GOT['__heap_base'].value = end; |
249 | 249 | return ret; |
250 | }, | |
251 | ||
252 | // fetchBinary fetches binary data @ url. (async) | |
253 | $fetchBinary: function(url) { | |
254 | return fetch(url, { credentials: 'same-origin' }).then(function(response) { | |
255 | if (!response['ok']) { | |
256 | throw "failed to load binary file at '" + url + "'"; | |
257 | } | |
258 | return response['arrayBuffer'](); | |
259 | }).then(function(buffer) { | |
260 | return new Uint8Array(buffer); | |
261 | }); | |
262 | 250 | }, |
263 | 251 | |
264 | 252 | // returns the side module metadata as an object |
575 | 563 | // If a library was already loaded, it is not loaded a second time. However |
576 | 564 | // flags.global and flags.nodelete are handled every time a load request is made. |
577 | 565 | // Once a library becomes "global" or "nodelete", it cannot be removed or unloaded. |
578 | $loadDynamicLibrary__deps: ['$LDSO', '$loadWebAssemblyModule', '$asmjsMangle', '$fetchBinary', '$isInternalSym', '$mergeLibSymbols'], | |
566 | $loadDynamicLibrary__deps: ['$LDSO', '$loadWebAssemblyModule', '$asmjsMangle', '$isInternalSym', '$mergeLibSymbols'], | |
579 | 567 | $loadDynamicLibrary: function(lib, flags) { |
580 | 568 | if (lib == '__main__' && !LDSO.loadedLibNames[lib]) { |
581 | 569 | LDSO.loadedLibs[-1] = { |
627 | 615 | // libData <- libFile |
628 | 616 | function loadLibData(libFile) { |
629 | 617 | // for wasm, we can use fetch for async, but for fs mode we can only imitate it |
630 | if (flags.fs) { | |
618 | if (flags.fs && flags.fs.findObject(libFile)) { | |
631 | 619 | var libData = flags.fs.readFile(libFile, {encoding: 'binary'}); |
632 | 620 | if (!(libData instanceof Uint8Array)) { |
633 | 621 | libData = new Uint8Array(libData); |
636 | 624 | } |
637 | 625 | |
638 | 626 | if (flags.loadAsync) { |
639 | return fetchBinary(libFile); | |
640 | } | |
627 | return new Promise(function(resolve, reject) { | |
628 | readAsync(libFile, function(data) { resolve(new Uint8Array(data)); }, reject); | |
629 | }); | |
630 | } | |
631 | ||
641 | 632 | // load the binary synchronously |
633 | if (!readBinary) { | |
634 | throw new Error(libFile + ': file not found, and synchronous loading of external files is not available'); | |
635 | } | |
642 | 636 | return readBinary(libFile); |
643 | 637 | } |
644 | 638 | |
673 | 667 | return getLibModule().then(function(libModule) { |
674 | 668 | moduleLoaded(libModule); |
675 | 669 | return handle; |
676 | }) | |
670 | }); | |
677 | 671 | } |
678 | 672 | |
679 | 673 | moduleLoaded(getLibModule()); |
693 | 687 | return; |
694 | 688 | } |
695 | 689 | |
696 | // if we can load dynamic libraries synchronously, do so, otherwise, preload | |
697 | if (!readBinary) { | |
698 | // we can't read binary data synchronously, so preload | |
699 | addRunDependency('preloadDylibs'); | |
700 | dynamicLibraries.reduce(function(chain, lib) { | |
701 | return chain.then(function() { | |
702 | return loadDynamicLibrary(lib, {loadAsync: true, global: true, nodelete: true, allowUndefined: true}); | |
703 | }); | |
704 | }, Promise.resolve()).then(function() { | |
705 | // we got them all, wonderful | |
706 | removeRunDependency('preloadDylibs'); | |
707 | reportUndefinedSymbols(); | |
690 | // Load binaries asynchronously | |
691 | addRunDependency('preloadDylibs'); | |
692 | dynamicLibraries.reduce(function(chain, lib) { | |
693 | return chain.then(function() { | |
694 | return loadDynamicLibrary(lib, {loadAsync: true, global: true, nodelete: true, allowUndefined: true}); | |
708 | 695 | }); |
709 | return; | |
710 | } | |
711 | ||
712 | dynamicLibraries.forEach(function(lib) { | |
713 | // libraries linked to main never go away | |
714 | loadDynamicLibrary(lib, {global: true, nodelete: true, allowUndefined: true}); | |
696 | }, Promise.resolve()).then(function() { | |
697 | // we got them all, wonderful | |
698 | reportUndefinedSymbols(); | |
699 | removeRunDependency('preloadDylibs'); | |
700 | #if DYLINK_DEBUG | |
701 | err('preloadDylibs done!'); | |
702 | #endif | |
715 | 703 | }); |
716 | reportUndefinedSymbols(); | |
717 | 704 | }, |
718 | 705 | |
719 | 706 | // void* dlopen(const char* filename, int flags); |
720 | 707 | dlopen__deps: ['$DLFCN', '$FS', '$ENV'], |
721 | dlopen__proxy: 'sync', | |
722 | 708 | dlopen__sig: 'iii', |
723 | 709 | dlopen: function(filenameAddr, flags) { |
724 | 710 | // void *dlopen(const char *file, int mode); |
776 | 762 | |
777 | 763 | // int dlclose(void* handle); |
778 | 764 | dlclose__deps: ['$DLFCN'], |
779 | dlclose__proxy: 'sync', | |
780 | 765 | dlclose__sig: 'ii', |
781 | 766 | dlclose: function(handle) { |
782 | 767 | // int dlclose(void *handle); |
795 | 780 | |
796 | 781 | // void* dlsym(void* handle, const char* symbol); |
797 | 782 | dlsym__deps: ['$DLFCN'], |
798 | dlsym__proxy: 'sync', | |
799 | 783 | dlsym__sig: 'iii', |
800 | 784 | dlsym: function(handle, symbol) { |
801 | 785 | // void *dlsym(void *restrict handle, const char *restrict name); |
839 | 823 | |
840 | 824 | // char* dlerror(void); |
841 | 825 | dlerror__deps: ['$DLFCN', '$stringToNewUTF8'], |
842 | dlerror__proxy: 'sync', | |
843 | 826 | dlerror__sig: 'i', |
844 | 827 | dlerror: function() { |
845 | 828 | // char *dlerror(void); |
854 | 837 | }, |
855 | 838 | |
856 | 839 | dladdr__deps: ['$stringToNewUTF8', '$getExecutableName'], |
857 | dladdr__proxy: 'sync', | |
858 | 840 | dladdr__sig: 'iii', |
859 | 841 | dladdr: function(addr, info) { |
860 | 842 | // report all function pointers as coming from this program itself XXX not really correct in any way |
8 | 8 | $exceptionLast: '0', |
9 | 9 | $exceptionCaught: ' []', |
10 | 10 | |
11 | // Static fields for ExceptionInfo class. | |
12 | $ExceptionInfoAttrs: { | |
13 | // ExceptionInfo native structure layout. | |
14 | DESTRUCTOR_OFFSET: 0, | |
15 | REFCOUNT_OFFSET: Runtime.POINTER_SIZE, | |
16 | TYPE_OFFSET: Runtime.POINTER_SIZE + 4, | |
17 | CAUGHT_OFFSET: Runtime.POINTER_SIZE + 8, | |
18 | RETHROWN_OFFSET: Runtime.POINTER_SIZE + 9, | |
19 | ||
20 | // Total structure size with padding, should be multiple of allocation alignment. | |
21 | SIZE: alignMemory(Runtime.POINTER_SIZE + 10) | |
22 | }, | |
23 | ||
24 | $ExceptionInfo__deps: ['$ExceptionInfoAttrs'], | |
25 | 11 | // This class is the exception metadata which is prepended to each thrown object (in WASM memory). |
26 | 12 | // It is allocated in one block among with a thrown object in __cxa_allocate_exception and freed |
27 | 13 | // in ___cxa_free_exception. It roughly corresponds to __cxa_exception structure in libcxxabi. The |
36 | 22 | // excPtr - Thrown object pointer to wrap. Metadata pointer is calculated from it. |
37 | 23 | $ExceptionInfo: function(excPtr) { |
38 | 24 | this.excPtr = excPtr; |
39 | this.ptr = excPtr - ExceptionInfoAttrs.SIZE; | |
25 | this.ptr = excPtr - {{{ C_STRUCTS.__cxa_exception.__size__ }}}; | |
40 | 26 | |
41 | 27 | this.set_type = function(type) { |
42 | {{{ makeSetValue('this.ptr', 'ExceptionInfoAttrs.TYPE_OFFSET', 'type', '*') }}}; | |
28 | {{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionType, 'type', '*') }}}; | |
43 | 29 | }; |
44 | 30 | |
45 | 31 | this.get_type = function() { |
46 | return {{{ makeGetValue('this.ptr', 'ExceptionInfoAttrs.TYPE_OFFSET', '*') }}}; | |
32 | return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionType, '*') }}}; | |
47 | 33 | }; |
48 | 34 | |
49 | 35 | this.set_destructor = function(destructor) { |
50 | {{{ makeSetValue('this.ptr', 'ExceptionInfoAttrs.DESTRUCTOR_OFFSET', 'destructor', '*') }}}; | |
36 | {{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionDestructor, 'destructor', '*') }}}; | |
51 | 37 | }; |
52 | 38 | |
53 | 39 | this.get_destructor = function() { |
54 | return {{{ makeGetValue('this.ptr', 'ExceptionInfoAttrs.DESTRUCTOR_OFFSET', '*') }}}; | |
40 | return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionDestructor, '*') }}}; | |
55 | 41 | }; |
56 | 42 | |
57 | 43 | this.set_refcount = function(refcount) { |
58 | {{{ makeSetValue('this.ptr', 'ExceptionInfoAttrs.REFCOUNT_OFFSET', 'refcount', 'i32') }}}; | |
44 | {{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'refcount', 'i32') }}}; | |
59 | 45 | }; |
60 | 46 | |
61 | 47 | this.set_caught = function (caught) { |
62 | 48 | caught = caught ? 1 : 0; |
63 | {{{ makeSetValue('this.ptr', 'ExceptionInfoAttrs.CAUGHT_OFFSET', 'caught', 'i8') }}}; | |
49 | {{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.caught, 'caught', 'i8') }}}; | |
64 | 50 | }; |
65 | 51 | |
66 | 52 | this.get_caught = function () { |
67 | return {{{ makeGetValue('this.ptr', 'ExceptionInfoAttrs.CAUGHT_OFFSET', 'i8') }}} != 0; | |
53 | return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.caught, 'i8') }}} != 0; | |
68 | 54 | }; |
69 | 55 | |
70 | 56 | this.set_rethrown = function (rethrown) { |
71 | 57 | rethrown = rethrown ? 1 : 0; |
72 | {{{ makeSetValue('this.ptr', 'ExceptionInfoAttrs.RETHROWN_OFFSET', 'rethrown', 'i8') }}}; | |
58 | {{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.rethrown, 'rethrown', 'i8') }}}; | |
73 | 59 | }; |
74 | 60 | |
75 | 61 | this.get_rethrown = function () { |
76 | return {{{ makeGetValue('this.ptr', 'ExceptionInfoAttrs.RETHROWN_OFFSET', 'i8') }}} != 0; | |
62 | return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.rethrown, 'i8') }}} != 0; | |
77 | 63 | }; |
78 | 64 | |
79 | 65 | // Initialize native structure fields. Should be called once after allocated. |
87 | 73 | |
88 | 74 | this.add_ref = function() { |
89 | 75 | #if USE_PTHREADS |
90 | Atomics.add(HEAP32, (this.ptr + ExceptionInfoAttrs.REFCOUNT_OFFSET) >> 2, 1); | |
76 | Atomics.add(HEAP32, (this.ptr + {{{ C_STRUCTS.__cxa_exception.referenceCount }}}) >> 2, 1); | |
91 | 77 | #else |
92 | var value = {{{ makeGetValue('this.ptr', 'ExceptionInfoAttrs.REFCOUNT_OFFSET', 'i32') }}}; | |
93 | {{{ makeSetValue('this.ptr', 'ExceptionInfoAttrs.REFCOUNT_OFFSET', 'value + 1', 'i32') }}}; | |
78 | var value = {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'i32') }}}; | |
79 | {{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'value + 1', 'i32') }}}; | |
94 | 80 | #endif |
95 | 81 | }; |
96 | 82 | |
97 | 83 | // Returns true if last reference released. |
98 | 84 | this.release_ref = function() { |
99 | 85 | #if USE_PTHREADS |
100 | var prev = Atomics.sub(HEAP32, (this.ptr + ExceptionInfoAttrs.REFCOUNT_OFFSET) >> 2, 1); | |
86 | var prev = Atomics.sub(HEAP32, (this.ptr + {{{ C_STRUCTS.__cxa_exception.referenceCount }}}) >> 2, 1); | |
101 | 87 | #else |
102 | var prev = {{{ makeGetValue('this.ptr', 'ExceptionInfoAttrs.REFCOUNT_OFFSET', 'i32') }}}; | |
103 | {{{ makeSetValue('this.ptr', 'ExceptionInfoAttrs.REFCOUNT_OFFSET', 'prev - 1', 'i32') }}}; | |
88 | var prev = {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'i32') }}}; | |
89 | {{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'prev - 1', 'i32') }}}; | |
104 | 90 | #endif |
105 | 91 | #if ASSERTIONS |
106 | 92 | assert(prev > 0); |
133 | 119 | }; |
134 | 120 | |
135 | 121 | this.set_adjusted_ptr = function(adjustedPtr) { |
136 | var ptrSize = {{{ Runtime.POINTER_SIZE }}}; | |
137 | {{{ makeSetValue('this.ptr', 'ptrSize', 'adjustedPtr', '*') }}}; | |
138 | }; | |
122 | {{{ makeSetValue('this.ptr', Runtime.POINTER_SIZE, 'adjustedPtr', '*') }}}; | |
123 | }; | |
124 | ||
125 | this.get_adjusted_ptr_addr = function() { | |
126 | return this.ptr + {{{ Runtime.POINTER_SIZE }}}; | |
127 | } | |
139 | 128 | |
140 | 129 | this.get_adjusted_ptr = function() { |
141 | var ptrSize = {{{ Runtime.POINTER_SIZE }}}; | |
142 | return {{{ makeGetValue('this.ptr', 'ptrSize', '*') }}}; | |
130 | return {{{ makeGetValue('this.ptr', Runtime.POINTER_SIZE, '*') }}}; | |
143 | 131 | }; |
144 | 132 | |
145 | 133 | // Get pointer which is expected to be received by catch clause in C++ code. It may be adjusted |
204 | 192 | }, |
205 | 193 | |
206 | 194 | // Exceptions |
207 | __cxa_allocate_exception__deps: ['$ExceptionInfoAttrs'], | |
208 | 195 | __cxa_allocate_exception__sig: 'vi', |
209 | 196 | __cxa_allocate_exception: function(size) { |
210 | 197 | // Thrown object is prepended by exception metadata block |
211 | return _malloc(size + ExceptionInfoAttrs.SIZE) + ExceptionInfoAttrs.SIZE; | |
198 | return _malloc(size + {{{ C_STRUCTS.__cxa_exception.__size__ }}}) + {{{ C_STRUCTS.__cxa_exception.__size__ }}}; | |
212 | 199 | }, |
213 | 200 | |
214 | 201 | __cxa_free_exception__deps: ['$ExceptionInfo'], |
392 | 379 | var thrownType = info.get_type(); |
393 | 380 | var catchInfo = new CatchInfo(); |
394 | 381 | catchInfo.set_base_ptr(thrown); |
382 | catchInfo.set_adjusted_ptr(thrown); | |
395 | 383 | if (!thrownType) { |
396 | 384 | // just pass through the thrown ptr |
397 | 385 | {{{ makeStructuralReturn(['catchInfo.ptr', 0]) }}}; |
402 | 390 | #if EXCEPTION_DEBUG |
403 | 391 | out("can_catch on " + [thrown]); |
404 | 392 | #endif |
405 | var stackTop = stackSave(); | |
406 | var exceptionThrowBuf = stackAlloc(4); | |
407 | {{{ makeSetValue('exceptionThrowBuf', '0', 'thrown', '*') }}}; | |
408 | 393 | // The different catch blocks are denoted by different types. |
409 | 394 | // Due to inheritance, those types may not precisely match the |
410 | 395 | // type of the thrown object. Find one which matches, and |
415 | 400 | // Catch all clause matched or exactly the same type is caught |
416 | 401 | break; |
417 | 402 | } |
418 | if ({{{ exportedAsmFunc('___cxa_can_catch') }}}(caughtType, thrownType, exceptionThrowBuf)) { | |
419 | var adjusted = {{{ makeGetValue('exceptionThrowBuf', '0', '*') }}}; | |
420 | if (thrown !== adjusted) { | |
421 | catchInfo.set_adjusted_ptr(adjusted); | |
422 | } | |
423 | #if EXCEPTION_DEBUG | |
424 | out(" can_catch found " + [adjusted, caughtType]); | |
403 | if ({{{ exportedAsmFunc('___cxa_can_catch') }}}(caughtType, thrownType, catchInfo.get_adjusted_ptr_addr())) { | |
404 | #if EXCEPTION_DEBUG | |
405 | out(" can_catch found " + [catchInfo.get_adjusted_ptr(), caughtType]); | |
425 | 406 | #endif |
426 | 407 | {{{ makeStructuralReturn(['catchInfo.ptr', 'caughtType']) }}}; |
427 | 408 | } |
428 | 409 | } |
429 | stackRestore(stackTop); | |
430 | 410 | {{{ makeStructuralReturn(['catchInfo.ptr', 'thrownType']) }}}; |
431 | 411 | }, |
432 | 412 |
28 | 28 | '$Fetch', |
29 | 29 | '$fetchXHR', |
30 | 30 | '$callUserCallback', |
31 | 'emscripten_is_main_browser_thread', | |
32 | 31 | #if !MINIMAL_RUNTIME |
33 | 32 | '$runtimeKeepalivePush', |
34 | 33 | '$runtimeKeepalivePop', |
4 | 4 | */ |
5 | 5 | |
6 | 6 | mergeInto(LibraryManager.library, { |
7 | $FS__deps: ['$getRandomDevice', '$PATH', '$PATH_FS', '$TTY', '$MEMFS', | |
7 | $FS__deps: ['$getRandomDevice', '$PATH', '$PATH_FS', '$TTY', '$MEMFS', '$asyncLoad', | |
8 | 8 | #if LibraryManager.has('library_idbfs.js') |
9 | 9 | '$IDBFS', |
10 | 10 | #endif |
1875 | 1875 | } |
1876 | 1876 | addRunDependency(dep); |
1877 | 1877 | if (typeof url == 'string') { |
1878 | Browser.asyncLoad(url, function(byteArray) { | |
1878 | asyncLoad(url, function(byteArray) { | |
1879 | 1879 | processData(byteArray); |
1880 | 1880 | }, onerror); |
1881 | 1881 | } else { |
1992 | 1992 | }, |
1993 | 1993 | #endif |
1994 | 1994 | }, |
1995 | ||
1996 | // Allocate memory for an mmap operation. This allocates space of the right | |
1997 | // page-aligned size, and clears the padding. | |
1998 | $mmapAlloc: function(size) { | |
1999 | var alignedSize = alignMemory(size, {{{ WASM_PAGE_SIZE }}}); | |
2000 | var ptr = {{{ makeMalloc('mmapAlloc', 'alignedSize') }}}; | |
2001 | while (size < alignedSize) HEAP8[ptr + size++] = 0; | |
2002 | return ptr; | |
2003 | }, | |
2004 | 1995 | }); |
2005 | 1996 | |
2006 | 1997 | if (FORCE_FILESYSTEM) { |
417 | 417 | }, |
418 | 418 | |
419 | 419 | glutIdleFunc__proxy: 'sync', |
420 | glutIdleFunc__deps: ['$safeSetTimeout'], | |
420 | 421 | glutIdleFunc__sig: 'vi', |
421 | 422 | glutIdleFunc: function(func) { |
422 | 423 | function callback() { |
423 | 424 | if (GLUT.idleFunc) { |
424 | 425 | {{{ makeDynCall('v', 'GLUT.idleFunc') }}}(); |
425 | Browser.safeSetTimeout(callback, 4); // HTML spec specifies a 4ms minimum delay on the main thread; workers might get more, but we standardize here | |
426 | safeSetTimeout(callback, 4); // HTML spec specifies a 4ms minimum delay on the main thread; workers might get more, but we standardize here | |
426 | 427 | } |
427 | 428 | } |
428 | 429 | if (!GLUT.idleFunc) { |
429 | Browser.safeSetTimeout(callback, 0); | |
430 | safeSetTimeout(callback, 0); | |
430 | 431 | } |
431 | 432 | GLUT.idleFunc = func; |
432 | 433 | }, |
433 | 434 | |
434 | 435 | glutTimerFunc__proxy: 'sync', |
436 | glutTimerFunc__deps: ['$safeSetTimeout'], | |
435 | 437 | glutTimerFunc__sig: 'viii', |
436 | 438 | glutTimerFunc: function(msec, func, value) { |
437 | Browser.safeSetTimeout(function() { {{{ makeDynCall('vi', 'func') }}}(value); }, msec); | |
439 | safeSetTimeout(function() { {{{ makeDynCall('vi', 'func') }}}(value); }, msec); | |
438 | 440 | }, |
439 | 441 | |
440 | 442 | glutDisplayFunc__proxy: 'sync', |
26 | 26 | "{{{ cDefine('O_TRUNC') }}}": flags["O_TRUNC"], |
27 | 27 | "{{{ cDefine('O_WRONLY') }}}": flags["O_WRONLY"] |
28 | 28 | }; |
29 | }, | |
30 | bufferFrom: function (arrayBuffer) { | |
31 | // Node.js < 4.5 compatibility: Buffer.from does not support ArrayBuffer | |
32 | // Buffer.from before 4.5 was just a method inherited from Uint8Array | |
33 | // Buffer.alloc has been added with Buffer.from together, so check it instead | |
34 | return Buffer["alloc"] ? Buffer.from(arrayBuffer) : new Buffer(arrayBuffer); | |
35 | 29 | }, |
36 | 30 | convertNodeCode: function(e) { |
37 | 31 | var code = e.code; |
261 | 255 | // Node.js < 6 compatibility: node errors on 0 length reads |
262 | 256 | if (length === 0) return 0; |
263 | 257 | try { |
264 | return fs.readSync(stream.nfd, NODEFS.bufferFrom(buffer.buffer), offset, length, position); | |
258 | return fs.readSync(stream.nfd, Buffer.from(buffer.buffer), offset, length, position); | |
265 | 259 | } catch (e) { |
266 | 260 | throw new FS.ErrnoError(NODEFS.convertNodeCode(e)); |
267 | 261 | } |
268 | 262 | }, |
269 | 263 | write: function (stream, buffer, offset, length, position) { |
270 | 264 | try { |
271 | return fs.writeSync(stream.nfd, NODEFS.bufferFrom(buffer.buffer), offset, length, position); | |
265 | return fs.writeSync(stream.nfd, Buffer.from(buffer.buffer), offset, length, position); | |
272 | 266 | } catch (e) { |
273 | 267 | throw new FS.ErrnoError(NODEFS.convertNodeCode(e)); |
274 | 268 | } |
97 | 97 | } |
98 | 98 | var seeking = typeof position !== 'undefined'; |
99 | 99 | if (!seeking && stream.seekable) position = stream.position; |
100 | var bytesRead = fs.readSync(stream.nfd, NODEFS.bufferFrom(buffer.buffer), offset, length, position); | |
100 | var bytesRead = fs.readSync(stream.nfd, Buffer.from(buffer.buffer), offset, length, position); | |
101 | 101 | // update position marker when non-seeking |
102 | 102 | if (!seeking) stream.position += bytesRead; |
103 | 103 | return bytesRead; |
113 | 113 | } |
114 | 114 | var seeking = typeof position !== 'undefined'; |
115 | 115 | if (!seeking && stream.seekable) position = stream.position; |
116 | var bytesWritten = fs.writeSync(stream.nfd, NODEFS.bufferFrom(buffer.buffer), offset, length, position); | |
116 | var bytesWritten = fs.writeSync(stream.nfd, Buffer.from(buffer.buffer), offset, length, position); | |
117 | 117 | // update position marker when non-seeking |
118 | 118 | if (!seeking) stream.position += bytesWritten; |
119 | 119 | return bytesWritten; |
169 | 169 | } |
170 | 170 | |
171 | 171 | // Call into the musl function that runs destructors of all thread-specific data. |
172 | if (ENVIRONMENT_IS_PTHREAD && _pthread_self()) ___pthread_tsd_run_dtors(); | |
172 | #if ASSERTIONS | |
173 | assert(_pthread_self()) | |
174 | #endif | |
175 | ___pthread_tsd_run_dtors(); | |
173 | 176 | }, |
174 | 177 | |
175 | 178 | runExitHandlersAndDeinitThread: function(tb, exitCode) { |
657 | 660 | #endif |
658 | 661 | return navigator['hardwareConcurrency']; |
659 | 662 | }, |
660 | ||
663 | ||
664 | {{{ USE_LSAN || USE_ASAN ? 'emscripten_builtin_' : '' }}}pthread_create__sig: 'iiiii', | |
661 | 665 | {{{ USE_LSAN || USE_ASAN ? 'emscripten_builtin_' : '' }}}pthread_create__deps: ['$spawnThread', 'pthread_self', 'memalign', 'emscripten_sync_run_in_main_thread_4'], |
662 | 666 | {{{ USE_LSAN || USE_ASAN ? 'emscripten_builtin_' : '' }}}pthread_create: function(pthread_ptr, attr, start_routine, arg) { |
663 | 667 | if (typeof SharedArrayBuffer === 'undefined') { |
1054 | 1058 | if (!ENVIRONMENT_IS_PTHREAD) _exit(status); |
1055 | 1059 | else PThread.threadExit(status); |
1056 | 1060 | // pthread_exit is marked noReturn, so we must not return from it. |
1057 | if (ENVIRONMENT_IS_NODE) { | |
1058 | // exit the pthread properly on node, as a normal JS exception will halt | |
1059 | // the entire application. | |
1060 | process.exit(status); | |
1061 | } | |
1062 | 1061 | throw 'unwind'; |
1063 | 1062 | }, |
1064 | 1063 | |
1065 | pthread_cleanup_push__sig: 'vii', | |
1066 | pthread_cleanup_push: function(routine, arg) { | |
1067 | PThread.threadExitHandlers.push(function() { {{{ makeDynCall('vi', 'routine') }}}(arg) }); | |
1068 | }, | |
1069 | ||
1070 | pthread_cleanup_pop: function(execute) { | |
1071 | var routine = PThread.threadExitHandlers.pop(); | |
1072 | if (execute) routine(); | |
1073 | }, | |
1064 | __cxa_thread_atexit__sig: 'vii', | |
1065 | __cxa_thread_atexit: function(routine, arg) { | |
1066 | PThread.threadExitHandlers.push(function() { {{{ makeDynCall('vi', 'routine') }}}(arg) }); | |
1067 | }, | |
1068 | __cxa_thread_atexit_impl: '__cxa_thread_atexit', | |
1069 | ||
1074 | 1070 | |
1075 | 1071 | // Returns 0 on success, or one of the values -ETIMEDOUT, -EWOULDBLOCK or -EINVAL on error. |
1076 | 1072 | emscripten_futex_wait__deps: ['emscripten_main_thread_process_queued_calls'], |
16 | 16 | #endif |
17 | 17 | }, |
18 | 18 | |
19 | pthread_cleanup_push__sig: 'vii', | |
20 | pthread_cleanup_push: function(routine, arg) { | |
21 | __ATEXIT__.push({ func: routine, arg: arg }); | |
22 | _pthread_cleanup_push.level = __ATEXIT__.length; | |
23 | }, | |
24 | ||
25 | pthread_cleanup_pop__deps: ['pthread_cleanup_push'], | |
26 | pthread_cleanup_pop__sig: 'vi', | |
27 | pthread_cleanup_pop: function(execute) { | |
28 | assert(_pthread_cleanup_push.level == __ATEXIT__.length, 'cannot pop if something else added meanwhile!'); | |
29 | var callback = __ATEXIT__.pop(); | |
30 | if (execute) { | |
31 | {{{ makeDynCall('vi', 'callback.func') }}}(callback.arg) | |
32 | } | |
33 | _pthread_cleanup_push.level = __ATEXIT__.length; | |
34 | }, | |
35 | ||
36 | 19 | // When pthreads is not enabled, we can't use the Atomics futex api to do |
37 | 20 | // proper sleeps, so simulate a busy spin wait loop instead. |
38 | 21 | emscripten_thread_sleep__deps: ['emscripten_get_now'], |
42 | 25 | // Do nothing. |
43 | 26 | } |
44 | 27 | }, |
28 | ||
29 | __cxa_thread_atexit: 'atexit', | |
30 | __cxa_thread_atexit_impl: 'atexit', | |
45 | 31 | }; |
46 | 32 | |
47 | 33 | mergeInto(LibraryManager.library, LibraryPThreadStub); |
1347 | 1347 | return SDL.version; |
1348 | 1348 | }, |
1349 | 1349 | |
1350 | SDL_Init__deps: ['$zeroMemory'], | |
1350 | 1351 | SDL_Init__proxy: 'sync', |
1351 | 1352 | SDL_Init__sig: 'ii', |
1352 | 1353 | SDL_Init__docs: '/** @param{number=} initFlags */', |
1367 | 1368 | |
1368 | 1369 | window.addEventListener("unload", SDL.receiveEvent); |
1369 | 1370 | SDL.keyboardState = _malloc(0x10000); // Our SDL needs 512, but 64K is safe for older SDLs |
1370 | _memset(SDL.keyboardState, 0, 0x10000); | |
1371 | zeroMemory(SDL.keyboardState, 0x10000); | |
1371 | 1372 | // Initialize this structure carefully for closure |
1372 | 1373 | SDL.DOMEventToSDLEvent['keydown'] = 0x300 /* SDL_KEYDOWN */; |
1373 | 1374 | SDL.DOMEventToSDLEvent['keyup'] = 0x301 /* SDL_KEYUP */; |
2413 | 2414 | |
2414 | 2415 | // SDL_Audio |
2415 | 2416 | |
2416 | SDL_OpenAudio__deps: ['$autoResumeAudioContext'], | |
2417 | SDL_OpenAudio__deps: ['$autoResumeAudioContext', '$safeSetTimeout'], | |
2417 | 2418 | SDL_OpenAudio__proxy: 'sync', |
2418 | 2419 | SDL_OpenAudio__sig: 'iii', |
2419 | 2420 | SDL_OpenAudio: function(desired, obtained) { |
2535 | 2536 | |
2536 | 2537 | if (SDL.audio.numAudioTimersPending < SDL.audio.numSimultaneouslyQueuedBuffers) { |
2537 | 2538 | ++SDL.audio.numAudioTimersPending; |
2538 | SDL.audio.timer = Browser.safeSetTimeout(SDL.audio.caller, Math.max(0.0, 1000.0*(secsUntilNextPlayStart-preemptBufferFeedSecs))); | |
2539 | SDL.audio.timer = safeSetTimeout(SDL.audio.caller, Math.max(0.0, 1000.0*(secsUntilNextPlayStart-preemptBufferFeedSecs))); | |
2539 | 2540 | |
2540 | 2541 | // If we are risking starving, immediately queue an extra buffer. |
2541 | 2542 | if (SDL.audio.numAudioTimersPending < SDL.audio.numSimultaneouslyQueuedBuffers) { |
2542 | 2543 | ++SDL.audio.numAudioTimersPending; |
2543 | Browser.safeSetTimeout(SDL.audio.caller, 1.0); | |
2544 | safeSetTimeout(SDL.audio.caller, 1.0); | |
2544 | 2545 | } |
2545 | 2546 | } |
2546 | 2547 | }; |
2637 | 2638 | }, |
2638 | 2639 | |
2639 | 2640 | SDL_PauseAudio__proxy: 'sync', |
2641 | SDL_PauseAudio__deps: ['$safeSetTimeout'], | |
2640 | 2642 | SDL_PauseAudio__sig: 'vi', |
2641 | 2643 | SDL_PauseAudio: function(pauseOn) { |
2642 | 2644 | if (!SDL.audio) { |
2651 | 2653 | } else if (!SDL.audio.timer) { |
2652 | 2654 | // Start the audio playback timer callback loop. |
2653 | 2655 | SDL.audio.numAudioTimersPending = 1; |
2654 | SDL.audio.timer = Browser.safeSetTimeout(SDL.audio.caller, 1); | |
2656 | SDL.audio.timer = safeSetTimeout(SDL.audio.caller, 1); | |
2655 | 2657 | } |
2656 | 2658 | SDL.audio.paused = pauseOn; |
2657 | 2659 | }, |
4 | 4 | */ |
5 | 5 | |
6 | 6 | // 'use strict' |
7 | var funs = { | |
7 | var LibrarySignals = { | |
8 | 8 | _sigalrm_handler: 0, |
9 | 9 | |
10 | signal__deps: ['_sigalrm_handler'], | |
11 | signal: function(sig, func) { | |
10 | __sigaction__deps: ['_sigalrm_handler'], | |
11 | __sigaction: function(sig, act, oldact) { | |
12 | //int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); | |
12 | 13 | if (sig == {{{ cDefine('SIGALRM') }}}) { |
13 | __sigalrm_handler = func; | |
14 | } else { | |
14 | __sigalrm_handler = {{{ makeGetValue('act', '0', 'i32') }}}; | |
15 | return 0; | |
16 | } | |
15 | 17 | #if ASSERTIONS |
16 | err('Calling stub instead of signal()'); | |
17 | #endif | |
18 | } | |
19 | return 0; | |
20 | }, | |
21 | bsd_signal__sig: 'iii', | |
22 | bsd_signal: 'signal', | |
23 | sigemptyset: function(set) { | |
24 | {{{ makeSetValue('set', '0', '0', 'i32') }}}; | |
25 | return 0; | |
26 | }, | |
27 | sigfillset: function(set) { | |
28 | {{{ makeSetValue('set', '0', '-1>>>0', 'i32') }}}; | |
29 | return 0; | |
30 | }, | |
31 | sigaddset: function(set, signum) { | |
32 | {{{ makeSetValue('set', '0', makeGetValue('set', '0', 'i32') + '| (1 << (signum-1))', 'i32') }}}; | |
33 | return 0; | |
34 | }, | |
35 | sigdelset: function(set, signum) { | |
36 | {{{ makeSetValue('set', '0', makeGetValue('set', '0', 'i32') + '& (~(1 << (signum-1)))', 'i32') }}}; | |
37 | return 0; | |
38 | }, | |
39 | sigismember: function(set, signum) { | |
40 | return {{{ makeGetValue('set', '0', 'i32') }}} & (1 << (signum-1)); | |
41 | }, | |
42 | sigaction: function(signum, act, oldact) { | |
43 | //int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); | |
44 | #if ASSERTIONS | |
45 | err('Calling stub instead of sigaction()'); | |
18 | err('sigaction: signal type not supported: this is a no-op.'); | |
46 | 19 | #endif |
47 | 20 | return 0; |
48 | 21 | }, |
49 | sigprocmask: function() { | |
50 | #if ASSERTIONS | |
51 | err('Calling stub instead of sigprocmask()'); | |
52 | #endif | |
53 | return 0; | |
54 | }, | |
22 | sigaction__sig: 'viii', | |
23 | sigaction: '__sigaction', | |
24 | ||
55 | 25 | // pthread_sigmask - examine and change mask of blocked signals |
56 | 26 | pthread_sigmask: function(how, set, oldset) { |
57 | 27 | err('pthread_sigmask() is not supported: this is a no-op.'); |
58 | 28 | return 0; |
59 | 29 | }, |
60 | __libc_current_sigrtmin: function() { | |
61 | #if ASSERTIONS | |
62 | err('Calling stub instead of __libc_current_sigrtmin'); | |
63 | #endif | |
64 | return 0; | |
65 | }, | |
66 | __libc_current_sigrtmax: function() { | |
67 | #if ASSERTIONS | |
68 | err('Calling stub instead of __libc_current_sigrtmax'); | |
69 | #endif | |
70 | return 0; | |
71 | }, | |
30 | ||
72 | 31 | kill__deps: ['$ERRNO_CODES', '$setErrNo'], |
73 | 32 | kill: function(pid, sig) { |
74 | 33 | // http://pubs.opengroup.org/onlinepubs/000095399/functions/kill.html |
75 | 34 | // Makes no sense in a single-process environment. |
76 | // Should kill itself somtimes depending on `pid` | |
35 | // Should kill itself somtimes depending on `pid` | |
77 | 36 | #if ASSERTIONS |
78 | 37 | err('Calling stub instead of kill()'); |
79 | 38 | #endif |
81 | 40 | return -1; |
82 | 41 | }, |
83 | 42 | |
84 | killpg__deps: ['$ERRNO_CODES', '$setErrNo'], | |
85 | killpg: function() { | |
86 | #if ASSERTIONS | |
87 | err('Calling stub instead of killpg()'); | |
88 | #endif | |
89 | setErrNo(ERRNO_CODES.EPERM); | |
90 | return -1; | |
91 | }, | |
92 | 43 | siginterrupt: function() { |
93 | 44 | #if ASSERTIONS |
94 | 45 | err('Calling stub instead of siginterrupt()'); |
101 | 52 | #if ASSERTIONS |
102 | 53 | err('Calling stub instead of raise()'); |
103 | 54 | #endif |
104 | setErrNo(ERRNO_CODES.ENOSYS); | |
105 | #if ASSERTIONS | |
106 | warnOnce('raise() returning an error as we do not support it'); | |
107 | #endif | |
55 | setErrNo(ERRNO_CODES.ENOSYS); | |
108 | 56 | return -1; |
109 | 57 | }, |
110 | 58 | |
115 | 63 | if (__sigalrm_handler) {{{ makeDynCall('vi', '__sigalrm_handler') }}}(0); |
116 | 64 | }, seconds*1000); |
117 | 65 | }, |
118 | ualarm: function() { | |
119 | throw 'ualarm() is not implemented yet'; | |
120 | }, | |
121 | setitimer: function() { | |
122 | throw 'setitimer() is not implemented yet'; | |
123 | }, | |
124 | getitimer: function() { | |
125 | throw 'getitimer() is not implemented yet'; | |
126 | }, | |
127 | ||
128 | pause__deps: ['$setErrNo', '$ERRNO_CODES'], | |
129 | pause: function() { | |
130 | // int pause(void); | |
131 | // http://pubs.opengroup.org/onlinepubs/000095399/functions/pause.html | |
132 | // We don't support signals, so we return immediately. | |
133 | #if ASSERTIONS | |
134 | err('Calling stub instead of pause()'); | |
135 | #endif | |
136 | setErrNo(ERRNO_CODES.EINTR); | |
137 | return -1; | |
138 | }, | |
139 | 66 | |
140 | 67 | sigpending: function(set) { |
141 | 68 | {{{ makeSetValue('set', 0, 0, 'i32') }}}; |
142 | 69 | return 0; |
143 | 70 | }, |
144 | 71 | |
145 | sigwait: function(set, sig) { | |
72 | //int sigtimedwait(const sigset_t *restrict mask, siginfo_t *restrict si, const struct timespec *restrict timeout) | |
73 | sigtimedwait: function(set, sig, timeout) { | |
146 | 74 | // POSIX SIGNALS are not supported |
147 | 75 | // if set contains an invalid signal number, EINVAL is returned |
148 | 76 | // in our case we return EINVAL all the time |
151 | 79 | #endif |
152 | 80 | return {{{ cDefine('EINVAL') }}}; |
153 | 81 | } |
154 | ||
155 | //signalfd | |
156 | //ppoll | |
157 | //epoll_pwait | |
158 | //pselect | |
159 | //sigvec | |
160 | //sigmask | |
161 | //sigblock | |
162 | //sigsetmask | |
163 | //siggetmask | |
164 | //sigsuspend | |
165 | //siginterrupt | |
166 | //sigqueue | |
167 | //sysv_signal | |
168 | //signal | |
169 | //pthread_kill | |
170 | //gsignal | |
171 | //ssignal | |
172 | //psignal | |
173 | //psiginfo | |
174 | //sigpause | |
175 | //sigisemptyset | |
176 | //sigtimedwait | |
177 | //sigwaitinfo | |
178 | //sigreturn | |
179 | //sigstack | |
180 | //sigaltstack(2) | |
181 | //sigsetops(3), | |
182 | //sighold | |
183 | //sigrelse | |
184 | //sigignore | |
185 | //sigset | |
186 | 82 | }; |
187 | 83 | |
188 | mergeInto(LibraryManager.library, funs); | |
84 | mergeInto(LibraryManager.library, LibrarySignals); |
225 | 225 | } |
226 | 226 | }, |
227 | 227 | |
228 | $syscallMmap2__deps: ['$SYSCALLS', | |
228 | $syscallMmap2__deps: ['$SYSCALLS', '$zeroMemory', '$mmapAlloc', | |
229 | 229 | #if FILESYSTEM && SYSCALLS_REQUIRE_FILESYSTEM |
230 | 230 | '$FS', |
231 | 231 | #endif |
244 | 244 | // but it is widely used way to allocate memory pages on Linux, BSD and Mac. |
245 | 245 | // In this case fd argument is ignored. |
246 | 246 | if ((flags & {{{ cDefine('MAP_ANONYMOUS') }}}) !== 0) { |
247 | ptr = _memalign({{{ WASM_PAGE_SIZE }}}, len); | |
247 | ptr = mmapAlloc(len); | |
248 | 248 | if (!ptr) return -{{{ cDefine('ENOMEM') }}}; |
249 | _memset(ptr, 0, len); | |
250 | 249 | allocated = true; |
251 | 250 | } else { |
252 | 251 | #if FILESYSTEM && SYSCALLS_REQUIRE_FILESYSTEM |
275 | 274 | #if CAN_ADDRESS_2GB |
276 | 275 | addr >>>= 0; |
277 | 276 | #endif |
278 | if ((addr | 0) === {{{ cDefine('MAP_FAILED') }}} || len === 0) { | |
279 | return -{{{ cDefine('EINVAL') }}}; | |
280 | } | |
281 | 277 | // TODO: support unmmap'ing parts of allocations |
282 | 278 | var info = SYSCALLS.mappings[addr]; |
283 | if (!info) return 0; | |
279 | if (len === 0 || !info) { | |
280 | return -{{{ cDefine('EINVAL') }}}; | |
281 | } | |
284 | 282 | if (len === info.len) { |
285 | 283 | #if FILESYSTEM && SYSCALLS_REQUIRE_FILESYSTEM |
286 | 284 | var stream = FS.getStream(info.fd); |
304 | 302 | return 0; |
305 | 303 | }, |
306 | 304 | |
307 | __sys_exit: function(status) { | |
308 | exit(status); | |
309 | // no return | |
310 | }, | |
311 | 305 | __sys_open: function(path, flags, varargs) { |
312 | 306 | var pathname = SYSCALLS.getStr(path); |
313 | 307 | var mode = varargs ? SYSCALLS.get() : 0; |
393 | 387 | {{{ makeSetValue('fdPtr', 4, 'res.writable_fd', 'i32') }}}; |
394 | 388 | |
395 | 389 | return 0; |
396 | }, | |
397 | __sys_acct__nothrow: true, | |
398 | __sys_acct__proxy: false, | |
399 | __sys_acct: function(filename) { | |
400 | return -{{{ cDefine('ENOSYS') }}}; // unsupported features | |
401 | 390 | }, |
402 | 391 | __sys_ioctl: function(fd, op, varargs) { |
403 | 392 | #if SYSCALLS_REQUIRE_FILESYSTEM == 0 |
493 | 482 | __sys_setrlimit: function(varargs) { |
494 | 483 | return 0; // no-op |
495 | 484 | }, |
485 | __sys_getrusage__deps: ['$zeroMemory'], | |
496 | 486 | __sys_getrusage: function(who, usage) { |
497 | #if SYSCALL_DEBUG | |
498 | err('warning: untested syscall'); | |
499 | #endif | |
500 | _memset(usage, 0, {{{ C_STRUCTS.rusage.__size__ }}}); | |
487 | zeroMemory(usage, {{{ C_STRUCTS.rusage.__size__ }}}); | |
501 | 488 | {{{ makeSetValue('usage', C_STRUCTS.rusage.ru_utime.tv_sec, '1', 'i32') }}}; // fake some values |
502 | 489 | {{{ makeSetValue('usage', C_STRUCTS.rusage.ru_utime.tv_usec, '2', 'i32') }}}; |
503 | 490 | {{{ makeSetValue('usage', C_STRUCTS.rusage.ru_stime.tv_sec, '3', 'i32') }}}; |
572 | 559 | assert(!errno); |
573 | 560 | #endif |
574 | 561 | return 0; |
575 | }, | |
576 | __sys_socketpair: function() { | |
577 | #if SYSCALL_DEBUG | |
578 | err('unsupported syscall: __sys_socketpair'); | |
579 | #endif | |
580 | return -{{{ cDefine('ENOSYS') }}}; | |
581 | 562 | }, |
582 | 563 | __sys_setsockopt: function(fd) { |
583 | 564 | return -{{{ cDefine('ENOPROTOOPT') }}}; // The option is unknown at the level indicated. |
762 | 743 | return bytesRead; |
763 | 744 | }, |
764 | 745 | #endif // ~PROXY_POSIX_SOCKETS==0 |
765 | __sys_setitimer__nothrow: true, | |
766 | __sys_setitimer__proxy: false, | |
767 | __sys_setitimer: function(which, new_value, old_value) { | |
768 | return -{{{ cDefine('ENOSYS') }}}; // unsupported feature | |
769 | }, | |
770 | __sys_wait4__proxy: false, | |
771 | __sys_wait4: function(pid, wstart, options, rusage) { | |
772 | return -{{{ cDefine('ENOSYS') }}}; // unsupported feature | |
773 | }, | |
774 | 746 | __sys_setdomainname__nothrow: true, |
775 | 747 | __sys_setdomainname__proxy: false, |
776 | 748 | __sys_setdomainname: function(name, size) { |
798 | 770 | #endif |
799 | 771 | return 0; |
800 | 772 | }, |
801 | __sys_mprotect__nothrow: true, | |
802 | __sys_mprotect__proxy: false, | |
803 | 773 | __sys_mprotect: function(addr, len, size) { |
804 | 774 | return 0; // let's not and say we did |
805 | 775 | }, |
914 | 884 | var stream = SYSCALLS.getStreamFromFD(fd); |
915 | 885 | return 0; // we can't do anything synchronously; the in-memory FS is already synced to |
916 | 886 | }, |
917 | __sys_mlock__nothrow: true, | |
918 | __sys_mlock__proxy: false, | |
919 | 887 | __sys_mlock__sig: 'iii', |
920 | 888 | __sys_mlock: function(addr, len) { |
921 | 889 | return 0; |
922 | 890 | }, |
923 | __sys_munlock__nothrow: true, | |
924 | __sys_munlock__proxy: false, | |
925 | 891 | __sys_munlock__sig: 'iii', |
926 | 892 | __sys_munlock: function(addr, len) { |
927 | 893 | return 0; |
928 | 894 | }, |
929 | __sys_mlockall__nothrow: true, | |
930 | __sys_mlockall__proxy: false, | |
931 | 895 | __sys_mlockall__sig: 'ii', |
932 | 896 | __sys_mlockall: function(flags) { |
933 | 897 | return 0; |
934 | 898 | }, |
935 | __sys_munlockall__nothrow: true, | |
936 | __sys_munlockall__proxy: false, | |
937 | 899 | __sys_munlockall__sig: 'i', |
938 | 900 | __sys_munlockall: function() { |
939 | 901 | return 0; |
940 | 902 | }, |
941 | __sys_mremap__nothrow: true, | |
942 | __sys_mremap__proxy: false, | |
943 | 903 | __sys_mremap: function(old_addr, old_size, new_size, flags) { |
944 | 904 | return -{{{ cDefine('ENOMEM') }}}; // never succeed |
945 | 905 | }, |
963 | 923 | } |
964 | 924 | return nonzero; |
965 | 925 | }, |
966 | __sys_rt_sigqueueinfo__nothrow: true, | |
967 | __sys_rt_sigqueueinfo__proxy: false, | |
968 | 926 | __sys_rt_sigqueueinfo: function(tgid, pid, uinfo) { |
969 | #if SYSCALL_DEBUG | |
970 | err('warning: ignoring SYS_rt_sigqueueinfo'); | |
971 | #endif | |
972 | 927 | return 0; |
973 | 928 | }, |
974 | 929 | |
981 | 936 | return buf; |
982 | 937 | }, |
983 | 938 | __sys_ugetrlimit: function(resource, rlim) { |
984 | #if SYSCALL_DEBUG | |
985 | err('warning: untested syscall'); | |
986 | #endif | |
987 | 939 | {{{ makeSetValue('rlim', C_STRUCTS.rlimit.rlim_cur, '-1', 'i32') }}}; // RLIM_INFINITY |
988 | 940 | {{{ makeSetValue('rlim', C_STRUCTS.rlimit.rlim_cur + 4, '-1', 'i32') }}}; // RLIM_INFINITY |
989 | 941 | {{{ makeSetValue('rlim', C_STRUCTS.rlimit.rlim_max, '-1', 'i32') }}}; // RLIM_INFINITY |
1091 | 1043 | __sys_getresuid32__nothrow: true, |
1092 | 1044 | __sys_getresuid32__proxy: false, |
1093 | 1045 | __sys_getresuid32: '__sys_getresgid32', |
1094 | __sys_getresgid32__nothrow: true, | |
1095 | __sys_getresgid32__proxy: false, | |
1096 | 1046 | __sys_getresgid32: function(ruid, euid, suid) { |
1097 | #if SYSCALL_DEBUG | |
1098 | err('warning: untested syscall'); | |
1099 | #endif | |
1100 | 1047 | {{{ makeSetValue('ruid', '0', '0', 'i32') }}}; |
1101 | 1048 | {{{ makeSetValue('euid', '0', '0', 'i32') }}}; |
1102 | 1049 | {{{ makeSetValue('suid', '0', '0', 'i32') }}}; |
1103 | 1050 | return 0; |
1104 | 1051 | }, |
1105 | __sys_mincore__nothrow: true, | |
1106 | __sys_mincore__proxy: false, | |
1107 | __sys_mincore: function(addr, length, vec) { | |
1108 | return -{{{ cDefine('ENOSYS') }}}; // unsupported feature | |
1109 | }, | |
1110 | __sys_madvise1__nothrow: true, | |
1111 | __sys_madvise1__proxy: false, | |
1112 | 1052 | __sys_madvise1: function(addr, length, advice) { |
1113 | return 0; // advice is welcome, but ignored | |
1053 | // advice is welcome, but ignored | |
1054 | return 0; | |
1114 | 1055 | }, |
1115 | 1056 | __sys_getdents64: function(fd, dirp, count) { |
1116 | 1057 | var stream = SYSCALLS.getStreamFromFD(fd) |
1212 | 1153 | #endif // SYSCALLS_REQUIRE_FILESYSTEM |
1213 | 1154 | }, |
1214 | 1155 | |
1215 | #if MINIMAL_RUNTIME | |
1216 | __sys_exit_group__deps: ['$exit'], | |
1217 | #endif | |
1218 | __sys_exit_group: function(status) { | |
1219 | exit(status); | |
1220 | return 0; | |
1221 | }, | |
1222 | 1156 | __sys_statfs64: function(path, size, buf) { |
1223 | 1157 | path = SYSCALLS.getStr(path); |
1224 | 1158 | #if ASSERTIONS |
1359 | 1293 | path = SYSCALLS.calculateAt(dirfd, path); |
1360 | 1294 | return SYSCALLS.doAccess(path, amode); |
1361 | 1295 | }, |
1362 | __sys_pselect6__nothrow: true, | |
1363 | __sys_pselect6__proxy: false, | |
1364 | __sys_pselect6: function() { | |
1365 | return -{{{ cDefine('ENOSYS') }}}; // unsupported feature | |
1366 | }, | |
1367 | 1296 | __sys_utimensat: function(dirfd, path, times, flags) { |
1368 | #if SYSCALL_DEBUG | |
1369 | err('warning: untested syscall'); | |
1370 | #endif | |
1371 | 1297 | path = SYSCALLS.getStr(path); |
1372 | 1298 | #if ASSERTIONS |
1373 | 1299 | assert(flags === 0); |
1381 | 1307 | nanoseconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_nsec, 'i32') }}}; |
1382 | 1308 | var mtime = (seconds*1000) + (nanoseconds/(1000*1000)); |
1383 | 1309 | FS.utime(path, atime, mtime); |
1384 | return 0; | |
1310 | return 0; | |
1385 | 1311 | }, |
1386 | 1312 | __sys_fallocate: function(fd, mode, off_low, off_high, len_low, len_high) { |
1387 | 1313 | var stream = SYSCALLS.getStreamFromFD(fd) |
1395 | 1321 | }, |
1396 | 1322 | __sys_dup3: function(fd, suggestFD, flags) { |
1397 | 1323 | #if SYSCALL_DEBUG |
1398 | err('warning: untested syscall'); | |
1324 | err('warning: untested syscall: dup3'); | |
1399 | 1325 | #endif |
1400 | 1326 | var old = SYSCALLS.getStreamFromFD(fd); |
1401 | 1327 | #if ASSERTIONS |
1404 | 1330 | if (old.fd === suggestFD) return -{{{ cDefine('EINVAL') }}}; |
1405 | 1331 | return SYSCALLS.doDup(old.path, old.flags, suggestFD); |
1406 | 1332 | }, |
1407 | __sys_pipe2__nothrow: true, | |
1408 | __sys_pipe2__proxy: false, | |
1409 | __sys_pipe2: function(fds, flags) { | |
1410 | return -{{{ cDefine('ENOSYS') }}}; // unsupported feature | |
1411 | }, | |
1412 | ||
1413 | __sys_recvmmsg__nothrow: true, | |
1414 | __sys_recvmmsg__proxy: false, | |
1415 | __sys_recvmmsg: function(sockfd, msgvec, vlen, flags) { | |
1416 | #if SYSCALL_DEBUG | |
1417 | err('warning: ignoring SYS_recvmmsg'); | |
1418 | #endif | |
1419 | return 0; | |
1420 | }, | |
1333 | ||
1421 | 1334 | __sys_prlimit64: function(pid, resource, new_limit, old_limit) { |
1422 | 1335 | if (old_limit) { // just report no limits |
1423 | 1336 | {{{ makeSetValue('old_limit', C_STRUCTS.rlimit.rlim_cur, '-1', 'i32') }}}; // RLIM_INFINITY |
1425 | 1338 | {{{ makeSetValue('old_limit', C_STRUCTS.rlimit.rlim_max, '-1', 'i32') }}}; // RLIM_INFINITY |
1426 | 1339 | {{{ makeSetValue('old_limit', C_STRUCTS.rlimit.rlim_max + 4, '-1', 'i32') }}}; // RLIM_INFINITY |
1427 | 1340 | } |
1428 | return 0; | |
1429 | }, | |
1430 | __sys_sendmmsg__nothrow: true, | |
1431 | __sys_sendmmsg__proxy: false, | |
1432 | __sys_sendmmsg: function(sockfd, msg, flags) { | |
1433 | #if SYSCALL_DEBUG | |
1434 | err('warning: ignoring SYS_sendmmsg'); | |
1435 | #endif | |
1436 | 1341 | return 0; |
1437 | 1342 | }, |
1438 | 1343 | }; |
1541 | 1446 | wrapSyscallFunction(x, SyscallsLibrary, false); |
1542 | 1447 | } |
1543 | 1448 | |
1449 | function unimplementedSycall(name) { | |
1450 | SyscallsLibrary[name + '__nothrow'] = true; | |
1451 | SyscallsLibrary[name + '__proxy'] = false; | |
1452 | SyscallsLibrary[name + '__unimplemented'] = true; | |
1453 | msg = `\n err('warning: unsupported syscall: ${name}');`; | |
1454 | if (SyscallsLibrary[name]) { | |
1455 | SyscallsLibrary[name] = modifyFunction(SyscallsLibrary[name].toString(), (name, args, body) => { | |
1456 | return `function(${args}) {\n ${msg}${body}\n}\n`; | |
1457 | }); | |
1458 | } else { | |
1459 | errcode = cDefine('ENOSYS'); | |
1460 | SyscallsLibrary[name] = `function() {\n ${msg}return -${errcode};\n}`; | |
1461 | } | |
1462 | } | |
1463 | ||
1464 | [ | |
1465 | '__sys_acct', | |
1466 | '__sys_getresgid32', | |
1467 | '__sys_getrusage', | |
1468 | '__sys_madvise1', | |
1469 | '__sys_mincore', | |
1470 | '__sys_mlock', | |
1471 | '__sys_mlockall', | |
1472 | '__sys_mprotect', | |
1473 | '__sys_mremap', | |
1474 | '__sys_munlock', | |
1475 | '__sys_munlockall', | |
1476 | '__sys_pipe2', | |
1477 | '__sys_prlimit64', | |
1478 | '__sys_pselect6', | |
1479 | '__sys_recvmmsg', | |
1480 | '__sys_rt_sigqueueinfo', | |
1481 | '__sys_sendmmsg', | |
1482 | '__sys_setitimer', | |
1483 | '__sys_getitimer', | |
1484 | '__sys_shutdown', | |
1485 | '__sys_socketpair', | |
1486 | '__sys_setsockopt', | |
1487 | '__sys_ugetrlimit', | |
1488 | '__sys_wait4', | |
1489 | '__sys_pause', | |
1490 | ].forEach(unimplementedSycall); | |
1491 | ||
1544 | 1492 | mergeInto(LibraryManager.library, SyscallsLibrary); |
107 | 107 | if (ENVIRONMENT_IS_NODE) { |
108 | 108 | // we will read data by chunks of BUFSIZE |
109 | 109 | var BUFSIZE = 256; |
110 | var buf = Buffer.alloc ? Buffer.alloc(BUFSIZE) : new Buffer(BUFSIZE); | |
110 | var buf = Buffer.alloc(BUFSIZE); | |
111 | 111 | var bytesRead = 0; |
112 | 112 | |
113 | 113 | try { |
7 | 7 | |
8 | 8 | mergeInto(LibraryManager.library, { |
9 | 9 | // Clear a 'compact' UUID. |
10 | uuid_clear__deps: ['$zeroMemory'], | |
10 | 11 | uuid_clear: function(uu) { |
11 | 12 | // void uuid_clear(uuid_t uu); |
12 | _memset(uu, 0, 16); | |
13 | zeroMemory(uu, 16); | |
13 | 14 | }, |
14 | 15 | |
15 | 16 | // Compare whether or not two 'compact' UUIDs are the same. |
0 | /** | |
1 | * @license | |
2 | * Copyright 2011 The Emscripten Authors | |
3 | * SPDX-License-Identifier: MIT | |
4 | */ | |
5 | ||
6 | var LibraryWget = { | |
7 | $wget: { | |
8 | wgetRequests: {}, | |
9 | nextWgetRequestHandle: 0, | |
10 | ||
11 | getNextWgetRequestHandle: function() { | |
12 | var handle = wget.nextWgetRequestHandle; | |
13 | wget.nextWgetRequestHandle++; | |
14 | return handle; | |
15 | }, | |
16 | }, | |
17 | ||
18 | emscripten_async_wget__deps: ['$PATH_FS', '$wget', '$callUserCallback', '$Browser', | |
19 | #if !MINIMAL_RUNTIME | |
20 | '$runtimeKeepalivePush', '$runtimeKeepalivePop', | |
21 | #endif | |
22 | ], | |
23 | emscripten_async_wget__proxy: 'sync', | |
24 | emscripten_async_wget__sig: 'viiii', | |
25 | emscripten_async_wget: function(url, file, onload, onerror) { | |
26 | {{{ runtimeKeepalivePush() }}} | |
27 | ||
28 | var _url = UTF8ToString(url); | |
29 | var _file = UTF8ToString(file); | |
30 | _file = PATH_FS.resolve(_file); | |
31 | function doCallback(callback) { | |
32 | if (callback) { | |
33 | {{{ runtimeKeepalivePop() }}} | |
34 | callUserCallback(function() { | |
35 | var stack = stackSave(); | |
36 | {{{ makeDynCall('vi', 'callback') }}}(allocate(intArrayFromString(_file), ALLOC_STACK)); | |
37 | stackRestore(stack); | |
38 | }); | |
39 | } | |
40 | } | |
41 | var destinationDirectory = PATH.dirname(_file); | |
42 | FS.createPreloadedFile( | |
43 | destinationDirectory, | |
44 | PATH.basename(_file), | |
45 | _url, true, true, | |
46 | function() { | |
47 | doCallback(onload); | |
48 | }, | |
49 | function() { | |
50 | doCallback(onerror); | |
51 | }, | |
52 | false, // dontCreateFile | |
53 | false, // canOwn | |
54 | function() { // preFinish | |
55 | // if a file exists there, we overwrite it | |
56 | try { | |
57 | FS.unlink(_file); | |
58 | } catch (e) {} | |
59 | // if the destination directory does not yet exist, create it | |
60 | FS.mkdirTree(destinationDirectory); | |
61 | } | |
62 | ); | |
63 | }, | |
64 | ||
65 | emscripten_async_wget_data__deps: ['$asyncLoad', 'malloc', 'free', '$callUserCallback', | |
66 | #if !MINIMAL_RUNTIME | |
67 | '$runtimeKeepalivePush', '$runtimeKeepalivePop', | |
68 | #endif | |
69 | ], | |
70 | emscripten_async_wget_data__proxy: 'sync', | |
71 | emscripten_async_wget_data__sig: 'viiii', | |
72 | emscripten_async_wget_data: function(url, arg, onload, onerror) { | |
73 | {{{ runtimeKeepalivePush() }}} | |
74 | asyncLoad(UTF8ToString(url), function(byteArray) { | |
75 | {{{ runtimeKeepalivePop() }}} | |
76 | callUserCallback(function() { | |
77 | var buffer = _malloc(byteArray.length); | |
78 | HEAPU8.set(byteArray, buffer); | |
79 | {{{ makeDynCall('viii', 'onload') }}}(arg, buffer, byteArray.length); | |
80 | _free(buffer); | |
81 | }); | |
82 | }, function() { | |
83 | if (onerror) { | |
84 | {{{ runtimeKeepalivePop() }}} | |
85 | callUserCallback(function() { | |
86 | {{{ makeDynCall('vi', 'onerror') }}}(arg); | |
87 | }); | |
88 | } | |
89 | }, true /* no need for run dependency, this is async but will not do any prepare etc. step */ ); | |
90 | }, | |
91 | ||
92 | emscripten_async_wget2__deps: ['$PATH_FS', '$wget', | |
93 | #if !MINIMAL_RUNTIME | |
94 | '$runtimeKeepalivePush', '$runtimeKeepalivePop', | |
95 | #endif | |
96 | ], | |
97 | emscripten_async_wget2__proxy: 'sync', | |
98 | emscripten_async_wget2__sig: 'iiiiiiiii', | |
99 | emscripten_async_wget2: function(url, file, request, param, arg, onload, onerror, onprogress) { | |
100 | {{{ runtimeKeepalivePush() }}} | |
101 | ||
102 | var _url = UTF8ToString(url); | |
103 | var _file = UTF8ToString(file); | |
104 | _file = PATH_FS.resolve(_file); | |
105 | var _request = UTF8ToString(request); | |
106 | var _param = UTF8ToString(param); | |
107 | var index = _file.lastIndexOf('/'); | |
108 | ||
109 | var http = new XMLHttpRequest(); | |
110 | http.open(_request, _url, true); | |
111 | http.responseType = 'arraybuffer'; | |
112 | ||
113 | var handle = wget.getNextWgetRequestHandle(); | |
114 | ||
115 | var destinationDirectory = PATH.dirname(_file); | |
116 | ||
117 | // LOAD | |
118 | http.onload = function http_onload(e) { | |
119 | {{{ runtimeKeepalivePop() }}} | |
120 | if (http.status >= 200 && http.status < 300) { | |
121 | // if a file exists there, we overwrite it | |
122 | try { | |
123 | FS.unlink(_file); | |
124 | } catch (e) {} | |
125 | // if the destination directory does not yet exist, create it | |
126 | FS.mkdirTree(destinationDirectory); | |
127 | ||
128 | FS.createDataFile( _file.substr(0, index), _file.substr(index + 1), new Uint8Array(/** @type{ArrayBuffer}*/(http.response)), true, true, false); | |
129 | if (onload) { | |
130 | var stack = stackSave(); | |
131 | {{{ makeDynCall('viii', 'onload') }}}(handle, arg, allocate(intArrayFromString(_file), ALLOC_STACK)); | |
132 | stackRestore(stack); | |
133 | } | |
134 | } else { | |
135 | if (onerror) {{{ makeDynCall('viii', 'onerror') }}}(handle, arg, http.status); | |
136 | } | |
137 | ||
138 | delete wget.wgetRequests[handle]; | |
139 | }; | |
140 | ||
141 | // ERROR | |
142 | http.onerror = function http_onerror(e) { | |
143 | {{{ runtimeKeepalivePop() }}} | |
144 | if (onerror) {{{ makeDynCall('viii', 'onerror') }}}(handle, arg, http.status); | |
145 | delete wget.wgetRequests[handle]; | |
146 | }; | |
147 | ||
148 | // PROGRESS | |
149 | http.onprogress = function http_onprogress(e) { | |
150 | if (e.lengthComputable || (e.lengthComputable === undefined && e.total != 0)) { | |
151 | var percentComplete = (e.loaded / e.total)*100; | |
152 | if (onprogress) {{{ makeDynCall('viii', 'onprogress') }}}(handle, arg, percentComplete); | |
153 | } | |
154 | }; | |
155 | ||
156 | // ABORT | |
157 | http.onabort = function http_onabort(e) { | |
158 | {{{ runtimeKeepalivePop() }}} | |
159 | delete wget.wgetRequests[handle]; | |
160 | }; | |
161 | ||
162 | if (_request == "POST") { | |
163 | //Send the proper header information along with the request | |
164 | http.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); | |
165 | http.send(_param); | |
166 | } else { | |
167 | http.send(null); | |
168 | } | |
169 | ||
170 | wget.wgetRequests[handle] = http; | |
171 | ||
172 | return handle; | |
173 | }, | |
174 | ||
175 | emscripten_async_wget2_data__deps: ['$wget', 'malloc', 'free'], | |
176 | emscripten_async_wget2_data__proxy: 'sync', | |
177 | emscripten_async_wget2_data__sig: 'iiiiiiiii', | |
178 | emscripten_async_wget2_data: function(url, request, param, arg, free, onload, onerror, onprogress) { | |
179 | var _url = UTF8ToString(url); | |
180 | var _request = UTF8ToString(request); | |
181 | var _param = UTF8ToString(param); | |
182 | ||
183 | var http = new XMLHttpRequest(); | |
184 | http.open(_request, _url, true); | |
185 | http.responseType = 'arraybuffer'; | |
186 | ||
187 | var handle = wget.getNextWgetRequestHandle(); | |
188 | ||
189 | function onerrorjs() { | |
190 | if (onerror) { | |
191 | var statusText = 0; | |
192 | if (http.statusText) { | |
193 | var len = lengthBytesUTF8(http.statusText) + 1; | |
194 | statusText = stackAlloc(len); | |
195 | stringToUTF8(http.statusText, statusText, len); | |
196 | } | |
197 | {{{ makeDynCall('viiii', 'onerror') }}}(handle, arg, http.status, statusText); | |
198 | } | |
199 | } | |
200 | ||
201 | // LOAD | |
202 | http.onload = function http_onload(e) { | |
203 | if (http.status >= 200 && http.status < 300 || (http.status === 0 && _url.substr(0,4).toLowerCase() != "http")) { | |
204 | var byteArray = new Uint8Array(/** @type{ArrayBuffer} */(http.response)); | |
205 | var buffer = _malloc(byteArray.length); | |
206 | HEAPU8.set(byteArray, buffer); | |
207 | if (onload) {{{ makeDynCall('viiii', 'onload') }}}(handle, arg, buffer, byteArray.length); | |
208 | if (free) _free(buffer); | |
209 | } else { | |
210 | onerrorjs(); | |
211 | } | |
212 | delete wget.wgetRequests[handle]; | |
213 | }; | |
214 | ||
215 | // ERROR | |
216 | http.onerror = function http_onerror(e) { | |
217 | onerrorjs(); | |
218 | delete wget.wgetRequests[handle]; | |
219 | }; | |
220 | ||
221 | // PROGRESS | |
222 | http.onprogress = function http_onprogress(e) { | |
223 | if (onprogress) {{{ makeDynCall('viiii', 'onprogress') }}}(handle, arg, e.loaded, e.lengthComputable || e.lengthComputable === undefined ? e.total : 0); | |
224 | }; | |
225 | ||
226 | // ABORT | |
227 | http.onabort = function http_onabort(e) { | |
228 | delete wget.wgetRequests[handle]; | |
229 | }; | |
230 | ||
231 | if (_request == "POST") { | |
232 | //Send the proper header information along with the request | |
233 | http.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); | |
234 | http.send(_param); | |
235 | } else { | |
236 | http.send(null); | |
237 | } | |
238 | ||
239 | wget.wgetRequests[handle] = http; | |
240 | ||
241 | return handle; | |
242 | }, | |
243 | ||
244 | emscripten_async_wget2_abort__deps: ['$wget'], | |
245 | emscripten_async_wget2_abort__proxy: 'sync', | |
246 | emscripten_async_wget2_abort__sig: 'vi', | |
247 | emscripten_async_wget2_abort: function(handle) { | |
248 | var http = wget.wgetRequests[handle]; | |
249 | if (http) { | |
250 | http.abort(); | |
251 | } | |
252 | }, | |
253 | }; | |
254 | ||
255 | mergeInto(LibraryManager.library, LibraryWget); |
85 | 85 | libraries.push('library_strings.js'); |
86 | 86 | } else { |
87 | 87 | libraries.push('library_browser.js'); |
88 | libraries.push('library_wget.js'); | |
88 | 89 | } |
89 | 90 | |
90 | 91 | if (USE_PTHREADS) { // TODO: Currently WebGL proxying makes pthreads library depend on WebGL. |
415 | 416 | 'setTempRet0', |
416 | 417 | 'callMain', |
417 | 418 | 'abort', |
419 | 'keepRuntimeAlive', | |
418 | 420 | ]; |
419 | 421 | |
420 | 422 | if (USE_OFFSET_CONVERTER) { |
24 | 24 | assert(ret.buffer); |
25 | 25 | return ret; |
26 | 26 | }; |
27 | ||
28 | readAsync = function readAsync(filename, onload, onerror) { | |
29 | #if SUPPORT_BASE64_EMBEDDING | |
30 | var ret = tryParseAsDataURI(filename); | |
31 | if (ret) { | |
32 | onload(ret); | |
33 | } | |
34 | #endif | |
35 | if (!nodeFS) nodeFS = require('fs'); | |
36 | if (!nodePath) nodePath = require('path'); | |
37 | filename = nodePath['normalize'](filename); | |
38 | nodeFS['readFile'](filename, function(err, data) { | |
39 | if (err) onerror(err); | |
40 | else onload(data.buffer); | |
41 | }); | |
42 | }; |
189 | 189 | assert(ret == 0, '_emscripten_proxy_main failed to start proxy thread: ' + ret); |
190 | 190 | #endif |
191 | 191 | #else |
192 | #if ASYNCIFY | |
193 | // if we are saving the stack, then do not call exit, we are not | |
194 | // really exiting now, just unwinding the JS stack | |
195 | if (!keepRuntimeAlive()) { | |
196 | #endif // ASYNCIFY | |
197 | // if we're not running an evented main loop, it's time to exit | |
198 | exit(ret, /* implicit = */ true); | |
199 | #if ASYNCIFY | |
200 | } | |
201 | #endif // ASYNCIFY | |
202 | } | |
203 | catch(e) { | |
204 | if (e instanceof ExitStatus) { | |
205 | // exit() throws this once it's done to make sure execution | |
206 | // has been stopped completely | |
192 | // if we're not running an evented main loop, it's time to exit | |
193 | exit(ret, /* implicit = */ true); | |
194 | } | |
195 | catch (e) { | |
196 | // Certain exception types we do not treat as errors since they are used for | |
197 | // internal control flow. | |
198 | // 1. ExitStatus, which is thrown by exit() | |
199 | // 2. "unwind", which is thrown by emscripten_unwind_to_js_event_loop() and others | |
200 | // that wish to return to JS event loop. | |
201 | if (e instanceof ExitStatus || e == 'unwind') { | |
207 | 202 | return; |
208 | } else if (e == 'unwind') { | |
209 | // running an evented main loop, don't immediately exit | |
210 | return; | |
211 | } else { | |
212 | var toLog = e; | |
213 | if (e && typeof e === 'object' && e.stack) { | |
214 | toLog = [e, e.stack]; | |
215 | } | |
216 | err('exception thrown: ' + toLog); | |
217 | quit_(1, e); | |
218 | } | |
203 | } | |
204 | // Anything else is an unexpected exception and we treat it as hard error. | |
205 | var toLog = e; | |
206 | if (e && typeof e === 'object' && e.stack) { | |
207 | toLog = [e, e.stack]; | |
208 | } | |
209 | err('exception thrown: ' + toLog); | |
210 | quit_(1, e); | |
219 | 211 | #endif // !PROXY_TO_PTHREAD |
220 | 212 | } finally { |
221 | 213 | calledMain = true; |
424 | 416 | #endif // EXIT_RUNTIME |
425 | 417 | #endif // ASSERTIONS |
426 | 418 | |
427 | // if this is just main exit-ing implicitly, and the status is 0, then we | |
428 | // don't need to do anything here and can just leave. if the status is | |
429 | // non-zero, though, then we need to report it. | |
430 | // (we may have warned about this earlier, if a situation justifies doing so) | |
431 | if (implicit && keepRuntimeAlive() && status === 0) { | |
432 | return; | |
433 | } | |
434 | ||
435 | 419 | #if USE_PTHREADS |
436 | 420 | if (!implicit) { |
437 | 421 | if (ENVIRONMENT_IS_PTHREAD) { |
23 | 23 | var ret = _main(); |
24 | 24 | |
25 | 25 | #if EXIT_RUNTIME |
26 | #if USE_PTHREADS | |
27 | PThread.runExitHandlers(); | |
28 | #endif | |
26 | 29 | callRuntimeCallbacks(__ATEXIT__); |
27 | 30 | <<< ATEXITS >>> |
28 | #if USE_PTHREADS | |
29 | PThread.runExitHandlers(); | |
30 | #endif | |
31 | 31 | #endif |
32 | 32 | |
33 | 33 | #if IN_TEST_HARNESS |
355 | 355 | |
356 | 356 | var runtimeInitialized = false; |
357 | 357 | var runtimeExited = false; |
358 | var runtimeKeepaliveCounter = 0; | |
359 | ||
360 | function keepRuntimeAlive() { | |
361 | return noExitRuntime || runtimeKeepaliveCounter > 0; | |
362 | } | |
358 | 363 | |
359 | 364 | function preRun() { |
360 | 365 | #if USE_PTHREADS |
418 | 423 | checkStackCookie(); |
419 | 424 | #endif |
420 | 425 | #if USE_PTHREADS |
426 | #if EXIT_RUNTIME | |
427 | PThread.runExitHandlers(); | |
428 | #endif | |
421 | 429 | if (ENVIRONMENT_IS_PTHREAD) return; // PThreads reuse the runtime from the main thread. |
422 | 430 | #endif |
423 | 431 | #if EXIT_RUNTIME |
424 | 432 | callRuntimeCallbacks(__ATEXIT__); |
425 | 433 | <<< ATEXITS >>> |
426 | #if USE_PTHREADS | |
427 | PThread.runExitHandlers(); | |
428 | #endif | |
429 | 434 | #endif |
430 | 435 | runtimeExited = true; |
431 | 436 | } |
501 | 506 | } |
502 | 507 | |
503 | 508 | function addRunDependency(id) { |
504 | #if USE_PTHREADS | |
505 | // We should never get here in pthreads (could no-op this out if called in pthreads, but that might indicate a bug in caller side, | |
506 | // so good to be very explicit) | |
507 | assert(!ENVIRONMENT_IS_PTHREAD, "addRunDependency cannot be used in a pthread worker"); | |
508 | #endif | |
509 | 509 | runDependencies++; |
510 | 510 | |
511 | 511 | #if expectToReceiveOnModule('monitorRunDependencies') |
835 | 835 | #endif |
836 | 836 | |
837 | 837 | #if SPLIT_MODULE |
838 | {{{ makeModuleReceiveWithVar('loadSplitModule', 'loadSplitModule', 'instantiateSync', true) }}} | |
838 | 839 | var splitModuleProxyHandler = { |
839 | 840 | 'get': function(target, prop, receiver) { |
840 | 841 | return function() { |
842 | 843 | var imports = {'primary': Module['asm']}; |
843 | 844 | // Replace '.wasm' suffix with '.deferred.wasm'. |
844 | 845 | var deferred = wasmBinaryFile.slice(0, -5) + '.deferred.wasm' |
845 | instantiateSync(deferred, imports); | |
846 | loadSplitModule(deferred, imports, prop); | |
846 | 847 | err('instantiated deferred module, continuing'); |
847 | 848 | #if RELOCATABLE |
848 | 849 | // When the table is dynamically laid out, the placeholder functions names |
960 | 961 | |
961 | 962 | Module['asm'] = exports; |
962 | 963 | |
963 | #if MAIN_MODULE && AUTOLOAD_DYLIBS | |
964 | #if MAIN_MODULE | |
965 | #if AUTOLOAD_DYLIBS | |
964 | 966 | var metadata = getDylinkMetadata(module); |
965 | 967 | if (metadata.neededDynlibs) { |
966 | 968 | dynamicLibraries = metadata.neededDynlibs.concat(dynamicLibraries); |
967 | 969 | } |
970 | #endif | |
968 | 971 | mergeLibSymbols(exports, 'main') |
969 | 972 | #endif |
970 | 973 |
597 | 597 | // * Work around old Chromium WebGL 1 bug (-s WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG=1) |
598 | 598 | // * Disable WebAssembly. (Must be paired with -s WASM=0) |
599 | 599 | // * Adjusts MIN_X_VERSION settings to 0 to include support for all browser versions. |
600 | // * Avoid TypedArray.fill, if necessary, in zeroMemory utility function. | |
600 | 601 | // You can also configure the above options individually. |
601 | 602 | // [link] |
602 | 603 | var LEGACY_VM_SUPPORT = 0; |
603 | 604 | |
604 | // By default, emscripten output will run on the web, in a web worker, | |
605 | // in node.js, or in a JS shell like d8, js, or jsc. You can set this option to | |
606 | // specify that the output should only run in one particular environment, which | |
607 | // must be one of | |
605 | // Specify which runtime environments the JS output will be capable of running | |
606 | // in. For maximum portability this can configured to support all envionements | |
607 | // or it can be limited to reduce overall code size. The supported environments | |
608 | // are: | |
608 | 609 | // 'web' - the normal web environment. |
609 | 610 | // 'webview' - just like web, but in a webview like Cordova; |
610 | 611 | // considered to be same as "web" in almost every place |
611 | 612 | // 'worker' - a web worker environment. |
612 | 613 | // 'node' - Node.js. |
613 | 614 | // 'shell' - a JS shell like d8, js, or jsc. |
614 | // Or it can be a comma-separated list of them, e.g., "web,worker". If this is | |
615 | // the empty string, then all runtime environments are supported. | |
615 | // This settings can be a comma-separated list of these environments, e.g., | |
616 | // "web,worker". If this is the empty string, then all environments are | |
617 | // supported. | |
616 | 618 | // |
617 | 619 | // Note that the set of environments recognized here is not identical to the |
618 | 620 | // ones we identify at runtime using ENVIRONMENT_IS_*. Specifically: |
621 | 623 | // * The webview target is basically a subset of web. It must be specified |
622 | 624 | // alongside web (e.g. "web,webview") and we only use it for code generation |
623 | 625 | // at compile time, there is no runtime behavior change. |
624 | // [link] | |
625 | var ENVIRONMENT = ''; | |
626 | // | |
627 | // Note that by default we do not include the 'shell' environment since direct | |
628 | // usage of d8, js, jsc is extremely rare. | |
629 | // [link] | |
630 | var ENVIRONMENT = 'web,webview,worker,node'; | |
626 | 631 | |
627 | 632 | // Enable this to support lz4-compressed file packages. They are stored compressed in memory, and |
628 | 633 | // decompressed on the fly, avoiding storing the entire decompressed data in memory at once. |
830 | 835 | 'buffer', 'canvas', 'doNotCaptureKeyboard', 'dynamicLibraries', |
831 | 836 | 'elementPointerLock', 'extraStackTrace', 'forcedAspectRatio', |
832 | 837 | 'instantiateWasm', 'keyboardListeningElement', 'freePreloadedMediaOnUse', |
833 | 'locateFile', 'logReadFiles', 'mainScriptUrlOrBlob', 'mem', | |
838 | 'loadSplitModule', 'locateFile', 'logReadFiles', 'mainScriptUrlOrBlob', 'mem', | |
834 | 839 | 'monitorRunDependencies', 'noExitRuntime', 'noInitialRun', 'onAbort', |
835 | 840 | 'onCustomMessage', 'onExit', 'onFree', 'onFullScreen', 'onMalloc', |
836 | 841 | 'onRealloc', 'onRuntimeInitialized', 'postMainLoop', 'postRun', 'preInit', |
1026 | 1031 | // * AUTO_NATIVE_LIBRARIES is disabled. |
1027 | 1032 | // * AUTO_ARCHIVE_INDEXES is disabled. |
1028 | 1033 | // * DEFAULT_TO_CXX is disabled. |
1034 | // * ALLOW_UNIMPLEMENTED_SYSCALLS is disabled. | |
1029 | 1035 | // [compile+link] |
1030 | 1036 | var STRICT = 0; |
1031 | 1037 | |
1936 | 1942 | // For MAIN_MODULE builds, automatically load any dynamic library dependencies |
1937 | 1943 | // on startup, before loading the main module. |
1938 | 1944 | var AUTOLOAD_DYLIBS = 1; |
1945 | ||
1946 | // Include unimplemented JS syscalls to be included in the final output. This | |
1947 | // allows programs that depend on these syscalls at runtime to be compiled, even | |
1948 | // though these syscalls will fail (or do nothing) at runtime. | |
1949 | var ALLOW_UNIMPLEMENTED_SYSCALLS = 1; | |
1939 | 1950 | |
1940 | 1951 | //=========================================== |
1941 | 1952 | // Internal, used for testing only, from here |
80 | 80 | // Determine the runtime environment we are in. You can customize this by |
81 | 81 | // setting the ENVIRONMENT setting at compile time (see settings.js). |
82 | 82 | |
83 | #if ENVIRONMENT && ENVIRONMENT.indexOf(',') < 0 | |
83 | #if ENVIRONMENT && !ENVIRONMENT.includes(',') | |
84 | 84 | var ENVIRONMENT_IS_WEB = {{{ ENVIRONMENT === 'web' }}}; |
85 | 85 | #if USE_PTHREADS && ENVIRONMENT_MAY_BE_NODE |
86 | 86 | // node+pthreads always supports workers; detect which we are at runtime |
91 | 91 | var ENVIRONMENT_IS_NODE = {{{ ENVIRONMENT === 'node' }}}; |
92 | 92 | var ENVIRONMENT_IS_SHELL = {{{ ENVIRONMENT === 'shell' }}}; |
93 | 93 | #else // ENVIRONMENT |
94 | var ENVIRONMENT_IS_WEB = false; | |
95 | var ENVIRONMENT_IS_WORKER = false; | |
96 | var ENVIRONMENT_IS_NODE = false; | |
97 | var ENVIRONMENT_IS_SHELL = false; | |
98 | ENVIRONMENT_IS_WEB = typeof window === 'object'; | |
99 | ENVIRONMENT_IS_WORKER = typeof importScripts === 'function'; | |
94 | // Attempt to auto-detect the environment | |
95 | var ENVIRONMENT_IS_WEB = typeof window === 'object'; | |
96 | var ENVIRONMENT_IS_WORKER = typeof importScripts === 'function'; | |
100 | 97 | // N.b. Electron.js environment is simultaneously a NODE-environment, but |
101 | 98 | // also a web environment. |
102 | ENVIRONMENT_IS_NODE = typeof process === 'object' && typeof process.versions === 'object' && typeof process.versions.node === 'string'; | |
103 | ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER; | |
99 | var ENVIRONMENT_IS_NODE = typeof process === 'object' && typeof process.versions === 'object' && typeof process.versions.node === 'string'; | |
100 | var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER; | |
104 | 101 | #endif // ENVIRONMENT |
105 | 102 | |
106 | 103 | #if ASSERTIONS |
200 | 197 | process['on']('unhandledRejection', abort); |
201 | 198 | #endif |
202 | 199 | |
203 | quit_ = function(status) { | |
200 | quit_ = function(status, toThrow) { | |
201 | if (keepRuntimeAlive()) { | |
202 | process['exitCode'] = status; | |
203 | throw toThrow; | |
204 | } | |
204 | 205 | process['exit'](status); |
205 | 206 | }; |
206 | 207 | |
227 | 228 | |
228 | 229 | } else |
229 | 230 | #endif // ENVIRONMENT_MAY_BE_NODE |
230 | #if ENVIRONMENT_MAY_BE_SHELL | |
231 | #if ENVIRONMENT_MAY_BE_SHELL || ASSERTIONS | |
231 | 232 | if (ENVIRONMENT_IS_SHELL) { |
232 | 233 | |
233 | 234 | #if ENVIRONMENT |
262 | 263 | data = read(f, 'binary'); |
263 | 264 | assert(typeof data === 'object'); |
264 | 265 | return data; |
266 | }; | |
267 | ||
268 | readAsync = function readAsync(f, onload, onerror) { | |
269 | setTimeout(function() { onload(readBinary(f)); }, 0); | |
265 | 270 | }; |
266 | 271 | |
267 | 272 | if (typeof scriptArgs != 'undefined') { |
407 | 412 | #if USE_PTHREADS |
408 | 413 | assert(ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER || ENVIRONMENT_IS_NODE, 'Pthreads do not work in this environment yet (need Web Workers, or an alternative to them)'); |
409 | 414 | #endif // USE_PTHREADS |
415 | ||
416 | #if !ENVIRONMENT_MAY_BE_WEB | |
417 | assert(!ENVIRONMENT_IS_WEB, "web environment detected but not enabled at build time. Add 'web' to `-s ENVIRONMENT` to enable."); | |
418 | #endif | |
419 | ||
420 | #if !ENVIRONMENT_MAY_BE_WORKER | |
421 | assert(!ENVIRONMENT_IS_WORKER, "worker environment detected but not enabled at build time. Add 'worker' to `-s ENVIRONMENT` to enable."); | |
422 | #endif | |
423 | ||
424 | #if !ENVIRONMENT_MAY_BE_NODE | |
425 | assert(!ENVIRONMENT_IS_NODE, "node environment detected but not enabled at build time. Add 'node' to `-s ENVIRONMENT` to enable."); | |
426 | #endif | |
427 | ||
428 | #if !ENVIRONMENT_MAY_BE_SHELL | |
429 | assert(!ENVIRONMENT_IS_SHELL, "shell environment detected but not enabled at build time. Add 'shell' to `-s ENVIRONMENT` to enable."); | |
430 | #endif | |
431 | ||
410 | 432 | #endif // ASSERTIONS |
411 | 433 | |
412 | 434 | {{BODY}} |
34 | 34 | #if ASSERTIONS |
35 | 35 | #if !ENVIRONMENT_MAY_BE_NODE && !ENVIRONMENT_MAY_BE_SHELL |
36 | 36 | var ENVIRONMENT_IS_WEB = true |
37 | #elif ENVIRONMENT && !ENVIRONMENT.includes(',') | |
38 | var ENVIRONMENT_IS_WEB = {{{ ENVIRONMENT === 'web' }}}; | |
39 | #elif ENVIRONMENT_MAY_BE_SHELL && ENVIRONMENT_IS_NODE | |
40 | var ENVIRONMENT_IS_WEB = !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_SHELL; | |
41 | #elif ENVIRONMENT_MAY_BE_SHELL | |
42 | var ENVIRONMENT_IS_WEB = !ENVIRONMENT_IS_SHELL; | |
37 | 43 | #else |
38 | #if ENVIRONMENT && ENVIRONMENT.indexOf(',') < 0 | |
39 | var ENVIRONMENT_IS_WEB = {{{ ENVIRONMENT === 'web' }}}; | |
44 | var ENVIRONMENT_IS_WEB = !ENVIRONMENT_IS_NODE; | |
40 | 45 | #else |
41 | var ENVIRONMENT_IS_WEB = !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_SHELL; | |
46 | ||
42 | 47 | #endif |
43 | #endif | |
44 | #endif | |
48 | #endif // ASSERTIONS | |
45 | 49 | |
46 | 50 | #if ASSERTIONS && ENVIRONMENT_MAY_BE_NODE && ENVIRONMENT_MAY_BE_SHELL |
47 | 51 | if (ENVIRONMENT_IS_NODE && ENVIRONMENT_IS_SHELL) { |
39 | 39 | "global_locale" |
40 | 40 | ] |
41 | 41 | } |
42 | }, | |
43 | { | |
44 | "file": "cxa_exception.h", | |
45 | "structs": { | |
46 | "__cxxabiv1::__cxa_exception": [ | |
47 | "exceptionDestructor", | |
48 | "referenceCount", | |
49 | "exceptionType", | |
50 | "caught", | |
51 | "rethrown" | |
52 | ] | |
53 | } | |
42 | 54 | } |
43 | 55 | ] |
110 | 110 | } |
111 | 111 | |
112 | 112 | function isJsLibraryConfigIdentifier(ident) { |
113 | return ident.endsWith('__sig') || ident.endsWith('__proxy') || ident.endsWith('__asm') || ident.endsWith('__inline') | |
114 | || ident.endsWith('__deps') || ident.endsWith('__postset') || ident.endsWith('__docs') || ident.endsWith('__import') | |
115 | || ident.endsWith('__nothrow'); | |
113 | suffixes = [ | |
114 | '__sig', | |
115 | '__proxy', | |
116 | '__asm', | |
117 | '__inline', | |
118 | '__deps', | |
119 | '__postset', | |
120 | '__docs', | |
121 | '__import', | |
122 | '__nothrow', | |
123 | '__unimplemented' | |
124 | ]; | |
125 | return suffixes.some((suffix) => ident.endsWith(suffix)); | |
116 | 126 | } |
117 | 127 | |
118 | 128 | // Sets |
0 | /* | |
1 | * Copyright 2012 The Emscripten Authors. All rights reserved. | |
2 | * Emscripten is available under two separate licenses, the MIT license and the | |
3 | * University of Illinois/NCSA Open Source License. Both these licenses can be | |
4 | * found in the LICENSE file. | |
5 | */ | |
6 | ||
7 | #pragma once | |
8 | ||
9 | /* Typedefs */ | |
10 | ||
11 | typedef short __attribute__((aligned(1))) emscripten_align1_short; | |
12 | ||
13 | typedef long long __attribute__((aligned(4))) emscripten_align4_int64; | |
14 | typedef long long __attribute__((aligned(2))) emscripten_align2_int64; | |
15 | typedef long long __attribute__((aligned(1))) emscripten_align1_int64; | |
16 | ||
17 | typedef int __attribute__((aligned(2))) emscripten_align2_int; | |
18 | typedef int __attribute__((aligned(1))) emscripten_align1_int; | |
19 | ||
20 | typedef float __attribute__((aligned(2))) emscripten_align2_float; | |
21 | typedef float __attribute__((aligned(1))) emscripten_align1_float; | |
22 | ||
23 | typedef double __attribute__((aligned(4))) emscripten_align4_double; | |
24 | typedef double __attribute__((aligned(2))) emscripten_align2_double; | |
25 | typedef double __attribute__((aligned(1))) emscripten_align1_double; | |
26 | ||
27 | typedef void (*em_callback_func)(void); | |
28 | typedef void (*em_arg_callback_func)(void*); | |
29 | typedef void (*em_str_callback_func)(const char *); |
21 | 21 | |
22 | 22 | #include "em_asm.h" |
23 | 23 | #include "em_macros.h" |
24 | #include "em_types.h" | |
24 | 25 | #include "em_js.h" |
26 | #include "wget.h" | |
25 | 27 | |
26 | 28 | #ifdef __cplusplus |
27 | 29 | extern "C" { |
28 | 30 | #endif |
29 | ||
30 | /* Typedefs */ | |
31 | ||
32 | typedef short __attribute__((aligned(1))) emscripten_align1_short; | |
33 | ||
34 | typedef long long __attribute__((aligned(4))) emscripten_align4_int64; | |
35 | typedef long long __attribute__((aligned(2))) emscripten_align2_int64; | |
36 | typedef long long __attribute__((aligned(1))) emscripten_align1_int64; | |
37 | ||
38 | typedef int __attribute__((aligned(2))) emscripten_align2_int; | |
39 | typedef int __attribute__((aligned(1))) emscripten_align1_int; | |
40 | ||
41 | typedef float __attribute__((aligned(2))) emscripten_align2_float; | |
42 | typedef float __attribute__((aligned(1))) emscripten_align1_float; | |
43 | ||
44 | typedef double __attribute__((aligned(4))) emscripten_align4_double; | |
45 | typedef double __attribute__((aligned(2))) emscripten_align2_double; | |
46 | typedef double __attribute__((aligned(1))) emscripten_align1_double; | |
47 | ||
48 | typedef void (*em_callback_func)(void); | |
49 | typedef void (*em_arg_callback_func)(void*); | |
50 | typedef void (*em_str_callback_func)(const char *); | |
51 | 31 | |
52 | 32 | void emscripten_run_script(const char *script); |
53 | 33 | int emscripten_run_script_int(const char *script); |
104 | 84 | double emscripten_get_now(void); |
105 | 85 | float emscripten_random(void); |
106 | 86 | |
107 | // wget | |
108 | ||
109 | void emscripten_async_wget(const char* url, const char* file, em_str_callback_func onload, em_str_callback_func onerror); | |
110 | ||
111 | typedef void (*em_async_wget_onload_func)(void*, void*, int); | |
112 | void emscripten_async_wget_data(const char* url, void *arg, em_async_wget_onload_func onload, em_arg_callback_func onerror); | |
113 | ||
114 | typedef void (*em_async_wget2_onload_func)(unsigned, void*, const char*); | |
115 | typedef void (*em_async_wget2_onstatus_func)(unsigned, void*, int); | |
116 | ||
117 | int emscripten_async_wget2(const char* url, const char* file, const char* requesttype, const char* param, void *arg, em_async_wget2_onload_func onload, em_async_wget2_onstatus_func onerror, em_async_wget2_onstatus_func onprogress); | |
118 | ||
119 | typedef void (*em_async_wget2_data_onload_func)(unsigned, void*, void*, unsigned); | |
120 | typedef void (*em_async_wget2_data_onerror_func)(unsigned, void*, int, const char*); | |
121 | typedef void (*em_async_wget2_data_onprogress_func)(unsigned, void*, int, int); | |
122 | ||
123 | int emscripten_async_wget2_data(const char* url, const char* requesttype, const char* param, void *arg, int free, em_async_wget2_data_onload_func onload, em_async_wget2_data_onerror_func onerror, em_async_wget2_data_onprogress_func onprogress); | |
124 | ||
125 | void emscripten_async_wget2_abort(int handle); | |
126 | ||
127 | // wget "sync" | |
128 | ||
129 | void emscripten_wget(const char* url, const char* file); | |
130 | void emscripten_wget_data(const char* url, void** pbuffer, int* pnum, int *perror); | |
131 | ||
132 | 87 | // IDB |
133 | 88 | |
134 | void emscripten_idb_async_load(const char *db_name, const char *file_id, void* arg, em_async_wget_onload_func onload, em_arg_callback_func onerror); | |
89 | typedef void (*em_idb_onload_func)(void*, void*, int); | |
90 | void emscripten_idb_async_load(const char *db_name, const char *file_id, void* arg, em_idb_onload_func onload, em_arg_callback_func onerror); | |
135 | 91 | void emscripten_idb_async_store(const char *db_name, const char *file_id, void* ptr, int num, void* arg, em_arg_callback_func onstore, em_arg_callback_func onerror); |
136 | 92 | void emscripten_idb_async_delete(const char *db_name, const char *file_id, void* arg, em_arg_callback_func ondelete, em_arg_callback_func onerror); |
137 | 93 | typedef void (*em_idb_exists_func)(void*, int); |
0 | /* | |
1 | * Copyright 2012 The Emscripten Authors. All rights reserved. | |
2 | * Emscripten is available under two separate licenses, the MIT license and the | |
3 | * University of Illinois/NCSA Open Source License. Both these licenses can be | |
4 | * found in the LICENSE file. | |
5 | */ | |
6 | ||
7 | #pragma once | |
8 | ||
9 | #include "em_types.h" | |
10 | ||
11 | #ifdef __cplusplus | |
12 | extern "C" { | |
13 | #endif | |
14 | ||
15 | // wget | |
16 | ||
17 | void emscripten_async_wget(const char* url, const char* file, em_str_callback_func onload, em_str_callback_func onerror); | |
18 | ||
19 | typedef void (*em_async_wget_onload_func)(void*, void*, int); | |
20 | void emscripten_async_wget_data(const char* url, void *arg, em_async_wget_onload_func onload, em_arg_callback_func onerror); | |
21 | ||
22 | typedef void (*em_async_wget2_onload_func)(unsigned, void*, const char*); | |
23 | typedef void (*em_async_wget2_onstatus_func)(unsigned, void*, int); | |
24 | ||
25 | int emscripten_async_wget2(const char* url, const char* file, const char* requesttype, const char* param, void *arg, em_async_wget2_onload_func onload, em_async_wget2_onstatus_func onerror, em_async_wget2_onstatus_func onprogress); | |
26 | ||
27 | typedef void (*em_async_wget2_data_onload_func)(unsigned, void*, void*, unsigned); | |
28 | typedef void (*em_async_wget2_data_onerror_func)(unsigned, void*, int, const char*); | |
29 | typedef void (*em_async_wget2_data_onprogress_func)(unsigned, void*, int, int); | |
30 | ||
31 | int emscripten_async_wget2_data(const char* url, const char* requesttype, const char* param, void *arg, int free, em_async_wget2_data_onload_func onload, em_async_wget2_data_onerror_func onerror, em_async_wget2_data_onprogress_func onprogress); | |
32 | ||
33 | void emscripten_async_wget2_abort(int handle); | |
34 | ||
35 | // wget "sync" | |
36 | ||
37 | void emscripten_wget(const char* url, const char* file); | |
38 | void emscripten_wget_data(const char* url, void** pbuffer, int* pnum, int *perror); | |
39 | ||
40 | #ifdef __cplusplus | |
41 | } | |
42 | #endif |
59 | 59 | } |
60 | 60 | |
61 | 61 | static thread_return_t THREAD_CALLING_CONV asan_thread_start(void *arg) { |
62 | atomic_uintptr_t *param = reinterpret_cast<atomic_uintptr_t *>(arg); | |
63 | AsanThread *t = nullptr; | |
64 | while ((t = reinterpret_cast<AsanThread *>( | |
65 | atomic_load(param, memory_order_acquire))) == nullptr) | |
66 | internal_sched_yield(); | |
67 | emscripten_builtin_free(param); | |
62 | AsanThread *t = (AsanThread *)arg; | |
68 | 63 | SetCurrentThread(t); |
69 | 64 | return t->ThreadStart(GetTid()); |
70 | 65 | } |
79 | 74 | int detached = 0; |
80 | 75 | if (attr && attr != __ATTRP_C11_THREAD) |
81 | 76 | pthread_attr_getdetachstate(attr, &detached); |
82 | atomic_uintptr_t *param = (atomic_uintptr_t *) | |
83 | emscripten_builtin_malloc(sizeof(atomic_uintptr_t)); | |
84 | atomic_store(param, 0, memory_order_relaxed); | |
77 | ||
78 | u32 current_tid = GetCurrentTidOrInvalid(); | |
79 | AsanThread *t = | |
80 | AsanThread::Create(start_routine, arg, current_tid, &stack, detached); | |
81 | ||
85 | 82 | int result; |
86 | 83 | { |
87 | 84 | // Ignore all allocations made by pthread_create: thread stack/TLS may be |
91 | 88 | #if CAN_SANITIZE_LEAKS |
92 | 89 | __lsan::ScopedInterceptorDisabler disabler; |
93 | 90 | #endif |
94 | result = REAL(pthread_create)(thread, attr, asan_thread_start, param); | |
91 | result = REAL(pthread_create)(thread, attr, asan_thread_start, t); | |
95 | 92 | } |
96 | if (result == 0) { | |
97 | u32 current_tid = GetCurrentTidOrInvalid(); | |
98 | AsanThread *t = | |
99 | AsanThread::Create(start_routine, arg, current_tid, &stack, detached); | |
100 | atomic_store(param, reinterpret_cast<uptr>(t), memory_order_release); | |
93 | if (result != 0) { | |
94 | // If the thread didn't start delete the AsanThread to avoid leaking it. | |
95 | // Note AsanThreadContexts never get destroyed so the AsanThreadContext | |
96 | // that was just created for the AsanThread is wasted. | |
97 | t->Destroy(); | |
101 | 98 | } |
102 | 99 | return result; |
103 | 100 | } |
482 | 482 | |
483 | 483 | #if !SANITIZER_NETBSD |
484 | 484 | void internal__exit(int exitcode) { |
485 | #if SANITIZER_FREEBSD || SANITIZER_SOLARIS | |
485 | #if SANITIZER_EMSCRIPTEN | |
486 | __wasi_proc_exit(exitcode); | |
487 | #elif SANITIZER_FREEBSD || SANITIZER_SOLARIS | |
486 | 488 | internal_syscall(SYSCALL(exit), exitcode); |
487 | 489 | #else |
488 | 490 | internal_syscall(SYSCALL(exit_group), exitcode); |
35 | 35 | #define a_cas_p a_cas_p |
36 | 36 | static inline void *a_cas_p(volatile void *p, void *t, void *s) |
37 | 37 | { |
38 | void* expected = t; | |
39 | __c11_atomic_compare_exchange_strong((_Atomic uintptr_t*)p, &expected, s, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); | |
40 | return expected; | |
38 | uintptr_t expected = (uintptr_t)t; | |
39 | __c11_atomic_compare_exchange_strong((_Atomic uintptr_t*)p, &expected, (uintptr_t)s, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); | |
40 | return (void*)expected; | |
41 | 41 | } |
42 | 42 | |
43 | 43 | #define a_cas_l a_cas_l |
37 | 37 | #define __NR_getpriority 96 |
38 | 38 | #define __NR_setpriority 97 |
39 | 39 | #define __NR_setitimer 104 |
40 | #define __NR_getitimer 105 | |
40 | 41 | #define __NR_wait4 114 |
41 | 42 | #define __NR_setdomainname 121 |
42 | 43 | #define __NR_uname 122 |
162 | 163 | #define SYS_getpriority 96 |
163 | 164 | #define SYS_setpriority 97 |
164 | 165 | #define SYS_setitimer 104 |
166 | #define SYS_getitimer 105 | |
165 | 167 | #define SYS_wait4 114 |
166 | 168 | #define SYS_setdomainname 121 |
167 | 169 | #define SYS_uname 122 |
13 | 13 | /* Causes the final import in the wasm binary be named "env.sys_<name>" */ |
14 | 14 | #define SYS_IMPORT(NAME) EM_IMPORT(__sys_##NAME) |
15 | 15 | |
16 | long SYS_IMPORT(exit) __syscall1(long exit_code); | |
17 | 16 | long SYS_IMPORT(open) __syscall5(long path, long flags, ...); // mode is optional |
18 | 17 | long SYS_IMPORT(link) __syscall9(long oldpath, long newpath); |
19 | 18 | long SYS_IMPORT(unlink) __syscall10(long path); |
48 | 47 | long SYS_IMPORT(setpriority) __syscall97(long which, long who, long prio); |
49 | 48 | long SYS_IMPORT(socketcall) __syscall102(long call, long args); |
50 | 49 | long SYS_IMPORT(setitimer) __syscall104(long which, long new_value, long old_value); |
50 | long SYS_IMPORT(getitimer) __syscall105(long which, long old_value); | |
51 | 51 | long SYS_IMPORT(wait4) __syscall114(long pid, long wstatus, long options, long rusage); |
52 | 52 | long SYS_IMPORT(setdomainname) __syscall121(long name, long size); |
53 | 53 | long SYS_IMPORT(uname) __syscall122(long buf); |
95 | 95 | long SYS_IMPORT(madvise1) __syscall219(long addr, long length, long advice); |
96 | 96 | long SYS_IMPORT(getdents64) __syscall220(long fd, long dirp, long count); |
97 | 97 | long SYS_IMPORT(fcntl64) __syscall221(long fd, long cmd, ...); |
98 | long SYS_IMPORT(exit_group) __syscall252(long status); | |
99 | 98 | long SYS_IMPORT(statfs64) __syscall268(long path, long size, long buf); |
100 | 99 | long SYS_IMPORT(fstatfs64) __syscall269(long fd, long size, long buf); |
101 | 100 | long SYS_IMPORT(fadvise64_64) |
202 | 202 | struct __ptcb *__next; |
203 | 203 | }; |
204 | 204 | |
205 | #ifdef __EMSCRIPTEN__ | |
206 | // For Emscripten, the cleanup stack is not implemented as a macro, since it's currently in the JS side. | |
207 | typedef void (*cleanup_handler_routine)(void *arg); | |
208 | void pthread_cleanup_push(cleanup_handler_routine routine, void *arg); | |
209 | void pthread_cleanup_pop(int execute); | |
210 | #else | |
211 | 205 | void _pthread_cleanup_push(struct __ptcb *, void (*)(void *), void *); |
212 | 206 | void _pthread_cleanup_pop(struct __ptcb *, int); |
213 | 207 | |
214 | 208 | #define pthread_cleanup_push(f, x) do { struct __ptcb __cb; _pthread_cleanup_push(&__cb, f, x); |
215 | 209 | #define pthread_cleanup_pop(r) _pthread_cleanup_pop(&__cb, (r)); } while(0) |
216 | #endif | |
217 | 210 | |
218 | 211 | #ifdef _GNU_SOURCE |
219 | 212 | struct cpu_set_t; |
2 | 2 | |
3 | 3 | _Noreturn void _Exit(int ec) |
4 | 4 | { |
5 | #ifdef __EMSCRIPTEN__ | |
6 | __wasi_proc_exit(ec); | |
7 | #else | |
5 | 8 | __syscall(SYS_exit_group, ec); |
6 | 9 | for (;;) __syscall(SYS_exit, ec); |
10 | #endif | |
7 | 11 | } |
0 | 0 | #define _GNU_SOURCE |
1 | 1 | #include <unistd.h> |
2 | 2 | #include <time.h> |
3 | #ifdef __EMSCRTIPEN__ | |
4 | #include <emscripten/threading.h> | |
5 | #endif | |
6 | 3 | |
7 | 4 | int usleep(unsigned useconds) |
8 | 5 | { |
9 | #ifdef __EMSCRTIPEN__ | |
10 | emscripten_thread_sleep(usec / 1e3); | |
11 | return 0; | |
12 | #else | |
13 | 6 | struct timespec tv = { |
14 | 7 | .tv_sec = useconds/1000000, |
15 | 8 | .tv_nsec = (useconds%1000000)*1000 |
16 | 9 | }; |
17 | 10 | return nanosleep(&tv, &tv); |
18 | #endif | |
19 | 11 | } |
17 | 17 | #include "unwind.h" |
18 | 18 | |
19 | 19 | namespace __cxxabiv1 { |
20 | ||
21 | #ifdef __USING_EMSCRIPTEN_EXCEPTIONS__ | |
22 | ||
23 | struct _LIBCXXABI_HIDDEN __cxa_exception { | |
24 | size_t referenceCount; | |
25 | std::type_info *exceptionType; | |
26 | void (*exceptionDestructor)(void *); | |
27 | uint8_t caught; | |
28 | uint8_t rethrown; | |
29 | }; | |
30 | ||
31 | #else | |
20 | 32 | |
21 | 33 | static const uint64_t kOurExceptionClass = 0x434C4E47432B2B00; // CLNGC++\0 |
22 | 34 | static const uint64_t kOurDependentExceptionClass = 0x434C4E47432B2B01; // CLNGC++\1 |
163 | 175 | extern "C" _LIBCXXABI_FUNC_VIS void * __cxa_allocate_dependent_exception (); |
164 | 176 | extern "C" _LIBCXXABI_FUNC_VIS void __cxa_free_dependent_exception (void * dependent_exception); |
165 | 177 | |
178 | #endif // !__USING_EMSCRIPTEN_EXCEPTIONS__ | |
179 | ||
166 | 180 | } // namespace __cxxabiv1 |
167 | 181 | |
168 | 182 | #endif // _CXA_EXCEPTION_H |
17 | 17 | #include "cxa_exception.h" |
18 | 18 | #include "private_typeinfo.h" |
19 | 19 | #include "include/atomic_support.h" |
20 | ||
21 | namespace __cxxabiv1 { | |
22 | ||
23 | #ifdef __USING_EMSCRIPTEN_EXCEPTIONS__ | |
24 | // XXX EMSCRIPTEN: Copied from cxa_exception.cpp since we don't compile that | |
25 | // file in Emscripten EH mode. Note that in no-exceptions builds we include | |
26 | // cxa_noexception.cpp which provides stubs of those anyhow. | |
27 | ||
28 | // Is it one of ours? | |
29 | uint64_t __getExceptionClass(const _Unwind_Exception* unwind_exception) { | |
30 | // On x86 and some ARM unwinders, unwind_exception->exception_class is | |
31 | // a uint64_t. On other ARM unwinders, it is a char[8] | |
32 | // See: http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038b/IHI0038B_ehabi.pdf | |
33 | // So we just copy it into a uint64_t to be sure. | |
34 | uint64_t exClass; | |
35 | ::memcpy(&exClass, &unwind_exception->exception_class, sizeof(exClass)); | |
36 | return exClass; | |
37 | } | |
38 | ||
39 | bool __isOurExceptionClass(const _Unwind_Exception* unwind_exception) { | |
40 | return (__getExceptionClass(unwind_exception) & get_vendor_and_language) == | |
41 | (kOurExceptionClass & get_vendor_and_language); | |
42 | } | |
43 | #endif | |
44 | ||
45 | } | |
46 | 20 | |
47 | 21 | namespace std |
48 | 22 | { |
98 | 72 | void |
99 | 73 | terminate() _NOEXCEPT |
100 | 74 | { |
101 | #ifndef _LIBCXXABI_NO_EXCEPTIONS | |
75 | #if !defined(_LIBCXXABI_NO_EXCEPTIONS) && !defined(__USING_EMSCRIPTEN_EXCEPTIONS__) | |
102 | 76 | // If there might be an uncaught exception |
103 | 77 | using namespace __cxxabiv1; |
104 | 78 | __cxa_eh_globals* globals = __cxa_get_globals_fast(); |
945 | 945 | } |
946 | 946 | |
947 | 947 | weak_alias(__pthread_testcancel, pthread_testcancel); |
948 | ||
949 | // See musl's pthread_create.c | |
950 | void __run_cleanup_handlers(void* _unused) { | |
951 | pthread_t self = __pthread_self(); | |
952 | while (self->cancelbuf) { | |
953 | void (*f)(void *) = self->cancelbuf->__f; | |
954 | void *x = self->cancelbuf->__x; | |
955 | self->cancelbuf = self->cancelbuf->__next; | |
956 | f(x); | |
957 | } | |
958 | } | |
959 | ||
960 | extern int __cxa_thread_atexit(void (*)(void *), void *, void *); | |
961 | ||
962 | extern int8_t __dso_handle; | |
963 | ||
964 | // Copied from musl's pthread_create.c | |
965 | void __do_cleanup_push(struct __ptcb *cb) { | |
966 | struct pthread *self = __pthread_self(); | |
967 | cb->__next = self->cancelbuf; | |
968 | self->cancelbuf = cb; | |
969 | static thread_local bool registered = false; | |
970 | if (!registered) { | |
971 | __cxa_thread_atexit(__run_cleanup_handlers, NULL, &__dso_handle); | |
972 | registered = true; | |
973 | } | |
974 | } | |
975 | ||
976 | // Copied from musl's pthread_create.c | |
977 | void __do_cleanup_pop(struct __ptcb *cb) { | |
978 | __pthread_self()->cancelbuf = cb->__next; | |
979 | } |
23 | 23 | */ |
24 | 24 | |
25 | 25 | // libc |
26 | ||
27 | void _Exit(int status) { | |
28 | __wasi_proc_exit(status); | |
29 | __builtin_unreachable(); | |
30 | } | |
31 | 26 | |
32 | 27 | void abort() { |
33 | 28 | _Exit(1); |
25 | 25 | |
26 | 26 | char *randomString(int len) { |
27 | 27 | if (!utf8_corpus) { |
28 | // FILE *handle = fopen("ascii_corpus.txt", "rb"); | |
29 | 28 | FILE *handle = fopen("utf8_corpus.txt", "rb"); |
30 | 29 | fseek(handle, 0, SEEK_END); |
31 | 30 | utf8_corpus_length = ftell(handle); |
45 | 44 | char *s = new char[len+1]; |
46 | 45 | memcpy(s, utf8_corpus + startIdx, len); |
47 | 46 | s[len] = '\0'; |
48 | while(((unsigned char)s[len-1] & 0xC0) == 0x80) { s[--len] = '\0'; } | |
49 | while(((unsigned char)s[len-1] & 0xC0) == 0xC0) { s[--len] = '\0'; } | |
47 | while(len > 0 && ((unsigned char)s[len-1] & 0xC0) == 0x80) { s[--len] = '\0'; } | |
48 | while(len > 0 && ((unsigned char)s[len-1] & 0xC0) == 0xC0) { s[--len] = '\0'; } | |
50 | 49 | assert(len >= 0); |
51 | 50 | return s; |
52 | 51 | } |
0 | 0 | #include <emscripten/html5.h> |
1 | 1 | #include <stdio.h> |
2 | #include <stdlib.h> | |
2 | 3 | |
3 | void timeout(void *userData) | |
4 | { | |
5 | printf("Got timeout handler\n"); | |
6 | #ifdef REPORT_RESULT | |
7 | // Test passed | |
8 | REPORT_RESULT(1); | |
9 | #endif | |
4 | void timeout(void *userData) { | |
5 | printf("Got timeout handler\n"); | |
6 | // Test passed | |
7 | exit(0); | |
10 | 8 | } |
11 | 9 | |
12 | int main() | |
13 | { | |
14 | emscripten_set_timeout(timeout, 2000, 0); | |
15 | emscripten_unwind_to_js_event_loop(); | |
16 | printf("This should not be called!\n"); | |
17 | #ifdef REPORT_RESULT | |
18 | // Should not reach here | |
19 | REPORT_RESULT(-1); | |
20 | #endif | |
10 | int main() { | |
11 | emscripten_set_timeout(timeout, 2000, 0); | |
12 | emscripten_unwind_to_js_event_loop(); | |
13 | // emscripten_unwind_to_js_event_loop should never return | |
14 | __builtin_unreachable(); | |
21 | 15 | } |
0 | // Copyright 2013 The Emscripten Authors. All rights reserved. | |
1 | // Emscripten is available under two separate licenses, the MIT license and the | |
2 | // University of Illinois/NCSA Open Source License. Both these licenses can be | |
3 | // found in the LICENSE file. | |
4 | ||
5 | #include <assert.h> | |
6 | #include <stdio.h> | |
7 | #include <stdlib.h> | |
8 | #include <dlfcn.h> | |
9 | #include <emscripten.h> | |
10 | ||
11 | typedef void (*voidfunc)(); | |
12 | typedef int (*intfunc)(); | |
13 | ||
14 | void *lib_handle; | |
15 | voidfunc onefunc; | |
16 | intfunc twofunc; | |
17 | ||
18 | void next(const char *x) { | |
19 | lib_handle = dlopen("thelib.wasm", RTLD_NOW); | |
20 | assert(lib_handle != NULL); | |
21 | ||
22 | onefunc = (voidfunc)dlsym(lib_handle, "one"); | |
23 | twofunc = (intfunc)dlsym(lib_handle, "two"); | |
24 | assert(onefunc && twofunc); | |
25 | ||
26 | assert(twofunc() == 0); | |
27 | onefunc(); | |
28 | assert(twofunc() == 1); | |
29 | onefunc(); | |
30 | onefunc(); | |
31 | assert(twofunc() == 3); | |
32 | onefunc(); | |
33 | onefunc(); | |
34 | onefunc(); | |
35 | onefunc(); | |
36 | assert(twofunc() == 7); | |
37 | onefunc(); | |
38 | int result = twofunc(); | |
39 | assert(result == 8); | |
40 | exit(0); | |
41 | } | |
42 | ||
43 | int main() { | |
44 | emscripten_async_wget("lib.wasm", "thelib.wasm", next, NULL); | |
45 | printf("returning from main\n"); | |
46 | return 0; | |
47 | } | |
48 |
0 | // Copyright 2013 The Emscripten Authors. All rights reserved. | |
1 | // Emscripten is available under two separate licenses, the MIT license and the | |
2 | // University of Illinois/NCSA Open Source License. Both these licenses can be | |
3 | // found in the LICENSE file. | |
4 | ||
5 | #include <assert.h> | |
6 | #include <stdio.h> | |
7 | #include <stdlib.h> | |
8 | #include <dlfcn.h> | |
9 | #include <emscripten.h> | |
10 | ||
11 | typedef void (*voidfunc)(); | |
12 | typedef int (*intfunc)(); | |
13 | ||
14 | void *lib_handle; | |
15 | voidfunc onefunc; | |
16 | intfunc twofunc; | |
17 | ||
18 | void next(const char *x) { | |
19 | lib_handle = dlopen("thelib.wasm", RTLD_NOW); | |
20 | assert(lib_handle != NULL); | |
21 | ||
22 | onefunc = (voidfunc)dlsym(lib_handle, "one"); | |
23 | twofunc = (intfunc)dlsym(lib_handle, "two"); | |
24 | assert(onefunc && twofunc); | |
25 | ||
26 | assert(twofunc() == 0); | |
27 | onefunc(); | |
28 | assert(twofunc() == 1); | |
29 | onefunc(); | |
30 | onefunc(); | |
31 | assert(twofunc() == 3); | |
32 | onefunc(); | |
33 | onefunc(); | |
34 | onefunc(); | |
35 | onefunc(); | |
36 | assert(twofunc() == 7); | |
37 | onefunc(); | |
38 | int result = twofunc(); | |
39 | exit(result); | |
40 | } | |
41 | ||
42 | int main() { | |
43 | emscripten_async_wget("lib.wasm", "thelib.wasm", next, NULL); | |
44 | printf("returning from main\n"); | |
45 | return 0; | |
46 | } | |
47 |
0 | // Copyright 2013 The Emscripten Authors. All rights reserved. | |
1 | // Emscripten is available under two separate licenses, the MIT license and the | |
2 | // University of Illinois/NCSA Open Source License. Both these licenses can be | |
3 | // found in the LICENSE file. | |
4 | ||
5 | int state = 0; | |
6 | ||
7 | void one() { | |
8 | state++; | |
9 | } | |
10 | ||
11 | int two() { | |
12 | return state; | |
13 | } |
0 | // Copyright 2013 The Emscripten Authors. All rights reserved. | |
1 | // Emscripten is available under two separate licenses, the MIT license and the | |
2 | // University of Illinois/NCSA Open Source License. Both these licenses can be | |
3 | // found in the LICENSE file. | |
4 | ||
5 | int state = 0; | |
6 | ||
7 | extern "C" { | |
8 | ||
9 | void one() { | |
10 | state++; | |
11 | } | |
12 | ||
13 | int two() { | |
14 | return state; | |
15 | } | |
16 | ||
17 | } | |
18 |
80 | 80 | emscripten_webgl_make_context_current(0); |
81 | 81 | emscripten_webgl_destroy_context(ctx); |
82 | 82 | printf("quit\n"); |
83 | #ifdef REPORT_RESULT | |
84 | REPORT_RESULT(1); | |
85 | #endif | |
86 | 83 | exit(0); |
87 | 84 | } |
88 | 85 | } |
153 | 150 | usleep(16*1000); |
154 | 151 | } |
155 | 152 | #endif |
153 | return 99; | |
156 | 154 | } |
2 | 2 | "a.html.gz": 377, |
3 | 3 | "a.js": 4987, |
4 | 4 | "a.js.gz": 2406, |
5 | "a.wasm": 10343, | |
6 | "a.wasm.gz": 6687, | |
7 | "total": 15893, | |
8 | "total_gz": 9470 | |
5 | "a.wasm": 10310, | |
6 | "a.wasm.gz": 6660, | |
7 | "total": 15860, | |
8 | "total_gz": 9443 | |
9 | 9 | } |
0 | 0 | { |
1 | 1 | "a.html": 588, |
2 | 2 | "a.html.gz": 386, |
3 | "a.js": 19948, | |
4 | "a.js.gz": 8191, | |
3 | "a.js": 19895, | |
4 | "a.js.gz": 8158, | |
5 | 5 | "a.mem": 3171, |
6 | 6 | "a.mem.gz": 2714, |
7 | "total": 23707, | |
8 | "total_gz": 11291 | |
7 | "total": 23654, | |
8 | "total_gz": 11258 | |
9 | 9 | } |
2 | 2 | "a.html.gz": 377, |
3 | 3 | "a.js": 4471, |
4 | 4 | "a.js.gz": 2236, |
5 | "a.wasm": 10343, | |
6 | "a.wasm.gz": 6687, | |
7 | "total": 15377, | |
8 | "total_gz": 9300 | |
5 | "a.wasm": 10310, | |
6 | "a.wasm.gz": 6660, | |
7 | "total": 15344, | |
8 | "total_gz": 9273 | |
9 | 9 | } |
0 | 0 | { |
1 | 1 | "a.html": 588, |
2 | 2 | "a.html.gz": 386, |
3 | "a.js": 19430, | |
4 | "a.js.gz": 8023, | |
3 | "a.js": 19377, | |
4 | "a.js.gz": 7992, | |
5 | 5 | "a.mem": 3171, |
6 | 6 | "a.mem.gz": 2714, |
7 | "total": 23189, | |
8 | "total_gz": 11123 | |
7 | "total": 23136, | |
8 | "total_gz": 11092 | |
9 | 9 | } |
0 | 0 | { |
1 | "a.html": 12609, | |
2 | "a.html.gz": 6723, | |
3 | "total": 12609, | |
4 | "total_gz": 6723 | |
1 | "a.html": 12521, | |
2 | "a.html.gz": 6725, | |
3 | "total": 12521, | |
4 | "total_gz": 6725 | |
5 | 5 | } |
0 | 0 | { |
1 | "a.html": 17384, | |
2 | "a.html.gz": 7487, | |
3 | "total": 17384, | |
4 | "total_gz": 7487 | |
1 | "a.html": 17343, | |
2 | "a.html.gz": 7450, | |
3 | "total": 17343, | |
4 | "total_gz": 7450 | |
5 | 5 | } |
0 | # Copyright 2021 The Emscripten Authors. All rights reserved. | |
1 | # Emscripten is available under two separate licenses, the MIT license and the | |
2 | # University of Illinois/NCSA Open Source License. Both these licenses can be | |
3 | # found in the LICENSE file. | |
4 | ||
5 | from enum import Enum | |
6 | from functools import wraps | |
7 | from pathlib import Path | |
8 | from subprocess import PIPE, STDOUT | |
9 | from urllib.parse import unquote, unquote_plus | |
10 | from http.server import HTTPServer, SimpleHTTPRequestHandler | |
11 | import contextlib | |
12 | import difflib | |
13 | import hashlib | |
14 | import logging | |
15 | import multiprocessing | |
16 | import os | |
17 | import shlex | |
18 | import shutil | |
19 | import stat | |
20 | import string | |
21 | import subprocess | |
22 | import sys | |
23 | import tempfile | |
24 | import time | |
25 | import webbrowser | |
26 | import unittest | |
27 | ||
28 | import clang_native | |
29 | import jsrun | |
30 | from jsrun import NON_ZERO | |
31 | from tools.shared import TEMP_DIR, EMCC, EMXX, DEBUG, EMCONFIGURE, EMCMAKE | |
32 | from tools.shared import EMSCRIPTEN_TEMP_DIR | |
33 | from tools.shared import EM_BUILD_VERBOSE | |
34 | from tools.shared import get_canonical_temp_dir, try_delete, path_from_root | |
35 | from tools.utils import MACOS, WINDOWS | |
36 | from tools import shared, line_endings, building, config | |
37 | ||
38 | logger = logging.getLogger('common') | |
39 | ||
40 | # User can specify an environment variable EMTEST_BROWSER to force the browser | |
41 | # test suite to run using another browser command line than the default system | |
42 | # browser. Setting '0' as the browser disables running a browser (but we still | |
43 | # see tests compile) | |
44 | EMTEST_BROWSER = None | |
45 | EMTEST_DETECT_TEMPFILE_LEAKS = None | |
46 | EMTEST_SAVE_DIR = None | |
47 | # generally js engines are equivalent, testing 1 is enough. set this | |
48 | # to force testing on all js engines, good to find js engine bugs | |
49 | EMTEST_ALL_ENGINES = None | |
50 | EMTEST_SKIP_SLOW = None | |
51 | EMTEST_LACKS_NATIVE_CLANG = None | |
52 | EMTEST_VERBOSE = None | |
53 | EMTEST_REBASELINE = None | |
54 | ||
55 | TEST_ROOT = path_from_root('tests') | |
56 | ||
57 | WEBIDL_BINDER = shared.bat_suffix(path_from_root('tools/webidl_binder')) | |
58 | ||
59 | EMBUILDER = shared.bat_suffix(path_from_root('embuilder')) | |
60 | EMMAKE = shared.bat_suffix(path_from_root('emmake')) | |
61 | ||
62 | ||
63 | def delete_contents(pathname): | |
64 | for entry in os.listdir(pathname): | |
65 | try_delete(os.path.join(pathname, entry)) | |
66 | ||
67 | ||
68 | def test_file(*path_components): | |
69 | """Construct a path relative to the emscripten "tests" directory.""" | |
70 | return str(Path(TEST_ROOT, *path_components)) | |
71 | ||
72 | ||
73 | def read_file(*path_components): | |
74 | return Path(*path_components).read_text() | |
75 | ||
76 | ||
77 | def read_binary(*path_components): | |
78 | return Path(*path_components).read_bytes() | |
79 | ||
80 | ||
81 | # checks if browser testing is enabled | |
82 | def has_browser(): | |
83 | return EMTEST_BROWSER != '0' | |
84 | ||
85 | ||
86 | def compiler_for(filename, force_c=False): | |
87 | if shared.suffix(filename) in ('.cc', '.cxx', '.cpp') and not force_c: | |
88 | return EMXX | |
89 | else: | |
90 | return EMCC | |
91 | ||
92 | ||
93 | # Generic decorator that calls a function named 'condition' on the test class and | |
94 | # skips the test if that function returns true | |
95 | def skip_if(func, condition, explanation='', negate=False): | |
96 | assert callable(func) | |
97 | explanation_str = ' : %s' % explanation if explanation else '' | |
98 | ||
99 | @wraps(func) | |
100 | def decorated(self, *args, **kwargs): | |
101 | choice = self.__getattribute__(condition)() | |
102 | if negate: | |
103 | choice = not choice | |
104 | if choice: | |
105 | self.skipTest(condition + explanation_str) | |
106 | func(self, *args, **kwargs) | |
107 | ||
108 | return decorated | |
109 | ||
110 | ||
111 | def needs_dylink(func): | |
112 | assert callable(func) | |
113 | ||
114 | @wraps(func) | |
115 | def decorated(self): | |
116 | self.check_dylink() | |
117 | return func(self) | |
118 | ||
119 | return decorated | |
120 | ||
121 | ||
122 | def is_slow_test(func): | |
123 | assert callable(func) | |
124 | ||
125 | @wraps(func) | |
126 | def decorated(self, *args, **kwargs): | |
127 | if EMTEST_SKIP_SLOW: | |
128 | return self.skipTest('skipping slow tests') | |
129 | return func(self, *args, **kwargs) | |
130 | ||
131 | return decorated | |
132 | ||
133 | ||
134 | def disabled(note=''): | |
135 | assert not callable(note) | |
136 | return unittest.skip(note) | |
137 | ||
138 | ||
139 | def no_mac(note=''): | |
140 | assert not callable(note) | |
141 | if MACOS: | |
142 | return unittest.skip(note) | |
143 | return lambda f: f | |
144 | ||
145 | ||
146 | def no_windows(note=''): | |
147 | assert not callable(note) | |
148 | if WINDOWS: | |
149 | return unittest.skip(note) | |
150 | return lambda f: f | |
151 | ||
152 | ||
153 | def requires_native_clang(func): | |
154 | assert callable(func) | |
155 | ||
156 | def decorated(self, *args, **kwargs): | |
157 | if EMTEST_LACKS_NATIVE_CLANG: | |
158 | return self.skipTest('native clang tests are disabled') | |
159 | return func(self, *args, **kwargs) | |
160 | ||
161 | return decorated | |
162 | ||
163 | ||
164 | def require_node(func): | |
165 | assert callable(func) | |
166 | ||
167 | def decorated(self, *args, **kwargs): | |
168 | self.require_node() | |
169 | return func(self, *args, **kwargs) | |
170 | ||
171 | return decorated | |
172 | ||
173 | ||
174 | def require_v8(func): | |
175 | assert callable(func) | |
176 | ||
177 | def decorated(self, *args, **kwargs): | |
178 | self.require_v8() | |
179 | return func(self, *args, **kwargs) | |
180 | ||
181 | return decorated | |
182 | ||
183 | ||
184 | def node_pthreads(f): | |
185 | def decorated(self): | |
186 | self.set_setting('USE_PTHREADS') | |
187 | self.emcc_args += ['-Wno-pthreads-mem-growth'] | |
188 | if self.get_setting('MINIMAL_RUNTIME'): | |
189 | self.skipTest('node pthreads not yet supported with MINIMAL_RUNTIME') | |
190 | self.js_engines = [config.NODE_JS] | |
191 | self.node_args += ['--experimental-wasm-threads', '--experimental-wasm-bulk-memory'] | |
192 | f(self) | |
193 | return decorated | |
194 | ||
195 | ||
196 | @contextlib.contextmanager | |
197 | def env_modify(updates): | |
198 | """A context manager that updates os.environ.""" | |
199 | # This could also be done with mock.patch.dict() but taking a dependency | |
200 | # on the mock library is probably not worth the benefit. | |
201 | old_env = os.environ.copy() | |
202 | print("env_modify: " + str(updates)) | |
203 | # Seting a value to None means clear the environment variable | |
204 | clears = [key for key, value in updates.items() if value is None] | |
205 | updates = {key: value for key, value in updates.items() if value is not None} | |
206 | os.environ.update(updates) | |
207 | for key in clears: | |
208 | if key in os.environ: | |
209 | del os.environ[key] | |
210 | try: | |
211 | yield | |
212 | finally: | |
213 | os.environ.clear() | |
214 | os.environ.update(old_env) | |
215 | ||
216 | ||
217 | # Decorator version of env_modify | |
218 | def with_env_modify(updates): | |
219 | def decorated(f): | |
220 | def modified(self): | |
221 | with env_modify(updates): | |
222 | return f(self) | |
223 | return modified | |
224 | return decorated | |
225 | ||
226 | ||
227 | def ensure_dir(dirname): | |
228 | dirname = Path(dirname) | |
229 | dirname.mkdir(parents=True, exist_ok=True) | |
230 | ||
231 | ||
232 | def limit_size(string, maxbytes=800000 * 20, maxlines=100000, max_line=5000): | |
233 | lines = string.splitlines() | |
234 | for i, line in enumerate(lines): | |
235 | if len(line) > max_line: | |
236 | lines[i] = line[:max_line] + '[..]' | |
237 | if len(lines) > maxlines: | |
238 | lines = lines[0:maxlines // 2] + ['[..]'] + lines[-maxlines // 2:] | |
239 | string = '\n'.join(lines) + '\n' | |
240 | if len(string) > maxbytes: | |
241 | string = string[0:maxbytes // 2] + '\n[..]\n' + string[-maxbytes // 2:] | |
242 | return string | |
243 | ||
244 | ||
245 | def create_file(name, contents, binary=False): | |
246 | name = Path(name) | |
247 | assert not name.is_absolute() | |
248 | if binary: | |
249 | name.write_bytes(contents) | |
250 | else: | |
251 | name.write_text(contents) | |
252 | ||
253 | ||
254 | def make_executable(name): | |
255 | Path(name).chmod(stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC) | |
256 | ||
257 | ||
258 | def parameterized(parameters): | |
259 | """ | |
260 | Mark a test as parameterized. | |
261 | ||
262 | Usage: | |
263 | @parameterized({ | |
264 | 'subtest1': (1, 2, 3), | |
265 | 'subtest2': (4, 5, 6), | |
266 | }) | |
267 | def test_something(self, a, b, c): | |
268 | ... # actual test body | |
269 | ||
270 | This is equivalent to defining two tests: | |
271 | ||
272 | def test_something_subtest1(self): | |
273 | # runs test_something(1, 2, 3) | |
274 | ||
275 | def test_something_subtest2(self): | |
276 | # runs test_something(4, 5, 6) | |
277 | """ | |
278 | def decorator(func): | |
279 | func._parameterize = parameters | |
280 | return func | |
281 | return decorator | |
282 | ||
283 | ||
284 | class RunnerMeta(type): | |
285 | @classmethod | |
286 | def make_test(mcs, name, func, suffix, args): | |
287 | """ | |
288 | This is a helper function to create new test functions for each parameterized form. | |
289 | ||
290 | :param name: the original name of the function | |
291 | :param func: the original function that we are parameterizing | |
292 | :param suffix: the suffix to append to the name of the function for this parameterization | |
293 | :param args: the positional arguments to pass to the original function for this parameterization | |
294 | :returns: a tuple of (new_function_name, new_function_object) | |
295 | """ | |
296 | ||
297 | # Create the new test function. It calls the original function with the specified args. | |
298 | # We use @functools.wraps to copy over all the function attributes. | |
299 | @wraps(func) | |
300 | def resulting_test(self): | |
301 | return func(self, *args) | |
302 | ||
303 | # Add suffix to the function name so that it displays correctly. | |
304 | if suffix: | |
305 | resulting_test.__name__ = f'{name}_{suffix}' | |
306 | else: | |
307 | resulting_test.__name__ = name | |
308 | ||
309 | # On python 3, functions have __qualname__ as well. This is a full dot-separated path to the | |
310 | # function. We add the suffix to it as well. | |
311 | resulting_test.__qualname__ = f'{func.__qualname__}_{suffix}' | |
312 | ||
313 | return resulting_test.__name__, resulting_test | |
314 | ||
315 | def __new__(mcs, name, bases, attrs): | |
316 | # This metaclass expands parameterized methods from `attrs` into separate ones in `new_attrs`. | |
317 | new_attrs = {} | |
318 | ||
319 | for attr_name, value in attrs.items(): | |
320 | # Check if a member of the new class has _parameterize, the tag inserted by @parameterized. | |
321 | if hasattr(value, '_parameterize'): | |
322 | # If it does, we extract the parameterization information, build new test functions. | |
323 | for suffix, args in value._parameterize.items(): | |
324 | new_name, func = mcs.make_test(attr_name, value, suffix, args) | |
325 | assert new_name not in new_attrs, 'Duplicate attribute name generated when parameterizing %s' % attr_name | |
326 | new_attrs[new_name] = func | |
327 | else: | |
328 | # If not, we just copy it over to new_attrs verbatim. | |
329 | assert attr_name not in new_attrs, '%s collided with an attribute from parameterization' % attr_name | |
330 | new_attrs[attr_name] = value | |
331 | ||
332 | # We invoke type, the default metaclass, to actually create the new class, with new_attrs. | |
333 | return type.__new__(mcs, name, bases, new_attrs) | |
334 | ||
335 | ||
336 | class RunnerCore(unittest.TestCase, metaclass=RunnerMeta): | |
337 | # default temporary directory settings. set_temp_dir may be called later to | |
338 | # override these | |
339 | temp_dir = TEMP_DIR | |
340 | canonical_temp_dir = get_canonical_temp_dir(TEMP_DIR) | |
341 | ||
342 | # This avoids cluttering the test runner output, which is stderr too, with compiler warnings etc. | |
343 | # Change this to None to get stderr reporting, for debugging purposes | |
344 | stderr_redirect = STDOUT | |
345 | ||
346 | def is_wasm(self): | |
347 | return self.get_setting('WASM') != 0 | |
348 | ||
349 | def check_dylink(self): | |
350 | if self.get_setting('ALLOW_MEMORY_GROWTH') == 1 and not self.is_wasm(): | |
351 | self.skipTest('no dynamic linking with memory growth (without wasm)') | |
352 | if not self.is_wasm(): | |
353 | self.skipTest('no dynamic linking support in wasm2js yet') | |
354 | if '-fsanitize=address' in self.emcc_args: | |
355 | self.skipTest('no dynamic linking support in ASan yet') | |
356 | if '-fsanitize=leak' in self.emcc_args: | |
357 | self.skipTest('no dynamic linking support in LSan yet') | |
358 | ||
359 | def require_v8(self): | |
360 | if not config.V8_ENGINE or config.V8_ENGINE not in config.JS_ENGINES: | |
361 | if 'EMTEST_SKIP_V8' in os.environ: | |
362 | self.skipTest('test requires v8 and EMTEST_SKIP_V8 is set') | |
363 | else: | |
364 | self.fail('d8 required to run this test. Use EMTEST_SKIP_V8 to skip') | |
365 | self.js_engines = [config.V8_ENGINE] | |
366 | self.emcc_args.append('-sENVIRONMENT=shell') | |
367 | ||
368 | def require_node(self): | |
369 | if not config.NODE_JS or config.NODE_JS not in config.JS_ENGINES: | |
370 | if 'EMTEST_SKIP_NODE' in os.environ: | |
371 | self.skipTest('test requires node and EMTEST_SKIP_NODE is set') | |
372 | else: | |
373 | self.fail('node required to run this test. Use EMTEST_SKIP_NODE to skip') | |
374 | self.js_engines = [config.NODE_JS] | |
375 | ||
376 | def uses_memory_init_file(self): | |
377 | if self.get_setting('SIDE_MODULE') or (self.is_wasm() and not self.get_setting('WASM2JS')): | |
378 | return False | |
379 | elif '--memory-init-file' in self.emcc_args: | |
380 | return int(self.emcc_args[self.emcc_args.index('--memory-init-file') + 1]) | |
381 | else: | |
382 | # side modules handle memory differently; binaryen puts the memory in the wasm module | |
383 | opt_supports = any(opt in self.emcc_args for opt in ('-O2', '-O3', '-Os', '-Oz')) | |
384 | return opt_supports | |
385 | ||
386 | def set_temp_dir(self, temp_dir): | |
387 | self.temp_dir = temp_dir | |
388 | self.canonical_temp_dir = get_canonical_temp_dir(self.temp_dir) | |
389 | # Explicitly set dedicated temporary directory for parallel tests | |
390 | os.environ['EMCC_TEMP_DIR'] = self.temp_dir | |
391 | ||
392 | @classmethod | |
393 | def setUpClass(cls): | |
394 | super().setUpClass() | |
395 | print('(checking sanity from test runner)') # do this after we set env stuff | |
396 | shared.check_sanity(force=True) | |
397 | ||
398 | def setUp(self): | |
399 | super().setUp() | |
400 | self.settings_mods = {} | |
401 | self.emcc_args = ['-Werror'] | |
402 | self.node_args = [] | |
403 | self.v8_args = [] | |
404 | self.env = {} | |
405 | self.temp_files_before_run = [] | |
406 | self.uses_es6 = False | |
407 | self.js_engines = config.JS_ENGINES.copy() | |
408 | self.wasm_engines = config.WASM_ENGINES.copy() | |
409 | self.banned_js_engines = [] | |
410 | self.use_all_engines = EMTEST_ALL_ENGINES | |
411 | ||
412 | if EMTEST_DETECT_TEMPFILE_LEAKS: | |
413 | for root, dirnames, filenames in os.walk(self.temp_dir): | |
414 | for dirname in dirnames: | |
415 | self.temp_files_before_run.append(os.path.normpath(os.path.join(root, dirname))) | |
416 | for filename in filenames: | |
417 | self.temp_files_before_run.append(os.path.normpath(os.path.join(root, filename))) | |
418 | ||
419 | if EMTEST_SAVE_DIR: | |
420 | self.working_dir = os.path.join(self.temp_dir, 'emscripten_test') | |
421 | if os.path.exists(self.working_dir): | |
422 | if EMTEST_SAVE_DIR == 2: | |
423 | print('Not clearing existing test directory') | |
424 | else: | |
425 | print('Clearing existing test directory') | |
426 | # Even when EMTEST_SAVE_DIR we still try to start with an empty directoy as many tests | |
427 | # expect this. EMTEST_SAVE_DIR=2 can be used to keep the old contents for the new test | |
428 | # run. This can be useful when iterating on a given test with extra files you want to keep | |
429 | # around in the output directory. | |
430 | delete_contents(self.working_dir) | |
431 | else: | |
432 | print('Creating new test output directory') | |
433 | ensure_dir(self.working_dir) | |
434 | else: | |
435 | self.working_dir = tempfile.mkdtemp(prefix='emscripten_test_' + self.__class__.__name__ + '_', dir=self.temp_dir) | |
436 | os.chdir(self.working_dir) | |
437 | ||
438 | if not EMTEST_SAVE_DIR: | |
439 | self.has_prev_ll = False | |
440 | for temp_file in os.listdir(TEMP_DIR): | |
441 | if temp_file.endswith('.ll'): | |
442 | self.has_prev_ll = True | |
443 | ||
444 | def tearDown(self): | |
445 | if not EMTEST_SAVE_DIR: | |
446 | # rmtree() fails on Windows if the current working directory is inside the tree. | |
447 | os.chdir(os.path.dirname(self.get_dir())) | |
448 | try_delete(self.get_dir()) | |
449 | ||
450 | if EMTEST_DETECT_TEMPFILE_LEAKS and not DEBUG: | |
451 | temp_files_after_run = [] | |
452 | for root, dirnames, filenames in os.walk(self.temp_dir): | |
453 | for dirname in dirnames: | |
454 | temp_files_after_run.append(os.path.normpath(os.path.join(root, dirname))) | |
455 | for filename in filenames: | |
456 | temp_files_after_run.append(os.path.normpath(os.path.join(root, filename))) | |
457 | ||
458 | # Our leak detection will pick up *any* new temp files in the temp dir. | |
459 | # They may not be due to us, but e.g. the browser when running browser | |
460 | # tests. Until we figure out a proper solution, ignore some temp file | |
461 | # names that we see on our CI infrastructure. | |
462 | ignorable_file_prefixes = [ | |
463 | '/tmp/tmpaddon', | |
464 | '/tmp/circleci-no-output-timeout', | |
465 | '/tmp/wasmer' | |
466 | ] | |
467 | ||
468 | left_over_files = set(temp_files_after_run) - set(self.temp_files_before_run) | |
469 | left_over_files = [f for f in left_over_files if not any([f.startswith(prefix) for prefix in ignorable_file_prefixes])] | |
470 | if len(left_over_files): | |
471 | print('ERROR: After running test, there are ' + str(len(left_over_files)) + ' new temporary files/directories left behind:', file=sys.stderr) | |
472 | for f in left_over_files: | |
473 | print('leaked file: ' + f, file=sys.stderr) | |
474 | self.fail('Test leaked ' + str(len(left_over_files)) + ' temporary files!') | |
475 | ||
476 | def get_setting(self, key, default=None): | |
477 | return self.settings_mods.get(key, default) | |
478 | ||
479 | def set_setting(self, key, value=1): | |
480 | if value is None: | |
481 | self.clear_setting(key) | |
482 | self.settings_mods[key] = value | |
483 | ||
484 | def has_changed_setting(self, key): | |
485 | return key in self.settings_mods | |
486 | ||
487 | def clear_setting(self, key): | |
488 | self.settings_mods.pop(key, None) | |
489 | ||
490 | def serialize_settings(self): | |
491 | ret = [] | |
492 | for key, value in self.settings_mods.items(): | |
493 | if value == 1: | |
494 | ret.append(f'-s{key}') | |
495 | elif type(value) == list: | |
496 | ret.append(f'-s{key}={",".join(value)}') | |
497 | else: | |
498 | ret.append(f'-s{key}={value}') | |
499 | return ret | |
500 | ||
501 | def get_dir(self): | |
502 | return self.working_dir | |
503 | ||
504 | def in_dir(self, *pathelems): | |
505 | return os.path.join(self.get_dir(), *pathelems) | |
506 | ||
507 | def add_pre_run(self, code): | |
508 | create_file('prerun.js', 'Module.preRun = function() { %s }' % code) | |
509 | self.emcc_args += ['--pre-js', 'prerun.js'] | |
510 | ||
511 | def add_post_run(self, code): | |
512 | create_file('postrun.js', 'Module.postRun = function() { %s }' % code) | |
513 | self.emcc_args += ['--pre-js', 'postrun.js'] | |
514 | ||
515 | def add_on_exit(self, code): | |
516 | create_file('onexit.js', 'Module.onExit = function() { %s }' % code) | |
517 | self.emcc_args += ['--pre-js', 'onexit.js'] | |
518 | ||
519 | # returns the full list of arguments to pass to emcc | |
520 | # param @main_file whether this is the main file of the test. some arguments | |
521 | # (like --pre-js) do not need to be passed when building | |
522 | # libraries, for example | |
523 | def get_emcc_args(self, main_file=False): | |
524 | args = self.serialize_settings() + self.emcc_args | |
525 | if not main_file: | |
526 | for i, arg in enumerate(args): | |
527 | if arg in ('--pre-js', '--post-js'): | |
528 | args[i] = None | |
529 | args[i + 1] = None | |
530 | args = [arg for arg in args if arg is not None] | |
531 | return args | |
532 | ||
533 | def verify_es5(self, filename): | |
534 | es_check = shared.get_npm_cmd('es-check') | |
535 | # use --quiet once its available | |
536 | # See: https://github.com/dollarshaveclub/es-check/pull/126/ | |
537 | es_check_env = os.environ.copy() | |
538 | es_check_env['PATH'] = os.path.dirname(config.NODE_JS[0]) + os.pathsep + es_check_env['PATH'] | |
539 | try: | |
540 | shared.run_process(es_check + ['es5', os.path.abspath(filename), '--quiet'], stderr=PIPE, env=es_check_env) | |
541 | except subprocess.CalledProcessError as e: | |
542 | print(e.stderr) | |
543 | self.fail('es-check failed to verify ES5 output compliance') | |
544 | ||
545 | # Build JavaScript code from source code | |
546 | def build(self, filename, libraries=[], includes=[], force_c=False, js_outfile=True, emcc_args=[]): | |
547 | suffix = '.js' if js_outfile else '.wasm' | |
548 | compiler = [compiler_for(filename, force_c)] | |
549 | if compiler[0] == EMCC: | |
550 | # TODO(https://github.com/emscripten-core/emscripten/issues/11121) | |
551 | # We link with C++ stdlibs, even when linking with emcc for historical reasons. We can remove | |
552 | # this if this issues is fixed. | |
553 | compiler.append('-nostdlib++') | |
554 | ||
555 | if force_c: | |
556 | compiler.append('-xc') | |
557 | ||
558 | dirname, basename = os.path.split(filename) | |
559 | output = shared.unsuffixed(basename) + suffix | |
560 | cmd = compiler + [filename, '-o', output] + self.get_emcc_args(main_file=True) + emcc_args + libraries | |
561 | if shared.suffix(filename) not in ('.i', '.ii'): | |
562 | # Add the location of the test file to include path. | |
563 | cmd += ['-I.'] | |
564 | cmd += ['-I' + str(include) for include in includes] | |
565 | ||
566 | self.run_process(cmd, stderr=self.stderr_redirect if not DEBUG else None) | |
567 | self.assertExists(output) | |
568 | if js_outfile and not self.uses_es6: | |
569 | self.verify_es5(output) | |
570 | ||
571 | if js_outfile and self.uses_memory_init_file(): | |
572 | src = read_file(output) | |
573 | # side memory init file, or an empty one in the js | |
574 | assert ('/* memory initializer */' not in src) or ('/* memory initializer */ allocate([]' in src) | |
575 | ||
576 | return output | |
577 | ||
578 | def get_func(self, src, name): | |
579 | start = src.index('function ' + name + '(') | |
580 | t = start | |
581 | n = 0 | |
582 | while True: | |
583 | if src[t] == '{': | |
584 | n += 1 | |
585 | elif src[t] == '}': | |
586 | n -= 1 | |
587 | if n == 0: | |
588 | return src[start:t + 1] | |
589 | t += 1 | |
590 | assert t < len(src) | |
591 | ||
592 | def count_funcs(self, javascript_file): | |
593 | num_funcs = 0 | |
594 | start_tok = "// EMSCRIPTEN_START_FUNCS" | |
595 | end_tok = "// EMSCRIPTEN_END_FUNCS" | |
596 | start_off = 0 | |
597 | end_off = 0 | |
598 | ||
599 | js = read_file(javascript_file) | |
600 | blob = "".join(js.splitlines()) | |
601 | ||
602 | start_off = blob.find(start_tok) + len(start_tok) | |
603 | end_off = blob.find(end_tok) | |
604 | asm_chunk = blob[start_off:end_off] | |
605 | num_funcs = asm_chunk.count('function ') | |
606 | return num_funcs | |
607 | ||
608 | def count_wasm_contents(self, wasm_binary, what): | |
609 | out = self.run_process([os.path.join(building.get_binaryen_bin(), 'wasm-opt'), wasm_binary, '--metrics'], stdout=PIPE).stdout | |
610 | # output is something like | |
611 | # [?] : 125 | |
612 | for line in out.splitlines(): | |
613 | if '[' + what + ']' in line: | |
614 | ret = line.split(':')[1].strip() | |
615 | return int(ret) | |
616 | self.fail('Failed to find [%s] in wasm-opt output' % what) | |
617 | ||
618 | def get_wasm_text(self, wasm_binary): | |
619 | return self.run_process([os.path.join(building.get_binaryen_bin(), 'wasm-dis'), wasm_binary], stdout=PIPE).stdout | |
620 | ||
621 | def is_exported_in_wasm(self, name, wasm): | |
622 | wat = self.get_wasm_text(wasm) | |
623 | return ('(export "%s"' % name) in wat | |
624 | ||
625 | def run_js(self, filename, engine=None, args=[], output_nicerizer=None, assert_returncode=0): | |
626 | # use files, as PIPE can get too full and hang us | |
627 | stdout = self.in_dir('stdout') | |
628 | stderr = self.in_dir('stderr') | |
629 | error = None | |
630 | if not engine: | |
631 | engine = self.js_engines[0] | |
632 | if engine == config.NODE_JS: | |
633 | engine = engine + self.node_args | |
634 | if engine == config.V8_ENGINE: | |
635 | engine = engine + self.v8_args | |
636 | if EMTEST_VERBOSE: | |
637 | print(f"Running '{filename}' under '{shared.shlex_join(engine)}'") | |
638 | try: | |
639 | jsrun.run_js(filename, engine, args, | |
640 | stdout=open(stdout, 'w'), | |
641 | stderr=open(stderr, 'w'), | |
642 | assert_returncode=assert_returncode) | |
643 | except subprocess.CalledProcessError as e: | |
644 | error = e | |
645 | ||
646 | # Make sure that we produced proper line endings to the .js file we are about to run. | |
647 | if not filename.endswith('.wasm'): | |
648 | self.assertEqual(line_endings.check_line_endings(filename), 0) | |
649 | ||
650 | out = read_file(stdout) | |
651 | err = read_file(stderr) | |
652 | if output_nicerizer: | |
653 | ret = output_nicerizer(out, err) | |
654 | else: | |
655 | ret = out + err | |
656 | if error or EMTEST_VERBOSE: | |
657 | ret = limit_size(ret) | |
658 | print('-- begin program output --') | |
659 | print(ret, end='') | |
660 | print('-- end program output --') | |
661 | if error: | |
662 | if assert_returncode == NON_ZERO: | |
663 | self.fail('JS subprocess unexpectedly succeeded (%s): Output:\n%s' % (error.cmd, ret)) | |
664 | else: | |
665 | self.fail('JS subprocess failed (%s): %s. Output:\n%s' % (error.cmd, error.returncode, ret)) | |
666 | ||
667 | # We should pass all strict mode checks | |
668 | self.assertNotContained('strict warning:', ret) | |
669 | return ret | |
670 | ||
671 | def assertExists(self, filename, msg=None): | |
672 | if not msg: | |
673 | msg = 'Expected file not found: ' + filename | |
674 | self.assertTrue(os.path.exists(filename), msg) | |
675 | ||
676 | def assertNotExists(self, filename, msg=None): | |
677 | if not msg: | |
678 | msg = 'Unexpected file exists: ' + filename | |
679 | self.assertFalse(os.path.exists(filename), msg) | |
680 | ||
681 | # Tests that the given two paths are identical, modulo path delimiters. E.g. "C:/foo" is equal to "C:\foo". | |
682 | def assertPathsIdentical(self, path1, path2): | |
683 | path1 = path1.replace('\\', '/') | |
684 | path2 = path2.replace('\\', '/') | |
685 | return self.assertIdentical(path1, path2) | |
686 | ||
687 | # Tests that the given two multiline text content are identical, modulo line | |
688 | # ending differences (\r\n on Windows, \n on Unix). | |
689 | def assertTextDataIdentical(self, text1, text2, msg=None, | |
690 | fromfile='expected', tofile='actual'): | |
691 | text1 = text1.replace('\r\n', '\n') | |
692 | text2 = text2.replace('\r\n', '\n') | |
693 | return self.assertIdentical(text1, text2, msg, fromfile, tofile) | |
694 | ||
695 | def assertIdentical(self, values, y, msg=None, | |
696 | fromfile='expected', tofile='actual'): | |
697 | if type(values) not in (list, tuple): | |
698 | values = [values] | |
699 | for x in values: | |
700 | if x == y: | |
701 | return # success | |
702 | diff_lines = difflib.unified_diff(x.splitlines(), y.splitlines(), | |
703 | fromfile=fromfile, tofile=tofile) | |
704 | diff = ''.join([a.rstrip() + '\n' for a in diff_lines]) | |
705 | if EMTEST_VERBOSE: | |
706 | print("Expected to have '%s' == '%s'" % (limit_size(values[0]), limit_size(y))) | |
707 | fail_message = 'Unexpected difference:\n' + limit_size(diff) | |
708 | if not EMTEST_VERBOSE: | |
709 | fail_message += '\nFor full output run with EMTEST_VERBOSE=1.' | |
710 | if msg: | |
711 | fail_message += '\n' + msg | |
712 | self.fail(fail_message) | |
713 | ||
714 | def assertTextDataContained(self, text1, text2): | |
715 | text1 = text1.replace('\r\n', '\n') | |
716 | text2 = text2.replace('\r\n', '\n') | |
717 | return self.assertContained(text1, text2) | |
718 | ||
719 | def assertContained(self, values, string, additional_info=''): | |
720 | if type(values) not in [list, tuple]: | |
721 | values = [values] | |
722 | if callable(string): | |
723 | string = string() | |
724 | ||
725 | if not any(v in string for v in values): | |
726 | diff = difflib.unified_diff(values[0].split('\n'), string.split('\n'), fromfile='expected', tofile='actual') | |
727 | diff = ''.join(a.rstrip() + '\n' for a in diff) | |
728 | self.fail("Expected to find '%s' in '%s', diff:\n\n%s\n%s" % ( | |
729 | limit_size(values[0]), limit_size(string), limit_size(diff), | |
730 | additional_info | |
731 | )) | |
732 | ||
733 | def assertNotContained(self, value, string): | |
734 | if callable(value): | |
735 | value = value() # lazy loading | |
736 | if callable(string): | |
737 | string = string() | |
738 | if value in string: | |
739 | self.fail("Expected to NOT find '%s' in '%s', diff:\n\n%s" % ( | |
740 | limit_size(value), limit_size(string), | |
741 | limit_size(''.join([a.rstrip() + '\n' for a in difflib.unified_diff(value.split('\n'), string.split('\n'), fromfile='expected', tofile='actual')])) | |
742 | )) | |
743 | ||
744 | def assertContainedIf(self, value, string, condition): | |
745 | if condition: | |
746 | self.assertContained(value, string) | |
747 | else: | |
748 | self.assertNotContained(value, string) | |
749 | ||
750 | def assertBinaryEqual(self, file1, file2): | |
751 | self.assertEqual(os.path.getsize(file1), | |
752 | os.path.getsize(file2)) | |
753 | self.assertEqual(read_binary(file1), | |
754 | read_binary(file2)) | |
755 | ||
756 | library_cache = {} | |
757 | ||
758 | def get_build_dir(self): | |
759 | ret = os.path.join(self.get_dir(), 'building') | |
760 | ensure_dir(ret) | |
761 | return ret | |
762 | ||
763 | def get_library(self, name, generated_libs, configure=['sh', './configure'], | |
764 | configure_args=[], make=['make'], make_args=None, | |
765 | env_init=None, cache_name_extra='', native=False): | |
766 | if env_init is None: | |
767 | env_init = {} | |
768 | if make_args is None: | |
769 | make_args = ['-j', str(shared.get_num_cores())] | |
770 | ||
771 | build_dir = self.get_build_dir() | |
772 | output_dir = self.get_dir() | |
773 | ||
774 | emcc_args = self.get_emcc_args() | |
775 | ||
776 | hash_input = (str(emcc_args) + ' $ ' + str(env_init)).encode('utf-8') | |
777 | cache_name = name + ','.join([opt for opt in emcc_args if len(opt) < 7]) + '_' + hashlib.md5(hash_input).hexdigest() + cache_name_extra | |
778 | ||
779 | valid_chars = "_%s%s" % (string.ascii_letters, string.digits) | |
780 | cache_name = ''.join([(c if c in valid_chars else '_') for c in cache_name]) | |
781 | ||
782 | if self.library_cache.get(cache_name): | |
783 | print('<load %s from cache> ' % cache_name, file=sys.stderr) | |
784 | generated_libs = [] | |
785 | for basename, contents in self.library_cache[cache_name]: | |
786 | bc_file = os.path.join(build_dir, cache_name + '_' + basename) | |
787 | with open(bc_file, 'wb') as f: | |
788 | f.write(contents) | |
789 | generated_libs.append(bc_file) | |
790 | return generated_libs | |
791 | ||
792 | print(f'<building and saving {cache_name} into cache>', file=sys.stderr) | |
793 | if configure is not None: | |
794 | # Avoid += so we don't mutate the default arg | |
795 | configure = configure + configure_args | |
796 | ||
797 | cflags = ' '.join(self.get_emcc_args()) | |
798 | env_init.setdefault('CFLAGS', cflags) | |
799 | env_init.setdefault('CXXFLAGS', cflags) | |
800 | return build_library(name, build_dir, output_dir, generated_libs, configure, | |
801 | make, make_args, self.library_cache, | |
802 | cache_name, env_init=env_init, native=native) | |
803 | ||
804 | def clear(self): | |
805 | delete_contents(self.get_dir()) | |
806 | if EMSCRIPTEN_TEMP_DIR: | |
807 | delete_contents(EMSCRIPTEN_TEMP_DIR) | |
808 | ||
809 | def run_process(self, cmd, check=True, **args): | |
810 | # Wrapper around shared.run_process. This is desirable so that the tests | |
811 | # can fail (in the unittest sense) rather than error'ing. | |
812 | # In the long run it would nice to completely remove the dependency on | |
813 | # core emscripten code (shared.py) here. | |
814 | try: | |
815 | return shared.run_process(cmd, check=check, **args) | |
816 | except subprocess.CalledProcessError as e: | |
817 | if check and e.returncode != 0: | |
818 | self.fail('subprocess exited with non-zero return code(%d): `%s`' % | |
819 | (e.returncode, shared.shlex_join(cmd))) | |
820 | ||
821 | def emcc(self, filename, args=[], output_filename=None, **kwargs): | |
822 | if output_filename is None: | |
823 | output_filename = filename + '.o' | |
824 | try_delete(output_filename) | |
825 | self.run_process([compiler_for(filename), filename] + args + ['-o', output_filename], **kwargs) | |
826 | ||
827 | # Shared test code between main suite and others | |
828 | ||
829 | def expect_fail(self, cmd, **args): | |
830 | """Run a subprocess and assert that it returns non-zero. | |
831 | ||
832 | Return the stderr of the subprocess. | |
833 | """ | |
834 | proc = self.run_process(cmd, check=False, stderr=PIPE, **args) | |
835 | self.assertNotEqual(proc.returncode, 0, 'subprocess unexpectedly succeeded. stderr:\n' + proc.stderr) | |
836 | # When we check for failure we expect a user-visible error, not a traceback. | |
837 | # However, on windows a python traceback can happen randomly sometimes, | |
838 | # due to "Access is denied" https://github.com/emscripten-core/emscripten/issues/718 | |
839 | if not WINDOWS or 'Access is denied' not in proc.stderr: | |
840 | self.assertNotContained('Traceback', proc.stderr) | |
841 | return proc.stderr | |
842 | ||
843 | # excercise dynamic linker. | |
844 | # | |
845 | # test that linking to shared library B, which is linked to A, loads A as well. | |
846 | # main is also linked to C, which is also linked to A. A is loaded/initialized only once. | |
847 | # | |
848 | # B | |
849 | # main < > A | |
850 | # C | |
851 | # | |
852 | # this test is used by both test_core and test_browser. | |
853 | # when run under broswer it excercises how dynamic linker handles concurrency | |
854 | # - because B and C are loaded in parallel. | |
855 | def _test_dylink_dso_needed(self, do_run): | |
856 | create_file('liba.cpp', r''' | |
857 | #include <stdio.h> | |
858 | #include <emscripten.h> | |
859 | ||
860 | static const char *afunc_prev; | |
861 | ||
862 | extern "C" { | |
863 | EMSCRIPTEN_KEEPALIVE void afunc(const char *s); | |
864 | } | |
865 | ||
866 | void afunc(const char *s) { | |
867 | printf("a: %s (prev: %s)\n", s, afunc_prev); | |
868 | afunc_prev = s; | |
869 | } | |
870 | ||
871 | struct ainit { | |
872 | ainit() { | |
873 | puts("a: loaded"); | |
874 | } | |
875 | }; | |
876 | ||
877 | static ainit _; | |
878 | ''') | |
879 | ||
880 | create_file('libb.c', r''' | |
881 | #include <emscripten.h> | |
882 | ||
883 | void afunc(const char *s); | |
884 | ||
885 | EMSCRIPTEN_KEEPALIVE void bfunc() { | |
886 | afunc("b"); | |
887 | } | |
888 | ''') | |
889 | ||
890 | create_file('libc.c', r''' | |
891 | #include <emscripten.h> | |
892 | ||
893 | void afunc(const char *s); | |
894 | ||
895 | EMSCRIPTEN_KEEPALIVE void cfunc() { | |
896 | afunc("c"); | |
897 | } | |
898 | ''') | |
899 | ||
900 | # _test_dylink_dso_needed can be potentially called several times by a test. | |
901 | # reset dylink-related options first. | |
902 | self.clear_setting('MAIN_MODULE') | |
903 | self.clear_setting('SIDE_MODULE') | |
904 | ||
905 | # XXX in wasm each lib load currently takes 5MB; default INITIAL_MEMORY=16MB is thus not enough | |
906 | self.set_setting('INITIAL_MEMORY', '32mb') | |
907 | ||
908 | so = '.wasm' if self.is_wasm() else '.js' | |
909 | ||
910 | def ccshared(src, linkto=[]): | |
911 | cmdv = [EMCC, src, '-o', shared.unsuffixed(src) + so, '-s', 'SIDE_MODULE'] + self.get_emcc_args() | |
912 | cmdv += linkto | |
913 | self.run_process(cmdv) | |
914 | ||
915 | ccshared('liba.cpp') | |
916 | ccshared('libb.c', ['liba' + so]) | |
917 | ccshared('libc.c', ['liba' + so]) | |
918 | ||
919 | self.set_setting('MAIN_MODULE') | |
920 | extra_args = ['-L.', 'libb' + so, 'libc' + so] | |
921 | do_run(r''' | |
922 | #ifdef __cplusplus | |
923 | extern "C" { | |
924 | #endif | |
925 | void bfunc(); | |
926 | void cfunc(); | |
927 | #ifdef __cplusplus | |
928 | } | |
929 | #endif | |
930 | ||
931 | int test_main() { | |
932 | bfunc(); | |
933 | cfunc(); | |
934 | return 0; | |
935 | } | |
936 | ''', | |
937 | 'a: loaded\na: b (prev: (null))\na: c (prev: b)\n', emcc_args=extra_args) | |
938 | ||
939 | for libname in ['liba', 'libb', 'libc']: | |
940 | self.emcc_args += ['--embed-file', libname + so] | |
941 | do_run(r''' | |
942 | #include <assert.h> | |
943 | #include <dlfcn.h> | |
944 | #include <stddef.h> | |
945 | ||
946 | int test_main() { | |
947 | void *bdso, *cdso; | |
948 | void (*bfunc_ptr)(), (*cfunc_ptr)(); | |
949 | ||
950 | // FIXME for RTLD_LOCAL binding symbols to loaded lib is not currently working | |
951 | bdso = dlopen("libb%(so)s", RTLD_NOW|RTLD_GLOBAL); | |
952 | assert(bdso != NULL); | |
953 | cdso = dlopen("libc%(so)s", RTLD_NOW|RTLD_GLOBAL); | |
954 | assert(cdso != NULL); | |
955 | ||
956 | bfunc_ptr = (void (*)())dlsym(bdso, "bfunc"); | |
957 | assert(bfunc_ptr != NULL); | |
958 | cfunc_ptr = (void (*)())dlsym(cdso, "cfunc"); | |
959 | assert(cfunc_ptr != NULL); | |
960 | ||
961 | bfunc_ptr(); | |
962 | cfunc_ptr(); | |
963 | return 0; | |
964 | } | |
965 | ''' % locals(), | |
966 | 'a: loaded\na: b (prev: (null))\na: c (prev: b)\n') | |
967 | ||
968 | def filtered_js_engines(self, js_engines=None): | |
969 | if js_engines is None: | |
970 | js_engines = self.js_engines | |
971 | for engine in js_engines: | |
972 | assert engine in config.JS_ENGINES, "js engine does not exist in config.JS_ENGINES" | |
973 | assert type(engine) == list | |
974 | for engine in self.banned_js_engines: | |
975 | assert type(engine) in (list, type(None)) | |
976 | banned = [b[0] for b in self.banned_js_engines if b] | |
977 | return [engine for engine in js_engines if engine and engine[0] not in banned] | |
978 | ||
979 | def do_run(self, src, expected_output, force_c=False, **kwargs): | |
980 | if 'no_build' in kwargs: | |
981 | filename = src | |
982 | else: | |
983 | if force_c: | |
984 | filename = 'src.c' | |
985 | else: | |
986 | filename = 'src.cpp' | |
987 | with open(filename, 'w') as f: | |
988 | f.write(src) | |
989 | self._build_and_run(filename, expected_output, **kwargs) | |
990 | ||
991 | def do_runf(self, filename, expected_output=None, **kwargs): | |
992 | self._build_and_run(filename, expected_output, **kwargs) | |
993 | ||
994 | ## Just like `do_run` but with filename of expected output | |
995 | def do_run_from_file(self, filename, expected_output_filename, **kwargs): | |
996 | self._build_and_run(filename, read_file(expected_output_filename), **kwargs) | |
997 | ||
998 | def do_run_in_out_file_test(self, *path, **kwargs): | |
999 | srcfile = test_file(*path) | |
1000 | out_suffix = kwargs.pop('out_suffix', '') | |
1001 | outfile = shared.unsuffixed(srcfile) + out_suffix + '.out' | |
1002 | expected = read_file(outfile) | |
1003 | self._build_and_run(srcfile, expected, **kwargs) | |
1004 | ||
1005 | ## Does a complete test - builds, runs, checks output, etc. | |
1006 | def _build_and_run(self, filename, expected_output, args=[], output_nicerizer=None, | |
1007 | no_build=False, | |
1008 | js_engines=None, libraries=[], | |
1009 | includes=[], | |
1010 | assert_returncode=0, assert_identical=False, assert_all=False, | |
1011 | check_for_error=True, force_c=False, emcc_args=[]): | |
1012 | logger.debug(f'_build_and_run: {filename}') | |
1013 | ||
1014 | if no_build: | |
1015 | js_file = filename | |
1016 | else: | |
1017 | self.build(filename, libraries=libraries, includes=includes, | |
1018 | force_c=force_c, emcc_args=emcc_args) | |
1019 | js_file = shared.unsuffixed(os.path.basename(filename)) + '.js' | |
1020 | self.assertExists(js_file) | |
1021 | ||
1022 | engines = self.filtered_js_engines(js_engines) | |
1023 | if len(engines) > 1 and not self.use_all_engines: | |
1024 | engines = engines[:1] | |
1025 | # In standalone mode, also add wasm vms as we should be able to run there too. | |
1026 | if self.get_setting('STANDALONE_WASM'): | |
1027 | # TODO once standalone wasm support is more stable, apply use_all_engines | |
1028 | # like with js engines, but for now as we bring it up, test in all of them | |
1029 | if not self.wasm_engines: | |
1030 | logger.warning('no wasm engine was found to run the standalone part of this test') | |
1031 | engines += self.wasm_engines | |
1032 | if self.get_setting('WASM2C') and not EMTEST_LACKS_NATIVE_CLANG: | |
1033 | # compile the c file to a native executable. | |
1034 | c = shared.unsuffixed(js_file) + '.wasm.c' | |
1035 | executable = shared.unsuffixed(js_file) + '.exe' | |
1036 | cmd = [shared.CLANG_CC, c, '-o', executable] + clang_native.get_clang_native_args() | |
1037 | self.run_process(cmd, env=clang_native.get_clang_native_env()) | |
1038 | # we can now run the executable directly, without an engine, which | |
1039 | # we indicate with None as the engine | |
1040 | engines += [[None]] | |
1041 | if len(engines) == 0: | |
1042 | self.skipTest('No JS engine present to run this test with. Check %s and the paths therein.' % config.EM_CONFIG) | |
1043 | for engine in engines: | |
1044 | js_output = self.run_js(js_file, engine, args, output_nicerizer=output_nicerizer, assert_returncode=assert_returncode) | |
1045 | js_output = js_output.replace('\r\n', '\n') | |
1046 | if expected_output: | |
1047 | try: | |
1048 | if assert_identical: | |
1049 | self.assertIdentical(expected_output, js_output) | |
1050 | elif assert_all: | |
1051 | for o in expected_output: | |
1052 | self.assertContained(o, js_output) | |
1053 | else: | |
1054 | self.assertContained(expected_output, js_output) | |
1055 | if check_for_error: | |
1056 | self.assertNotContained('ERROR', js_output) | |
1057 | except Exception: | |
1058 | print('(test did not pass in JS engine: %s)' % engine) | |
1059 | raise | |
1060 | ||
1061 | def get_freetype_library(self): | |
1062 | if '-Werror' in self.emcc_args: | |
1063 | self.emcc_args.remove('-Werror') | |
1064 | return self.get_library(os.path.join('third_party', 'freetype'), os.path.join('objs', '.libs', 'libfreetype.a'), configure_args=['--disable-shared', '--without-zlib']) | |
1065 | ||
1066 | def get_poppler_library(self, env_init=None): | |
1067 | # The fontconfig symbols are all missing from the poppler build | |
1068 | # e.g. FcConfigSubstitute | |
1069 | self.set_setting('ERROR_ON_UNDEFINED_SYMBOLS', 0) | |
1070 | ||
1071 | self.emcc_args += [ | |
1072 | '-I' + test_file('third_party/freetype/include'), | |
1073 | '-I' + test_file('third_party/poppler/include') | |
1074 | ] | |
1075 | ||
1076 | freetype = self.get_freetype_library() | |
1077 | ||
1078 | # Poppler has some pretty glaring warning. Suppress them to keep the | |
1079 | # test output readable. | |
1080 | if '-Werror' in self.emcc_args: | |
1081 | self.emcc_args.remove('-Werror') | |
1082 | self.emcc_args += [ | |
1083 | '-Wno-sentinel', | |
1084 | '-Wno-logical-not-parentheses', | |
1085 | '-Wno-unused-private-field', | |
1086 | '-Wno-tautological-compare', | |
1087 | '-Wno-unknown-pragmas', | |
1088 | ] | |
1089 | env_init = env_init.copy() if env_init else {} | |
1090 | env_init['FONTCONFIG_CFLAGS'] = ' ' | |
1091 | env_init['FONTCONFIG_LIBS'] = ' ' | |
1092 | ||
1093 | poppler = self.get_library( | |
1094 | os.path.join('third_party', 'poppler'), | |
1095 | [os.path.join('utils', 'pdftoppm.o'), os.path.join('utils', 'parseargs.o'), os.path.join('poppler', '.libs', 'libpoppler.a')], | |
1096 | env_init=env_init, | |
1097 | configure_args=['--disable-libjpeg', '--disable-libpng', '--disable-poppler-qt', '--disable-poppler-qt4', '--disable-cms', '--disable-cairo-output', '--disable-abiword-output', '--disable-shared']) | |
1098 | ||
1099 | return poppler + freetype | |
1100 | ||
1101 | def get_zlib_library(self): | |
1102 | if WINDOWS: | |
1103 | return self.get_library(os.path.join('third_party', 'zlib'), os.path.join('libz.a'), | |
1104 | configure=['cmake', '.'], | |
1105 | make=['cmake', '--build', '.'], | |
1106 | make_args=[]) | |
1107 | return self.get_library(os.path.join('third_party', 'zlib'), os.path.join('libz.a'), make_args=['libz.a']) | |
1108 | ||
1109 | ||
1110 | # Run a server and a web page. When a test runs, we tell the server about it, | |
1111 | # which tells the web page, which then opens a window with the test. Doing | |
1112 | # it this way then allows the page to close() itself when done. | |
1113 | def harness_server_func(in_queue, out_queue, port): | |
1114 | class TestServerHandler(SimpleHTTPRequestHandler): | |
1115 | # Request header handler for default do_GET() path in | |
1116 | # SimpleHTTPRequestHandler.do_GET(self) below. | |
1117 | def send_head(self): | |
1118 | if self.path.endswith('.js'): | |
1119 | path = self.translate_path(self.path) | |
1120 | try: | |
1121 | f = open(path, 'rb') | |
1122 | except IOError: | |
1123 | self.send_error(404, "File not found: " + path) | |
1124 | return None | |
1125 | self.send_response(200) | |
1126 | self.send_header('Content-type', 'application/javascript') | |
1127 | self.send_header('Connection', 'close') | |
1128 | self.end_headers() | |
1129 | return f | |
1130 | else: | |
1131 | return SimpleHTTPRequestHandler.send_head(self) | |
1132 | ||
1133 | # Add COOP, COEP, CORP, and no-caching headers | |
1134 | def end_headers(self): | |
1135 | self.send_header('Access-Control-Allow-Origin', '*') | |
1136 | self.send_header('Cross-Origin-Opener-Policy', 'same-origin') | |
1137 | self.send_header('Cross-Origin-Embedder-Policy', 'require-corp') | |
1138 | self.send_header('Cross-Origin-Resource-Policy', 'cross-origin') | |
1139 | self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate') | |
1140 | return SimpleHTTPRequestHandler.end_headers(self) | |
1141 | ||
1142 | def do_GET(self): | |
1143 | if self.path == '/run_harness': | |
1144 | if DEBUG: | |
1145 | print('[server startup]') | |
1146 | self.send_response(200) | |
1147 | self.send_header('Content-type', 'text/html') | |
1148 | self.end_headers() | |
1149 | self.wfile.write(read_binary(test_file('browser_harness.html'))) | |
1150 | elif 'report_' in self.path: | |
1151 | # the test is reporting its result. first change dir away from the | |
1152 | # test dir, as it will be deleted now that the test is finishing, and | |
1153 | # if we got a ping at that time, we'd return an error | |
1154 | os.chdir(path_from_root()) | |
1155 | # for debugging, tests may encode the result and their own url (window.location) as result|url | |
1156 | if '|' in self.path: | |
1157 | path, url = self.path.split('|', 1) | |
1158 | else: | |
1159 | path = self.path | |
1160 | url = '?' | |
1161 | if DEBUG: | |
1162 | print('[server response:', path, url, ']') | |
1163 | if out_queue.empty(): | |
1164 | out_queue.put(path) | |
1165 | else: | |
1166 | # a badly-behaving test may send multiple xhrs with reported results; we just care | |
1167 | # about the first (if we queued the others, they might be read as responses for | |
1168 | # later tests, or maybe the test sends more than one in a racy manner). | |
1169 | # we place 'None' in the queue here so that the outside knows something went wrong | |
1170 | # (none is not a valid value otherwise; and we need the outside to know because if we | |
1171 | # raise an error in here, it is just swallowed in python's webserver code - we want | |
1172 | # the test to actually fail, which a webserver response can't do). | |
1173 | out_queue.put(None) | |
1174 | raise Exception('browser harness error, excessive response to server - test must be fixed! "%s"' % self.path) | |
1175 | self.send_response(200) | |
1176 | self.send_header('Content-type', 'text/plain') | |
1177 | self.send_header('Cache-Control', 'no-cache, must-revalidate') | |
1178 | self.send_header('Connection', 'close') | |
1179 | self.send_header('Expires', '-1') | |
1180 | self.end_headers() | |
1181 | self.wfile.write(b'OK') | |
1182 | ||
1183 | elif 'stdout=' in self.path or 'stderr=' in self.path or 'exception=' in self.path: | |
1184 | ''' | |
1185 | To get logging to the console from browser tests, add this to | |
1186 | print/printErr/the exception handler in src/shell.html: | |
1187 | ||
1188 | var xhr = new XMLHttpRequest(); | |
1189 | xhr.open('GET', encodeURI('http://localhost:8888?stdout=' + text)); | |
1190 | xhr.send(); | |
1191 | ''' | |
1192 | print('[client logging:', unquote_plus(self.path), ']') | |
1193 | self.send_response(200) | |
1194 | self.send_header('Content-type', 'text/html') | |
1195 | self.end_headers() | |
1196 | elif self.path == '/check': | |
1197 | self.send_response(200) | |
1198 | self.send_header('Content-type', 'text/html') | |
1199 | self.end_headers() | |
1200 | if not in_queue.empty(): | |
1201 | # there is a new test ready to be served | |
1202 | url, dir = in_queue.get() | |
1203 | if DEBUG: | |
1204 | print('[queue command:', url, dir, ']') | |
1205 | assert in_queue.empty(), 'should not be any blockage - one test runs at a time' | |
1206 | assert out_queue.empty(), 'the single response from the last test was read' | |
1207 | # tell the browser to load the test | |
1208 | self.wfile.write(b'COMMAND:' + url.encode('utf-8')) | |
1209 | # move us to the right place to serve the files for the new test | |
1210 | os.chdir(dir) | |
1211 | else: | |
1212 | # the browser must keep polling | |
1213 | self.wfile.write(b'(wait)') | |
1214 | else: | |
1215 | # Use SimpleHTTPServer default file serving operation for GET. | |
1216 | if DEBUG: | |
1217 | print('[simple HTTP serving:', unquote_plus(self.path), ']') | |
1218 | SimpleHTTPRequestHandler.do_GET(self) | |
1219 | ||
1220 | def log_request(code=0, size=0): | |
1221 | # don't log; too noisy | |
1222 | pass | |
1223 | ||
1224 | # allows streaming compilation to work | |
1225 | SimpleHTTPRequestHandler.extensions_map['.wasm'] = 'application/wasm' | |
1226 | ||
1227 | httpd = HTTPServer(('localhost', port), TestServerHandler) | |
1228 | httpd.serve_forever() # test runner will kill us | |
1229 | ||
1230 | ||
1231 | class Reporting(Enum): | |
1232 | """When running browser tests we normally automatically include support | |
1233 | code for reporting results back to the browser. This enum allows tests | |
1234 | to decide what type of support code they need/want. | |
1235 | """ | |
1236 | NONE = 0 | |
1237 | # Include the JS helpers for reporting results | |
1238 | JS_ONLY = 1 | |
1239 | # Include C/C++ reporting code (REPORT_RESULT mactros) as well as JS helpers | |
1240 | FULL = 2 | |
1241 | ||
1242 | ||
1243 | class BrowserCore(RunnerCore): | |
1244 | # note how many tests hang / do not send an output. if many of these | |
1245 | # happen, likely something is broken and it is best to abort the test | |
1246 | # suite early, as otherwise we will wait for the timeout on every | |
1247 | # single test (hundreds of minutes) | |
1248 | MAX_UNRESPONSIVE_TESTS = 10 | |
1249 | ||
1250 | unresponsive_tests = 0 | |
1251 | ||
1252 | def __init__(self, *args, **kwargs): | |
1253 | super().__init__(*args, **kwargs) | |
1254 | ||
1255 | @staticmethod | |
1256 | def browser_open(url): | |
1257 | if not EMTEST_BROWSER: | |
1258 | logger.info('Using default system browser') | |
1259 | webbrowser.open_new(url) | |
1260 | return | |
1261 | ||
1262 | browser_args = shlex.split(EMTEST_BROWSER) | |
1263 | # If the given browser is a scalar, treat it like one of the possible types | |
1264 | # from https://docs.python.org/2/library/webbrowser.html | |
1265 | if len(browser_args) == 1: | |
1266 | try: | |
1267 | # This throws if the type of browser isn't available | |
1268 | webbrowser.get(browser_args[0]).open_new(url) | |
1269 | logger.info('Using Emscripten browser: %s', browser_args[0]) | |
1270 | return | |
1271 | except webbrowser.Error: | |
1272 | # Ignore the exception and fallback to the custom command logic | |
1273 | pass | |
1274 | # Else assume the given browser is a specific program with additional | |
1275 | # parameters and delegate to that | |
1276 | logger.info('Using Emscripten browser: %s', str(browser_args)) | |
1277 | subprocess.Popen(browser_args + [url]) | |
1278 | ||
1279 | @classmethod | |
1280 | def setUpClass(cls): | |
1281 | super().setUpClass() | |
1282 | cls.also_asmjs = int(os.getenv('EMTEST_BROWSER_ALSO_ASMJS', '0')) == 1 | |
1283 | cls.port = int(os.getenv('EMTEST_BROWSER_PORT', '8888')) | |
1284 | if not has_browser(): | |
1285 | return | |
1286 | cls.browser_timeout = 60 | |
1287 | cls.harness_in_queue = multiprocessing.Queue() | |
1288 | cls.harness_out_queue = multiprocessing.Queue() | |
1289 | cls.harness_server = multiprocessing.Process(target=harness_server_func, args=(cls.harness_in_queue, cls.harness_out_queue, cls.port)) | |
1290 | cls.harness_server.start() | |
1291 | print('[Browser harness server on process %d]' % cls.harness_server.pid) | |
1292 | cls.browser_open('http://localhost:%s/run_harness' % cls.port) | |
1293 | ||
1294 | @classmethod | |
1295 | def tearDownClass(cls): | |
1296 | super().tearDownClass() | |
1297 | if not has_browser(): | |
1298 | return | |
1299 | cls.harness_server.terminate() | |
1300 | print('[Browser harness server terminated]') | |
1301 | if WINDOWS: | |
1302 | # On Windows, shutil.rmtree() in tearDown() raises this exception if we do not wait a bit: | |
1303 | # WindowsError: [Error 32] The process cannot access the file because it is being used by another process. | |
1304 | time.sleep(0.1) | |
1305 | ||
1306 | def assert_out_queue_empty(self, who): | |
1307 | if not self.harness_out_queue.empty(): | |
1308 | while not self.harness_out_queue.empty(): | |
1309 | self.harness_out_queue.get() | |
1310 | raise Exception('excessive responses from %s' % who) | |
1311 | ||
1312 | # @param extra_tries: how many more times to try this test, if it fails. browser tests have | |
1313 | # many more causes of flakiness (in particular, they do not run | |
1314 | # synchronously, so we have a timeout, which can be hit if the VM | |
1315 | # we run on stalls temporarily), so we let each test try more than | |
1316 | # once by default | |
1317 | def run_browser(self, html_file, message, expectedResult=None, timeout=None, extra_tries=1): | |
1318 | if not has_browser(): | |
1319 | return | |
1320 | if BrowserCore.unresponsive_tests >= BrowserCore.MAX_UNRESPONSIVE_TESTS: | |
1321 | self.skipTest('too many unresponsive tests, skipping browser launch - check your setup!') | |
1322 | self.assert_out_queue_empty('previous test') | |
1323 | if DEBUG: | |
1324 | print('[browser launch:', html_file, ']') | |
1325 | if expectedResult is not None: | |
1326 | try: | |
1327 | self.harness_in_queue.put(( | |
1328 | 'http://localhost:%s/%s' % (self.port, html_file), | |
1329 | self.get_dir() | |
1330 | )) | |
1331 | received_output = False | |
1332 | output = '[no http server activity]' | |
1333 | start = time.time() | |
1334 | if timeout is None: | |
1335 | timeout = self.browser_timeout | |
1336 | while time.time() - start < timeout: | |
1337 | if not self.harness_out_queue.empty(): | |
1338 | output = self.harness_out_queue.get() | |
1339 | received_output = True | |
1340 | break | |
1341 | time.sleep(0.1) | |
1342 | if not received_output: | |
1343 | BrowserCore.unresponsive_tests += 1 | |
1344 | print('[unresponsive tests: %d]' % BrowserCore.unresponsive_tests) | |
1345 | if output is None: | |
1346 | # the browser harness reported an error already, and sent a None to tell | |
1347 | # us to also fail the test | |
1348 | raise Exception('failing test due to browser harness error') | |
1349 | if output.startswith('/report_result?skipped:'): | |
1350 | self.skipTest(unquote(output[len('/report_result?skipped:'):]).strip()) | |
1351 | else: | |
1352 | # verify the result, and try again if we should do so | |
1353 | output = unquote(output) | |
1354 | try: | |
1355 | self.assertContained(expectedResult, output) | |
1356 | except Exception as e: | |
1357 | if extra_tries > 0: | |
1358 | print('[test error (see below), automatically retrying]') | |
1359 | print(e) | |
1360 | return self.run_browser(html_file, message, expectedResult, timeout, extra_tries - 1) | |
1361 | else: | |
1362 | raise e | |
1363 | finally: | |
1364 | time.sleep(0.1) # see comment about Windows above | |
1365 | self.assert_out_queue_empty('this test') | |
1366 | else: | |
1367 | webbrowser.open_new(os.path.abspath(html_file)) | |
1368 | print('A web browser window should have opened a page containing the results of a part of this test.') | |
1369 | print('You need to manually look at the page to see that it works ok: ' + message) | |
1370 | print('(sleeping for a bit to keep the directory alive for the web browser..)') | |
1371 | time.sleep(5) | |
1372 | print('(moving on..)') | |
1373 | ||
1374 | # @manually_trigger If set, we do not assume we should run the reftest when main() is done. | |
1375 | # Instead, call doReftest() in JS yourself at the right time. | |
1376 | def reftest(self, expected, manually_trigger=False): | |
1377 | # make sure the pngs used here have no color correction, using e.g. | |
1378 | # pngcrush -rem gAMA -rem cHRM -rem iCCP -rem sRGB infile outfile | |
1379 | basename = os.path.basename(expected) | |
1380 | shutil.copyfile(expected, os.path.join(self.get_dir(), basename)) | |
1381 | reporting = read_file(test_file('browser_reporting.js')) | |
1382 | with open('reftest.js', 'w') as out: | |
1383 | out.write(''' | |
1384 | function doReftest() { | |
1385 | if (doReftest.done) return; | |
1386 | doReftest.done = true; | |
1387 | var img = new Image(); | |
1388 | img.onload = function() { | |
1389 | assert(img.width == Module.canvas.width, 'Invalid width: ' + Module.canvas.width + ', should be ' + img.width); | |
1390 | assert(img.height == Module.canvas.height, 'Invalid height: ' + Module.canvas.height + ', should be ' + img.height); | |
1391 | ||
1392 | var canvas = document.createElement('canvas'); | |
1393 | canvas.width = img.width; | |
1394 | canvas.height = img.height; | |
1395 | var ctx = canvas.getContext('2d'); | |
1396 | ctx.drawImage(img, 0, 0); | |
1397 | var expected = ctx.getImageData(0, 0, img.width, img.height).data; | |
1398 | ||
1399 | var actualUrl = Module.canvas.toDataURL(); | |
1400 | var actualImage = new Image(); | |
1401 | actualImage.onload = function() { | |
1402 | /* | |
1403 | document.body.appendChild(img); // for comparisons | |
1404 | var div = document.createElement('div'); | |
1405 | div.innerHTML = '^=expected, v=actual'; | |
1406 | document.body.appendChild(div); | |
1407 | document.body.appendChild(actualImage); // to grab it for creating the test reference | |
1408 | */ | |
1409 | ||
1410 | var actualCanvas = document.createElement('canvas'); | |
1411 | actualCanvas.width = actualImage.width; | |
1412 | actualCanvas.height = actualImage.height; | |
1413 | var actualCtx = actualCanvas.getContext('2d'); | |
1414 | actualCtx.drawImage(actualImage, 0, 0); | |
1415 | var actual = actualCtx.getImageData(0, 0, actualImage.width, actualImage.height).data; | |
1416 | ||
1417 | var total = 0; | |
1418 | var width = img.width; | |
1419 | var height = img.height; | |
1420 | for (var x = 0; x < width; x++) { | |
1421 | for (var y = 0; y < height; y++) { | |
1422 | total += Math.abs(expected[y*width*4 + x*4 + 0] - actual[y*width*4 + x*4 + 0]); | |
1423 | total += Math.abs(expected[y*width*4 + x*4 + 1] - actual[y*width*4 + x*4 + 1]); | |
1424 | total += Math.abs(expected[y*width*4 + x*4 + 2] - actual[y*width*4 + x*4 + 2]); | |
1425 | } | |
1426 | } | |
1427 | var wrong = Math.floor(total / (img.width*img.height*3)); // floor, to allow some margin of error for antialiasing | |
1428 | // If the main JS file is in a worker, or modularize, then we need to supply our own reporting logic. | |
1429 | if (typeof reportResultToServer === 'undefined') { | |
1430 | (function() { | |
1431 | %s | |
1432 | reportResultToServer(wrong); | |
1433 | })(); | |
1434 | } else { | |
1435 | reportResultToServer(wrong); | |
1436 | } | |
1437 | }; | |
1438 | actualImage.src = actualUrl; | |
1439 | } | |
1440 | img.src = '%s'; | |
1441 | }; | |
1442 | ||
1443 | // Automatically trigger the reftest? | |
1444 | if (!%s) { | |
1445 | // Yes, automatically | |
1446 | ||
1447 | Module['postRun'] = doReftest; | |
1448 | ||
1449 | if (typeof WebGLClient !== 'undefined') { | |
1450 | // trigger reftest from RAF as well, needed for workers where there is no pre|postRun on the main thread | |
1451 | var realRAF = window.requestAnimationFrame; | |
1452 | window.requestAnimationFrame = /** @suppress{checkTypes} */ (function(func) { | |
1453 | realRAF(function() { | |
1454 | func(); | |
1455 | realRAF(doReftest); | |
1456 | }); | |
1457 | }); | |
1458 | ||
1459 | // trigger reftest from canvas render too, for workers not doing GL | |
1460 | var realWOM = worker.onmessage; | |
1461 | worker.onmessage = function(event) { | |
1462 | realWOM(event); | |
1463 | if (event.data.target === 'canvas' && event.data.op === 'render') { | |
1464 | realRAF(doReftest); | |
1465 | } | |
1466 | }; | |
1467 | } | |
1468 | ||
1469 | } else { | |
1470 | // Manually trigger the reftest. | |
1471 | ||
1472 | // The user will call it. | |
1473 | // Add an event loop iteration to ensure rendering, so users don't need to bother. | |
1474 | var realDoReftest = doReftest; | |
1475 | doReftest = function() { | |
1476 | setTimeout(realDoReftest, 1); | |
1477 | }; | |
1478 | } | |
1479 | ''' % (reporting, basename, int(manually_trigger))) | |
1480 | ||
1481 | def compile_btest(self, args, reporting=Reporting.FULL): | |
1482 | # Inject support code for reporting results. This adds an include a header so testcases can | |
1483 | # use REPORT_RESULT, and also adds a cpp file to be compiled alongside the testcase, which | |
1484 | # contains the implementation of REPORT_RESULT (we can't just include that implementation in | |
1485 | # the header as there may be multiple files being compiled here). | |
1486 | args += ['-s', 'IN_TEST_HARNESS'] | |
1487 | if reporting != Reporting.NONE: | |
1488 | # For basic reporting we inject JS helper funtions to report result back to server. | |
1489 | args += ['-DEMTEST_PORT_NUMBER=%d' % self.port, | |
1490 | '--pre-js', test_file('browser_reporting.js')] | |
1491 | if reporting == Reporting.FULL: | |
1492 | # If C reporting (i.e. REPORT_RESULT macro) is required | |
1493 | # also compile in report_result.cpp and forice-include report_result.h | |
1494 | args += ['-I' + TEST_ROOT, | |
1495 | '-include', test_file('report_result.h'), | |
1496 | test_file('report_result.cpp')] | |
1497 | self.run_process([EMCC] + self.get_emcc_args() + args) | |
1498 | ||
1499 | def btest_exit(self, filename, assert_returncode=0, *args, **kwargs): | |
1500 | """Special case of btest that reports its result solely via exiting | |
1501 | with a give result code. | |
1502 | ||
1503 | In this case we set EXIT_RUNTIME and we don't need to provide the | |
1504 | REPORT_RESULT macro to the C code. | |
1505 | """ | |
1506 | self.set_setting('EXIT_RUNTIME') | |
1507 | kwargs['reporting'] = Reporting.JS_ONLY | |
1508 | kwargs['expected'] = 'exit:%d' % assert_returncode | |
1509 | return self.btest(filename, *args, **kwargs) | |
1510 | ||
1511 | def btest(self, filename, expected=None, reference=None, | |
1512 | reference_slack=0, manual_reference=False, post_build=None, | |
1513 | args=None, message='.', also_proxied=False, | |
1514 | url_suffix='', timeout=None, also_asmjs=False, | |
1515 | manually_trigger_reftest=False, extra_tries=1, | |
1516 | reporting=Reporting.FULL): | |
1517 | assert expected or reference, 'a btest must either expect an output, or have a reference image' | |
1518 | if args is None: | |
1519 | args = [] | |
1520 | original_args = args.copy() | |
1521 | if not os.path.exists(filename): | |
1522 | filename = test_file(filename) | |
1523 | if reference: | |
1524 | self.reference = reference | |
1525 | expected = [str(i) for i in range(0, reference_slack + 1)] | |
1526 | self.reftest(test_file(reference), manually_trigger=manually_trigger_reftest) | |
1527 | if not manual_reference: | |
1528 | args += ['--pre-js', 'reftest.js', '-s', 'GL_TESTING'] | |
1529 | outfile = 'test.html' | |
1530 | args += [filename, '-o', outfile] | |
1531 | # print('all args:', args) | |
1532 | try_delete(outfile) | |
1533 | self.compile_btest(args, reporting=reporting) | |
1534 | self.assertExists(outfile) | |
1535 | if post_build: | |
1536 | post_build() | |
1537 | if not isinstance(expected, list): | |
1538 | expected = [expected] | |
1539 | self.run_browser(outfile + url_suffix, message, ['/report_result?' + e for e in expected], timeout=timeout, extra_tries=extra_tries) | |
1540 | ||
1541 | # Tests can opt into being run under asmjs as well | |
1542 | if 'WASM=0' not in original_args and (also_asmjs or self.also_asmjs): | |
1543 | print('WASM=0') | |
1544 | self.btest(filename, expected, reference, reference_slack, manual_reference, post_build, | |
1545 | original_args + ['-s', 'WASM=0'], message, also_proxied=False, timeout=timeout) | |
1546 | ||
1547 | if also_proxied: | |
1548 | print('proxied...') | |
1549 | if reference: | |
1550 | assert not manual_reference | |
1551 | manual_reference = True | |
1552 | assert not post_build | |
1553 | post_build = self.post_manual_reftest | |
1554 | # run proxied | |
1555 | self.btest(filename, expected, reference, reference_slack, manual_reference, post_build, | |
1556 | original_args + ['--proxy-to-worker', '-s', 'GL_TESTING'], message, timeout=timeout) | |
1557 | ||
1558 | ||
1559 | ################################################################################################### | |
1560 | ||
1561 | ||
1562 | def build_library(name, | |
1563 | build_dir, | |
1564 | output_dir, | |
1565 | generated_libs, | |
1566 | configure=['sh', './configure'], | |
1567 | make=['make'], | |
1568 | make_args=[], | |
1569 | cache=None, | |
1570 | cache_name=None, | |
1571 | env_init={}, | |
1572 | native=False): | |
1573 | """Build a library and cache the result. We build the library file | |
1574 | once and cache it for all our tests. (We cache in memory since the test | |
1575 | directory is destroyed and recreated for each test. Note that we cache | |
1576 | separately for different compilers). This cache is just during the test | |
1577 | runner. There is a different concept of caching as well, see |Cache|. | |
1578 | """ | |
1579 | ||
1580 | if type(generated_libs) is not list: | |
1581 | generated_libs = [generated_libs] | |
1582 | source_dir = test_file(name.replace('_native', '')) | |
1583 | ||
1584 | project_dir = Path(build_dir, name) | |
1585 | if os.path.exists(project_dir): | |
1586 | shutil.rmtree(project_dir) | |
1587 | # Useful in debugging sometimes to comment this out, and two lines above | |
1588 | shutil.copytree(source_dir, project_dir) | |
1589 | ||
1590 | generated_libs = [os.path.join(project_dir, lib) for lib in generated_libs] | |
1591 | ||
1592 | if native: | |
1593 | env = clang_native.get_clang_native_env() | |
1594 | else: | |
1595 | env = os.environ.copy() | |
1596 | env.update(env_init) | |
1597 | ||
1598 | if configure: | |
1599 | if configure[0] == 'cmake': | |
1600 | configure = [EMCMAKE] + configure | |
1601 | else: | |
1602 | configure = [EMCONFIGURE] + configure | |
1603 | try: | |
1604 | with open(os.path.join(project_dir, 'configure_out'), 'w') as out: | |
1605 | with open(os.path.join(project_dir, 'configure_err'), 'w') as err: | |
1606 | stdout = out if EM_BUILD_VERBOSE < 2 else None | |
1607 | stderr = err if EM_BUILD_VERBOSE < 1 else None | |
1608 | shared.run_process(configure, env=env, stdout=stdout, stderr=stderr, | |
1609 | cwd=project_dir) | |
1610 | except subprocess.CalledProcessError: | |
1611 | print('-- configure stdout --') | |
1612 | print(read_file(Path(project_dir, 'configure_out'))) | |
1613 | print('-- end configure stdout --') | |
1614 | print('-- configure stderr --') | |
1615 | print(read_file(Path(project_dir, 'configure_err'))) | |
1616 | print('-- end configure stderr --') | |
1617 | raise | |
1618 | # if we run configure or cmake we don't then need any kind | |
1619 | # of special env when we run make below | |
1620 | env = None | |
1621 | ||
1622 | def open_make_out(mode='r'): | |
1623 | return open(os.path.join(project_dir, 'make.out'), mode) | |
1624 | ||
1625 | def open_make_err(mode='r'): | |
1626 | return open(os.path.join(project_dir, 'make.err'), mode) | |
1627 | ||
1628 | if EM_BUILD_VERBOSE >= 3: | |
1629 | make_args += ['VERBOSE=1'] | |
1630 | ||
1631 | try: | |
1632 | with open_make_out('w') as make_out: | |
1633 | with open_make_err('w') as make_err: | |
1634 | stdout = make_out if EM_BUILD_VERBOSE < 2 else None | |
1635 | stderr = make_err if EM_BUILD_VERBOSE < 1 else None | |
1636 | shared.run_process(make + make_args, stdout=stdout, stderr=stderr, env=env, | |
1637 | cwd=project_dir) | |
1638 | except subprocess.CalledProcessError: | |
1639 | with open_make_out() as f: | |
1640 | print('-- make stdout --') | |
1641 | print(f.read()) | |
1642 | print('-- end make stdout --') | |
1643 | with open_make_err() as f: | |
1644 | print('-- make stderr --') | |
1645 | print(f.read()) | |
1646 | print('-- end stderr --') | |
1647 | raise | |
1648 | ||
1649 | if cache is not None: | |
1650 | cache[cache_name] = [] | |
1651 | for f in generated_libs: | |
1652 | basename = os.path.basename(f) | |
1653 | cache[cache_name].append((basename, read_binary(f))) | |
1654 | ||
1655 | return generated_libs |
0 | #include <assert.h> | |
1 | #include <stdbool.h> | |
2 | #include <stdlib.h> | |
3 | #include <stdio.h> | |
4 | #include <emscripten/emscripten.h> | |
5 | ||
6 | bool doneCallback = false; | |
7 | ||
8 | void myatexit() { | |
9 | printf("myatexit\n"); | |
10 | assert(doneCallback); | |
11 | } | |
12 | ||
13 | void callback(void *arg) { | |
14 | printf("callback\n"); | |
15 | doneCallback = true; | |
16 | assert((int)arg == 42); | |
17 | // Runtime should exit after this callback returns | |
18 | } | |
19 | ||
20 | int main() { | |
21 | atexit(myatexit); | |
22 | // The runtime should stay alive long enough for the callbackl | |
23 | // to run. | |
24 | emscripten_async_call(callback, (void*)42, 500); | |
25 | printf("returning from main\n"); | |
26 | return 0; | |
27 | } |
63 | 63 | atexit(cleanup); |
64 | 64 | setup(); |
65 | 65 | test(); |
66 | ||
67 | #ifdef REPORT_RESULT | |
68 | REPORT_RESULT(0); | |
69 | #endif | |
70 | return EXIT_SUCCESS; | |
66 | return 0; | |
71 | 67 | } |
0 | // Copyright 2012 The Emscripten Authors. All rights reserved. | |
1 | // Emscripten is available under two separate licenses, the MIT license and the | |
2 | // University of Illinois/NCSA Open Source License. Both these licenses can be | |
3 | // found in the LICENSE file. | |
4 | ||
5 | #include <stdbool.h> | |
6 | #include <stdio.h> | |
7 | #include <math.h> | |
8 | #include <stdlib.h> | |
9 | #include <SDL.h> | |
10 | #include <emscripten.h> | |
11 | #include <assert.h> | |
12 | ||
13 | bool exit_ok = false; | |
14 | int last = 0; | |
15 | ||
16 | bool pre1ed = false; | |
17 | bool pre2ed = false; | |
18 | void pre1(void *arg) { | |
19 | assert(!pre1ed); | |
20 | assert(!pre2ed); | |
21 | assert((int)arg == 123); | |
22 | pre1ed = true; | |
23 | } | |
24 | void pre2(void *arg) { | |
25 | assert(pre1ed); | |
26 | assert(!pre2ed); | |
27 | assert((int)arg == 98); | |
28 | pre2ed = true; | |
29 | } | |
30 | ||
31 | bool fived = false; | |
32 | void five(void *arg) { | |
33 | assert((int)arg == 55); | |
34 | fived = true; | |
35 | emscripten_resume_main_loop(); | |
36 | } | |
37 | ||
38 | void argey(void* arg) { | |
39 | static int counter = 0; | |
40 | assert((int)arg == 17); | |
41 | counter++; | |
42 | printf("argey: %d\n", counter); | |
43 | if (counter == 5) { | |
44 | emscripten_cancel_main_loop(); | |
45 | // The main loop is now done so its ok to run atexit handlers. | |
46 | exit_ok = true; | |
47 | exit(0); | |
48 | } | |
49 | } | |
50 | ||
51 | void mainey() { | |
52 | static int counter = 0; | |
53 | printf("mainey: %d\n", counter++); | |
54 | if (counter == 20) { | |
55 | emscripten_pause_main_loop(); | |
56 | emscripten_async_call(five, (void*)55, 1000); | |
57 | } else if (counter == 22) { // very soon after 20, so without pausing we fail | |
58 | assert(fived); | |
59 | emscripten_push_main_loop_blocker(pre1, (void*)123); | |
60 | emscripten_push_main_loop_blocker(pre2, (void*)98); | |
61 | } else if (counter == 23) { | |
62 | assert(pre1ed); | |
63 | assert(pre2ed); | |
64 | printf("Good!\n"); | |
65 | emscripten_cancel_main_loop(); | |
66 | emscripten_set_main_loop_arg(argey, (void*)17, 0, 0); | |
67 | } | |
68 | } | |
69 | ||
70 | void four(void *arg) { | |
71 | assert((int)arg == 43); | |
72 | printf("four!\n"); | |
73 | emscripten_set_main_loop(mainey, 0, 0); | |
74 | } | |
75 | ||
76 | void __attribute__((used)) third() { | |
77 | int now = SDL_GetTicks(); | |
78 | printf("thard! %d\n", now); | |
79 | assert(abs(now - last - 1000) < 500); | |
80 | emscripten_async_call(four, (void*)43, -1); // triggers requestAnimationFrame | |
81 | } | |
82 | ||
83 | void second(void *arg) { | |
84 | int now = SDL_GetTicks(); | |
85 | printf("sacond! %d\n", now); | |
86 | assert(abs(now - last - 500) < 250); | |
87 | last = now; | |
88 | emscripten_async_run_script("Module._third()", 1000); | |
89 | } | |
90 | ||
91 | // Should not be called when main return but only once the | |
92 | // main loops is stopped and the runtime shuts down. | |
93 | void check_exit_ok() { | |
94 | assert(exit_ok == true); | |
95 | } | |
96 | ||
97 | int main() { | |
98 | SDL_Init(0); | |
99 | last = SDL_GetTicks(); | |
100 | printf("frist! %d\n", last); | |
101 | ||
102 | double ratio = emscripten_get_device_pixel_ratio(); | |
103 | double ratio2 = EM_ASM_DOUBLE({ | |
104 | return window.devicePixelRatio || 1.0; | |
105 | }); | |
106 | ||
107 | assert(ratio == ratio2); | |
108 | ||
109 | atexit(check_exit_ok); | |
110 | ||
111 | emscripten_async_call(second, (void*)0, 500); | |
112 | ||
113 | return 1; | |
114 | } | |
115 |
0 | // Copyright 2012 The Emscripten Authors. All rights reserved. | |
1 | // Emscripten is available under two separate licenses, the MIT license and the | |
2 | // University of Illinois/NCSA Open Source License. Both these licenses can be | |
3 | // found in the LICENSE file. | |
4 | ||
5 | #include <stdio.h> | |
6 | #include <math.h> | |
7 | #include <stdlib.h> | |
8 | #include <SDL.h> | |
9 | #include <emscripten.h> | |
10 | #include <assert.h> | |
11 | ||
12 | int last = 0; | |
13 | ||
14 | extern "C" { | |
15 | ||
16 | bool pre1ed = false; | |
17 | bool pre2ed = false; | |
18 | void pre1(void *arg) { | |
19 | assert(!pre1ed); | |
20 | assert(!pre2ed); | |
21 | assert((int)arg == 123); | |
22 | pre1ed = true; | |
23 | } | |
24 | void pre2(void *arg) { | |
25 | assert(pre1ed); | |
26 | assert(!pre2ed); | |
27 | assert((int)arg == 98); | |
28 | pre2ed = true; | |
29 | } | |
30 | ||
31 | bool fived = false; | |
32 | void five(void *arg) { | |
33 | assert((int)arg == 55); | |
34 | fived = true; | |
35 | emscripten_resume_main_loop(); | |
36 | } | |
37 | ||
38 | void argey(void* arg) { | |
39 | static int counter = 0; | |
40 | assert((int)arg == 17); | |
41 | counter++; | |
42 | printf("argey: %d\n", counter); | |
43 | if (counter == 5) { | |
44 | emscripten_cancel_main_loop(); | |
45 | REPORT_RESULT(1); | |
46 | } | |
47 | } | |
48 | ||
49 | void mainey() { | |
50 | static int counter = 0; | |
51 | printf("mainey: %d\n", counter++); | |
52 | if (counter == 20) { | |
53 | emscripten_pause_main_loop(); | |
54 | emscripten_async_call(five, (void*)55, 1000); | |
55 | } else if (counter == 22) { // very soon after 20, so without pausing we fail | |
56 | assert(fived); | |
57 | emscripten_push_main_loop_blocker(pre1, (void*)123); | |
58 | emscripten_push_main_loop_blocker(pre2, (void*)98); | |
59 | } else if (counter == 23) { | |
60 | assert(pre1ed); | |
61 | assert(pre2ed); | |
62 | printf("Good!\n"); | |
63 | emscripten_cancel_main_loop(); | |
64 | emscripten_set_main_loop_arg(argey, (void*)17, 0, 0); | |
65 | } | |
66 | } | |
67 | ||
68 | void four(void *arg) { | |
69 | assert((int)arg == 43); | |
70 | printf("four!\n"); | |
71 | emscripten_set_main_loop(mainey, 0, 0); | |
72 | } | |
73 | ||
74 | void __attribute__((used)) third() { | |
75 | int now = SDL_GetTicks(); | |
76 | printf("thard! %d\n", now); | |
77 | assert(fabs(now - last - 1000) < 500); | |
78 | emscripten_async_call(four, (void*)43, -1); // triggers requestAnimationFrame | |
79 | } | |
80 | ||
81 | void second(void *arg) { | |
82 | int now = SDL_GetTicks(); | |
83 | printf("sacond! %d\n", now); | |
84 | assert(fabs(now - last - 500) < 250); | |
85 | last = now; | |
86 | emscripten_async_run_script("Module._third()", 1000); | |
87 | } | |
88 | ||
89 | } | |
90 | ||
91 | void never() { | |
92 | REPORT_RESULT(0); | |
93 | } | |
94 | ||
95 | int main() { | |
96 | SDL_Init(0); | |
97 | last = SDL_GetTicks(); | |
98 | printf("frist! %d\n", last); | |
99 | ||
100 | double ratio = emscripten_get_device_pixel_ratio(); | |
101 | double ratio2 = EM_ASM_DOUBLE({ | |
102 | return window.devicePixelRatio || 1.0; | |
103 | }); | |
104 | ||
105 | assert(ratio == ratio2); | |
106 | ||
107 | atexit(never); // should never be called - it is wrong to exit the runtime orderly if we have async calls! | |
108 | ||
109 | emscripten_async_call(second, (void*)0, 500); | |
110 | ||
111 | return 1; | |
112 | } | |
113 |
0 | // Copyright 2013 The Emscripten Authors. All rights reserved. | |
1 | // Emscripten is available under two separate licenses, the MIT license and the | |
2 | // University of Illinois/NCSA Open Source License. Both these licenses can be | |
3 | // found in the LICENSE file. | |
4 | ||
5 | #include <stdio.h> | |
6 | #include <stdlib.h> | |
7 | #include <string.h> | |
8 | #include <assert.h> | |
9 | ||
10 | #include <emscripten.h> | |
11 | ||
12 | int value = 0; | |
13 | ||
14 | void set(int x) { | |
15 | printf("set! %d\n", x); | |
16 | value = x; | |
17 | } | |
18 | ||
19 | void load2() { | |
20 | printf("load2\n"); | |
21 | ||
22 | char buffer[10]; | |
23 | memset(buffer, 0, 10); | |
24 | FILE *f = fopen("file1.txt", "r"); | |
25 | fread(buffer, 1, 5, f); | |
26 | fclose(f); | |
27 | assert(strcmp(buffer, "first") == 0); | |
28 | ||
29 | memset(buffer, 0, 10); | |
30 | f = fopen("file2.txt", "r"); | |
31 | fread(buffer, 1, 6, f); | |
32 | fclose(f); | |
33 | assert(strcmp(buffer, "second") == 0); | |
34 | ||
35 | exit(0); | |
36 | } | |
37 | ||
38 | void error2() { | |
39 | printf("fail2\n"); | |
40 | } | |
41 | ||
42 | void load1() { | |
43 | printf("load1\n"); | |
44 | assert(value == 456); | |
45 | emscripten_async_load_script("script2.js", load2, error2); | |
46 | } | |
47 | ||
48 | void error1() { | |
49 | printf("fail1\n"); | |
50 | } | |
51 | ||
52 | int main() { | |
53 | emscripten_async_load_script("script1.js", load1, error1); | |
54 | return 99; | |
55 | } |
0 | // Copyright 2013 The Emscripten Authors. All rights reserved. | |
1 | // Emscripten is available under two separate licenses, the MIT license and the | |
2 | // University of Illinois/NCSA Open Source License. Both these licenses can be | |
3 | // found in the LICENSE file. | |
4 | ||
5 | #include <stdio.h> | |
6 | #include <string.h> | |
7 | #include <assert.h> | |
8 | ||
9 | #include <emscripten.h> | |
10 | ||
11 | int value = 0; | |
12 | ||
13 | extern "C" { | |
14 | void set(int x) { | |
15 | printf("set! %d\n", x); | |
16 | value = x; | |
17 | } | |
18 | } | |
19 | ||
20 | void load2() { | |
21 | printf("load2\n"); | |
22 | ||
23 | char buffer[10]; | |
24 | memset(buffer, 0, 10); | |
25 | FILE *f = fopen("file1.txt", "r"); | |
26 | fread(buffer, 1, 5, f); | |
27 | fclose(f); | |
28 | assert(strcmp(buffer, "first") == 0); | |
29 | ||
30 | memset(buffer, 0, 10); | |
31 | f = fopen("file2.txt", "r"); | |
32 | fread(buffer, 1, 6, f); | |
33 | fclose(f); | |
34 | assert(strcmp(buffer, "second") == 0); | |
35 | ||
36 | REPORT_RESULT(1); | |
37 | } | |
38 | void error2() { | |
39 | printf("fail2\n"); | |
40 | } | |
41 | ||
42 | void load1() { | |
43 | printf("load1\n"); | |
44 | assert(value == 456); | |
45 | emscripten_async_load_script("script2.js", load2, error2); | |
46 | } | |
47 | void error1() { | |
48 | printf("fail1\n"); | |
49 | } | |
50 | ||
51 | int main() { | |
52 | emscripten_async_load_script("script1.js", load1, error1); | |
53 | ||
54 | return 1; | |
55 | } | |
56 |
3 | 3 | // found in the LICENSE file. |
4 | 4 | |
5 | 5 | #include <stdio.h> |
6 | #include <stdlib.h> | |
6 | 7 | #include <string.h> |
7 | 8 | #include <emscripten.h> |
8 | 9 | |
18 | 19 | printf("waka %d\n", x++); |
19 | 20 | |
20 | 21 | if (x == 7 || x < 0) { |
21 | REPORT_RESULT(x); | |
22 | 22 | emscripten_cancel_main_loop(); |
23 | exit(x); | |
23 | 24 | } |
24 | 25 | } |
25 | 26 |
0 | #include <assert.h> | |
0 | 1 | #include <stdio.h> |
1 | 2 | #include <emscripten/emscripten.h> |
2 | 3 | |
3 | int main() | |
4 | { | |
5 | double devicePixelRatio = emscripten_get_device_pixel_ratio(); | |
6 | printf("window.devicePixelRatio = %f.\n", devicePixelRatio); | |
7 | int result = (devicePixelRatio > 0) ? 1 : 0; | |
8 | if (result) { | |
9 | printf("Test succeeded!\n"); | |
10 | } | |
11 | #ifdef REPORT_RESULT | |
12 | REPORT_RESULT(result); | |
13 | #endif | |
4 | int main() { | |
5 | double devicePixelRatio = emscripten_get_device_pixel_ratio(); | |
6 | printf("window.devicePixelRatio = %f.\n", devicePixelRatio); | |
7 | assert(devicePixelRatio > 0); | |
8 | return 0; | |
14 | 9 | } |
12 | 12 | |
13 | 13 | void final(void*) { |
14 | 14 | assert(frame == 110); |
15 | #ifdef REPORT_RESULT | |
16 | 15 | printf("Test passed.\n"); |
17 | REPORT_RESULT(0); | |
18 | #endif | |
16 | exit(0); | |
19 | 17 | } |
20 | 18 | |
21 | 19 | void looper() { |
28 | 26 | timesTooSoon++; |
29 | 27 | if (timesTooSoon >= 10) { |
30 | 28 | printf("Abort: main loop tick was called too quickly after the previous frame, too many times!\n"); |
31 | #ifdef REPORT_RESULT | |
32 | REPORT_RESULT(1); | |
33 | #endif | |
34 | 29 | emscripten_cancel_main_loop(); |
35 | exit(0); | |
30 | exit(1); | |
36 | 31 | } |
37 | 32 | } |
38 | 33 | prevTime = curTime; |
41 | 36 | timesTooSoon++; |
42 | 37 | if (timesTooSoon >= 2) { |
43 | 38 | printf("Abort: With swap interval of 4, we should be running at most 15fps! (or 30fps on 120Hz displays) but seems like swap control is not working and we are running at 60fps!\n"); |
44 | #ifdef REPORT_RESULT | |
45 | REPORT_RESULT(2); | |
46 | #endif | |
47 | 39 | emscripten_cancel_main_loop(); |
48 | exit(0); | |
40 | exit(2); | |
49 | 41 | } |
50 | 42 | } |
51 | 43 | if (frame > 0 && frame < 90 && frame % 10 == 0) { |
78 | 70 | |
79 | 71 | int main() { |
80 | 72 | emscripten_set_main_loop(looper, 5, 1); |
73 | return 99; | |
81 | 74 | } |
13 | 13 | |
14 | 14 | void final(void*) { |
15 | 15 | assert(frame == 20); |
16 | #ifdef REPORT_RESULT | |
17 | REPORT_RESULT(0); | |
18 | #else | |
19 | 16 | exit(0); |
20 | #endif | |
21 | 17 | } |
22 | 18 | |
23 | 19 | void looper() { |
24 | 20 | if (blockerExecuted == false) { |
25 | #ifdef REPORT_RESULT | |
26 | REPORT_RESULT(1); | |
27 | #else | |
28 | 21 | exit(1); |
29 | #endif | |
30 | 22 | } |
31 | 23 | |
32 | 24 | frame++; |
2 | 2 | // University of Illinois/NCSA Open Source License. Both these licenses can be |
3 | 3 | // found in the LICENSE file. |
4 | 4 | |
5 | #include <stdbool.h> | |
5 | 6 | #include <stdlib.h> |
6 | 7 | #include <stdio.h> |
7 | 8 | #include <assert.h> |
20 | 21 | double now = emscripten_get_now(); |
21 | 22 | double msecsPerFrame = (now - frame0) / (numFrames-1); // Sub one to account for intervals vs endpoints |
22 | 23 | printf("Avg. msecs/frame: %f\n", msecsPerFrame); |
23 | #ifdef REPORT_RESULT | |
24 | int result = (msecsPerFrame < 5); // Expecting to run extremely fast unthrottled, and certainly not bounded by vsync, so less than common 16.667 msecs per frame (this is assuming 60hz display) | |
25 | REPORT_RESULT(result); | |
26 | #endif | |
27 | 24 | emscripten_cancel_main_loop(); |
25 | // Expecting to run extremely fast unthrottled, and certainly not bounded by | |
26 | // vsync, so less than common 16.667 msecs per frame (this is assuming 60hz | |
27 | // display) | |
28 | exit((msecsPerFrame < 5) ? 0 : 1); | |
28 | 29 | } |
29 | 30 | } |
30 | 31 | |
31 | 32 | int main() { |
32 | 33 | emscripten_set_main_loop(looper, 1, 0); |
33 | 34 | emscripten_set_main_loop_timing(EM_TIMING_SETIMMEDIATE, 0); |
35 | return 99; | |
34 | 36 | } |
18 | 18 | printf("Frame %d\n", numFrames); |
19 | 19 | if (numFrames == 10) { |
20 | 20 | double now = emscripten_get_now(); |
21 | double msecsPerFrame = (now - frame0) / (numFrames-1); // Sub one to account for intervals vs endpoints | |
21 | // Sub one to account for intervals vs endpoints | |
22 | double msecsPerFrame = (now - frame0) / (numFrames-1); | |
22 | 23 | printf("Avg. msecs/frame: %f\n", msecsPerFrame); |
23 | #ifdef REPORT_RESULT | |
24 | int result = (msecsPerFrame > 350 && msecsPerFrame < 650); // Expecting 500msecs/frame, but allow a lot of leeway. Bad value would be 900msecs/frame (400msecs of processing below and 500msecs of delay) | |
25 | REPORT_RESULT(result); | |
26 | #endif | |
27 | 24 | emscripten_cancel_main_loop(); |
25 | // Expecting 500msecs/frame, but allow a lot of leeway. Bad value would be | |
26 | // 900msecs/frame (400msecs of processing below and 500msecs of delay) | |
27 | exit((msecsPerFrame > 350 && msecsPerFrame < 650) ? 0 : 1); | |
28 | 28 | } |
29 | 29 | |
30 | 30 | // Busy wait 400 msecs. |
37 | 37 | int main() { |
38 | 38 | // Want to run at 2 fps, or 500msecs/frame. |
39 | 39 | emscripten_set_main_loop(looper, 2, 0); |
40 | return 99; | |
40 | 41 | } |
48 | 48 | assert(h == 202); |
49 | 49 | w = h = 0; |
50 | 50 | |
51 | #ifdef REPORT_RESULT | |
52 | REPORT_RESULT(1); | |
53 | #endif | |
51 | return 0; | |
54 | 52 | } |
21 | 21 | assert(fetch->data != 0); |
22 | 22 | printf("Finished downloading %llu bytes\n", fetch->numBytes); |
23 | 23 | emscripten_fetch_close(fetch); |
24 | ||
25 | #ifdef REPORT_RESULT | |
26 | // Fetch API appears to sometimes call the handlers more than once, see https://github.com/emscripten-core/emscripten/pull/8191 | |
27 | MAYBE_REPORT_RESULT(1); | |
28 | #endif | |
29 | ||
24 | exit(0); | |
30 | 25 | }; |
31 | 26 | attr.onprogress = [](emscripten_fetch_t *fetch) { |
32 | 27 | if (fetch->totalBytes > 0) { |
65 | 60 | emscripten_fetch_t *fetch = emscripten_fetch(&attr, "gears.png"); |
66 | 61 | assert(fetch != 0); |
67 | 62 | memset(&attr, 0, sizeof(attr)); // emscripten_fetch() must be able to operate without referencing to this structure after the call. |
63 | return 99; | |
68 | 64 | } |
2 | 2 | // University of Illinois/NCSA Open Source License. Both these licenses can be |
3 | 3 | // found in the LICENSE file. |
4 | 4 | |
5 | #include <assert.h> | |
5 | 6 | #include <string.h> |
6 | 7 | #include <stdio.h> |
7 | 8 | #include <math.h> |
15 | 16 | attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_SYNCHRONOUS; |
16 | 17 | emscripten_fetch_t *fetch = emscripten_fetch(&attr, "gears.png"); |
17 | 18 | printf("Fetch finished with status %d\n", fetch->status); |
18 | #ifdef REPORT_RESULT | |
19 | REPORT_RESULT(fetch->status); | |
20 | #endif | |
19 | assert(fetch->status == 200); | |
20 | return 0; | |
21 | 21 | } |
8 | 8 | #include <assert.h> |
9 | 9 | #include <emscripten/fetch.h> |
10 | 10 | |
11 | int result = 0; | |
11 | int result = 1; | |
12 | 12 | |
13 | 13 | int main() |
14 | 14 | { |
46 | 46 | assert( checksum == 0x08 ); |
47 | 47 | emscripten_fetch_close( fetch ); |
48 | 48 | |
49 | if ( result == 0 ) result = 1; | |
50 | #ifdef REPORT_RESULT | |
51 | // Fetch API appears to sometimes call the handlers more than once, see https://github.com/emscripten-core/emscripten/pull/8191 | |
52 | MAYBE_REPORT_RESULT(result); | |
53 | #endif | |
49 | if ( result == 1 ) result = 0; | |
54 | 50 | }; |
55 | 51 | |
56 | 52 | attr.onprogress = [] ( emscripten_fetch_t *fetch ) |
73 | 69 | }; |
74 | 70 | |
75 | 71 | emscripten_fetch_t *fetch = emscripten_fetch( &attr, "gears.png" ); |
76 | if ( result == 0 ) | |
72 | if ( result != 0 ) | |
77 | 73 | { |
78 | 74 | result = 2; |
79 | 75 | printf( "emscripten_fetch() failed to run synchronously!\n" ); |
80 | 76 | } |
81 | #ifdef REPORT_RESULT | |
82 | // Fetch API appears to sometimes call the handlers more than once, see https://github.com/emscripten-core/emscripten/pull/8191 | |
83 | MAYBE_REPORT_RESULT(result); | |
84 | #endif | |
77 | return result; | |
85 | 78 | } |
10 | 10 | |
11 | 11 | // Compute rudimentary checksum of data |
12 | 12 | uint32_t checksum = 0; |
13 | ||
14 | int result = 0; | |
15 | 13 | |
16 | 14 | int main() |
17 | 15 | { |
27 | 25 | assert(checksum == 0xA7F8E858U); |
28 | 26 | emscripten_fetch_close(fetch); |
29 | 27 | |
30 | #ifdef REPORT_RESULT | |
31 | // Fetch API appears to sometimes call the handlers more than once, see https://github.com/emscripten-core/emscripten/pull/8191 | |
32 | MAYBE_REPORT_RESULT(1); | |
33 | #endif | |
28 | exit(0); | |
34 | 29 | }; |
35 | 30 | attr.onprogress = [](emscripten_fetch_t *fetch) { |
36 | 31 | printf("Downloading.. %.2f%s complete. Received chunk [%llu, %llu[\n", |
48 | 43 | }; |
49 | 44 | attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_APPEND | EMSCRIPTEN_FETCH_STREAM_DATA; |
50 | 45 | emscripten_fetch_t *fetch = emscripten_fetch(&attr, "largefile.txt"); |
46 | return 99; | |
51 | 47 | } |
54 | 54 | if (result == -1) { |
55 | 55 | printf("emscripten_fetch() failed to run synchronously!\n"); |
56 | 56 | } |
57 | #ifndef __EMSCRIPTEN_PTHREADS__ | |
58 | // For proxy-to-worker mode (the only case where we can do sync xhr in main()) | |
59 | // Just use REPORT_RESULT | |
60 | REPORT_RESULT(result); | |
61 | #endif | |
62 | // Otherwise test that the exit status gets returned correctly. | |
57 | assert(result == 0); | |
63 | 58 | return result; |
64 | 59 | } |
25 | 25 | assert((uintptr_t)fetch->userData == 0x12345678); |
26 | 26 | assert(fetch->totalBytes == 6407); |
27 | 27 | emscripten_fetch_close(fetch); |
28 | ||
29 | #ifdef REPORT_RESULT | |
30 | // Fetch API appears to sometimes call the handlers more than once, see https://github.com/emscripten-core/emscripten/pull/8191 | |
31 | MAYBE_REPORT_RESULT(1); | |
32 | #endif | |
28 | exit(0); | |
33 | 29 | }; |
34 | 30 | |
35 | 31 | attr.onprogress = [](emscripten_fetch_t *fetch) { |
49 | 45 | emscripten_fetch_t *fetch = emscripten_fetch(&attr, "gears.png"); |
50 | 46 | assert(fetch != 0); |
51 | 47 | memset(&attr, 0, sizeof(attr)); // emscripten_fetch() must be able to operate without referencing to this structure after the call. |
48 | return 99; | |
52 | 49 | } |
8 | 8 | #include <assert.h> |
9 | 9 | #include <emscripten/fetch.h> |
10 | 10 | |
11 | int result = 0; | |
11 | int result = 1; | |
12 | 12 | |
13 | 13 | // This test is run in two modes: if FILE_DOES_NOT_EXIST defined, |
14 | 14 | // then testing an XHR of a missing file. |
43 | 43 | assert(checksum == 0x08); |
44 | 44 | emscripten_fetch_close(fetch); |
45 | 45 | |
46 | #ifdef REPORT_RESULT | |
47 | 46 | #ifndef FILE_DOES_NOT_EXIST |
48 | result = 1; | |
47 | result = 0; | |
49 | 48 | #endif |
50 | // Fetch API appears to sometimes call the handlers more than once, see https://github.com/emscripten-core/emscripten/pull/8191 | |
51 | MAYBE_REPORT_RESULT(result); | |
52 | #endif | |
49 | exit(result); | |
53 | 50 | }; |
54 | 51 | |
55 | 52 | attr.onprogress = [](emscripten_fetch_t *fetch) { |
62 | 59 | printf("Downloading.. %lld bytes complete.\n", fetch->dataOffset + fetch->numBytes); |
63 | 60 | } |
64 | 61 | #ifdef FILE_DOES_NOT_EXIST |
65 | assert(false && "onprogress handler called, but the file should not exist"); // We should not receive progress reports if the file doesn't exist. | |
62 | // We should not receive progress reports if the file doesn't exist. | |
63 | assert(false && "onprogress handler called, but the file should not exist"); | |
66 | 64 | #endif |
67 | 65 | // We must receive a call to the onprogress handler with 100% completion. |
68 | 66 | if (fetch->dataOffset + fetch->numBytes == fetch->totalBytes) ++result; |
76 | 74 | attr.onerror = [](emscripten_fetch_t *fetch) { |
77 | 75 | printf("Download failed!\n"); |
78 | 76 | #ifndef FILE_DOES_NOT_EXIST |
79 | assert(false && "onerror handler called, but the transfer should have succeeded!"); // The file exists, shouldn't reach here. | |
77 | // The file exists, shouldn't reach here. | |
78 | assert(false && "onerror handler called, but the transfer should have succeeded!"); | |
80 | 79 | #endif |
81 | 80 | assert(fetch); |
82 | 81 | assert(fetch->id != 0); |
83 | 82 | assert(!strcmp(fetch->url, "gears.png")); |
84 | 83 | assert((uintptr_t)fetch->userData == 0x12345678); |
85 | 84 | |
86 | #ifdef REPORT_RESULT | |
87 | 85 | #ifdef FILE_DOES_NOT_EXIST |
88 | result = 1; | |
86 | result = 0; | |
89 | 87 | #endif |
90 | // Fetch API appears to sometimes call the handlers more than once, see https://github.com/emscripten-core/emscripten/pull/8191 | |
91 | MAYBE_REPORT_RESULT(result); | |
92 | #endif | |
88 | exit(result); | |
93 | 89 | }; |
94 | 90 | |
95 | 91 | emscripten_fetch_t *fetch = emscripten_fetch(&attr, "gears.png"); |
96 | 92 | assert(fetch != 0); |
97 | memset(&attr, 0, sizeof(attr)); // emscripten_fetch() must be able to operate without referencing to this structure after the call. | |
93 | // emscripten_fetch() must be able to operate without referencing to this | |
94 | // structure after the call. | |
95 | memset(&attr, 0, sizeof(attr)); | |
96 | return 99; | |
98 | 97 | } |
0 | 0 | #include <stdio.h> |
1 | 1 | #include <stdlib.h> |
2 | 2 | |
3 | int _Zxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(int i1) | |
4 | { | |
5 | if (i1 > 0) | |
6 | return _Zxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(--i1); | |
7 | for(;;) | |
8 | malloc(1); | |
9 | return 1; | |
3 | int _Zxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(int i1) { | |
4 | if (i1 > 0) | |
5 | return _Zxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(--i1); | |
6 | for(;;) | |
7 | malloc(1); | |
8 | return 1; | |
10 | 9 | } |
11 | 10 | |
12 | int main() | |
13 | { | |
14 | _Zxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(100); | |
11 | int main() { | |
12 | _Zxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(100); | |
15 | 13 | } |
5 | 5 | */ |
6 | 6 | |
7 | 7 | #include <errno.h> |
8 | #include <string.h> | |
8 | 9 | #include <fcntl.h> |
9 | 10 | #include <stdlib.h> |
10 | 11 | #include <stdio.h> |
15 | 16 | |
16 | 17 | int line = 0; |
17 | 18 | |
18 | void main_loop() | |
19 | { | |
19 | void main_loop() { | |
20 | 20 | char str[10] = {0}; |
21 | 21 | int ret; |
22 | 22 | |
32 | 32 | } |
33 | 33 | |
34 | 34 | int err = ferror(stdin); |
35 | if (ferror(stdin) && errno != EAGAIN) { | |
36 | printf("error %d\n", err); | |
35 | if (err && errno != EAGAIN) { | |
36 | printf("error %s\n", strerror(errno)); | |
37 | 37 | exit(EXIT_FAILURE); |
38 | 38 | } |
39 | 39 | |
46 | 46 | } |
47 | 47 | } |
48 | 48 | |
49 | int main(int argc, char const *argv[]) | |
50 | { | |
49 | int main(int argc, char const *argv[]) { | |
51 | 50 | fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); |
52 | 51 | |
53 | 52 | // SM shell doesn't implement an event loop and therefor doesn't support |
0 | $GetQueue | |
1 | $__emscripten_pthread_data_constructor | |
2 | $__errno_location | |
3 | $__pthread_mutex_lock | |
4 | $__pthread_mutex_trylock | |
5 | $__pthread_mutex_unlock | |
6 | $__pthread_self_internal | |
7 | $__pthread_setcancelstate | |
8 | $__pthread_tsd_run_dtors | |
9 | $__wasm_call_ctors | |
10 | $__wasm_init_memory | |
11 | $_do_call | |
12 | $_emscripten_call_on_thread | |
13 | $_emscripten_do_dispatch_to_thread | |
14 | $_emscripten_thread_init | |
15 | $_main_thread | |
16 | $a_cas | |
17 | $add | |
18 | $dispose_chunk | |
19 | $dlfree | |
20 | $dlmalloc | |
21 | $dlmemalign | |
22 | $em_queued_call_free | |
23 | $em_queued_call_malloc | |
24 | $emscripten_async_run_in_main_thread | |
25 | $emscripten_current_thread_process_queued_calls | |
26 | $emscripten_get_global_libc | |
27 | $emscripten_main_thread_process_queued_calls | |
28 | $emscripten_register_main_browser_thread_id | |
29 | $emscripten_run_in_main_runtime_thread_js | |
30 | $emscripten_stack_set_limits | |
31 | $emscripten_sync_run_in_main_thread | |
32 | $emscripten_sync_run_in_main_thread | |
33 | $emscripten_tls_init | |
34 | $free_tls | |
35 | $init_mparams | |
36 | $sbrk | |
37 | $stackAlloc | |
38 | $stackRestore | |
39 | $stackSave |
0 | a.a | |
1 | a.b | |
2 | a.c | |
3 | a.d | |
4 | a.e | |
5 | a.f | |
6 | a.g | |
7 | a.h | |
8 | a.i | |
9 | a.j | |
10 | a.k | |
11 | a.l | |
12 | a.m | |
13 | a.n | |
14 | a.o | |
15 | a.p | |
16 | a.q | |
17 | a.r |
0 | #include <emscripten.h> | |
1 | ||
2 | EMSCRIPTEN_KEEPALIVE int add(int x, int y) { | |
3 | return x + y; | |
4 | } | |
5 | ||
6 | EMSCRIPTEN_KEEPALIVE int global_val = 0; | |
7 | ||
8 | int main() { | |
9 | } |
0 | A | |
1 | B | |
2 | C | |
3 | D | |
4 | E | |
5 | F | |
6 | G | |
7 | H | |
8 | I | |
9 | J | |
10 | K | |
11 | L | |
12 | M | |
13 | N | |
14 | O | |
15 | P | |
16 | s | |
17 | t | |
18 | u | |
19 | v | |
20 | w | |
21 | x | |
22 | y | |
23 | z |
0 | $GetQueue | |
1 | $__emscripten_pthread_data_constructor | |
2 | $__errno_location | |
3 | $__pthread_mutex_lock | |
4 | $__pthread_mutex_trylock | |
5 | $__pthread_mutex_unlock | |
6 | $__pthread_self_internal | |
7 | $__pthread_setcancelstate | |
8 | $__pthread_tsd_run_dtors | |
9 | $__wasm_call_ctors | |
10 | $__wasm_init_memory | |
11 | $_do_call | |
12 | $_emscripten_call_on_thread | |
13 | $_emscripten_do_dispatch_to_thread | |
14 | $_emscripten_thread_init | |
15 | $_main_thread | |
16 | $a_cas | |
17 | $add | |
18 | $dispose_chunk | |
19 | $dlfree | |
20 | $dlmalloc | |
21 | $dlmemalign | |
22 | $em_queued_call_free | |
23 | $em_queued_call_malloc | |
24 | $emscripten_async_run_in_main_thread | |
25 | $emscripten_current_thread_process_queued_calls | |
26 | $emscripten_get_global_libc | |
27 | $emscripten_main_thread_process_queued_calls | |
28 | $emscripten_proxy_main | |
29 | $emscripten_register_main_browser_thread_id | |
30 | $emscripten_run_in_main_runtime_thread_js | |
31 | $emscripten_stack_set_limits | |
32 | $emscripten_sync_run_in_main_thread | |
33 | $emscripten_sync_run_in_main_thread | |
34 | $emscripten_tls_init | |
35 | $free_tls | |
36 | $init_mparams | |
37 | $main | |
38 | $sbrk | |
39 | $stackAlloc | |
40 | $stackRestore | |
41 | $stackSave |
0 | a.a | |
1 | a.b | |
2 | a.c | |
3 | a.d | |
4 | a.e | |
5 | a.f | |
6 | a.g | |
7 | a.h | |
8 | a.i | |
9 | a.j | |
10 | a.k | |
11 | a.l | |
12 | a.m | |
13 | a.n | |
14 | a.o | |
15 | a.p | |
16 | a.q | |
17 | a.r |
0 | #include <assert.h> | |
1 | #include <stdlib.h> | |
2 | #include <dlfcn.h> | |
3 | #include <stdio.h> | |
4 | ||
5 | int main() { | |
6 | void* handle = dlopen("libside.so", RTLD_NOW); | |
7 | printf("handle: %p\n", handle); | |
8 | if (!handle) { | |
9 | printf("%s\n", dlerror()); | |
10 | return 1; | |
11 | } | |
12 | int* foo = (int*)dlsym(handle, "foo"); | |
13 | if (!foo) { | |
14 | printf("%s\n", dlerror()); | |
15 | return 1; | |
16 | } | |
17 | printf("foo = %d\n", *foo); | |
18 | assert(*foo == 42); | |
19 | exit(0); | |
20 | } |
0 | foo = 42 |
0 | Module["loadSplitModule"] = function(deferred, imports, prop) { | |
1 | console.log('Custom handler for loading split module.'); | |
2 | console.log('Called with placeholder ', prop); | |
3 | ||
4 | return instantiateSync(deferred, imports); | |
5 | } |
189 | 189 | TEST_START(); |
190 | 190 | errno = 0; |
191 | 191 | |
192 | ASSERT(munmap(MAP_FAILED, file_len()) == -1 && errno == EINVAL, | |
192 | ASSERT(munmap((void*)0xdeadbeef, file_len()) == -1 && errno == EINVAL, | |
193 | 193 | "Expected EINVAL, as munmap should fail for wrong addr argument"); |
194 | 194 | TEST_PASS(); |
195 | 195 | } |
3 | 3 | # found in the LICENSE file. |
4 | 4 | |
5 | 5 | import multiprocessing |
6 | import os | |
7 | 6 | import sys |
8 | 7 | import unittest |
9 | 8 | import tempfile |
11 | 10 | import queue |
12 | 11 | |
13 | 12 | from tools.tempfiles import try_delete |
13 | ||
14 | NUM_CORES = None | |
14 | 15 | |
15 | 16 | |
16 | 17 | def g_testing_thread(work_queue, result_queue, temp_dir): |
39 | 40 | self.max_cores = max_cores |
40 | 41 | |
41 | 42 | def run(self, result): |
43 | # The 'spawn' method is used on windows and it can be useful to set this on | |
44 | # all platforms when debugging multiprocessing issues. Without this we | |
45 | # default to 'fork' on unix which is better because global state is | |
46 | # inherited by the child process, but can lead to hard-to-debug windows-only | |
47 | # issues. | |
48 | # multiprocessing.set_start_method('spawn') | |
42 | 49 | test_queue = self.create_test_queue() |
43 | 50 | self.init_processes(test_queue) |
44 | 51 | results = self.collect_results() |
136 | 143 | print(test, '... ok (%.2fs)' % (time.perf_counter() - self.start_time), file=sys.stderr) |
137 | 144 | self.buffered_result = BufferedTestSuccess(test) |
138 | 145 | |
146 | def addExpectedFailure(self, test, err): | |
147 | if hasattr(time, 'perf_counter'): | |
148 | print(test, '... expected failure (%.2fs)' % (time.perf_counter() - self.start_time), file=sys.stderr) | |
149 | self.buffered_result = BufferedTestExpectedFailure(test, err) | |
150 | ||
151 | def addUnexpectedSuccess(self, test): | |
152 | if hasattr(time, 'perf_counter'): | |
153 | print(test, '... unexpected success (%.2fs)' % (time.perf_counter() - self.start_time), file=sys.stderr) | |
154 | self.buffered_result = BufferedTestUnexpectedSuccess(test) | |
155 | ||
139 | 156 | def addSkip(self, test, reason): |
140 | 157 | print(test, "... skipped '%s'" % reason, file=sys.stderr) |
141 | 158 | self.buffered_result = BufferedTestSkip(test, reason) |
180 | 197 | result.addFailure(self.test, self.error) |
181 | 198 | |
182 | 199 | |
200 | class BufferedTestExpectedFailure(BufferedTestBase): | |
201 | def updateResult(self, result): | |
202 | result.addExpectedFailure(self.test, self.error) | |
203 | ||
204 | ||
183 | 205 | class BufferedTestError(BufferedTestBase): |
184 | 206 | def updateResult(self, result): |
185 | 207 | result.addError(self.test, self.error) |
208 | ||
209 | ||
210 | class BufferedTestUnexpectedSuccess(BufferedTestBase): | |
211 | def updateResult(self, result): | |
212 | result.addUnexpectedSuccess(self.test) | |
186 | 213 | |
187 | 214 | |
188 | 215 | class FakeTraceback(): |
217 | 244 | |
218 | 245 | |
219 | 246 | def num_cores(): |
220 | emcc_cores = os.environ.get('PARALLEL_SUITE_EMCC_CORES') or os.environ.get('EMCC_CORES') | |
221 | if emcc_cores: | |
222 | return int(emcc_cores) | |
247 | if NUM_CORES: | |
248 | return int(NUM_CORES) | |
223 | 249 | return multiprocessing.cpu_count() |
224 | 250 | |
225 | 251 |
15 | 15 | // Stores/encodes the results of calling to cleanup handlers. |
16 | 16 | int cleanupState = 1; |
17 | 17 | |
18 | static void cleanup_handler1(void *arg) | |
19 | { | |
20 | cleanupState <<= 2; | |
21 | cleanupState *= (int)arg; // Perform non-commutative arithmetic to a global var that encodes the cleanup stack order ops. | |
22 | EM_ASM(console.log('Called clean-up handler 1 with arg ' + $0), arg); | |
23 | // printf("Called clean-up handler 1 with arg %d\n", (int)arg); | |
18 | static void cleanup_handler1(void *arg) { | |
19 | cleanupState <<= 2; | |
20 | // Perform non-commutative arithmetic to a global var that encodes the cleanup stack order ops. | |
21 | cleanupState *= (int)arg; | |
22 | EM_ASM(console.log('Called clean-up handler 1 with arg ' + $0), arg); | |
23 | //printf("Called clean-up handler 1 with arg %d\n", (int)arg); | |
24 | 24 | } |
25 | 25 | |
26 | static void cleanup_handler2(void *arg) | |
27 | { | |
28 | cleanupState <<= 3; | |
29 | cleanupState *= (int)arg; // Perform non-commutative arithmetic to a global var that encodes the cleanup stack order ops. | |
30 | EM_ASM(console.log('Called clean-up handler 2 with arg ' + $0), arg); | |
31 | // printf("Called clean-up handler 2 with arg %d\n", (int)arg); | |
26 | static void cleanup_handler2(void *arg) { | |
27 | cleanupState <<= 3; | |
28 | // Perform non-commutative arithmetic to a global var that encodes the cleanup stack order ops. | |
29 | cleanupState *= (int)arg; | |
30 | EM_ASM(console.log('Called clean-up handler 2 with arg ' + $0), arg); | |
31 | //printf("Called clean-up handler 2 with arg %d\n", (int)arg); | |
32 | 32 | } |
33 | 33 | |
34 | static void *thread_start1(void *arg) | |
35 | { | |
36 | pthread_cleanup_push(cleanup_handler1, (void*)(42 + (int)arg*100)); | |
37 | pthread_cleanup_push(cleanup_handler2, (void*)(69 + (int)arg*100)); | |
38 | pthread_cleanup_pop((int)arg); | |
39 | pthread_cleanup_pop((int)arg); | |
40 | pthread_exit(0); | |
34 | static void *thread_start1(void *arg) { | |
35 | pthread_cleanup_push(cleanup_handler1, (void*)(42 + (int)arg*100)); | |
36 | pthread_cleanup_push(cleanup_handler2, (void*)(69 + (int)arg*100)); | |
37 | pthread_cleanup_pop((int)arg); | |
38 | pthread_cleanup_pop((int)arg); | |
39 | pthread_exit(0); | |
41 | 40 | } |
42 | 41 | |
43 | static void *thread_start2(void *arg) | |
44 | { | |
45 | pthread_cleanup_push(cleanup_handler1, (void*)52); | |
46 | pthread_cleanup_push(cleanup_handler2, (void*)79); | |
47 | if (arg) | |
48 | pthread_exit(0); | |
49 | pthread_cleanup_pop(0); | |
50 | pthread_cleanup_pop(0); | |
51 | return 0; | |
42 | static void *thread_start2(void *arg) { | |
43 | pthread_cleanup_push(cleanup_handler1, (void*)52); | |
44 | pthread_cleanup_push(cleanup_handler2, (void*)79); | |
45 | if (arg) | |
46 | pthread_exit(0); | |
47 | pthread_cleanup_pop(0); | |
48 | pthread_cleanup_pop(0); | |
49 | return 0; | |
52 | 50 | } |
53 | 51 | |
54 | static void *thread_start3(void *arg) | |
55 | { | |
56 | pthread_cleanup_push(cleanup_handler1, (void*)62); | |
57 | pthread_cleanup_push(cleanup_handler2, (void*)89); | |
58 | for(;;) | |
59 | { | |
60 | pthread_testcancel(); | |
61 | } | |
62 | pthread_cleanup_pop(0); | |
63 | pthread_cleanup_pop(0); | |
64 | pthread_exit(0); | |
52 | static void *thread_start3(void *arg) { | |
53 | pthread_cleanup_push(cleanup_handler1, (void*)62); | |
54 | pthread_cleanup_push(cleanup_handler2, (void*)89); | |
55 | for (;;) { | |
56 | pthread_testcancel(); | |
57 | } | |
58 | pthread_cleanup_pop(0); | |
59 | pthread_cleanup_pop(0); | |
60 | pthread_exit(0); | |
65 | 61 | } |
66 | 62 | |
67 | 63 | pthread_t thr[4]; |
68 | 64 | |
69 | int main() | |
70 | { | |
71 | int result = 0; | |
65 | int main() { | |
66 | int result = 0; | |
72 | 67 | |
73 | if (!emscripten_has_threading_support()) | |
74 | { | |
75 | #ifdef REPORT_RESULT | |
76 | REPORT_RESULT(907640832); | |
77 | #endif | |
78 | printf("Skipped: Threading is not supported.\n"); | |
79 | return 0; | |
80 | } | |
68 | if (!emscripten_has_threading_support()) { | |
69 | printf("Skipped: Threading is not supported.\n"); | |
70 | return 0; | |
71 | } | |
81 | 72 | |
82 | pthread_cleanup_push(cleanup_handler1, (void*)9998); | |
83 | pthread_cleanup_push(cleanup_handler1, (void*)9999); | |
73 | pthread_cleanup_push(cleanup_handler1, (void*)9998); | |
74 | pthread_cleanup_push(cleanup_handler1, (void*)9999); | |
84 | 75 | |
85 | int s = pthread_create(&thr[0], NULL, thread_start1, (void*)0); | |
86 | assert(s == 0); | |
87 | pthread_join(thr[0], 0); | |
88 | s = pthread_create(&thr[1], NULL, thread_start1, (void*)1); | |
89 | assert(s == 0); | |
90 | pthread_join(thr[1], 0); | |
91 | s = pthread_create(&thr[2], NULL, thread_start2, (void*)1); | |
92 | assert(s == 0); | |
93 | pthread_join(thr[2], 0); | |
76 | int s = pthread_create(&thr[0], NULL, thread_start1, (void*)0); | |
77 | assert(s == 0); | |
78 | pthread_join(thr[0], 0); | |
79 | s = pthread_create(&thr[1], NULL, thread_start1, (void*)1); | |
80 | assert(s == 0); | |
81 | pthread_join(thr[1], 0); | |
82 | s = pthread_create(&thr[2], NULL, thread_start2, (void*)1); | |
83 | assert(s == 0); | |
84 | pthread_join(thr[2], 0); | |
94 | 85 | // TODO |
95 | 86 | // s = pthread_create(&thr[3], NULL, thread_start3, (void*)1); |
96 | 87 | // assert(s == 0); |
97 | 88 | // s = pthread_cancel(thr[3]); |
98 | 89 | // assert(s == 0); |
99 | pthread_cleanup_pop(1); | |
100 | EM_ASM(console.log('Cleanup state variable: ' + $0), cleanupState); | |
90 | pthread_cleanup_pop(1); | |
91 | printf("Cleanup state variable: %d", cleanupState); | |
92 | assert(cleanupState == 907640832); | |
101 | 93 | |
102 | #ifdef REPORT_RESULT | |
103 | REPORT_RESULT(cleanupState); | |
104 | #endif | |
105 | ||
106 | exit(EXIT_SUCCESS); | |
94 | pthread_cleanup_pop(1); | |
95 | exit(EXIT_SUCCESS); | |
107 | 96 | } |
108 | 97 | |
109 | 98 | /* |
0 | Cleanup state variable: 907640832 |
4 | 4 | * found in the LICENSE file. |
5 | 5 | */ |
6 | 6 | |
7 | #include <assert.h> | |
7 | 8 | #include <pthread.h> |
8 | 9 | #include <stdio.h> |
9 | 10 | #include <stdlib.h> |
10 | // #include <emscripten/emscripten.h> | |
11 | 11 | #include <emscripten/threading.h> |
12 | 12 | #include "../../system/lib/libc/musl/src/internal/pthread_impl.h" |
13 | 13 | |
25 | 25 | return loc; |
26 | 26 | } |
27 | 27 | |
28 | void *thread_test(void *t) | |
29 | { | |
28 | void *thread_test(void *t) { | |
30 | 29 | puts("Doing test in child thread"); |
31 | 30 | pthread_exit((void*)do_test()); |
32 | 31 | } |
33 | 32 | |
34 | int main (int argc, char *argv[]) | |
35 | { | |
33 | int main (int argc, char *argv[]) { | |
36 | 34 | puts("Doing test in main thread"); |
37 | 35 | locale_t main_loc = do_test(); |
38 | 36 | locale_t child_loc; |
39 | 37 | |
40 | if (!emscripten_has_threading_support()) | |
41 | { | |
38 | if (!emscripten_has_threading_support()) { | |
42 | 39 | child_loc = main_loc; |
43 | } | |
44 | else | |
45 | { | |
40 | } else { | |
46 | 41 | long id = 1; |
47 | 42 | pthread_t thread; |
48 | 43 | |
51 | 46 | pthread_join(thread, (void**)&child_loc); |
52 | 47 | } |
53 | 48 | |
54 | #ifdef REPORT_RESULT | |
55 | int result = (main_loc == child_loc); | |
56 | REPORT_RESULT(result); | |
57 | #endif | |
58 | ||
59 | pthread_exit(NULL); | |
49 | assert(main_loc == child_loc); | |
50 | return 0; | |
60 | 51 | } |
0 | #include <assert.h> | |
0 | 1 | #include <stdlib.h> |
1 | 2 | #include <stdio.h> |
2 | 3 | #include <assert.h> |
4 | 5 | #include <emscripten/emscripten.h> |
5 | 6 | #include <emscripten/threading.h> |
6 | 7 | |
7 | extern "C" | |
8 | { | |
9 | void EMSCRIPTEN_KEEPALIVE FinishTest(int result) | |
8 | extern "C" void EMSCRIPTEN_KEEPALIVE FinishTest(int result) | |
10 | 9 | { |
11 | 10 | printf("Test finished, result: %d\n", result); |
12 | #ifdef REPORT_RESULT | |
13 | REPORT_RESULT(result); | |
14 | #endif | |
15 | } | |
11 | assert(result == 1); | |
12 | exit(0); | |
16 | 13 | } |
17 | 14 | |
18 | 15 | void TestAsyncRunScript() |
39 | 36 | |
40 | 37 | int main() { |
41 | 38 | |
42 | // 1. Test that emscripten_run_script() works in a pthread, and it gets executed in the web worker and not on the main thread. | |
39 | // 1. Test that emscripten_run_script() works in a pthread, and it gets | |
40 | // executed in the web worker and not on the main thread. | |
43 | 41 | #if __EMSCRIPTEN_PTHREADS__ |
44 | 42 | emscripten_run_script("Module['ranScript'] = ENVIRONMENT_IS_PTHREAD && (typeof ENVIRONMENT_IS_WORKER !== 'undefined' && ENVIRONMENT_IS_WORKER);"); |
45 | 43 | #else |
46 | 44 | emscripten_run_script("Module['ranScript'] = true;"); |
47 | 45 | #endif |
48 | 46 | |
49 | // 2. Test that emscripten_run_script_int() works in a pthread and it gets executed in the web worker and not on the main thread. | |
47 | // 2. Test that emscripten_run_script_int() works in a pthread and it gets | |
48 | // executed in the web worker and not on the main thread. | |
50 | 49 | #if __EMSCRIPTEN_PTHREADS__ |
51 | 50 | int result = emscripten_run_script_int("Module['ranScript'] && ENVIRONMENT_IS_PTHREAD && (typeof ENVIRONMENT_IS_WORKER !== 'undefined' && ENVIRONMENT_IS_WORKER);"); |
52 | 51 | #else |
68 | 67 | |
69 | 68 | // 4. Test emscripten_async_load_script() runs in a pthread. |
70 | 69 | emscripten_async_load_script("foo.js", AsyncScriptLoaded, AsyncScriptFailed); |
70 | ||
71 | // Should never get here | |
72 | return 99; | |
71 | 73 | } |
0 | // Copyright 2015 The Emscripten Authors. All rights reserved. | |
1 | // Emscripten is available under two separate licenses, the MIT license and the | |
2 | // University of Illinois/NCSA Open Source License. Both these licenses can be | |
3 | // found in the LICENSE file. | |
4 | ||
5 | #include <assert.h> | |
6 | #include <stdio.h> | |
7 | #include <pthread.h> | |
8 | ||
9 | void destructor(void* arg) { | |
10 | printf("destructor: %d\n", (int)arg); | |
11 | assert(arg == (void*)42); | |
12 | } | |
13 | ||
14 | int main() { | |
15 | pthread_key_t key; | |
16 | pthread_key_create(&key, destructor); | |
17 | void *val = pthread_getspecific(key); | |
18 | assert(val == 0); | |
19 | pthread_setspecific(key, (void*)42); | |
20 | val = pthread_getspecific(key); | |
21 | assert(val == (void*)42); | |
22 | return 0; | |
23 | } |
0 | // Copyright 2015 The Emscripten Authors. All rights reserved. | |
1 | // Emscripten is available under two separate licenses, the MIT license and the | |
2 | // University of Illinois/NCSA Open Source License. Both these licenses can be | |
3 | // found in the LICENSE file. | |
4 | ||
5 | #include <assert.h> | |
6 | #include <pthread.h> | |
7 | ||
8 | int main() | |
9 | { | |
10 | pthread_key_t key; | |
11 | pthread_key_create(&key, NULL); | |
12 | void *val = pthread_getspecific(key); | |
13 | assert(val == 0); | |
14 | pthread_setspecific(key, (void*)1); | |
15 | val = pthread_getspecific(key); | |
16 | assert(val == (void*)1); | |
17 | ||
18 | #ifdef REPORT_RESULT | |
19 | REPORT_RESULT(0); | |
20 | #endif | |
21 | } |
0 | destructor: 42 |
1221 | 1221 | "module": 4, |
1222 | 1222 | "nextInChain": 0 |
1223 | 1223 | }, |
1224 | "__cxa_exception": { | |
1225 | "__size__": 16, | |
1226 | "caught": 12, | |
1227 | "exceptionDestructor": 8, | |
1228 | "exceptionType": 4, | |
1229 | "referenceCount": 0, | |
1230 | "rethrown": 13 | |
1231 | }, | |
1224 | 1232 | "__wasi_fdstat_t": { |
1225 | 1233 | "__size__": 24, |
1226 | 1234 | "fs_filetype": 0, |
16 | 16 | |
17 | 17 | # XXX Use EMTEST_ALL_ENGINES=1 in the env to test all engines! |
18 | 18 | |
19 | from enum import Enum | |
20 | from functools import wraps | |
21 | from subprocess import PIPE, STDOUT | |
22 | 19 | import argparse |
23 | 20 | import atexit |
24 | import contextlib | |
25 | import difflib | |
26 | 21 | import fnmatch |
27 | 22 | import glob |
28 | import hashlib | |
29 | 23 | import logging |
30 | 24 | import math |
31 | import multiprocessing | |
32 | 25 | import operator |
33 | 26 | import os |
34 | 27 | import random |
35 | import shlex | |
36 | import shutil | |
37 | import string | |
38 | import subprocess | |
39 | import stat | |
40 | 28 | import sys |
41 | import tempfile | |
42 | import time | |
43 | 29 | import unittest |
44 | import webbrowser | |
45 | from pathlib import Path | |
46 | from http.server import HTTPServer, SimpleHTTPRequestHandler | |
47 | from urllib.parse import unquote, unquote_plus | |
48 | 30 | |
49 | 31 | # Setup |
50 | 32 | |
51 | 33 | __rootpath__ = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
52 | 34 | sys.path.append(__rootpath__) |
53 | 35 | |
54 | import clang_native | |
55 | 36 | import jsrun |
56 | 37 | import parallel_testsuite |
57 | from jsrun import NON_ZERO | |
58 | from tools.shared import TEMP_DIR, EMCC, EMXX, DEBUG, EMCONFIGURE, EMCMAKE | |
59 | from tools.shared import EMSCRIPTEN_TEMP_DIR | |
60 | from tools.shared import EM_BUILD_VERBOSE | |
61 | from tools.shared import get_canonical_temp_dir, try_delete | |
62 | from tools.utils import MACOS, WINDOWS | |
63 | from tools import shared, line_endings, building, config | |
64 | ||
65 | ||
66 | def path_from_root(*pathelems): | |
67 | """Construct a path relative to the emscripten root directory.""" | |
68 | return str(Path(__rootpath__, *pathelems)) | |
69 | ||
70 | ||
71 | sys.path.append(path_from_root('third_party/websockify')) | |
38 | import common | |
39 | from tools import shared, config | |
40 | ||
41 | ||
42 | sys.path.append(shared.path_from_root('third_party/websockify')) | |
72 | 43 | |
73 | 44 | logger = logging.getLogger("runner") |
74 | 45 | |
75 | # User can specify an environment variable EMTEST_BROWSER to force the browser | |
76 | # test suite to run using another browser command line than the default system | |
77 | # browser. Setting '0' as the browser disables running a browser (but we still | |
78 | # see tests compile) | |
79 | EMTEST_BROWSER = os.getenv('EMTEST_BROWSER') | |
80 | ||
81 | EMTEST_DETECT_TEMPFILE_LEAKS = int(os.getenv('EMTEST_DETECT_TEMPFILE_LEAKS', '0')) | |
82 | ||
83 | # TODO(sbc): Remove this check for the legacy name once its been around for a while. | |
84 | assert 'EM_SAVE_DIR' not in os.environ, "Please use EMTEST_SAVE_DIR instead of EM_SAVE_DIR" | |
85 | ||
86 | EMTEST_SAVE_DIR = int(os.getenv('EMTEST_SAVE_DIR', '0')) | |
87 | ||
88 | # generally js engines are equivalent, testing 1 is enough. set this | |
89 | # to force testing on all js engines, good to find js engine bugs | |
90 | EMTEST_ALL_ENGINES = os.getenv('EMTEST_ALL_ENGINES') | |
91 | ||
92 | EMTEST_SKIP_SLOW = os.getenv('EMTEST_SKIP_SLOW') | |
93 | ||
94 | EMTEST_LACKS_NATIVE_CLANG = os.getenv('EMTEST_LACKS_NATIVE_CLANG') | |
95 | ||
96 | EMTEST_VERBOSE = int(os.getenv('EMTEST_VERBOSE', '0')) or shared.DEBUG | |
97 | ||
98 | TEST_ROOT = path_from_root('tests') | |
99 | ||
100 | WEBIDL_BINDER = shared.bat_suffix(path_from_root('tools/webidl_binder')) | |
101 | ||
102 | EMBUILDER = shared.bat_suffix(path_from_root('embuilder')) | |
103 | ||
104 | ||
105 | if EMTEST_VERBOSE: | |
46 | ||
47 | if common.EMTEST_VERBOSE: | |
106 | 48 | logging.root.setLevel(logging.DEBUG) |
107 | ||
108 | ||
109 | def delete_contents(pathname): | |
110 | for entry in os.listdir(pathname): | |
111 | try_delete(os.path.join(pathname, entry)) | |
112 | ||
113 | ||
114 | def test_file(*path_components): | |
115 | """Construct a path relative to the emscripten "tests" directory.""" | |
116 | return str(Path(TEST_ROOT, *path_components)) | |
117 | ||
118 | ||
119 | def read_file(*path_components): | |
120 | return Path(*path_components).read_text() | |
121 | ||
122 | ||
123 | def read_binary(*path_components): | |
124 | return Path(*path_components).read_bytes() | |
125 | ||
126 | ||
127 | # checks if browser testing is enabled | |
128 | def has_browser(): | |
129 | return EMTEST_BROWSER != '0' | |
130 | ||
131 | ||
132 | def compiler_for(filename, force_c=False): | |
133 | if shared.suffix(filename) in ('.cc', '.cxx', '.cpp') and not force_c: | |
134 | return EMXX | |
135 | else: | |
136 | return EMCC | |
137 | ||
138 | ||
139 | # Generic decorator that calls a function named 'condition' on the test class and | |
140 | # skips the test if that function returns true | |
141 | def skip_if(func, condition, explanation='', negate=False): | |
142 | assert callable(func) | |
143 | explanation_str = ' : %s' % explanation if explanation else '' | |
144 | ||
145 | @wraps(func) | |
146 | def decorated(self, *args, **kwargs): | |
147 | choice = self.__getattribute__(condition)() | |
148 | if negate: | |
149 | choice = not choice | |
150 | if choice: | |
151 | self.skipTest(condition + explanation_str) | |
152 | func(self, *args, **kwargs) | |
153 | ||
154 | return decorated | |
155 | ||
156 | ||
157 | def needs_dylink(func): | |
158 | assert callable(func) | |
159 | ||
160 | @wraps(func) | |
161 | def decorated(self): | |
162 | self.check_dylink() | |
163 | return func(self) | |
164 | ||
165 | return decorated | |
166 | ||
167 | ||
168 | def is_slow_test(func): | |
169 | assert callable(func) | |
170 | ||
171 | @wraps(func) | |
172 | def decorated(self, *args, **kwargs): | |
173 | if EMTEST_SKIP_SLOW: | |
174 | return self.skipTest('skipping slow tests') | |
175 | return func(self, *args, **kwargs) | |
176 | ||
177 | return decorated | |
178 | ||
179 | ||
180 | def disabled(note=''): | |
181 | assert not callable(note) | |
182 | return unittest.skip(note) | |
183 | ||
184 | ||
185 | def no_mac(note=''): | |
186 | assert not callable(note) | |
187 | if MACOS: | |
188 | return unittest.skip(note) | |
189 | return lambda f: f | |
190 | ||
191 | ||
192 | def no_windows(note=''): | |
193 | assert not callable(note) | |
194 | if WINDOWS: | |
195 | return unittest.skip(note) | |
196 | return lambda f: f | |
197 | ||
198 | ||
199 | def requires_native_clang(func): | |
200 | assert callable(func) | |
201 | ||
202 | def decorated(self, *args, **kwargs): | |
203 | if EMTEST_LACKS_NATIVE_CLANG: | |
204 | return self.skipTest('native clang tests are disabled') | |
205 | return func(self, *args, **kwargs) | |
206 | ||
207 | return decorated | |
208 | ||
209 | ||
210 | def require_node(func): | |
211 | assert callable(func) | |
212 | ||
213 | def decorated(self, *args, **kwargs): | |
214 | self.require_node() | |
215 | return func(self, *args, **kwargs) | |
216 | ||
217 | return decorated | |
218 | ||
219 | ||
220 | def require_v8(func): | |
221 | assert callable(func) | |
222 | ||
223 | def decorated(self, *args, **kwargs): | |
224 | self.require_v8() | |
225 | return func(self, *args, **kwargs) | |
226 | ||
227 | return decorated | |
228 | ||
229 | ||
230 | def node_pthreads(f): | |
231 | def decorated(self): | |
232 | self.set_setting('USE_PTHREADS') | |
233 | self.emcc_args += ['-Wno-pthreads-mem-growth'] | |
234 | if self.get_setting('MINIMAL_RUNTIME'): | |
235 | self.skipTest('node pthreads not yet supported with MINIMAL_RUNTIME') | |
236 | self.js_engines = [config.NODE_JS] | |
237 | self.node_args += ['--experimental-wasm-threads', '--experimental-wasm-bulk-memory'] | |
238 | f(self) | |
239 | return decorated | |
240 | ||
241 | ||
242 | @contextlib.contextmanager | |
243 | def env_modify(updates): | |
244 | """A context manager that updates os.environ.""" | |
245 | # This could also be done with mock.patch.dict() but taking a dependency | |
246 | # on the mock library is probably not worth the benefit. | |
247 | old_env = os.environ.copy() | |
248 | print("env_modify: " + str(updates)) | |
249 | # Seting a value to None means clear the environment variable | |
250 | clears = [key for key, value in updates.items() if value is None] | |
251 | updates = {key: value for key, value in updates.items() if value is not None} | |
252 | os.environ.update(updates) | |
253 | for key in clears: | |
254 | if key in os.environ: | |
255 | del os.environ[key] | |
256 | try: | |
257 | yield | |
258 | finally: | |
259 | os.environ.clear() | |
260 | os.environ.update(old_env) | |
261 | ||
262 | ||
263 | # Decorator version of env_modify | |
264 | def with_env_modify(updates): | |
265 | def decorated(f): | |
266 | def modified(self): | |
267 | with env_modify(updates): | |
268 | return f(self) | |
269 | return modified | |
270 | return decorated | |
271 | ||
272 | ||
273 | def ensure_dir(dirname): | |
274 | dirname = Path(dirname) | |
275 | dirname.mkdir(parents=True, exist_ok=True) | |
276 | ||
277 | ||
278 | def limit_size(string, maxbytes=800000 * 20, maxlines=100000, max_line=5000): | |
279 | lines = string.splitlines() | |
280 | for i, line in enumerate(lines): | |
281 | if len(line) > max_line: | |
282 | lines[i] = line[:max_line] + '[..]' | |
283 | if len(lines) > maxlines: | |
284 | lines = lines[0:maxlines // 2] + ['[..]'] + lines[-maxlines // 2:] | |
285 | string = '\n'.join(lines) + '\n' | |
286 | if len(string) > maxbytes: | |
287 | string = string[0:maxbytes // 2] + '\n[..]\n' + string[-maxbytes // 2:] | |
288 | return string | |
289 | ||
290 | ||
291 | def create_file(name, contents, binary=False): | |
292 | name = Path(name) | |
293 | assert not name.is_absolute() | |
294 | if binary: | |
295 | name.write_bytes(contents) | |
296 | else: | |
297 | name.write_text(contents) | |
298 | ||
299 | ||
300 | def make_executable(name): | |
301 | Path(name).chmod(stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC) | |
302 | 49 | |
303 | 50 | |
304 | 51 | # The core test modes |
337 | 84 | ] |
338 | 85 | |
339 | 86 | |
340 | def parameterized(parameters): | |
341 | """ | |
342 | Mark a test as parameterized. | |
343 | ||
344 | Usage: | |
345 | @parameterized({ | |
346 | 'subtest1': (1, 2, 3), | |
347 | 'subtest2': (4, 5, 6), | |
348 | }) | |
349 | def test_something(self, a, b, c): | |
350 | ... # actual test body | |
351 | ||
352 | This is equivalent to defining two tests: | |
353 | ||
354 | def test_something_subtest1(self): | |
355 | # runs test_something(1, 2, 3) | |
356 | ||
357 | def test_something_subtest2(self): | |
358 | # runs test_something(4, 5, 6) | |
359 | """ | |
360 | def decorator(func): | |
361 | func._parameterize = parameters | |
362 | return func | |
363 | return decorator | |
364 | ||
365 | ||
366 | class RunnerMeta(type): | |
367 | @classmethod | |
368 | def make_test(mcs, name, func, suffix, args): | |
369 | """ | |
370 | This is a helper function to create new test functions for each parameterized form. | |
371 | ||
372 | :param name: the original name of the function | |
373 | :param func: the original function that we are parameterizing | |
374 | :param suffix: the suffix to append to the name of the function for this parameterization | |
375 | :param args: the positional arguments to pass to the original function for this parameterization | |
376 | :returns: a tuple of (new_function_name, new_function_object) | |
377 | """ | |
378 | ||
379 | # Create the new test function. It calls the original function with the specified args. | |
380 | # We use @functools.wraps to copy over all the function attributes. | |
381 | @wraps(func) | |
382 | def resulting_test(self): | |
383 | return func(self, *args) | |
384 | ||
385 | # Add suffix to the function name so that it displays correctly. | |
386 | if suffix: | |
387 | resulting_test.__name__ = f'{name}_{suffix}' | |
388 | else: | |
389 | resulting_test.__name__ = name | |
390 | ||
391 | # On python 3, functions have __qualname__ as well. This is a full dot-separated path to the | |
392 | # function. We add the suffix to it as well. | |
393 | resulting_test.__qualname__ = f'{func.__qualname__}_{suffix}' | |
394 | ||
395 | return resulting_test.__name__, resulting_test | |
396 | ||
397 | def __new__(mcs, name, bases, attrs): | |
398 | # This metaclass expands parameterized methods from `attrs` into separate ones in `new_attrs`. | |
399 | new_attrs = {} | |
400 | ||
401 | for attr_name, value in attrs.items(): | |
402 | # Check if a member of the new class has _parameterize, the tag inserted by @parameterized. | |
403 | if hasattr(value, '_parameterize'): | |
404 | # If it does, we extract the parameterization information, build new test functions. | |
405 | for suffix, args in value._parameterize.items(): | |
406 | new_name, func = mcs.make_test(attr_name, value, suffix, args) | |
407 | assert new_name not in new_attrs, 'Duplicate attribute name generated when parameterizing %s' % attr_name | |
408 | new_attrs[new_name] = func | |
409 | else: | |
410 | # If not, we just copy it over to new_attrs verbatim. | |
411 | assert attr_name not in new_attrs, '%s collided with an attribute from parameterization' % attr_name | |
412 | new_attrs[attr_name] = value | |
413 | ||
414 | # We invoke type, the default metaclass, to actually create the new class, with new_attrs. | |
415 | return type.__new__(mcs, name, bases, new_attrs) | |
416 | ||
417 | ||
418 | class RunnerCore(unittest.TestCase, metaclass=RunnerMeta): | |
419 | # default temporary directory settings. set_temp_dir may be called later to | |
420 | # override these | |
421 | temp_dir = TEMP_DIR | |
422 | canonical_temp_dir = get_canonical_temp_dir(TEMP_DIR) | |
423 | ||
424 | # This avoids cluttering the test runner output, which is stderr too, with compiler warnings etc. | |
425 | # Change this to None to get stderr reporting, for debugging purposes | |
426 | stderr_redirect = STDOUT | |
427 | ||
428 | def is_wasm(self): | |
429 | return self.get_setting('WASM') != 0 | |
430 | ||
431 | def check_dylink(self): | |
432 | if self.get_setting('ALLOW_MEMORY_GROWTH') == 1 and not self.is_wasm(): | |
433 | self.skipTest('no dynamic linking with memory growth (without wasm)') | |
434 | if not self.is_wasm(): | |
435 | self.skipTest('no dynamic linking support in wasm2js yet') | |
436 | if '-fsanitize=address' in self.emcc_args: | |
437 | self.skipTest('no dynamic linking support in ASan yet') | |
438 | if '-fsanitize=leak' in self.emcc_args: | |
439 | self.skipTest('no dynamic linking support in LSan yet') | |
440 | ||
441 | def require_v8(self): | |
442 | if not config.V8_ENGINE or config.V8_ENGINE not in config.JS_ENGINES: | |
443 | if 'EMTEST_SKIP_V8' in os.environ: | |
444 | self.skipTest('test requires v8 and EMTEST_SKIP_V8 is set') | |
445 | else: | |
446 | self.fail('d8 required to run this test. Use EMTEST_SKIP_V8 to skip') | |
447 | self.js_engines = [config.V8_ENGINE] | |
448 | ||
449 | def require_node(self): | |
450 | if not config.NODE_JS or config.NODE_JS not in config.JS_ENGINES: | |
451 | if 'EMTEST_SKIP_NODE' in os.environ: | |
452 | self.skipTest('test requires node and EMTEST_SKIP_NODE is set') | |
453 | else: | |
454 | self.fail('node required to run this test. Use EMTEST_SKIP_NODE to skip') | |
455 | self.js_engines = [config.NODE_JS] | |
456 | ||
457 | def uses_memory_init_file(self): | |
458 | if self.get_setting('SIDE_MODULE') or (self.is_wasm() and not self.get_setting('WASM2JS')): | |
459 | return False | |
460 | elif '--memory-init-file' in self.emcc_args: | |
461 | return int(self.emcc_args[self.emcc_args.index('--memory-init-file') + 1]) | |
462 | else: | |
463 | # side modules handle memory differently; binaryen puts the memory in the wasm module | |
464 | opt_supports = any(opt in self.emcc_args for opt in ('-O2', '-O3', '-Os', '-Oz')) | |
465 | return opt_supports | |
466 | ||
467 | def set_temp_dir(self, temp_dir): | |
468 | self.temp_dir = temp_dir | |
469 | self.canonical_temp_dir = get_canonical_temp_dir(self.temp_dir) | |
470 | # Explicitly set dedicated temporary directory for parallel tests | |
471 | os.environ['EMCC_TEMP_DIR'] = self.temp_dir | |
472 | ||
473 | @classmethod | |
474 | def setUpClass(cls): | |
475 | super().setUpClass() | |
476 | print('(checking sanity from test runner)') # do this after we set env stuff | |
477 | shared.check_sanity(force=True) | |
478 | ||
479 | def setUp(self): | |
480 | super().setUp() | |
481 | self.settings_mods = {} | |
482 | self.emcc_args = ['-Werror'] | |
483 | self.node_args = [] | |
484 | self.v8_args = [] | |
485 | self.env = {} | |
486 | self.temp_files_before_run = [] | |
487 | self.uses_es6 = False | |
488 | self.js_engines = config.JS_ENGINES.copy() | |
489 | self.wasm_engines = config.WASM_ENGINES.copy() | |
490 | self.banned_js_engines = [] | |
491 | self.use_all_engines = EMTEST_ALL_ENGINES | |
492 | ||
493 | if EMTEST_DETECT_TEMPFILE_LEAKS: | |
494 | for root, dirnames, filenames in os.walk(self.temp_dir): | |
495 | for dirname in dirnames: | |
496 | self.temp_files_before_run.append(os.path.normpath(os.path.join(root, dirname))) | |
497 | for filename in filenames: | |
498 | self.temp_files_before_run.append(os.path.normpath(os.path.join(root, filename))) | |
499 | ||
500 | if EMTEST_SAVE_DIR: | |
501 | self.working_dir = os.path.join(self.temp_dir, 'emscripten_test') | |
502 | if os.path.exists(self.working_dir): | |
503 | if EMTEST_SAVE_DIR == 2: | |
504 | print('Not clearing existing test directory') | |
505 | else: | |
506 | print('Clearing existing test directory') | |
507 | # Even when EMTEST_SAVE_DIR we still try to start with an empty directoy as many tests | |
508 | # expect this. EMTEST_SAVE_DIR=2 can be used to keep the old contents for the new test | |
509 | # run. This can be useful when iterating on a given test with extra files you want to keep | |
510 | # around in the output directory. | |
511 | delete_contents(self.working_dir) | |
512 | else: | |
513 | print('Creating new test output directory') | |
514 | ensure_dir(self.working_dir) | |
515 | else: | |
516 | self.working_dir = tempfile.mkdtemp(prefix='emscripten_test_' + self.__class__.__name__ + '_', dir=self.temp_dir) | |
517 | os.chdir(self.working_dir) | |
518 | ||
519 | if not EMTEST_SAVE_DIR: | |
520 | self.has_prev_ll = False | |
521 | for temp_file in os.listdir(TEMP_DIR): | |
522 | if temp_file.endswith('.ll'): | |
523 | self.has_prev_ll = True | |
524 | ||
525 | def tearDown(self): | |
526 | if not EMTEST_SAVE_DIR: | |
527 | # rmtree() fails on Windows if the current working directory is inside the tree. | |
528 | os.chdir(os.path.dirname(self.get_dir())) | |
529 | try_delete(self.get_dir()) | |
530 | ||
531 | if EMTEST_DETECT_TEMPFILE_LEAKS and not DEBUG: | |
532 | temp_files_after_run = [] | |
533 | for root, dirnames, filenames in os.walk(self.temp_dir): | |
534 | for dirname in dirnames: | |
535 | temp_files_after_run.append(os.path.normpath(os.path.join(root, dirname))) | |
536 | for filename in filenames: | |
537 | temp_files_after_run.append(os.path.normpath(os.path.join(root, filename))) | |
538 | ||
539 | # Our leak detection will pick up *any* new temp files in the temp dir. | |
540 | # They may not be due to us, but e.g. the browser when running browser | |
541 | # tests. Until we figure out a proper solution, ignore some temp file | |
542 | # names that we see on our CI infrastructure. | |
543 | ignorable_file_prefixes = [ | |
544 | '/tmp/tmpaddon', | |
545 | '/tmp/circleci-no-output-timeout', | |
546 | '/tmp/wasmer' | |
547 | ] | |
548 | ||
549 | left_over_files = set(temp_files_after_run) - set(self.temp_files_before_run) | |
550 | left_over_files = [f for f in left_over_files if not any([f.startswith(prefix) for prefix in ignorable_file_prefixes])] | |
551 | if len(left_over_files): | |
552 | print('ERROR: After running test, there are ' + str(len(left_over_files)) + ' new temporary files/directories left behind:', file=sys.stderr) | |
553 | for f in left_over_files: | |
554 | print('leaked file: ' + f, file=sys.stderr) | |
555 | self.fail('Test leaked ' + str(len(left_over_files)) + ' temporary files!') | |
556 | ||
557 | def get_setting(self, key, default=None): | |
558 | return self.settings_mods.get(key, default) | |
559 | ||
560 | def set_setting(self, key, value=1): | |
561 | if value is None: | |
562 | self.clear_setting(key) | |
563 | self.settings_mods[key] = value | |
564 | ||
565 | def has_changed_setting(self, key): | |
566 | return key in self.settings_mods | |
567 | ||
568 | def clear_setting(self, key): | |
569 | self.settings_mods.pop(key, None) | |
570 | ||
571 | def serialize_settings(self): | |
572 | ret = [] | |
573 | for key, value in self.settings_mods.items(): | |
574 | if value == 1: | |
575 | ret.append(f'-s{key}') | |
576 | elif type(value) == list: | |
577 | ret.append(f'-s{key}={",".join(value)}') | |
578 | else: | |
579 | ret.append(f'-s{key}={value}') | |
580 | return ret | |
581 | ||
582 | def get_dir(self): | |
583 | return self.working_dir | |
584 | ||
585 | def in_dir(self, *pathelems): | |
586 | return os.path.join(self.get_dir(), *pathelems) | |
587 | ||
588 | def add_pre_run(self, code): | |
589 | create_file('prerun.js', 'Module.preRun = function() { %s }' % code) | |
590 | self.emcc_args += ['--pre-js', 'prerun.js'] | |
591 | ||
592 | def add_post_run(self, code): | |
593 | create_file('postrun.js', 'Module.postRun = function() { %s }' % code) | |
594 | self.emcc_args += ['--pre-js', 'postrun.js'] | |
595 | ||
596 | def add_on_exit(self, code): | |
597 | create_file('onexit.js', 'Module.onExit = function() { %s }' % code) | |
598 | self.emcc_args += ['--pre-js', 'onexit.js'] | |
599 | ||
600 | # returns the full list of arguments to pass to emcc | |
601 | # param @main_file whether this is the main file of the test. some arguments | |
602 | # (like --pre-js) do not need to be passed when building | |
603 | # libraries, for example | |
604 | def get_emcc_args(self, main_file=False): | |
605 | args = self.serialize_settings() + self.emcc_args | |
606 | if not main_file: | |
607 | for i, arg in enumerate(args): | |
608 | if arg in ('--pre-js', '--post-js'): | |
609 | args[i] = None | |
610 | args[i + 1] = None | |
611 | args = [arg for arg in args if arg is not None] | |
612 | return args | |
613 | ||
614 | def verify_es5(self, filename): | |
615 | es_check = shared.get_npm_cmd('es-check') | |
616 | # use --quiet once its available | |
617 | # See: https://github.com/dollarshaveclub/es-check/pull/126/ | |
618 | es_check_env = os.environ.copy() | |
619 | es_check_env['PATH'] = os.path.dirname(config.NODE_JS[0]) + os.pathsep + es_check_env['PATH'] | |
620 | try: | |
621 | shared.run_process(es_check + ['es5', os.path.abspath(filename)], stderr=PIPE, env=es_check_env) | |
622 | except subprocess.CalledProcessError as e: | |
623 | print(e.stderr) | |
624 | self.fail('es-check failed to verify ES5 output compliance') | |
625 | ||
626 | # Build JavaScript code from source code | |
627 | def build(self, filename, libraries=[], includes=[], force_c=False, | |
628 | post_build=None, js_outfile=True, emcc_args=[]): | |
629 | suffix = '.js' if js_outfile else '.wasm' | |
630 | compiler = [compiler_for(filename, force_c)] | |
631 | if compiler[0] == EMCC: | |
632 | # TODO(https://github.com/emscripten-core/emscripten/issues/11121) | |
633 | # We link with C++ stdlibs, even when linking with emcc for historical reasons. We can remove | |
634 | # this if this issues is fixed. | |
635 | compiler.append('-nostdlib++') | |
636 | ||
637 | if force_c: | |
638 | compiler.append('-xc') | |
639 | ||
640 | dirname, basename = os.path.split(filename) | |
641 | output = shared.unsuffixed(basename) + suffix | |
642 | cmd = compiler + [filename, '-o', output] + self.get_emcc_args(main_file=True) + emcc_args + libraries | |
643 | if shared.suffix(filename) not in ('.i', '.ii'): | |
644 | # Add the location of the test file to include path. | |
645 | cmd += ['-I.'] | |
646 | cmd += ['-I' + str(include) for include in includes] | |
647 | ||
648 | self.run_process(cmd, stderr=self.stderr_redirect if not DEBUG else None) | |
649 | self.assertExists(output) | |
650 | if js_outfile and not self.uses_es6: | |
651 | self.verify_es5(output) | |
652 | ||
653 | if post_build: | |
654 | post_build(output) | |
655 | ||
656 | if js_outfile and self.uses_memory_init_file(): | |
657 | src = read_file(output) | |
658 | # side memory init file, or an empty one in the js | |
659 | assert ('/* memory initializer */' not in src) or ('/* memory initializer */ allocate([]' in src) | |
660 | ||
661 | return output | |
662 | ||
663 | def get_func(self, src, name): | |
664 | start = src.index('function ' + name + '(') | |
665 | t = start | |
666 | n = 0 | |
667 | while True: | |
668 | if src[t] == '{': | |
669 | n += 1 | |
670 | elif src[t] == '}': | |
671 | n -= 1 | |
672 | if n == 0: | |
673 | return src[start:t + 1] | |
674 | t += 1 | |
675 | assert t < len(src) | |
676 | ||
677 | def count_funcs(self, javascript_file): | |
678 | num_funcs = 0 | |
679 | start_tok = "// EMSCRIPTEN_START_FUNCS" | |
680 | end_tok = "// EMSCRIPTEN_END_FUNCS" | |
681 | start_off = 0 | |
682 | end_off = 0 | |
683 | ||
684 | js = read_file(javascript_file) | |
685 | blob = "".join(js.splitlines()) | |
686 | ||
687 | start_off = blob.find(start_tok) + len(start_tok) | |
688 | end_off = blob.find(end_tok) | |
689 | asm_chunk = blob[start_off:end_off] | |
690 | num_funcs = asm_chunk.count('function ') | |
691 | return num_funcs | |
692 | ||
693 | def count_wasm_contents(self, wasm_binary, what): | |
694 | out = self.run_process([os.path.join(building.get_binaryen_bin(), 'wasm-opt'), wasm_binary, '--metrics'], stdout=PIPE).stdout | |
695 | # output is something like | |
696 | # [?] : 125 | |
697 | for line in out.splitlines(): | |
698 | if '[' + what + ']' in line: | |
699 | ret = line.split(':')[1].strip() | |
700 | return int(ret) | |
701 | self.fail('Failed to find [%s] in wasm-opt output' % what) | |
702 | ||
703 | def get_wasm_text(self, wasm_binary): | |
704 | return self.run_process([os.path.join(building.get_binaryen_bin(), 'wasm-dis'), wasm_binary], stdout=PIPE).stdout | |
705 | ||
706 | def is_exported_in_wasm(self, name, wasm): | |
707 | wat = self.get_wasm_text(wasm) | |
708 | return ('(export "%s"' % name) in wat | |
709 | ||
710 | def run_js(self, filename, engine=None, args=[], output_nicerizer=None, assert_returncode=0): | |
711 | # use files, as PIPE can get too full and hang us | |
712 | stdout = self.in_dir('stdout') | |
713 | stderr = self.in_dir('stderr') | |
714 | error = None | |
715 | if not engine: | |
716 | engine = self.js_engines[0] | |
717 | if engine == config.NODE_JS: | |
718 | engine = engine + self.node_args | |
719 | if engine == config.V8_ENGINE: | |
720 | engine = engine + self.v8_args | |
721 | if EMTEST_VERBOSE: | |
722 | print(f"Running '{filename}' under '{shared.shlex_join(engine)}'") | |
723 | try: | |
724 | jsrun.run_js(filename, engine, args, | |
725 | stdout=open(stdout, 'w'), | |
726 | stderr=open(stderr, 'w'), | |
727 | assert_returncode=assert_returncode) | |
728 | except subprocess.CalledProcessError as e: | |
729 | error = e | |
730 | ||
731 | # Make sure that we produced proper line endings to the .js file we are about to run. | |
732 | if not filename.endswith('.wasm'): | |
733 | self.assertEqual(line_endings.check_line_endings(filename), 0) | |
734 | ||
735 | out = read_file(stdout) | |
736 | err = read_file(stderr) | |
737 | if output_nicerizer: | |
738 | ret = output_nicerizer(out, err) | |
739 | else: | |
740 | ret = out + err | |
741 | if error or EMTEST_VERBOSE: | |
742 | ret = limit_size(ret) | |
743 | print('-- begin program output --') | |
744 | print(ret, end='') | |
745 | print('-- end program output --') | |
746 | if error: | |
747 | if assert_returncode == NON_ZERO: | |
748 | self.fail('JS subprocess unexpectedly succeeded (%s): Output:\n%s' % (error.cmd, ret)) | |
749 | else: | |
750 | self.fail('JS subprocess failed (%s): %s. Output:\n%s' % (error.cmd, error.returncode, ret)) | |
751 | ||
752 | # We should pass all strict mode checks | |
753 | self.assertNotContained('strict warning:', ret) | |
754 | return ret | |
755 | ||
756 | def assertExists(self, filename, msg=None): | |
757 | if not msg: | |
758 | msg = 'Expected file not found: ' + filename | |
759 | self.assertTrue(os.path.exists(filename), msg) | |
760 | ||
761 | def assertNotExists(self, filename, msg=None): | |
762 | if not msg: | |
763 | msg = 'Unexpected file exists: ' + filename | |
764 | self.assertFalse(os.path.exists(filename), msg) | |
765 | ||
766 | # Tests that the given two paths are identical, modulo path delimiters. E.g. "C:/foo" is equal to "C:\foo". | |
767 | def assertPathsIdentical(self, path1, path2): | |
768 | path1 = path1.replace('\\', '/') | |
769 | path2 = path2.replace('\\', '/') | |
770 | return self.assertIdentical(path1, path2) | |
771 | ||
772 | # Tests that the given two multiline text content are identical, modulo line | |
773 | # ending differences (\r\n on Windows, \n on Unix). | |
774 | def assertTextDataIdentical(self, text1, text2, msg=None, | |
775 | fromfile='expected', tofile='actual'): | |
776 | text1 = text1.replace('\r\n', '\n') | |
777 | text2 = text2.replace('\r\n', '\n') | |
778 | return self.assertIdentical(text1, text2, msg, fromfile, tofile) | |
779 | ||
780 | def assertIdentical(self, values, y, msg=None, | |
781 | fromfile='expected', tofile='actual'): | |
782 | if type(values) not in (list, tuple): | |
783 | values = [values] | |
784 | for x in values: | |
785 | if x == y: | |
786 | return # success | |
787 | diff_lines = difflib.unified_diff(x.splitlines(), y.splitlines(), | |
788 | fromfile=fromfile, tofile=tofile) | |
789 | diff = ''.join([a.rstrip() + '\n' for a in diff_lines]) | |
790 | if EMTEST_VERBOSE: | |
791 | print("Expected to have '%s' == '%s'" % (limit_size(values[0]), limit_size(y))) | |
792 | fail_message = 'Unexpected difference:\n' + limit_size(diff) | |
793 | if not EMTEST_VERBOSE: | |
794 | fail_message += '\nFor full output run with EMTEST_VERBOSE=1.' | |
795 | if msg: | |
796 | fail_message += '\n' + msg | |
797 | self.fail(fail_message) | |
798 | ||
799 | def assertTextDataContained(self, text1, text2): | |
800 | text1 = text1.replace('\r\n', '\n') | |
801 | text2 = text2.replace('\r\n', '\n') | |
802 | return self.assertContained(text1, text2) | |
803 | ||
804 | def assertContained(self, values, string, additional_info=''): | |
805 | if type(values) not in [list, tuple]: | |
806 | values = [values] | |
807 | if callable(string): | |
808 | string = string() | |
809 | ||
810 | if not any(v in string for v in values): | |
811 | diff = difflib.unified_diff(values[0].split('\n'), string.split('\n'), fromfile='expected', tofile='actual') | |
812 | diff = ''.join(a.rstrip() + '\n' for a in diff) | |
813 | self.fail("Expected to find '%s' in '%s', diff:\n\n%s\n%s" % ( | |
814 | limit_size(values[0]), limit_size(string), limit_size(diff), | |
815 | additional_info | |
816 | )) | |
817 | ||
818 | def assertNotContained(self, value, string): | |
819 | if callable(value): | |
820 | value = value() # lazy loading | |
821 | if callable(string): | |
822 | string = string() | |
823 | if value in string: | |
824 | self.fail("Expected to NOT find '%s' in '%s', diff:\n\n%s" % ( | |
825 | limit_size(value), limit_size(string), | |
826 | limit_size(''.join([a.rstrip() + '\n' for a in difflib.unified_diff(value.split('\n'), string.split('\n'), fromfile='expected', tofile='actual')])) | |
827 | )) | |
828 | ||
829 | def assertContainedIf(self, value, string, condition): | |
830 | if condition: | |
831 | self.assertContained(value, string) | |
832 | else: | |
833 | self.assertNotContained(value, string) | |
834 | ||
835 | def assertBinaryEqual(self, file1, file2): | |
836 | self.assertEqual(os.path.getsize(file1), | |
837 | os.path.getsize(file2)) | |
838 | self.assertEqual(read_binary(file1), | |
839 | read_binary(file2)) | |
840 | ||
841 | library_cache = {} | |
842 | ||
843 | def get_build_dir(self): | |
844 | ret = os.path.join(self.get_dir(), 'building') | |
845 | ensure_dir(ret) | |
846 | return ret | |
847 | ||
848 | def get_library(self, name, generated_libs, configure=['sh', './configure'], | |
849 | configure_args=[], make=['make'], make_args=None, | |
850 | env_init={}, cache_name_extra='', native=False): | |
851 | if make_args is None: | |
852 | make_args = ['-j', str(shared.get_num_cores())] | |
853 | ||
854 | build_dir = self.get_build_dir() | |
855 | output_dir = self.get_dir() | |
856 | ||
857 | emcc_args = self.get_emcc_args() | |
858 | ||
859 | hash_input = (str(emcc_args) + ' $ ' + str(env_init)).encode('utf-8') | |
860 | cache_name = name + ','.join([opt for opt in emcc_args if len(opt) < 7]) + '_' + hashlib.md5(hash_input).hexdigest() + cache_name_extra | |
861 | ||
862 | valid_chars = "_%s%s" % (string.ascii_letters, string.digits) | |
863 | cache_name = ''.join([(c if c in valid_chars else '_') for c in cache_name]) | |
864 | ||
865 | if self.library_cache.get(cache_name): | |
866 | print('<load %s from cache> ' % cache_name, file=sys.stderr) | |
867 | generated_libs = [] | |
868 | for basename, contents in self.library_cache[cache_name]: | |
869 | bc_file = os.path.join(build_dir, cache_name + '_' + basename) | |
870 | with open(bc_file, 'wb') as f: | |
871 | f.write(contents) | |
872 | generated_libs.append(bc_file) | |
873 | return generated_libs | |
874 | ||
875 | print(f'<building and saving {cache_name} into cache>', file=sys.stderr) | |
876 | if configure is not None: | |
877 | # Avoid += so we don't mutate the default arg | |
878 | configure = configure + configure_args | |
879 | ||
880 | return build_library(name, build_dir, output_dir, generated_libs, configure, | |
881 | make, make_args, self.library_cache, | |
882 | cache_name, env_init=env_init, native=native, cflags=self.get_emcc_args()) | |
883 | ||
884 | def clear(self): | |
885 | delete_contents(self.get_dir()) | |
886 | if EMSCRIPTEN_TEMP_DIR: | |
887 | delete_contents(EMSCRIPTEN_TEMP_DIR) | |
888 | ||
889 | def run_process(self, cmd, check=True, **args): | |
890 | # Wrapper around shared.run_process. This is desirable so that the tests | |
891 | # can fail (in the unittest sense) rather than error'ing. | |
892 | # In the long run it would nice to completely remove the dependency on | |
893 | # core emscripten code (shared.py) here. | |
894 | try: | |
895 | return shared.run_process(cmd, check=check, **args) | |
896 | except subprocess.CalledProcessError as e: | |
897 | if check and e.returncode != 0: | |
898 | self.fail('subprocess exited with non-zero return code(%d): `%s`' % | |
899 | (e.returncode, shared.shlex_join(cmd))) | |
900 | ||
901 | def emcc(self, filename, args=[], output_filename=None, **kwargs): | |
902 | if output_filename is None: | |
903 | output_filename = filename + '.o' | |
904 | try_delete(output_filename) | |
905 | self.run_process([compiler_for(filename), filename] + args + ['-o', output_filename], **kwargs) | |
906 | ||
907 | # Shared test code between main suite and others | |
908 | ||
909 | def expect_fail(self, cmd, **args): | |
910 | """Run a subprocess and assert that it returns non-zero. | |
911 | ||
912 | Return the stderr of the subprocess. | |
913 | """ | |
914 | proc = self.run_process(cmd, check=False, stderr=PIPE, **args) | |
915 | self.assertNotEqual(proc.returncode, 0, 'subprocess unexpectedly succeeded. stderr:\n' + proc.stderr) | |
916 | # When we check for failure we expect a user-visible error, not a traceback. | |
917 | # However, on windows a python traceback can happen randomly sometimes, | |
918 | # due to "Access is denied" https://github.com/emscripten-core/emscripten/issues/718 | |
919 | if not WINDOWS or 'Access is denied' not in proc.stderr: | |
920 | self.assertNotContained('Traceback', proc.stderr) | |
921 | return proc.stderr | |
922 | ||
923 | # excercise dynamic linker. | |
924 | # | |
925 | # test that linking to shared library B, which is linked to A, loads A as well. | |
926 | # main is also linked to C, which is also linked to A. A is loaded/initialized only once. | |
927 | # | |
928 | # B | |
929 | # main < > A | |
930 | # C | |
931 | # | |
932 | # this test is used by both test_core and test_browser. | |
933 | # when run under broswer it excercises how dynamic linker handles concurrency | |
934 | # - because B and C are loaded in parallel. | |
935 | def _test_dylink_dso_needed(self, do_run): | |
936 | create_file('liba.cpp', r''' | |
937 | #include <stdio.h> | |
938 | #include <emscripten.h> | |
939 | ||
940 | static const char *afunc_prev; | |
941 | ||
942 | extern "C" { | |
943 | EMSCRIPTEN_KEEPALIVE void afunc(const char *s); | |
944 | } | |
945 | ||
946 | void afunc(const char *s) { | |
947 | printf("a: %s (prev: %s)\n", s, afunc_prev); | |
948 | afunc_prev = s; | |
949 | } | |
950 | ||
951 | struct ainit { | |
952 | ainit() { | |
953 | puts("a: loaded"); | |
954 | } | |
955 | }; | |
956 | ||
957 | static ainit _; | |
958 | ''') | |
959 | ||
960 | create_file('libb.c', r''' | |
961 | #include <emscripten.h> | |
962 | ||
963 | void afunc(const char *s); | |
964 | ||
965 | EMSCRIPTEN_KEEPALIVE void bfunc() { | |
966 | afunc("b"); | |
967 | } | |
968 | ''') | |
969 | ||
970 | create_file('libc.c', r''' | |
971 | #include <emscripten.h> | |
972 | ||
973 | void afunc(const char *s); | |
974 | ||
975 | EMSCRIPTEN_KEEPALIVE void cfunc() { | |
976 | afunc("c"); | |
977 | } | |
978 | ''') | |
979 | ||
980 | # _test_dylink_dso_needed can be potentially called several times by a test. | |
981 | # reset dylink-related options first. | |
982 | self.clear_setting('MAIN_MODULE') | |
983 | self.clear_setting('SIDE_MODULE') | |
984 | ||
985 | # XXX in wasm each lib load currently takes 5MB; default INITIAL_MEMORY=16MB is thus not enough | |
986 | self.set_setting('INITIAL_MEMORY', '32mb') | |
987 | ||
988 | so = '.wasm' if self.is_wasm() else '.js' | |
989 | ||
990 | def ccshared(src, linkto=[]): | |
991 | cmdv = [EMCC, src, '-o', shared.unsuffixed(src) + so, '-s', 'SIDE_MODULE'] + self.get_emcc_args() | |
992 | cmdv += linkto | |
993 | self.run_process(cmdv) | |
994 | ||
995 | ccshared('liba.cpp') | |
996 | ccshared('libb.c', ['liba' + so]) | |
997 | ccshared('libc.c', ['liba' + so]) | |
998 | ||
999 | self.set_setting('MAIN_MODULE') | |
1000 | extra_args = ['-L.', 'libb' + so, 'libc' + so] | |
1001 | do_run(r''' | |
1002 | #ifdef __cplusplus | |
1003 | extern "C" { | |
1004 | #endif | |
1005 | void bfunc(); | |
1006 | void cfunc(); | |
1007 | #ifdef __cplusplus | |
1008 | } | |
1009 | #endif | |
1010 | ||
1011 | int test_main() { | |
1012 | bfunc(); | |
1013 | cfunc(); | |
1014 | return 0; | |
1015 | } | |
1016 | ''', | |
1017 | 'a: loaded\na: b (prev: (null))\na: c (prev: b)\n', emcc_args=extra_args) | |
1018 | ||
1019 | for libname in ['liba', 'libb', 'libc']: | |
1020 | self.emcc_args += ['--embed-file', libname + so] | |
1021 | do_run(r''' | |
1022 | #include <assert.h> | |
1023 | #include <dlfcn.h> | |
1024 | #include <stddef.h> | |
1025 | ||
1026 | int test_main() { | |
1027 | void *bdso, *cdso; | |
1028 | void (*bfunc_ptr)(), (*cfunc_ptr)(); | |
1029 | ||
1030 | // FIXME for RTLD_LOCAL binding symbols to loaded lib is not currently working | |
1031 | bdso = dlopen("libb%(so)s", RTLD_NOW|RTLD_GLOBAL); | |
1032 | assert(bdso != NULL); | |
1033 | cdso = dlopen("libc%(so)s", RTLD_NOW|RTLD_GLOBAL); | |
1034 | assert(cdso != NULL); | |
1035 | ||
1036 | bfunc_ptr = (void (*)())dlsym(bdso, "bfunc"); | |
1037 | assert(bfunc_ptr != NULL); | |
1038 | cfunc_ptr = (void (*)())dlsym(cdso, "cfunc"); | |
1039 | assert(cfunc_ptr != NULL); | |
1040 | ||
1041 | bfunc_ptr(); | |
1042 | cfunc_ptr(); | |
1043 | return 0; | |
1044 | } | |
1045 | ''' % locals(), | |
1046 | 'a: loaded\na: b (prev: (null))\na: c (prev: b)\n') | |
1047 | ||
1048 | def filtered_js_engines(self, js_engines=None): | |
1049 | if js_engines is None: | |
1050 | js_engines = self.js_engines | |
1051 | for engine in js_engines: | |
1052 | assert engine in config.JS_ENGINES, "js engine does not exist in config.JS_ENGINES" | |
1053 | assert type(engine) == list | |
1054 | for engine in self.banned_js_engines: | |
1055 | assert type(engine) in (list, type(None)) | |
1056 | banned = [b[0] for b in self.banned_js_engines if b] | |
1057 | return [engine for engine in js_engines if engine and engine[0] not in banned] | |
1058 | ||
1059 | def do_run(self, src, expected_output, force_c=False, **kwargs): | |
1060 | if 'no_build' in kwargs: | |
1061 | filename = src | |
1062 | else: | |
1063 | if force_c: | |
1064 | filename = 'src.c' | |
1065 | else: | |
1066 | filename = 'src.cpp' | |
1067 | with open(filename, 'w') as f: | |
1068 | f.write(src) | |
1069 | self._build_and_run(filename, expected_output, **kwargs) | |
1070 | ||
1071 | def do_runf(self, filename, expected_output=None, **kwargs): | |
1072 | self._build_and_run(filename, expected_output, **kwargs) | |
1073 | ||
1074 | ## Just like `do_run` but with filename of expected output | |
1075 | def do_run_from_file(self, filename, expected_output_filename, **kwargs): | |
1076 | self._build_and_run(filename, read_file(expected_output_filename), **kwargs) | |
1077 | ||
1078 | def do_run_in_out_file_test(self, *path, **kwargs): | |
1079 | srcfile = test_file(*path) | |
1080 | out_suffix = kwargs.pop('out_suffix', '') | |
1081 | outfile = shared.unsuffixed(srcfile) + out_suffix + '.out' | |
1082 | expected = read_file(outfile) | |
1083 | self._build_and_run(srcfile, expected, **kwargs) | |
1084 | ||
1085 | ## Does a complete test - builds, runs, checks output, etc. | |
1086 | def _build_and_run(self, filename, expected_output, args=[], output_nicerizer=None, | |
1087 | no_build=False, | |
1088 | js_engines=None, post_build=None, libraries=[], | |
1089 | includes=[], | |
1090 | assert_returncode=0, assert_identical=False, assert_all=False, | |
1091 | check_for_error=True, force_c=False, emcc_args=[]): | |
1092 | logger.debug(f'_build_and_run: {filename}') | |
1093 | ||
1094 | if no_build: | |
1095 | js_file = filename | |
1096 | else: | |
1097 | self.build(filename, libraries=libraries, includes=includes, post_build=post_build, | |
1098 | force_c=force_c, emcc_args=emcc_args) | |
1099 | js_file = shared.unsuffixed(os.path.basename(filename)) + '.js' | |
1100 | self.assertExists(js_file) | |
1101 | ||
1102 | engines = self.filtered_js_engines(js_engines) | |
1103 | if len(engines) > 1 and not self.use_all_engines: | |
1104 | engines = engines[:1] | |
1105 | # In standalone mode, also add wasm vms as we should be able to run there too. | |
1106 | if self.get_setting('STANDALONE_WASM'): | |
1107 | # TODO once standalone wasm support is more stable, apply use_all_engines | |
1108 | # like with js engines, but for now as we bring it up, test in all of them | |
1109 | if not self.wasm_engines: | |
1110 | logger.warning('no wasm engine was found to run the standalone part of this test') | |
1111 | engines += self.wasm_engines | |
1112 | if self.get_setting('WASM2C') and not EMTEST_LACKS_NATIVE_CLANG: | |
1113 | # compile the c file to a native executable. | |
1114 | c = shared.unsuffixed(js_file) + '.wasm.c' | |
1115 | executable = shared.unsuffixed(js_file) + '.exe' | |
1116 | cmd = [shared.CLANG_CC, c, '-o', executable] + clang_native.get_clang_native_args() | |
1117 | self.run_process(cmd, env=clang_native.get_clang_native_env()) | |
1118 | # we can now run the executable directly, without an engine, which | |
1119 | # we indicate with None as the engine | |
1120 | engines += [[None]] | |
1121 | if len(engines) == 0: | |
1122 | self.skipTest('No JS engine present to run this test with. Check %s and the paths therein.' % config.EM_CONFIG) | |
1123 | for engine in engines: | |
1124 | js_output = self.run_js(js_file, engine, args, output_nicerizer=output_nicerizer, assert_returncode=assert_returncode) | |
1125 | js_output = js_output.replace('\r\n', '\n') | |
1126 | if expected_output: | |
1127 | try: | |
1128 | if assert_identical: | |
1129 | self.assertIdentical(expected_output, js_output) | |
1130 | elif assert_all: | |
1131 | for o in expected_output: | |
1132 | self.assertContained(o, js_output) | |
1133 | else: | |
1134 | self.assertContained(expected_output, js_output) | |
1135 | if check_for_error: | |
1136 | self.assertNotContained('ERROR', js_output) | |
1137 | except Exception: | |
1138 | print('(test did not pass in JS engine: %s)' % engine) | |
1139 | raise | |
1140 | ||
1141 | def get_freetype_library(self): | |
1142 | if '-Werror' in self.emcc_args: | |
1143 | self.emcc_args.remove('-Werror') | |
1144 | return self.get_library(os.path.join('third_party', 'freetype'), os.path.join('objs', '.libs', 'libfreetype.a'), configure_args=['--disable-shared', '--without-zlib']) | |
1145 | ||
1146 | def get_poppler_library(self, env_init=None): | |
1147 | # The fontconfig symbols are all missing from the poppler build | |
1148 | # e.g. FcConfigSubstitute | |
1149 | self.set_setting('ERROR_ON_UNDEFINED_SYMBOLS', 0) | |
1150 | ||
1151 | self.emcc_args += [ | |
1152 | '-I' + test_file('third_party/freetype/include'), | |
1153 | '-I' + test_file('third_party/poppler/include') | |
1154 | ] | |
1155 | ||
1156 | freetype = self.get_freetype_library() | |
1157 | ||
1158 | # Poppler has some pretty glaring warning. Suppress them to keep the | |
1159 | # test output readable. | |
1160 | if '-Werror' in self.emcc_args: | |
1161 | self.emcc_args.remove('-Werror') | |
1162 | self.emcc_args += [ | |
1163 | '-Wno-sentinel', | |
1164 | '-Wno-logical-not-parentheses', | |
1165 | '-Wno-unused-private-field', | |
1166 | '-Wno-tautological-compare', | |
1167 | '-Wno-unknown-pragmas', | |
1168 | ] | |
1169 | env_init = env_init.copy() if env_init else {} | |
1170 | env_init['FONTCONFIG_CFLAGS'] = ' ' | |
1171 | env_init['FONTCONFIG_LIBS'] = ' ' | |
1172 | ||
1173 | poppler = self.get_library( | |
1174 | os.path.join('third_party', 'poppler'), | |
1175 | [os.path.join('utils', 'pdftoppm.o'), os.path.join('utils', 'parseargs.o'), os.path.join('poppler', '.libs', 'libpoppler.a')], | |
1176 | env_init=env_init, | |
1177 | configure_args=['--disable-libjpeg', '--disable-libpng', '--disable-poppler-qt', '--disable-poppler-qt4', '--disable-cms', '--disable-cairo-output', '--disable-abiword-output', '--disable-shared']) | |
1178 | ||
1179 | return poppler + freetype | |
1180 | ||
1181 | def get_zlib_library(self): | |
1182 | if WINDOWS: | |
1183 | return self.get_library(os.path.join('third_party', 'zlib'), os.path.join('libz.a'), | |
1184 | configure=['cmake', '.'], | |
1185 | make=['cmake', '--build', '.'], | |
1186 | make_args=[]) | |
1187 | return self.get_library(os.path.join('third_party', 'zlib'), os.path.join('libz.a'), make_args=['libz.a']) | |
1188 | ||
1189 | ||
1190 | # Run a server and a web page. When a test runs, we tell the server about it, | |
1191 | # which tells the web page, which then opens a window with the test. Doing | |
1192 | # it this way then allows the page to close() itself when done. | |
1193 | def harness_server_func(in_queue, out_queue, port): | |
1194 | class TestServerHandler(SimpleHTTPRequestHandler): | |
1195 | # Request header handler for default do_GET() path in | |
1196 | # SimpleHTTPRequestHandler.do_GET(self) below. | |
1197 | def send_head(self): | |
1198 | if self.path.endswith('.js'): | |
1199 | path = self.translate_path(self.path) | |
1200 | try: | |
1201 | f = open(path, 'rb') | |
1202 | except IOError: | |
1203 | self.send_error(404, "File not found: " + path) | |
1204 | return None | |
1205 | self.send_response(200) | |
1206 | self.send_header('Content-type', 'application/javascript') | |
1207 | self.send_header('Connection', 'close') | |
1208 | self.end_headers() | |
1209 | return f | |
1210 | else: | |
1211 | return SimpleHTTPRequestHandler.send_head(self) | |
1212 | ||
1213 | # Add COOP, COEP, CORP, and no-caching headers | |
1214 | def end_headers(self): | |
1215 | self.send_header('Access-Control-Allow-Origin', '*') | |
1216 | self.send_header('Cross-Origin-Opener-Policy', 'same-origin') | |
1217 | self.send_header('Cross-Origin-Embedder-Policy', 'require-corp') | |
1218 | self.send_header('Cross-Origin-Resource-Policy', 'cross-origin') | |
1219 | self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate') | |
1220 | return SimpleHTTPRequestHandler.end_headers(self) | |
1221 | ||
1222 | def do_GET(self): | |
1223 | if self.path == '/run_harness': | |
1224 | if DEBUG: | |
1225 | print('[server startup]') | |
1226 | self.send_response(200) | |
1227 | self.send_header('Content-type', 'text/html') | |
1228 | self.end_headers() | |
1229 | self.wfile.write(read_binary(test_file('browser_harness.html'))) | |
1230 | elif 'report_' in self.path: | |
1231 | # the test is reporting its result. first change dir away from the | |
1232 | # test dir, as it will be deleted now that the test is finishing, and | |
1233 | # if we got a ping at that time, we'd return an error | |
1234 | os.chdir(path_from_root()) | |
1235 | # for debugging, tests may encode the result and their own url (window.location) as result|url | |
1236 | if '|' in self.path: | |
1237 | path, url = self.path.split('|', 1) | |
1238 | else: | |
1239 | path = self.path | |
1240 | url = '?' | |
1241 | if DEBUG: | |
1242 | print('[server response:', path, url, ']') | |
1243 | if out_queue.empty(): | |
1244 | out_queue.put(path) | |
1245 | else: | |
1246 | # a badly-behaving test may send multiple xhrs with reported results; we just care | |
1247 | # about the first (if we queued the others, they might be read as responses for | |
1248 | # later tests, or maybe the test sends more than one in a racy manner). | |
1249 | # we place 'None' in the queue here so that the outside knows something went wrong | |
1250 | # (none is not a valid value otherwise; and we need the outside to know because if we | |
1251 | # raise an error in here, it is just swallowed in python's webserver code - we want | |
1252 | # the test to actually fail, which a webserver response can't do). | |
1253 | out_queue.put(None) | |
1254 | raise Exception('browser harness error, excessive response to server - test must be fixed! "%s"' % self.path) | |
1255 | self.send_response(200) | |
1256 | self.send_header('Content-type', 'text/plain') | |
1257 | self.send_header('Cache-Control', 'no-cache, must-revalidate') | |
1258 | self.send_header('Connection', 'close') | |
1259 | self.send_header('Expires', '-1') | |
1260 | self.end_headers() | |
1261 | self.wfile.write(b'OK') | |
1262 | ||
1263 | elif 'stdout=' in self.path or 'stderr=' in self.path or 'exception=' in self.path: | |
1264 | ''' | |
1265 | To get logging to the console from browser tests, add this to | |
1266 | print/printErr/the exception handler in src/shell.html: | |
1267 | ||
1268 | var xhr = new XMLHttpRequest(); | |
1269 | xhr.open('GET', encodeURI('http://localhost:8888?stdout=' + text)); | |
1270 | xhr.send(); | |
1271 | ''' | |
1272 | print('[client logging:', unquote_plus(self.path), ']') | |
1273 | self.send_response(200) | |
1274 | self.send_header('Content-type', 'text/html') | |
1275 | self.end_headers() | |
1276 | elif self.path == '/check': | |
1277 | self.send_response(200) | |
1278 | self.send_header('Content-type', 'text/html') | |
1279 | self.end_headers() | |
1280 | if not in_queue.empty(): | |
1281 | # there is a new test ready to be served | |
1282 | url, dir = in_queue.get() | |
1283 | if DEBUG: | |
1284 | print('[queue command:', url, dir, ']') | |
1285 | assert in_queue.empty(), 'should not be any blockage - one test runs at a time' | |
1286 | assert out_queue.empty(), 'the single response from the last test was read' | |
1287 | # tell the browser to load the test | |
1288 | self.wfile.write(b'COMMAND:' + url.encode('utf-8')) | |
1289 | # move us to the right place to serve the files for the new test | |
1290 | os.chdir(dir) | |
1291 | else: | |
1292 | # the browser must keep polling | |
1293 | self.wfile.write(b'(wait)') | |
1294 | else: | |
1295 | # Use SimpleHTTPServer default file serving operation for GET. | |
1296 | if DEBUG: | |
1297 | print('[simple HTTP serving:', unquote_plus(self.path), ']') | |
1298 | SimpleHTTPRequestHandler.do_GET(self) | |
1299 | ||
1300 | def log_request(code=0, size=0): | |
1301 | # don't log; too noisy | |
1302 | pass | |
1303 | ||
1304 | # allows streaming compilation to work | |
1305 | SimpleHTTPRequestHandler.extensions_map['.wasm'] = 'application/wasm' | |
1306 | ||
1307 | httpd = HTTPServer(('localhost', port), TestServerHandler) | |
1308 | httpd.serve_forever() # test runner will kill us | |
1309 | ||
1310 | ||
1311 | class Reporting(Enum): | |
1312 | """When running browser tests we normally automatically include support | |
1313 | code for reporting results back to the browser. This enum allows tests | |
1314 | to decide what type of support code they need/want. | |
1315 | """ | |
1316 | NONE = 0 | |
1317 | # Include the JS helpers for reporting results | |
1318 | JS_ONLY = 1 | |
1319 | # Include C/C++ reporting code (REPORT_RESULT mactros) as well as JS helpers | |
1320 | FULL = 2 | |
1321 | ||
1322 | ||
1323 | class BrowserCore(RunnerCore): | |
1324 | # note how many tests hang / do not send an output. if many of these | |
1325 | # happen, likely something is broken and it is best to abort the test | |
1326 | # suite early, as otherwise we will wait for the timeout on every | |
1327 | # single test (hundreds of minutes) | |
1328 | MAX_UNRESPONSIVE_TESTS = 10 | |
1329 | ||
1330 | unresponsive_tests = 0 | |
1331 | ||
1332 | def __init__(self, *args, **kwargs): | |
1333 | super().__init__(*args, **kwargs) | |
1334 | ||
1335 | @staticmethod | |
1336 | def browser_open(url): | |
1337 | if not EMTEST_BROWSER: | |
1338 | logger.info('Using default system browser') | |
1339 | webbrowser.open_new(url) | |
1340 | return | |
1341 | ||
1342 | browser_args = shlex.split(EMTEST_BROWSER) | |
1343 | # If the given browser is a scalar, treat it like one of the possible types | |
1344 | # from https://docs.python.org/2/library/webbrowser.html | |
1345 | if len(browser_args) == 1: | |
1346 | try: | |
1347 | # This throws if the type of browser isn't available | |
1348 | webbrowser.get(browser_args[0]).open_new(url) | |
1349 | logger.info('Using Emscripten browser: %s', browser_args[0]) | |
1350 | return | |
1351 | except webbrowser.Error: | |
1352 | # Ignore the exception and fallback to the custom command logic | |
1353 | pass | |
1354 | # Else assume the given browser is a specific program with additional | |
1355 | # parameters and delegate to that | |
1356 | logger.info('Using Emscripten browser: %s', str(browser_args)) | |
1357 | subprocess.Popen(browser_args + [url]) | |
1358 | ||
1359 | @classmethod | |
1360 | def setUpClass(cls): | |
1361 | super().setUpClass() | |
1362 | cls.also_asmjs = int(os.getenv('EMTEST_BROWSER_ALSO_ASMJS', '0')) == 1 | |
1363 | cls.port = int(os.getenv('EMTEST_BROWSER_PORT', '8888')) | |
1364 | if not has_browser(): | |
1365 | return | |
1366 | cls.browser_timeout = 60 | |
1367 | cls.harness_in_queue = multiprocessing.Queue() | |
1368 | cls.harness_out_queue = multiprocessing.Queue() | |
1369 | cls.harness_server = multiprocessing.Process(target=harness_server_func, args=(cls.harness_in_queue, cls.harness_out_queue, cls.port)) | |
1370 | cls.harness_server.start() | |
1371 | print('[Browser harness server on process %d]' % cls.harness_server.pid) | |
1372 | cls.browser_open('http://localhost:%s/run_harness' % cls.port) | |
1373 | ||
1374 | @classmethod | |
1375 | def tearDownClass(cls): | |
1376 | super().tearDownClass() | |
1377 | if not has_browser(): | |
1378 | return | |
1379 | cls.harness_server.terminate() | |
1380 | print('[Browser harness server terminated]') | |
1381 | if WINDOWS: | |
1382 | # On Windows, shutil.rmtree() in tearDown() raises this exception if we do not wait a bit: | |
1383 | # WindowsError: [Error 32] The process cannot access the file because it is being used by another process. | |
1384 | time.sleep(0.1) | |
1385 | ||
1386 | def assert_out_queue_empty(self, who): | |
1387 | if not self.harness_out_queue.empty(): | |
1388 | while not self.harness_out_queue.empty(): | |
1389 | self.harness_out_queue.get() | |
1390 | raise Exception('excessive responses from %s' % who) | |
1391 | ||
1392 | # @param extra_tries: how many more times to try this test, if it fails. browser tests have | |
1393 | # many more causes of flakiness (in particular, they do not run | |
1394 | # synchronously, so we have a timeout, which can be hit if the VM | |
1395 | # we run on stalls temporarily), so we let each test try more than | |
1396 | # once by default | |
1397 | def run_browser(self, html_file, message, expectedResult=None, timeout=None, extra_tries=1): | |
1398 | if not has_browser(): | |
1399 | return | |
1400 | if BrowserCore.unresponsive_tests >= BrowserCore.MAX_UNRESPONSIVE_TESTS: | |
1401 | self.skipTest('too many unresponsive tests, skipping browser launch - check your setup!') | |
1402 | self.assert_out_queue_empty('previous test') | |
1403 | if DEBUG: | |
1404 | print('[browser launch:', html_file, ']') | |
1405 | if expectedResult is not None: | |
1406 | try: | |
1407 | self.harness_in_queue.put(( | |
1408 | 'http://localhost:%s/%s' % (self.port, html_file), | |
1409 | self.get_dir() | |
1410 | )) | |
1411 | received_output = False | |
1412 | output = '[no http server activity]' | |
1413 | start = time.time() | |
1414 | if timeout is None: | |
1415 | timeout = self.browser_timeout | |
1416 | while time.time() - start < timeout: | |
1417 | if not self.harness_out_queue.empty(): | |
1418 | output = self.harness_out_queue.get() | |
1419 | received_output = True | |
1420 | break | |
1421 | time.sleep(0.1) | |
1422 | if not received_output: | |
1423 | BrowserCore.unresponsive_tests += 1 | |
1424 | print('[unresponsive tests: %d]' % BrowserCore.unresponsive_tests) | |
1425 | if output is None: | |
1426 | # the browser harness reported an error already, and sent a None to tell | |
1427 | # us to also fail the test | |
1428 | raise Exception('failing test due to browser harness error') | |
1429 | if output.startswith('/report_result?skipped:'): | |
1430 | self.skipTest(unquote(output[len('/report_result?skipped:'):]).strip()) | |
1431 | else: | |
1432 | # verify the result, and try again if we should do so | |
1433 | output = unquote(output) | |
1434 | try: | |
1435 | self.assertContained(expectedResult, output) | |
1436 | except Exception as e: | |
1437 | if extra_tries > 0: | |
1438 | print('[test error (see below), automatically retrying]') | |
1439 | print(e) | |
1440 | return self.run_browser(html_file, message, expectedResult, timeout, extra_tries - 1) | |
1441 | else: | |
1442 | raise e | |
1443 | finally: | |
1444 | time.sleep(0.1) # see comment about Windows above | |
1445 | self.assert_out_queue_empty('this test') | |
1446 | else: | |
1447 | webbrowser.open_new(os.path.abspath(html_file)) | |
1448 | print('A web browser window should have opened a page containing the results of a part of this test.') | |
1449 | print('You need to manually look at the page to see that it works ok: ' + message) | |
1450 | print('(sleeping for a bit to keep the directory alive for the web browser..)') | |
1451 | time.sleep(5) | |
1452 | print('(moving on..)') | |
1453 | ||
1454 | # @manually_trigger If set, we do not assume we should run the reftest when main() is done. | |
1455 | # Instead, call doReftest() in JS yourself at the right time. | |
1456 | def reftest(self, expected, manually_trigger=False): | |
1457 | # make sure the pngs used here have no color correction, using e.g. | |
1458 | # pngcrush -rem gAMA -rem cHRM -rem iCCP -rem sRGB infile outfile | |
1459 | basename = os.path.basename(expected) | |
1460 | shutil.copyfile(expected, os.path.join(self.get_dir(), basename)) | |
1461 | reporting = read_file(test_file('browser_reporting.js')) | |
1462 | with open('reftest.js', 'w') as out: | |
1463 | out.write(''' | |
1464 | function doReftest() { | |
1465 | if (doReftest.done) return; | |
1466 | doReftest.done = true; | |
1467 | var img = new Image(); | |
1468 | img.onload = function() { | |
1469 | assert(img.width == Module.canvas.width, 'Invalid width: ' + Module.canvas.width + ', should be ' + img.width); | |
1470 | assert(img.height == Module.canvas.height, 'Invalid height: ' + Module.canvas.height + ', should be ' + img.height); | |
1471 | ||
1472 | var canvas = document.createElement('canvas'); | |
1473 | canvas.width = img.width; | |
1474 | canvas.height = img.height; | |
1475 | var ctx = canvas.getContext('2d'); | |
1476 | ctx.drawImage(img, 0, 0); | |
1477 | var expected = ctx.getImageData(0, 0, img.width, img.height).data; | |
1478 | ||
1479 | var actualUrl = Module.canvas.toDataURL(); | |
1480 | var actualImage = new Image(); | |
1481 | actualImage.onload = function() { | |
1482 | /* | |
1483 | document.body.appendChild(img); // for comparisons | |
1484 | var div = document.createElement('div'); | |
1485 | div.innerHTML = '^=expected, v=actual'; | |
1486 | document.body.appendChild(div); | |
1487 | document.body.appendChild(actualImage); // to grab it for creating the test reference | |
1488 | */ | |
1489 | ||
1490 | var actualCanvas = document.createElement('canvas'); | |
1491 | actualCanvas.width = actualImage.width; | |
1492 | actualCanvas.height = actualImage.height; | |
1493 | var actualCtx = actualCanvas.getContext('2d'); | |
1494 | actualCtx.drawImage(actualImage, 0, 0); | |
1495 | var actual = actualCtx.getImageData(0, 0, actualImage.width, actualImage.height).data; | |
1496 | ||
1497 | var total = 0; | |
1498 | var width = img.width; | |
1499 | var height = img.height; | |
1500 | for (var x = 0; x < width; x++) { | |
1501 | for (var y = 0; y < height; y++) { | |
1502 | total += Math.abs(expected[y*width*4 + x*4 + 0] - actual[y*width*4 + x*4 + 0]); | |
1503 | total += Math.abs(expected[y*width*4 + x*4 + 1] - actual[y*width*4 + x*4 + 1]); | |
1504 | total += Math.abs(expected[y*width*4 + x*4 + 2] - actual[y*width*4 + x*4 + 2]); | |
1505 | } | |
1506 | } | |
1507 | var wrong = Math.floor(total / (img.width*img.height*3)); // floor, to allow some margin of error for antialiasing | |
1508 | // If the main JS file is in a worker, or modularize, then we need to supply our own reporting logic. | |
1509 | if (typeof reportResultToServer === 'undefined') { | |
1510 | (function() { | |
1511 | %s | |
1512 | reportResultToServer(wrong); | |
1513 | })(); | |
1514 | } else { | |
1515 | reportResultToServer(wrong); | |
1516 | } | |
1517 | }; | |
1518 | actualImage.src = actualUrl; | |
1519 | } | |
1520 | img.src = '%s'; | |
1521 | }; | |
1522 | ||
1523 | // Automatically trigger the reftest? | |
1524 | if (!%s) { | |
1525 | // Yes, automatically | |
1526 | ||
1527 | Module['postRun'] = doReftest; | |
1528 | ||
1529 | if (typeof WebGLClient !== 'undefined') { | |
1530 | // trigger reftest from RAF as well, needed for workers where there is no pre|postRun on the main thread | |
1531 | var realRAF = window.requestAnimationFrame; | |
1532 | window.requestAnimationFrame = /** @suppress{checkTypes} */ (function(func) { | |
1533 | realRAF(function() { | |
1534 | func(); | |
1535 | realRAF(doReftest); | |
1536 | }); | |
1537 | }); | |
1538 | ||
1539 | // trigger reftest from canvas render too, for workers not doing GL | |
1540 | var realWOM = worker.onmessage; | |
1541 | worker.onmessage = function(event) { | |
1542 | realWOM(event); | |
1543 | if (event.data.target === 'canvas' && event.data.op === 'render') { | |
1544 | realRAF(doReftest); | |
1545 | } | |
1546 | }; | |
1547 | } | |
1548 | ||
1549 | } else { | |
1550 | // Manually trigger the reftest. | |
1551 | ||
1552 | // The user will call it. | |
1553 | // Add an event loop iteration to ensure rendering, so users don't need to bother. | |
1554 | var realDoReftest = doReftest; | |
1555 | doReftest = function() { | |
1556 | setTimeout(realDoReftest, 1); | |
1557 | }; | |
1558 | } | |
1559 | ''' % (reporting, basename, int(manually_trigger))) | |
1560 | ||
1561 | def compile_btest(self, args, reporting=Reporting.FULL): | |
1562 | # Inject support code for reporting results. This adds an include a header so testcases can | |
1563 | # use REPORT_RESULT, and also adds a cpp file to be compiled alongside the testcase, which | |
1564 | # contains the implementation of REPORT_RESULT (we can't just include that implementation in | |
1565 | # the header as there may be multiple files being compiled here). | |
1566 | args += ['-s', 'IN_TEST_HARNESS'] | |
1567 | if reporting != Reporting.NONE: | |
1568 | # For basic reporting we inject JS helper funtions to report result back to server. | |
1569 | args += ['-DEMTEST_PORT_NUMBER=%d' % self.port, | |
1570 | '--pre-js', test_file('browser_reporting.js')] | |
1571 | if reporting == Reporting.FULL: | |
1572 | # If C reporting (i.e. REPORT_RESULT macro) is required | |
1573 | # also compile in report_result.cpp and forice-include report_result.h | |
1574 | args += ['-I' + TEST_ROOT, | |
1575 | '-include', test_file('report_result.h'), | |
1576 | test_file('report_result.cpp')] | |
1577 | self.run_process([EMCC] + self.get_emcc_args() + args) | |
1578 | ||
1579 | def btest_exit(self, filename, assert_returncode=0, *args, **kwargs): | |
1580 | """Special case of btest that reports its result solely via exiting | |
1581 | with a give result code. | |
1582 | ||
1583 | In this case we set EXIT_RUNTIME and we don't need to provide the | |
1584 | REPORT_RESULT macro to the C code. | |
1585 | """ | |
1586 | self.set_setting('EXIT_RUNTIME') | |
1587 | kwargs['reporting'] = Reporting.JS_ONLY | |
1588 | kwargs['expected'] = 'exit:%d' % assert_returncode | |
1589 | return self.btest(filename, *args, **kwargs) | |
1590 | ||
1591 | def btest(self, filename, expected=None, reference=None, | |
1592 | reference_slack=0, manual_reference=False, post_build=None, | |
1593 | args=None, message='.', also_proxied=False, | |
1594 | url_suffix='', timeout=None, also_asmjs=False, | |
1595 | manually_trigger_reftest=False, extra_tries=1, | |
1596 | reporting=Reporting.FULL): | |
1597 | assert expected or reference, 'a btest must either expect an output, or have a reference image' | |
1598 | if args is None: | |
1599 | args = [] | |
1600 | original_args = args.copy() | |
1601 | if not os.path.exists(filename): | |
1602 | filename = test_file(filename) | |
1603 | if reference: | |
1604 | self.reference = reference | |
1605 | expected = [str(i) for i in range(0, reference_slack + 1)] | |
1606 | self.reftest(test_file(reference), manually_trigger=manually_trigger_reftest) | |
1607 | if not manual_reference: | |
1608 | args += ['--pre-js', 'reftest.js', '-s', 'GL_TESTING'] | |
1609 | outfile = 'test.html' | |
1610 | args += [filename, '-o', outfile] | |
1611 | # print('all args:', args) | |
1612 | try_delete(outfile) | |
1613 | self.compile_btest(args, reporting=reporting) | |
1614 | self.assertExists(outfile) | |
1615 | if post_build: | |
1616 | post_build() | |
1617 | if not isinstance(expected, list): | |
1618 | expected = [expected] | |
1619 | self.run_browser(outfile + url_suffix, message, ['/report_result?' + e for e in expected], timeout=timeout, extra_tries=extra_tries) | |
1620 | ||
1621 | # Tests can opt into being run under asmjs as well | |
1622 | if 'WASM=0' not in original_args and (also_asmjs or self.also_asmjs): | |
1623 | print('WASM=0') | |
1624 | self.btest(filename, expected, reference, reference_slack, manual_reference, post_build, | |
1625 | original_args + ['-s', 'WASM=0'], message, also_proxied=False, timeout=timeout) | |
1626 | ||
1627 | if also_proxied: | |
1628 | print('proxied...') | |
1629 | if reference: | |
1630 | assert not manual_reference | |
1631 | manual_reference = True | |
1632 | assert not post_build | |
1633 | post_build = self.post_manual_reftest | |
1634 | # run proxied | |
1635 | self.btest(filename, expected, reference, reference_slack, manual_reference, post_build, | |
1636 | original_args + ['--proxy-to-worker', '-s', 'GL_TESTING'], message, timeout=timeout) | |
1637 | ||
1638 | ||
1639 | ################################################################################################### | |
1640 | ||
1641 | ||
1642 | def build_library(name, | |
1643 | build_dir, | |
1644 | output_dir, | |
1645 | generated_libs, | |
1646 | configure=['sh', './configure'], | |
1647 | make=['make'], | |
1648 | make_args=[], | |
1649 | cache=None, | |
1650 | cache_name=None, | |
1651 | env_init={}, | |
1652 | native=False, | |
1653 | cflags=[]): | |
1654 | """Build a library and cache the result. We build the library file | |
1655 | once and cache it for all our tests. (We cache in memory since the test | |
1656 | directory is destroyed and recreated for each test. Note that we cache | |
1657 | separately for different compilers). This cache is just during the test | |
1658 | runner. There is a different concept of caching as well, see |Cache|. | |
1659 | """ | |
1660 | ||
1661 | if type(generated_libs) is not list: | |
1662 | generated_libs = [generated_libs] | |
1663 | source_dir = test_file(name.replace('_native', '')) | |
1664 | ||
1665 | project_dir = Path(build_dir, name) | |
1666 | if os.path.exists(project_dir): | |
1667 | shutil.rmtree(project_dir) | |
1668 | # Useful in debugging sometimes to comment this out, and two lines above | |
1669 | shutil.copytree(source_dir, project_dir) | |
1670 | ||
1671 | generated_libs = [os.path.join(project_dir, lib) for lib in generated_libs] | |
1672 | if native: | |
1673 | env = clang_native.get_clang_native_env() | |
1674 | else: | |
1675 | env = building.get_building_env(cflags=cflags) | |
1676 | for k, v in env_init.items(): | |
1677 | env[k] = v | |
1678 | if configure: | |
1679 | if configure[0] == 'cmake': | |
1680 | configure = [EMCMAKE] + configure | |
1681 | else: | |
1682 | configure = [EMCONFIGURE] + configure | |
1683 | try: | |
1684 | with open(os.path.join(project_dir, 'configure_out'), 'w') as out: | |
1685 | with open(os.path.join(project_dir, 'configure_err'), 'w') as err: | |
1686 | stdout = out if EM_BUILD_VERBOSE < 2 else None | |
1687 | stderr = err if EM_BUILD_VERBOSE < 1 else None | |
1688 | shared.run_process(configure, env=env, stdout=stdout, stderr=stderr, | |
1689 | cwd=project_dir) | |
1690 | except subprocess.CalledProcessError: | |
1691 | print('-- configure stdout --') | |
1692 | print(read_file(Path(project_dir, 'configure_out'))) | |
1693 | print('-- end configure stdout --') | |
1694 | print('-- configure stderr --') | |
1695 | print(read_file(Path(project_dir, 'configure_err'))) | |
1696 | print('-- end configure stderr --') | |
1697 | raise | |
1698 | ||
1699 | def open_make_out(mode='r'): | |
1700 | return open(os.path.join(project_dir, 'make.out'), mode) | |
1701 | ||
1702 | def open_make_err(mode='r'): | |
1703 | return open(os.path.join(project_dir, 'make.err'), mode) | |
1704 | ||
1705 | if EM_BUILD_VERBOSE >= 3: | |
1706 | make_args += ['VERBOSE=1'] | |
1707 | ||
1708 | try: | |
1709 | with open_make_out('w') as make_out: | |
1710 | with open_make_err('w') as make_err: | |
1711 | stdout = make_out if EM_BUILD_VERBOSE < 2 else None | |
1712 | stderr = make_err if EM_BUILD_VERBOSE < 1 else None | |
1713 | shared.run_process(make + make_args, stdout=stdout, stderr=stderr, env=env, | |
1714 | cwd=project_dir) | |
1715 | except subprocess.CalledProcessError: | |
1716 | with open_make_out() as f: | |
1717 | print('-- make stdout --') | |
1718 | print(f.read()) | |
1719 | print('-- end make stdout --') | |
1720 | with open_make_err() as f: | |
1721 | print('-- make stderr --') | |
1722 | print(f.read()) | |
1723 | print('-- end stderr --') | |
1724 | raise | |
1725 | ||
1726 | if cache is not None: | |
1727 | cache[cache_name] = [] | |
1728 | for f in generated_libs: | |
1729 | basename = os.path.basename(f) | |
1730 | cache[cache_name].append((basename, read_binary(f))) | |
1731 | ||
1732 | return generated_libs | |
1733 | ||
1734 | ||
1735 | 87 | def check_js_engines(): |
1736 | 88 | working_engines = [e for e in config.JS_ENGINES if jsrun.check_engine(e)] |
1737 | 89 | if len(working_engines) < len(config.JS_ENGINES): |
1738 | 90 | print('Not all the JS engines in JS_ENGINES appears to work.') |
1739 | 91 | exit(1) |
1740 | 92 | |
1741 | if EMTEST_ALL_ENGINES: | |
93 | if common.EMTEST_ALL_ENGINES: | |
1742 | 94 | print('(using ALL js engines)') |
1743 | 95 | else: |
1744 | 96 | logger.warning('use EMTEST_ALL_ENGINES=1 in the env to run against all JS ' |
1910 | 262 | |
1911 | 263 | def suite_for_module(module, tests): |
1912 | 264 | suite_supported = module.__name__ in ('test_core', 'test_other', 'test_posixtest') |
1913 | if not EMTEST_SAVE_DIR and not DEBUG: | |
265 | if not common.EMTEST_SAVE_DIR and not shared.DEBUG: | |
1914 | 266 | has_multiple_tests = len(tests) > 1 |
1915 | 267 | has_multiple_cores = parallel_testsuite.num_cores() > 1 |
1916 | 268 | if suite_supported and has_multiple_tests and has_multiple_cores: |
1931 | 283 | res = testRunner.run(suite) |
1932 | 284 | msg = ('%s: %s run, %s errors, %s failures, %s skipped' % |
1933 | 285 | (mod_name, res.testsRun, len(res.errors), len(res.failures), len(res.skipped))) |
1934 | num_failures += len(res.errors) + len(res.failures) | |
286 | num_failures += len(res.errors) + len(res.failures) + len(res.unexpectedSuccesses) | |
1935 | 287 | resultMessages.append(msg) |
1936 | 288 | |
1937 | 289 | if len(resultMessages) > 1: |
1941 | 293 | for msg in resultMessages: |
1942 | 294 | print(' ' + msg) |
1943 | 295 | |
1944 | # Return the number of failures as the process exit code for automating success/failure reporting. | |
1945 | return min(num_failures, 255) | |
296 | return num_failures | |
1946 | 297 | |
1947 | 298 | |
1948 | 299 | def parse_args(args): |
1949 | 300 | parser = argparse.ArgumentParser(prog='runner.py', description=__doc__) |
301 | parser.add_argument('--save-dir', action='store_true', default='EMTEST_SAVE_DIR' in os.environ, | |
302 | help='Save the temporary directory used during for each ' | |
303 | 'test. Implies --cores=1.') | |
304 | parser.add_argument('--no-clean', action='store_true', | |
305 | help='Do not clean the temporary directory before each test run') | |
306 | parser.add_argument('--verbose', action='store_true') | |
307 | parser.add_argument('--all-engines', action='store_true') | |
308 | parser.add_argument('--detect-leaks', action='store_true') | |
309 | parser.add_argument('--skip-slow', action='store_true', help='Skip tests marked as slow') | |
310 | parser.add_argument('--cores', | |
311 | help='Set the number tests to run in parallel. Defaults ' | |
312 | 'to the number of CPU cores.') | |
313 | parser.add_argument('--rebaseline', action='store_true', | |
314 | help='Automatically update test expectations for tests that support it.') | |
315 | parser.add_argument('--browser', | |
316 | help='Command to launch web browser in which to run browser tests.') | |
1950 | 317 | parser.add_argument('tests', nargs='*') |
1951 | 318 | return parser.parse_args() |
1952 | 319 | |
1953 | 320 | |
321 | def configure(): | |
322 | common.EMTEST_BROWSER = os.getenv('EMTEST_BROWSER') | |
323 | common.EMTEST_DETECT_TEMPFILE_LEAKS = int(os.getenv('EMTEST_DETECT_TEMPFILE_LEAKS', '0')) | |
324 | common.EMTEST_SAVE_DIR = int(os.getenv('EMTEST_SAVE_DIR', '0')) | |
325 | common.EMTEST_ALL_ENGINES = int(os.getenv('EMTEST_ALL_ENGINES', '0')) | |
326 | common.EMTEST_SKIP_SLOW = int(os.getenv('EMTEST_SKIP_SLOW', '0')) | |
327 | common.EMTEST_LACKS_NATIVE_CLANG = int(os.getenv('EMTEST_LACKS_NATIVE_CLANG', '0')) | |
328 | common.EMTEST_REBASELINE = int(os.getenv('EMTEST_REBASELINE', '0')) | |
329 | common.EMTEST_VERBOSE = int(os.getenv('EMTEST_VERBOSE', '0')) or shared.DEBUG | |
330 | ||
331 | assert 'PARALLEL_SUITE_EMCC_CORES' not in os.environ, 'use EMTEST_CORES rather than PARALLEL_SUITE_EMCC_CORES' | |
332 | parallel_testsuite.NUM_CORES = os.environ.get('EMTEST_CORES') or os.environ.get('EMCC_CORES') | |
333 | ||
334 | ||
1954 | 335 | def main(args): |
1955 | 336 | options = parse_args(args) |
337 | ||
338 | # We set the environments variables here and then call configure, | |
339 | # to apply them. This means the python's multiprocessing child | |
340 | # process will see the same configuration even though they don't | |
341 | # parse the command line. | |
342 | def set_env(name, option_value): | |
343 | if option_value is None: | |
344 | return | |
345 | if option_value is False: | |
346 | value = '0' | |
347 | elif option_value is True: | |
348 | value = '1' | |
349 | else: | |
350 | value = str(option_value) | |
351 | os.environ[name] = value | |
352 | ||
353 | set_env('EMTEST_BROWSER', options.browser) | |
354 | set_env('EMTEST_DETECT_TEMPFILE_LEAKS', options.detect_leaks) | |
355 | set_env('EMTEST_SAVE_DIR', options.save_dir) | |
356 | if options.no_clean: | |
357 | set_env('EMTEST_SAVE_DIR', 2) | |
358 | else: | |
359 | set_env('EMTEST_SAVE_DIR', options.save_dir) | |
360 | set_env('EMTEST_SKIP_SLOW', options.skip_slow) | |
361 | set_env('EMTEST_ALL_ENGINES', options.all_engines) | |
362 | set_env('EMTEST_REBASELINE', options.rebaseline) | |
363 | set_env('EMTEST_VERBOSE', options.verbose) | |
364 | set_env('EMTEST_CORES', options.cores) | |
365 | ||
366 | configure() | |
367 | ||
1956 | 368 | check_js_engines() |
1957 | 369 | |
1958 | 370 | def prepend_default(arg): |
1972 | 384 | print('ERROR: could not find the following tests: ' + ' '.join(unmatched_tests)) |
1973 | 385 | return 1 |
1974 | 386 | |
1975 | return run_tests(options, suites) | |
1976 | ||
387 | num_failures = run_tests(options, suites) | |
388 | # Return the number of failures as the process exit code | |
389 | # for automating success/failure reporting. Return codes | |
390 | # over 125 are not well supported on UNIX. | |
391 | return min(num_failures, 125) | |
392 | ||
393 | ||
394 | configure() | |
1977 | 395 | |
1978 | 396 | if __name__ == '__main__': |
1979 | 397 | try: |
0 | // Copyright 2015 The Emscripten Authors. All rights reserved. | |
1 | // Emscripten is available under two separate licenses, the MIT license and the | |
2 | // University of Illinois/NCSA Open Source License. Both these licenses can be | |
3 | // found in the LICENSE file. | |
4 | ||
5 | #include <stdio.h> | |
6 | #include <stdlib.h> | |
7 | #include <signal.h> | |
8 | #include <unistd.h> | |
9 | #include <pthread.h> | |
10 | ||
11 | void alarm_handler(int dummy) | |
12 | { | |
13 | printf("Received alarm!\n"); | |
14 | #ifdef REPORT_RESULT | |
15 | REPORT_RESULT(0); | |
16 | #endif | |
17 | exit(0); | |
18 | } | |
19 | ||
20 | int main() | |
21 | { | |
22 | if (signal(SIGALRM, alarm_handler) == SIG_ERR) | |
23 | { | |
24 | printf("Error in signal()!\n"); | |
25 | #ifdef REPORT_RESULT | |
26 | REPORT_RESULT(1); | |
27 | #endif | |
28 | exit(1); | |
29 | } | |
30 | alarm(5); | |
31 | } |
27 | 27 | assert(errno == ENOSYS); |
28 | 28 | assert(system("true") == -1); |
29 | 29 | assert(errno == ENOSYS); |
30 | #ifdef REPORT_RESULT | |
31 | REPORT_RESULT(0); | |
32 | #endif | |
33 | 30 | #endif |
34 | 31 | return 0; |
35 | 32 | } |
17 | 17 | |
18 | 18 | import clang_native |
19 | 19 | import jsrun |
20 | import runner | |
21 | from runner import TEST_ROOT, test_file, read_file, read_binary | |
20 | import common | |
21 | from common import TEST_ROOT, test_file, read_file, read_binary | |
22 | 22 | from tools.shared import run_process, PIPE, try_delete, EMCC, config |
23 | 23 | from tools import building |
24 | 24 | |
388 | 388 | ] |
389 | 389 | |
390 | 390 | |
391 | class benchmark(runner.RunnerCore): | |
391 | class benchmark(common.RunnerCore): | |
392 | 392 | save_dir = True |
393 | 393 | |
394 | 394 | @classmethod |
404 | 404 | except Exception: |
405 | 405 | pass |
406 | 406 | try: |
407 | with runner.chdir(os.path.expanduser('~/Dev/mozilla-central')): | |
407 | with common.chdir(os.path.expanduser('~/Dev/mozilla-central')): | |
408 | 408 | fingerprint.append('sm: ' + [line for line in run_process(['hg', 'tip'], stdout=PIPE).stdout.splitlines() if 'changeset' in line][0]) |
409 | 409 | except Exception: |
410 | 410 | pass |
19 | 19 | from pathlib import Path |
20 | 20 | from urllib.request import urlopen |
21 | 21 | |
22 | from runner import BrowserCore, RunnerCore, path_from_root, has_browser, EMTEST_BROWSER, Reporting | |
23 | from runner import create_file, parameterized, ensure_dir, disabled, test_file, WEBIDL_BINDER | |
24 | from runner import read_file | |
22 | from common import BrowserCore, RunnerCore, path_from_root, has_browser, EMTEST_BROWSER, Reporting | |
23 | from common import create_file, parameterized, ensure_dir, disabled, test_file, WEBIDL_BINDER, EMMAKE | |
24 | from common import read_file, require_v8 | |
25 | 25 | from tools import shared |
26 | 26 | from tools import system_libs |
27 | 27 | from tools.shared import EMCC, WINDOWS, FILE_PACKAGER, PIPE |
28 | from tools.shared import try_delete, config | |
28 | from tools.shared import try_delete | |
29 | 29 | |
30 | 30 | |
31 | 31 | def test_chunked_synchronous_xhr_server(support_byte_ranges, chunkSize, data, checksum, port): |
425 | 425 | self.compile_btest([cpp, '--pre-js', data_js_file, '-o', abs_page_file, '-s', 'FORCE_FILESYSTEM']) |
426 | 426 | self.run_browser(page_file, '|load me right before|.', '/report_result?0') |
427 | 427 | |
428 | def test_preload_caching(self): | |
428 | @parameterized({ | |
429 | '0': (0,), | |
430 | '1mb': (1 * 1024 * 1024,), | |
431 | '100mb': (100 * 1024 * 1024,), | |
432 | '150mb': (150 * 1024 * 1024,), | |
433 | }) | |
434 | def test_preload_caching(self, extra_size): | |
429 | 435 | create_file('main.cpp', r''' |
430 | 436 | #include <stdio.h> |
431 | 437 | #include <string.h> |
472 | 478 | # chrome's limit on IndexedDB item sizes, see |
473 | 479 | # https://cs.chromium.org/chromium/src/content/renderer/indexed_db/webidbdatabase_impl.cc?type=cs&q=%22The+serialized+value+is+too+large%22&sq=package:chromium&g=0&l=177 |
474 | 480 | # https://cs.chromium.org/chromium/src/out/Debug/gen/third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h?type=cs&sq=package:chromium&g=0&l=60 |
475 | for extra_size in (0, 1 * 1024 * 1024, 100 * 1024 * 1024, 150 * 1024 * 1024): | |
476 | if is_chrome() and extra_size >= 100 * 1024 * 1024: | |
477 | continue | |
478 | create_file('somefile.txt', '''load me right before running the code please''' + ('_' * extra_size)) | |
479 | print('size:', os.path.getsize('somefile.txt')) | |
480 | self.compile_btest(['main.cpp', '--use-preload-cache', '--js-library', 'test.js', '--preload-file', 'somefile.txt', '-o', 'page.html', '-s', 'ALLOW_MEMORY_GROWTH']) | |
481 | self.run_browser('page.html', 'You should see |load me right before|.', '/report_result?1') | |
482 | self.run_browser('page.html', 'You should see |load me right before|.', '/report_result?2') | |
481 | if is_chrome() and extra_size >= 100 * 1024 * 1024: | |
482 | self.skipTest('chrome bug') | |
483 | create_file('somefile.txt', '''load me right before running the code please''' + ('_' * extra_size)) | |
484 | print('size:', os.path.getsize('somefile.txt')) | |
485 | self.compile_btest(['main.cpp', '--use-preload-cache', '--js-library', 'test.js', '--preload-file', 'somefile.txt', '-o', 'page.html', '-s', 'ALLOW_MEMORY_GROWTH']) | |
486 | self.run_browser('page.html', 'You should see |load me right before|.', '/report_result?1') | |
487 | self.run_browser('page.html', 'You should see |load me right before|.', '/report_result?2') | |
483 | 488 | |
484 | 489 | def test_preload_caching_indexeddb_name(self): |
485 | 490 | create_file('somefile.txt', '''load me right before running the code please''') |
718 | 723 | shutil.copyfile(test_file('screenshot.jpg'), 'screenshot.not') |
719 | 724 | self.btest('sdl_image_prepare.c', reference='screenshot.jpg', args=['--preload-file', 'screenshot.not', '-lSDL', '-lGL'], also_proxied=True, manually_trigger_reftest=True) |
720 | 725 | |
721 | def test_sdl_image_prepare_data(self): | |
726 | @parameterized({ | |
727 | '': ([],), | |
728 | # add testing for closure on preloaded files + ENVIRONMENT=web (we must not | |
729 | # emit any node.js code here, see | |
730 | # https://github.com/emscripten-core/emscripten/issues/14486 | |
731 | 'closure_webonly': (['--closure', '1', '-s', 'ENVIRONMENT=web'],) | |
732 | }) | |
733 | def test_sdl_image_prepare_data(self, args): | |
722 | 734 | # load an image file, get pixel data. |
723 | 735 | shutil.copyfile(test_file('screenshot.jpg'), 'screenshot.not') |
724 | self.btest('sdl_image_prepare_data.c', reference='screenshot.jpg', args=['--preload-file', 'screenshot.not', '-lSDL', '-lGL'], manually_trigger_reftest=True) | |
736 | self.btest('sdl_image_prepare_data.c', reference='screenshot.jpg', args=['--preload-file', 'screenshot.not', '-lSDL', '-lGL'] + args, manually_trigger_reftest=True) | |
725 | 737 | |
726 | 738 | def test_sdl_image_must_prepare(self): |
727 | 739 | # load an image file, get pixel data. |
1746 | 1758 | Path('Chapter_9/TextureWrap', 'CH09_TextureWrap.o'), |
1747 | 1759 | Path('Chapter_10/MultiTexture', 'CH10_MultiTexture.o'), |
1748 | 1760 | Path('Chapter_13/ParticleSystem', 'CH13_ParticleSystem.o'), |
1749 | ], configure=None) | |
1761 | ], configure=None, make=[EMMAKE, 'make']) | |
1750 | 1762 | |
1751 | 1763 | def book_path(*pathelems): |
1752 | 1764 | return test_file('glbook', *pathelems) |
1804 | 1816 | self.btest('clientside_vertex_arrays_es3.c', reference='gl_triangle.png', args=['-s', 'FULL_ES3=1', '-s', 'USE_GLFW=3', '-lglfw', '-lGLESv2']) |
1805 | 1817 | |
1806 | 1818 | def test_emscripten_api(self): |
1807 | self.btest('emscripten_api_browser.cpp', '1', args=['-s', 'EXPORTED_FUNCTIONS=_main,_third', '-lSDL']) | |
1819 | self.btest_exit('emscripten_api_browser.c', args=['-s', 'EXPORTED_FUNCTIONS=_main,_third', '-lSDL']) | |
1808 | 1820 | |
1809 | 1821 | def test_emscripten_api2(self): |
1810 | 1822 | def setup(): |
1816 | 1828 | |
1817 | 1829 | setup() |
1818 | 1830 | self.run_process([FILE_PACKAGER, 'test.data', '--preload', 'file1.txt', 'file2.txt'], stdout=open('script2.js', 'w')) |
1819 | self.btest('emscripten_api_browser2.cpp', '1', args=['-s', 'EXPORTED_FUNCTIONS=_main,_set', '-s', 'FORCE_FILESYSTEM']) | |
1831 | self.btest_exit('emscripten_api_browser2.c', args=['-s', 'EXPORTED_FUNCTIONS=_main,_set', '-s', 'FORCE_FILESYSTEM']) | |
1820 | 1832 | |
1821 | 1833 | # check using file packager to another dir |
1822 | 1834 | self.clear() |
1824 | 1836 | ensure_dir('sub') |
1825 | 1837 | self.run_process([FILE_PACKAGER, 'sub/test.data', '--preload', 'file1.txt', 'file2.txt'], stdout=open('script2.js', 'w')) |
1826 | 1838 | shutil.copyfile(Path('sub/test.data'), 'test.data') |
1827 | self.btest('emscripten_api_browser2.cpp', '1', args=['-s', 'EXPORTED_FUNCTIONS=_main,_set', '-s', 'FORCE_FILESYSTEM']) | |
1839 | self.btest_exit('emscripten_api_browser2.c', args=['-s', 'EXPORTED_FUNCTIONS=_main,_set', '-s', 'FORCE_FILESYSTEM']) | |
1828 | 1840 | |
1829 | 1841 | def test_emscripten_api_infloop(self): |
1830 | self.btest('emscripten_api_browser_infloop.cpp', '7') | |
1842 | self.btest_exit('emscripten_api_browser_infloop.cpp', assert_returncode=7) | |
1831 | 1843 | |
1832 | 1844 | def test_emscripten_fs_api(self): |
1833 | 1845 | shutil.copyfile(test_file('screenshot.png'), 'screenshot.png') # preloaded *after* run |
1840 | 1852 | @requires_threads |
1841 | 1853 | def test_emscripten_main_loop(self): |
1842 | 1854 | for args in [[], ['-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD', '-s', 'EXIT_RUNTIME']]: |
1843 | self.btest('emscripten_main_loop.cpp', '0', args=args) | |
1855 | self.btest_exit('emscripten_main_loop.cpp', args=args) | |
1844 | 1856 | |
1845 | 1857 | @requires_threads |
1846 | 1858 | def test_emscripten_main_loop_settimeout(self): |
1847 | 1859 | for args in [ |
1848 | 1860 | [], |
1849 | 1861 | # test pthreads + AUTO_JS_LIBRARIES mode as well |
1850 | ['-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD', '-s', 'AUTO_JS_LIBRARIES=0'] | |
1862 | ['-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD', '-s', 'AUTO_JS_LIBRARIES=0'], | |
1851 | 1863 | ]: |
1852 | self.btest('emscripten_main_loop_settimeout.cpp', '1', args=args) | |
1864 | self.btest_exit('emscripten_main_loop_settimeout.cpp', args=args) | |
1853 | 1865 | |
1854 | 1866 | @requires_threads |
1855 | 1867 | def test_emscripten_main_loop_and_blocker(self): |
1856 | 1868 | for args in [[], ['-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD']]: |
1857 | self.btest('emscripten_main_loop_and_blocker.cpp', '0', args=args) | |
1869 | self.btest_exit('emscripten_main_loop_and_blocker.cpp', args=args) | |
1858 | 1870 | |
1859 | 1871 | @requires_threads |
1860 | 1872 | def test_emscripten_main_loop_and_blocker_exit(self): |
1861 | 1873 | # Same as above but tests that EXIT_RUNTIME works with emscripten_main_loop. The |
1862 | 1874 | # app should still stay alive until the loop ends |
1863 | self.btest_exit('emscripten_main_loop_and_blocker.cpp', 0) | |
1875 | self.btest_exit('emscripten_main_loop_and_blocker.cpp') | |
1864 | 1876 | |
1865 | 1877 | @requires_threads |
1866 | 1878 | def test_emscripten_main_loop_setimmediate(self): |
1867 | 1879 | for args in [[], ['--proxy-to-worker'], ['-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD']]: |
1868 | self.btest('emscripten_main_loop_setimmediate.cpp', '1', args=args) | |
1880 | self.btest_exit('emscripten_main_loop_setimmediate.cpp', args=args) | |
1869 | 1881 | |
1870 | 1882 | def test_fs_after_main(self): |
1871 | 1883 | for args in [[], ['-O1']]: |
2220 | 2232 | |
2221 | 2233 | def test_runtimelink(self): |
2222 | 2234 | create_file('header.h', r''' |
2223 | struct point | |
2224 | { | |
2235 | struct point { | |
2225 | 2236 | int x, y; |
2226 | 2237 | }; |
2227 | 2238 | ''') |
2228 | 2239 | |
2229 | create_file('supp.cpp', r''' | |
2240 | create_file('supp.c', r''' | |
2230 | 2241 | #include <stdio.h> |
2231 | 2242 | #include "header.h" |
2232 | 2243 | |
2233 | 2244 | extern void mainFunc(int x); |
2234 | 2245 | extern int mainInt; |
2235 | 2246 | |
2236 | void suppFunc(struct point &p) { | |
2237 | printf("supp: %d,%d\n", p.x, p.y); | |
2238 | mainFunc(p.x + p.y); | |
2247 | void suppFunc(struct point *p) { | |
2248 | printf("supp: %d,%d\n", p->x, p->y); | |
2249 | mainFunc(p->x + p->y); | |
2239 | 2250 | printf("supp see: %d\n", mainInt); |
2240 | 2251 | } |
2241 | 2252 | |
2242 | 2253 | int suppInt = 76; |
2243 | 2254 | ''') |
2244 | 2255 | |
2245 | create_file('main.cpp', r''' | |
2256 | create_file('main.c', r''' | |
2246 | 2257 | #include <stdio.h> |
2258 | #include <assert.h> | |
2247 | 2259 | #include "header.h" |
2248 | 2260 | |
2249 | extern void suppFunc(struct point &p); | |
2261 | extern void suppFunc(struct point *p); | |
2250 | 2262 | extern int suppInt; |
2251 | 2263 | |
2252 | 2264 | void mainFunc(int x) { |
2253 | 2265 | printf("main: %d\n", x); |
2266 | assert(x == 56); | |
2254 | 2267 | } |
2255 | 2268 | |
2256 | 2269 | int mainInt = 543; |
2257 | 2270 | |
2258 | 2271 | int main( int argc, const char *argv[] ) { |
2259 | 2272 | struct point p = { 54, 2 }; |
2260 | suppFunc(p); | |
2273 | suppFunc(&p); | |
2261 | 2274 | printf("main see: %d\nok.\n", suppInt); |
2262 | return suppInt; | |
2275 | assert(suppInt == 76); | |
2276 | return 0; | |
2263 | 2277 | } |
2264 | 2278 | ''') |
2265 | self.run_process([EMCC, 'supp.cpp', '-o', 'supp.wasm', '-s', 'SIDE_MODULE', '-O2', '-s', 'EXPORT_ALL']) | |
2266 | self.btest_exit('main.cpp', args=['-DBROWSER=1', '-s', 'MAIN_MODULE', '-O2', 'supp.wasm', '-s', 'EXPORT_ALL'], assert_returncode=76) | |
2279 | self.run_process([EMCC, 'supp.c', '-o', 'supp.wasm', '-s', 'SIDE_MODULE', '-O2']) | |
2280 | self.btest_exit('main.c', args=['-s', 'MAIN_MODULE=2', '-O2', 'supp.wasm']) | |
2267 | 2281 | |
2268 | 2282 | def test_pre_run_deps(self): |
2269 | 2283 | # Adding a dependency in preRun will delay run |
2457 | 2471 | time.sleep(10) |
2458 | 2472 | |
2459 | 2473 | def test_emscripten_async_wget_side_module(self): |
2460 | self.run_process([EMCC, test_file('browser_module.cpp'), '-o', 'lib.wasm', '-O2', '-s', 'SIDE_MODULE', '-s', 'EXPORTED_FUNCTIONS=_one,_two']) | |
2461 | self.btest_exit('browser_main.cpp', args=['-O2', '-s', 'MAIN_MODULE'], assert_returncode=8) | |
2474 | self.run_process([EMCC, test_file('browser_module.c'), '-o', 'lib.wasm', '-O2', '-s', 'SIDE_MODULE']) | |
2475 | self.btest_exit('browser_main.c', args=['-O2', '-s', 'MAIN_MODULE=2']) | |
2462 | 2476 | |
2463 | 2477 | @parameterized({ |
2464 | 2478 | 'non-lz4': ([],), |
2471 | 2485 | return 42; |
2472 | 2486 | } |
2473 | 2487 | ''') |
2474 | self.run_process([EMCC, 'library.c', '-s', 'SIDE_MODULE', '-O2', '-o', 'library.so', '-s', 'EXPORT_ALL']) | |
2488 | self.run_process([EMCC, 'library.c', '-s', 'SIDE_MODULE', '-O2', '-o', 'library.so']) | |
2475 | 2489 | create_file('main.c', r''' |
2476 | 2490 | #include <dlfcn.h> |
2477 | 2491 | #include <stdio.h> |
2497 | 2511 | ''') |
2498 | 2512 | self.btest_exit( |
2499 | 2513 | 'main.c', |
2500 | args=['-s', 'MAIN_MODULE', '--preload-file', '.@/', '-O2', '--use-preload-plugins', '-s', 'EXPORT_ALL'] + args) | |
2514 | args=['-s', 'MAIN_MODULE=2', '--preload-file', '.@/', '-O2', '--use-preload-plugins'] + args) | |
2501 | 2515 | |
2502 | 2516 | def test_mmap_file(self): |
2503 | 2517 | create_file('data.dat', 'data from the file ' + ('.' * 9000)) |
2607 | 2621 | self.btest(test_file('webgl_color_buffer_readpixels.cpp'), args=['-lGL'], expected='0') |
2608 | 2622 | |
2609 | 2623 | # Test for PR#5373 (https://github.com/emscripten-core/emscripten/pull/5373) |
2624 | @requires_graphics_hardware | |
2610 | 2625 | def test_webgl_shader_source_length(self): |
2611 | 2626 | for opts in [[], ['-s', 'FULL_ES2=1']]: |
2612 | 2627 | print(opts) |
2613 | 2628 | self.btest(test_file('webgl_shader_source_length.cpp'), args=opts + ['-lGL'], expected='0') |
2614 | 2629 | |
2615 | 2630 | # Tests calling glGetString(GL_UNMASKED_VENDOR_WEBGL). |
2631 | @requires_graphics_hardware | |
2616 | 2632 | def test_webgl_unmasked_vendor_webgl(self): |
2617 | 2633 | self.btest(test_file('webgl_unmasked_vendor_webgl.c'), args=['-lGL'], expected='0') |
2618 | 2634 | |
2635 | @requires_graphics_hardware | |
2619 | 2636 | def test_webgl2(self): |
2620 | 2637 | for opts in [ |
2621 | 2638 | ['-s', 'MIN_CHROME_VERSION=0'], |
2637 | 2654 | # (the testcase doesn't even use threads, but is compiled with thread support). |
2638 | 2655 | self.btest(test_file('webgl2.cpp'), args=['-s', 'MAX_WEBGL_VERSION=2', '-lGL', '-s', 'USE_PTHREADS'], expected='0') |
2639 | 2656 | |
2657 | @requires_graphics_hardware | |
2640 | 2658 | def test_webgl2_objects(self): |
2641 | 2659 | self.btest(test_file('webgl2_objects.cpp'), args=['-s', 'MAX_WEBGL_VERSION=2', '-lGL'], expected='0') |
2642 | 2660 | |
2661 | @requires_graphics_hardware | |
2643 | 2662 | def test_html5_webgl_api(self): |
2644 | 2663 | for mode in [['-s', 'OFFSCREENCANVAS_SUPPORT', '-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD'], |
2645 | 2664 | ['-s', 'OFFSCREEN_FRAMEBUFFER', '-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD'], |
2648 | 2667 | continue |
2649 | 2668 | self.btest(test_file('html5_webgl.c'), args=['-s', 'MAX_WEBGL_VERSION=2', '-lGL'] + mode, expected='0') |
2650 | 2669 | |
2670 | @requires_graphics_hardware | |
2651 | 2671 | def test_webgl2_ubos(self): |
2652 | 2672 | self.btest(test_file('webgl2_ubos.cpp'), args=['-s', 'MAX_WEBGL_VERSION=2', '-lGL'], expected='0') |
2653 | 2673 | |
2718 | 2738 | |
2719 | 2739 | def test_wget(self): |
2720 | 2740 | create_file('test.txt', 'emscripten') |
2721 | self.btest(test_file('test_wget.c'), expected='1', args=['-s', 'ASYNCIFY']) | |
2741 | self.btest_exit(test_file('test_wget.c'), args=['-s', 'ASYNCIFY']) | |
2722 | 2742 | |
2723 | 2743 | def test_wget_data(self): |
2724 | 2744 | create_file('test.txt', 'emscripten') |
2725 | self.btest(test_file('test_wget_data.c'), expected='1', args=['-O2', '-g2', '-s', 'ASYNCIFY']) | |
2745 | self.btest_exit(test_file('test_wget_data.c'), args=['-O2', '-g2', '-s', 'ASYNCIFY']) | |
2726 | 2746 | |
2727 | 2747 | @parameterized({ |
2728 | 2748 | '': ([],), |
3208 | 3228 | def test_sdl2_misc(self): |
3209 | 3229 | self.btest_exit('sdl2_misc.c', args=['-s', 'USE_SDL=2']) |
3210 | 3230 | |
3211 | @disabled('https://github.com/emscripten-core/emscripten/issues/13101') | |
3212 | 3231 | def test_sdl2_misc_main_module(self): |
3213 | 3232 | self.btest_exit('sdl2_misc.c', args=['-s', 'USE_SDL=2', '-s', 'MAIN_MODULE']) |
3214 | 3233 | |
3472 | 3491 | |
3473 | 3492 | @requires_sync_compilation |
3474 | 3493 | def test_dynamic_link(self): |
3475 | create_file('main.cpp', r''' | |
3494 | create_file('main.c', r''' | |
3476 | 3495 | #include <stdio.h> |
3477 | 3496 | #include <stdlib.h> |
3478 | 3497 | #include <string.h> |
3492 | 3511 | }); |
3493 | 3512 | puts(ret); |
3494 | 3513 | EM_ASM({ assert(Module.printed === 'hello through side', ['expected', Module.printed]); }); |
3495 | REPORT_RESULT(2); | |
3496 | 3514 | return 0; |
3497 | 3515 | } |
3498 | 3516 | ''') |
3499 | create_file('side.cpp', r''' | |
3517 | create_file('side.c', r''' | |
3500 | 3518 | #include <stdlib.h> |
3501 | 3519 | #include <string.h> |
3502 | 3520 | char *side(const char *data); |
3506 | 3524 | return ret; |
3507 | 3525 | } |
3508 | 3526 | ''') |
3509 | self.run_process([EMCC, 'side.cpp', '-s', 'SIDE_MODULE', '-O2', '-o', 'side.wasm', '-s', 'EXPORT_ALL']) | |
3510 | self.btest(self.in_dir('main.cpp'), '2', args=['-s', 'MAIN_MODULE', '-O2', '-s', 'EXPORT_ALL', 'side.wasm']) | |
3527 | self.run_process([EMCC, 'side.c', '-s', 'SIDE_MODULE', '-O2', '-o', 'side.wasm']) | |
3528 | self.btest_exit(self.in_dir('main.c'), args=['-s', 'MAIN_MODULE=2', '-O2', 'side.wasm']) | |
3511 | 3529 | |
3512 | 3530 | print('wasm in worker (we can read binary data synchronously there)') |
3513 | 3531 | |
3514 | self.run_process([EMCC, 'side.cpp', '-s', 'SIDE_MODULE', '-O2', '-o', 'side.wasm', '-s', 'EXPORT_ALL']) | |
3515 | self.btest(self.in_dir('main.cpp'), '2', args=['-s', 'MAIN_MODULE', '-O2', '--proxy-to-worker', '-s', 'EXPORT_ALL', 'side.wasm']) | |
3532 | self.run_process([EMCC, 'side.c', '-s', 'SIDE_MODULE', '-O2', '-o', 'side.wasm']) | |
3533 | self.btest_exit(self.in_dir('main.c'), args=['-s', 'MAIN_MODULE=2', '-O2', '--proxy-to-worker', 'side.wasm']) | |
3516 | 3534 | |
3517 | 3535 | print('wasm (will auto-preload since no sync binary reading)') |
3518 | 3536 | |
3519 | 3537 | # same wasm side module works |
3520 | self.btest(self.in_dir('main.cpp'), '2', args=['-s', 'MAIN_MODULE', '-O2', '-s', 'EXPORT_ALL', 'side.wasm']) | |
3538 | self.btest_exit(self.in_dir('main.c'), args=['-s', 'MAIN_MODULE=2', '-O2', '-s', 'EXPORT_ALL', 'side.wasm']) | |
3539 | ||
3540 | def test_dlopen_blocking(self): | |
3541 | create_file('side.c', 'int foo = 42;\n') | |
3542 | self.run_process([EMCC, 'side.c', '-o', 'libside.so', '-s', 'SIDE_MODULE', '-s', 'USE_PTHREADS', '-Wno-experimental']) | |
3543 | # Attempt to use dlopen without preloading the side module should fail on the main thread | |
3544 | # since the syncronous `readBinary` function does not exist. | |
3545 | self.btest_exit(test_file('other/test_dlopen_blocking.c'), assert_returncode=1, args=['-s', 'MAIN_MODULE=2']) | |
3546 | # But with PROXY_TO_PTHEAD it does work, since we can do blocking and sync XHR in a worker. | |
3547 | self.btest_exit(test_file('other/test_dlopen_blocking.c'), args=['-s', 'MAIN_MODULE=2', '-s', 'PROXY_TO_PTHREAD', '-s', 'USE_PTHREADS', '-Wno-experimental']) | |
3521 | 3548 | |
3522 | 3549 | # verify that dynamic linking works in all kinds of in-browser environments. |
3523 | 3550 | # don't mix different kinds in a single test. |
3566 | 3593 | @requires_graphics_hardware |
3567 | 3594 | @requires_sync_compilation |
3568 | 3595 | def test_dynamic_link_glemu(self): |
3569 | create_file('main.cpp', r''' | |
3596 | create_file('main.c', r''' | |
3570 | 3597 | #include <stdio.h> |
3571 | 3598 | #include <string.h> |
3572 | 3599 | #include <assert.h> |
3579 | 3606 | return 0; |
3580 | 3607 | } |
3581 | 3608 | ''') |
3582 | create_file('side.cpp', r''' | |
3609 | create_file('side.c', r''' | |
3583 | 3610 | #include "SDL/SDL.h" |
3584 | 3611 | #include "SDL/SDL_opengl.h" |
3585 | 3612 | const char *side() { |
3588 | 3615 | return (const char *)glGetString(GL_EXTENSIONS); |
3589 | 3616 | } |
3590 | 3617 | ''') |
3591 | self.run_process([EMCC, 'side.cpp', '-s', 'SIDE_MODULE', '-O2', '-o', 'side.wasm', '-lSDL', '-s', 'EXPORT_ALL']) | |
3592 | ||
3593 | self.btest(self.in_dir('main.cpp'), '1', args=['-s', 'MAIN_MODULE', '-O2', '-s', 'LEGACY_GL_EMULATION', '-lSDL', '-lGL', '-s', 'EXPORT_ALL', 'side.wasm']) | |
3618 | self.run_process([EMCC, 'side.c', '-s', 'SIDE_MODULE', '-O2', '-o', 'side.wasm', '-lSDL']) | |
3619 | ||
3620 | self.btest(self.in_dir('main.c'), '1', args=['-s', 'MAIN_MODULE=2', '-O2', '-s', 'LEGACY_GL_EMULATION', '-lSDL', '-lGL', 'side.wasm']) | |
3594 | 3621 | |
3595 | 3622 | def test_dynamic_link_many(self): |
3596 | 3623 | # test asynchronously loading two side modules during startup |
3597 | 3624 | create_file('main.c', r''' |
3625 | #include <assert.h> | |
3598 | 3626 | int side1(); |
3599 | 3627 | int side2(); |
3600 | 3628 | int main() { |
3601 | return side1() + side2(); | |
3629 | assert(side1() == 1); | |
3630 | assert(side2() == 2); | |
3631 | return 0; | |
3602 | 3632 | } |
3603 | 3633 | ''') |
3604 | 3634 | create_file('side1.c', r''' |
3609 | 3639 | ''') |
3610 | 3640 | self.run_process([EMCC, 'side1.c', '-s', 'SIDE_MODULE', '-o', 'side1.wasm']) |
3611 | 3641 | self.run_process([EMCC, 'side2.c', '-s', 'SIDE_MODULE', '-o', 'side2.wasm']) |
3612 | self.btest_exit(self.in_dir('main.c'), assert_returncode=3, | |
3613 | args=['-s', 'MAIN_MODULE', 'side1.wasm', 'side2.wasm']) | |
3642 | self.btest_exit(self.in_dir('main.c'), args=['-s', 'MAIN_MODULE=2', 'side1.wasm', 'side2.wasm']) | |
3614 | 3643 | |
3615 | 3644 | def test_dynamic_link_pthread_many(self): |
3616 | 3645 | # Test asynchronously loading two side modules during startup |
3654 | 3683 | self.run_process([EMCC, 'side1.cpp', '-Wno-experimental', '-pthread', '-s', 'SIDE_MODULE', '-o', 'side1.wasm']) |
3655 | 3684 | self.run_process([EMCC, 'side2.cpp', '-Wno-experimental', '-pthread', '-s', 'SIDE_MODULE', '-o', 'side2.wasm']) |
3656 | 3685 | self.btest(self.in_dir('main.cpp'), '1', |
3657 | args=['-Wno-experimental', '-pthread', '-s', 'MAIN_MODULE', 'side1.wasm', 'side2.wasm']) | |
3686 | args=['-Wno-experimental', '-pthread', '-s', 'MAIN_MODULE=2', 'side1.wasm', 'side2.wasm']) | |
3658 | 3687 | |
3659 | 3688 | def test_memory_growth_during_startup(self): |
3660 | 3689 | create_file('data.dat', 'X' * (30 * 1024 * 1024)) |
3850 | 3879 | # Test that pthread cleanup stack (pthread_cleanup_push/_pop) works. |
3851 | 3880 | @requires_threads |
3852 | 3881 | def test_pthread_cleanup(self): |
3853 | self.btest(test_file('pthread/test_pthread_cleanup.cpp'), expected='907640832', args=['-O3', '-s', 'USE_PTHREADS', '-s', 'PTHREAD_POOL_SIZE=8']) | |
3882 | self.btest_exit(test_file('pthread/test_pthread_cleanup.cpp'), args=['-O3', '-s', 'USE_PTHREADS', '-s', 'PTHREAD_POOL_SIZE=8']) | |
3854 | 3883 | |
3855 | 3884 | # Tests the pthread mutex api. |
3856 | 3885 | @requires_threads |
3926 | 3955 | # Test that the main thread is able to use pthread_set/getspecific. |
3927 | 3956 | @requires_threads |
3928 | 3957 | def test_pthread_setspecific_mainthread(self): |
3929 | self.btest(test_file('pthread/test_pthread_setspecific_mainthread.cpp'), expected='0', args=['-s', 'INITIAL_MEMORY=64MB', '-O3', '-s', 'USE_PTHREADS'], also_asmjs=True) | |
3958 | self.btest_exit(test_file('pthread/test_pthread_setspecific_mainthread.c'), args=['-s', 'INITIAL_MEMORY=64MB', '-O3', '-s', 'USE_PTHREADS'], also_asmjs=True) | |
3930 | 3959 | |
3931 | 3960 | # Test that pthreads have access to filesystem. |
3932 | 3961 | @requires_threads |
4151 | 4180 | |
4152 | 4181 | # Test that it is possible to send a signal via calling alarm(timeout), which in turn calls to the signal handler set by signal(SIGALRM, func); |
4153 | 4182 | def test_sigalrm(self): |
4154 | self.btest(test_file('sigalrm.cpp'), expected='0', args=['-O3']) | |
4183 | self.btest_exit(test_file('test_sigalrm.c'), args=['-O3']) | |
4155 | 4184 | |
4156 | 4185 | def test_canvas_style_proxy(self): |
4157 | 4186 | self.btest('canvas_style_proxy.c', expected='1', args=['--proxy-to-worker', '--shell-file', test_file('canvas_style_proxy_shell.html'), '--pre-js', test_file('canvas_style_proxy_pre.js')]) |
4276 | 4305 | size = os.path.getsize('test.js') |
4277 | 4306 | print('size:', size) |
4278 | 4307 | # Note that this size includes test harness additions (for reporting the result, etc.). |
4279 | self.assertLess(abs(size - 5453), 100) | |
4308 | self.assertLess(abs(size - 5368), 100) | |
4280 | 4309 | |
4281 | 4310 | # Tests that it is possible to initialize and render WebGL content in a pthread by using OffscreenCanvas. |
4282 | 4311 | # -DTEST_CHAINED_WEBGL_CONTEXT_PASSING: Tests that it is possible to transfer WebGL canvas in a chain from main thread -> thread 1 -> thread 2 and then init and render WebGL content there. |
4283 | 4312 | @no_chrome('see https://crbug.com/961765') |
4284 | 4313 | @requires_threads |
4285 | 4314 | @requires_offscreen_canvas |
4315 | @requires_graphics_hardware | |
4286 | 4316 | def test_webgl_offscreen_canvas_in_pthread(self): |
4287 | 4317 | for args in [[], ['-DTEST_CHAINED_WEBGL_CONTEXT_PASSING']]: |
4288 | 4318 | self.btest('gl_in_pthread.cpp', expected='1', args=args + ['-s', 'USE_PTHREADS', '-s', 'PTHREAD_POOL_SIZE=2', '-s', 'OFFSCREENCANVAS_SUPPORT', '-lGL']) |
4291 | 4321 | # -DTEST_MAIN_THREAD_EXPLICIT_COMMIT: Test the same (WebGL on main thread after pthread), but by using explicit .commit() to swap on the main thread instead of implicit "swap when rAF ends" logic |
4292 | 4322 | @requires_threads |
4293 | 4323 | @requires_offscreen_canvas |
4324 | @requires_graphics_hardware | |
4294 | 4325 | @disabled('This test is disabled because current OffscreenCanvas does not allow transfering it after a rendering context has been created for it.') |
4295 | 4326 | def test_webgl_offscreen_canvas_in_mainthread_after_pthread(self): |
4296 | 4327 | for args in [[], ['-DTEST_MAIN_THREAD_EXPLICIT_COMMIT']]: |
4298 | 4329 | |
4299 | 4330 | @requires_threads |
4300 | 4331 | @requires_offscreen_canvas |
4332 | @requires_graphics_hardware | |
4301 | 4333 | def test_webgl_offscreen_canvas_only_in_pthread(self): |
4302 | self.btest('gl_only_in_pthread.cpp', expected='0', args=['-s', 'USE_PTHREADS', '-s', 'PTHREAD_POOL_SIZE', '-s', 'OFFSCREENCANVAS_SUPPORT', '-lGL', '-s', 'OFFSCREEN_FRAMEBUFFER']) | |
4334 | self.btest_exit('gl_only_in_pthread.cpp', args=['-s', 'USE_PTHREADS', '-s', 'PTHREAD_POOL_SIZE', '-s', 'OFFSCREENCANVAS_SUPPORT', '-lGL', '-s', 'OFFSCREEN_FRAMEBUFFER']) | |
4303 | 4335 | |
4304 | 4336 | # Tests that rendering from client side memory without default-enabling extensions works. |
4305 | 4337 | @requires_graphics_hardware |
4306 | 4338 | def test_webgl_from_client_side_memory_without_default_enabled_extensions(self): |
4307 | self.btest('webgl_draw_triangle.c', '0', args=['-lGL', '-s', 'OFFSCREEN_FRAMEBUFFER', '-DEXPLICIT_SWAP=1', '-DDRAW_FROM_CLIENT_MEMORY=1', '-s', 'FULL_ES2=1']) | |
4339 | self.btest_exit('webgl_draw_triangle.c', args=['-lGL', '-s', 'OFFSCREEN_FRAMEBUFFER', '-DEXPLICIT_SWAP=1', '-DDRAW_FROM_CLIENT_MEMORY=1', '-s', 'FULL_ES2=1']) | |
4308 | 4340 | |
4309 | 4341 | # Tests for WEBGL_multi_draw extension |
4310 | 4342 | # For testing WebGL draft extensions like this, if using chrome as the browser, |
4341 | 4373 | @requires_graphics_hardware |
4342 | 4374 | def test_webgl_sample_query(self): |
4343 | 4375 | cmd = ['-s', 'MAX_WEBGL_VERSION=2', '-lGL'] |
4344 | self.btest('webgl_sample_query.cpp', expected='0', args=cmd) | |
4376 | self.btest_exit('webgl_sample_query.cpp', args=cmd) | |
4345 | 4377 | |
4346 | 4378 | @requires_graphics_hardware |
4347 | 4379 | def test_webgl_timer_query(self): |
4354 | 4386 | ['-s', 'MAX_WEBGL_VERSION=2'], |
4355 | 4387 | ]: |
4356 | 4388 | cmd = args + ['-lGL'] |
4357 | self.btest('webgl_timer_query.cpp', expected='0', args=cmd) | |
4389 | self.btest_exit('webgl_timer_query.cpp', args=cmd) | |
4358 | 4390 | |
4359 | 4391 | # Tests that -s OFFSCREEN_FRAMEBUFFER=1 rendering works. |
4360 | 4392 | @requires_graphics_hardware |
4364 | 4396 | for version in [[], ['-s', 'FULL_ES3'], ['-s', 'FULL_ES3']]: |
4365 | 4397 | args = ['-lGL', '-s', 'OFFSCREEN_FRAMEBUFFER', '-DEXPLICIT_SWAP=1'] + threads + version |
4366 | 4398 | print('with args: %s' % str(args)) |
4367 | self.btest('webgl_draw_triangle.c', '0', args=args) | |
4399 | self.btest_exit('webgl_draw_triangle.c', args=args) | |
4368 | 4400 | |
4369 | 4401 | # Tests that VAOs can be used even if WebGL enableExtensionsByDefault is set to 0. |
4370 | 4402 | @requires_graphics_hardware |
4371 | 4403 | def test_webgl_vao_without_automatic_extensions(self): |
4372 | self.btest('test_webgl_no_auto_init_extensions.c', '0', args=['-lGL', '-s', 'GL_SUPPORT_AUTOMATIC_ENABLE_EXTENSIONS=0']) | |
4404 | self.btest_exit('test_webgl_no_auto_init_extensions.c', args=['-lGL', '-s', 'GL_SUPPORT_AUTOMATIC_ENABLE_EXTENSIONS=0']) | |
4373 | 4405 | |
4374 | 4406 | # Tests that offscreen framebuffer state restoration works |
4375 | 4407 | @requires_graphics_hardware |
4388 | 4420 | ['-s', 'MAX_WEBGL_VERSION=2', '-DTEST_WEBGL2=1', '-DTEST_ANTIALIAS=0'], |
4389 | 4421 | ]: |
4390 | 4422 | cmd = args + ['-lGL', '-s', 'OFFSCREEN_FRAMEBUFFER', '-DEXPLICIT_SWAP=1'] |
4391 | self.btest('webgl_offscreen_framebuffer_swap_with_bad_state.c', '0', args=cmd) | |
4423 | self.btest_exit('webgl_offscreen_framebuffer_swap_with_bad_state.c', args=cmd) | |
4392 | 4424 | |
4393 | 4425 | # Tests that -s WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG=1 rendering works. |
4394 | 4426 | @requires_graphics_hardware |
4395 | 4427 | def test_webgl_workaround_webgl_uniform_upload_bug(self): |
4396 | self.btest('webgl_draw_triangle_with_uniform_color.c', '0', args=['-lGL', '-s', 'WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG']) | |
4428 | self.btest_exit('webgl_draw_triangle_with_uniform_color.c', args=['-lGL', '-s', 'WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG']) | |
4397 | 4429 | |
4398 | 4430 | # Tests that using an array of structs in GL uniforms works. |
4399 | 4431 | @requires_graphics_hardware |
4405 | 4437 | # -DTEST_OFFSCREEN_CANVAS=2: Tests that if a WebGL context is created on a pthread that has the canvas transferred to it via automatic transferring of Module.canvas when EMSCRIPTEN_PTHREAD_TRANSFERRED_CANVASES is not defined, then OffscreenCanvas is also used |
4406 | 4438 | @requires_threads |
4407 | 4439 | @requires_offscreen_canvas |
4440 | @requires_graphics_hardware | |
4408 | 4441 | def test_webgl_offscreen_canvas_in_proxied_pthread(self): |
4409 | 4442 | for asyncify in [0, 1]: |
4410 | 4443 | cmd = ['-s', 'USE_PTHREADS', '-s', 'OFFSCREENCANVAS_SUPPORT', '-lGL', '-s', 'GL_DEBUG', '-s', 'PROXY_TO_PTHREAD'] |
4435 | 4468 | '-s', 'MAX_WEBGL_VERSION=2', |
4436 | 4469 | '-s', 'GL_SUPPORT_AUTOMATIC_ENABLE_EXTENSIONS=' + str(simple_enable_extensions), |
4437 | 4470 | '-s', 'GL_SUPPORT_SIMPLE_ENABLE_EXTENSIONS=' + str(simple_enable_extensions)] |
4438 | self.btest('webgl2_simple_enable_extensions.c', expected='0', args=cmd) | |
4471 | self.btest_exit('webgl2_simple_enable_extensions.c', args=cmd) | |
4439 | 4472 | |
4440 | 4473 | # Tests the feature that shell html page can preallocate the typed array and place it |
4441 | 4474 | # to Module.buffer before loading the script page. |
4447 | 4480 | # Tests emscripten_fetch() usage to XHR data directly to memory without persisting results to IndexedDB. |
4448 | 4481 | def test_fetch_to_memory(self): |
4449 | 4482 | # Test error reporting in the negative case when the file URL doesn't exist. (http 404) |
4450 | self.btest('fetch/to_memory.cpp', | |
4451 | expected='1', | |
4452 | args=['-s', 'FETCH_DEBUG', '-s', 'FETCH', '-DFILE_DOES_NOT_EXIST'], | |
4453 | also_asmjs=True) | |
4483 | self.btest_exit('fetch/to_memory.cpp', | |
4484 | args=['-s', 'FETCH_DEBUG', '-s', 'FETCH', '-DFILE_DOES_NOT_EXIST'], | |
4485 | also_asmjs=True) | |
4454 | 4486 | |
4455 | 4487 | # Test the positive case when the file URL exists. (http 200) |
4456 | 4488 | shutil.copyfile(test_file('gears.png'), 'gears.png') |
4457 | 4489 | for arg in [[], ['-s', 'FETCH_SUPPORT_INDEXEDDB=0']]: |
4458 | self.btest('fetch/to_memory.cpp', | |
4459 | expected='1', | |
4460 | args=['-s', 'FETCH_DEBUG', '-s', 'FETCH'] + arg, | |
4461 | also_asmjs=True) | |
4490 | self.btest_exit('fetch/to_memory.cpp', | |
4491 | args=['-s', 'FETCH_DEBUG', '-s', 'FETCH'] + arg, | |
4492 | also_asmjs=True) | |
4462 | 4493 | |
4463 | 4494 | @parameterized({ |
4464 | 4495 | '': ([],), |
4474 | 4505 | |
4475 | 4506 | def test_fetch_to_indexdb(self): |
4476 | 4507 | shutil.copyfile(test_file('gears.png'), 'gears.png') |
4477 | self.btest('fetch/to_indexeddb.cpp', | |
4478 | expected='1', | |
4479 | args=['-s', 'FETCH_DEBUG', '-s', 'FETCH'], | |
4480 | also_asmjs=True) | |
4508 | self.btest_exit('fetch/to_indexeddb.cpp', | |
4509 | args=['-s', 'FETCH_DEBUG', '-s', 'FETCH'], | |
4510 | also_asmjs=True) | |
4481 | 4511 | |
4482 | 4512 | # Tests emscripten_fetch() usage to persist an XHR into IndexedDB and subsequently load up from there. |
4483 | 4513 | def test_fetch_cached_xhr(self): |
4484 | 4514 | shutil.copyfile(test_file('gears.png'), 'gears.png') |
4485 | self.btest('fetch/cached_xhr.cpp', | |
4486 | expected='1', | |
4487 | args=['-s', 'FETCH_DEBUG', '-s', 'FETCH'], | |
4488 | also_asmjs=True) | |
4515 | self.btest_exit('fetch/cached_xhr.cpp', | |
4516 | args=['-s', 'FETCH_DEBUG', '-s', 'FETCH'], | |
4517 | also_asmjs=True) | |
4489 | 4518 | |
4490 | 4519 | # Tests that response headers get set on emscripten_fetch_t values. |
4491 | 4520 | @requires_threads |
4492 | 4521 | def test_fetch_response_headers(self): |
4493 | 4522 | shutil.copyfile(test_file('gears.png'), 'gears.png') |
4494 | self.btest('fetch/response_headers.cpp', expected='1', args=['-s', 'FETCH_DEBUG', '-s', 'FETCH', '-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD'], also_asmjs=True) | |
4523 | self.btest_exit('fetch/response_headers.cpp', args=['-s', 'FETCH_DEBUG', '-s', 'FETCH', '-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD'], also_asmjs=True) | |
4495 | 4524 | |
4496 | 4525 | # Test emscripten_fetch() usage to stream a XHR in to memory without storing the full file in memory |
4497 | 4526 | def test_fetch_stream_file(self): |
4504 | 4533 | with open('largefile.txt', 'w') as f: |
4505 | 4534 | for i in range(1024): |
4506 | 4535 | f.write(s) |
4507 | self.btest('fetch/stream_file.cpp', | |
4508 | expected='1', | |
4509 | args=['-s', 'FETCH_DEBUG', '-s', 'FETCH', '-s', 'INITIAL_MEMORY=536870912'], | |
4510 | also_asmjs=True) | |
4536 | self.btest_exit('fetch/stream_file.cpp', | |
4537 | args=['-s', 'FETCH_DEBUG', '-s', 'FETCH', '-s', 'INITIAL_MEMORY=536870912'], | |
4538 | also_asmjs=True) | |
4511 | 4539 | |
4512 | 4540 | # Tests emscripten_fetch() usage in synchronous mode when used from the main |
4513 | 4541 | # thread proxied to a Worker with -s PROXY_TO_PTHREAD=1 option. |
4521 | 4549 | @requires_threads |
4522 | 4550 | def test_fetch_implicit_append(self): |
4523 | 4551 | shutil.copyfile(test_file('gears.png'), 'gears.png') |
4524 | self.btest('fetch/example_synchronous_fetch.cpp', expected='200', args=['-s', 'FETCH', '-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD']) | |
4552 | self.btest_exit('fetch/example_synchronous_fetch.cpp', args=['-s', 'FETCH', '-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD']) | |
4525 | 4553 | |
4526 | 4554 | # Tests synchronous emscripten_fetch() usage from wasm pthread in fastcomp. |
4527 | 4555 | @requires_threads |
4528 | 4556 | def test_fetch_sync_xhr_in_wasm(self): |
4529 | 4557 | shutil.copyfile(test_file('gears.png'), 'gears.png') |
4530 | self.btest('fetch/example_synchronous_fetch.cpp', expected='200', args=['-s', 'FETCH', '-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD']) | |
4558 | self.btest_exit('fetch/example_synchronous_fetch.cpp', args=['-s', 'FETCH', '-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD']) | |
4531 | 4559 | |
4532 | 4560 | # Tests that the Fetch API works for synchronous XHRs when used with --proxy-to-worker. |
4533 | 4561 | @requires_threads |
4534 | 4562 | def test_fetch_sync_xhr_in_proxy_to_worker(self): |
4535 | 4563 | shutil.copyfile(test_file('gears.png'), 'gears.png') |
4536 | self.btest('fetch/sync_xhr.cpp', | |
4537 | expected='0', | |
4538 | args=['-s', 'FETCH_DEBUG', '-s', 'FETCH', '--proxy-to-worker'], | |
4539 | also_asmjs=True) | |
4564 | self.btest_exit('fetch/sync_xhr.cpp', | |
4565 | args=['-s', 'FETCH_DEBUG', '-s', 'FETCH', '--proxy-to-worker'], | |
4566 | also_asmjs=True) | |
4540 | 4567 | |
4541 | 4568 | # Tests waiting on EMSCRIPTEN_FETCH_WAITABLE request from a worker thread |
4542 | 4569 | @no_wasm_backend("emscripten_fetch_wait uses an asm.js based web worker") |
4578 | 4605 | @requires_asmfs |
4579 | 4606 | @requires_threads |
4580 | 4607 | def test_asmfs_mkdir_create_unlink_rmdir(self): |
4581 | self.btest('cstdio/test_remove.cpp', expected='0', args=['-s', 'ASMFS', '-s', 'WASM=0', '-s', 'USE_PTHREADS', '-s', 'FETCH_DEBUG']) | |
4608 | self.btest_exit('cstdio/test_remove.cpp', args=['-s', 'ASMFS', '-s', 'WASM=0', '-s', 'USE_PTHREADS', '-s', 'FETCH_DEBUG']) | |
4582 | 4609 | |
4583 | 4610 | @requires_asmfs |
4584 | 4611 | @requires_threads |
4623 | 4650 | ['-s', 'USE_PTHREADS', '-s', 'PTHREAD_POOL_SIZE=2'], |
4624 | 4651 | ]: |
4625 | 4652 | print("Testing with: ", args) |
4626 | self.btest('pthread/test_pthread_locale.c', expected='1', args=args) | |
4627 | ||
4628 | # Tests the Emscripten HTML5 API emscripten_set_canvas_element_size() and emscripten_get_canvas_element_size() functionality in singlethreaded programs. | |
4653 | self.btest_exit('pthread/test_pthread_locale.c', args=args) | |
4654 | ||
4655 | # Tests the Emscripten HTML5 API emscripten_set_canvas_element_size() and | |
4656 | # emscripten_get_canvas_element_size() functionality in singlethreaded programs. | |
4629 | 4657 | def test_emscripten_set_canvas_element_size(self): |
4630 | self.btest('emscripten_set_canvas_element_size.c', expected='1') | |
4631 | ||
4632 | # Test that emscripten_get_device_pixel_ratio() is callable from pthreads (and proxies to main thread to obtain the proper window.devicePixelRatio value). | |
4658 | self.btest_exit('emscripten_set_canvas_element_size.c') | |
4659 | ||
4660 | # Test that emscripten_get_device_pixel_ratio() is callable from pthreads (and proxies to main | |
4661 | # thread to obtain the proper window.devicePixelRatio value). | |
4633 | 4662 | @requires_threads |
4634 | 4663 | def test_emscripten_get_device_pixel_ratio(self): |
4635 | 4664 | for args in [[], ['-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD']]: |
4636 | self.btest('emscripten_get_device_pixel_ratio.c', expected='1', args=args) | |
4665 | self.btest_exit('emscripten_get_device_pixel_ratio.c', args=args) | |
4637 | 4666 | |
4638 | 4667 | # Tests that emscripten_run_script() variants of functions work in pthreads. |
4639 | 4668 | @requires_threads |
4640 | 4669 | def test_pthread_run_script(self): |
4641 | 4670 | for args in [[], ['-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD']]: |
4642 | self.btest(test_file('pthread/test_pthread_run_script.cpp'), expected='1', args=['-O3'] + args) | |
4671 | self.btest_exit(test_file('pthread/test_pthread_run_script.cpp'), args=['-O3'] + args) | |
4643 | 4672 | |
4644 | 4673 | # Tests emscripten_set_canvas_element_size() and OffscreenCanvas functionality in different build configurations. |
4645 | 4674 | @requires_threads |
4655 | 4684 | ]: |
4656 | 4685 | cmd = ['-lGL', '-O3', '-g2', '--shell-file', test_file('canvas_animate_resize_shell.html'), '-s', 'GL_DEBUG', '--threadprofiler'] + args |
4657 | 4686 | print(' '.join(cmd)) |
4658 | self.btest('canvas_animate_resize.cpp', expected='1', args=cmd) | |
4687 | self.btest_exit('canvas_animate_resize.cpp', args=cmd) | |
4659 | 4688 | |
4660 | 4689 | # Tests the absolute minimum pthread-enabled application. |
4661 | 4690 | @requires_threads |
4958 | 4987 | # Tests that Closure run in combination with -s ENVIRONMENT=web mode works with a small WebGL application |
4959 | 4988 | @requires_graphics_hardware |
4960 | 4989 | def test_closure_in_web_only_target_environment_webgl(self): |
4961 | self.btest('webgl_draw_triangle.c', '0', args=['-lGL', '-s', 'ENVIRONMENT=web', '-O3', '--closure=1']) | |
4990 | self.btest_exit('webgl_draw_triangle.c', args=['-lGL', '-s', 'ENVIRONMENT=web', '-O3', '--closure=1']) | |
4962 | 4991 | |
4963 | 4992 | def test_no_declare_asm_module_exports_asmjs(self): |
4964 | 4993 | for minimal_runtime in [[], ['-s', 'MINIMAL_RUNTIME']]: |
4986 | 5015 | |
4987 | 5016 | # Tests emscripten_unwind_to_js_event_loop() behavior |
4988 | 5017 | def test_emscripten_unwind_to_js_event_loop(self, *args): |
4989 | self.btest(test_file('browser/test_emscripten_unwind_to_js_event_loop.c'), '1', args=['-s', 'NO_EXIT_RUNTIME']) | |
5018 | self.btest_exit(test_file('browser/test_emscripten_unwind_to_js_event_loop.c')) | |
4990 | 5019 | |
4991 | 5020 | def test_wasm2js_fallback(self): |
4992 | 5021 | for args in [[], ['-s', 'MINIMAL_RUNTIME']]: |
5019 | 5048 | self.run_browser('test.html', 'hello!', '/report_result?0') |
5020 | 5049 | |
5021 | 5050 | def test_system(self): |
5022 | self.btest(test_file('system.c'), '0') | |
5051 | self.btest_exit(test_file('system.c')) | |
5023 | 5052 | |
5024 | 5053 | # Tests that it is possible to hook into/override a symbol defined in a system library. |
5025 | 5054 | @requires_graphics_hardware |
5028 | 5057 | |
5029 | 5058 | # When WebGL is implicitly linked in, the implicit linking should happen before any user --js-libraries, so that they can adjust |
5030 | 5059 | # the behavior afterwards. |
5031 | self.btest(test_file('test_override_system_js_lib_symbol.c'), | |
5032 | expected='5121', | |
5033 | args=['--js-library', test_file('test_override_system_js_lib_symbol.js')]) | |
5060 | self.btest_exit(test_file('test_override_system_js_lib_symbol.c'), | |
5061 | args=['--js-library', test_file('test_override_system_js_lib_symbol.js')]) | |
5034 | 5062 | |
5035 | 5063 | # When WebGL is explicitly linked to in strict mode, the linking order on command line should enable overriding. |
5036 | self.btest(test_file('test_override_system_js_lib_symbol.c'), | |
5037 | expected='5121', | |
5038 | args=['-s', 'AUTO_JS_LIBRARIES=0', '-lwebgl.js', '--js-library', test_file('test_override_system_js_lib_symbol.js')]) | |
5064 | self.btest_exit(test_file('test_override_system_js_lib_symbol.c'), | |
5065 | args=['-s', 'AUTO_JS_LIBRARIES=0', '-lwebgl.js', '--js-library', test_file('test_override_system_js_lib_symbol.js')]) | |
5039 | 5066 | |
5040 | 5067 | @no_firefox('no 4GB support yet') |
5068 | @require_v8 | |
5041 | 5069 | def test_zzz_zzz_4gb(self): |
5042 | 5070 | # TODO Convert to an actual browser test when it reaches stable. |
5043 | 5071 | # For now, keep this in browser as this suite runs serially, which |
5048 | 5076 | # test that we can allocate in the 2-4GB range, if we enable growth and |
5049 | 5077 | # set the max appropriately |
5050 | 5078 | self.emcc_args += ['-O2', '-s', 'ALLOW_MEMORY_GROWTH', '-s', 'MAXIMUM_MEMORY=4GB'] |
5051 | self.do_run_in_out_file_test('browser', 'test_4GB.cpp', js_engines=[config.V8_ENGINE]) | |
5079 | self.do_run_in_out_file_test('browser', 'test_4GB.cpp') | |
5052 | 5080 | |
5053 | 5081 | # Tests that emmalloc supports up to 4GB Wasm heaps. |
5054 | 5082 | @no_firefox('no 4GB support yet') |
5076 | 5104 | self.btest(test_file('browser/emmalloc_memgrowth.cpp'), expected='0', args=['-s', 'MALLOC=emmalloc', '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'ABORTING_MALLOC=0', '-s', 'ASSERTIONS=2', '-s', 'MINIMAL_RUNTIME=1', '-s', 'MAXIMUM_MEMORY=4GB']) |
5077 | 5105 | |
5078 | 5106 | @no_firefox('no 4GB support yet') |
5107 | @require_v8 | |
5079 | 5108 | def test_zzz_zzz_2gb_fail(self): |
5080 | 5109 | # TODO Convert to an actual browser test when it reaches stable. |
5081 | 5110 | # For now, keep this in browser as this suite runs serially, which |
5086 | 5115 | # test that growth doesn't go beyond 2GB without the max being set for that, |
5087 | 5116 | # and that we can catch an allocation failure exception for that |
5088 | 5117 | self.emcc_args += ['-O2', '-s', 'ALLOW_MEMORY_GROWTH', '-s', 'MAXIMUM_MEMORY=2GB'] |
5089 | self.do_run_in_out_file_test('browser', 'test_2GB_fail.cpp', js_engines=[config.V8_ENGINE]) | |
5118 | self.do_run_in_out_file_test('browser', 'test_2GB_fail.cpp') | |
5090 | 5119 | |
5091 | 5120 | @no_firefox('no 4GB support yet') |
5121 | @require_v8 | |
5092 | 5122 | def test_zzz_zzz_4gb_fail(self): |
5093 | 5123 | # TODO Convert to an actual browser test when it reaches stable. |
5094 | 5124 | # For now, keep this in browser as this suite runs serially, which |
5099 | 5129 | # test that we properly report an allocation error that would overflow over |
5100 | 5130 | # 4GB. |
5101 | 5131 | self.emcc_args += ['-O2', '-s', 'ALLOW_MEMORY_GROWTH', '-s', 'MAXIMUM_MEMORY=4GB', '-s', 'ABORTING_MALLOC=0'] |
5102 | self.do_run_in_out_file_test('browser', 'test_4GB_fail.cpp', js_engines=[config.V8_ENGINE]) | |
5132 | self.do_run_in_out_file_test('browser', 'test_4GB_fail.cpp') | |
5103 | 5133 | |
5104 | 5134 | @disabled("only run this manually, to test for race conditions") |
5105 | 5135 | @parameterized({ |
23 | 23 | from tools.shared import PYTHON, EMCC, EMAR |
24 | 24 | from tools.utils import WINDOWS, MACOS |
25 | 25 | from tools import shared, building, config, webassembly |
26 | from runner import RunnerCore, path_from_root, requires_native_clang, test_file | |
27 | from runner import skip_if, needs_dylink, no_windows, is_slow_test, create_file, parameterized | |
28 | from runner import env_modify, with_env_modify, disabled, node_pthreads | |
29 | from runner import read_file, read_binary | |
30 | from runner import NON_ZERO, WEBIDL_BINDER, EMBUILDER | |
26 | from common import RunnerCore, path_from_root, requires_native_clang, test_file | |
27 | from common import skip_if, needs_dylink, no_windows, is_slow_test, create_file, parameterized | |
28 | from common import env_modify, with_env_modify, disabled, node_pthreads | |
29 | from common import read_file, read_binary, require_node, require_v8 | |
30 | from common import NON_ZERO, WEBIDL_BINDER, EMBUILDER, EMMAKE | |
31 | 31 | import clang_native |
32 | 32 | |
33 | 33 | # decorators for limiting which modes a test can run in |
76 | 76 | def decorated(self): |
77 | 77 | old = self.use_all_engines |
78 | 78 | self.use_all_engines = True |
79 | self.set_setting('ENVIRONMENT', 'web,node,shell') | |
79 | 80 | try: |
80 | 81 | f(self) |
81 | 82 | finally: |
462 | 463 | def test_cube2hash(self): |
463 | 464 | # A good test of i64 math |
464 | 465 | self.do_run('// empty file', 'Usage: hashstring <seed>', |
465 | libraries=self.get_library('third_party/cube2hash', ['libcube2hash.a'], configure=None), | |
466 | libraries=self.get_library('third_party/cube2hash', ['libcube2hash.a'], configure=None, make=[EMMAKE, 'make']), | |
466 | 467 | includes=[test_file('third_party/cube2hash')], assert_returncode=NON_ZERO) |
467 | 468 | |
468 | 469 | for text, output in [('fleefl', '892BDB6FD3F62E863D63DA55851700FDE3ACF30204798CE9'), |
2335 | 2336 | self.set_setting('INITIAL_MEMORY', '300mb') |
2336 | 2337 | self.do_run_in_out_file_test('pthread/test_pthread_thread_local_storage.cpp') |
2337 | 2338 | |
2339 | @node_pthreads | |
2340 | def test_pthread_cleanup(self): | |
2341 | self.set_setting('EXIT_RUNTIME') | |
2342 | self.set_setting('PTHREAD_POOL_SIZE', 4) | |
2343 | self.do_run_in_out_file_test('pthread/test_pthread_cleanup.cpp') | |
2344 | ||
2345 | @node_pthreads | |
2346 | def test_pthread_setspecific_mainthread(self): | |
2347 | self.set_setting('EXIT_RUNTIME') | |
2348 | self.do_run_in_out_file_test('pthread/test_pthread_setspecific_mainthread.c') | |
2349 | ||
2338 | 2350 | def test_tcgetattr(self): |
2339 | 2351 | self.do_runf(test_file('termios/test_tcgetattr.c'), 'success') |
2340 | 2352 | |
2546 | 2558 | return 0; |
2547 | 2559 | } |
2548 | 2560 | ''' |
2549 | self.do_run(src, 'error: Could not load dynamic lib: libfoo.so\nError: No such file or directory') | |
2550 | print('without assertions, the error is less clear') | |
2551 | self.set_setting('ASSERTIONS', 0) | |
2552 | self.do_run(src, 'error: Could not load dynamic lib: libfoo.so\nError: FS error') | |
2561 | self.do_run(src, "error: Could not load dynamic lib: libfoo.so\nError: ENOENT: no such file or directory, open 'libfoo.so'") | |
2553 | 2562 | |
2554 | 2563 | @needs_dylink |
2555 | 2564 | def test_dlfcn_basic(self): |
5284 | 5293 | self.do_runf(test_file('fs/test_64bit.c'), 'success') |
5285 | 5294 | |
5286 | 5295 | def test_sigalrm(self): |
5287 | self.do_runf(test_file('sigalrm.cpp'), '') | |
5296 | self.do_runf(test_file('test_sigalrm.c'), 'Received alarm!') | |
5288 | 5297 | |
5289 | 5298 | @no_windows('https://github.com/emscripten-core/emscripten/issues/8882') |
5290 | 5299 | def test_unistd_access(self): |
5682 | 5691 | def test_whets(self): |
5683 | 5692 | self.do_runf(test_file('whets.cpp'), 'Single Precision C Whetstone Benchmark') |
5684 | 5693 | |
5694 | # node is slower, and fail on 64-bit | |
5695 | @require_v8 | |
5685 | 5696 | @no_asan('depends on the specifics of memory size, which for asan we are forced to increase') |
5686 | 5697 | def test_dlmalloc_inline(self): |
5687 | self.banned_js_engines = [config.NODE_JS] # slower, and fail on 64-bit | |
5688 | 5698 | # needed with typed arrays |
5689 | 5699 | self.set_setting('INITIAL_MEMORY', '128mb') |
5690 | 5700 | |
5692 | 5702 | self.do_run(src, '*1,0*', args=['200', '1'], force_c=True) |
5693 | 5703 | self.do_run('src.js', '*400,0*', args=['400', '400'], force_c=True, no_build=True) |
5694 | 5704 | |
5705 | # node is slower, and fail on 64-bit | |
5706 | @require_v8 | |
5695 | 5707 | @no_asan('depends on the specifics of memory size, which for asan we are forced to increase') |
5696 | 5708 | def test_dlmalloc(self): |
5697 | self.banned_js_engines = [config.NODE_JS] # slower, and fail on 64-bit | |
5698 | 5709 | # needed with typed arrays |
5699 | 5710 | self.set_setting('INITIAL_MEMORY', '128mb') |
5700 | 5711 | |
5947 | 5958 | def test_lua(self): |
5948 | 5959 | self.emcc_args.remove('-Werror') |
5949 | 5960 | |
5961 | libs = self.get_library('third_party/lua', [Path('src/lua.o'), Path('src/liblua.a')], make=[EMMAKE, 'make', 'generic'], configure=None) | |
5950 | 5962 | self.do_run('', |
5951 | 5963 | 'hello lua world!\n17\n1\n2\n3\n4\n7', |
5952 | 5964 | args=['-e', '''print("hello lua world!");print(17);for x = 1,4 do print(x) end;print(10-3)'''], |
5953 | libraries=self.get_library('third_party/lua', [Path('src/lua.o'), Path('src/liblua.a')], make=['make', 'generic'], configure=None), | |
5965 | libraries=libs, | |
5954 | 5966 | includes=[test_file('lua')], |
5955 | 5967 | output_nicerizer=lambda string, err: (string + err).replace('\n\n', '\n').replace('\n\n', '\n')) |
5956 | 5968 | |
6944 | 6956 | }) |
6945 | 6957 | @sync |
6946 | 6958 | def test_webidl(self, mode, allow_memory_growth): |
6959 | self.uses_es6 = True | |
6947 | 6960 | if self.maybe_closure(): |
6948 | 6961 | # avoid closure minified names competing with our test code in the global name space |
6949 | 6962 | self.set_setting('MODULARIZE') |
6954 | 6967 | self.assertExists('glue.cpp') |
6955 | 6968 | self.assertExists('glue.js') |
6956 | 6969 | |
6970 | post_js = '\n\n' | |
6971 | if self.get_setting('MODULARIZE'): | |
6972 | post_js += 'var TheModule = Module();\n' | |
6973 | else: | |
6974 | post_js += 'var TheModule = Module;\n' | |
6975 | post_js += '\n\n' | |
6976 | if allow_memory_growth: | |
6977 | post_js += "var isMemoryGrowthAllowed = true;\n" | |
6978 | else: | |
6979 | post_js += "var isMemoryGrowthAllowed = false;\n" | |
6980 | post_js += read_file(test_file('webidl/post.js')) | |
6981 | post_js += '\n\n' | |
6982 | create_file('extern-post.js', post_js) | |
6983 | ||
6957 | 6984 | # Export things on "TheModule". This matches the typical use pattern of the bound library |
6958 | 6985 | # being used as Box2D.* or Ammo.*, and we cannot rely on "Module" being always present (closure may remove it). |
6959 | self.emcc_args += ['-s', 'EXPORTED_FUNCTIONS=_malloc,_free', '--post-js', 'glue.js'] | |
6986 | self.emcc_args += ['-s', 'EXPORTED_FUNCTIONS=_malloc,_free', '--post-js=glue.js', '--extern-post-js=extern-post.js'] | |
6960 | 6987 | if allow_memory_growth: |
6961 | 6988 | self.set_setting('ALLOW_MEMORY_GROWTH') |
6962 | 6989 | |
6963 | def post(filename): | |
6964 | with open(filename, 'a') as f: | |
6965 | f.write('\n\n') | |
6966 | if self.get_setting('MODULARIZE'): | |
6967 | f.write('var TheModule = Module();\n') | |
6968 | else: | |
6969 | f.write('var TheModule = Module;\n') | |
6970 | f.write('\n\n') | |
6971 | if allow_memory_growth: | |
6972 | f.write("var isMemoryGrowthAllowed = true;") | |
6973 | else: | |
6974 | f.write("var isMemoryGrowthAllowed = false;") | |
6975 | f.write(read_file(test_file('webidl/post.js'))) | |
6976 | f.write('\n\n') | |
6977 | ||
6978 | 6990 | output = test_file('webidl/output_%s.txt' % mode) |
6979 | self.do_run_from_file(test_file('webidl/test.cpp'), output, post_build=post) | |
6991 | self.do_run_from_file(test_file('webidl/test.cpp'), output) | |
6980 | 6992 | |
6981 | 6993 | ### Tests for tools |
6982 | 6994 | |
7214 | 7226 | def test_modularize_closure_pre(self): |
7215 | 7227 | # test that the combination of modularize + closure + pre-js works. in that mode, |
7216 | 7228 | # closure should not minify the Module object in a way that the pre-js cannot use it. |
7229 | create_file('post.js', 'var TheModule = Module();\n') | |
7217 | 7230 | self.emcc_args += [ |
7218 | 7231 | '--pre-js', test_file('core/modularize_closure_pre.js'), |
7232 | '--extern-post-js=post.js', | |
7219 | 7233 | '--closure=1', |
7220 | 7234 | '-g1', |
7221 | 7235 | '-s', |
7222 | 7236 | 'MODULARIZE=1', |
7223 | 7237 | ] |
7224 | ||
7225 | def post(filename): | |
7226 | with open(filename, 'a') as f: | |
7227 | f.write('\n\n') | |
7228 | f.write('var TheModule = Module();\n') | |
7229 | ||
7230 | self.do_core_test('modularize_closure_pre.c', post_build=post) | |
7238 | self.do_core_test('modularize_closure_pre.c') | |
7231 | 7239 | |
7232 | 7240 | @no_wasm2js('symbol names look different wasm2js backtraces') |
7233 | 7241 | def test_emscripten_log(self): |
7312 | 7320 | def test_vswprintf_utf8(self): |
7313 | 7321 | self.do_run_in_out_file_test('vswprintf_utf8.c') |
7314 | 7322 | |
7323 | # needs setTimeout which only node has | |
7324 | @require_node | |
7315 | 7325 | @no_asan('asan is not compatible with asyncify stack operations; may also need to not instrument asan_c_load_4, TODO') |
7316 | def test_async(self): | |
7326 | def test_async_hello(self): | |
7317 | 7327 | # needs to flush stdio streams |
7318 | 7328 | self.set_setting('EXIT_RUNTIME') |
7319 | 7329 | self.set_setting('ASYNCIFY') |
7320 | self.banned_js_engines = [config.SPIDERMONKEY_ENGINE, config.V8_ENGINE] # needs setTimeout which only node has | |
7321 | ||
7322 | src = r''' | |
7330 | ||
7331 | create_file('main.c', r''' | |
7323 | 7332 | #include <stdio.h> |
7324 | 7333 | #include <emscripten.h> |
7325 | 7334 | void f(void *p) { |
7334 | 7343 | emscripten_sleep(100); |
7335 | 7344 | printf("%d\n", i); |
7336 | 7345 | } |
7337 | ''' | |
7338 | ||
7339 | self.do_run(src, 'HelloWorld!99') | |
7340 | ||
7341 | print('check bad ccall use') | |
7342 | src = r''' | |
7346 | ''') | |
7347 | ||
7348 | self.do_runf('main.c', 'HelloWorld!99') | |
7349 | ||
7350 | @require_node | |
7351 | @no_asan('asyncify stack operations confuse asan') | |
7352 | def test_async_ccall_bad(self): | |
7353 | # check bad ccall use | |
7354 | # needs to flush stdio streams | |
7355 | self.set_setting('EXIT_RUNTIME') | |
7356 | self.set_setting('ASYNCIFY') | |
7357 | self.set_setting('ASSERTIONS') | |
7358 | self.set_setting('INVOKE_RUN', 0) | |
7359 | create_file('main.c', r''' | |
7343 | 7360 | #include <stdio.h> |
7344 | 7361 | #include <emscripten.h> |
7345 | 7362 | int main() { |
7347 | 7364 | emscripten_sleep(100); |
7348 | 7365 | printf("World\n"); |
7349 | 7366 | } |
7350 | ''' | |
7351 | self.set_setting('ASSERTIONS') | |
7352 | self.set_setting('INVOKE_RUN', 0) | |
7367 | ''') | |
7353 | 7368 | create_file('pre.js', ''' |
7354 | 7369 | Module['onRuntimeInitialized'] = function() { |
7355 | 7370 | try { |
7362 | 7377 | }; |
7363 | 7378 | ''') |
7364 | 7379 | self.emcc_args += ['--pre-js', 'pre.js'] |
7365 | self.do_run(src, 'The call to main is running asynchronously.') | |
7366 | ||
7367 | print('check reasonable ccall use') | |
7368 | src = r''' | |
7380 | self.do_runf('main.c', 'The call to main is running asynchronously.') | |
7381 | ||
7382 | @require_node | |
7383 | @no_asan('asyncify stack operations confuse asan') | |
7384 | def test_async_ccall_good(self): | |
7385 | # check reasonable ccall use | |
7386 | # needs to flush stdio streams | |
7387 | self.set_setting('EXIT_RUNTIME') | |
7388 | self.set_setting('ASYNCIFY') | |
7389 | self.set_setting('ASSERTIONS') | |
7390 | self.set_setting('INVOKE_RUN', 0) | |
7391 | create_file('main.c', r''' | |
7369 | 7392 | #include <stdio.h> |
7370 | 7393 | #include <emscripten.h> |
7371 | 7394 | int main() { |
7373 | 7396 | emscripten_sleep(100); |
7374 | 7397 | printf("World\n"); |
7375 | 7398 | } |
7376 | ''' | |
7399 | ''') | |
7377 | 7400 | create_file('pre.js', ''' |
7378 | 7401 | Module['onRuntimeInitialized'] = function() { |
7379 | 7402 | ccall('main', null, ['number', 'string'], [2, 'waka'], { async: true }); |
7380 | 7403 | }; |
7381 | 7404 | ''') |
7382 | self.do_run(src, 'HelloWorld') | |
7383 | ||
7405 | self.emcc_args += ['--pre-js', 'pre.js'] | |
7406 | self.do_runf('main.c', 'HelloWorld') | |
7407 | ||
7408 | @no_asan('asyncify stack operations confuse asan') | |
7409 | def test_async_ccall_promise(self): | |
7384 | 7410 | print('check ccall promise') |
7411 | self.set_setting('ASYNCIFY') | |
7412 | self.set_setting('ASSERTIONS') | |
7413 | self.set_setting('INVOKE_RUN', 0) | |
7385 | 7414 | self.set_setting('EXPORTED_FUNCTIONS', ['_stringf', '_floatf']) |
7386 | src = r''' | |
7415 | create_file('main.c', r''' | |
7387 | 7416 | #include <stdio.h> |
7388 | 7417 | #include <emscripten.h> |
7389 | extern "C" { | |
7390 | const char* stringf(char* param) { | |
7391 | emscripten_sleep(20); | |
7392 | printf("%s", param); | |
7393 | return "second"; | |
7394 | } | |
7395 | double floatf() { | |
7396 | emscripten_sleep(20); | |
7397 | emscripten_sleep(20); | |
7398 | return 6.4; | |
7399 | } | |
7418 | const char* stringf(char* param) { | |
7419 | emscripten_sleep(20); | |
7420 | printf("%s", param); | |
7421 | return "second"; | |
7400 | 7422 | } |
7401 | ''' | |
7423 | double floatf() { | |
7424 | emscripten_sleep(20); | |
7425 | emscripten_sleep(20); | |
7426 | return 6.4; | |
7427 | } | |
7428 | ''') | |
7402 | 7429 | create_file('pre.js', r''' |
7403 | 7430 | Module['onRuntimeInitialized'] = function() { |
7404 | 7431 | ccall('stringf', 'string', ['string'], ['first\n'], { async: true }) |
7408 | 7435 | }); |
7409 | 7436 | }; |
7410 | 7437 | ''') |
7411 | self.do_run(src, 'first\nsecond\n6.4') | |
7438 | self.emcc_args += ['--pre-js', 'pre.js'] | |
7439 | self.do_runf('main.c', 'first\nsecond\n6.4') | |
7412 | 7440 | |
7413 | 7441 | @no_asan('asyncify stack operations confuse asan') |
7414 | 7442 | def test_fibers_asyncify(self): |
7439 | 7467 | self.set_setting('ASYNCIFY_ONLY', '@response.file') |
7440 | 7468 | self.set_setting('ASYNCIFY') |
7441 | 7469 | self.emcc_args += args |
7442 | try: | |
7470 | ||
7471 | if should_pass: | |
7443 | 7472 | self.do_core_test('test_asyncify_lists.cpp', assert_identical=True) |
7444 | if not should_pass: | |
7445 | should_pass = True | |
7446 | raise Exception('should not have passed') | |
7447 | except Exception: | |
7448 | if should_pass: | |
7449 | raise | |
7473 | else: | |
7474 | self.do_runf(test_file('core/test_asyncify_lists.cpp'), 'exception thrown', assert_returncode=NON_ZERO) | |
7450 | 7475 | |
7451 | 7476 | # use of ASYNCIFY_* options may require intermediate debug info. that should |
7452 | 7477 | # not end up emitted in the final binary |
7453 | 7478 | if self.is_wasm(): |
7454 | with open('test_asyncify_lists.wasm', 'rb') as f: | |
7455 | binary = f.read() | |
7479 | binary = read_binary('test_asyncify_lists.wasm') | |
7456 | 7480 | # there should be no name section |
7457 | 7481 | self.assertFalse(b'name' in binary) |
7458 | 7482 | # in a fully-optimized build, imports and exports are minified too and we |
7495 | 7519 | self.set_setting('ASYNCIFY') |
7496 | 7520 | self.set_setting('ASSERTIONS') |
7497 | 7521 | self.set_setting('EXIT_RUNTIME', 1) |
7498 | self.do_core_test('test_asyncify_during_exit.cpp', assert_returncode=1) | |
7522 | self.do_core_test('test_asyncify_during_exit.cpp', assert_returncode=NON_ZERO) | |
7499 | 7523 | print('NO_ASYNC') |
7500 | 7524 | self.do_core_test('test_asyncify_during_exit.cpp', emcc_args=['-DNO_ASYNC'], out_suffix='_no_async') |
7501 | 7525 | |
8003 | 8027 | }) |
8004 | 8028 | @no_wasm2js('TODO: sanitizers in wasm2js') |
8005 | 8029 | def test_ubsan_full_stack_trace(self, g_flag, expected_output): |
8006 | self.emcc_args += ['-fsanitize=null', g_flag] | |
8007 | self.set_setting('ALLOW_MEMORY_GROWTH') | |
8008 | ||
8009 | 8030 | if g_flag == '-gsource-map': |
8010 | 8031 | if not self.is_wasm(): |
8011 | 8032 | self.skipTest('wasm2js has no source map support') |
8012 | 8033 | elif '-Oz' in self.emcc_args: |
8013 | 8034 | self.skipTest('-Oz breaks stack traces') |
8014 | 8035 | |
8015 | def modify_env(filename): | |
8016 | contents = read_file(filename) | |
8017 | contents = 'Module = {UBSAN_OPTIONS: "print_stacktrace=1"};' + contents | |
8018 | with open(filename, 'w') as f: | |
8019 | f.write(contents) | |
8020 | ||
8036 | create_file('pre.js', 'Module = {UBSAN_OPTIONS: "print_stacktrace=1"};') | |
8037 | self.emcc_args += ['-fsanitize=null', g_flag, '--pre-js=pre.js'] | |
8038 | self.set_setting('ALLOW_MEMORY_GROWTH') | |
8021 | 8039 | self.do_runf(test_file('core/test_ubsan_full_null_ref.cpp'), |
8022 | post_build=modify_env, assert_all=True, expected_output=expected_output) | |
8040 | assert_all=True, expected_output=expected_output) | |
8023 | 8041 | |
8024 | 8042 | @no_wasm2js('TODO: sanitizers in wasm2js') |
8025 | 8043 | def test_ubsan_typeinfo_eq(self): |
8144 | 8162 | @no_safe_heap('asan does not work with SAFE_HEAP') |
8145 | 8163 | @no_wasm2js('TODO: ASAN in wasm2js') |
8146 | 8164 | def test_asan_modularized_with_closure(self): |
8147 | self.emcc_args.append('-fsanitize=address') | |
8165 | # the bug is that createModule() returns undefined, instead of the | |
8166 | # proper Promise object. | |
8167 | create_file('post.js', 'if (!(createModule() instanceof Promise)) throw "Promise was not returned :(";\n') | |
8168 | self.emcc_args += ['-fsanitize=address', '--extern-post-js=post.js'] | |
8148 | 8169 | self.set_setting('MODULARIZE') |
8149 | 8170 | self.set_setting('EXPORT_NAME', 'createModule') |
8150 | 8171 | self.set_setting('USE_CLOSURE_COMPILER') |
8151 | 8172 | self.set_setting('ALLOW_MEMORY_GROWTH') |
8152 | 8173 | self.set_setting('INITIAL_MEMORY', '300mb') |
8153 | ||
8154 | def post(filename): | |
8155 | with open(filename, 'a') as f: | |
8156 | f.write('\n\n') | |
8157 | # the bug is that createModule() returns undefined, instead of the | |
8158 | # proper Promise object. | |
8159 | f.write('if (!(createModule() instanceof Promise)) throw "Promise was not returned :(";\n') | |
8160 | ||
8161 | self.do_runf(test_file('hello_world.c'), | |
8162 | post_build=post, | |
8163 | expected_output='hello, world!') | |
8174 | self.do_runf(test_file('hello_world.c'), expected_output='hello, world!') | |
8164 | 8175 | |
8165 | 8176 | @no_asan('SAFE_HEAP cannot be used with ASan') |
8166 | 8177 | def test_safe_heap_user_js(self): |
8241 | 8252 | def test_pthread_create_pool(self): |
8242 | 8253 | # with a pool, we can synchronously depend on workers being available |
8243 | 8254 | self.set_setting('PTHREAD_POOL_SIZE', '2') |
8255 | self.set_setting('EXIT_RUNTIME') | |
8244 | 8256 | self.emcc_args += ['-DALLOW_SYNC'] |
8245 | 8257 | self.do_run_in_out_file_test('core/pthread/create.cpp') |
8246 | 8258 | |
8248 | 8260 | def test_pthread_create_proxy(self): |
8249 | 8261 | # with PROXY_TO_PTHREAD, we can synchronously depend on workers being available |
8250 | 8262 | self.set_setting('PROXY_TO_PTHREAD') |
8263 | self.set_setting('EXIT_RUNTIME') | |
8251 | 8264 | self.emcc_args += ['-DALLOW_SYNC'] |
8252 | 8265 | self.do_run_in_out_file_test('core/pthread/create.cpp') |
8253 | 8266 | |
8255 | 8268 | def test_pthread_create_embind_stack_check(self): |
8256 | 8269 | # embind should work with stack overflow checks (see #12356) |
8257 | 8270 | self.set_setting('STACK_OVERFLOW_CHECK', 2) |
8271 | self.set_setting('EXIT_RUNTIME') | |
8258 | 8272 | self.emcc_args += ['--bind'] |
8259 | 8273 | self.do_run_in_out_file_test('core/pthread/create.cpp') |
8260 | 8274 | |
8261 | 8275 | @node_pthreads |
8262 | 8276 | def test_pthread_exceptions(self): |
8263 | 8277 | self.set_setting('PTHREAD_POOL_SIZE', '2') |
8278 | self.set_setting('EXIT_RUNTIME') | |
8264 | 8279 | self.emcc_args += ['-fexceptions'] |
8265 | 8280 | self.do_run_in_out_file_test('core/pthread/exceptions.cpp') |
8266 | 8281 | |
8503 | 8518 | err = self.expect_fail([EMCC, 'connect.c', '-sREVERSE_DEPS=none']) |
8504 | 8519 | self.assertContained('undefined symbol: ntohs', err) |
8505 | 8520 | |
8521 | def test_emscripten_async_call(self): | |
8522 | self.set_setting('EXIT_RUNTIME') | |
8523 | self.do_run_in_out_file_test(test_file('core/test_emscripten_async_call.c')) | |
8524 | ||
8506 | 8525 | |
8507 | 8526 | # Generate tests for everything |
8508 | 8527 | def make_run(name, emcc_args, settings=None, env=None): |
9 | 9 | if __name__ == '__main__': |
10 | 10 | raise Exception('do not run this file directly; do something like: tests/runner.py interactive') |
11 | 11 | |
12 | from runner import parameterized | |
13 | from runner import BrowserCore, test_file | |
12 | from common import parameterized | |
13 | from common import BrowserCore, test_file | |
14 | 14 | from tools.shared import WINDOWS |
15 | 15 | from tools.utils import which |
16 | 16 |
31 | 31 | from tools.shared import try_delete, config |
32 | 32 | from tools.shared import EMCC, EMXX, EMAR, EMRANLIB, PYTHON, FILE_PACKAGER, WINDOWS, EM_BUILD_VERBOSE |
33 | 33 | from tools.shared import CLANG_CC, CLANG_CXX, LLVM_AR, LLVM_DWARFDUMP, EMCMAKE, EMCONFIGURE |
34 | from runner import RunnerCore, path_from_root, is_slow_test, ensure_dir, disabled, make_executable | |
35 | from runner import env_modify, no_mac, no_windows, requires_native_clang, with_env_modify | |
36 | from runner import create_file, parameterized, NON_ZERO, node_pthreads, TEST_ROOT, test_file | |
37 | from runner import compiler_for, read_file, read_binary, EMBUILDER, require_v8, require_node | |
34 | from common import RunnerCore, path_from_root, is_slow_test, ensure_dir, disabled, make_executable | |
35 | from common import env_modify, no_mac, no_windows, requires_native_clang, with_env_modify | |
36 | from common import create_file, parameterized, NON_ZERO, node_pthreads, TEST_ROOT, test_file | |
37 | from common import compiler_for, read_file, read_binary, EMBUILDER, require_v8, require_node | |
38 | 38 | from tools import shared, building, utils, deps_info |
39 | import common | |
39 | 40 | import jsrun |
40 | 41 | import clang_native |
41 | 42 | from tools import line_endings |
47 | 48 | emsize = shared.bat_suffix(path_from_root('emsize')) |
48 | 49 | wasm_dis = Path(building.get_binaryen_bin(), 'wasm-dis') |
49 | 50 | wasm_opt = Path(building.get_binaryen_bin(), 'wasm-opt') |
50 | ||
51 | EMTEST_REBASELINE = int(os.getenv('EMTEST_REBASELINE', '0')) | |
52 | 51 | |
53 | 52 | |
54 | 53 | class temp_directory(): |
511 | 510 | self.clear() |
512 | 511 | wasm = '=0' not in str(mode) |
513 | 512 | print(' mode', mode, 'wasm?', wasm) |
514 | self.run_process([EMCC, test_file('hello_world.c')] + opts + mode) | |
513 | self.run_process([EMCC, test_file('hello_world.c'), '-sENVIRONMENT=node,shell'] + opts + mode) | |
515 | 514 | self.assertExists('a.out.js') |
516 | 515 | if wasm: |
517 | 516 | self.assertExists('a.out.wasm') |
2601 | 2600 | def test_demangle_malloc_infinite_loop_crash(self): |
2602 | 2601 | self.run_process([EMXX, test_file('malloc_demangle_infinite_loop.cpp'), '-g', '-s', 'ABORTING_MALLOC', '-s', 'DEMANGLE_SUPPORT']) |
2603 | 2602 | output = self.run_js('a.out.js', assert_returncode=NON_ZERO) |
2604 | if output.count('Cannot enlarge memory arrays') > 4: | |
2603 | if output.count('Cannot enlarge memory arrays') > 5: | |
2605 | 2604 | print(output) |
2606 | self.assertLess(output.count('Cannot enlarge memory arrays'), 5) | |
2605 | self.assertLess(output.count('Cannot enlarge memory arrays'), 6) | |
2607 | 2606 | |
2608 | 2607 | @require_node |
2609 | 2608 | def test_module_exports_with_closure(self): |
2755 | 2754 | return 0; |
2756 | 2755 | } |
2757 | 2756 | ''') |
2758 | self.run_process([EMXX, 'src.cpp', '--embed-file', 'src.cpp']) | |
2757 | self.run_process([EMXX, 'src.cpp', '--embed-file', 'src.cpp', '-sENVIRONMENT=node,shell']) | |
2759 | 2758 | for engine in config.JS_ENGINES: |
2760 | 2759 | out = self.run_js('a.out.js', engine=engine) |
2761 | 2760 | self.assertContained('File size: 724', out) |
3879 | 3878 | self.run_process([EMXX, 'code.cpp']) |
3880 | 3879 | self.assertContained('I am ' + os.path.realpath(self.get_dir()).replace('\\', '/') + '/a.out.js', self.run_js('a.out.js').replace('\\', '/')) |
3881 | 3880 | |
3882 | def test_returncode(self): | |
3881 | @parameterized({ | |
3882 | 'no_exit_runtime': [True], | |
3883 | '': [False], | |
3884 | }) | |
3885 | def test_returncode(self, no_exit): | |
3883 | 3886 | create_file('src.cpp', r''' |
3884 | 3887 | #include <stdio.h> |
3885 | 3888 | #include <stdlib.h> |
3892 | 3895 | } |
3893 | 3896 | ''') |
3894 | 3897 | for code in [0, 123]: |
3895 | for no_exit in [0, 1]: | |
3896 | for call_exit in [0, 1]: | |
3897 | for async_compile in [0, 1]: | |
3898 | self.run_process([EMXX, 'src.cpp', '-DCODE=%d' % code, '-s', 'EXIT_RUNTIME=%d' % (1 - no_exit), '-DCALL_EXIT=%d' % call_exit, '-s', 'WASM_ASYNC_COMPILATION=%d' % async_compile]) | |
3899 | for engine in config.JS_ENGINES: | |
3900 | # async compilation can't return a code in d8 | |
3901 | if async_compile and engine == config.V8_ENGINE: | |
3902 | continue | |
3903 | print(code, no_exit, call_exit, async_compile, engine) | |
3904 | proc = self.run_process(engine + ['a.out.js'], stderr=PIPE, check=False) | |
3905 | # we always emit the right exit code, whether we exit the runtime or not | |
3906 | self.assertEqual(proc.returncode, code) | |
3907 | msg = 'but EXIT_RUNTIME is not set, so halting execution but not exiting the runtime or preventing further async execution (build with EXIT_RUNTIME=1, if you want a true shutdown)' | |
3908 | if no_exit and call_exit: | |
3909 | self.assertContained(msg, proc.stderr) | |
3910 | else: | |
3911 | self.assertNotContained(msg, proc.stderr) | |
3898 | for call_exit in [0, 1]: | |
3899 | for async_compile in [0, 1]: | |
3900 | self.run_process([EMXX, 'src.cpp', '-sENVIRONMENT=node,shell', '-DCODE=%d' % code, '-s', 'EXIT_RUNTIME=%d' % (1 - no_exit), '-DCALL_EXIT=%d' % call_exit, '-s', 'WASM_ASYNC_COMPILATION=%d' % async_compile]) | |
3901 | for engine in config.JS_ENGINES: | |
3902 | # async compilation can't return a code in d8 | |
3903 | if async_compile and engine == config.V8_ENGINE: | |
3904 | continue | |
3905 | print(code, call_exit, async_compile, engine) | |
3906 | proc = self.run_process(engine + ['a.out.js'], stderr=PIPE, check=False) | |
3907 | msg = 'but EXIT_RUNTIME is not set, so halting execution but not exiting the runtime or preventing further async execution (build with EXIT_RUNTIME=1, if you want a true shutdown)' | |
3908 | if no_exit and call_exit: | |
3909 | self.assertContained(msg, proc.stderr) | |
3910 | else: | |
3911 | self.assertNotContained(msg, proc.stderr) | |
3912 | # we always emit the right exit code, whether we exit the runtime or not | |
3913 | self.assertEqual(proc.returncode, code) | |
3912 | 3914 | |
3913 | 3915 | def test_emscripten_force_exit_NO_EXIT_RUNTIME(self): |
3914 | 3916 | create_file('src.cpp', r''' |
4225 | 4227 | # a program which includes a non-trivial syscall, but disables the filesystem. |
4226 | 4228 | create_file('src.c', r''' |
4227 | 4229 | #include <sys/time.h> |
4228 | #include <stddef.h> | |
4229 | extern int __sys_openat(int); | |
4230 | #include <fcntl.h> | |
4230 | 4231 | int main() { |
4231 | return __sys_openat(0); | |
4232 | return openat(0, "foo", 0); | |
4232 | 4233 | }''') |
4233 | 4234 | self.run_process([EMCC, 'src.c', '-s', 'NO_FILESYSTEM']) |
4234 | 4235 | |
5722 | 5723 | self.assertContained('hello1_val by hello1:3', out) |
5723 | 5724 | self.assertContained('hello1_val by hello2:3', out) |
5724 | 5725 | |
5726 | def test_dlopen_blocking(self): | |
5727 | create_file('side.c', 'int foo = 42;\n') | |
5728 | self.run_process([EMCC, 'side.c', '-o', 'libside.so', '-s', 'SIDE_MODULE']) | |
5729 | self.set_setting('MAIN_MODULE', 2) | |
5730 | self.set_setting('EXIT_RUNTIME') | |
5731 | # Under node this should should always works because we can do synchronous readBinary | |
5732 | self.do_other_test('test_dlopen_blocking.c') | |
5733 | ||
5725 | 5734 | def test_dlsym_rtld_default(self): |
5726 | 5735 | create_file('main.c', r''' |
5727 | 5736 | #include <stdio.h> |
6962 | 6971 | def assertFileContents(self, filename, contents): |
6963 | 6972 | contents = contents.replace('\r', '') |
6964 | 6973 | |
6965 | if EMTEST_REBASELINE: | |
6974 | if common.EMTEST_REBASELINE: | |
6966 | 6975 | with open(filename, 'w') as f: |
6967 | 6976 | f.write(contents) |
6968 | 6977 | return |
7027 | 7036 | # measure the wasm size without the name section |
7028 | 7037 | self.run_process([wasm_opt, 'a.out.wasm', '--strip-debug', '--all-features', '-o', 'a.out.nodebug.wasm']) |
7029 | 7038 | wasm_size = os.path.getsize('a.out.nodebug.wasm') |
7030 | if EMTEST_REBASELINE: | |
7039 | if common.EMTEST_REBASELINE: | |
7031 | 7040 | with open(size_file, 'w') as f: |
7032 | 7041 | f.write(f'{wasm_size}\n') |
7033 | 7042 | |
7090 | 7099 | |
7091 | 7100 | @node_pthreads |
7092 | 7101 | def test_metadce_minimal_pthreads(self): |
7093 | self.run_metadce_test('minimal.c', ['-Oz', '-sUSE_PTHREADS', '-sPROXY_TO_PTHREAD'], [], []) | |
7102 | self.run_metadce_test('minimal_main.c', ['-Oz', '-sUSE_PTHREADS', '-sPROXY_TO_PTHREAD'], [], []) | |
7094 | 7103 | |
7095 | 7104 | @parameterized({ |
7096 | 7105 | 'noexcept': (['-O2'], [], ['waka']), # noqa |
8281 | 8290 | proc = self.run_process([EMCC, test_file('hello_world.c'), '-s', 'ASYNCIFY', '-s', "ASYNCIFY_ONLY=[DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)]"], stdout=PIPE, stderr=PIPE) |
8282 | 8291 | self.assertContained('emcc: ASYNCIFY list contains an item without balanced parentheses', proc.stderr) |
8283 | 8292 | self.assertContained(' DOS_ReadFile(unsigned short', proc.stderr) |
8284 | self.assertContained('Try to quote the entire argument', proc.stderr) | |
8293 | self.assertContained('Try using a response file', proc.stderr) | |
8285 | 8294 | |
8286 | 8295 | def test_asyncify_response_file(self): |
8287 | 8296 | create_file('a.txt', r'''[ |
8781 | 8790 | try: |
8782 | 8791 | expected_results = json.loads(read_file(results_file)) |
8783 | 8792 | except Exception: |
8784 | if not EMTEST_REBASELINE: | |
8793 | if not common.EMTEST_REBASELINE: | |
8785 | 8794 | raise |
8786 | 8795 | |
8787 | 8796 | args = [EMCC, '-o', 'a.html'] + args + sources |
8850 | 8859 | # happens in wasm2js, so it may be platform-nondeterminism in closure |
8851 | 8860 | # compiler). |
8852 | 8861 | # TODO: identify what is causing this. meanwhile allow some amount of slop |
8853 | if not EMTEST_REBASELINE: | |
8862 | if not common.EMTEST_REBASELINE: | |
8854 | 8863 | if js: |
8855 | 8864 | slop = 30 |
8856 | 8865 | else: |
8871 | 8880 | print('Total output size=' + str(total_output_size) + ' bytes, expected total size=' + str(total_expected_size) + ', delta=' + str(total_output_size - total_expected_size) + print_percent(total_output_size, total_expected_size)) |
8872 | 8881 | print('Total output size gzipped=' + str(total_output_size_gz) + ' bytes, expected total size gzipped=' + str(total_expected_size_gz) + ', delta=' + str(total_output_size_gz - total_expected_size_gz) + print_percent(total_output_size_gz, total_expected_size_gz)) |
8873 | 8882 | |
8874 | if EMTEST_REBASELINE: | |
8883 | if common.EMTEST_REBASELINE: | |
8875 | 8884 | open(results_file, 'w').write(json.dumps(obtained_results, indent=2) + '\n') |
8876 | 8885 | else: |
8877 | 8886 | if total_output_size > total_expected_size: |
9172 | 9181 | # DEFAULT_PTHREAD_STACK_SIZE. |
9173 | 9182 | self.do_smart_test(test_file('other/test_proxy_to_pthread_stack.c'), |
9174 | 9183 | ['success'], |
9175 | emcc_args=['-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD', '-s', 'DEFAULT_PTHREAD_STACK_SIZE=64kb', '-s', 'TOTAL_STACK=128kb']) | |
9184 | emcc_args=['-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD', '-s', 'DEFAULT_PTHREAD_STACK_SIZE=64kb', '-s', 'TOTAL_STACK=128kb', '-s', 'EXIT_RUNTIME']) | |
9176 | 9185 | |
9177 | 9186 | @parameterized({ |
9178 | 9187 | 'async': ['-s', 'WASM_ASYNC_COMPILATION'], |
9237 | 9246 | |
9238 | 9247 | def test_INCOMING_MODULE_JS_API(self): |
9239 | 9248 | def test(args): |
9240 | self.run_process([EMCC, test_file('hello_world.c'), '-O3', '--closure=1'] + args) | |
9249 | self.run_process([EMCC, test_file('hello_world.c'), '-O3', '--closure=1', '-sENVIRONMENT=node,shell'] + args) | |
9241 | 9250 | for engine in config.JS_ENGINES: |
9242 | 9251 | self.assertContained('hello, world!', self.run_js('a.out.js', engine=engine)) |
9243 | 9252 | # ignore \r which on windows can increase the size |
9248 | 9257 | # Changing this option to [] should decrease code size. |
9249 | 9258 | self.assertLess(changed, normal) |
9250 | 9259 | # Check an absolute code size as well, with some slack. |
9251 | self.assertLess(abs(changed - 5872), 150) | |
9260 | self.assertLess(abs(changed - 5001), 150) | |
9252 | 9261 | |
9253 | 9262 | def test_llvm_includes(self): |
9254 | 9263 | create_file('atomics.c', '#include <stdatomic.h>') |
9581 | 9590 | |
9582 | 9591 | def test_non_wasm_without_wasm_in_vm(self): |
9583 | 9592 | # Test that our non-wasm output does not depend on wasm support in the vm. |
9584 | self.run_process([EMXX, test_file('hello_world.cpp'), '-s', 'WASM=0']) | |
9593 | self.run_process([EMXX, test_file('hello_world.cpp'), '-s', 'WASM=0', '-sENVIRONMENT=node,shell']) | |
9585 | 9594 | js = read_file('a.out.js') |
9586 | 9595 | with open('a.out.js', 'w') as f: |
9587 | 9596 | f.write('var WebAssembly = null;\n' + js) |
10306 | 10315 | def test_syslog(self): |
10307 | 10316 | self.do_other_test('test_syslog.c') |
10308 | 10317 | |
10309 | def test_split_module(self): | |
10318 | @parameterized({ | |
10319 | '': (False,), | |
10320 | 'custom': (True,), | |
10321 | }) | |
10322 | def test_split_module(self, customLoader): | |
10310 | 10323 | self.set_setting('SPLIT_MODULE') |
10311 | 10324 | self.emcc_args += ['-g', '-Wno-experimental'] |
10312 | 10325 | self.emcc_args += ['--post-js', test_file('other/test_split_module.post.js')] |
10326 | if customLoader: | |
10327 | self.emcc_args += ['--pre-js', test_file('other/test_load_split_module.pre.js')] | |
10313 | 10328 | self.emcc_args += ['-sEXPORTED_FUNCTIONS=_malloc,_free'] |
10314 | 10329 | self.do_other_test('test_split_module.c') |
10315 | 10330 | self.assertExists('test_split_module.wasm') |
10324 | 10339 | os.rename('secondary.wasm', 'test_split_module.deferred.wasm') |
10325 | 10340 | result = self.run_js('test_split_module.js') |
10326 | 10341 | self.assertNotIn('profile', result) |
10342 | self.assertContainedIf('Custom handler for loading split module.', result, condition=customLoader) | |
10327 | 10343 | self.assertIn('Hello! answer: 42', result) |
10328 | 10344 | |
10329 | 10345 | def test_split_main_module(self): |
10369 | 10385 | def test_gen_struct_info(self): |
10370 | 10386 | # This tests is fragile and will need updating any time any of the refereced |
10371 | 10387 | # structs or defines change. However its easy to rebaseline with |
10372 | # EMTEST_REBASELINE and it prrevents regressions or unintended changes | |
10388 | # EMTEST_REBASELINE and it prevents regressions or unintended changes | |
10373 | 10389 | # to the output json. |
10374 | 10390 | self.run_process([PYTHON, path_from_root('tools/gen_struct_info.py'), '-o', 'out.json']) |
10375 | 10391 | self.assertFileContents(test_file('reference_struct_info.json'), read_file('out.json')) |
10433 | 10449 | cmd.append('-sUSE_OFFSET_CONVERTER') |
10434 | 10450 | if 'embind' in function: |
10435 | 10451 | cmd.append('--bind') |
10436 | if 'fetch' in function: | |
10437 | cmd.append('-sFETCH') | |
10438 | 10452 | if 'websocket' in function: |
10439 | 10453 | cmd += ['-sPROXY_POSIX_SOCKETS', '-lwebsocket.js'] |
10440 | 10454 | if function == 'Mix_LoadWAV_RW': |
10483 | 10497 | def test_shell_Oz(self): |
10484 | 10498 | # regression test for -Oz working on non-web, non-node environments that |
10485 | 10499 | # lack TextDecoder |
10486 | self.run_process([EMCC, test_file('hello_world.c'), '-Oz']) | |
10487 | self.assertContained('hello, world!', self.run_js('a.out.js')) | |
10500 | self.emcc_args += ['-Oz'] | |
10501 | self.do_runf(test_file('hello_world.c'), 'hello, world!') | |
10488 | 10502 | |
10489 | 10503 | def test_runtime_keepalive(self): |
10490 | 10504 | self.uses_es6 = True |
10656 | 10670 | # Here we use NO_AUTO_NATIVE_LIBRARIES to disable the implictly linking that normally |
10657 | 10671 | # includes the native GL library. |
10658 | 10672 | self.run_process([EMCC, test_file('other/test_explict_gl_linking.c'), '-sNO_AUTO_NATIVE_LIBRARIES', '-lGL']) |
10673 | ||
10674 | def test_no_main_with_PROXY_TO_PTHREAD(self): | |
10675 | create_file('lib.cpp', r''' | |
10676 | #include <emscripten.h> | |
10677 | EMSCRIPTEN_KEEPALIVE | |
10678 | void foo() {} | |
10679 | ''') | |
10680 | err = self.expect_fail([EMCC, 'lib.cpp', '-pthread', '-sPROXY_TO_PTHREAD']) | |
10681 | self.assertContained('emcc: error: PROXY_TO_PTHREAD proxies main() for you, but no main exists', err) | |
10682 | ||
10683 | def test_archive_bad_extension(self): | |
10684 | # Regression test for https://github.com/emscripten-core/emscripten/issues/14012 | |
10685 | # where llvm_nm_multiple would be confused by archives names like object files. | |
10686 | create_file('main.c', ''' | |
10687 | #include <sys/socket.h> | |
10688 | int main() { | |
10689 | return (int)&accept; | |
10690 | } | |
10691 | ''') | |
10692 | ||
10693 | self.run_process([EMCC, '-c', 'main.c']) | |
10694 | self.run_process([EMAR, 'crs', 'libtest.bc', 'main.o']) | |
10695 | self.run_process([EMCC, 'libtest.bc', 'libtest.bc']) | |
10696 | ||
10697 | def test_split_dwarf_implicit_compile(self): | |
10698 | # Verify that the dwo file is generated in the current working directory, even when implicitly | |
10699 | # compiling (compile+link). | |
10700 | self.run_process([EMCC, test_file('hello_world.c'), '-g', '-gsplit-dwarf']) | |
10701 | self.assertExists('hello_world.dwo') | |
10702 | ||
10703 | @parameterized({ | |
10704 | '': [[]], | |
10705 | 'strict': [['-sSTRICT']], | |
10706 | 'no_allow': [['-sALLOW_UNIMPLEMENTED_SYSCALLS=0']], | |
10707 | }) | |
10708 | def test_unimplemented_syscalls(self, args): | |
10709 | create_file('main.c', ''' | |
10710 | #include <assert.h> | |
10711 | #include <errno.h> | |
10712 | #include <sys/mman.h> | |
10713 | ||
10714 | int main() { | |
10715 | assert(mincore(0, 0, 0) == -1); | |
10716 | assert(errno == ENOSYS); | |
10717 | return 0; | |
10718 | } | |
10719 | ''') | |
10720 | cmd = [EMCC, 'main.c', '-sASSERTIONS'] + args | |
10721 | if args: | |
10722 | err = self.expect_fail(cmd) | |
10723 | self.assertContained('error: attempt to link unsupport syscall: __sys_mincore (use -s ALLOW_UNIMPLEMENTED_SYSCALLS (the default) to allow linking with a stub version', err) | |
10724 | else: | |
10725 | self.run_process(cmd) | |
10726 | err = self.run_js('a.out.js') | |
10727 | self.assertContained('warning: unsupported syscall: __sys_mincore', err) | |
10728 | ||
10729 | @require_v8 | |
10730 | def test_missing_shell_support(self): | |
10731 | # By default shell support is not included | |
10732 | self.run_process([EMCC, test_file('hello_world.c')]) | |
10733 | err = self.run_js('a.out.js', assert_returncode=NON_ZERO) | |
10734 | self.assertContained('shell environment detected but not enabled at build time.', err) |
5 | 5 | |
6 | 6 | GLuint what_got_created(void); |
7 | 7 | |
8 | int main() | |
9 | { | |
8 | int main() { | |
10 | 9 | EmscriptenWebGLContextAttributes attr; |
11 | 10 | emscripten_webgl_init_context_attributes(&attr); |
12 | 11 | EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context("#canvas", &attr); |
21 | 20 | GLuint whatGotCreated = what_got_created(); |
22 | 21 | printf("Created texture of type 0x%x\n", whatGotCreated); |
23 | 22 | assert(createType == whatGotCreated); |
24 | #ifdef REPORT_RESULT | |
25 | REPORT_RESULT(whatGotCreated); | |
26 | #endif | |
23 | return 0; | |
27 | 24 | } |
10 | 10 | |
11 | 11 | import glob |
12 | 12 | import os |
13 | import unittest | |
13 | 14 | |
14 | from runner import RunnerCore, path_from_root | |
15 | from tools import config | |
16 | from tools.shared import EMCC | |
15 | from common import RunnerCore, path_from_root, node_pthreads | |
17 | 16 | import test_posixtest_browser |
18 | 17 | |
19 | 18 | testsuite_root = path_from_root('tests/third_party/posixtestsuite') |
30 | 29 | def filter_tests(all_tests): |
31 | 30 | pthread_tests = [t for t in all_tests if t.startswith('pthread_')] |
32 | 31 | # filter out some tests we don't support |
33 | pthread_tests = [t for t in pthread_tests if not t.startswith('pthread_atfork')] | |
34 | 32 | pthread_tests = [t for t in pthread_tests if not t.startswith('pthread_sigmask')] |
35 | 33 | return pthread_tests |
36 | 34 | |
47 | 45 | return pthread_tests |
48 | 46 | |
49 | 47 | |
50 | engine = config.NODE_JS + ['--experimental-wasm-threads', '--experimental-wasm-bulk-memory'] | |
51 | ||
52 | 48 | # Mark certain tests as unsupported |
53 | 49 | # TODO: Investigate failing semaphores tests. |
54 | unsupported = { | |
50 | unsupported_noreturn = { | |
55 | 51 | 'test_pthread_atfork_1_1': 'fork() and multiple processes are not supported', |
56 | 52 | 'test_pthread_atfork_1_2': 'fork() and multiple processes are not supported', |
57 | 53 | 'test_pthread_atfork_2_1': 'fork() and multiple processes are not supported', |
58 | 54 | 'test_pthread_atfork_2_2': 'fork() and multiple processes are not supported', |
59 | 55 | 'test_pthread_atfork_3_2': 'fork() and multiple processes are not supported', |
60 | 56 | 'test_pthread_atfork_4_1': 'fork() and multiple processes are not supported', |
57 | 'test_pthread_kill_1_1': 'signals are not supported', | |
58 | 'test_pthread_create_1_5': 'semaphores are not supported', | |
59 | 'test_pthread_exit_6_1': 'lacking necessary mmap() support', | |
60 | 'test_pthread_spin_lock_1_1': 'signals are not supported', | |
61 | 'test_pthread_mutex_lock_5_1': 'signals are not supported', | |
62 | 'test_pthread_mutexattr_settype_2_1': 'interrupting pthread_mutex_lock wait via SIGALRM is not supported', | |
63 | 'test_pthread_spin_lock_3_1': 'signals are not supported', | |
64 | 'test_pthread_mutex_lock_3_1': 'signals are not supported', | |
65 | 'test_pthread_create_14_1': 'signals are not supported', | |
66 | 'test_pthread_detach_4_3': 'signals are not supported', | |
67 | 'test_pthread_join_6_3': 'signals are not supported', | |
68 | 'test_pthread_cond_init_4_2': 'signals are not supported', | |
69 | 'test_pthread_barrier_wait_3_2': 'signals are not supported', | |
70 | } | |
71 | ||
72 | unsupported = { | |
61 | 73 | 'test_pthread_attr_setinheritsched_2_3': 'scheduling policy/parameters are not supported', |
62 | 74 | 'test_pthread_attr_setinheritsched_2_4': 'scheduling policy/parameters are not supported', |
63 | 75 | 'test_pthread_attr_setschedparam_1_3': 'scheduling policy/parameters are not supported', |
65 | 77 | 'test_pthread_attr_setschedpolicy_4_1': 'scheduling policy/parameters are not supported', |
66 | 78 | 'test_pthread_barrierattr_getpshared_2_1': 'shm_open and shm_unlink are not supported', |
67 | 79 | 'test_pthread_barrier_wait_3_1': 'signals are not supported', |
68 | 'test_pthread_barrier_wait_3_2': 'signals are not supported', | |
69 | 'test_pthread_cancel_5_2': 'signals are not supported', | |
70 | 80 | 'test_pthread_cond_broadcast_1_2': 'lacking necessary mmap() support', |
71 | 81 | 'test_pthread_cond_broadcast_2_3': 'lacking necessary mmap() support', |
72 | 'test_pthread_cond_broadcast_4_2': 'signals are not supported', | |
73 | 82 | 'test_pthread_cond_destroy_2_1': 'lacking necessary mmap() support', |
74 | 83 | 'test_pthread_cond_init_1_2': 'clock_settime() is not supported', |
75 | 84 | 'test_pthread_cond_init_1_3': 'lacking necessary mmap() support', |
76 | 85 | 'test_pthread_cond_init_2_2': 'clock_settime() is not supported', |
77 | 86 | 'test_pthread_cond_init_4_1': 'fork() and multiple processes are not supported', |
78 | 'test_pthread_cond_init_4_2': 'signals are not supported', | |
79 | 87 | 'test_pthread_cond_signal_1_2': 'lacking necessary mmap() support', |
80 | 'test_pthread_cond_signal_4_2': 'signals are not supported', | |
81 | 88 | 'test_pthread_cond_timedwait_2_4': 'lacking necessary mmap() support', |
82 | 89 | 'test_pthread_cond_timedwait_2_7': 'lacking necessary mmap() support', |
83 | 90 | 'test_pthread_cond_timedwait_4_2': 'lacking necessary mmap() support', |
84 | 91 | 'test_pthread_cond_wait_2_2': 'lacking necessary mmap() support', |
85 | 'test_pthread_cond_wait_4_1': 'fork() and multiple processes are not supported', | |
86 | 'test_pthread_create_1_5': 'semaphores are not supported', | |
87 | 'test_pthread_create_1_6': 'semaphores are not supported', | |
88 | 92 | 'test_pthread_create_8_1': 'signals are not supported', |
89 | 93 | 'test_pthread_create_8_2': 'signals are not supported', |
90 | 94 | 'test_pthread_create_10_1': 'signals are not supported', |
91 | 'test_pthread_create_14_1': 'signals are not supported', | |
92 | 'test_pthread_create_15_1': 'signals are not supported', | |
93 | 'test_pthread_detach_4_3': 'signals are not supported', | |
94 | 'test_pthread_equal_2_1': 'signals are not supported', | |
95 | 'test_pthread_exit_6_1': 'lacking necessary mmap() support', | |
96 | 'test_pthread_exit_6_2': 'semaphores are not supported', | |
97 | 95 | 'test_pthread_getschedparam_1_3': 'scheduling policy/parameters are not supported', |
98 | 'test_pthread_getschedparam_4_1': 'signals are not supported', | |
99 | 'test_pthread_join_6_3': 'signals are not supported', | |
100 | 'test_pthread_kill_1_1': 'signals are not supported', | |
101 | 96 | 'test_pthread_kill_1_2': 'signals are not supported', |
102 | 'test_pthread_kill_8_1': 'signals are not supported', | |
103 | 97 | 'test_pthread_mutexattr_getprioceiling_1_2': 'pthread_mutexattr_setprioceiling is not supported', |
104 | 98 | 'test_pthread_mutexattr_getprotocol_1_2': 'pthread_mutexattr_setprotocol is not supported', |
105 | 99 | 'test_pthread_mutexattr_setprioceiling_1_1': 'pthread_mutexattr_setprioceiling is not supported', |
106 | 100 | 'test_pthread_mutexattr_setprioceiling_3_1': 'pthread_mutexattr_setprioceiling is not supported', |
107 | 101 | 'test_pthread_mutexattr_setprioceiling_3_2': 'pthread_mutexattr_setprioceiling is not supported', |
108 | 102 | 'test_pthread_mutexattr_setprotocol_1_1': 'setting pthread_mutexattr_setprotocol to a nonzero value is not supported', |
109 | 'test_pthread_mutexattr_settype_2_1': 'interrupting pthread_mutex_lock wait via SIGALRM is not supported', | |
110 | 103 | 'test_pthread_mutex_getprioceiling_1_1': 'pthread_mutex_getprioceiling is not supported', |
111 | 'test_pthread_mutex_init_1_2': 'signals are not supported', | |
112 | 104 | 'test_pthread_mutex_init_5_1': 'fork() and multiple processes are not supported', |
113 | 'test_pthread_mutex_lock_3_1': 'signals are not supported', | |
114 | 'test_pthread_mutex_lock_5_1': 'signals are not supported', | |
115 | 105 | 'test_pthread_mutex_trylock_1_2': 'lacking necessary mmap() support', |
116 | 106 | 'test_pthread_mutex_trylock_2_1': 'lacking necessary mmap() support', |
117 | 107 | 'test_pthread_mutex_trylock_4_2': 'lacking necessary mmap() support', |
118 | 'test_pthread_mutex_trylock_4_3': 'signals are not supported', | |
119 | 'test_pthread_once_6_1': 'signals are not supported', | |
120 | 108 | 'test_pthread_rwlockattr_getpshared_2_1': 'shm_open and shm_unlink are not supported', |
121 | 109 | 'test_pthread_rwlock_rdlock_2_1': 'thread priorities not supported, cannot test rwlocking in priority order', |
122 | 110 | 'test_pthread_rwlock_rdlock_2_2': 'thread priorities not supported, cannot test rwlocking in priority order', |
125 | 113 | 'test_pthread_rwlock_timedrdlock_6_2': 'signals are not supported', |
126 | 114 | 'test_pthread_rwlock_timedwrlock_6_1': 'signals are not supported', |
127 | 115 | 'test_pthread_rwlock_timedwrlock_6_2': 'signals are not supported', |
128 | 'test_pthread_rwlock_unlock_3_1': 'thread priorities not supported, cannot test rwlocking in priority order', | |
129 | 116 | 'test_pthread_rwlock_wrlock_2_1': 'signals are not supported', |
130 | 'test_pthread_setschedparam_5_1': 'signals are not supported', | |
131 | 117 | 'test_pthread_spin_init_2_1': 'shm_open and shm_unlink are not supported', |
132 | 118 | 'test_pthread_spin_init_2_2': 'shm_open and shm_unlink are not supported', |
133 | 'test_pthread_spin_lock_1_1': 'signals are not supported', | |
134 | 'test_pthread_spin_lock_3_1': 'signals are not supported', | |
135 | 119 | } |
136 | 120 | |
137 | 121 | # Mark certain tests as flaky, which may sometimes fail. |
138 | # TODO invesigate these tests. | |
122 | # TODO investigate these tests. | |
139 | 123 | flaky = { |
140 | 124 | 'test_pthread_cond_signal_1_1': 'flaky: https://github.com/emscripten-core/emscripten/issues/13283', |
125 | 'test_pthread_barrier_wait_2_1': 'flaky: https://github.com/emscripten-core/emscripten/issues/14508', | |
126 | 'test_pthread_rwlock_unlock_3_1': 'Test fail: writer did not get write lock, when main release the lock', | |
141 | 127 | } |
142 | 128 | |
143 | # Mark certain tests as not passing | |
129 | # Mark certain tests as disabled. These are tests that are either flaky or never return. | |
144 | 130 | disabled = { |
131 | **flaky, | |
132 | **unsupported_noreturn, | |
133 | } | |
134 | ||
135 | # These tests are known to fail reliably. We run them anyway so that we can | |
136 | # detect fixes overtime. | |
137 | expect_fail = { | |
145 | 138 | **unsupported, |
146 | **flaky, | |
147 | 'test_pthread_create_11_1': 'never returns', | |
148 | 'test_pthread_barrier_wait_2_1': 'never returns', | |
149 | 139 | 'test_pthread_attr_setscope_5_1': 'internally skipped (PTS_UNTESTED)', |
150 | 'test_pthread_create_5_1': 'never returns', | |
151 | 'test_pthread_exit_1_2': 'never returns', | |
152 | 'test_pthread_exit_2_2': 'never returns', | |
153 | 'test_pthread_exit_3_2': 'never returns', | |
154 | 'test_pthread_exit_4_1': 'never returns', | |
155 | 'test_pthread_getcpuclockid_1_1': 'never returns', | |
156 | 'test_pthread_key_create_1_2': 'never returns', | |
157 | 'test_pthread_rwlock_rdlock_1_1': 'fails with "main: Unexpected thread state"', | |
158 | 'test_pthread_rwlock_timedrdlock_1_1': 'fails with "main: Unexpected thread state"', | |
159 | 'test_pthread_rwlock_timedrdlock_3_1': 'fails with "main: Unexpected thread state"', | |
160 | 'test_pthread_rwlock_timedrdlock_5_1': 'fails with "main: Unexpected thread state"', | |
161 | 'test_pthread_rwlock_timedwrlock_1_1': 'fails with "main: Unexpected thread state"', | |
162 | 'test_pthread_rwlock_timedwrlock_3_1': 'fails with "main: Unexpected thread state"', | |
163 | 'test_pthread_rwlock_timedwrlock_5_1': 'fails with "main: Unexpected thread state"', | |
164 | 'test_pthread_rwlock_wrlock_1_1': 'fails with "main: Unexpected thread state"', | |
165 | 'test_pthread_rwlock_trywrlock_1_1': 'fails with "main: Unexpected thread state"', | |
166 | 'test_pthread_spin_destroy_3_1': 'never returns', | |
167 | 'test_pthread_spin_init_4_1': 'never returns', | |
168 | 140 | } |
169 | 141 | |
170 | 142 | |
171 | 143 | def make_test(name, testfile, browser): |
172 | 144 | |
145 | @node_pthreads | |
173 | 146 | def f(self): |
174 | 147 | if name in disabled: |
175 | 148 | self.skipTest(disabled[name]) |
179 | 152 | '-Wno-int-conversion', |
180 | 153 | '-sUSE_PTHREADS', |
181 | 154 | '-sEXIT_RUNTIME', |
182 | '-sTOTAL_MEMORY=268435456', | |
155 | '-sTOTAL_MEMORY=256mb', | |
183 | 156 | '-sPTHREAD_POOL_SIZE=40'] |
184 | 157 | if browser: |
185 | # Only are only needed for browser tests of the was btest | |
186 | # injects headers using `-include` flag. | |
187 | args += ['-Wno-macro-redefined', '-D_GNU_SOURCE'] | |
188 | self.btest(testfile, args=args, expected='exit:0') | |
158 | self.btest_exit(testfile, args=args) | |
189 | 159 | else: |
190 | self.run_process([EMCC, testfile, '-o', 'test.js'] + args) | |
191 | self.run_js('test.js', engine=engine) | |
160 | self.do_runf(testfile, emcc_args=args) | |
161 | ||
162 | if name in expect_fail: | |
163 | f = unittest.expectedFailure(f) | |
192 | 164 | |
193 | 165 | return f |
194 | 166 |
6 | 6 | parallel. |
7 | 7 | """ |
8 | 8 | |
9 | from runner import BrowserCore | |
9 | from common import BrowserCore | |
10 | 10 | |
11 | 11 | |
12 | 12 | class posixtest_browser(BrowserCore): |
11 | 11 | from pathlib import Path |
12 | 12 | from subprocess import PIPE, STDOUT |
13 | 13 | |
14 | from runner import RunnerCore, path_from_root, env_modify, test_file | |
15 | from runner import create_file, ensure_dir, make_executable, with_env_modify | |
16 | from runner import parameterized, EMBUILDER | |
14 | from common import RunnerCore, path_from_root, env_modify, test_file | |
15 | from common import create_file, ensure_dir, make_executable, with_env_modify | |
16 | from common import parameterized, EMBUILDER | |
17 | 17 | from tools.config import EM_CONFIG |
18 | 18 | from tools.shared import EMCC |
19 | 19 | from tools.shared import CANONICAL_TEMP_DIR |
0 | // Copyright 2015 The Emscripten Authors. All rights reserved. | |
1 | // Emscripten is available under two separate licenses, the MIT license and the | |
2 | // University of Illinois/NCSA Open Source License. Both these licenses can be | |
3 | // found in the LICENSE file. | |
4 | ||
5 | #include <stdio.h> | |
6 | #include <stdlib.h> | |
7 | #include <signal.h> | |
8 | #include <unistd.h> | |
9 | #include <pthread.h> | |
10 | ||
11 | void alarm_handler(int dummy) { | |
12 | printf("Received alarm!\n"); | |
13 | exit(0); | |
14 | } | |
15 | ||
16 | int main() { | |
17 | if (signal(SIGALRM, alarm_handler) == SIG_ERR) { | |
18 | printf("Error in signal()!\n"); | |
19 | exit(1); | |
20 | } | |
21 | alarm(1); | |
22 | } |
22 | 22 | # which is the same behavior as before. |
23 | 23 | pass |
24 | 24 | import clang_native |
25 | from runner import BrowserCore, no_windows, create_file, test_file, read_file | |
25 | from common import BrowserCore, no_windows, create_file, test_file, read_file | |
26 | 26 | from tools import shared, config, utils |
27 | 27 | from tools.shared import PYTHON, EMCC, path_from_root, WINDOWS, run_process, CLANG_CC |
28 | 28 |
21 | 21 | glGenVertexArraysOES(1, &vao); |
22 | 22 | assert(vao != 0); |
23 | 23 | } |
24 | #ifdef REPORT_RESULT | |
25 | REPORT_RESULT(0); | |
26 | #endif | |
24 | ||
25 | return 0; | |
27 | 26 | } |
4 | 4 | * found in the LICENSE file. |
5 | 5 | */ |
6 | 6 | |
7 | #include <assert.h> | |
7 | 8 | #include <stdio.h> |
9 | #include <stdlib.h> | |
8 | 10 | #include <ctype.h> |
9 | 11 | #include <string.h> |
10 | 12 | #include <emscripten.h> |
11 | 13 | |
12 | int main() | |
13 | { | |
14 | const char * file = "/test.txt"; | |
15 | emscripten_wget(file , file); | |
16 | FILE * f = fopen(file, "r"); | |
17 | int result = 0; | |
18 | if(f) { | |
19 | 14 | #define BUFSIZE 1024 |
20 | char buf[BUFSIZE]; | |
21 | fgets(buf, BUFSIZE, f); | |
22 | buf[BUFSIZE-1] = 0; | |
23 | for(int i = 0; i < BUFSIZE; ++i) | |
24 | buf[i] = tolower(buf[i]); | |
25 | if(strstr(buf, "emscripten")) | |
26 | result = 1; | |
27 | fclose(f); | |
28 | } | |
29 | REPORT_RESULT(result); | |
30 | return 0; | |
15 | ||
16 | int main() { | |
17 | const char * file = "/test.txt"; | |
18 | printf("calling wget\n"); | |
19 | emscripten_wget(file , file); | |
20 | printf("back from wget\n"); | |
21 | ||
22 | FILE * f = fopen(file, "r"); | |
23 | assert(f); | |
24 | ||
25 | char buf[BUFSIZE]; | |
26 | fgets(buf, BUFSIZE, f); | |
27 | buf[BUFSIZE-1] = 0; | |
28 | for(int i = 0; i < BUFSIZE; ++i) { | |
29 | buf[i] = tolower(buf[i]); | |
30 | } | |
31 | assert(strstr(buf, "emscripten")); | |
32 | fclose(f); | |
33 | ||
34 | printf("exiting main\n"); | |
35 | // Implicit return from main with ASYNCIFY + EXIT_RUNTIME | |
36 | // currently doesn't work so we need to explictly exit. | |
37 | // https://github.com/emscripten-core/emscripten/issues/14417 | |
38 | exit(0); | |
31 | 39 | } |
5 | 5 | */ |
6 | 6 | |
7 | 7 | #include <stdio.h> |
8 | #include <stdlib.h> | |
8 | 9 | #include <ctype.h> |
9 | 10 | #include <string.h> |
10 | 11 | #include <assert.h> |
11 | 12 | |
12 | 13 | #include <emscripten.h> |
13 | 14 | |
14 | int main() | |
15 | { | |
16 | const char * file = "/test.txt"; | |
17 | void* buffer; | |
18 | int num, error; | |
15 | int main() { | |
16 | const char * file = "/test.txt"; | |
17 | void* buffer; | |
18 | int num, error; | |
19 | 19 | |
20 | printf("load %s\n", file); | |
21 | emscripten_wget_data(file, &buffer, &num, &error); | |
22 | assert(!error); | |
23 | printf("buffer: %s\n", (char*)buffer); | |
24 | assert(strstr(buffer, "emscripten") == buffer); | |
20 | printf("load %s\n", file); | |
21 | emscripten_wget_data(file, &buffer, &num, &error); | |
22 | assert(!error); | |
23 | printf("buffer: %s\n", (char*)buffer); | |
24 | assert(strstr(buffer, "emscripten") == buffer); | |
25 | 25 | |
26 | printf("load non-existing\n"); | |
27 | emscripten_wget_data("doesnotexist", &buffer, &num, &error); | |
28 | assert(error); | |
26 | printf("load non-existing\n"); | |
27 | emscripten_wget_data("doesnotexist", &buffer, &num, &error); | |
28 | assert(error); | |
29 | 29 | |
30 | printf("ok!\n"); | |
31 | REPORT_RESULT(1); | |
32 | return 0; | |
30 | printf("ok!\n"); | |
31 | // Implicit return from main with ASYNCIFY + EXIT_RUNTIME | |
32 | // currently doesn't work so we need to explictly exit. | |
33 | // https://github.com/emscripten-core/emscripten/issues/14417 | |
34 | exit(0); | |
33 | 35 | } |
69 | 69 | assert(hasext(exts, "WEBGL_multi_draw_instanced_base_vertex_base_instance") == emscripten_webgl_enable_extension(context, "WEBGL_multi_draw_instanced_base_vertex_base_instance")); |
70 | 70 | #endif |
71 | 71 | |
72 | #ifdef REPORT_RESULT | |
73 | REPORT_RESULT(0); | |
74 | #endif | |
72 | return 0; | |
75 | 73 | } |
116 | 116 | glClearColor(0.3f,0.3f,0.3f,1); |
117 | 117 | glClear(GL_COLOR_BUFFER_BIT); |
118 | 118 | glDrawArrays(GL_TRIANGLES, 0, 3); |
119 | return 0; | |
119 | 120 | } |
108 | 108 | emscripten_webgl_commit_frame(); |
109 | 109 | #endif |
110 | 110 | |
111 | #ifdef REPORT_RESULT | |
112 | REPORT_RESULT(0); | |
113 | #endif | |
111 | return 0; | |
114 | 112 | } |
111 | 111 | emscripten_webgl_commit_frame(); |
112 | 112 | #endif |
113 | 113 | |
114 | #ifdef REPORT_RESULT | |
115 | REPORT_RESULT(0); | |
116 | #endif | |
114 | return 0; | |
117 | 115 | } |
13 | 13 | #include <emscripten/html5.h> |
14 | 14 | #include <GLES2/gl2.h> |
15 | 15 | |
16 | #ifdef REPORT_RESULT | |
17 | #define DIE() do { REPORT_RESULT(1); exit(1); } while (0) | |
18 | #else | |
19 | #define DIE() do { exit(1); } while (0) | |
20 | #endif | |
21 | ||
22 | int main() | |
23 | { | |
16 | int main() { | |
24 | 17 | EmscriptenWebGLContextAttributes attr; |
25 | 18 | emscripten_webgl_init_context_attributes(&attr); |
26 | 19 | attr.explicitSwapControl = 1; |
37 | 30 | #if !TEST_WEBGL2 && TEST_VAO |
38 | 31 | // This test cannot run without browser support for OES_vertex_array_object. |
39 | 32 | if (!emscripten_webgl_enable_extension(ctx, "OES_vertex_array_object")) { |
40 | DIE(); | |
33 | return 1; | |
41 | 34 | } |
42 | 35 | #endif |
43 | 36 | |
48 | 41 | emscripten_webgl_commit_frame(); |
49 | 42 | |
50 | 43 | if (glGetError() != GL_NO_ERROR) { |
51 | DIE(); | |
44 | return 1; | |
52 | 45 | } |
53 | 46 | |
54 | 47 | // C doesn't have access to the "frontbuffer" (canvas contents). |
60 | 53 | return pixels[0] == 0 && pixels[1] == 255 && pixels[2] == 0 && pixels[3] == 255; |
61 | 54 | }); |
62 | 55 | if (!canvas_is_green) { |
63 | DIE(); | |
56 | return 1; | |
64 | 57 | } |
65 | 58 | |
66 | #ifdef REPORT_RESULT | |
67 | REPORT_RESULT(0); | |
68 | #endif | |
59 | return 0; | |
69 | 60 | } |
2 | 2 | // University of Illinois/NCSA Open Source License. Both these licenses can be |
3 | 3 | // found in the LICENSE file. |
4 | 4 | |
5 | #include <stdlib.h> | |
5 | 6 | #include <cassert> |
6 | 7 | #include <cstdio> |
7 | 8 | #include <emscripten.h> |
29 | 30 | GLuint any; |
30 | 31 | GL_CALL(glGetQueryObjectuiv(sampleQuery, GL_QUERY_RESULT, &any)); |
31 | 32 | |
32 | if(!any) return; | |
33 | if (!any) return; | |
33 | 34 | |
34 | 35 | printf("queried any samples passed: %u\n", any); |
35 | 36 | emscripten_cancel_main_loop(); |
36 | 37 | |
37 | 38 | GL_CALL(glDeleteQueries(1, &sampleQuery)); |
38 | 39 | |
39 | #ifdef REPORT_RESULT | |
40 | REPORT_RESULT(result); | |
41 | #endif | |
40 | // result == 0 signals success | |
41 | exit(result); | |
42 | 42 | } |
43 | 43 | |
44 | 44 | GLuint compile_shader(GLenum shaderType, const char *src) |
63 | 63 | return program; |
64 | 64 | } |
65 | 65 | |
66 | int main() | |
67 | { | |
66 | int main() { | |
68 | 67 | EmscriptenWebGLContextAttributes attrs; |
69 | 68 | emscripten_webgl_init_context_attributes(&attrs); |
70 | 69 | |
73 | 72 | attrs.minorVersion = 0; |
74 | 73 | |
75 | 74 | /* Skip WebGL 2 tests if not supported */ |
76 | EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context( "#canvas", &attrs ); | |
77 | if (!context) | |
78 | { | |
75 | EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context("#canvas", &attrs); | |
76 | if (!context) { | |
79 | 77 | printf("Skipped: WebGL 2 is not supported.\n"); |
80 | #ifdef REPORT_RESULT | |
81 | REPORT_RESULT(result); | |
82 | #endif | |
83 | 78 | return 0; |
84 | 79 | } |
85 | 80 | emscripten_webgl_make_context_current(context); |
139 | 134 | /* Run the main loop to get the result */ |
140 | 135 | emscripten_set_main_loop(getQueryResult, 0, 0); |
141 | 136 | |
142 | return 0; | |
137 | return 99; | |
143 | 138 | } |
3 | 3 | // found in the LICENSE file. |
4 | 4 | |
5 | 5 | #include <cassert> |
6 | #include <cstdlib> | |
6 | 7 | #include <cstdio> |
7 | 8 | #include <cstring> |
8 | 9 | #include <emscripten.h> |
51 | 52 | GL_CALL(glDeleteQueriesEXT(1, &timerQuery)); |
52 | 53 | #endif |
53 | 54 | |
54 | #ifdef REPORT_RESULT | |
55 | REPORT_RESULT(result); | |
56 | #endif | |
55 | exit(result); | |
57 | 56 | } |
58 | 57 | |
59 | 58 | int main() |
374 | 374 | */ |
375 | 375 | nodeBuffer.SlowBuffer.prototype.toString = function() {}; |
376 | 376 | |
377 | /** | |
378 | * @param {number} size | |
379 | * @param {(string|!Buffer|number)=} fill | |
380 | * @param {string=} encoding | |
381 | * @return {!Buffer} | |
382 | */ | |
383 | nodeBuffer.Buffer.alloc; | |
384 | ||
385 | /** | |
386 | * @param {Array} aray | |
387 | * @return {!Buffer} | |
388 | */ | |
389 | nodeBuffer.Buffer.from; | |
390 | ||
377 | 391 | // |
378 | 392 | // Legacy |
379 | 393 | // |
112 | 112 | for a in archive_contents: |
113 | 113 | missing_contents = [x for x in a['o_files'] if not os.path.exists(x)] |
114 | 114 | if missing_contents: |
115 | exit_with_error('llvm-ar failed to extract file(s) ' + str(missing_contents) + ' from archive file ' + f + '!') | |
115 | exit_with_error(f'llvm-ar failed to extract file(s) {missing_contents} from archive file {f}!') | |
116 | 116 | |
117 | 117 | return archive_contents |
118 | 118 | |
155 | 155 | return arg |
156 | 156 | |
157 | 157 | |
158 | def get_building_env(cflags=[]): | |
158 | def get_building_env(): | |
159 | 159 | env = os.environ.copy() |
160 | 160 | # point CC etc. to the em* tools. |
161 | 161 | env['CC'] = EMCC |
166 | 166 | env['LDSHARED'] = EMCC |
167 | 167 | env['RANLIB'] = EMRANLIB |
168 | 168 | env['EMSCRIPTEN_TOOLS'] = path_from_root('tools') |
169 | if cflags: | |
170 | env['CFLAGS'] = env['EMMAKEN_CFLAGS'] = ' '.join(cflags) | |
171 | 169 | env['HOST_CC'] = CLANG_CC |
172 | 170 | env['HOST_CXX'] = CLANG_CXX |
173 | 171 | env['HOST_CFLAGS'] = '-W' # if set to nothing, CFLAGS is used, which we don't want |
202 | 200 | |
203 | 201 | # Runs llvm-nm for the given list of files. |
204 | 202 | # The results are populated in nm_cache |
203 | @ToolchainProfiler.profile_block('llvm_nm_multiple') | |
205 | 204 | def llvm_nm_multiple(files): |
206 | with ToolchainProfiler.profile_block('llvm_nm_multiple'): | |
207 | if len(files) == 0: | |
208 | return [] | |
209 | # Run llvm-nm on files that we haven't cached yet | |
210 | llvm_nm_files = [f for f in files if f not in nm_cache] | |
211 | ||
212 | # We can issue multiple files in a single llvm-nm calls, but only if those | |
213 | # files are all .o or .bc files. Because of llvm-nm output format, we cannot | |
214 | # llvm-nm multiple .a files in one call, but those must be individually checked. | |
215 | ||
216 | o_files = [f for f in llvm_nm_files if os.path.splitext(f)[1].lower() in ['.o', '.obj', '.bc']] | |
217 | a_files = [f for f in llvm_nm_files if f not in o_files] | |
218 | ||
219 | # Issue parallel calls for .a files | |
220 | if len(a_files) > 0: | |
221 | results = shared.run_multiple_processes([[LLVM_NM, a] for a in a_files], pipe_stdout=True, check=False) | |
222 | for i in range(len(results)): | |
223 | nm_cache[a_files[i]] = parse_symbols(results[i]) | |
224 | ||
225 | # Issue a single batch call for multiple .o files | |
226 | if len(o_files) > 0: | |
227 | cmd = [LLVM_NM] + o_files | |
228 | cmd = get_command_with_possible_response_file(cmd) | |
229 | results = run_process(cmd, stdout=PIPE, stderr=PIPE, check=False) | |
230 | ||
231 | # If one or more of the input files cannot be processed, llvm-nm will return a non-zero error code, but it will still process and print | |
232 | # out all the other files in order. So even if process return code is non zero, we should always look at what we got to stdout. | |
233 | if results.returncode != 0: | |
234 | logger.debug('Subcommand ' + ' '.join(cmd) + ' failed with return code ' + str(results.returncode) + '! (An input file was corrupt?)') | |
235 | ||
236 | results = results.stdout | |
237 | ||
238 | # llvm-nm produces a single listing of form | |
239 | # file1.o: | |
240 | # 00000001 T __original_main | |
241 | # U __stack_pointer | |
242 | # | |
243 | # file2.o: | |
244 | # 0000005d T main | |
245 | # U printf | |
246 | # | |
247 | # ... | |
248 | # so loop over the report to extract the results | |
249 | # for each individual file. | |
250 | ||
251 | filename = o_files[0] | |
252 | ||
253 | # When we dispatched more than one file, we must manually parse | |
254 | # the file result delimiters (like shown structured above) | |
255 | if len(o_files) > 1: | |
256 | file_start = 0 | |
257 | i = 0 | |
258 | ||
259 | while True: | |
260 | nl = results.find('\n', i) | |
261 | if nl < 0: | |
262 | break | |
263 | colon = results.rfind(':', i, nl) | |
264 | if colon >= 0 and results[colon + 1] == '\n': # New file start? | |
265 | nm_cache[filename] = parse_symbols(results[file_start:i - 1]) | |
266 | filename = results[i:colon].strip() | |
267 | file_start = colon + 2 | |
268 | i = nl + 1 | |
269 | ||
270 | nm_cache[filename] = parse_symbols(results[file_start:]) | |
271 | else: | |
272 | # We only dispatched a single file, so can parse all of the result directly | |
273 | # to that file. | |
274 | nm_cache[filename] = parse_symbols(results) | |
205 | if len(files) == 0: | |
206 | return [] | |
207 | # Run llvm-nm on files that we haven't cached yet | |
208 | llvm_nm_files = [f for f in files if f not in nm_cache] | |
209 | ||
210 | # We can issue multiple files in a single llvm-nm calls, but only if those | |
211 | # files are all .o or .bc files. Because of llvm-nm output format, we cannot | |
212 | # llvm-nm multiple .a files in one call, but those must be individually checked. | |
213 | ||
214 | a_files = [f for f in llvm_nm_files if is_ar(f)] | |
215 | o_files = [f for f in llvm_nm_files if f not in a_files] | |
216 | ||
217 | # Issue parallel calls for .a files | |
218 | if len(a_files) > 0: | |
219 | results = shared.run_multiple_processes([[LLVM_NM, a] for a in a_files], pipe_stdout=True, check=False) | |
220 | for i in range(len(results)): | |
221 | nm_cache[a_files[i]] = parse_symbols(results[i]) | |
222 | ||
223 | # Issue a single batch call for multiple .o files | |
224 | if len(o_files) > 0: | |
225 | cmd = [LLVM_NM] + o_files | |
226 | cmd = get_command_with_possible_response_file(cmd) | |
227 | results = run_process(cmd, stdout=PIPE, stderr=PIPE, check=False) | |
228 | ||
229 | # If one or more of the input files cannot be processed, llvm-nm will return a non-zero error code, but it will still process and print | |
230 | # out all the other files in order. So even if process return code is non zero, we should always look at what we got to stdout. | |
231 | if results.returncode != 0: | |
232 | logger.debug(f'Subcommand {" ".join(cmd)} failed with return code {results.returncode}! (An input file was corrupt?)') | |
233 | ||
234 | results = results.stdout | |
235 | ||
236 | # llvm-nm produces a single listing of form | |
237 | # file1.o: | |
238 | # 00000001 T __original_main | |
239 | # U __stack_pointer | |
240 | # | |
241 | # file2.o: | |
242 | # 0000005d T main | |
243 | # U printf | |
244 | # | |
245 | # ... | |
246 | # so loop over the report to extract the results | |
247 | # for each individual file. | |
248 | ||
249 | filename = o_files[0] | |
250 | ||
251 | # When we dispatched more than one file, we must manually parse | |
252 | # the file result delimiters (like shown structured above) | |
253 | if len(o_files) > 1: | |
254 | file_start = 0 | |
255 | i = 0 | |
256 | ||
257 | while True: | |
258 | nl = results.find('\n', i) | |
259 | if nl < 0: | |
260 | break | |
261 | colon = results.rfind(':', i, nl) | |
262 | if colon >= 0 and results[colon + 1] == '\n': # New file start? | |
263 | nm_cache[filename] = parse_symbols(results[file_start:i - 1]) | |
264 | filename = results[i:colon].strip() | |
265 | file_start = colon + 2 | |
266 | i = nl + 1 | |
267 | ||
268 | nm_cache[filename] = parse_symbols(results[file_start:]) | |
269 | else: | |
270 | # We only dispatched a single file, so can parse all of the result directly | |
271 | # to that file. | |
272 | nm_cache[filename] = parse_symbols(results) | |
275 | 273 | |
276 | 274 | return [nm_cache[f] if f in nm_cache else ObjectFileInfo(1, '') for f in files] |
277 | 275 | |
280 | 278 | return llvm_nm_multiple([file])[0] |
281 | 279 | |
282 | 280 | |
281 | @ToolchainProfiler.profile_block('read_link_inputs') | |
283 | 282 | def read_link_inputs(files): |
284 | with ToolchainProfiler.profile_block('read_link_inputs'): | |
285 | # Before performing the link, we need to look at each input file to determine which symbols | |
286 | # each of them provides. Do this in multiple parallel processes. | |
287 | archive_names = [] # .a files passed in to the command line to the link | |
288 | object_names = [] # .o/.bc files passed in to the command line to the link | |
289 | for f in files: | |
290 | absolute_path_f = make_paths_absolute(f) | |
291 | ||
292 | if absolute_path_f not in ar_contents and is_ar(absolute_path_f): | |
293 | archive_names.append(absolute_path_f) | |
294 | elif absolute_path_f not in nm_cache and is_bitcode(absolute_path_f): | |
295 | object_names.append(absolute_path_f) | |
296 | ||
297 | # Archives contain objects, so process all archives first in parallel to obtain the object files in them. | |
298 | archive_contents = extract_archive_contents(archive_names) | |
299 | ||
300 | for a in archive_contents: | |
301 | ar_contents[os.path.abspath(a['archive_name'])] = a['o_files'] | |
302 | for o in a['o_files']: | |
303 | if o not in nm_cache: | |
304 | object_names.append(o) | |
305 | ||
306 | # Next, extract symbols from all object files (either standalone or inside archives we just extracted) | |
307 | # The results are not used here directly, but populated to llvm-nm cache structure. | |
308 | llvm_nm_multiple(object_names) | |
283 | # Before performing the link, we need to look at each input file to determine which symbols | |
284 | # each of them provides. Do this in multiple parallel processes. | |
285 | archive_names = [] # .a files passed in to the command line to the link | |
286 | object_names = [] # .o/.bc files passed in to the command line to the link | |
287 | for f in files: | |
288 | absolute_path_f = make_paths_absolute(f) | |
289 | ||
290 | if absolute_path_f not in ar_contents and is_ar(absolute_path_f): | |
291 | archive_names.append(absolute_path_f) | |
292 | elif absolute_path_f not in nm_cache and is_bitcode(absolute_path_f): | |
293 | object_names.append(absolute_path_f) | |
294 | ||
295 | # Archives contain objects, so process all archives first in parallel to obtain the object files in them. | |
296 | archive_contents = extract_archive_contents(archive_names) | |
297 | ||
298 | for a in archive_contents: | |
299 | ar_contents[os.path.abspath(a['archive_name'])] = a['o_files'] | |
300 | for o in a['o_files']: | |
301 | if o not in nm_cache: | |
302 | object_names.append(o) | |
303 | ||
304 | # Next, extract symbols from all object files (either standalone or inside archives we just extracted) | |
305 | # The results are not used here directly, but populated to llvm-nm cache structure. | |
306 | llvm_nm_multiple(object_names) | |
309 | 307 | |
310 | 308 | |
311 | 309 | def llvm_backend_args(): |
334 | 332 | return args |
335 | 333 | |
336 | 334 | |
335 | @ToolchainProfiler.profile_block('linking to object file') | |
337 | 336 | def link_to_object(args, target): |
338 | 337 | # link using lld unless LTO is requested (lld can't output LTO/bitcode object files). |
339 | 338 | if not settings.LTO: |
673 | 672 | elif shrink_level >= 2: |
674 | 673 | return '-Oz' |
675 | 674 | else: |
676 | return '-O' + str(min(opt_level, 3)) | |
675 | return f'-O{min(opt_level, 3)}' | |
677 | 676 | |
678 | 677 | |
679 | 678 | def js_optimizer(filename, passes): |
756 | 755 | return True |
757 | 756 | |
758 | 757 | |
758 | @ToolchainProfiler.profile_block('closure_compiler') | |
759 | 759 | def closure_compiler(filename, pretty, advanced=True, extra_closure_args=None): |
760 | with ToolchainProfiler.profile_block('closure_compiler'): | |
761 | env = shared.env_with_node_in_path() | |
762 | user_args = [] | |
763 | env_args = os.environ.get('EMCC_CLOSURE_ARGS') | |
764 | if env_args: | |
765 | user_args += shlex.split(env_args) | |
766 | if extra_closure_args: | |
767 | user_args += extra_closure_args | |
768 | ||
769 | # Closure compiler expects JAVA_HOME to be set *and* java.exe to be in the PATH in order | |
770 | # to enable use the java backend. Without this it will only try the native and JavaScript | |
771 | # versions of the compiler. | |
772 | java_bin = os.path.dirname(config.JAVA) | |
773 | if java_bin: | |
774 | def add_to_path(dirname): | |
775 | env['PATH'] = env['PATH'] + os.pathsep + dirname | |
776 | add_to_path(java_bin) | |
777 | java_home = os.path.dirname(java_bin) | |
778 | env.setdefault('JAVA_HOME', java_home) | |
779 | ||
780 | closure_cmd = get_closure_compiler() | |
781 | ||
782 | native_closure_compiler_works = check_closure_compiler(closure_cmd, user_args, env, allowed_to_fail=True) | |
783 | if not native_closure_compiler_works and not any(a.startswith('--platform') for a in user_args): | |
784 | # Run with Java Closure compiler as a fallback if the native version does not work | |
785 | user_args.append('--platform=java') | |
786 | check_closure_compiler(closure_cmd, user_args, env, allowed_to_fail=False) | |
787 | ||
788 | # Closure externs file contains known symbols to be extern to the minification, Closure | |
789 | # should not minify these symbol names. | |
790 | CLOSURE_EXTERNS = [path_from_root('src', 'closure-externs', 'closure-externs.js')] | |
791 | ||
792 | # Closure compiler needs to know about all exports that come from the wasm module, because to optimize for small code size, | |
793 | # the exported symbols are added to global scope via a foreach loop in a way that evades Closure's static analysis. With an explicit | |
794 | # externs file for the exports, Closure is able to reason about the exports. | |
795 | if settings.WASM_FUNCTION_EXPORTS and not settings.DECLARE_ASM_MODULE_EXPORTS: | |
796 | # Generate an exports file that records all the exported symbols from the wasm module. | |
797 | module_exports_suppressions = '\n'.join(['/**\n * @suppress {duplicate, undefinedVars}\n */\nvar %s;\n' % asmjs_mangle(i) for i in settings.WASM_FUNCTION_EXPORTS]) | |
798 | exports_file = configuration.get_temp_files().get('_module_exports.js') | |
799 | exports_file.write(module_exports_suppressions.encode()) | |
800 | exports_file.close() | |
801 | ||
802 | CLOSURE_EXTERNS += [exports_file.name] | |
803 | ||
804 | # Node.js specific externs | |
805 | if shared.target_environment_may_be('node'): | |
806 | NODE_EXTERNS_BASE = path_from_root('third_party', 'closure-compiler', 'node-externs') | |
807 | NODE_EXTERNS = os.listdir(NODE_EXTERNS_BASE) | |
808 | NODE_EXTERNS = [os.path.join(NODE_EXTERNS_BASE, name) for name in NODE_EXTERNS | |
809 | if name.endswith('.js')] | |
810 | CLOSURE_EXTERNS += [path_from_root('src', 'closure-externs', 'node-externs.js')] + NODE_EXTERNS | |
811 | ||
812 | # V8/SpiderMonkey shell specific externs | |
813 | if shared.target_environment_may_be('shell'): | |
814 | V8_EXTERNS = [path_from_root('src', 'closure-externs', 'v8-externs.js')] | |
815 | SPIDERMONKEY_EXTERNS = [path_from_root('src', 'closure-externs', 'spidermonkey-externs.js')] | |
816 | CLOSURE_EXTERNS += V8_EXTERNS + SPIDERMONKEY_EXTERNS | |
817 | ||
818 | # Web environment specific externs | |
819 | if shared.target_environment_may_be('web') or shared.target_environment_may_be('worker'): | |
820 | BROWSER_EXTERNS_BASE = path_from_root('src', 'closure-externs', 'browser-externs') | |
821 | if os.path.isdir(BROWSER_EXTERNS_BASE): | |
822 | BROWSER_EXTERNS = os.listdir(BROWSER_EXTERNS_BASE) | |
823 | BROWSER_EXTERNS = [os.path.join(BROWSER_EXTERNS_BASE, name) for name in BROWSER_EXTERNS | |
824 | if name.endswith('.js')] | |
825 | CLOSURE_EXTERNS += BROWSER_EXTERNS | |
826 | ||
827 | if settings.MINIMAL_RUNTIME and settings.USE_PTHREADS and not settings.MODULARIZE: | |
828 | CLOSURE_EXTERNS += [path_from_root('src', 'minimal_runtime_worker_externs.js')] | |
829 | ||
830 | args = ['--compilation_level', 'ADVANCED_OPTIMIZATIONS' if advanced else 'SIMPLE_OPTIMIZATIONS'] | |
831 | # Keep in sync with ecmaVersion in tools/acorn-optimizer.js | |
832 | args += ['--language_in', 'ECMASCRIPT_2020'] | |
833 | # Tell closure not to do any transpiling or inject any polyfills. | |
834 | # At some point we may want to look into using this as way to convert to ES5 but | |
835 | # babel is perhaps a better tool for that. | |
836 | args += ['--language_out', 'NO_TRANSPILE'] | |
837 | # Tell closure never to inject the 'use strict' directive. | |
838 | args += ['--emit_use_strict=false'] | |
839 | ||
840 | # Closure compiler is unable to deal with path names that are not 7-bit ASCII: | |
841 | # https://github.com/google/closure-compiler/issues/3784 | |
842 | tempfiles = configuration.get_temp_files() | |
843 | outfile = tempfiles.get('.cc.js').name # Safe 7-bit filename | |
844 | ||
845 | def move_to_safe_7bit_ascii_filename(filename): | |
846 | safe_filename = tempfiles.get('.js').name # Safe 7-bit filename | |
847 | shutil.copyfile(filename, safe_filename) | |
848 | return os.path.relpath(safe_filename, tempfiles.tmpdir) | |
849 | ||
850 | for e in CLOSURE_EXTERNS: | |
851 | args += ['--externs', move_to_safe_7bit_ascii_filename(e)] | |
852 | ||
853 | for i in range(len(user_args)): | |
854 | if user_args[i] == '--externs': | |
855 | user_args[i + 1] = move_to_safe_7bit_ascii_filename(user_args[i + 1]) | |
856 | ||
857 | # Specify output file relative to the temp directory to avoid specifying non-7-bit-ASCII path names. | |
858 | args += ['--js_output_file', os.path.relpath(outfile, tempfiles.tmpdir)] | |
859 | ||
860 | if settings.IGNORE_CLOSURE_COMPILER_ERRORS: | |
861 | args.append('--jscomp_off=*') | |
862 | if pretty: | |
863 | args += ['--formatting', 'PRETTY_PRINT'] | |
864 | # Specify input file relative to the temp directory to avoid specifying non-7-bit-ASCII path names. | |
865 | args += ['--js', move_to_safe_7bit_ascii_filename(filename)] | |
866 | cmd = closure_cmd + args + user_args | |
867 | logger.debug('closure compiler: ' + ' '.join(cmd)) | |
868 | ||
869 | # Closure compiler does not work if any of the input files contain characters outside the | |
870 | # 7-bit ASCII range. Therefore make sure the command line we pass does not contain any such | |
871 | # input files by passing all input filenames relative to the cwd. (user temp directory might | |
872 | # be in user's home directory, and user's profile name might contain unicode characters) | |
873 | proc = run_process(cmd, stderr=PIPE, check=False, env=env, cwd=tempfiles.tmpdir) | |
874 | ||
875 | # XXX Closure bug: if Closure is invoked with --create_source_map, Closure should create a | |
876 | # outfile.map source map file (https://github.com/google/closure-compiler/wiki/Source-Maps) | |
877 | # But it looks like it creates such files on Linux(?) even without setting that command line | |
878 | # flag (and currently we don't), so delete the produced source map file to not leak files in | |
879 | # temp directory. | |
880 | try_delete(outfile + '.map') | |
881 | ||
882 | # Print Closure diagnostics result up front. | |
883 | if proc.returncode != 0: | |
884 | logger.error('Closure compiler run failed:\n') | |
885 | elif len(proc.stderr.strip()) > 0: | |
886 | if settings.CLOSURE_WARNINGS == 'error': | |
887 | logger.error('Closure compiler completed with warnings and -s CLOSURE_WARNINGS=error enabled, aborting!\n') | |
888 | elif settings.CLOSURE_WARNINGS == 'warn': | |
889 | logger.warn('Closure compiler completed with warnings:\n') | |
890 | ||
891 | # Print input file (long wall of text!) | |
892 | if DEBUG == 2 and (proc.returncode != 0 or (len(proc.stderr.strip()) > 0 and settings.CLOSURE_WARNINGS != 'quiet')): | |
893 | input_file = open(filename, 'r').read().splitlines() | |
894 | for i in range(len(input_file)): | |
895 | sys.stderr.write(str(i + 1) + ': ' + input_file[i] + '\n') | |
896 | ||
897 | if proc.returncode != 0: | |
898 | logger.error(proc.stderr) # print list of errors (possibly long wall of text if input was minified) | |
899 | ||
900 | # Exit and print final hint to get clearer output | |
901 | msg = 'closure compiler failed (rc: %d): %s' % (proc.returncode, shared.shlex_join(cmd)) | |
902 | if not pretty: | |
903 | msg += ' the error message may be clearer with -g1 and EMCC_DEBUG=2 set' | |
904 | exit_with_error(msg) | |
905 | ||
906 | if len(proc.stderr.strip()) > 0 and settings.CLOSURE_WARNINGS != 'quiet': | |
907 | # print list of warnings (possibly long wall of text if input was minified) | |
908 | if settings.CLOSURE_WARNINGS == 'error': | |
909 | logger.error(proc.stderr) | |
910 | else: | |
911 | logger.warn(proc.stderr) | |
912 | ||
913 | # Exit and/or print final hint to get clearer output | |
914 | if not pretty: | |
915 | logger.warn('(rerun with -g1 linker flag for an unminified output)') | |
916 | elif DEBUG != 2: | |
917 | logger.warn('(rerun with EMCC_DEBUG=2 enabled to dump Closure input file)') | |
918 | ||
919 | if settings.CLOSURE_WARNINGS == 'error': | |
920 | exit_with_error('closure compiler produced warnings and -s CLOSURE_WARNINGS=error enabled') | |
921 | ||
922 | return outfile | |
760 | env = shared.env_with_node_in_path() | |
761 | user_args = [] | |
762 | env_args = os.environ.get('EMCC_CLOSURE_ARGS') | |
763 | if env_args: | |
764 | user_args += shlex.split(env_args) | |
765 | if extra_closure_args: | |
766 | user_args += extra_closure_args | |
767 | ||
768 | # Closure compiler expects JAVA_HOME to be set *and* java.exe to be in the PATH in order | |
769 | # to enable use the java backend. Without this it will only try the native and JavaScript | |
770 | # versions of the compiler. | |
771 | java_bin = os.path.dirname(config.JAVA) | |
772 | if java_bin: | |
773 | def add_to_path(dirname): | |
774 | env['PATH'] = env['PATH'] + os.pathsep + dirname | |
775 | add_to_path(java_bin) | |
776 | java_home = os.path.dirname(java_bin) | |
777 | env.setdefault('JAVA_HOME', java_home) | |
778 | ||
779 | closure_cmd = get_closure_compiler() | |
780 | ||
781 | native_closure_compiler_works = check_closure_compiler(closure_cmd, user_args, env, allowed_to_fail=True) | |
782 | if not native_closure_compiler_works and not any(a.startswith('--platform') for a in user_args): | |
783 | # Run with Java Closure compiler as a fallback if the native version does not work | |
784 | user_args.append('--platform=java') | |
785 | check_closure_compiler(closure_cmd, user_args, env, allowed_to_fail=False) | |
786 | ||
787 | # Closure externs file contains known symbols to be extern to the minification, Closure | |
788 | # should not minify these symbol names. | |
789 | CLOSURE_EXTERNS = [path_from_root('src', 'closure-externs', 'closure-externs.js')] | |
790 | ||
791 | # Closure compiler needs to know about all exports that come from the wasm module, because to optimize for small code size, | |
792 | # the exported symbols are added to global scope via a foreach loop in a way that evades Closure's static analysis. With an explicit | |
793 | # externs file for the exports, Closure is able to reason about the exports. | |
794 | if settings.WASM_FUNCTION_EXPORTS and not settings.DECLARE_ASM_MODULE_EXPORTS: | |
795 | # Generate an exports file that records all the exported symbols from the wasm module. | |
796 | module_exports_suppressions = '\n'.join(['/**\n * @suppress {duplicate, undefinedVars}\n */\nvar %s;\n' % asmjs_mangle(i) for i in settings.WASM_FUNCTION_EXPORTS]) | |
797 | exports_file = configuration.get_temp_files().get('_module_exports.js') | |
798 | exports_file.write(module_exports_suppressions.encode()) | |
799 | exports_file.close() | |
800 | ||
801 | CLOSURE_EXTERNS += [exports_file.name] | |
802 | ||
803 | # Node.js specific externs | |
804 | if shared.target_environment_may_be('node'): | |
805 | NODE_EXTERNS_BASE = path_from_root('third_party', 'closure-compiler', 'node-externs') | |
806 | NODE_EXTERNS = os.listdir(NODE_EXTERNS_BASE) | |
807 | NODE_EXTERNS = [os.path.join(NODE_EXTERNS_BASE, name) for name in NODE_EXTERNS | |
808 | if name.endswith('.js')] | |
809 | CLOSURE_EXTERNS += [path_from_root('src', 'closure-externs', 'node-externs.js')] + NODE_EXTERNS | |
810 | ||
811 | # V8/SpiderMonkey shell specific externs | |
812 | if shared.target_environment_may_be('shell'): | |
813 | V8_EXTERNS = [path_from_root('src', 'closure-externs', 'v8-externs.js')] | |
814 | SPIDERMONKEY_EXTERNS = [path_from_root('src', 'closure-externs', 'spidermonkey-externs.js')] | |
815 | CLOSURE_EXTERNS += V8_EXTERNS + SPIDERMONKEY_EXTERNS | |
816 | ||
817 | # Web environment specific externs | |
818 | if shared.target_environment_may_be('web') or shared.target_environment_may_be('worker'): | |
819 | BROWSER_EXTERNS_BASE = path_from_root('src', 'closure-externs', 'browser-externs') | |
820 | if os.path.isdir(BROWSER_EXTERNS_BASE): | |
821 | BROWSER_EXTERNS = os.listdir(BROWSER_EXTERNS_BASE) | |
822 | BROWSER_EXTERNS = [os.path.join(BROWSER_EXTERNS_BASE, name) for name in BROWSER_EXTERNS | |
823 | if name.endswith('.js')] | |
824 | CLOSURE_EXTERNS += BROWSER_EXTERNS | |
825 | ||
826 | if settings.MINIMAL_RUNTIME and settings.USE_PTHREADS and not settings.MODULARIZE: | |
827 | CLOSURE_EXTERNS += [path_from_root('src', 'minimal_runtime_worker_externs.js')] | |
828 | ||
829 | args = ['--compilation_level', 'ADVANCED_OPTIMIZATIONS' if advanced else 'SIMPLE_OPTIMIZATIONS'] | |
830 | # Keep in sync with ecmaVersion in tools/acorn-optimizer.js | |
831 | args += ['--language_in', 'ECMASCRIPT_2020'] | |
832 | # Tell closure not to do any transpiling or inject any polyfills. | |
833 | # At some point we may want to look into using this as way to convert to ES5 but | |
834 | # babel is perhaps a better tool for that. | |
835 | args += ['--language_out', 'NO_TRANSPILE'] | |
836 | # Tell closure never to inject the 'use strict' directive. | |
837 | args += ['--emit_use_strict=false'] | |
838 | ||
839 | # Closure compiler is unable to deal with path names that are not 7-bit ASCII: | |
840 | # https://github.com/google/closure-compiler/issues/3784 | |
841 | tempfiles = configuration.get_temp_files() | |
842 | outfile = tempfiles.get('.cc.js').name # Safe 7-bit filename | |
843 | ||
844 | def move_to_safe_7bit_ascii_filename(filename): | |
845 | safe_filename = tempfiles.get('.js').name # Safe 7-bit filename | |
846 | shutil.copyfile(filename, safe_filename) | |
847 | return os.path.relpath(safe_filename, tempfiles.tmpdir) | |
848 | ||
849 | for e in CLOSURE_EXTERNS: | |
850 | args += ['--externs', move_to_safe_7bit_ascii_filename(e)] | |
851 | ||
852 | for i in range(len(user_args)): | |
853 | if user_args[i] == '--externs': | |
854 | user_args[i + 1] = move_to_safe_7bit_ascii_filename(user_args[i + 1]) | |
855 | ||
856 | # Specify output file relative to the temp directory to avoid specifying non-7-bit-ASCII path names. | |
857 | args += ['--js_output_file', os.path.relpath(outfile, tempfiles.tmpdir)] | |
858 | ||
859 | if settings.IGNORE_CLOSURE_COMPILER_ERRORS: | |
860 | args.append('--jscomp_off=*') | |
861 | if pretty: | |
862 | args += ['--formatting', 'PRETTY_PRINT'] | |
863 | # Specify input file relative to the temp directory to avoid specifying non-7-bit-ASCII path names. | |
864 | args += ['--js', move_to_safe_7bit_ascii_filename(filename)] | |
865 | cmd = closure_cmd + args + user_args | |
866 | logger.debug('closure compiler: ' + ' '.join(cmd)) | |
867 | ||
868 | # Closure compiler does not work if any of the input files contain characters outside the | |
869 | # 7-bit ASCII range. Therefore make sure the command line we pass does not contain any such | |
870 | # input files by passing all input filenames relative to the cwd. (user temp directory might | |
871 | # be in user's home directory, and user's profile name might contain unicode characters) | |
872 | proc = run_process(cmd, stderr=PIPE, check=False, env=env, cwd=tempfiles.tmpdir) | |
873 | ||
874 | # XXX Closure bug: if Closure is invoked with --create_source_map, Closure should create a | |
875 | # outfile.map source map file (https://github.com/google/closure-compiler/wiki/Source-Maps) | |
876 | # But it looks like it creates such files on Linux(?) even without setting that command line | |
877 | # flag (and currently we don't), so delete the produced source map file to not leak files in | |
878 | # temp directory. | |
879 | try_delete(outfile + '.map') | |
880 | ||
881 | # Print Closure diagnostics result up front. | |
882 | if proc.returncode != 0: | |
883 | logger.error('Closure compiler run failed:\n') | |
884 | elif len(proc.stderr.strip()) > 0: | |
885 | if settings.CLOSURE_WARNINGS == 'error': | |
886 | logger.error('Closure compiler completed with warnings and -s CLOSURE_WARNINGS=error enabled, aborting!\n') | |
887 | elif settings.CLOSURE_WARNINGS == 'warn': | |
888 | logger.warn('Closure compiler completed with warnings:\n') | |
889 | ||
890 | # Print input file (long wall of text!) | |
891 | if DEBUG == 2 and (proc.returncode != 0 or (len(proc.stderr.strip()) > 0 and settings.CLOSURE_WARNINGS != 'quiet')): | |
892 | input_file = open(filename, 'r').read().splitlines() | |
893 | for i in range(len(input_file)): | |
894 | sys.stderr.write(f'{i + 1}: {input_file[i]}\n') | |
895 | ||
896 | if proc.returncode != 0: | |
897 | logger.error(proc.stderr) # print list of errors (possibly long wall of text if input was minified) | |
898 | ||
899 | # Exit and print final hint to get clearer output | |
900 | msg = 'closure compiler failed (rc: %d): %s' % (proc.returncode, shared.shlex_join(cmd)) | |
901 | if not pretty: | |
902 | msg += ' the error message may be clearer with -g1 and EMCC_DEBUG=2 set' | |
903 | exit_with_error(msg) | |
904 | ||
905 | if len(proc.stderr.strip()) > 0 and settings.CLOSURE_WARNINGS != 'quiet': | |
906 | # print list of warnings (possibly long wall of text if input was minified) | |
907 | if settings.CLOSURE_WARNINGS == 'error': | |
908 | logger.error(proc.stderr) | |
909 | else: | |
910 | logger.warn(proc.stderr) | |
911 | ||
912 | # Exit and/or print final hint to get clearer output | |
913 | if not pretty: | |
914 | logger.warn('(rerun with -g1 linker flag for an unminified output)') | |
915 | elif DEBUG != 2: | |
916 | logger.warn('(rerun with EMCC_DEBUG=2 enabled to dump Closure input file)') | |
917 | ||
918 | if settings.CLOSURE_WARNINGS == 'error': | |
919 | exit_with_error('closure compiler produced warnings and -s CLOSURE_WARNINGS=error enabled') | |
920 | ||
921 | return outfile | |
923 | 922 | |
924 | 923 | |
925 | 924 | # minify the final wasm+JS combination. this is done after all the JS |
1144 | 1143 | passes += ['last'] |
1145 | 1144 | if passes: |
1146 | 1145 | # hackish fixups to work around wasm2js style and the js optimizer FIXME |
1147 | wasm2js_js = '// EMSCRIPTEN_START_ASM\n' + wasm2js_js + '// EMSCRIPTEN_END_ASM\n' | |
1146 | wasm2js_js = f'// EMSCRIPTEN_START_ASM\n{wasm2js_js}// EMSCRIPTEN_END_ASM\n' | |
1148 | 1147 | wasm2js_js = wasm2js_js.replace('// EMSCRIPTEN_START_FUNCS;\n', '// EMSCRIPTEN_START_FUNCS\n') |
1149 | 1148 | wasm2js_js = wasm2js_js.replace('// EMSCRIPTEN_END_FUNCS;\n', '// EMSCRIPTEN_END_FUNCS\n') |
1150 | 1149 | wasm2js_js = wasm2js_js.replace('\n function $', '\nfunction $') |
1182 | 1181 | finds = re.findall(r'''[\w\d_$]+\.__wasm2jsInstantiate__''', all_js) |
1183 | 1182 | assert len(finds) == 1 |
1184 | 1183 | marker = finds[0] |
1185 | all_js = all_js.replace(marker, '(\n' + wasm2js_js + '\n)') | |
1184 | all_js = all_js.replace(marker, f'(\n{wasm2js_js}\n)') | |
1186 | 1185 | # replace the placeholder with the actual code |
1187 | 1186 | js_file = js_file + '.wasm2js.js' |
1188 | 1187 | with open(js_file, 'w') as f: |
1378 | 1377 | logger.debug('Mapping library `%s` to JS libraries: %s' % (library_name, libs)) |
1379 | 1378 | return (libs, native_library_map.get(library_name)) |
1380 | 1379 | |
1381 | if library_name.endswith('.js') and os.path.isfile(path_from_root('src', 'library_' + library_name)): | |
1382 | return (['library_' + library_name], None) | |
1380 | if library_name.endswith('.js') and os.path.isfile(path_from_root('src', f'library_{library_name}')): | |
1381 | return ([f'library_{library_name}'], None) | |
1383 | 1382 | |
1384 | 1383 | return (None, None) |
1385 | 1384 | |
1493 | 1492 | extra += '\nnote: to disable int64 legalization (which requires changes after link) use -s WASM_BIGINT' |
1494 | 1493 | if settings.OPT_LEVEL > 0: |
1495 | 1494 | extra += '\nnote: -O2+ optimizations always require changes, build with -O0 or -O1 instead' |
1496 | exit_with_error('changes to the wasm are required after link, but disallowed by ERROR_ON_WASM_CHANGES_AFTER_LINK: ' + str(cmd) + extra) | |
1495 | exit_with_error(f'changes to the wasm are required after link, but disallowed by ERROR_ON_WASM_CHANGES_AFTER_LINK: {cmd}{extra}') | |
1497 | 1496 | if debug: |
1498 | 1497 | cmd += ['-g'] # preserve the debug info |
1499 | 1498 | # if the features are not already handled, handle them |
1501 | 1500 | # if we are emitting a source map, every time we load and save the wasm |
1502 | 1501 | # we must tell binaryen to update it |
1503 | 1502 | if settings.GENERATE_SOURCE_MAP and outfile: |
1504 | cmd += ['--input-source-map=' + infile + '.map'] | |
1505 | cmd += ['--output-source-map=' + outfile + '.map'] | |
1503 | cmd += [f'--input-source-map={infile}.map'] | |
1504 | cmd += [f'--output-source-map={outfile}.map'] | |
1506 | 1505 | ret = check_call(cmd, stdout=stdout).stdout |
1507 | 1506 | if outfile: |
1508 | 1507 | save_intermediate(outfile, '%s.wasm' % tool) |
42 | 42 | raise Exception('Attempt to lock the cache but FROZEN_CACHE is set') |
43 | 43 | |
44 | 44 | if not self.EM_EXCLUSIVE_CACHE_ACCESS and self.acquired_count == 0: |
45 | logger.debug('PID %s acquiring multiprocess file lock to Emscripten cache at %s' % (str(os.getpid()), self.dirname)) | |
45 | logger.debug(f'PID {os.getpid()} acquiring multiprocess file lock to Emscripten cache at {self.dirname}') | |
46 | 46 | try: |
47 | 47 | self.filelock.acquire(60) |
48 | 48 | except filelock.Timeout: |
49 | 49 | # The multiprocess cache locking can be disabled altogether by setting EM_EXCLUSIVE_CACHE_ACCESS=1 environment |
50 | 50 | # variable before building. (in that case, use "embuilder.py build ALL" to prepopulate the cache) |
51 | logger.warning('Accessing the Emscripten cache at "' + self.dirname + '" is taking a long time, another process should be writing to it. If there are none and you suspect this process has deadlocked, try deleting the lock file "' + self.filelock_name + '" and try again. If this occurs deterministically, consider filing a bug.') | |
51 | logger.warning(f'Accessing the Emscripten cache at "{self.dirname}" is taking a long time, another process should be writing to it. If there are none and you suspect this process has deadlocked, try deleting the lock file "{self.filelock_name}" and try again. If this occurs deterministically, consider filing a bug.') | |
52 | 52 | self.filelock.acquire() |
53 | 53 | |
54 | 54 | self.prev_EM_EXCLUSIVE_CACHE_ACCESS = os.environ.get('EM_EXCLUSIVE_CACHE_ACCESS') |
65 | 65 | else: |
66 | 66 | del os.environ['EM_EXCLUSIVE_CACHE_ACCESS'] |
67 | 67 | self.filelock.release() |
68 | logger.debug('PID %s released multiprocess file lock to Emscripten cache at %s' % (str(os.getpid()), self.dirname)) | |
68 | logger.debug(f'PID {os.getpid()} released multiprocess file lock to Emscripten cache at {self.dirname}') | |
69 | 69 | |
70 | 70 | @contextlib.contextmanager |
71 | 71 | def lock(self): |
125 | 125 | with self.lock(): |
126 | 126 | name = os.path.join(self.dirname, shortname) |
127 | 127 | if os.path.exists(name): |
128 | logger.info('deleting cached file: %s', name) | |
128 | logger.info(f'deleting cached file: {name}') | |
129 | 129 | tempfiles.try_delete(name) |
130 | 130 | |
131 | 131 | def get_lib(self, libname, *args, **kwargs): |
145 | 145 | if config.FROZEN_CACHE: |
146 | 146 | # Raise an exception here rather than exit_with_error since in practice this |
147 | 147 | # should never happen |
148 | raise Exception('FROZEN_CACHE is set, but cache file is missing: "%s" (in cache root path "%s")' % (shortname, self.dirname)) | |
148 | raise Exception(f'FROZEN_CACHE is set, but cache file is missing: "{shortname}" (in cache root path "{self.dirname}")') | |
149 | 149 | |
150 | 150 | with self.lock(): |
151 | 151 | if os.path.exists(cachename) and not force: |
155 | 155 | what = 'system library' |
156 | 156 | else: |
157 | 157 | what = 'system asset' |
158 | message = 'generating ' + what + ': ' + shortname + '... (this will be cached in "' + cachename + '" for subsequent builds)' | |
158 | message = f'generating {what}: {shortname}... (this will be cached in "{cachename}" for subsequent builds)' | |
159 | 159 | logger.info(message) |
160 | 160 | utils.safe_ensure_dirs(os.path.dirname(cachename)) |
161 | 161 | creator(cachename) |
8 | 8 | import os |
9 | 9 | import re |
10 | 10 | import sys |
11 | from pathlib import Path | |
11 | 12 | |
12 | 13 | __rootpath__ = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
13 | 14 | |
20 | 21 | return '0x' + ('0' * (len(x) - 6)) + x[2:].upper() |
21 | 22 | |
22 | 23 | |
23 | repdata = open(path_from_root('system', 'include', 'GL', 'gl.h')).readlines() + ['\n'] + \ | |
24 | open(path_from_root('system', 'include', 'GL', 'glext.h')).readlines() | |
24 | repdata = ( | |
25 | Path(path_from_root('system', 'include', 'GL', 'gl.h')).read_text().splitline(keepends=True) + | |
26 | ['\n'] + | |
27 | Path(path_from_root('system', 'include', 'GL', 'glext.h')).read_text().splitlines(keepends=True) | |
28 | ) | |
25 | 29 | reps = {} |
26 | 30 | for rep in repdata: |
27 | 31 | rep = rep.replace('\t', ' ').replace('\n', '') |
5 | 5 | import os |
6 | 6 | import sys |
7 | 7 | import logging |
8 | ||
9 | from . import utils | |
8 | 10 | from .utils import path_from_root, exit_with_error, __rootpath__, which |
9 | 11 | |
10 | 12 | logger = logging.getLogger('shared') |
105 | 107 | Also check EM_<KEY> environment variables to override specific config keys. |
106 | 108 | """ |
107 | 109 | config = {} |
108 | config_text = open(EM_CONFIG, 'r').read() | |
110 | config_text = utils.read_file(EM_CONFIG) | |
109 | 111 | try: |
110 | 112 | exec(config_text, config) |
111 | 113 | except Exception as e: |
170 | 172 | def generate_config(path, first_time=False): |
171 | 173 | # Note: repr is used to ensure the paths are escaped correctly on Windows. |
172 | 174 | # The full string is replaced so that the template stays valid Python. |
173 | config_data = open(path_from_root('tools', 'settings_template.py')).read().splitlines() | |
174 | config_data = config_data[3:] # remove the initial comment | |
175 | ||
176 | config_data = utils.read_file(path_from_root('tools', 'settings_template.py')) | |
177 | config_data = config_data.splitlines()[3:] # remove the initial comment | |
175 | 178 | config_data = '\n'.join(config_data) |
176 | 179 | # autodetect some default paths |
177 | 180 | config_data = config_data.replace('\'{{{ EMSCRIPTEN_ROOT }}}\'', repr(__rootpath__)) |
183 | 186 | |
184 | 187 | abspath = os.path.abspath(os.path.expanduser(path)) |
185 | 188 | # write |
186 | with open(abspath, 'w') as f: | |
187 | f.write(config_data) | |
189 | ||
190 | utils.write_file(abspath, config_data) | |
188 | 191 | |
189 | 192 | if first_time: |
190 | 193 | print(''' |
16 | 16 | |
17 | 17 | sys.path.insert(1, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
18 | 18 | |
19 | from tools import shared | |
19 | from tools import shared, utils | |
20 | 20 | |
21 | 21 | |
22 | 22 | js_file = sys.argv[1] |
92 | 92 | |
93 | 93 | # main |
94 | 94 | def main(): |
95 | js = open(js_file).read() | |
95 | js = utils.read_file(js_file) | |
96 | 96 | ctors_start, ctors_end = find_ctors(js) |
97 | 97 | if ctors_start < 0: |
98 | 98 | logger.debug('ctor_evaller: no ctors') |
113 | 113 | logger.debug('ctor_evaller: not successful') |
114 | 114 | sys.exit(0) |
115 | 115 | logger.debug('ctor_evaller: we managed to remove %d ctors' % num_successful) |
116 | open(js_file, 'w').write(new_js) | |
116 | utils.write_file(js_file, new_js) | |
117 | 117 | |
118 | 118 | |
119 | 119 | if __name__ == '__main__': |
11 | 11 | from __future__ import print_function |
12 | 12 | import os, sys |
13 | 13 | |
14 | from pathlib import Path | |
15 | ||
14 | 16 | |
15 | 17 | def process_line(line): |
16 | 18 | #AD:2041,0.900000 |
19 | 21 | num, val = line.split(',') |
20 | 22 | return [int(num), float(val)] |
21 | 23 | |
22 | a = open(sys.argv[1], 'r').readlines() | |
23 | b = open(sys.argv[2], 'r').readlines() | |
24 | a = Path(sys.argv[1]).read_text().splitlines(keepends=True) | |
25 | b = Path(sys.argv[2]).read_text().splitlines(keepends=True) | |
24 | 26 | MIN = 0.0001 if len(sys.argv) < 4 else sys.argv[3] |
25 | 27 | |
26 | 28 | ai = 0 |
10 | 10 | |
11 | 11 | from __future__ import print_function |
12 | 12 | import os, sys, shutil |
13 | from pathlib import Path | |
13 | 14 | from subprocess import Popen, PIPE, STDOUT |
14 | 15 | |
15 | 16 | __rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) |
17 | ||
18 | ||
16 | 19 | def path_from_root(*pathelems): |
17 | 20 | return os.path.join(__rootpath__, *pathelems) |
18 | exec(open(path_from_root('tools', 'shared.py'), 'r').read()) | |
21 | ||
22 | exec(Path(path_from_root('tools', 'shared.py').read_text())) | |
19 | 23 | |
20 | file1 = open(sys.argv[1]).read() | |
21 | file2 = open(sys.argv[2]).read() | |
24 | shutil.copyfile(sys.argv[1], 'left') | |
25 | shutil.copyfile(sys.argv[2], 'right') | |
22 | 26 | |
23 | leftf = open('left', 'w') | |
24 | leftf.write(file1) | |
25 | leftf.close() | |
26 | ||
27 | rightf = open('right', 'w') | |
28 | rightf.write(file2) | |
29 | rightf.close() | |
30 | 27 | |
31 | 28 | def run_code(name): |
32 | 29 | ret = run_js(name, stderr=PIPE, full_output=True, assert_returncode=None, engine=SPIDERMONKEY_ENGINE) |
12 | 12 | |
13 | 13 | from __future__ import print_function |
14 | 14 | import os, sys, shutil |
15 | from pathlib import Path | |
15 | 16 | from subprocess import Popen, PIPE, STDOUT |
16 | 17 | |
17 | 18 | __rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) |
18 | 19 | def path_from_root(*pathelems): |
19 | 20 | return os.path.join(__rootpath__, *pathelems) |
20 | exec(open(path_from_root('tools', 'shared.py'), 'r').read()) | |
21 | ||
22 | exec(Path(path_from_root('tools', 'shared.py').read()) | |
21 | 23 | |
22 | file1 = open(sys.argv[1]).read() | |
23 | file2 = open(sys.argv[2]).read() | |
24 | shutil.copyfile(sys.argv[1], 'left') | |
25 | shutil.copyfile(sys.argv[2], 'right') | |
24 | 26 | |
25 | leftf = open('left', 'w') | |
26 | leftf.write(file1) | |
27 | leftf.close() | |
28 | ||
29 | rightf = open('right', 'w') | |
30 | rightf.write(file2) | |
31 | rightf.close() | |
32 | 27 | |
33 | 28 | def run_code(name): |
34 | 29 | ret = run_js(name, stderr=PIPE, full_output=True) |
52 | 47 | while True: |
53 | 48 | mid = int((low + high)/2) |
54 | 49 | print(low, high, ' current: %d' % mid, end=' ') |
55 | open('middle', 'w').write('\n'.join(left_lines[:mid] + right_lines[mid:])) | |
50 | Path('middle').write_text('\n'.join(left_lines[:mid] + right_lines[mid:])) | |
56 | 51 | shutil.copyfile('middle', 'middle' + str(mid)) |
57 | 52 | result = run_code('middle') |
58 | 53 | print(result == left_result, result == right_result)#, 'XXX', left_result, 'YYY', result, 'ZZZ', right_result |
10 | 10 | |
11 | 11 | from __future__ import print_function |
12 | 12 | import os, sys, shutil |
13 | from pathlib import Path | |
13 | 14 | from subprocess import Popen, PIPE, STDOUT |
14 | 15 | |
15 | 16 | __rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) |
17 | ||
16 | 18 | def path_from_root(*pathelems): |
17 | 19 | return os.path.join(__rootpath__, *pathelems) |
18 | exec(open(path_from_root('tools', 'shared.py'), 'r').read()) | |
19 | 20 | |
20 | file1 = open(sys.argv[1]).read() | |
21 | file2 = open(sys.argv[2]).read() | |
21 | exec(Path(path_from_root('tools', 'shared.py')).read_text()) | |
22 | 22 | |
23 | leftf = open('left', 'w') | |
24 | leftf.write(file1) | |
25 | leftf.close() | |
26 | ||
27 | rightf = open('right', 'w') | |
28 | rightf.write(file2) | |
29 | rightf.close() | |
23 | shutil.copyfile(sys.argv[1], 'left') | |
24 | shutil.copyfile(sys.argv[2], 'right') | |
30 | 25 | |
31 | 26 | def run_code(name): |
32 | 27 | shutil.copyfile(name, 'src.cpp.o.wat') |
8 | 8 | ''' |
9 | 9 | from __future__ import print_function |
10 | 10 | import os, sys |
11 | from pathlib import Path | |
11 | 12 | |
12 | f1 = open(sys.argv[1], 'r').readlines() | |
13 | f2 = open(sys.argv[2], 'r').readlines() | |
13 | ||
14 | f1 = Path(sys.argv[1]).read_text().splitlines(keepends=True) | |
15 | f2 = Path(sys.argv[2]).read_text().splitlines(keepends=True) | |
14 | 16 | |
15 | 17 | for i in range(len(f1)): |
16 | 18 | if f1[i] == f2[i]: continue |
8 | 8 | |
9 | 9 | from __future__ import print_function |
10 | 10 | import os, sys, re |
11 | from pathlib import Path | |
11 | 12 | |
12 | 13 | filename = sys.argv[1] |
13 | data = open(filename).read() | |
14 | data = Path(filename).read_text() | |
14 | 15 | iss = re.findall(r' i\d+ [^=]', data) |
15 | 16 | set_iss = set(iss) |
16 | 17 | bigs = [] |
8 | 8 | from __future__ import print_function |
9 | 9 | import os, sys |
10 | 10 | |
11 | from pathlib import Path | |
12 | ||
11 | 13 | kill = False |
12 | 14 | |
13 | 15 | valids = sys.argv[2].split(',') |
14 | 16 | |
15 | for line in open(sys.argv[1]).readlines(): | |
17 | for line in Path(sys.argv[1]).read_text().splitlines(keepends=True): | |
16 | 18 | line = line.replace('\n', '') |
17 | 19 | if line.startswith('define ') and line.endswith('{'): |
18 | 20 | ok = False |
53 | 53 | 'Mix_LoadWAV_RW': ['fileno'], |
54 | 54 | 'SDL_CreateRGBSurface': ['malloc', 'free'], |
55 | 55 | 'SDL_GL_GetProcAddress': ['malloc'], |
56 | 'SDL_Init': ['malloc', 'free', 'memset', 'memcpy'], | |
56 | 'SDL_Init': ['malloc', 'free', 'memcpy'], | |
57 | 57 | 'SDL_LockSurface': ['malloc', 'free'], |
58 | 58 | 'SDL_OpenAudio': ['malloc', 'free'], |
59 | 59 | 'SDL_PushEvent': ['malloc', 'free'], |
83 | 83 | 'emscripten_async_wget2_data': ['malloc', 'free'], |
84 | 84 | 'emscripten_async_wget_data': ['malloc', 'free'], |
85 | 85 | 'emscripten_create_worker': ['malloc', 'free'], |
86 | 'emscripten_fetch': ['emscripten_is_main_browser_thread'], | |
87 | 86 | 'emscripten_get_compiler_setting': ['malloc'], |
88 | 87 | 'emscripten_get_preloaded_image_data': ['malloc'], |
89 | 88 | 'emscripten_get_preloaded_image_data_from_FILE': ['fileno'], |
138 | 137 | 'emscripten_set_touchstart_callback_on_thread': ['malloc', 'free'], |
139 | 138 | 'emscripten_set_visibilitychange_callback_on_thread': ['malloc', 'free'], |
140 | 139 | 'emscripten_set_wheel_callback_on_thread': ['malloc', 'free'], |
141 | 'emscripten_start_fetch': ['emscripten_is_main_browser_thread'], | |
142 | 140 | 'emscripten_webgl_create_context': ['malloc'], |
143 | 141 | 'emscripten_webgl_destroy_context': ['emscripten_webgl_make_context_current', 'emscripten_webgl_get_current_context'], |
144 | 142 | 'emscripten_webgl_get_parameter_utf8': ['malloc'], |
159 | 157 | 'getnameinfo': ['htons', 'ntohs'], |
160 | 158 | 'getpeername': ['htons'], |
161 | 159 | 'getsockname': ['htons'], |
162 | 'getrusage': ['memset'], | |
163 | 160 | 'glGetString': ['malloc'], |
164 | 161 | 'glGetStringi': ['malloc'], |
165 | 162 | 'glMapBufferRange': ['malloc'], |
172 | 169 | 'localtime': ['_get_tzname', '_get_daylight', '_get_timezone', 'malloc'], |
173 | 170 | 'localtime_r': ['_get_tzname', '_get_daylight', '_get_timezone', 'malloc'], |
174 | 171 | 'mktime': ['_get_tzname', '_get_daylight', '_get_timezone', 'malloc'], |
175 | 'mmap': ['memalign', 'memset', 'malloc'], | |
176 | 'munmap': ['malloc', 'free'], | |
172 | 'mmap': ['memalign'], | |
173 | 'munmap': ['free'], | |
177 | 174 | 'pthread_create': ['malloc', 'free', 'emscripten_main_thread_process_queued_calls'], |
178 | 'readdir': ['malloc'], | |
179 | 'realpath': ['malloc'], | |
180 | 175 | 'recv': ['htons'], |
181 | 176 | 'accept': ['htons'], |
182 | 177 | 'recvfrom': ['htons'], |
192 | 187 | 'setgroups': ['sysconf'], |
193 | 188 | 'syslog': ['malloc', 'ntohs'], |
194 | 189 | 'timegm': ['_get_tzname', '_get_daylight', '_get_timezone', 'malloc'], |
195 | 'times': ['memset'], | |
196 | 'tmpnam': ['malloc'], | |
197 | 'ttyname': ['malloc'], | |
198 | 190 | 'tzset': ['_get_tzname', '_get_daylight', '_get_timezone', 'malloc'], |
199 | 'uuid_clear': ['memset'], | |
200 | 191 | 'uuid_compare': ['memcmp'], |
201 | 192 | 'uuid_copy': ['memcpy'], |
202 | 193 | 'wgpuBufferGetMappedRange': ['malloc', 'free'], |
9 | 9 | import random |
10 | 10 | import subprocess |
11 | 11 | import time |
12 | from pathlib import Path | |
12 | 13 | |
13 | 14 | |
14 | 15 | def run(): |
15 | 16 | subprocess.check_call(['emcc', 'src.cpp', '-O2']) |
16 | 17 | ret = {} |
17 | 18 | for relevant_file in os.listdir('.'): |
18 | ret[relevant_file] = open(relevant_file).read() | |
19 | ret[relevant_file] = Path(relevant_file).read_text() | |
19 | 20 | return ret |
20 | 21 | |
21 | 22 | |
23 | 24 | if not os.path.exists(subdir): |
24 | 25 | os.mkdir(subdir) |
25 | 26 | for relevant_file in data.keys(): |
26 | open(os.path.join(subdir, relevant_file), 'w').write(data[relevant_file]) | |
27 | Path(os.path.join(subdir, relevant_file)).write_text(data[relevant_file]) | |
27 | 28 | |
28 | 29 | |
29 | 30 | os.chdir('/tmp/emscripten_temp') |
30 | 31 | assert len(os.listdir('.')) == 0, 'temp dir should start out empty, after that, everything there looks important to us' |
31 | open('src.cpp', 'w').write(''' | |
32 | Path('src.cpp').write_text(''' | |
32 | 33 | #include <iostream> |
33 | 34 | |
34 | 35 | int main() |
13 | 13 | import re |
14 | 14 | import subprocess |
15 | 15 | import sys |
16 | from pathlib import Path | |
17 | ||
16 | 18 | |
17 | 19 | # If true, we are printing delta information between two data sets. If false, we are just printing symbol info for a single data set |
18 | 20 | diffing_two_data_sets = False |
371 | 373 | |
372 | 374 | |
373 | 375 | def analyze_javascript_file(filename, total_source_set_size, symbol_map=None): |
374 | file_contents = open(filename).read() | |
376 | file_contents = Path(filename).read_text() | |
375 | 377 | print('Analyzing JS file ' + filename + ', ' + str(len(file_contents)) + ' bytes...') |
376 | 378 | return analyze_javascript_file_contents(filename, file_contents, total_source_set_size, symbol_map) |
377 | 379 | |
378 | 380 | |
379 | 381 | def analyze_html_file(filename, total_source_set_size, symbol_map=None): |
380 | file_contents = open(filename).read() | |
382 | file_contents = Path(filename).read_text() | |
381 | 383 | print('Analyzing HTML file ' + filename + ', ' + str(len(file_contents)) + ' bytes...') |
382 | 384 | data = {} |
383 | 385 | parse_pos = 0 |
9 | 9 | import sys |
10 | 10 | import tempfile |
11 | 11 | import time |
12 | from pathlib import Path | |
13 | ||
12 | 14 | |
13 | 15 | profiler_logs_path = os.path.join(tempfile.gettempdir(), 'emscripten_toolchain_profiler_logs') |
14 | 16 | |
52 | 54 | print('Processing ' + str(len(log_files)) + ' profile log files in "' + profiler_logs_path + '"...') |
53 | 55 | for f in log_files: |
54 | 56 | try: |
55 | json_data = open(f, 'r').read() | |
57 | json_data = Path(f).read_text() | |
56 | 58 | if len(json_data.strip()) == 0: |
57 | 59 | continue |
58 | 60 | lines = json_data.split('\n') |
74 | 76 | emprofile_json_data = json.dumps(all_results, indent=2) |
75 | 77 | |
76 | 78 | html_file = OUTFILE + '.html' |
77 | html_contents = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'toolchain_profiler.results_template.html'), 'r').read().replace('{{{ emprofile_json_data }}}', emprofile_json_data) | |
78 | open(html_file, 'w').write(html_contents) | |
79 | html_contents = Path(os.path.dirname(os.path.realpath(__file__)), 'toolchain_profiler.results_template.html').read_text().replace('{{{ emprofile_json_data }}}', emprofile_json_data) | |
80 | Path(html_file).write_text(html_contents) | |
79 | 81 | print('Wrote "' + html_file + '"') |
80 | 82 | |
81 | 83 |
112 | 112 | import re |
113 | 113 | import sys |
114 | 114 | import shutil |
115 | from pathlib import Path | |
115 | 116 | |
116 | 117 | assert len(sys.argv) >= 4, 'Usage: reproduceriter.py IN_DIR OUT_DIR FIRST_JS [WINDOW_LOCATION]' |
117 | 118 | |
146 | 147 | if filename.endswith('.js'): |
147 | 148 | fullname = os.path.join(parent, filename) |
148 | 149 | print(' ', fullname) |
149 | js = open(fullname).read() | |
150 | with open(fullname) as fh: | |
151 | js = fh.read() | |
150 | 152 | js = re.sub(r'document\.on(\w+) ?= ?([\w.$]+)', lambda m: 'Recorder.onEvent("' + m.group(1) + '", ' + m.group(2) + ')', js) |
151 | 153 | js = re.sub(r'''([\w.'"\[\]]+)\.addEventListener\(([\w,. $]+)\)''', lambda m: 'Recorder.addListener(' + m.group(1) + ', ' + m.group(2) + ')', js) |
152 | open(fullname, 'w').write(js) | |
154 | Path(fullname).write_text(js) | |
153 | 155 | |
154 | 156 | # Add our boilerplate |
155 | 157 | |
156 | 158 | print('add boilerplate...') |
157 | 159 | |
158 | open(os.path.join(out_dir, first_js), 'w').write( | |
159 | (open(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'src', 'headless.js')).read() % ( | |
160 | window_location, window_location.split('?')[-1], on_idle or 'null', dirs_to_drop | |
161 | ) if shell else '') + | |
162 | open(os.path.join(os.path.dirname(__file__), 'reproduceriter.js')).read() + | |
163 | open(os.path.join(in_dir, first_js)).read() + ('\nwindow.runEventLoop();\n' if shell else '') | |
164 | ) | |
160 | with open(os.path.join(out_dir, first_js), 'w') as fh1: | |
161 | fh1.write( | |
162 | (Path(os.path.dirname(os.path.dirname(__file__)), 'src', 'headless.js').read_text() % ( | |
163 | window_location, window_location.split('?')[-1], on_idle or 'null', dirs_to_drop | |
164 | ) if shell else '') + | |
165 | Path(os.path.dirname(__file__), 'reproduceriter.js').read_text() + | |
166 | Path(in_dir, first_js).read_text() + ('\nwindow.runEventLoop();\n' if shell else '') | |
167 | ) | |
165 | 168 | |
166 | 169 | print('done!') |
20 | 20 | |
21 | 21 | Usage: |
22 | 22 | |
23 | file_packager TARGET [--preload A [B..]] [--embed C [D..]] [--exclude E [F..]]] [--js-output=OUTPUT.js] [--no-force] [--use-preload-cache] [--indexedDB-name=EM_PRELOAD_CACHE] [--separate-metadata] [--lz4] [--use-preload-plugins] | |
23 | file_packager TARGET [--preload A [B..]] [--embed C [D..]] [--exclude E [F..]]] [--js-output=OUTPUT.js] [--no-force] [--use-preload-cache] [--indexedDB-name=EM_PRELOAD_CACHE] [--separate-metadata] [--lz4] [--use-preload-plugins] [--no-node] | |
24 | 24 | |
25 | 25 | --preload , |
26 | 26 | --embed See emcc --help for more details on those options. |
47 | 47 | |
48 | 48 | --use-preload-plugins Tells the file packager to run preload plugins on the files as they are loaded. This performs tasks like decoding images |
49 | 49 | and audio using the browser's codecs. |
50 | ||
51 | --no-node Whether to support Node.js. By default we do, which emits some extra code. | |
50 | 52 | |
51 | 53 | Notes: |
52 | 54 | |
54 | 56 | subdir\file, in JS it will be subdir/file. For simplicity we treat the web platform as a *NIX. |
55 | 57 | """ |
56 | 58 | |
59 | import base64 | |
57 | 60 | import os |
58 | 61 | import sys |
59 | 62 | import shutil |
64 | 67 | sys.path.insert(1, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
65 | 68 | |
66 | 69 | import posixpath |
67 | from tools import shared | |
70 | from tools import shared, utils | |
68 | 71 | from subprocess import PIPE |
69 | 72 | import fnmatch |
70 | 73 | import json |
90 | 93 | |
91 | 94 | excluded_patterns = [] |
92 | 95 | new_data_files = [] |
96 | ||
97 | ||
98 | def base64_encode(b): | |
99 | b64 = base64.b64encode(b) | |
100 | return b64.decode('ascii') | |
93 | 101 | |
94 | 102 | |
95 | 103 | def has_hidden_attribute(filepath): |
173 | 181 | separate_metadata = False |
174 | 182 | lz4 = False |
175 | 183 | use_preload_plugins = False |
184 | support_node = True | |
176 | 185 | |
177 | 186 | for arg in sys.argv[2:]: |
178 | 187 | if arg == '--preload': |
202 | 211 | leading = '' |
203 | 212 | elif arg == '--use-preload-plugins': |
204 | 213 | use_preload_plugins = True |
214 | leading = '' | |
215 | elif arg == '--no-node': | |
216 | support_node = False | |
205 | 217 | leading = '' |
206 | 218 | elif arg.startswith('--js-output'): |
207 | 219 | jsoutput = arg.split('=', 1)[1] if '=' in arg else None |
457 | 469 | basename = os.path.basename(filename) |
458 | 470 | if file_['mode'] == 'embed': |
459 | 471 | # Embed |
460 | data = list(bytearray(open(file_['srcpath'], 'rb').read())) | |
461 | code += '''var fileData%d = [];\n''' % counter | |
462 | if data: | |
463 | parts = [] | |
464 | chunk_size = 10240 | |
465 | start = 0 | |
466 | while start < len(data): | |
467 | parts.append('''fileData%d.push.apply(fileData%d, %s);\n''' | |
468 | % (counter, counter, str(data[start:start + chunk_size]))) | |
469 | start += chunk_size | |
470 | code += ''.join(parts) | |
471 | code += ('''Module['FS_createDataFile']('%s', '%s', fileData%d, true, true, false);\n''' | |
472 | data = base64_encode(utils.read_binary(file_['srcpath'])) | |
473 | code += '''var fileData%d = '%s';\n''' % (counter, data) | |
474 | code += ('''Module['FS_createDataFile']('%s', '%s', decodeBase64(fileData%d), true, true, false);\n''' | |
472 | 475 | % (dirname, basename, counter)) |
473 | 476 | counter += 1 |
474 | 477 | elif file_['mode'] == 'preload': |
707 | 710 | } |
708 | 711 | ''' |
709 | 712 | |
710 | ret += r''' | |
711 | function fetchRemotePackage(packageName, packageSize, callback, errback) { | |
713 | # add Node.js support code, if necessary | |
714 | node_support_code = '' | |
715 | if support_node: | |
716 | node_support_code = r''' | |
712 | 717 | if (typeof process === 'object') { |
713 | 718 | require('fs').readFile(packageName, function(err, contents) { |
714 | 719 | if (err) { |
719 | 724 | }); |
720 | 725 | return; |
721 | 726 | } |
727 | ''' | |
728 | ret += r''' | |
729 | function fetchRemotePackage(packageName, packageSize, callback, errback) { | |
730 | %(node_support_code)s | |
722 | 731 | var xhr = new XMLHttpRequest(); |
723 | 732 | xhr.open('GET', packageName, true); |
724 | 733 | xhr.responseType = 'arraybuffer'; |
769 | 778 | function handleError(error) { |
770 | 779 | console.error('package error:', error); |
771 | 780 | }; |
772 | ''' | |
781 | ''' % {'node_support_code': node_support_code} | |
773 | 782 | |
774 | 783 | code += r''' |
775 | 784 | function processPackageData(arrayBuffer) { |
140 | 140 | |
141 | 141 | for line in lines: |
142 | 142 | arg = line[1:].strip() |
143 | if '::' in arg: | |
144 | arg = arg.split('::', 1)[1] | |
143 | 145 | if line[0] == 'K': |
144 | 146 | # This is a key |
145 | 147 | key = arg |
251 | 253 | show('Compiling generated code...') |
252 | 254 | |
253 | 255 | # -Oz optimizes enough to avoid warnings on code size/num locals |
254 | cmd = [shared.EMCC] + cflags + ['-o', js_file[1], src_file[1], | |
256 | cmd = [shared.EMXX] + cflags + ['-o', js_file[1], src_file[1], | |
255 | 257 | '-O0', |
256 | 258 | '-Werror', |
257 | 259 | '-Wno-format', |
407 | 409 | |
408 | 410 | internal_cflags = [ |
409 | 411 | '-I' + shared.path_from_root('system', 'lib', 'libc', 'musl', 'src', 'internal'), |
412 | '-I' + shared.path_from_root('system', 'lib', 'libcxxabi', 'src'), | |
413 | '-D__USING_EMSCRIPTEN_EXCEPTIONS__', | |
410 | 414 | ] |
411 | 415 | |
412 | 416 | # Look for structs in all passed headers. |
5 | 5 | sys.path.insert(1, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
6 | 6 | |
7 | 7 | from tools import building |
8 | from tools.utils import read_file, write_file | |
8 | 9 | |
9 | f = open(sys.argv[1], 'r').read() | |
10 | f = read_file(sys.argv[1]) | |
10 | 11 | orig_size = len(f) |
11 | 12 | |
12 | 13 | f = f.strip() |
50 | 51 | f = re.sub(r'([;{}=,\*/\(\)\[\]])[\s]', r'\1', f) |
51 | 52 | |
52 | 53 | # Finally, rerun minifier because the above changes may have left redundant whitespaces |
53 | open(sys.argv[1], 'w').write(f) | |
54 | write_file(sys.argv[1], f) | |
54 | 55 | minified = building.acorn_optimizer(sys.argv[1], ['minifyWhitespace'], return_output=True) |
55 | open(sys.argv[1], 'w').write(minified) | |
56 | write_file(sys.argv[1], minified) | |
56 | 57 | |
57 | 58 | # optimized_size = len(f) |
58 | 59 | # print('Further optimized ' + str(optimized_size - orig_size) + ' bytes (' + str(orig_size) + ' -> ' + str(optimized_size) + ' bytes, {0:.2f}'.format((optimized_size-orig_size)*100.0/orig_size) + '%)') |
59 | open(sys.argv[1], 'w').write(f) | |
60 | write_file(sys.argv[1], f) |
14 | 14 | sys.path.insert(1, __rootpath__) |
15 | 15 | |
16 | 16 | from tools.toolchain_profiler import ToolchainProfiler |
17 | from tools import building, config, shared | |
17 | from tools import building, config, shared, utils | |
18 | 18 | |
19 | 19 | configuration = shared.configuration |
20 | 20 | temp_files = configuration.get_temp_files() |
150 | 150 | if not isinstance(passes, list): |
151 | 151 | passes = [passes] |
152 | 152 | |
153 | js = open(filename).read() | |
153 | js = utils.read_file(filename) | |
154 | 154 | if os.linesep != '\n': |
155 | 155 | js = js.replace(os.linesep, '\n') # we assume \n in the splitting code |
156 | 156 | |
332 | 332 | acorn_passes.append('minifyWhitespace') |
333 | 333 | cld = building.acorn_optimizer(cld, acorn_passes) |
334 | 334 | temp_files.note(cld) |
335 | coutput = open(cld).read() | |
335 | coutput = utils.read_file(cld) | |
336 | 336 | |
337 | 337 | coutput = coutput.replace('wakaUnknownBefore();', start_asm) |
338 | 338 | after = 'wakaUnknownAfter' |
363 | 363 | # sort functions by size, to make diffing easier and to improve aot times |
364 | 364 | funcses = [] |
365 | 365 | for out_file in filenames: |
366 | funcses.append(split_funcs(open(out_file).read(), False)) | |
366 | funcses.append(split_funcs(utils.read_file(out_file), False)) | |
367 | 367 | funcs = [item for sublist in funcses for item in sublist] |
368 | 368 | funcses = None |
369 | 369 | if not os.environ.get('EMCC_NO_OPT_SORT'): |
380 | 380 | else: |
381 | 381 | # just concat the outputs |
382 | 382 | for out_file in filenames: |
383 | f.write(open(out_file).read()) | |
383 | f.write(utils.read_file(out_file)) | |
384 | 384 | |
385 | 385 | with ToolchainProfiler.profile_block('write_post'): |
386 | 386 | f.write('\n') |
149 | 149 | |
150 | 150 | var start = Date.now(); |
151 | 151 | var compressedData = MiniLZ4.compressPackage(data); |
152 | nodeFS['writeFileSync'](output, Buffer(compressedData['data'])); | |
152 | nodeFS['writeFileSync'](output, Buffer.from(compressedData['data'])); | |
153 | 153 | compressedData['data'] = null; |
154 | 154 | printErr('compressed in ' + (Date.now() - start) + ' ms'); |
155 | 155 | print(JSON.stringify(compressedData)); |
6 | 6 | |
7 | 7 | from tools import shared |
8 | 8 | from tools import line_endings |
9 | from tools import utils | |
9 | 10 | from tools.settings import settings |
10 | 11 | |
11 | 12 | logger = logging.getLogger('minimal_runtime_shell') |
156 | 157 | |
157 | 158 | def generate_minimal_runtime_html(target, options, js_target, target_basename): |
158 | 159 | logger.debug('generating HTML for minimal runtime') |
159 | shell = open(options.shell_path, 'r').read() | |
160 | shell = utils.read_file(options.shell_path) | |
160 | 161 | if settings.SINGLE_FILE: |
161 | 162 | # No extra files needed to download in a SINGLE_FILE build. |
162 | 163 | shell = shell.replace('{{{ DOWNLOAD_JS_AND_WASM_FILES }}}', '') |
165 | 166 | |
166 | 167 | temp_files = shared.configuration.get_temp_files() |
167 | 168 | with temp_files.get_file(suffix='.js') as shell_temp: |
168 | open(shell_temp, 'w').write(shell) | |
169 | utils.write_file(shell_temp, shell) | |
169 | 170 | shell = shared.read_and_preprocess(shell_temp) |
170 | 171 | |
171 | 172 | if re.search(r'{{{\s*SCRIPT\s*}}}', shell): |
177 | 178 | |
178 | 179 | # In SINGLE_FILE build, embed the main .js file into the .html output |
179 | 180 | if settings.SINGLE_FILE: |
180 | js_contents = open(js_target).read() | |
181 | js_contents = utils.read_file(js_target) | |
181 | 182 | shared.try_delete(js_target) |
182 | 183 | else: |
183 | 184 | js_contents = '' |
4 | 4 | |
5 | 5 | import os |
6 | 6 | import shutil |
7 | from pathlib import Path | |
7 | 8 | |
8 | 9 | TAG = 'version_1' |
9 | 10 | HASH = '0d0b1280ba0501ad0a23cf1daa1f86821c722218b59432734d3087a89acd22aabd5c3e5e1269700dcd41e87073046e906060f167c032eb91a3ac8c5808a02783' |
25 | 26 | os.makedirs(dest_path) |
26 | 27 | shutil.rmtree(dest_path, ignore_errors=True) |
27 | 28 | shutil.copytree(source_path, dest_path) |
28 | open(os.path.join(dest_path, 'include/ftconfig.h'), 'w').write(ftconf_h) | |
29 | Path(dest_path, 'include/ftconfig.h').write_text(ftconf_h) | |
29 | 30 | |
30 | 31 | # build |
31 | 32 | srcs = ['src/autofit/autofit.c', |
3 | 3 | # found in the LICENSE file. |
4 | 4 | |
5 | 5 | import os |
6 | import shutil | |
7 | 6 | import logging |
8 | 7 | |
9 | from tools import system_libs | |
10 | ||
11 | TAG = '1.7.5' | |
12 | HASH = 'c2c13fc97bb74f0f13092b07804f7087e948bce49793f48b62c2c24a5792523acc0002840bebf21829172bb2e7c3df9f9625250aec6c786a55489667dd04d6a0' | |
8 | TAG = '2.8.1' | |
9 | HASH = 'c969ec1677f2f023c05698a226c96b23a815db732f1561d486b25b07c3663ea8192e49ee1253b7b623b43d713b9230df3265a47da6fd65378256ecada90c6ae4' | |
13 | 10 | |
14 | 11 | deps = ['freetype'] |
12 | ||
13 | srcs = ''' | |
14 | hb-aat-layout.cc | |
15 | hb-aat-map.cc | |
16 | hb-blob.cc | |
17 | hb-buffer-serialize.cc | |
18 | hb-buffer.cc | |
19 | hb-common.cc | |
20 | hb-draw.cc | |
21 | hb-face.cc | |
22 | hb-fallback-shape.cc | |
23 | hb-font.cc | |
24 | hb-map.cc | |
25 | hb-number.cc | |
26 | hb-ot-cff1-table.cc | |
27 | hb-ot-cff2-table.cc | |
28 | hb-ot-color.cc | |
29 | hb-ot-face.cc | |
30 | hb-ot-font.cc | |
31 | hb-ot-layout.cc | |
32 | hb-ot-map.cc | |
33 | hb-ot-math.cc | |
34 | hb-ot-meta.cc | |
35 | hb-ot-metrics.cc | |
36 | hb-ot-name.cc | |
37 | hb-ot-shape-complex-arabic.cc | |
38 | hb-ot-shape-complex-default.cc | |
39 | hb-ot-shape-complex-hangul.cc | |
40 | hb-ot-shape-complex-hebrew.cc | |
41 | hb-ot-shape-complex-indic-table.cc | |
42 | hb-ot-shape-complex-indic.cc | |
43 | hb-ot-shape-complex-khmer.cc | |
44 | hb-ot-shape-complex-myanmar.cc | |
45 | hb-ot-shape-complex-syllabic.cc | |
46 | hb-ot-shape-complex-thai.cc | |
47 | hb-ot-shape-complex-use.cc | |
48 | hb-ot-shape-complex-vowel-constraints.cc | |
49 | hb-ot-shape-fallback.cc | |
50 | hb-ot-shape-normalize.cc | |
51 | hb-ot-shape.cc | |
52 | hb-ot-tag.cc | |
53 | hb-ot-var.cc | |
54 | hb-set.cc | |
55 | hb-shape-plan.cc | |
56 | hb-shape.cc | |
57 | hb-shaper.cc | |
58 | hb-static.cc | |
59 | hb-style.cc | |
60 | hb-ucd.cc | |
61 | hb-unicode.cc | |
62 | hb-glib.cc | |
63 | hb-ft.cc | |
64 | hb-graphite2.cc | |
65 | hb-uniscribe.cc | |
66 | hb-gdi.cc | |
67 | hb-directwrite.cc | |
68 | hb-coretext.cc | |
69 | '''.split() | |
15 | 70 | |
16 | 71 | |
17 | 72 | def needed(settings): |
24 | 79 | |
25 | 80 | def get(ports, settings, shared): |
26 | 81 | ports.fetch_project('harfbuzz', 'https://github.com/harfbuzz/harfbuzz/releases/download/' + |
27 | TAG + '/harfbuzz-' + TAG + '.tar.bz2', 'harfbuzz-' + TAG, sha512hash=HASH) | |
82 | TAG + '/harfbuzz-' + TAG + '.tar.xz', 'harfbuzz-' + TAG, sha512hash=HASH) | |
28 | 83 | |
29 | 84 | def create(final): |
30 | 85 | logging.info('building port: harfbuzz') |
32 | 87 | |
33 | 88 | source_path = os.path.join(ports.get_dir(), 'harfbuzz', 'harfbuzz-' + TAG) |
34 | 89 | build_path = os.path.join(ports.get_build_dir(), 'harfbuzz') |
90 | freetype_include = os.path.join(ports.get_include_dir(), 'freetype2', 'freetype') | |
91 | ports.install_headers(os.path.join(source_path, 'src'), target='harfbuzz') | |
35 | 92 | |
36 | freetype_lib = shared.Cache.get_path(shared.Cache.get_lib_name('libfreetype.a')) | |
37 | freetype_include = os.path.join(ports.get_include_dir(), 'freetype2', 'freetype') | |
38 | freetype_include_dirs = freetype_include + ';' + os.path.join(freetype_include, 'config') | |
93 | # TODO(sbc): Look into HB_TINY, HB_LEAN, HB_MINI options. Remove | |
94 | # HAVE_MMAP/HAVE_MPROTECT/HAVE_SYSCONF since we don't really support those? | |
39 | 95 | |
40 | cmake_cmd = [ | |
41 | shared.EMCMAKE, | |
42 | 'cmake', | |
43 | '-B' + build_path, | |
44 | '-H' + source_path, | |
45 | '-DCMAKE_BUILD_TYPE=Release', | |
46 | '-DCMAKE_INSTALL_PREFIX=' + build_path, | |
47 | '-DFREETYPE_INCLUDE_DIRS=' + freetype_include_dirs, | |
48 | '-DFREETYPE_LIBRARY=' + freetype_lib, | |
49 | '-DHB_HAVE_FREETYPE=ON' | |
50 | ] | |
96 | # These cflags are the ones that the cmake build selects when running emcmake | |
97 | # with harfbuzz | |
98 | cflags = ''' | |
99 | -DHAVE_FREETYPE | |
100 | -DHAVE_ATEXIT | |
101 | -DHAVE_FALLBACK | |
102 | -DHAVE_FT_SET_VAR_BLEND_COORDINATES | |
103 | -DHAVE_INTEL_ATOMIC_PRIMITIVES | |
104 | -DHAVE_MMAP | |
105 | -DHAVE_MPROTECT | |
106 | -DHAVE_OT | |
107 | -DHAVE_STRTOD_L | |
108 | -DHAVE_SYSCONF | |
109 | -DHAVE_UCDN | |
110 | -DHAVE_UNIST_H | |
111 | -DHAVE_XLOCALE_H | |
112 | -DHAVE_SYS_MMAN_H | |
113 | -DHAVE_UNISTD_H | |
114 | -fno-rtti | |
115 | -fno-exceptions | |
116 | -O3 | |
117 | -DNDEBUG | |
118 | '''.split() | |
51 | 119 | |
52 | extra_cflags = [] | |
120 | cflags += ['-I' + freetype_include, '-I' + os.path.join(freetype_include, 'config')] | |
53 | 121 | |
54 | 122 | if settings.RELOCATABLE: |
55 | extra_cflags.append('-fPIC') | |
123 | cflags.append('-fPIC') | |
56 | 124 | |
57 | 125 | if settings.USE_PTHREADS: |
58 | extra_cflags.append('-pthread') | |
126 | cflags.append('-pthread') | |
127 | cflags.append('-DHAVE_PTHREAD') | |
128 | else: | |
129 | cflags.append('-DHB_NO_MT') | |
59 | 130 | |
60 | if len(extra_cflags): | |
61 | cmake_cmd += ['-DCMAKE_CXX_FLAGS="{}"'.format(' '.join(extra_cflags))] | |
62 | cmake_cmd += ['-DCMAKE_C_FLAGS="{}"'.format(' '.join(extra_cflags))] | |
63 | ||
64 | shared.run_process(cmake_cmd, env=system_libs.clean_env()) | |
65 | shared.run_process(['cmake', '--build', build_path, '--target', 'install']) | |
66 | ||
67 | ports.install_header_dir(os.path.join(build_path, 'include', 'harfbuzz')) | |
68 | ||
69 | shutil.copyfile(os.path.join(build_path, 'libharfbuzz.a'), final) | |
131 | commands = [] | |
132 | o_s = [] | |
133 | for src in srcs: | |
134 | o = os.path.join(build_path, src + '.o') | |
135 | shared.safe_ensure_dirs(os.path.dirname(o)) | |
136 | commands.append([shared.EMCC, '-c', os.path.join(source_path, 'src', src), '-o', o] + cflags) | |
137 | o_s.append(o) | |
138 | ports.run_commands(commands) | |
139 | ports.create_lib(final, o_s) | |
70 | 140 | |
71 | 141 | return [shared.Cache.get_lib(get_lib_name(settings), create, what='port')] |
72 | 142 | |
80 | 150 | |
81 | 151 | |
82 | 152 | def process_args(ports): |
83 | return ['-I' + os.path.join(ports.get_build_dir(), 'harfbuzz', 'include', 'harfbuzz')] | |
153 | return ['-I' + os.path.join(ports.get_include_dir(), 'harfbuzz')] | |
84 | 154 | |
85 | 155 | |
86 | 156 | def show(): |
5 | 5 | import os |
6 | 6 | import shutil |
7 | 7 | import logging |
8 | from pathlib import Path | |
8 | 9 | |
9 | 10 | VERSION = '9c' |
10 | 11 | HASH = 'b2affe9a1688bd49fc033f4682c4a242d4ee612f1affaef532f5adcb4602efc4433c4a52a4b3d69e7440ff1f6413b1b041b419bc90efd6d697999961a9a6afb7' |
29 | 30 | shutil.rmtree(dest_path, ignore_errors=True) |
30 | 31 | shutil.copytree(source_path, dest_path) |
31 | 32 | |
32 | open(os.path.join(dest_path, 'jconfig.h'), 'w').write(jconfig_h) | |
33 | Path(dest_path, 'jconfig.h').write_text(jconfig_h) | |
33 | 34 | ports.install_headers(dest_path) |
34 | 35 | |
35 | 36 | ports.build_port( |
5 | 5 | import os |
6 | 6 | import shutil |
7 | 7 | import logging |
8 | from pathlib import Path | |
8 | 9 | |
9 | 10 | TAG = '11022021' |
10 | 11 | HASH = 'f770031ad6c2152cbed8c8eab8edf2be1d27f9e74bc255a9930c17019944ee5fdda5308ea992c66a78af9fe1d8dca090f6c956910ce323f8728247c10e44036b' |
29 | 30 | |
30 | 31 | shutil.rmtree(dest_path, ignore_errors=True) |
31 | 32 | shutil.copytree(source_path, dest_path) |
32 | open(os.path.join(sauce_path, 'config.h'), 'w').write(config_h) | |
33 | Path(sauce_path, 'config.h').write_text(config_h) | |
33 | 34 | |
34 | 35 | flags = [ |
35 | 36 | '-DOPT_GENERIC', |
5 | 5 | import os |
6 | 6 | import shutil |
7 | 7 | import logging |
8 | from pathlib import Path | |
8 | 9 | |
9 | 10 | TAG = '1.6.37' |
10 | 11 | HASH = '2ce2b855af307ca92a6e053f521f5d262c36eb836b4810cb53c809aa3ea2dcc08f834aee0ffd66137768a54397e28e92804534a74abb6fc9f6f3127f14c9c338' |
29 | 30 | shutil.rmtree(dest_path, ignore_errors=True) |
30 | 31 | shutil.copytree(source_path, dest_path) |
31 | 32 | |
32 | open(os.path.join(dest_path, 'pnglibconf.h'), 'w').write(pnglibconf_h) | |
33 | Path(dest_path, 'pnglibconf.h').write_text(pnglibconf_h) | |
33 | 34 | ports.install_headers(dest_path) |
34 | 35 | |
35 | 36 | ports.build_port(dest_path, final, flags=['-s', 'USE_ZLIB=1'], exclude_files=['pngtest'], exclude_dirs=['scripts', 'contrib']) |
5 | 5 | import os |
6 | 6 | import shutil |
7 | 7 | import logging |
8 | from pathlib import Path | |
8 | 9 | |
9 | 10 | TAG = '1.26.2' |
10 | 11 | HASH = 'aa63fcb08b243a1e09f7701b3d84a19d7412a87253d54d49f014fdb9e75bbc81d152a41ed750fccde901453929b2a001585a7645351b41845ad205c17a73dcc9' |
29 | 30 | |
30 | 31 | shutil.rmtree(dest_path, ignore_errors=True) |
31 | 32 | shutil.copytree(source_path, dest_path) |
32 | open(os.path.join(sauce_path, 'config.h'), 'w').write(config_h) | |
33 | open(os.path.join(libmpg123_path, 'mpg123.h'), 'w').write(mpg123_h) | |
33 | Path(sauce_path, 'config.h').write_text(config_h) | |
34 | Path(libmpg123_path, 'mpg123.h').write_text(mpg123_h) | |
34 | 35 | |
35 | 36 | flags = [ |
36 | 37 | '-DOPT_GENERIC', |
5 | 5 | import logging |
6 | 6 | import os |
7 | 7 | import shutil |
8 | from pathlib import Path | |
8 | 9 | |
9 | 10 | TAG = 'version_1' |
10 | 11 | HASH = '929e8d6003c06ae09593021b83323c8f1f54532b67b8ba189f4aedce52c25dc182bac474de5392c46ad5b0dea5a24928e4ede1492d52f4dd5cd58eea9be4dba7' |
27 | 28 | shutil.rmtree(dest_path, ignore_errors=True) |
28 | 29 | shutil.copytree(source_path, dest_path) |
29 | 30 | |
30 | open(os.path.join(dest_path, 'include', 'ogg', 'config_types.h'), 'w').write(config_types_h) | |
31 | Path(dest_path, 'include', 'ogg', 'config_types.h').write_text(config_types_h) | |
31 | 32 | |
32 | 33 | header_dir = os.path.join(ports.get_include_dir(), 'ogg') |
33 | 34 | shutil.rmtree(header_dir, ignore_errors=True) |
4 | 4 | |
5 | 5 | import os |
6 | 6 | import shutil |
7 | from pathlib import Path | |
7 | 8 | |
8 | 9 | TAG = '1.2.11' |
9 | 10 | HASH = 'a42b8359e76cf7b3ae70bf31f0f8a8caa407ac80e8fe08b838076cd5e45ac2e685dae45eb59db2d25543fb3b5bd13b843a02bb8373cda704d7238be50d5e9c68' |
25 | 26 | os.makedirs(dest_path) |
26 | 27 | shutil.rmtree(dest_path, ignore_errors=True) |
27 | 28 | shutil.copytree(source_path, dest_path) |
28 | open(os.path.join(dest_path, 'zconf.h'), 'w').write(zconf_h) | |
29 | Path(dest_path, 'zconf.h').write_text(zconf_h) | |
29 | 30 | ports.install_headers(dest_path) |
30 | 31 | |
31 | 32 | # build |
33 | 34 | commands = [] |
34 | 35 | o_s = [] |
35 | 36 | for src in srcs: |
36 | o = os.path.join(ports.get_build_dir(), 'zlib', src + '.o') | |
37 | o = os.path.join(dest_path, src + '.o') | |
37 | 38 | shared.safe_ensure_dirs(os.path.dirname(o)) |
38 | 39 | commands.append([shared.EMCC, os.path.join(dest_path, src), '-O2', '-o', o, '-I' + dest_path, '-w', '-c']) |
39 | 40 | o_s.append(o) |
95 | 95 | self.allowed_settings.clear() |
96 | 96 | |
97 | 97 | # Load the JS defaults into python. |
98 | settings = open(path_from_root('src', 'settings.js')).read().replace('//', '#') | |
98 | with open(path_from_root('src', 'settings.js')) as fh: | |
99 | settings = fh.read().replace('//', '#') | |
99 | 100 | settings = re.sub(r'var ([\w\d]+)', r'attrs["\1"]', settings) |
100 | 101 | # Variable TARGET_NOT_SUPPORTED is referenced by value settings.js (also beyond declaring it), |
101 | 102 | # so must pass it there explicitly. |
102 | 103 | exec(settings, {'attrs': self.attrs}) |
103 | 104 | |
104 | settings = open(path_from_root('src', 'settings_internal.js')).read().replace('//', '#') | |
105 | with open(path_from_root('src', 'settings_internal.js')) as fh: | |
106 | settings = fh.read().replace('//', '#') | |
105 | 107 | settings = re.sub(r'var ([\w\d]+)', r'attrs["\1"]', settings) |
106 | 108 | internal_attrs = {} |
107 | 109 | exec(settings, {'attrs': internal_attrs}) |
30 | 30 | from . import diagnostics |
31 | 31 | from . import config |
32 | 32 | from . import filelock |
33 | from . import utils | |
33 | 34 | from .settings import settings |
34 | 35 | |
35 | 36 | |
242 | 243 | if check and proc.returncode != 0: |
243 | 244 | raise subprocess.CalledProcessError(proc.returncode, '', stdout, stderr) |
244 | 245 | if TRACK_PROCESS_SPAWNS: |
245 | logging.info('Process ' + str(proc.pid) + ' finished after ' + str(time.time() - start) + ' seconds. Exit code: ' + str(proc.returncode)) | |
246 | logging.info(f'Process {proc.pid} finished after {time.time() - start} seconds. Exit code: {proc.returncode}') | |
246 | 247 | return '\n'.join(out) if full_output else out[0] |
247 | 248 | |
248 | 249 | |
252 | 253 | else: |
253 | 254 | cmd = config.NODE_JS + [path_from_root('node_modules', '.bin', name)] |
254 | 255 | if not os.path.exists(cmd[-1]): |
255 | exit_with_error('%s was not found! Please run "npm install" in Emscripten root directory to set up npm dependencies' % name) | |
256 | exit_with_error(f'{name} was not found! Please run "npm install" in Emscripten root directory to set up npm dependencies') | |
256 | 257 | return cmd |
257 | 258 | |
258 | 259 | |
321 | 322 | return False |
322 | 323 | |
323 | 324 | if version < EXPECTED_NODE_VERSION: |
324 | diagnostics.warning('version-check', 'node version appears too old (seeing "%s", expected "%s")', actual, 'v' + ('.'.join(map(str, EXPECTED_NODE_VERSION)))) | |
325 | expected = '.'.join(str(v) for v in EXPECTED_NODE_VERSION) | |
326 | diagnostics.warning('version-check', f'node version appears too old (seeing "{actual}", expected "v{expected}")') | |
325 | 327 | return False |
326 | 328 | |
327 | 329 | return True |
337 | 339 | |
338 | 340 | |
339 | 341 | def generate_sanity(): |
340 | sanity_file_content = EMSCRIPTEN_VERSION + '|' + config.LLVM_ROOT + '|' + get_clang_version() | |
341 | config_data = open(config.EM_CONFIG).read() | |
342 | sanity_file_content = f'{EMSCRIPTEN_VERSION}|{config.LLVM_ROOT}|{get_clang_version()}' | |
343 | config_data = utils.read_file(config.EM_CONFIG) | |
342 | 344 | checksum = binascii.crc32(config_data.encode()) |
343 | 345 | sanity_file_content += '|%#x\n' % checksum |
344 | 346 | return sanity_file_content |
406 | 408 | sanity_file = Cache.get_path('sanity.txt') |
407 | 409 | with Cache.lock(): |
408 | 410 | if os.path.exists(sanity_file): |
409 | sanity_data = open(sanity_file).read() | |
411 | sanity_data = utils.read_file(sanity_file) | |
410 | 412 | if sanity_data != expected: |
411 | 413 | logger.debug('old sanity: %s' % sanity_data) |
412 | 414 | logger.debug('new sanity: %s' % expected) |
500 | 502 | |
501 | 503 | self.TEMP_DIR = os.environ.get("EMCC_TEMP_DIR", tempfile.gettempdir()) |
502 | 504 | if not os.path.isdir(self.TEMP_DIR): |
503 | exit_with_error("The temporary directory `" + self.TEMP_DIR + "` does not exist! Please make sure that the path is correct.") | |
505 | exit_with_error(f'The temporary directory `{self.TEMP_DIR}` does not exist! Please make sure that the path is correct.') | |
504 | 506 | |
505 | 507 | self.CANONICAL_TEMP_DIR = get_canonical_temp_dir(self.TEMP_DIR) |
506 | 508 | |
509 | 511 | try: |
510 | 512 | safe_ensure_dirs(self.EMSCRIPTEN_TEMP_DIR) |
511 | 513 | except Exception as e: |
512 | exit_with_error(str(e) + 'Could not create canonical temp dir. Check definition of TEMP_DIR in ' + config.EM_CONFIG) | |
514 | exit_with_error(str(e) + f'Could not create canonical temp dir. Check definition of TEMP_DIR in {config.EM_CONFIG}') | |
513 | 515 | |
514 | 516 | # Since the canonical temp directory is, by definition, the same |
515 | 517 | # between all processes that run in DEBUG mode we need to use a multi |
545 | 547 | |
546 | 548 | |
547 | 549 | def target_environment_may_be(environment): |
548 | return settings.ENVIRONMENT == '' or environment in settings.ENVIRONMENT.split(',') | |
550 | return not settings.ENVIRONMENT or environment in settings.ENVIRONMENT.split(',') | |
549 | 551 | |
550 | 552 | |
551 | 553 | def print_compiler_stage(cmd): |
787 | 789 | args += ['--expandMacros'] |
788 | 790 | |
789 | 791 | run_js_tool(path_from_root('tools/preprocessor.js'), args, True, stdout=open(stdout, 'w'), cwd=dirname) |
790 | out = open(stdout, 'r').read() | |
792 | out = utils.read_file(stdout) | |
791 | 793 | |
792 | 794 | return out |
793 | 795 |
97 | 97 | shutil.copyfile(inputs[0], libname) |
98 | 98 | else: |
99 | 99 | building.link_to_object(inputs, libname) |
100 | elif suffix == '.a': | |
100 | else: | |
101 | assert suffix == '.a' | |
101 | 102 | building.emar('cr', libname, inputs) |
102 | else: | |
103 | raise Exception('unknown suffix ' + libname) | |
104 | 103 | |
105 | 104 | |
106 | 105 | def get_wasm_libc_rt_files(): |
150 | 149 | return math_files + other_files + iprintf_files |
151 | 150 | |
152 | 151 | |
152 | def is_case_insensitive(path): | |
153 | """Returns True if the filesystem at `path` is case insensitive.""" | |
154 | utils.write_file(os.path.join(path, 'test_file'), '') | |
155 | case_insensitive = os.path.exists(os.path.join(path, 'TEST_FILE')) | |
156 | os.remove(os.path.join(path, 'test_file')) | |
157 | return case_insensitive | |
158 | ||
159 | ||
153 | 160 | class Library: |
154 | 161 | """ |
155 | 162 | `Library` is the base class of all system libraries. |
342 | 349 | objects = [] |
343 | 350 | cflags = self.get_cflags() |
344 | 351 | base_flags = get_base_cflags() |
352 | case_insensitive = is_case_insensitive(build_dir) | |
345 | 353 | for src in self.get_files(): |
346 | o = os.path.join(build_dir, shared.unsuffixed_basename(src) + '.o') | |
354 | object_basename = shared.unsuffixed_basename(src) | |
355 | # Resolve duplicates by appending unique. | |
356 | # This is needed on case insensitve filesystem to handle, | |
357 | # for example, _exit.o and _Exit.o. | |
358 | if case_insensitive: | |
359 | object_basename = object_basename.lower() | |
360 | o = os.path.join(build_dir, object_basename + '.o') | |
361 | object_uuid = 0 | |
362 | # Find a unique basename | |
363 | while o in objects: | |
364 | object_uuid += 1 | |
365 | o = os.path.join(build_dir, f'{object_basename}__{object_uuid}.o') | |
347 | 366 | ext = shared.suffix(src) |
348 | 367 | if ext in ('.s', '.S', '.c'): |
349 | 368 | cmd = [shared.EMCC] |
692 | 711 | |
693 | 712 | # musl modules |
694 | 713 | ignore = [ |
695 | 'ipc', 'passwd', 'signal', 'sched', 'ipc', 'time', 'linux', | |
714 | 'ipc', 'passwd', 'signal', 'sched', 'time', 'linux', | |
696 | 715 | 'aio', 'exit', 'legacy', 'mq', 'setjmp', 'env', |
697 | 716 | 'ldso' |
698 | 717 | ] |
703 | 722 | 'res_query.c', 'res_querydomain.c', 'gai_strerror.c', |
704 | 723 | 'proto.c', 'gethostbyaddr.c', 'gethostbyaddr_r.c', 'gethostbyname.c', |
705 | 724 | 'gethostbyname2_r.c', 'gethostbyname_r.c', 'gethostbyname2.c', |
706 | 'alarm.c', 'syscall.c', '_exit.c', 'popen.c', | |
725 | 'alarm.c', 'syscall.c', 'popen.c', | |
707 | 726 | 'getgrouplist.c', 'initgroups.c', 'wordexp.c', 'timer_create.c', |
708 | 727 | 'faccessat.c', |
709 | 728 | # 'process' exclusion |
730 | 749 | if self.is_mt: |
731 | 750 | ignore += [ |
732 | 751 | 'clone.c', '__lock.c', |
733 | 'pthread_cleanup_push.c', 'pthread_create.c', | |
752 | 'pthread_create.c', | |
734 | 753 | 'pthread_kill.c', 'pthread_sigmask.c', |
735 | 754 | '__set_thread_area.c', 'synccall.c', |
736 | 755 | '__syscall_cp.c', '__tls_get_addr.c', |
760 | 779 | path_components=['system', 'lib', 'libc', 'musl', 'src', 'thread'], |
761 | 780 | filenames=[ |
762 | 781 | 'pthread_self.c', |
782 | 'pthread_cleanup_push.c', | |
763 | 783 | # C11 thread library functions |
764 | 784 | 'call_once.c', |
765 | 785 | 'tss_create.c', |
825 | 845 | filenames=['sched_yield.c']) |
826 | 846 | |
827 | 847 | libc_files += files_in_path( |
848 | path_components=['system', 'lib', 'libc', 'musl', 'src', 'exit'], | |
849 | filenames=['_Exit.c']) | |
850 | ||
851 | libc_files += files_in_path( | |
852 | path_components=['system', 'lib', 'libc', 'musl', 'src', 'signal'], | |
853 | filenames=[ | |
854 | 'getitimer.c', | |
855 | 'killpg.c', | |
856 | 'setitimer.c', | |
857 | 'sigaddset.c', | |
858 | 'sigdelset.c', | |
859 | 'sigemptyset.c', | |
860 | 'sigfillset.c', | |
861 | 'sigismember.c', | |
862 | 'signal.c', | |
863 | 'sigprocmask.c', | |
864 | 'sigrtmax.c', | |
865 | 'sigrtmin.c', | |
866 | 'sigwait.c', | |
867 | ]) | |
868 | ||
869 | libc_files += files_in_path( | |
828 | 870 | path_components=['system', 'lib', 'libc'], |
829 | 871 | filenames=[ |
830 | 872 | 'extras.c', |
931 | 973 | cflags.append('-D_LIBCXXABI_NO_EXCEPTIONS') |
932 | 974 | elif self.eh_mode == Exceptions.EMSCRIPTEN: |
933 | 975 | cflags.append('-D__USING_EMSCRIPTEN_EXCEPTIONS__') |
976 | # The code used to interpret exceptions during terminate | |
977 | # is not compatible with emscripten exceptions. | |
978 | cflags.append('-DLIBCXXABI_SILENT_TERMINATE') | |
934 | 979 | elif self.eh_mode == Exceptions.WASM: |
935 | 980 | cflags.append('-D__USING_WASM_EXCEPTIONS__') |
936 | 981 | return cflags |
941 | 986 | 'cxa_aux_runtime.cpp', |
942 | 987 | 'cxa_default_handlers.cpp', |
943 | 988 | 'cxa_demangle.cpp', |
944 | 'cxa_exception_storage.cpp', | |
945 | 989 | 'cxa_guard.cpp', |
946 | 990 | 'cxa_handlers.cpp', |
947 | 991 | 'cxa_virtual.cpp', |
956 | 1000 | filenames += ['cxa_noexception.cpp'] |
957 | 1001 | elif self.eh_mode == Exceptions.WASM: |
958 | 1002 | filenames += [ |
1003 | 'cxa_exception_storage.cpp', | |
959 | 1004 | 'cxa_exception.cpp', |
960 | 1005 | 'cxa_personality.cpp' |
961 | 1006 | ] |
1386 | 1431 | # including fprintf etc. |
1387 | 1432 | exit_files = files_in_path( |
1388 | 1433 | path_components=['system', 'lib', 'libc', 'musl', 'src', 'exit'], |
1389 | filenames=['assert.c', 'atexit.c', 'exit.c']) + files_in_path( | |
1390 | path_components=['system', 'lib', 'libc', 'musl', 'src', 'unistd'], | |
1391 | filenames=['_exit.c']) | |
1434 | filenames=['assert.c', 'atexit.c', 'exit.c']) | |
1392 | 1435 | return base_files + time_files + exit_files |
1393 | 1436 | |
1394 | 1437 | |
1490 | 1533 | force = ','.join(name for name, lib in system_libs_map.items() if not lib.never_force) |
1491 | 1534 | force_include = set((force.split(',') if force else []) + forced) |
1492 | 1535 | if force_include: |
1493 | logger.debug('forcing stdlibs: ' + str(force_include)) | |
1536 | logger.debug(f'forcing stdlibs: {force_include}') | |
1494 | 1537 | |
1495 | 1538 | def add_library(libname): |
1496 | 1539 | lib = system_libs_map[libname] |
1630 | 1673 | target = os.path.basename(src_dir) |
1631 | 1674 | dest = os.path.join(Ports.get_include_dir(), target) |
1632 | 1675 | shared.try_delete(dest) |
1633 | logger.debug('installing headers: ' + dest) | |
1676 | logger.debug(f'installing headers: {dest}') | |
1634 | 1677 | shutil.copytree(src_dir, dest) |
1635 | 1678 | |
1636 | 1679 | @staticmethod |
1637 | def install_headers(src_dir, pattern="*.h", target=None): | |
1638 | logger.debug("install_headers") | |
1680 | def install_headers(src_dir, pattern='*.h', target=None): | |
1681 | logger.debug('install_headers') | |
1639 | 1682 | dest = Ports.get_include_dir() |
1640 | 1683 | if target: |
1641 | 1684 | dest = os.path.join(dest, target) |
1642 | 1685 | shared.safe_ensure_dirs(dest) |
1643 | 1686 | matches = glob.glob(os.path.join(src_dir, pattern)) |
1644 | assert matches, "no headers found to install in %s" % src_dir | |
1687 | assert matches, f'no headers found to install in {src_dir}' | |
1645 | 1688 | for f in matches: |
1646 | 1689 | logger.debug('installing: ' + os.path.join(dest, os.path.basename(f))) |
1647 | 1690 | shutil.copyfile(f, os.path.join(dest, os.path.basename(f))) |
1735 | 1778 | shared.exit_with_error('%s is not a known port' % name) |
1736 | 1779 | port = ports.ports_by_name[name] |
1737 | 1780 | if not hasattr(port, 'SUBDIR'): |
1738 | logger.error('port %s lacks .SUBDIR attribute, which we need in order to override it locally, please update it' % name) | |
1781 | logger.error(f'port {name} lacks .SUBDIR attribute, which we need in order to override it locally, please update it') | |
1739 | 1782 | sys.exit(1) |
1740 | 1783 | subdir = port.SUBDIR |
1741 | 1784 | target = os.path.join(fullname, subdir) |
1742 | 1785 | if os.path.exists(target) and not dir_is_newer(path, target): |
1743 | logger.warning('not grabbing local port: ' + name + ' from ' + path + ' to ' + fullname + ' (subdir: ' + subdir + ') as the destination ' + target + ' is newer (run emcc --clear-ports if that is incorrect)') | |
1786 | logger.warning(f'not grabbing local port: {name} from {path} to {fullname} (subdir: {subdir}) as the destination {target} is newer (run emcc --clear-ports if that is incorrect)') | |
1744 | 1787 | else: |
1745 | logger.warning('grabbing local port: ' + name + ' from ' + path + ' to ' + fullname + ' (subdir: ' + subdir + ')') | |
1788 | logger.warning(f'grabbing local port: {name} from {path} to {fullname} (subdir: {subdir})') | |
1746 | 1789 | shared.try_delete(fullname) |
1747 | 1790 | shutil.copytree(path, target) |
1748 | 1791 | Ports.clear_project_build(name) |
1749 | 1792 | return |
1750 | 1793 | |
1751 | if url.endswith('.tar.bz2'): | |
1752 | fullpath = fullname + '.tar.bz2' | |
1753 | elif url.endswith('.tar.gz'): | |
1754 | fullpath = fullname + '.tar.gz' | |
1755 | else: | |
1756 | fullpath = fullname + '.zip' | |
1794 | url_filename = url.rsplit('/')[-1] | |
1795 | ext = url_filename.split('.', 1)[1] | |
1796 | fullpath = fullname + '.' + ext | |
1757 | 1797 | |
1758 | 1798 | if name not in Ports.name_cache: # only mention each port once in log |
1759 | logger.debug('including port: ' + name) | |
1760 | logger.debug(' (at ' + fullname + ')') | |
1799 | logger.debug(f'including port: {name}') | |
1800 | logger.debug(f' (at {fullname})') | |
1761 | 1801 | Ports.name_cache.add(name) |
1762 | 1802 | |
1763 | 1803 | def retrieve(): |
1764 | 1804 | # retrieve from remote server |
1765 | logger.info('retrieving port: ' + name + ' from ' + url) | |
1805 | logger.info(f'retrieving port: {name} from {url}') | |
1766 | 1806 | try: |
1767 | 1807 | import requests |
1768 | 1808 | response = requests.get(url) |
1775 | 1815 | if sha512hash: |
1776 | 1816 | actual_hash = hashlib.sha512(data).hexdigest() |
1777 | 1817 | if actual_hash != sha512hash: |
1778 | shared.exit_with_error('Unexpected hash: ' + actual_hash + '\n' | |
1818 | shared.exit_with_error(f'Unexpected hash: {actual_hash}\n' | |
1779 | 1819 | 'If you are updating the port, please update the hash in the port module.') |
1780 | 1820 | with open(fullpath, 'wb') as f: |
1781 | 1821 | f.write(data) |
1783 | 1823 | marker = os.path.join(fullname, '.emscripten_url') |
1784 | 1824 | |
1785 | 1825 | def unpack(): |
1786 | logger.info('unpacking port: ' + name) | |
1826 | logger.info(f'unpacking port: {name}') | |
1787 | 1827 | shared.safe_ensure_dirs(fullname) |
1788 | 1828 | shutil.unpack_archive(filename=fullpath, extract_dir=fullname) |
1789 | 1829 | with open(marker, 'w') as f: |
7 | 7 | ''' |
8 | 8 | |
9 | 9 | import os |
10 | from pathlib import Path | |
10 | 11 | |
11 | 12 | |
12 | 13 | def all_children(subdir): |
19 | 20 | if not (x.endswith('.py') or x.endswith('.c') or x.endswith('.cpp') or x.endswith('.h') or x.endswith('.js') or x.endswith('.ll')): |
20 | 21 | continue |
21 | 22 | print(x) |
22 | orig = open(x).read() | |
23 | orig = Path(x).read_text() | |
23 | 24 | fixed = orig.copy() |
24 | 25 | fixed = fixed.replace('Module["print"](', 'out(') |
25 | 26 | fixed = fixed.replace('Module[\'print\'](', 'out(') |
44 | 45 | fixed = fixed.replace('Module.printErr = ', 'err = ') |
45 | 46 | |
46 | 47 | if fixed != orig: |
47 | open(x, 'w').write(fixed) | |
48 | Path(x).write_text(fixed) |
67 | 67 | return exe_file + suffix |
68 | 68 | |
69 | 69 | return None |
70 | ||
71 | ||
72 | def read_file(file_path): | |
73 | """Read from a file opened in text mode""" | |
74 | with open(file_path) as fh: | |
75 | return fh.read() | |
76 | ||
77 | ||
78 | def read_binary(file_path): | |
79 | """Read from a file opened in binary mode""" | |
80 | with open(file_path, 'rb') as fh: | |
81 | return fh.read() | |
82 | ||
83 | ||
84 | def write_file(file_path, text): | |
85 | """Write to a file opened in text mode""" | |
86 | with open(file_path, 'w') as fh: | |
87 | fh.write(text) |
17 | 17 | import os |
18 | 18 | import re |
19 | 19 | from subprocess import Popen, PIPE |
20 | from pathlib import Path | |
20 | 21 | import sys |
21 | 22 | |
22 | 23 | sys.path.insert(1, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
24 | ||
23 | 25 | |
24 | 26 | logger = logging.getLogger('wasm-sourcemap') |
25 | 27 | |
175 | 177 | |
176 | 178 | def read_dwarf_entries(wasm, options): |
177 | 179 | if options.dwarfdump_output: |
178 | output = open(options.dwarfdump_output, 'rb').read() | |
180 | output = Path(options.dwarfdump_output).read_bytes() | |
179 | 181 | elif options.dwarfdump: |
180 | 182 | logger.debug('Reading DWARF information from %s' % wasm) |
181 | 183 | if not os.path.exists(options.dwarfdump): |
12 | 12 | import sys |
13 | 13 | |
14 | 14 | from . import shared |
15 | from . import utils | |
15 | 16 | from .settings import settings |
16 | 17 | |
17 | 18 | sys.path.append(shared.path_from_root('third_party')) |
96 | 97 | # the EMSCRIPTEN_METADATA_MINOR |
97 | 98 | ) |
98 | 99 | |
99 | orig = open(wasm_file, 'rb').read() | |
100 | orig = utils.read_binary(wasm_file) | |
100 | 101 | with open(wasm_file, 'wb') as f: |
101 | 102 | f.write(orig[0:8]) # copy magic number and version |
102 | 103 | # write the special section |
12 | 12 | |
13 | 13 | sys.path.insert(1, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
14 | 14 | |
15 | from tools import shared | |
15 | from tools import shared, utils | |
16 | 16 | |
17 | 17 | sys.path.append(shared.path_from_root('third_party')) |
18 | 18 | sys.path.append(shared.path_from_root('third_party', 'ply')) |
54 | 54 | p.parse(r''' |
55 | 55 | interface VoidPtr { |
56 | 56 | }; |
57 | ''' + open(input_file).read()) | |
57 | ''' + utils.read_file(input_file)) | |
58 | 58 | data = p.finish() |
59 | 59 | |
60 | 60 | interfaces = {} |
381 | 381 | def render_function(class_name, func_name, sigs, return_type, non_pointer, |
382 | 382 | copy, operator, constructor, func_scope, |
383 | 383 | call_content=None, const=False, array_attribute=False): |
384 | global mid_c, mid_js, js_impl_methods | |
385 | ||
386 | 384 | legacy_mode = CHECKS not in ['ALL', 'FAST'] |
387 | 385 | all_checks = CHECKS == 'ALL' |
388 | 386 | |
520 | 518 | body += ' %s%s(%s)%s;\n' % (call_prefix, '_' + c_names[max_args], ', '.join(pre_arg + args), call_postfix) |
521 | 519 | if cache: |
522 | 520 | body += ' ' + cache + '\n' |
523 | mid_js += [r'''%sfunction%s(%s) { | |
521 | mid_js.append(r'''%sfunction%s(%s) { | |
524 | 522 | %s |
525 | };''' % (CONSTRUCTOR_CLOSURE_SUPPRESSIONS, (' ' + func_name) if constructor else '', ', '.join(args), body[:-1])] | |
523 | };''' % (CONSTRUCTOR_CLOSURE_SUPPRESSIONS, (' ' + func_name) if constructor else '', ', '.join(args), body[:-1])) | |
526 | 524 | |
527 | 525 | # C |
528 | 526 | |
578 | 576 | |
579 | 577 | c_return_type = type_to_c(return_type) |
580 | 578 | maybe_const = 'const ' if const else '' |
581 | mid_c += [r''' | |
579 | mid_c.append(r''' | |
582 | 580 | %s%s EMSCRIPTEN_KEEPALIVE %s(%s) { |
583 | 581 | %s %s%s%s; |
584 | 582 | } |
585 | ''' % (maybe_const, type_to_c(class_name) if constructor else c_return_type, c_names[i], full_args, pre, return_prefix, call, return_postfix)] | |
583 | ''' % (maybe_const, type_to_c(class_name) if constructor else c_return_type, c_names[i], full_args, pre, return_prefix, call, return_postfix)) | |
586 | 584 | |
587 | 585 | if not constructor: |
588 | 586 | if i == max_args: |
589 | 587 | dec_args = ', '.join([type_to_cdec(raw[j]) + ' ' + args[j] for j in range(i)]) |
590 | 588 | js_call_args = ', '.join(['%s%s' % (('(int)' if sig[j] in interfaces else '') + take_addr_if_nonpointer(raw[j]), args[j]) for j in range(i)]) |
591 | 589 | |
592 | js_impl_methods += [r''' %s %s(%s) %s { | |
590 | js_impl_methods.append(r''' %s %s(%s) %s { | |
593 | 591 | %sEM_ASM_%s({ |
594 | 592 | var self = Module['getCache'](Module['%s'])[$0]; |
595 | 593 | if (!self.hasOwnProperty('%s')) throw 'a JSImplementation must implement all functions, you forgot %s::%s.'; |
603 | 601 | func_name, |
604 | 602 | ','.join(['$%d' % i for i in range(1, max_args + 1)]), |
605 | 603 | return_postfix, |
606 | (', ' if js_call_args else '') + js_call_args)] | |
604 | (', ' if js_call_args else '') + js_call_args)) | |
607 | 605 | |
608 | 606 | |
609 | 607 | for name, interface in interfaces.items(): |
640 | 638 | mid_js += ['\n// ' + name + '\n'] |
641 | 639 | mid_c += ['\n// ' + name + '\n'] |
642 | 640 | |
643 | global js_impl_methods | |
644 | 641 | js_impl_methods = [] |
645 | 642 | |
646 | 643 | cons = interface.getExtendedAttribute('Constructor') |