# Copyright 2013 The Emscripten Authors.  All rights reserved.
# Emscripten is available under two separate licenses, the MIT license and the
# University of Illinois/NCSA Open Source License.  Both these licenses can be
# found in the LICENSE file.

import json
import logging
import os
import platform
import random
import re
import shutil
import time
from functools import wraps
from pathlib import Path

if __name__ == '__main__':
  raise Exception('do not run this file directly; do something like: test/runner')

import clang_native
import common
from common import (
  EMBUILDER,
  NON_ZERO,
  PYTHON,
  WEBIDL_BINDER,
  RunnerCore,
  compiler_for,
  copy_asset,
  create_file,
  engine_is_bun,
  engine_is_node,
  engine_is_v8,
  env_modify,
  get_nodejs,
  path_from_root,
  read_binary,
  read_file,
  test_file,
)
from decorators import (
  all_engines,
  also_with_minimal_runtime,
  also_with_modularize,
  also_with_nodefs,
  also_with_nodefs_both,
  also_with_noderawfs,
  also_with_pthreads,
  also_with_standalone_wasm,
  also_with_wasmfs,
  also_without_bigint,
  can_do_standalone,
  crossplatform,
  disabled,
  flaky,
  is_slow_test,
  needs_make,
  no_2gb,
  no_4gb,
  no_bun,
  no_deno,
  no_highmem,
  no_wasm64,
  no_windows,
  parameterize,
  parameterized,
  requires_dev_dependency,
  requires_jspi,
  requires_native_clang,
  requires_node,
  requires_node_25,
  requires_pthreads,
  requires_wasm2js,
  requires_wasm_eh,
  skip_if,
  with_all_eh_sjlj,
  with_all_fs,
  with_all_sjlj,
  with_env_modify,
)

from tools import building, config, shared, utils, webassembly
from tools.shared import EMAR, EMCC, EMXX, FILE_PACKAGER, LLVM_COV, LLVM_PROFDATA, PIPE
from tools.utils import LINUX, MACOS, WINDOWS, delete_file, write_file

# decorators for limiting which modes a test can run in

logger = logging.getLogger("test_core")

EM_SIGINT = 2
EM_SIGABRT = 6


def esm_integration(func):
  assert callable(func)

  @wraps(func)
  @no_sanitize('sanitizers do not support WASM_ESM_INTEGRATION')
  def decorated(self, *args, **kwargs):
    self.setup_esm_integration()
    return func(self, *args, **kwargs)

  return decorated


def no_modularize_instance(note):
  assert not callable(note)

  def decorator(func):
    assert callable(func)

    @wraps(func)
    def decorated(self, *args, **kwargs):
      if self.get_setting('MODULARIZE') == 'instance' or self.get_setting('WASM_ESM_INTEGRATION'):
        self.skipTest(note)
      return func(self, *args, **kwargs)
    return decorated

  return decorator


def no_esm_integration(note):
  assert not callable(note)

  def decorator(func):
    assert callable(func)

    @wraps(func)
    def decorated(self, *args, **kwargs):
      if self.get_setting('WASM_ESM_INTEGRATION'):
        self.skipTest(note)
      return func(self, *args, **kwargs)
    return decorated

  return decorator


def wasm_simd(func):
  assert callable(func)

  @wraps(func)
  def decorated(self, *args, **kwargs):
    if self.is_wasm2js():
      self.skipTest('wasm2js only supports MVP for now')
    if '-O3' in self.cflags:
      self.skipTest('SIMD tests are too slow with -O3 in the new LLVM pass manager, https://github.com/emscripten-core/emscripten/issues/13427')
    self.cflags += ['-msimd128', '-fno-lax-vector-conversions']
    return func(self, *args, **kwargs)
  return decorated


def asan(func):
  assert callable(func)

  @wraps(func)
  @no_safe_heap('asan does not work with SAFE_HEAP')
  @no_wasm2js('TODO: ASAN in wasm2js')
  @no_highmem("asan doesn't support GLOBAL_BASE")
  def decorated(self, *args, **kwargs):
    return func(self, *args, **kwargs)

  return decorated


def requires_wasm_workers(func):
  assert callable(func)

  @wraps(func)
  @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with WASM_WORKERS')
  @no_sanitize('sanitizers do not support WASM_WORKERS')
  def decorated(self, *args, **kwargs):
    return func(self, *args, **kwargs)

  return decorated


def wasm_relaxed_simd(func):
  assert callable(func)

  @wraps(func)
  def decorated(self, *args, **kwargs):
    if self.get_setting('MEMORY64') == 2:
      self.skipTest('https://github.com/WebAssembly/binaryen/issues/4638')
    # We don't actually run any tests yet, so don't require any engines.
    if self.is_wasm2js():
      self.skipTest('wasm2js only supports MVP for now')
    self.cflags.append('-mrelaxed-simd')
    return func(self, *args, **kwargs)
  return decorated


def needs_non_trapping_float_to_int(func):
  assert callable(func)

  @wraps(func)
  def decorated(self, *args, **kwargs):
    if self.is_wasm2js():
      self.skipTest('wasm2js only supports MVP for now')
    return func(self, *args, **kwargs)
  return decorated


def needs_dylink(func):
  assert callable(func)

  @wraps(func)
  def decorated(self, *args, **kwargs):
    self.check_dylink()
    return func(self, *args, **kwargs)

  return decorated


def requires_x64_cpu(func):
  assert callable(func)

  @wraps(func)
  def decorated(self, *args, **kwargs):
    if platform.machine().lower() not in {'x86_64', 'amd64'}:
      return self.skipTest(f'This test requires a native x64 CPU. Current CPU is {platform.machine()}.')
    return func(self, *args, **kwargs)

  return decorated


def with_dylink_reversed(func):
  assert callable(func)

  @wraps(func)
  def decorated(self, dylink_reversed, *args, **kwargs):
    self.dylink_reversed = dylink_reversed
    self.check_dylink()

    return func(self, *args, **kwargs)

  parameterize(decorated, {'': (False,),
                           'reversed': (True,)})

  return decorated


no_wasm2js = skip_if('no_wasm2js', lambda t: t.is_wasm2js())

# Some tests are marked as only-wasm2js because they test basic codegen in a way
# that is mainly useful for the wasm2js compiler and not LLVM. LLVM tests its
# own codegen, while wasm2js testing is split between the binaryen repo (which
# tests wat files) and this repo (which tests C/C++ files).
#
# Note that some tests here may seem excessive, e.g., testing 16-bit math, as
# LLVM turns those things into i32 values in wasm anyhow before wasm2js.
# However, it is still useful to test wasm2js there as LLVM emits patterns of
# shifts and such around those values to ensure they operate as 16-bit, and we
# want coverage of that.
only_wasm2js = skip_if('only_wasm2js', lambda t: not t.is_wasm2js())


def with_asyncify_and_jspi(func):
  assert callable(func)

  @wraps(func)
  def metafunc(self, jspi, *args, **kwargs):
    if self.get_setting('WASM_ESM_INTEGRATION'):
      self.skipTest('WASM_ESM_INTEGRATION is not compatible with ASYNCIFY')
    if jspi:
      self.set_setting('JSPI')
      self.require_jspi()
    else:
      self.set_setting('ASYNCIFY')
    return func(self, *args, **kwargs)

  parameterize(metafunc, {'': (False,),
                          'jspi': (True,)})
  return metafunc


def also_with_asyncify_and_jspi(func):
  assert callable(func)

  @wraps(func)
  def metafunc(self, asyncify, *args, **kwargs):
    if asyncify and self.get_setting('WASM_ESM_INTEGRATION'):
      self.skipTest('WASM_ESM_INTEGRATION is not compatible with ASYNCIFY')
    if asyncify == 2:
      self.set_setting('JSPI')
      self.require_jspi()
    elif asyncify == 1:
      self.set_setting('ASYNCIFY')
    else:
      assert asyncify == 0
    return func(self, *args, **kwargs)

  parameterize(metafunc, {'': (0,),
                          'asyncify': (1,),
                          'jspi': (2,)})
  return metafunc


def also_with_wasm_workers(func):
  assert callable(func)

  @wraps(func)
  def metafunc(self, ww, *args, **kwargs):
    f = func
    if ww:
      f = requires_wasm_workers(f)
      self.cflags += ['-sWASM_WORKERS']
    return f(self, *args, **kwargs)

  parameterize(metafunc, {'': (False,), 'ww': (True,)})
  return metafunc


def no_optimize(note=''):
  assert not callable(note)

  def decorator(func):
    assert callable(func)

    @wraps(func)
    def decorated(self, *args, **kwargs):
      if self.is_optimizing():
        self.skipTest(note)
      return func(self, *args, **kwargs)
    return decorated
  return decorator


def no_asan(note):
  assert not callable(note)

  def decorator(func):
    assert callable(func)

    @wraps(func)
    def decorated(self, *args, **kwargs):
      if '-fsanitize=address' in self.cflags:
        self.skipTest(note)
      return func(self, *args, **kwargs)
    return decorated
  return decorator


def no_lsan(note):
  assert not callable(note)

  def decorator(func):
    assert callable(func)

    @wraps(func)
    def decorated(self, *args, **kwargs):
      if '-fsanitize=leak' in self.cflags:
        self.skipTest(note)
      return func(self, *args, **kwargs)
    return decorated
  return decorator


def no_ubsan(note):
  assert not callable(note)

  def decorator(func):
    assert callable(func)

    @wraps(func)
    def decorated(self, *args, **kwargs):
      if '-fsanitize=undefined' in self.cflags:
        self.skipTest(note)
      return func(self, *args, **kwargs)
    return decorated
  return decorator


def no_sanitize(note):
  assert not callable(note)

  def decorator(func):
    assert callable(func)

    @wraps(func)
    def decorated(self, *args, **kwargs):
      if any(a.startswith('-fsanitize=') for a in self.cflags):
        self.skipTest(note)
      return func(self, *args, **kwargs)
    return decorated
  return decorator


def no_wasmfs(note):
  assert not callable(note)

  def decorator(func):
    assert callable(func)

    @wraps(func)
    def decorated(self, *args, **kwargs):
      if self.get_setting('WASMFS'):
        self.skipTest(note)
      return func(self, *args, **kwargs)
    return decorated
  return decorator


def make_no_decorator_for_setting(name):
  def outer_decorator(note):
    assert not callable(note)

    def decorator(func):
      assert callable(func)

      @wraps(func)
      def decorated(self, *args, **kwargs):
        if '=' in name:
          key, val = name.split('=', 1)
        else:
          key = name
          val = 1
        if int(val) == 1 and f'-s{key}' in self.cflags:
          self.skipTest(note)
        if f'-s{key}={val}' in self.cflags or self.get_setting(key) == int(val):
          self.skipTest(note)
        return func(self, *args, **kwargs)
      return decorated
    return decorator
  return outer_decorator


def with_both_text_decoder(func):
  assert callable(func)

  @wraps(func)
  def decorated(self, textdecoder, *args, **kwargs):
    self.set_setting('TEXTDECODER', textdecoder)
    return func(self, *args, **kwargs)

  parameterize(decorated, {'': (1,), 'force_textdecoder': (2,)})

  return decorated


no_safe_heap = make_no_decorator_for_setting('SAFE_HEAP')
no_strict = make_no_decorator_for_setting('STRICT')
no_strict_js = make_no_decorator_for_setting('STRICT_JS')
no_big_endian = make_no_decorator_for_setting('SUPPORT_BIG_ENDIAN')
no_omit_asm_module_exports = make_no_decorator_for_setting('DECLARE_ASM_MODULE_EXPORTS=0')
no_js_math = make_no_decorator_for_setting('JS_MATH')


def is_sanitizing(args):
  return '-fsanitize=' in str(args)


class TestCoreBase(RunnerCore):
  @classmethod
  def setUpClass(cls):
    """SetUpClass included purely so we can verify that is run."""
    super().setUpClass()
    cls.doneSetup = True

  # A simple check whether the compiler arguments cause optimization.
  def is_optimizing(self):
    return '-O' in str(self.cflags) and '-O0' not in self.cflags

  def should_use_closure(self):
    # Don't run closure in all test modes, just a couple, since it slows
    # the tests down quite a bit.
    required = ('-O2', '-Os')
    prohibited = ('-g', '--profiling')
    return all(f not in self.cflags for f in prohibited) and any(f in self.cflags for f in required)

  def setup_esm_integration(self):
    self.require_node_25()
    # Using instance phase imports current generates am ExperimentalWarning under node.
    self.node_args += ['--no-warnings']
    self.set_setting('WASM_ESM_INTEGRATION')
    self.cflags += ['-Wno-experimental']
    if self.is_wasm64():
      self.skipTest('wasm64 requires wasm export wrappers')
    if self.is_wasm2js():
      self.skipTest('WASM_ESM_INTEGRATION is not compatible with wasm2js')

  # Use closure in some tests for some additional coverage
  def maybe_closure(self):
    if '--closure=1' not in self.cflags and self.should_use_closure():
      self.cflags += ['--closure=1']
      logger.debug('using closure compiler..')
      return True
    return False

  def assertStartswith(self, output, prefix):
    self.assertEqual(prefix, output[:len(prefix)])

  def verify_in_strict_mode(self, filename):
    js = read_file(filename)
    base, ext = os.path.split(filename)
    strict_filename = base + '.strict' + ext
    write_file(strict_filename, '"use strict";\n' + js)
    self.run_js(strict_filename)

  def do_core_test(self, testname, **kwargs):
    self.do_runf_out_file(Path('core', testname), **kwargs)

  def get_bullet_library(self, use_cmake):
    if use_cmake:
      configure_commands = ['cmake', '.']
      configure_args = ['-DBUILD_DEMOS=OFF', '-DBUILD_EXTRAS=OFF', '-DUSE_GLUT=OFF',
                        '-DCMAKE_CXX_STANDARD=14']
      # Depending on whether 'configure' or 'cmake' is used to build, Bullet
      # places output files in different directory structures.
      generated_libs = ['src/BulletDynamics/libBulletDynamics.a',
                        'src/BulletCollision/libBulletCollision.a',
                        'src/LinearMath/libLinearMath.a']
    else:
      configure_commands = ['sh', './configure']
      # Force a nondefault --host= so that the configure script will interpret
      # that we are doing cross-compilation
      # and skip attempting to run the generated executable with './a.out',
      # which would fail since we are building a .js file.
      configure_args = ['--disable-shared', '--build=i686-pc-linux-gnu',
                        '--host=i686-pc-linux-gnu', '--disable-demos',
                        '--disable-dependency-tracking']
      generated_libs = ['src/.libs/libBulletDynamics.a',
                        'src/.libs/libBulletCollision.a',
                        'src/.libs/libLinearMath.a']

    return self.get_library('third_party/bullet', generated_libs,
                            configure=configure_commands,
                            configure_args=configure_args,
                            cache_name_extra=configure_commands[0])

  @crossplatform
  def test_hello_world(self):
    self.do_core_test('test_hello_world.c')

  @no_esm_integration('WASM_ASYNC_COMPILATION=0')
  def test_wasm_synchronous_compilation(self):
    if self.get_setting('MODULARIZE') != 'instance':
      self.set_setting('STRICT_JS')
    self.set_setting('WASM_ASYNC_COMPILATION', 0)
    self.do_core_test('test_hello_world.c')

  @also_with_standalone_wasm()
  def test_hello_argc(self):
    self.do_core_test('test_hello_argc.c', args=['hello', 'world'])

  @crossplatform
  @requires_pthreads
  def test_hello_argc_pthreads(self):
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    self.do_core_test('test_hello_argc.c', args=['hello', 'world'])

  @only_wasm2js('test shifts etc. on 64-bit integers')
  def test_intvars(self):
    self.do_core_test('test_intvars.cpp')

  def test_int53(self):
    if common.EMTEST_REBASELINE:
      ret = self.do_runf('core/test_int53.c', interleaved_output=False, cflags=['-DGENERATE_ANSWERS'])
      write_file(test_file('core/test_int53.out'), ret)
    else:
      self.do_core_test('test_int53.c', interleaved_output=False)

  def test_int53_convertI32PairToI53Checked(self):
    if common.EMTEST_REBASELINE:
      ret = self.do_runf('core/test_convertI32PairToI53Checked.cpp', interleaved_output=False, cflags=['-DGENERATE_ANSWERS'])
      write_file(test_file('core/test_convertI32PairToI53Checked.out'), ret)
    else:
      self.do_core_test('test_convertI32PairToI53Checked.cpp', interleaved_output=False)

  @only_wasm2js('test shifts etc. on 64-bit integers')
  def test_i64(self):
    # test shifts etc. on 64-bit integers as well as printf() on them. we need
    # the math testing only for wasm2js but do not apply @only_wasm2js since we
    # do want some testing of 64-bit printf in our libc (which is not tested in
    # clang upstream).
    self.do_core_test('test_i64.c')

  @only_wasm2js('test shifts etc. on 64-bit integers')
  def test_i64_2(self):
    self.do_core_test('test_i64_2.cpp')

  @only_wasm2js('test shifts etc. on 64-bit integers')
  def test_i64_3(self):
    self.do_core_test('test_i64_3.cpp')

  @only_wasm2js('test shifts etc. on 64-bit integers')
  def test_i64_4(self):
    # stuff that also needs sign corrections
    self.do_core_test('test_i64_4.c')

  @only_wasm2js('test shifts etc. on 64-bit integers')
  def test_i64_b(self):
    self.do_core_test('test_i64_b.cpp')

  @only_wasm2js('test shifts etc. on 64-bit integers')
  def test_i64_cmp(self):
    self.do_core_test('test_i64_cmp.cpp')

  @only_wasm2js('test shifts etc. on 64-bit integers')
  def test_i64_cmp2(self):
    self.do_core_test('test_i64_cmp2.c')

  @only_wasm2js('test unions of i64 and double')
  def test_i64_double(self):
    self.do_core_test('test_i64_double.cpp')

  @only_wasm2js('test 64-bit multiply')
  def test_i64_umul(self):
    self.do_core_test('test_i64_umul.c')

  @only_wasm2js('test 64-bit math')
  @also_with_standalone_wasm()
  @no_ubsan('contains UB')
  def test_i64_precise(self):
    self.do_core_test('test_i64_precise.c')

  @only_wasm2js('test 64-bit multiply')
  def test_i64_precise_needed(self):
    self.do_core_test('test_i64_precise_needed.c')

  def test_i64_llabs(self):
    # test the libc llabs() method
    self.do_core_test('test_i64_llabs.c')

  def test_i64_zextneg(self):
    # test zero/sign-extension in printf arguments
    self.do_core_test('test_i64_zextneg.c')

  @only_wasm2js('test 64-bit math')
  def test_i64_7z(self):
    self.do_core_test('test_i64_7z.c', args=['hallo'])

  @only_wasm2js('test 64-bit math with short values')
  def test_i64_i16(self):
    self.do_core_test('test_i64_i16.c')

  @only_wasm2js('test 64-bit/double conversions')
  def test_i64_qdouble(self):
    self.do_core_test('test_i64_qdouble.c')

  @only_wasm2js('tests va_arg() with i64 params')
  def test_i64_varargs(self):
    self.do_core_test('test_i64_varargs.c', args='waka fleefl asdfasdfasdfasdf'.split())

  @no_wasm2js('wasm_bigint')
  @requires_node
  def test_i64_invoke_bigint(self):
    self.do_core_test('test_i64_invoke_bigint.cpp', cflags=['-fexceptions'])

  @only_wasm2js('tests va_arg()')
  def test_vararg_copy(self):
    self.do_runf_out_file('va_arg/test_va_copy.c')

  def test_llvm_fabs(self):
    self.do_core_test('test_llvm_fabs.c')

  @only_wasm2js('tests va_arg()')
  def test_double_varargs(self):
    self.do_core_test('test_double_varargs.c')

  @only_wasm2js('tests va_arg()')
  def test_trivial_struct_varargs(self):
    self.do_core_test('test_trivial_struct_varargs.c')

  @only_wasm2js('tests va_arg()')
  def test_struct_varargs(self):
    self.do_core_test('test_struct_varargs.c')

  @only_wasm2js('tests va_arg()')
  def test_zero_struct_varargs(self):
    self.do_core_test('test_zero_struct_varargs.c')

  @only_wasm2js('tests va_arg()')
  def test_nested_struct_varargs(self):
    self.do_core_test('test_nested_struct_varargs.c')

  @only_wasm2js('tests 32-bit multiplication')
  def test_i32_mul_precise(self):
    self.do_core_test('test_i32_mul_precise.c')

  @only_wasm2js('tests operations on 16-bit values')
  def test_i16_emcc_intrinsic(self):
    self.do_core_test('test_i16_emcc_intrinsic.c')

  @only_wasm2js('tests 64-bit conversions')
  def test_double_i64_conversion(self):
    self.do_core_test('test_double_i64_conversion.c')

  @only_wasm2js('tests float32 ops')
  def test_float32_precise(self):
    self.do_core_test('test_float32_precise.c')

  def test_negative_zero(self):
    self.do_core_test('test_negative_zero.c')

  def test_literal_negative_zero(self):
    self.do_core_test('test_literal_negative_zero.c')

  @only_wasm2js('tests byte conversions')
  @also_with_standalone_wasm()
  def test_bswap64(self):
    self.do_core_test('test_bswap64.cpp')

  def test_sha1(self):
    self.do_runf('third_party/sha1.c', 'SHA1=15dd99a1991e0b3826fede3deffc1feba42278e6')

  def test_core_types(self):
    self.do_runf('core/test_core_types.c')

  def test_cube2md5(self):
    copy_asset('core/test_cube2md5.txt')
    self.do_core_test('test_cube2md5.c', cflags=['--embed-file', 'test_cube2md5.txt'])

  @also_with_standalone_wasm()
  @needs_make('make')
  def test_cube2hash(self):
    # A good test of i64 math
    self.do_run('// empty file', 'Usage: hashstring <seed>',
                libraries=self.get_library('third_party/cube2hash', ['libcube2hash.a'], configure=None),
                includes=[test_file('third_party/cube2hash')], assert_returncode=NON_ZERO)
    js_out = self.output_name('src')

    for text, output in [('fleefl', '892BDB6FD3F62E863D63DA55851700FDE3ACF30204798CE9'),
                         ('fleefl2', 'AA2CC5F96FC9D540CA24FDAF1F71E2942753DB83E8A81B61'),
                         ('64bitisslow', '64D8470573635EC354FEE7B7F87C566FCAF1EFB491041670')]:
      self.do_run(js_out, 'hash value: ' + output, args=[text], no_build=True)

  @only_wasm2js('tests 64-bit alignment of structs')
  def test_align64(self):
    src = r'''
      #include <stdio.h>

      // inspired by poppler

      enum Type {
        A = 10,
        B = 20
      };

      struct Object {
        Type type;
        union {
          int intg;
          double real;
          char *name;
        };
      };

      struct Principal {
        double x;
        Object a;
        double y;
      };

      int main(int argc, char **argv) {
        int base = argc-1;
        Object o[10];
        printf("%zu,%zu\n", sizeof(Object), sizeof(Principal));
        printf("%ld,%ld,%ld,%ld\n", (long)&o[base].type - (long)o, (long)&o[base].intg - (long)o, (long)&o[base].real - (long)o, (long)&o[base].name - (long)o);
        printf("%ld,%ld,%ld,%ld\n", (long)&o[base+1].type - (long)o, (long)&o[base+1].intg - (long)o, (long)&o[base+1].real - (long)o, (long)&o[base+1].name - (long)o);
        Principal p, q;
        p.x = p.y = q.x = q.y = 0;
        p.a.type = A;
        p.a.real = 123.456;
        *(&q.a) = p.a;
        printf("%.2f,%d,%.2f,%.2f : %.2f,%d,%.2f,%.2f\n", p.x, p.a.type, p.a.real, p.y, q.x, q.a.type, q.a.real, q.y);
        return 0;
      }
    '''

    self.do_run(src, '''16,32
0,8,8,8
16,24,24,24
0.00,10,123.46,0.00 : 0.00,10,123.46,0.00
''')

  @only_wasm2js('tests signed vs unsigned values')
  def test_unsigned(self):
    src = '''
      #include <stdio.h>
      const signed char cvals[2] = { -1, -2 }; // compiler can store this as a string, so -1 becomes \\FF, and needs re-signing
      int main()
      {
        {
          unsigned char x = 200;
          printf("*%d*\\n", x);
          unsigned char y = -22;
          printf("*%d*\\n", y);
        }

        int varey = 100;
        unsigned int MAXEY = -1, MAXEY2 = -77;
        printf("*%u,%d,%u*\\n", MAXEY, varey >= MAXEY, MAXEY2); // 100 >= -1? not in unsigned!

        int y = cvals[0];
        printf("*%d,%d,%d,%d*\\n", cvals[0], cvals[0] < 0, y, y < 0);
        y = cvals[1];
        printf("*%d,%d,%d,%d*\\n", cvals[1], cvals[1] < 0, y, y < 0);

        // zext issue - see mathop in jsifier
        unsigned char x8 = -10;
        unsigned long hold = 0;
        hold += x8;
        int y32 = hold+50;
        printf("*%lu,%d*\\n", hold, y32);

        // Comparisons
        x8 = 0;
        for (int i = 0; i < 254; i++) x8++; // make it an actual 254 in JS - not a -2
        printf("*%d,%d*\\n", x8+1 == 0xff, x8+1 != 0xff); // 0xff may be '-1' in the bitcode

        return 0;
      }
    '''
    self.do_run(src, '*4294967295,0,4294967219*\n*-1,1,-1,1*\n*-2,1,-2,1*\n*246,296*\n*1,0*')

    src = '''
      #include <stdio.h>
      int main()
      {
        {
          unsigned char x;
          unsigned char *y = &x;
          *y = -1;
          printf("*%d*\\n", x);
        }
        {
          unsigned short x;
          unsigned short *y = &x;
          *y = -1;
          printf("*%d*\\n", x);
        }
        /*{ // This case is not checked. The hint for unsignedness is just the %u in printf, and we do not analyze that
          unsigned int x;
          unsigned int *y = &x;
          *y = -1;
          printf("*%u*\\n", x);
        }*/
        {
          char x;
          char *y = &x;
          *y = 255;
          printf("*%d*\\n", x);
        }
        {
          char x;
          char *y = &x;
          *y = 65535;
          printf("*%d*\\n", x);
        }
        {
          char x;
          char *y = &x;
          *y = 0xffffffff;
          printf("*%d*\\n", x);
        }
        return 0;
      }
    '''
    self.do_run(src, '*255*\n*65535*\n*-1*\n*-1*\n*-1*', cflags=['-Wno-constant-conversion'])

  @only_wasm2js('tests 1-bit fields')
  def test_bitfields(self):
    self.do_core_test('test_bitfields.c')

  def test_floatvars(self):
    self.do_core_test('test_floatvars.cpp')

  @only_wasm2js('tests pointer casts')
  def test_closebitcasts(self):
    self.do_core_test('closebitcasts.c')

  def test_fast_math(self):
    self.do_core_test('test_fast_math.c', args=['5', '6', '8'], cflags=['-ffast-math'])

  @only_wasm2js('tests division by zero')
  def test_zerodiv(self):
    self.do_core_test('test_zerodiv.c')

  @only_wasm2js('tests multiplication by zero')
  def test_zero_multiplication(self):
    self.do_core_test('test_zero_multiplication.c')

  def test_isnan(self):
    self.do_core_test('test_isnan.c')

  @only_wasm2js('tests globals in static data')
  def test_globaldoubles(self):
    self.do_core_test('test_globaldoubles.c')

  def test_math(self):
    self.do_core_test('test_math.c')

  @only_wasm2js('tests lgamma and signbit')
  def test_math_lgamma(self):
    self.do_runf_out_file('math/lgamma.c', assert_returncode=NON_ZERO)

  @only_wasm2js('tests fmodf (which may use JS math)')
  def test_math_fmodf(self):
    self.do_runf_out_file('math/fmodf.c')

  def test_rounding(self):
    self.do_core_test('test_rounding.c')

  def test_stack(self):
    self.set_setting('INLINING_LIMIT')
    # some extra coverage in all test suites for stack checks
    self.set_setting('STACK_OVERFLOW_CHECK', 2)

    self.do_core_test('test_stack.c')

  def test_stack_align(self):
    self.do_core_test('test_stack_align.c')

  @no_asan('stack size is too low for asan to work properly')
  def test_stack_placement(self):
    self.set_setting('STACK_SIZE', 1024)
    self.do_core_test('test_stack_placement.c')
    self.set_setting('GLOBAL_BASE', '100kb')
    self.do_core_test('test_stack_placement.c')

  @no_sanitize('sanitizers do not yet support dynamic linking')
  @no_wasm2js('MAIN_MODULE support')
  @needs_dylink
  @no_js_math('JS_MATH is not compatible with MAIN_MODULE=1')
  def test_stack_placement_pic(self):
    self.set_setting('STACK_SIZE', 1024)
    self.set_setting('MAIN_MODULE')
    self.do_core_test('test_stack_placement.c')
    self.set_setting('GLOBAL_BASE', '100kb')
    self.do_core_test('test_stack_placement.c')

  def test_mainenv(self):
    self.do_core_test('test_mainenv.c')

  @no_asan('ASan does not support custom memory allocators')
  @no_lsan('LSan does not support custom memory allocators')
  @parameterized({
    'normal': [],
    'memvalidate': ['-DEMMALLOC_MEMVALIDATE'],
    'memvalidate_verbose': ['-DEMMALLOC_MEMVALIDATE', '-DEMMALLOC_VERBOSE', '-DRANDOM_ITERS=130'],
  })
  def test_emmalloc(self, *args):
    self.maybe_closure()
    # in newer clang+llvm, the internal calls to malloc in emmalloc may be optimized under
    # the assumption that they are external, so like in system_libs.py where we build
    # malloc, we need to disable builtin here too
    self.set_setting('MALLOC', 'none')
    self.cflags += [
      '-fno-builtin',
      path_from_root('system/lib/libc/sbrk.c'),
      path_from_root('system/lib/emmalloc.c'),
    ]
    self.do_core_test('test_emmalloc.c', cflags=args)

  @no_asan('ASan does not support custom memory allocators')
  @no_lsan('LSan does not support custom memory allocators')
  def test_emmalloc_usable_size(self):
    self.set_setting('MALLOC', 'emmalloc')
    self.do_core_test('test_malloc_usable_size.c', regex=True)

  @no_asan('ASan does not support custom memory allocators')
  @no_lsan('LSan does not support custom memory allocators')
  @no_highmem('uses INITIAL_MEMORY')
  def test_emmalloc_memory_statistics(self):
    self.set_setting('MALLOC', 'emmalloc')
    self.set_setting('INITIAL_MEMORY', '128MB')
    output = self.do_runf('test_emmalloc_memory_statistics.c')
    self.assertContained('valid allocs: 1', output)
    self.assertContained('emmalloc_validate_memory_regions: 0', output)
    self.assertContained(r'emmalloc_dynamic_heap_size\s*: [1-9]\d+', output, regex=True)
    self.assertContained(r'emmalloc_free_dynamic_memory\s*: [1-9]\d+', output, regex=True)
    self.assertContained(r'numFreeMemoryRegions\s*: [1-9]+', output, regex=True)
    self.assertContained(r'Free memory regions of size \[\d+,\d+\[ bytes: 1 regions', output, regex=True)
    self.assertContained(r'emmalloc_unclaimed_heap_memory\s*: [1-9]\d+', output, regex=True)

  @no_optimize('output is sensitive to optimization flags, so only test unoptimized builds')
  @no_highmem('output is sensitive to absolute data layout')
  @no_asan('ASan does not support custom memory allocators')
  @no_lsan('LSan does not support custom memory allocators')
  def test_emmalloc_trim(self):
    self.set_setting('MALLOC', 'emmalloc')
    self.do_core_test('test_emmalloc_trim.c', cflags=['-sINITIAL_MEMORY=128MB', '-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=2147418112'])

  # Test case against https://github.com/emscripten-core/emscripten/issues/10363
  def test_emmalloc_memalign_corruption(self):
    self.set_setting('MALLOC', 'emmalloc')
    self.do_core_test('test_emmalloc_memalign_corruption.c')

  @also_with_standalone_wasm()
  def test_assert(self):
    self.do_core_test('test_assert.c', assert_returncode=NON_ZERO)

  @crossplatform
  @also_with_standalone_wasm(impure=True)
  def test_longjmp_standalone(self):
    self.do_core_test('test_longjmp.c')

  @with_all_sjlj
  def test_longjmp(self):
    self.do_core_test('test_longjmp.c')

  @requires_wasm_workers
  def test_longjmp_wasm_workers(self):
    self.do_core_test('test_longjmp.c', cflags=['-sWASM_WORKERS'])

  @with_all_sjlj
  def test_longjmp_zero(self):
    if '-fsanitize=undefined' in self.cflags and self.get_setting('SUPPORT_LONGJMP') == 'emscripten':
      # For some reason this tests fails under ubsan, but only with emscripten EH.
      self.skipTest('https://github.com/emscripten-core/emscripten/issues/21533')
    self.do_core_test('test_longjmp_zero.c')

  @requires_wasm_eh
  def test_longjmp_with_and_without_exceptions(self):
    # Emscripten SjLj with and without Emscripten EH support
    self.set_setting('SUPPORT_LONGJMP', 'emscripten')
    self.set_setting('DEFAULT_TO_CXX') # See comments on @with_all_eh_sjlj
    for disable_catching in (0, 1):
      self.set_setting('DISABLE_EXCEPTION_CATCHING', disable_catching)
      self.do_core_test('test_longjmp.c')
    # Wasm SjLj with and without Wasm EH support
    self.clear_setting('DISABLE_EXCEPTION_CATCHING')
    self.set_setting('SUPPORT_LONGJMP', 'wasm')
    if self.is_wasm2js():
      self.skipTest('wasm2js does not support wasm EH/SjLj')
    # FIXME Temporarily disabled. Enable this later when the bug is fixed.
    if '-fsanitize=address' in self.cflags:
      self.skipTest('Wasm EH does not work with asan yet')
    for legacy in [0, 1]:
      self.set_setting('WASM_LEGACY_EXCEPTIONS', legacy)
      for arg in ('-fwasm-exceptions', '-fno-exceptions'):
        self.do_core_test('test_longjmp.c', cflags=[arg])

  @with_all_sjlj
  def test_longjmp2(self):
    self.do_core_test('test_longjmp2.c')

  @needs_dylink
  @with_all_sjlj
  @no_js_math('JS_MATH is not compatible with MAIN_MODULE=1')
  def test_longjmp2_main_module(self):
    # Test for binaryen regression:
    # https://github.com/WebAssembly/binaryen/issues/2180
    self.set_setting('MAIN_MODULE')
    self.do_core_test('test_longjmp2.c')

  @with_all_sjlj
  def test_longjmp3(self):
    self.do_core_test('test_longjmp3.c')

  @with_all_sjlj
  def test_longjmp4(self):
    self.do_core_test('test_longjmp4.c')

  @with_all_sjlj
  def test_longjmp_funcptr(self):
    self.do_core_test('test_longjmp_funcptr.c')

  @with_all_sjlj
  def test_longjmp_repeat(self):
    self.do_core_test('test_longjmp_repeat.c')

  @with_all_sjlj
  def test_longjmp_stacked(self):
    self.do_core_test('test_longjmp_stacked.c', assert_returncode=NON_ZERO)

  @with_all_sjlj
  def test_longjmp_exc(self):
    self.do_core_test('test_longjmp_exc.c', assert_returncode=NON_ZERO)

  def test_longjmp_throw(self):
    for disable_throw in (0, 1):
      print(disable_throw)
      self.set_setting('DISABLE_EXCEPTION_CATCHING', disable_throw)
      self.do_core_test('test_longjmp_throw.cpp')

  @with_all_sjlj
  def test_longjmp_unwind(self):
    self.do_core_test('test_longjmp_unwind.c', assert_returncode=NON_ZERO)

  @with_all_sjlj
  def test_longjmp_i64(self):
    self.do_core_test('test_longjmp_i64.c', assert_returncode=NON_ZERO, cflags=['-g'])

  @with_all_sjlj
  def test_siglongjmp(self):
    self.do_core_test('test_siglongjmp.c')

  @with_all_sjlj
  def test_setjmp_many(self):
    src = r'''
      #include <stdio.h>
      #include <setjmp.h>

      int main(int argc, char** argv) {
        jmp_buf buf;
        for (int i = 0; i < NUM; i++) printf("%d\n", setjmp(buf));
        if (argc-- == 1131) longjmp(buf, 11);
        return 0;
      }
    '''
    for num in (1, 5, 20, 1000):
      print('NUM=%d' % num)
      self.do_run(src.replace('NUM', str(num)), '0\n' * num)

  @with_all_sjlj
  def test_setjmp_many_2(self):
    src = r'''
#include <setjmp.h>
#include <stdio.h>

jmp_buf env;

void luaWork(int d){
    int x;
    printf("d is at %d\n", d);

    longjmp(env, 1);
}

int main()
{
    const int ITERATIONS=25;
    for(int i = 0; i < ITERATIONS; i++){
        if(!setjmp(env)){
            luaWork(i);
        }
    }
    return 0;
}
'''

    self.do_run(src, r'''d is at 24''')

  @with_all_sjlj
  def test_setjmp_noleak(self):
    self.do_runf('core/test_setjmp_noleak.c', 'ok.')

  @with_all_sjlj
  def test_setjmp_within_loop(self):
    self.do_core_test('test_setjmp_within_loop.c')

  @with_all_eh_sjlj
  def test_exceptions(self):
    self.set_setting('EXCEPTION_DEBUG')
    self.maybe_closure()
    self.do_core_test('test_exceptions.cpp', out_suffix='_caught')

  @requires_wasm_eh
  def test_exceptions_with_and_without_longjmp(self):
    self.set_setting('EXCEPTION_DEBUG')
    self.maybe_closure()
    # Emscripten EH with and without Emscripten SjLj support
    self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
    for support_longjmp in (0, 'emscripten'):
      self.set_setting('SUPPORT_LONGJMP', support_longjmp)
      self.do_core_test('test_exceptions.cpp', out_suffix='_caught')
    # Wasm EH with and without Wasm SjLj support
    self.clear_setting('DISABLE_EXCEPTION_CATCHING')
    if self.is_wasm2js():
      self.skipTest('wasm2js does not support wasm EH/SjLj')
    # FIXME Temporarily disabled. Enable this later when the bug is fixed.
    if '-fsanitize=address' in self.cflags:
      self.skipTest('Wasm EH does not work with asan yet')
    self.cflags.append('-fwasm-exceptions')
    for legacy in [0, 1]:
      self.set_setting('WASM_LEGACY_EXCEPTIONS', legacy)
      for support_longjmp in (0, 'wasm'):
        self.set_setting('SUPPORT_LONGJMP', support_longjmp)
        self.do_core_test('test_exceptions.cpp', out_suffix='_caught')

  def test_exceptions_off(self):
    self.set_setting('DISABLE_EXCEPTION_CATCHING')
    for support_longjmp in (0, 1):
      self.set_setting('SUPPORT_LONGJMP', support_longjmp)
      self.do_runf('core/test_exceptions.cpp', assert_returncode=NON_ZERO)

  @no_modularize_instance('MODULARIZE=instance is not compatible with MINIMAL_RUNTIME')
  def test_exceptions_minimal_runtime(self):
    self.maybe_closure()
    self.set_setting('MINIMAL_RUNTIME')
    self.cflags += ['--pre-js', test_file('minimal_runtime_exit_handling.js')]
    for support_longjmp in (0, 1):
      self.set_setting('SUPPORT_LONGJMP', support_longjmp)

      self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
      self.do_core_test('test_exceptions.cpp', out_suffix='_caught')

      self.set_setting('EXCEPTION_DEBUG')
      self.set_setting('DISABLE_EXCEPTION_CATCHING')
      self.do_core_test('test_exceptions.cpp', out_suffix='_uncaught', assert_returncode=NON_ZERO)

  @with_all_eh_sjlj
  def test_exceptions_custom(self):
    self.set_setting('EXCEPTION_DEBUG')
    self.maybe_closure()
    src = r'''
    #include <iostream>

    class MyException {
    public:
        MyException(){ std::cout << "Construct..."; }
        MyException( const MyException & ) { std::cout << "Copy..."; }
        ~MyException(){ std::cout << "Destruct..."; }
    };

    int function() {
        std::cout << "Throw...";
        throw MyException();
    }

    int function2() {
        return function();
    }

    int main() {
        try {
            function2();
        } catch (MyException & e) {
            std::cout << "Caught...";
        }

        try {
            function2();
        } catch (MyException e) {
            std::cout << "Caught...";
        }

        std::cout << "\n";
        return 0;
    }
    '''

    self.do_run(src, 'Throw...Construct...Caught...Destruct...Throw...Construct...Copy...Caught...Destruct...Destruct...\n')

  @with_all_eh_sjlj
  def test_exceptions_2(self):
    for safe in (0, 1):
      print(safe)
      if safe and '-fsanitize=address' in self.cflags:
        # Can't use safe heap with ASan
        continue
      self.set_setting('SAFE_HEAP', safe)
      self.do_core_test('test_exceptions_2.cpp')

  @with_all_eh_sjlj
  def test_exceptions_3(self):
    src = r'''
#include <iostream>
#include <stdexcept>

int main(int argc, char **argv) {
  if (argc != 2) {
    std::cout << "need an arg" << std::endl;
    return 1;
  }

  int arg = argv[1][0] - '0';
  try {
    if (arg == 0) throw "a c string";
    if (arg == 1) throw std::exception();
    if (arg == 2) throw std::runtime_error("Hello");
  } catch(const char * ex) {
    std::cout << "Caught C string: " << ex << std::endl;
  } catch(const std::exception &ex) {
    std::cout << "Caught exception: " << ex.what() << std::endl;
  } catch(...) {
    std::cout << "Caught something else" << std::endl;
  }

  std::cout << "Done.\n";
}
'''

    js_out = self.output_name('src')
    print('0')
    self.do_run(src, 'Caught C string: a c string\nDone.', args=['0'])
    print('1')
    self.do_run(js_out, 'Caught exception: std::exception\nDone.', args=['1'], no_build=True)
    print('2')
    self.do_run(js_out, 'Caught exception: Hello\nDone.', args=['2'], no_build=True)

  def test_exceptions_allowed(self):
    self.set_setting('EXCEPTION_CATCHING_ALLOWED', ["_Z12somefunctionv"])
    # otherwise it is inlined and not identified
    self.set_setting('INLINING_LIMIT')

    self.do_core_test('test_exceptions_allowed.cpp')
    js_out = self.output_name('test_exceptions_allowed')
    size = os.path.getsize(js_out)
    if self.is_wasm():
      size += os.path.getsize('test_exceptions_allowed.wasm')
    shutil.copy(js_out, 'orig.js')

    # check that an empty allow list works properly (as in, same as exceptions disabled)

    self.set_setting('EXCEPTION_CATCHING_ALLOWED', [])
    self.do_core_test('test_exceptions_allowed.cpp', out_suffix='_empty', assert_returncode=NON_ZERO)
    empty_size = os.path.getsize(js_out)
    if self.is_wasm():
      empty_size += os.path.getsize('test_exceptions_allowed.wasm')
    shutil.copy(js_out, 'empty.js')

    self.set_setting('EXCEPTION_CATCHING_ALLOWED', ['fake'])
    self.do_core_test('test_exceptions_allowed.cpp', out_suffix='_empty', assert_returncode=NON_ZERO)
    fake_size = os.path.getsize(js_out)
    if self.is_wasm():
      fake_size += os.path.getsize('test_exceptions_allowed.wasm')
    shutil.copy(js_out, 'fake.js')

    self.clear_setting('EXCEPTION_CATCHING_ALLOWED')
    self.do_core_test('test_exceptions_allowed.cpp', out_suffix='_empty', assert_returncode=NON_ZERO)
    disabled_size = os.path.getsize(js_out)
    if self.is_wasm():
      disabled_size += os.path.getsize('test_exceptions_allowed.wasm')
    shutil.copy(js_out, 'disabled.js')

    print('size: %d' % size)
    print('empty_size: %d' % empty_size)
    print('fake_size: %d' % fake_size)
    print('disabled_size: %d' % disabled_size)
    # empty list acts the same as fully disabled
    self.assertEqual(empty_size, disabled_size)
    # big change when we disable exception catching of the function
    if '-fsanitize=leak' not in self.cflags:
      self.assertGreater(size - empty_size, 0.01 * size)
    # full disable can remove a little bit more
    # For some reason this no longer holds true at high optimizations
    # levels: https://github.com/emscripten-core/emscripten/issues/18312
    if not any(o in self.cflags for o in ('-O3', '-Oz', '-Os')):
      self.assertLess(disabled_size, fake_size)

  def test_exceptions_allowed_2(self):
    self.set_setting('EXCEPTION_CATCHING_ALLOWED', ["main"])
    # otherwise it is inlined and not identified
    self.set_setting('INLINING_LIMIT')
    self.do_core_test('test_exceptions_allowed_2.cpp')

    # When 'main' function does not have a signature, its contents will be
    # outlined to '__original_main'. Check if we can handle that case.
    self.do_core_test('test_exceptions_allowed_2.cpp', cflags=['-DMAIN_NO_SIGNATURE'])

  def test_exceptions_allowed_uncaught(self):
    self.set_setting('EXCEPTION_CATCHING_ALLOWED', ["_Z4testv"])
    # otherwise it is inlined and not identified
    self.set_setting('INLINING_LIMIT')
    self.do_core_test('test_exceptions_allowed_uncaught.cpp', cflags=['-std=c++11'])

  def test_exceptions_allowed_misuse(self):
    self.set_setting('EXCEPTION_CATCHING_ALLOWED', ['foo'])

    # Test old =2 setting for DISABLE_EXCEPTION_CATCHING
    self.set_setting('DISABLE_EXCEPTION_CATCHING', 2)
    expected = 'error: DISABLE_EXCEPTION_CATCHING=X is no longer needed when specifying EXCEPTION_CATCHING_ALLOWED [-Wdeprecated] [-Werror]'
    self.assert_fail([EMCC, test_file('hello_world.c')] + self.get_cflags(), expected)

    # =0 should also be a warning
    self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
    expected = 'error: DISABLE_EXCEPTION_CATCHING=X is no longer needed when specifying EXCEPTION_CATCHING_ALLOWED [-Wdeprecated] [-Werror]'
    self.assert_fail([EMCC, test_file('hello_world.c')] + self.get_cflags(), expected)

    # =1 should be a hard error
    self.set_setting('DISABLE_EXCEPTION_CATCHING', 1)
    expected = 'error: DISABLE_EXCEPTION_CATCHING and EXCEPTION_CATCHING_ALLOWED are mutually exclusive'
    self.assert_fail([EMCC, test_file('hello_world.c')] + self.get_cflags(), expected)

    # even setting an empty list should trigger the error;
    self.set_setting('EXCEPTION_CATCHING_ALLOWED', [])
    expected = 'error: DISABLE_EXCEPTION_CATCHING and EXCEPTION_CATCHING_ALLOWED are mutually exclusive'
    self.assert_fail([EMCC, test_file('hello_world.c')] + self.get_cflags(), expected)

  @with_all_eh_sjlj
  def test_exceptions_uncaught(self):
    src = r'''
      #include <stdio.h>
      #include <exception>
      struct X {
        ~X() {
          printf("exception? %s\n", std::uncaught_exceptions() ? "yes" : "no");
        }
      };
      int main() {
        printf("exception? %s\n", std::uncaught_exceptions() ? "yes" : "no");
        try {
          X x;
          throw 1;
        } catch(...) {
          printf("exception? %s\n", std::uncaught_exceptions() ? "yes" : "no");
        }
        printf("exception? %s\n", std::uncaught_exceptions() ? "yes" : "no");
        return 0;
      }
    '''
    self.do_run(src, 'exception? no\nexception? yes\nexception? no\nexception? no\n')

    src = r'''
      #include <fstream>
      #include <iostream>
      int main() {
        std::ofstream os("test");
        os << std::unitbuf << "foo"; // trigger a call to std::uncaught_exceptions from
                                     // std::basic_ostream::sentry::~sentry
        std::cout << "done\n";
      }
    '''
    self.do_run(src, 'done\n')

  @with_all_eh_sjlj
  def test_exceptions_uncaught_2(self):
    src = r'''
      #include <iostream>
      #include <exception>

      int main() {
        try {
            throw std::exception();
        } catch(std::exception) {
          try {
            throw;
          } catch(std::exception) {}
        }

        if (std::uncaught_exceptions())
          std::cout << "ERROR: uncaught_exceptions still set.\n";
        else
          std::cout << "done\n";
      }
    '''
    self.do_run(src, 'done\n')

  @with_all_eh_sjlj
  def test_exceptions_typed(self):
    # Depends on static destructors running
    self.set_setting('EXIT_RUNTIME')
    self.clear_setting('SAFE_HEAP') # Throwing null will cause an ignorable null pointer access.
    self.do_core_test('test_exceptions_typed.cpp')

  @with_all_eh_sjlj
  def test_exceptions_virtual_inheritance(self):
    self.do_core_test('test_exceptions_virtual_inheritance.cpp')

  @with_all_eh_sjlj
  def test_exceptions_convert(self):
    self.do_core_test('test_exceptions_convert.cpp')

  # TODO Make setjmp-longjmp also use Wasm exception handling
  @with_all_eh_sjlj
  def test_exceptions_multi(self):
    self.do_core_test('test_exceptions_multi.cpp')

  @with_all_eh_sjlj
  def test_exceptions_std(self):
    self.clear_setting('SAFE_HEAP')
    self.do_core_test('test_exceptions_std.cpp')

  @with_all_eh_sjlj
  def test_exceptions_alias(self):
    self.do_core_test('test_exceptions_alias.cpp')

  @with_all_eh_sjlj
  def test_exceptions_rethrow(self):
    self.do_core_test('test_exceptions_rethrow.cpp')

  @with_all_eh_sjlj
  def test_exceptions_uncaught_3(self):
    self.do_core_test('test_exceptions_uncaught_3.cpp')

  @with_all_eh_sjlj
  def test_exceptions_uncaught_4(self):
    self.do_core_test('test_exceptions_uncaught_4.cpp')

  @with_all_eh_sjlj
  def test_exceptions_resume(self):
    self.set_setting('EXCEPTION_DEBUG')
    self.do_core_test('test_exceptions_resume.cpp')

  @with_all_eh_sjlj
  def test_exceptions_destroy_virtual(self):
    self.do_core_test('test_exceptions_destroy_virtual.cpp')

  @with_all_eh_sjlj
  def test_exceptions_refcount(self):
    self.do_core_test('test_exceptions_refcount.cpp')

  @with_all_eh_sjlj
  def test_exceptions_primary(self):
    self.do_core_test('test_exceptions_primary.cpp')

  @with_all_eh_sjlj
  def test_exceptions_simplify_cfg(self):
    self.do_core_test('test_exceptions_simplify_cfg.cpp')

  @with_all_eh_sjlj
  def test_exceptions_libcxx(self):
    self.do_core_test('test_exceptions_libcxx.cpp')

  @with_all_eh_sjlj
  def test_exceptions_multiple_inherit(self):
    self.do_core_test('test_exceptions_multiple_inherit.cpp')

  @with_all_eh_sjlj
  def test_exceptions_multiple_inherit_rethrow(self):
    self.do_core_test('test_exceptions_multiple_inherit_rethrow.cpp')

  @with_all_eh_sjlj
  def test_exceptions_rethrow_missing(self):
    create_file('main.cpp', 'int main() { throw; }')
    self.do_runf('main.cpp', None, assert_returncode=NON_ZERO)

  @with_all_eh_sjlj
  def test_getExceptionMessage(self):
    self.set_setting('ASSERTIONS')
    self.maybe_closure()
    create_file('main.cpp', '''
      #include <emscripten.h>
      #include <exception>
      #include <stdexcept>
      using namespace std;

      class myexception : public exception {
        virtual const char* what() const throw() { return "My exception happened"; }
      } myex;

      EMSCRIPTEN_KEEPALIVE extern "C" void throw_exc(int x) {
        if (x == 1) {
          throw 1000;
        }
        if (x == 2) {
          throw 'c';
        }
        if (x == 3) {
          throw runtime_error("abc");
        }
        if (x == 4) {
          throw myex;
        }
        if (x == 5) {
          throw "abc";
        }
      }

      int main() {
          EM_ASM({
            for (let i = 1; i < 6; i++){
              try {
                  _throw_exc(i);
              } catch(p) {
                  // Because we are catching and handling the exception in JS, the normal
                  // exception catching C++ code doesn't kick in, so we need to make sure we free
                  // the exception, if necessary. By decrementing the refcount we trigger the
                  // free'ing of the exception.
                  out(getExceptionMessage(p).toString());
                  decrementExceptionRefcount(p);
              }
            }
          });
      }
    ''')
    expected = '''\
int,
char,
std::runtime_error,abc
myexception,My exception happened
char const*,
'''

    self.do_runf('main.cpp', expected)

  @with_all_eh_sjlj
  def test_bad_typeid(self):
    self.do_run(r'''
// exception example
#include <iostream>       // std::cerr
#include <typeinfo>       // operator typeid
#include <exception>      // std::exception

class Polymorphic {virtual void member(){}};

int main () {
  try
  {
    Polymorphic * pb = 0;
    const std::type_info& ti = typeid(*pb);  // throws a bad_typeid exception
  }
  catch (std::exception& e)
  {
    std::cerr << "exception caught: " << e.what() << '\n';
  }
  return 0;
}
''', 'exception caught: std::bad_typeid')

  @with_all_eh_sjlj
  def test_abort_no_dtors(self):
    # abort() should not run destructors
    out = self.do_run(r'''
#include <stdlib.h>
#include <stdio.h>

struct Foo {
  ~Foo() { printf("Destructing Foo\n"); }
};

int main() {
  Foo f;
  abort();
}
''', assert_returncode=NON_ZERO)
    self.assertNotContained('Destructing Foo', out)

  def test_iostream_ctors(self):
    # iostream stuff must be globally constructed before user global
    # constructors, so iostream works in global constructors
    self.do_run(r'''
#include <iostream>

struct A {
  A() { std::cout << "bug"; }
};
A a;

int main() {
  std::cout << "free code" << std::endl;
  return 0;
}
''', 'bugfree code')

  @with_all_eh_sjlj
  def test_exceptions_longjmp1(self):
    self.do_core_test('test_exceptions_longjmp1.cpp')

  @with_all_eh_sjlj
  def test_exceptions_longjmp2(self):
    self.do_core_test('test_exceptions_longjmp2.cpp')

  @with_all_eh_sjlj
  def test_exceptions_longjmp3(self):
    self.do_core_test('test_exceptions_longjmp3.cpp')

  @with_all_eh_sjlj
  def test_exceptions_longjmp4(self):
    self.do_core_test('test_exceptions_longjmp4.cpp')

  def test_exception_sjlj_options(self):
    # Clear all settings used in this test
    def clear_all_relevant_settings(self):
      self.clear_setting('DISABLE_EXCEPTION_THROWING')
      self.clear_setting('DISABLE_EXCEPTION_CATCHING')
      self.clear_setting('SUPPORT_LONGJMP')
      self.clear_setting('ASYNCIFY')
      self.clear_setting('WASM_LEGACY_EXCEPTIONS')

    # Emscripten EH and Wasm EH cannot be enabled at the same time
    self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
    expected = 'error: DISABLE_EXCEPTION_CATCHING=0 is not compatible with -fwasm-exceptions'
    self.assert_fail([EMCC, test_file('hello_world.cpp'), '-fwasm-exceptions'] + self.get_cflags(), expected)
    clear_all_relevant_settings(self)

    self.set_setting('DISABLE_EXCEPTION_THROWING', 0)
    expected = 'error: DISABLE_EXCEPTION_THROWING=0 is not compatible with -fwasm-exceptions'
    self.assert_fail([EMCC, test_file('hello_world.cpp'), '-fwasm-exceptions'] + self.get_cflags(), expected)
    clear_all_relevant_settings(self)

    # Emscripten EH: You can't enable catching and disable throwing
    self.set_setting('DISABLE_EXCEPTION_THROWING', 1)
    self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
    expected = "error: DISABLE_EXCEPTION_THROWING was set (probably from -fno-exceptions) but is not compatible with enabling exception catching (DISABLE_EXCEPTION_CATCHING=0). If you don't want exceptions, set DISABLE_EXCEPTION_CATCHING to 1; if you do want exceptions, don't link with -fno-exceptions"
    self.assert_fail([EMCC, test_file('hello_world.cpp')] + self.get_cflags(), expected)
    clear_all_relevant_settings(self)

    # When using Wasm EH, users are not supposed to explicitly pass
    # DISABLE_EXCEPTION_THROWING / DISABLE_EXCEPTION_CATCHING (even in order to
    # correctly disable them; it will be taken care of by emcc)
    # We only warn on these cases, but the tests here error out because the
    # test setting includes -Werror.
    self.set_setting('DISABLE_EXCEPTION_THROWING', 1)
    expected = 'error: you no longer need to pass DISABLE_EXCEPTION_CATCHING or DISABLE_EXCEPTION_THROWING when using Wasm exceptions'
    self.assert_fail([EMCC, test_file('hello_world.cpp'), '-fwasm-exceptions'] + self.get_cflags(), expected)
    clear_all_relevant_settings(self)

    self.set_setting('DISABLE_EXCEPTION_CATCHING', 1)
    expected = 'error: you no longer need to pass DISABLE_EXCEPTION_CATCHING or DISABLE_EXCEPTION_THROWING when using Wasm exceptions'
    self.assert_fail([EMCC, test_file('hello_world.cpp'), '-fwasm-exceptions'] + self.get_cflags(), expected)
    clear_all_relevant_settings(self)

    # Emscripten SjLj and Wasm EH cannot mix
    self.set_setting('SUPPORT_LONGJMP', 'emscripten')
    expected = 'error: SUPPORT_LONGJMP=emscripten is not compatible with -fwasm-exceptions'
    self.assert_fail([EMCC, test_file('hello_world.cpp'), '-fwasm-exceptions'] + self.get_cflags(), expected)
    clear_all_relevant_settings(self)

    # Wasm SjLj and Emscripten EH cannot mix
    self.set_setting('SUPPORT_LONGJMP', 'wasm')
    self.set_setting('DISABLE_EXCEPTION_THROWING', 0)
    expected = 'error: SUPPORT_LONGJMP=wasm cannot be used with DISABLE_EXCEPTION_THROWING=0'
    self.assert_fail([EMCC, test_file('hello_world.cpp')] + self.get_cflags(), expected)
    clear_all_relevant_settings(self)

    self.set_setting('SUPPORT_LONGJMP', 'wasm')
    self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
    expected = 'error: SUPPORT_LONGJMP=wasm cannot be used with DISABLE_EXCEPTION_CATCHING=0'
    self.assert_fail([EMCC, test_file('hello_world.cpp')] + self.get_cflags(), expected)
    clear_all_relevant_settings(self)

    # Wasm EH does not support ASYNCIFY=1
    self.set_setting('ASYNCIFY', 1)
    expected = 'error: ASYNCIFY=1 is not compatible with -fwasm-exceptions. Parts of the program that mix ASYNCIFY and exceptions will not compile.'
    self.assert_fail([EMCC, test_file('hello_world.cpp'), '-fwasm-exceptions'] + self.get_cflags(), expected)
    clear_all_relevant_settings(self)

    # EXCEPTION_STACK_TRACES requires either Emscripten EH or Wasm EH
    self.set_setting('EXCEPTION_STACK_TRACES')
    expected = 'error: EXCEPTION_STACK_TRACES requires either of -fexceptions or -fwasm-exceptions'
    self.assert_fail([EMCC, test_file('hello_world.cpp')] + self.get_cflags(), expected)
    clear_all_relevant_settings(self)

    expected = "error: cannot use 'throw' with exceptions disabled"
    self.assert_fail([EMCC, test_file('core/test_exceptions.cpp'), '-fwasm-exceptions', '-fno-exceptions'] + self.get_cflags(), expected)
    clear_all_relevant_settings(self)
    self.assert_fail([EMCC, test_file('core/test_exceptions.cpp'), '-fno-exceptions', '-fwasm-exceptions'] + self.get_cflags(), expected)
    clear_all_relevant_settings(self)

  # Marked as impure since the WASI reactor modules (modules without main)
  # are not yet supported by the wasm engines we test against.
  @also_with_standalone_wasm(impure=True)
  @no_2gb('https://github.com/WebAssembly/binaryen/issues/5893')
  def test_ctors_no_main(self):
    self.do_core_test('test_ctors_no_main.cpp', cflags=['--no-entry'])

  @no_wasm2js('eval_ctors not supported yet')
  @no_highmem('https://github.com/WebAssembly/binaryen/issues/5893')
  @also_with_standalone_wasm(impure=True)
  def test_eval_ctors_no_main(self):
    self.set_setting('EVAL_CTORS')
    self.do_core_test('test_ctors_no_main.cpp', cflags=['--no-entry'])

  def test_float_builtins(self):
    # tests wasm_libc_rt
    self.do_core_test('test_float_builtins.c')

  @no_asan('SAFE_HEAP cannot be used with ASan')
  def test_segfault(self):
    self.set_setting('SAFE_HEAP')

    for addr in ('get_null()', 'new D2()'):
      print(addr)
      src = r'''
        #include <stdio.h>
        #include <emscripten.h>

        struct Classey {
          virtual void doIt() = 0;
          virtual ~Classey() = default;
        };

        struct D1 : Classey {
          virtual void doIt() { printf("fleefl\n"); }
        };

        struct D2 : Classey {
          virtual void doIt() { printf("marfoosh\n"); }
        };

        EM_JS(Classey*, get_null, (), {
        #if __wasm64__
          return 0n;
        #else
          return 0;
        #endif
        });

        int main(int argc, char **argv) {
          Classey *p = argc == 100 ? new D1() : (Classey*)%s;

          p->doIt();
          delete p;

          return 0;
        }
      ''' % addr
      if 'get_null' in addr:
        self.do_run(src, 'segmentation fault', assert_returncode=NON_ZERO)
      else:
        self.do_run(src, 'marfoosh')

  @only_wasm2js('tests function pointer calls')
  def test_funcptr(self):
    self.do_core_test('test_funcptr.c')

  @only_wasm2js('tests function pointer calls')
  def test_mathfuncptr(self):
    self.do_core_test('test_mathfuncptr.c')

  @only_wasm2js('tests function pointer calls')
  def test_funcptrfunc(self):
    self.do_core_test('test_funcptrfunc.c')

  def test_alloca(self):
    self.do_runf('core/test_alloca.c')

  @also_with_wasmfs
  def test_rename(self):
    if is_sanitizing(self.cflags) and self.get_setting('WASMFS'):
      self.skipTest('https://github.com/emscripten-core/emscripten/issues/15820')
    self.do_runf_out_file('stdio/test_rename.c')

  def test_remove(self):
   self.do_runf_out_file('stdio/test_remove.c')

  def test_alloca_stack(self):
    self.do_core_test('test_alloca_stack.c')

  def test_life(self):
    self.do_runf_out_file('life.c', args=['2'])

  def test_array2(self):
    self.do_core_test('test_array2.c')

  def test_array2b(self):
    self.do_core_test('test_array2b.c')

  def test_constglobalstructs(self):
    self.do_core_test('test_constglobalstructs.c')

  def test_conststructs(self):
    self.do_core_test('test_conststructs.c')

  def test_bigarray(self):
    self.do_core_test('test_bigarray.c')

  def test_mod_globalstruct(self):
    self.do_core_test('test_mod_globalstruct.c')

  def test_sizeof(self):
    self.do_core_test('test_sizeof.c')

  @no_modularize_instance('uses Module object directly')
  def test_llvm_used(self):
    self.do_core_test('test_llvm_used.c')

  @no_asan('SAFE_HEAP cannot be used with ASan')
  def test_set_align(self):
    self.set_setting('SAFE_HEAP')

    self.do_core_test('test_set_align.c')

  @no_modularize_instance('uses Module object directly')
  @parameterized({
    '': (['-sEXPORTED_FUNCTIONS=_main,_save_me_aimee'],),
    # test EXPORT_ALL too
    'export_all': (['-sEXPORT_ALL', '-sMAIN_MODULE'],),
  })
  def test_emscripten_api(self, args):
    if '-sMAIN_MODULE' in args:
      if self.get_setting('JS_MATH'):
        self.skipTest('JS_MATH is not compatible with MAIN_MODULE')
      self.check_dylink()
    self.do_core_test('test_emscripten_api.c', cflags=args)

  def test_emscripten_run_script_string_int(self):
    src = r'''
      #include <stdio.h>
      #include <emscripten.h>

      int main() {
        const char *str = emscripten_run_script_string("1+1");
        printf("got string: %s\n", str);
        return 0;
      }
    '''
    self.do_run(src, '''got string: 2''')

  def test_emscripten_run_script_string_utf8(self):
    src = r'''
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <emscripten.h>

      int main() {
        const char *str = emscripten_run_script_string("'\\u2603 \\u2603 \\u2603 Hello!'");
        printf("length of returned string: %zu. Position of substring 'Hello': %zu\n", strlen(str), strstr(str, "Hello")-str);
        return 0;
      }
    '''
    self.do_run(src, '''length of returned string: 18. Position of substring 'Hello': 12''')

  def test_emscripten_run_script_string_null(self):
    src = r'''
      #include <stdio.h>
      #include <emscripten.h>

      int main() {
        const char *str = emscripten_run_script_string("void(0)");
        if (str) {
          printf("got string: %s\n", str);
        } else {
          puts("got null");
        }
        return 0;
      }
    '''
    self.do_run(src, 'got null')

  def test_emscripten_get_now(self):
    self.maybe_closure()
    self.do_runf('test_emscripten_get_now.c', 'Timer resolution is good')

  def test_emscripten_get_compiler_setting(self):
    if not self.is_optimizing() and ('-flto' in self.cflags or '-flto=thin' in self.cflags):
      self.skipTest('https://github.com/emscripten-core/emscripten/issues/25015')
    self.do_core_test('emscripten_get_compiler_setting.c', cflags=['-sRETAIN_COMPILER_SETTINGS'])

  def test_emscripten_get_compiler_setting_error(self):
    # with assertions, a runtime error is shown if you try to use the API without RETAIN_COMPILER_SETTINGS
    self.do_runf('core/emscripten_get_compiler_setting.c', 'You must build with -sRETAIN_COMPILER_SETTINGS', cflags=['-sASSERTIONS'], assert_returncode=NON_ZERO)

  @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with ASYNCIFY=1')
  def test_emscripten_has_asyncify(self):
    src = r'''
      #include <stdio.h>
      #include <emscripten.h>

      int main() {
        printf("%d\n", emscripten_has_asyncify());
        return 0;
      }
    '''
    self.set_setting('ASYNCIFY', 0)
    self.do_run(src, '0')
    self.set_setting('ASYNCIFY')
    self.do_run(src, '1')

  def test_inlinejs3(self):
    self.do_core_test('test_inlinejs3.c')

    print('no debugger, check validation')
    src = test_file('core/test_inlinejs3.c')
    output = test_file('core/test_inlinejs3.out')
    src = read_file(src).replace('emscripten_debugger();', '')
    self.do_run(src, read_file(output))

  def test_inlinejs4(self):
    self.do_run(r'''
#include <emscripten.h>

#define TO_STRING_INNER(x) #x
#define TO_STRING(x) TO_STRING_INNER(x)
#define assert_msg(msg, file, line) EM_ASM( throw 'Assert (' + msg + ') failed in ' + file + ':' + line + '!'; )
#define assert(expr) { \
  if (!(expr)) { \
    assert_msg(#expr, TO_STRING(__FILE__), TO_STRING(__LINE__)); \
  } \
}

int main(int argc, char **argv) {
  assert(argc != 17);
  assert(false);
  return 0;
}
''', 'false', assert_returncode=NON_ZERO)

  def test_em_asm(self):
    self.maybe_closure()
    self.do_core_test('test_em_asm.cpp')

  def test_em_asm_c(self):
    self.do_core_test('test_em_asm.cpp', force_c=True, cflags=['-std=gnu89'])

  # Tests various different ways to invoke the EM_ASM(), EM_ASM_INT()
  # and EM_ASM_DOUBLE() macros.
  def test_em_asm_2(self):
    self.do_core_test('test_em_asm_2.cpp')
    self.do_core_test('test_em_asm_2.cpp', force_c=True, cflags=['-std=gnu89'])

  # Tests various different ways to invoke the MAIN_THREAD_EM_ASM(),
  # MAIN_THREAD_EM_ASM_INT(), MAIN_THREAD_EM_ASM_PTR, and
  # MAIN_THREAD_EM_ASM_DOUBLE() macros.  This test is identical to
  # test_em_asm_2, just search-replaces EM_ASM to MAIN_THREAD_EM_ASM on the test
  # file. That way if new test cases are added to test_em_asm_2.cpp for EM_ASM,
  # they will also get tested in MAIN_THREAD_EM_ASM form.
  @parameterized({
    '': ([],),
    'pthread': (['-pthread', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'],),
  })
  @flaky('https://github.com/emscripten-core/emscripten/issues/25175')
  def test_main_thread_em_asm(self, args):
    if args:
      self.require_pthreads()
    src = read_file(test_file('core/test_em_asm_2.cpp'))
    create_file('test.cpp', src.replace('EM_ASM', 'MAIN_THREAD_EM_ASM'))

    expected_result = read_file(test_file('core/test_em_asm_2.out'))
    create_file('test.out', expected_result.replace('EM_ASM', 'MAIN_THREAD_EM_ASM'))

    self.do_runf_out_file('test.cpp', cflags=args)
    self.do_runf_out_file('test.cpp', cflags=args, force_c=True)

  @needs_dylink
  @parameterized({
    '': ([], False),
    'relocatable': (['-sMAIN_MODULE=2'], False),
    'force_c': ([], True),
  })
  def test_main_thread_async_em_asm(self, args, force_c=False):
    self.do_core_test('test_main_thread_async_em_asm.cpp', cflags=args, force_c=force_c)

  # Tests MAIN_THREAD_EM_ASM_INT() function call with different signatures.
  def test_main_thread_em_asm_signatures(self):
    self.do_core_test('test_em_asm_signatures.cpp')

  @crossplatform
  def test_em_asm_unicode(self):
    self.do_core_test('test_em_asm_unicode.cpp')
    self.do_core_test('test_em_asm_unicode.cpp', force_c=True)

  def test_em_asm_types(self):
    self.do_core_test('test_em_asm_types.cpp')

  def test_em_asm_types_c(self):
    self.do_core_test('test_em_asm_types.cpp', force_c=True)

  def test_em_asm_unused_arguments(self):
    self.do_core_test('test_em_asm_unused_arguments.cpp')

  # Verify that EM_ASM macros support getting called with multiple arities.
  # Maybe tests will later be joined into larger compilation units?
  # Then this must still be compiled separately from other code using EM_ASM
  # macros with arities 1-3. Otherwise this may incorrectly report a success.
  def test_em_asm_parameter_pack(self):
    self.do_core_test('test_em_asm_parameter_pack.cpp')

  def test_em_asm_arguments_side_effects(self):
    self.do_core_test('test_em_asm_arguments_side_effects.cpp')
    self.do_core_test('test_em_asm_arguments_side_effects.cpp', force_c=True)

  def test_em_asm_direct(self):
    self.do_core_test('test_em_asm_direct.c')

  @needs_dylink
  def test_em_asm_side_module(self):
    self.build('core/test_em_asm_side.c', output_suffix='.wasm', cflags=['-sSIDE_MODULE'], output_basename='side')
    self.do_core_test('test_em_asm_main.c', cflags=['-sMAIN_MODULE=2', 'side.wasm'])

  @parameterized({
    '': ([], False),
    'pthreads': (['-pthread', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'], False),
    'pthreads_dylink': (['-pthread', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME', '-sMAIN_MODULE=2', '-Wno-experimental'], False),
    'c': ([], True),
    'dylink': (['-sMAIN_MODULE=2'], False),
    'dylink_c': (['-sMAIN_MODULE=2'], True),
  })
  @crossplatform
  def test_em_js(self, args, force_c):
    if '-sMAIN_MODULE=2' in args:
      self.check_dylink()
    self.cflags += ['-sEXPORTED_FUNCTIONS=_main,_malloc'] + args
    if '-pthread' in args:
      self.require_pthreads()

    self.do_core_test('test_em_js.cpp', force_c=force_c)
    if self.get_setting('WASM_ESM_INTEGRATION'):
      js_out = 'test_em_js.support.mjs'
    else:
      js_out = self.output_name('test_em_js')
    self.assertContained('no args returning int', read_file(js_out))

  @no_wasm2js('test depends on WASM_BIGINT which is not compatible with wasm2js')
  def test_em_js_i64(self):
    expected = 'emcc: error: using 64-bit arguments in EM_JS function without WASM_BIGINT is not yet fully supported: `foo`'
    self.assert_fail([EMCC, '-Werror', '-sWASM_BIGINT=0', test_file('core/test_em_js_i64.c')], expected)
    self.do_core_test('test_em_js_i64.c')

  def test_em_js_address_taken(self):
    self.do_core_test('test_em_js_address_taken.c')
    if self.check_dylink():
      self.set_setting('MAIN_MODULE', 2)
      self.do_core_test('test_em_js_address_taken.c')

  def test_runtime_stacksave(self):
    self.do_runf('core/test_runtime_stacksave.c', 'done\n')

  # This helper function removes the special 'Warning: Enlarging memory arrays, this is not fast!'
  # warning in WASM2JS modes that can interfere with testing.
  def remove_growth_warning(self, text):
    return re.sub(r"\nWarning: Enlarging memory arrays, this is not fast! \d+,\d+\n", "\n", text)

  # Tests that -sMINIMAL_RUNTIME builds can utilize -sALLOW_MEMORY_GROWTH option.
  @no_highmem('memory growth issues')
  @no_modularize_instance('MODULARIZE=instance is not compatible with MINIMAL_RUNTIME')
  def test_minimal_runtime_memorygrowth(self):
    if self.has_changed_setting('ALLOW_MEMORY_GROWTH'):
      self.skipTest('test needs to modify memory growth')
    self.set_setting('MINIMAL_RUNTIME')
    src = test_file('core/test_memorygrowth.c')
    # Fail without memory growth
    self.do_runf(src, 'OOM', assert_returncode=NON_ZERO)
    # Win with it
    self.set_setting('ALLOW_MEMORY_GROWTH')
    output = self.do_runf(src)
    output = self.remove_growth_warning(output)
    self.assertContained('*pre: hello,4.955*\n*hello,4.955*\n*hello,4.955*', output)

  @no_highmem('memory growth issues')
  def test_memorygrowth(self):
    if self.has_changed_setting('ALLOW_MEMORY_GROWTH'):
      self.skipTest('test needs to modify memory growth')
    if self.maybe_closure():
      # verify NO_DYNAMIC_EXECUTION is compatible with closure
      self.set_setting('DYNAMIC_EXECUTION', 0)
    # With typed arrays in particular, it is dangerous to use more memory than INITIAL_MEMORY,
    # since we then need to enlarge the heap(s).
    src = test_file('core/test_memorygrowth.c')

    # Fail without memory growth
    self.do_runf(src, 'OOM', assert_returncode=NON_ZERO)
    fail = read_file(self.output_name('test_memorygrowth'))

    # Win with it
    self.set_setting('ALLOW_MEMORY_GROWTH')
    output = self.do_runf(src)
    output = self.remove_growth_warning(output)
    self.assertContained('*pre: hello,4.955*\n*hello,4.955*\n*hello,4.955*', output)
    win = read_file(self.output_name('test_memorygrowth'))

    if '-O2' in self.cflags and self.is_wasm2js():
      # Make sure ALLOW_MEMORY_GROWTH generates different code (should be less optimized)
      code_start = '// EMSCRIPTEN_START_FUNCS'
      self.assertContained(code_start, fail)
      fail = fail[fail.find(code_start):]
      win = win[win.find(code_start):]
      assert len(fail) < len(win), 'failing code - without memory growth on - is more optimized, and smaller' + str([len(fail), len(win)])

    # Tracing of memory growths should work
    # (SAFE_HEAP would instrument the tracing code itself, leading to recursion)
    if not self.get_setting('SAFE_HEAP'):
      self.cflags += ['--tracing']
      output = self.do_runf(src)
      output = self.remove_growth_warning(output)
      self.assertContained('*pre: hello,4.955*\n*hello,4.955*\n*hello,4.955*', output)

  @no_highmem('memory growth issues')
  def test_memorygrowth_2(self):
    if self.has_changed_setting('ALLOW_MEMORY_GROWTH'):
      self.skipTest('test needs to modify memory growth')

    # With typed arrays in particular, it is dangerous to use more memory than INITIAL_MEMORY,
    # since we then need to enlarge the heap(s).
    src = test_file('core/test_memorygrowth_2.c')

    # Fail without memory growth
    self.do_runf(src, 'OOM', assert_returncode=NON_ZERO)
    fail = read_file(self.output_name('test_memorygrowth_2'))

    # Win with it
    self.set_setting('ALLOW_MEMORY_GROWTH')
    output = self.do_runf(src)
    output = self.remove_growth_warning(output)
    self.assertContained('*pre: hello,4.955*\n*hello,4.955*\n*hello,4.955*', output)
    win = read_file(self.output_name('test_memorygrowth_2'))

    if '-O2' in self.cflags and self.is_wasm2js():
      # Make sure ALLOW_MEMORY_GROWTH generates different code (should be less optimized)
      assert len(fail) < len(win), 'failing code - without memory growth on - is more optimized, and smaller' + str([len(fail), len(win)])

  def test_memorygrowth_3(self):
    if self.has_changed_setting('ALLOW_MEMORY_GROWTH'):
      self.skipTest('test needs to modify memory growth')

    # checks handling of malloc failure properly
    self.set_setting('ABORTING_MALLOC', 0)
    self.set_setting('SAFE_HEAP')
    self.do_core_test('test_memorygrowth_3.c')

  @also_with_standalone_wasm()
  @no_highmem('depends on INITIAL_MEMORY')
  def test_memorygrowth_MAXIMUM_MEMORY(self):
    if self.has_changed_setting('ALLOW_MEMORY_GROWTH'):
      self.skipTest('test needs to modify memory growth')
    if self.is_wasm2js():
      self.skipTest('wasm memory specific test')

    # check that memory growth does not exceed the wasm mem max limit
    self.cflags += ['-sALLOW_MEMORY_GROWTH', '-sINITIAL_MEMORY=64Mb', '-sMAXIMUM_MEMORY=100Mb']
    self.do_core_test('test_memorygrowth_wasm_mem_max.c')

  @no_highmem('depends on INITIAL_MEMORY')
  def test_memorygrowth_linear_step(self):
    if self.has_changed_setting('ALLOW_MEMORY_GROWTH'):
      self.skipTest('test needs to modify memory growth')
    if self.is_wasm2js():
      self.skipTest('wasm memory specific test')

    # check that memory growth does not exceed the wasm mem max limit and is exactly or one step below the wasm mem max
    self.cflags += ['-sALLOW_MEMORY_GROWTH', '-sSTACK_SIZE=1Mb', '-sINITIAL_MEMORY=32Mb', '-sMAXIMUM_MEMORY=64Mb', '-sMEMORY_GROWTH_LINEAR_STEP=1Mb']
    self.do_core_test('test_memorygrowth_linear_step.c')

  @no_ubsan('UBSan seems to affect the precise memory usage')
  @no_highmem('depends on specific memory layout')
  def test_memorygrowth_geometric_step(self):
    if self.has_changed_setting('ALLOW_MEMORY_GROWTH'):
      self.skipTest('test needs to modify memory growth')
    if self.is_wasm2js():
      self.skipTest('wasm memory specific test')

    self.cflags += ['-sINITIAL_MEMORY=16MB', '-sALLOW_MEMORY_GROWTH', '-sMEMORY_GROWTH_GEOMETRIC_STEP=8.5', '-sMEMORY_GROWTH_GEOMETRIC_CAP=32MB']
    self.do_core_test('test_memorygrowth_geometric_step.c')

  def test_memorygrowth_3_force_fail_reallocBuffer(self):
    if self.has_changed_setting('ALLOW_MEMORY_GROWTH'):
      self.skipTest('test needs to modify memory growth')

    self.set_setting('ALLOW_MEMORY_GROWTH')
    # Force memory growth to fail at runtime
    self.add_pre_run('growMemory = (size) => false;')
    self.do_core_test('test_memorygrowth_3.c')

  @parameterized({
    'nogrow': ([],),
    'grow': (['-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=18MB'],),
  })
  @no_asan('requires more memory when growing')
  @no_lsan('requires more memory when growing')
  @no_highmem('depends on MAXIMUM_MEMORY')
  def test_aborting_new(self, args):
    # test that C++ new properly errors if we fail to malloc when growth is
    # enabled, with or without growth
    self.do_core_test('test_aborting_new.cpp', cflags=args)

  @parameterized({
    'nogrow': (['-sABORTING_MALLOC=0'],),
    'grow': (['-sABORTING_MALLOC=0', '-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=18MB'],),
  })
  @no_asan('requires more memory when growing')
  @no_lsan('requires more memory when growing')
  @no_highmem('depends on MAXIMUM_MEMORY')
  def test_nothrow_new(self, args):
    self.do_core_test('test_nothrow_new.cpp', cflags=args)

  @no_wasm2js('no WebAssembly.Memory()')
  @no_asan('ASan alters the memory size')
  @no_lsan('LSan alters the memory size')
  @no_highmem('depends on memory size')
  @no_esm_integration('external wasmMemory')
  def test_module_wasm_memory(self):
    self.set_setting('IMPORTED_MEMORY')
    self.set_setting('STRICT')
    self.set_setting('INCOMING_MODULE_JS_API', ['wasmMemory'])
    self.do_runf('core/test_module_wasm_memory.c', 'done\n', cflags=['--pre-js', test_file('core/test_module_wasm_memory.js')])

  def test_ssr(self): # struct self-ref
    src = '''
      #include <stdio.h>

      // see related things in openjpeg
      typedef struct opj_mqc_state {
        unsigned int qeval;
        int mps;
        struct opj_mqc_state *nmps;
        struct opj_mqc_state *nlps;
      } opj_mqc_state_t;

      static opj_mqc_state_t mqc_states[4] = {
        {0x5600, 0, &mqc_states[2], &mqc_states[3]},
        {0x5602, 1, &mqc_states[3], &mqc_states[2]},
      };

      int main() {
        printf("*%ld*\\n", (long)(mqc_states+1)-(long)mqc_states);
        for (int i = 0; i < 2; i++)
          printf("%d:%d,%d,%ld,%ld\\n", i, mqc_states[i].qeval, mqc_states[i].mps,
                 (long)mqc_states[i].nmps-(long)mqc_states, (long)mqc_states[i].nlps-(long)mqc_states);
        return 0;
      }
      '''
    if self.is_wasm64():
      expected = '*24*\n0:22016,0,48,72\n1:22018,1,72,48\n'
    else:
      expected = '*16*\n0:22016,0,32,48\n1:22018,1,48,32\n'
    self.do_run(src, expected)

  def test_tinyfuncstr(self):
    self.do_core_test('test_tinyfuncstr.cpp')

  def test_llvmswitch(self):
    self.do_core_test('test_llvmswitch.c')

  @no_wasm2js('massive switches can break js engines')
  def test_bigswitch(self):
    if not self.is_optimizing():
      self.skipTest('nodejs takes ~4GB to compile this if the wasm is not optimized, which OOMs')
    self.set_setting('USE_SDL')
    self.do_runf('core/test_bigswitch.c', '''34962: GL_ARRAY_BUFFER (0x8892)
26214: what?
35040: GL_STREAM_DRAW (0x88E0)
3060: what?
''', args=['34962', '26214', '35040', str(0xbf4)])

  @no_wasm2js('massive switches can break js engines')
  @is_slow_test
  def test_biggerswitch(self):
    if self.is_optimizing():
      self.skipTest('https://github.com/emscripten-core/emscripten/issues/22179')
    if not self.is_optimizing():
      self.skipTest('nodejs takes >6GB to compile this if the wasm is not optimized, which OOMs, see https://github.com/emscripten-core/emscripten/issues/7928#issuecomment-458308453')
    num_cases = 20000
    switch_case = self.run_process([PYTHON, test_file('gen_large_switchcase.py'), str(num_cases)], stdout=PIPE, stderr=PIPE).stdout
    self.do_run(switch_case, '''58996: 589965899658996
59297: 592975929759297
59598: default
59899: 598995989959899
Success!''')

  @no_ubsan('local count too large for VMs')
  def test_indirectbr(self):
    self.do_core_test('test_indirectbr.c')

  @no_asan('local count too large for VMs')
  @no_ubsan('local count too large for VMs')
  @no_wasm2js('extremely deep nesting, hits stack limit on some VMs')
  def test_indirectbr_many(self):
    if not self.is_optimizing():
      self.skipTest('nodejs takes ~1.8GB to compile this if the wasm is not optimized, which can cause OOM on the test runners')
    self.do_core_test('test_indirectbr_many.c')

  def test_pack(self):
    src = '''
      #include <stdio.h>
      #include <string.h>

      #pragma pack(push,1)
      typedef struct header {
          unsigned char  id;
          unsigned short colour;
          unsigned char  desc;
      } header;
      #pragma pack(pop)

      typedef struct fatheader {
          unsigned char  id;
          unsigned short colour;
          unsigned char  desc;
      } fatheader;

      int main( int argc, const char *argv[] ) {
        header ph[2];
        fatheader pfh[2];
        printf("*%zu,%ld,%ld*\\n", sizeof(header), offsetof(header, desc) - offsetof(header, id), (long)(&ph[1])-(long)(&ph[0]));
        printf("*%zu,%ld,%ld*\\n", sizeof(fatheader), offsetof(fatheader, desc) - offsetof(fatheader, id), (long)(&pfh[1])-(long)(&pfh[0]));
        return 0;
      }
      '''
    self.do_run(src, '*4,3,4*\n*6,4,6*')

  def test_varargs(self):
    self.do_core_test('test_varargs.c')

  def test_varargs_multi(self):
    self.do_core_test('test_varargs_multi.c')

  def test_varargs_byval(self):
    src = r'''
      #include <stdio.h>
      #include <stdarg.h>

      typedef struct type_a {
        union {
          double f;
          void *p;
          int i;
          short sym;
        } value;
      } type_a;

      enum mrb_vtype {
        MRB_TT_FALSE = 0,   /*   0 */
        MRB_TT_CLASS = 9    /*   9 */
      };

      typedef struct type_b {
        enum mrb_vtype tt:8;
      } type_b;

      void print_type_a(int argc, ...);
      void print_type_b(int argc, ...);

      int main(int argc, char *argv[])
      {
        type_a a;
        type_b b;
        a.value.p = (void*) 0x12345678;
        b.tt = MRB_TT_CLASS;

        printf("The original address of a is: %p\n", a.value.p);
        printf("The original type of b is: %d\n", b.tt);

        print_type_a(1, a);
        print_type_b(1, b);

        return 0;
      }

      void print_type_a(int argc, ...) {
        va_list ap;
        type_a a;

        va_start(ap, argc);
        a = va_arg(ap, type_a);
        va_end(ap);

        printf("The current address of a is: %p\n", a.value.p);
      }

      void print_type_b(int argc, ...) {
        va_list ap;
        type_b b;

        va_start(ap, argc);
        b = va_arg(ap, type_b);
        va_end(ap);

        printf("The current type of b is: %d\n", b.tt);
      }
      '''
    self.do_run(src, '''The original address of a is: 0x12345678
The original type of b is: 9
The current address of a is: 0x12345678
The current type of b is: 9
''')

  def test_functionpointer_libfunc_varargs(self):
    self.do_core_test('test_functionpointer_libfunc_varargs.c')

  def test_structbyval(self):
    self.set_setting('INLINING_LIMIT')

    # part 1: make sure that normally, passing structs by value works

    src = r'''
      #include <stdio.h>

      struct point
      {
        int x, y;
      };

      void dump(struct point p) {
        p.x++; // should not modify
        p.y++; // anything in the caller!
        printf("dump: %d,%d\n", p.x, p.y);
      }

      void dumpmod(struct point *p) {
        p->x++; // should not modify
        p->y++; // anything in the caller!
        printf("dump: %d,%d\n", p->x, p->y);
      }

      int main( int argc, const char *argv[] ) {
        point p = { 54, 2 };
        printf("pre:  %d,%d\n", p.x, p.y);
        dump(p);
        void (*dp)(point p) = dump; // And, as a function pointer
        dp(p);
        printf("post: %d,%d\n", p.x, p.y);
        dumpmod(&p);
        dumpmod(&p);
        printf("last: %d,%d\n", p.x, p.y);
        return 0;
      }
    '''
    self.do_run(src, 'pre:  54,2\ndump: 55,3\ndump: 55,3\npost: 54,2\ndump: 55,3\ndump: 56,4\nlast: 56,4')

  def test_stdlibs(self):
    # safe heap prints a warning that messes up our output.
    self.set_setting('SAFE_HEAP', 0)
    # needs atexit
    self.set_setting('EXIT_RUNTIME')
    if self.is_wasm64():
      out_suffix = '64'
    else:
      out_suffix = ''
    self.do_core_test('test_stdlibs.c', out_suffix=out_suffix)

  def test_stdbool(self):
    create_file('test_stdbool.c', r'''
        #include <stdio.h>
        #include <stdbool.h>

        int main() {
          bool x = true;
          bool y = false;
          printf("*%d*\n", x != y);
          return 0;
        }
      ''')

    self.do_runf('test_stdbool.c', '*1*')

  def test_strtoll_hex(self):
    # tests strtoll for hex strings (0x...)
    self.do_core_test('test_strtoll_hex.c')

  def test_strtoll_dec(self):
    # tests strtoll for decimal strings (0x...)
    self.do_core_test('test_strtoll_dec.c')

  def test_strtoll_bin(self):
    # tests strtoll for binary strings (0x...)
    self.do_core_test('test_strtoll_bin.c')

  def test_strtoll_oct(self):
    # tests strtoll for decimal strings (0x...)
    self.do_core_test('test_strtoll_oct.c')

  def test_strtol_hex(self):
    # tests strtoll for hex strings (0x...)
    self.do_core_test('test_strtol_hex.c')

  def test_strtol_dec(self):
    # tests strtoll for decimal strings (0x...)
    self.do_core_test('test_strtol_dec.c')

  def test_strtol_bin(self):
    # tests strtoll for binary strings (0x...)
    self.do_core_test('test_strtol_bin.c')

  def test_strtol_oct(self):
    # tests strtoll for decimal strings (0x...)
    self.do_core_test('test_strtol_oct.c')

  @also_with_standalone_wasm()
  def test_atexit(self):
    # Confirms they are called in the proper reverse order
    if not self.get_setting('STANDALONE_WASM'):
      # STANDALONE_WASM mode always sets EXIT_RUNTIME if main exists
      self.set_setting('EXIT_RUNTIME')
    self.do_core_test('test_atexit.c')

  def test_force_exit(self):
    self.set_setting('EXIT_RUNTIME')
    self.do_runf_out_file('test_force_exit.c')

  @no_lsan('https://github.com/emscripten-core/emscripten/issues/15988')
  def test_atexit_threads_stub(self):
    # also tests thread exit (__cxa_thread_atexit)
    self.set_setting('EXIT_RUNTIME')
    self.do_core_test('test_atexit_threads.cpp')

  @requires_pthreads
  def test_atexit_threads(self):
    self.set_setting('EXIT_RUNTIME')
    self.do_core_test('test_atexit_threads.cpp')

  @requires_pthreads
  def test_pthread_cancel(self):
    self.do_runf_out_file('pthread/test_pthread_cancel.c')

  @requires_pthreads
  def test_pthread_cancel_async(self):
    self.do_runf_out_file('pthread/test_pthread_cancel_async.c')

  @no_asan('cannot replace malloc/free with ASan')
  @no_lsan('cannot replace malloc/free with LSan')
  @requires_pthreads
  def test_pthread_proxy_deadlock(self):
    self.do_runf('pthread/test_pthread_proxy_deadlock.c')

  @no_asan('test relies on null pointer reads')
  def test_pthread_specific(self):
    self.do_runf_out_file('pthread/specific.c')

  def test_pthread_equal(self):
    self.do_runf_out_file('pthread/test_pthread_equal.cpp')

  @requires_pthreads
  @also_with_modularize
  def test_pthread_proxying(self):
    if '-sMODULARIZE' in self.cflags:
      if self.get_setting('WASM') == 0:
        self.skipTest('MODULARIZE + WASM=0 + pthreads does not work (#16794)')
      self.set_setting('EXPORT_NAME=ModuleFactory')
    self.maybe_closure()
    self.set_setting('PROXY_TO_PTHREAD')
    if not self.has_changed_setting('INITIAL_MEMORY'):
      self.set_setting('INITIAL_MEMORY=32mb')
    self.do_runf_out_file('pthread/test_pthread_proxying.c', interleaved_output=False)

  @requires_pthreads
  def test_pthread_proxying_cpp(self):
    self.set_setting('PROXY_TO_PTHREAD')
    if not self.has_changed_setting('INITIAL_MEMORY'):
      self.set_setting('INITIAL_MEMORY=32mb')
    self.do_runf_out_file('pthread/test_pthread_proxying_cpp.cpp',
                                 interleaved_output=False)

  @requires_pthreads
  def test_pthread_proxying_dropped_work(self):
    self.do_runf_out_file('pthread/test_pthread_proxying_dropped_work.c')

  @requires_pthreads
  def test_pthread_proxying_canceled_work(self):
    self.set_setting('PROXY_TO_PTHREAD')
    self.do_runf_out_file(
        'pthread/test_pthread_proxying_canceled_work.c',
        interleaved_output=False)

  @requires_pthreads
  @flaky('https://github.com/emscripten-core/emscripten/issues/19795')
  def test_pthread_proxying_refcount(self):
    self.set_setting('EXIT_RUNTIME')
    self.set_setting('ASSERTIONS=0')
    self.do_runf_out_file('pthread/test_pthread_proxying_refcount.c')

  @requires_pthreads
  def test_pthread_dispatch_after_exit(self):
    self.do_runf_out_file('pthread/test_pthread_dispatch_after_exit.c', interleaved_output=False)

  @requires_pthreads
  def test_pthread_atexit(self):
    # Test to ensure threads are still running when atexit-registered functions are called
    self.set_setting('EXIT_RUNTIME')
    self.do_runf_out_file('pthread/test_pthread_atexit.c')

  @requires_pthreads
  def test_pthread_nested_work_queue(self):
    self.do_runf_out_file('pthread/test_pthread_nested_work_queue.c')

  @requires_pthreads
  @flaky('Times out in bigendian0 suite only. https://github.com/emscripten-core/emscripten/issues/25316')
  def test_pthread_thread_local_storage(self):
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    if not self.has_changed_setting('INITIAL_MEMORY'):
      self.set_setting('INITIAL_MEMORY', '300mb')
    self.do_runf_out_file('pthread/test_pthread_thread_local_storage.c')

  @requires_pthreads
  def test_pthread_cleanup(self):
    self.do_runf_out_file('pthread/test_pthread_cleanup.c')

  @requires_pthreads
  def test_pthread_setspecific_mainthread(self):
    print('.. return')
    self.do_runf('pthread/test_pthread_setspecific_mainthread.c', 'done!', cflags=['-DRETURN'])
    print('.. exit')
    self.do_runf('pthread/test_pthread_setspecific_mainthread.c', 'done!', cflags=['-DEXIT'])
    print('.. pthread_exit')
    self.do_runf_out_file('pthread/test_pthread_setspecific_mainthread.c')

  @requires_pthreads
  @also_with_minimal_runtime
  def test_pthread_attr_getstack(self):
    if self.get_setting('MINIMAL_RUNTIME') and is_sanitizing(self.cflags):
      self.skipTest('MINIMAL_RUNTIME + threads + asan does not work')
    self.do_runf_out_file('pthread/test_pthread_attr_getstack.c')

  @requires_pthreads
  def test_pthread_guardsize(self):
    self.do_runf_out_file('pthread/test_pthread_guardsize.c')

  @requires_pthreads
  def test_pthread_guardsize_overflow(self):
    self.set_setting('STACK_OVERFLOW_CHECK', 1)
    expected = r'Aborted\(Stack overflow! Stack cookie has been overwritten at 0x[0-9a-f]+, expected hex dwords 0x89bacdfe and 0x02135467, but received 0xaaaaaaaa 0xaaaaaaaa\)'
    self.do_runf('pthread/test_pthread_guardsize_overflow.c', expected, regex=True, assert_returncode=NON_ZERO)

  @requires_pthreads
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26199')
  @flaky('flaky specifically in esm_integration suite. https://github.com/emscripten-core/emscripten/issues/25151')
  def test_pthread_abort(self):
    self.set_setting('PROXY_TO_PTHREAD')
    # Add the onAbort handler at runtime during preRun.  This means that onAbort
    # handler will only be present in the main thread (much like it would if it
    # was passed in by pre-populating the module object on prior to loading).
    self.add_pre_run("Module.onAbort = () => console.log('My custom onAbort called');")
    self.cflags += ['-sINCOMING_MODULE_JS_API=preRun,onAbort']
    self.do_runf_out_file('pthread/test_pthread_abort.c', assert_returncode=NON_ZERO)

  @requires_pthreads
  def test_pthread_abort_interrupt(self):
    expected = ['Aborted(). Build with -sASSERTIONS for more info', 'Aborted(native code called abort())']
    self.do_runf('pthread/test_pthread_abort_interrupt.c', expected, assert_returncode=NON_ZERO)

  @no_asan('ASan does not support custom memory allocators')
  @no_lsan('LSan does not support custom memory allocators')
  @requires_pthreads
  def test_pthread_emmalloc(self):
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    self.set_setting('ASSERTIONS', 2)
    self.set_setting('MALLOC', 'emmalloc')
    self.do_core_test('test_emmalloc.c', cflags=['-fno-builtin'])

  @requires_pthreads
  def test_pthread_stdout_after_main(self):
    # Verify that secondary threads can continue to write to stdout even
    # after the main thread returns.  We had a regression where stdio
    # streams were locked when the main thread returned.
    self.do_runf('pthread/test_pthread_stdout_after_main.c')

  @requires_pthreads
  def test_pthread_proxy_to_pthread(self):
    self.do_runf_out_file('pthread/test_pthread_proxy_to_pthread.c', cflags=['-sEXIT_RUNTIME', '-sPROXY_TO_PTHREAD'])

  @requires_pthreads
  @needs_dylink
  def test_pthread_tls_dylink(self):
    self.do_runf_out_file('pthread/test_pthread_tls_dylink.c', cflags=['-sMAIN_MODULE=2', '-Wno-experimental'])

  @requires_pthreads
  @also_with_minimal_runtime
  def test_pthread_tls(self):
    if self.get_setting('MINIMAL_RUNTIME') and is_sanitizing(self.cflags):
      self.skipTest('MINIMAL_RUNTIME + threads + asan does not work')
    self.do_runf('pthread/test_pthread_tls.c')

  @no_modularize_instance('uses global Module object')
  def test_pthread_run_script(self):
    if not self.is_optimizing() and ('-flto' in self.cflags or '-flto=thin' in self.cflags):
      self.skipTest('https://github.com/emscripten-core/emscripten/issues/25015')

    copy_asset('pthread/foo.js')
    self.do_runf('pthread/test_pthread_run_script.c')

    # Run the test again with PROXY_TO_PTHREAD
    self.require_pthreads()
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    self.do_runf('pthread/test_pthread_run_script.c')

  @requires_pthreads
  def test_pthread_mutex_robust(self):
    self.do_runf_out_file('pthread/test_pthread_mutex_robust.c')

  @requires_pthreads
  def test_pthread_wait32_notify(self):
    self.do_runf_out_file('atomic/test_wait32_notify.c')

  @requires_pthreads
  @no_wasm2js('https://github.com/WebAssembly/binaryen/issues/5991')
  def test_pthread_wait64_notify(self):
    self.do_runf_out_file('atomic/test_wait64_notify.c')

  @requires_pthreads
  def test_pthread_wait_async(self):
    self.set_setting('PROXY_TO_PTHREAD')
    self.do_runf_out_file('atomic/test_wait_async.c')

  # Include @requires_node_25 explictly here so that this test will be disabled
  # by EMTEST_SKIP_NODE_25.  Without this, the `requires_pthreads` and `requires_jspi` can
  # end with conflicting requirements because we often run with both v8 (which satisfies
  # the `requires_jspi` part have node 22 (which satisfies the `requires_pthreads` part).
  # FIXME: This should not be needed.
  @requires_node_25
  @requires_pthreads
  @with_asyncify_and_jspi
  @also_with_wasm_workers
  def test_pthread_wait_suspending(self):
    self.do_runf_out_file('atomic/test_wait_suspending.c')

  @requires_pthreads
  @also_with_minimal_runtime
  def test_pthread_run_on_main_thread(self):
    if self.get_setting('MINIMAL_RUNTIME') and is_sanitizing(self.cflags):
      self.skipTest('MINIMAL_RUNTIME + threads + asan does not work')
    self.do_runf_out_file('pthread/test_pthread_run_on_main_thread.c')

  @requires_pthreads
  def test_pthread_is_lock_free(self):
    self.do_runf('pthread/is_lock_free.c', 'done\n', cflags=['-pthread'])

  # These wasm_worker tests are also tested in pure wasm-worker mode in test_browser.py.
  # We test them here in pthread-mode and in pthread+wasm-worker mode.

  @requires_pthreads
  @also_with_wasm_workers
  def test_emscripten_lock_waitinf_acquire(self):
    self.do_runf('wasm_worker/lock_waitinf_acquire.c', 'done\n', cflags=['-pthread'])

  @requires_pthreads
  @also_with_wasm_workers
  def test_emscripten_lock_wait_acquire(self):
    self.do_runf('wasm_worker/lock_wait_acquire.c', 'done\n', cflags=['-pthread'])

  @requires_pthreads
  @also_with_wasm_workers
  @no_sanitize('https://github.com/emscripten-core/emscripten/issues/26502')
  def test_emscripten_lock_busyspin_waitinf(self):
    self.do_runf('wasm_worker/lock_busyspin_waitinf_acquire.c', 'done\n', cflags=['-pthread'])

  @requires_pthreads
  @also_with_wasm_workers
  def test_emscripten_semaphore_waitinf_acquire(self):
    self.do_runf('wasm_worker/semaphore_waitinf_acquire.c', 'done\n', cflags=['-pthread'])

  @requires_pthreads
  @also_with_wasm_workers
  def test_emscripten_semaphore_wait_acquire(self):
    self.do_runf('wasm_worker/semaphore_wait_acquire.c', 'done\n', cflags=['-pthread'])

  @requires_pthreads
  @also_with_wasm_workers
  def test_emscripten_semaphore_try_acquire(self):
    self.do_runf('wasm_worker/semaphore_try_acquire.c', 'done\n', cflags=['-pthread'])

  @requires_pthreads
  @also_with_wasm_workers
  def test_emscripten_condvar_waitinf(self):
    self.do_runf('wasm_worker/condvar_waitinf.c', 'done\n', cflags=['-pthread'])

  def test_tcgetattr(self):
    self.do_runf('termios/test_tcgetattr.c', 'done\n')

  def test_trickystring(self):
    self.do_core_test('test_trickystring.c')

  def test_statics(self):
    self.do_core_test('test_statics.cpp')

  def test_copyop(self):
    # clang generated code is vulnerable to this, as it uses
    # memcpy for assignments, with hardcoded numbers of bytes
    # (llvm-gcc copies items one by one).
    self.do_core_test('test_copyop.cpp')

  @parameterized({
    '': ([],),
    'bulkmem': (['-mbulk-memory'],),
  })
  def test_memcpy_zero_bytes(self, args):
    self.do_core_test('test_memcpy_zero_bytes.c', cflags=['-fno-builtin'] + args)

  def test_memcpy2(self):
    self.do_core_test('test_memcpy2.c')

  def test_memcpy3(self):
    self.do_core_test('test_memcpy3.c')

  @also_with_standalone_wasm()
  def test_memcpy_alignment(self):
    self.do_runf('test_memcpy_alignment.c', 'done\n')

  def test_memset_alignment(self):
    self.do_runf('test_memset_alignment.c', 'done\n')

  def test_memset(self):
    self.do_core_test('test_memset.c')

  def test_getopt(self):
    self.do_core_test('test_getopt.c', args=['-t', '12', '-n', 'foobar'])

  def test_getopt_long(self):
    self.do_core_test('test_getopt_long.c', args=['--file', 'foobar', '-b'])

  def test_memmove(self):
    self.do_core_test('test_memmove.c')

  def test_memmove2(self):
    self.do_core_test('test_memmove2.c')

  def test_memmove3(self):
    self.do_core_test('test_memmove3.c')

  def test_flexarray_struct(self):
    self.do_core_test('test_flexarray_struct.c')

  def test_bsearch(self):
    self.do_core_test('test_bsearch.c')

  def test_stack_overflow(self):
    self.set_setting('ASSERTIONS', 2)
    self.do_runf('core/stack_overflow.c', 'stack overflow', assert_returncode=NON_ZERO)

  def test_stackAlloc(self):
    self.do_core_test('test_stackAlloc.c')

  def test_legacy_stack_deps(self):
    # stackSave/stackRestore/stackAlloc are now normal JS library
    # functions that must be $-prefixed in `__deps` lists.  However,
    # to support legacy code we continue to support the non-prefixed
    # versions in `__deps` lists.
    create_file('lib.js', '''
    addToLibrary({
      foo__deps: ['stackSave', 'stackRestore'],
      foo: () => {
        var a = stackSave();
        stackRestore(a);
        return 0;
      }
    })''')
    create_file('main.c', '''
    int foo();

    int main() {
      return foo();
    }''')
    self.do_runf('main.c', cflags=['--js-library=lib.js'])

  def test_nested_structs(self):
    # Bloated memory; same layout as C/C++
    if self.is_wasm64():
      expected = '*16,0,4,8,8,12|20,0,4,4,8,12,12,16|32,0,24,0,4,4,8,12,12,16*\n*0,0,1,2,64,68,69,72*\n*2*'
    else:
      expected = '*16,0,4,8,8,12|20,0,4,4,8,12,12,16|24,0,20,0,4,4,8,12,12,16*\n*0,0,1,2,64,68,69,72*\n*2*'
    self.do_runf('core/test_nested_structs.cpp', expected)

  def prep_dlfcn_main(self, libs=None):
    if libs is None:
      libs = ['libside.so']
    self.clear_setting('SIDE_MODULE')
    # Link against the side modules but don't load them on startup.
    self.set_setting('NO_AUTOLOAD_DYLIBS')
    self.cflags += libs
    # This means we can use MAIN_MODULE=2 without needing to explicitly
    # specify EXPORTED_FUNCTIONS.
    self.set_setting('MAIN_MODULE', 2)

  def build_dlfcn_lib(self, filename, outfile='libside.so', cflags=None):
    self.clear_setting('MAIN_MODULE')
    self.set_setting('SIDE_MODULE')
    cmd = [compiler_for(filename), filename, '-o', outfile] + self.get_cflags()
    if cflags:
      cmd += cflags
    self.run_process(cmd)

  @needs_dylink
  @no_js_math('JS_MATH is not compatible with MAIN_MODULE=1')
  def test_dlfcn_missing(self):
    self.set_setting('MAIN_MODULE')
    self.set_setting('ASSERTIONS')
    src = r'''
      #include <dlfcn.h>
      #include <stdio.h>
      #include <assert.h>

      int main() {
        void* lib_handle = dlopen("libfoo.so", RTLD_NOW);
        assert(!lib_handle);
        printf("error: %s\n", dlerror());
        return 0;
      }
      '''

    if self.get_current_js_engine() == config.V8_ENGINE:
      expected = "error: could not load dynamic lib: libfoo.so\nError: Error reading file"
    else:
      expected = "error: could not load dynamic lib: libfoo.so\nError: ENOENT: no such file or directory"
    self.do_run(src, expected)

  @needs_dylink
  @parameterized({
    '': ([],),
    'pthreads': (['-pthread', '-sEXIT_RUNTIME', '-sPROXY_TO_PTHREAD', '-Wno-experimental'],),
  })
  def test_dlfcn_basic(self, args):
    if args:
      self.require_pthreads()
    self.cflags += args
    create_file('libside.cpp', '''
      #include <cstdio>

      class Foo {
      public:
        Foo() {
          puts("Constructing lib object.");
        }
      };

      Foo side_global;
      ''')
    self.build_dlfcn_lib('libside.cpp')

    self.prep_dlfcn_main()
    src = '''
      #include <cstdio>
      #include <dlfcn.h>

      class Bar {
      public:
        Bar() {
          puts("Constructing main object.");
        }
      };

      Bar global;

      int main() {
        dlopen("libside.so", RTLD_NOW);
        return 0;
      }
      '''
    self.do_run(src, 'Constructing main object.\nConstructing lib object.\n')

  @needs_dylink
  def test_dlfcn_i64(self):
    create_file('libside.c', '''
      #include <inttypes.h>

      int64_t foo(int x) {
        return (long long)x / (long long)1234;
      }
      ''')
    self.build_dlfcn_lib('libside.c')

    self.prep_dlfcn_main()
    src = r'''
      #include <inttypes.h>
      #include <stdio.h>
      #include <stdlib.h>
      #include <dlfcn.h>

      typedef int64_t (*int64func)(int);

      int main() {
        void *lib_handle = dlopen("libside.so", RTLD_NOW);
        if (!lib_handle) {
          puts(dlerror());
          abort();
        }
        printf("dll handle: %p\n", lib_handle);
        int64func x = (int64func)dlsym(lib_handle, "foo");
        printf("foo func handle: %p\n", x);
        if (!x) {
          printf("dlsym failed: %s\n", dlerror());
          return 1;
        }
        printf("|%lld|\n", x(81234567));
        return 0;
      }
      '''
    self.do_run(src, '|65830|')

  @needs_dylink
  def test_dlfcn_em_asm(self):
    create_file('libside.cpp', '''
      #include <emscripten.h>
      class Foo {
      public:
        Foo() {
          EM_ASM( out("Constructing lib object.") );
        }
      };
      Foo side_global;
      ''')
    self.build_dlfcn_lib('libside.cpp')

    self.prep_dlfcn_main()
    src = '''
      #include <emscripten.h>
      #include <dlfcn.h>
      class Bar {
      public:
        Bar() {
          EM_ASM( out("Constructing main object.") );
        }
      };
      Bar global;
      int main() {
        dlopen("libside.so", RTLD_NOW);
        EM_ASM( out("All done.") );
        return 0;
      }
      '''
    self.do_run(src, 'Constructing main object.\nConstructing lib object.\nAll done.\n')

  @needs_dylink
  def test_dlfcn_qsort(self):
    create_file('libside.c', '''
      int lib_cmp(const void* left, const void* right) {
        const int* a = (const int*) left;
        const int* b = (const int*) right;
        if(*a > *b) return 1;
        else if(*a == *b) return  0;
        else return -1;
      }

      typedef int (*CMP_TYPE)(const void*, const void*);

      CMP_TYPE get_cmp() {
        return lib_cmp;
      }
      ''')
    self.build_dlfcn_lib('libside.c')

    self.prep_dlfcn_main()
    src = '''
      #include <stdio.h>
      #include <stdlib.h>
      #include <dlfcn.h>

      typedef int (*CMP_TYPE)(const void*, const void*);

      int main_cmp(const void* left, const void* right) {
        const int* a = (const int*) left;
        const int* b = (const int*) right;
        if(*a < *b) return 1;
        else if(*a == *b) return  0;
        else return -1;
      }

      int main() {
        void* lib_handle;
        CMP_TYPE (*getter_ptr)();
        CMP_TYPE lib_cmp_ptr;
        int arr[5] = {4, 2, 5, 1, 3};

        qsort((void*)arr, 5, sizeof(int), main_cmp);
        printf("Sort with main comparison: ");
        for (int i = 0; i < 5; i++) {
          printf("%d ", arr[i]);
        }
        printf("\\n");

        lib_handle = dlopen("libside.so", RTLD_NOW);
        if (lib_handle == NULL) {
          printf("Could not load lib.\\n");
          return 1;
        }
        getter_ptr = (CMP_TYPE (*)()) dlsym(lib_handle, "get_cmp");
        if (getter_ptr == NULL) {
          printf("Could not find func.\\n");
          return 1;
        }
        lib_cmp_ptr = getter_ptr();
        qsort((void*)arr, 5, sizeof(int), lib_cmp_ptr);
        printf("Sort with lib comparison: ");
        for (int i = 0; i < 5; i++) {
          printf("%d ", arr[i]);
        }
        printf("\\n");

        return 0;
      }
      '''
    self.do_run(src, 'Sort with main comparison: 5 4 3 2 1 \nSort with lib comparison: 1 2 3 4 5 \n')

  @needs_dylink
  def test_dlfcn_data_and_fptr(self):
    create_file('libside.c', r'''
      #include <stdio.h>

      int theglobal = 42;

      extern void parent_func(); // a function that is defined in the parent

      int* lib_get_global_addr() {
        return &theglobal;
      }

      void lib_fptr() {
        printf("Second calling lib_fptr from main.\n");
        parent_func();
        // call it also through a pointer, to check indexing
        void (*p_f)();
        p_f = parent_func;
        p_f();
      }

      void (*func(int x, void(*fptr)()))() {
        printf("In func: %d\n", x);
        fptr();
        return lib_fptr;
      }
      ''')
    self.build_dlfcn_lib('libside.c')

    self.prep_dlfcn_main()
    src = r'''
      #include <stdio.h>
      #include <dlfcn.h>
      #include <emscripten.h>

      typedef void (*FUNCTYPE(int, void(*)()))();

      FUNCTYPE func;

      void EMSCRIPTEN_KEEPALIVE parent_func() {
        printf("parent_func called from child\n");
      }

      void main_fptr() {
        printf("First calling main_fptr from lib.\n");
      }

      int main() {
        void* lib_handle;
        FUNCTYPE* func_fptr;

        // Test basic lib loading.
        lib_handle = dlopen("libside.so", RTLD_NOW);
        if (lib_handle == NULL) {
          printf("Could not load lib.\n");
          return 1;
        }

        // Test looked up function.
        func_fptr = (FUNCTYPE*) dlsym(lib_handle, "func");
        // Load twice to test cache.
        func_fptr = (FUNCTYPE*) dlsym(lib_handle, "func");
        if (func_fptr == NULL) {
          printf("Could not find func.\n");
          return 1;
        }

        // Test passing function pointers across module bounds.
        void (*fptr)() = func_fptr(13, main_fptr);
        fptr();

        // Test global data.
        int* globaladdr = (int*) dlsym(lib_handle, "theglobal");
        if (globaladdr == NULL) {
          printf("Could not find global.\n");
          return 1;
        }

        printf("Var: %d\n", *globaladdr);

        return 0;
      }
      '''
    self.do_run(src, '''\
In func: 13
First calling main_fptr from lib.
Second calling lib_fptr from main.
parent_func called from child
parent_func called from child
Var: 42
''', force_c=True)

  @needs_dylink
  def test_dlfcn_varargs(self):
    # this test is not actually valid - it fails natively. the child should fail
    # to be loaded, not load and successfully see the parent print_ints func

    create_file('libside.c', r'''
      void print_ints(int n, ...);
      void func() {
        print_ints(2, 13, 42);
      }
      ''')
    self.build_dlfcn_lib('libside.c')

    self.prep_dlfcn_main()
    src = r'''
      #include <stdarg.h>
      #include <stdio.h>
      #include <dlfcn.h>
      #include <assert.h>

      void print_ints(int n, ...) {
        va_list args;
        va_start(args, n);
        for (int i = 0; i < n; i++) {
          printf("%d\n", va_arg(args, int));
        }
        va_end(args);
      }

      int main() {
        void* lib_handle;
        void (*fptr)();

        print_ints(2, 100, 200);

        lib_handle = dlopen("libside.so", RTLD_NOW);
        assert(lib_handle);
        fptr = (void (*)())dlsym(lib_handle, "func");
        fptr();

        return 0;
      }
      '''
    self.do_run(src, '100\n200\n13\n42\n', force_c=True)

  @needs_dylink
  @no_sanitize('contains ODR violation')
  @no_highmem('output is sensitive to absolute data layout')
  def test_dlfcn_alignment_and_zeroing(self):
    self.set_setting('INITIAL_MEMORY', '16mb')
    create_file('libside.c', r'''
      int prezero = 0;
      __attribute__((aligned(1024))) int superAligned = 12345;
      int postzero = 0;
      ''')
    self.build_dlfcn_lib('libside.c')
    for i in range(10):
      curr = '%d.so' % i
      shutil.copy('libside.so', curr)

    self.prep_dlfcn_main()
    self.set_setting('INITIAL_MEMORY', '128mb')
    create_file('src.c', r'''
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <dlfcn.h>
      #include <assert.h>
      #include <emscripten.h>

      int main() {
        printf("'prepare' memory with non-zero inited stuff\n");
        int num = 120 * 1024 * 1024; // total is 128; we'll use 5*5 = 25 at least, so allocate pretty much all of it
        void* mem = malloc(num);
        assert(mem);
        printf("setting this range to non-zero: %lu - %lu\n", (uintptr_t)mem, ((uintptr_t)mem) + num);
        memset(mem, 1, num);
        EM_ASM({
          var value = HEAP8[64*1024*1024];
          out('verify middle of memory is non-zero: ' + value);
          assert(value === 1);
        });
        free(mem);
        for (int i = 0; i < 10; i++) {
          char curr[] = "?.so";
          curr[0] = '0' + i;
          printf("loading %s\n", curr);
          void* lib_handle = dlopen(curr, RTLD_NOW);
          if (!lib_handle) {
            puts(dlerror());
            assert(0);
          }
          printf("getting superAligned\n");
          int* superAligned = (int*)dlsym(lib_handle, "superAligned");
          assert(superAligned);
          assert(((long)superAligned) % 1024 == 0); // alignment
          printf("checking value of superAligned, at %p\n", superAligned);
          assert(*superAligned == 12345); // value
          printf("getting prezero\n");
          int* prezero = (int*)dlsym(lib_handle, "prezero");
          assert(prezero);
          printf("checking value of prezero, at %p\n", prezero);
          assert(*prezero == 0);
          *prezero = 1;
          assert(*prezero != 0);
          printf("getting postzero\n");
          int* postzero = (int*)dlsym(lib_handle, "postzero");
          printf("checking value of postzero, at %p\n", postzero);
          assert(postzero);
          printf("checking value of postzero\n");
          assert(*postzero == 0);
          *postzero = 1;
          assert(*postzero != 0);
        }
        printf("done\n");
        return 0;
      }
      ''')
    self.do_runf('src.c', 'done\n')

  @needs_dylink
  @no_js_math('JS_MATH is not compatible with MAIN_MODULE=1')
  def test_dlfcn_self(self):
    self.set_setting('MAIN_MODULE')
    self.set_setting('EXPORT_ALL')

    self.do_core_test('test_dlfcn_self.c')

    # check that we only export relevant things.
    # disable this in WasmFS as it adds a bunch of additional exports for its
    # own purposes internally TODO: when we focus on code size, we'll likely
    # want to look at this
    if self.get_setting('WASMFS'):
      return

    # sanitizers add a lot of extra symbols
    if is_sanitizing(self.cflags):
      return

    def get_data_exports(wasm):
      wat = self.get_wasm_text(wasm)
      lines = wat.splitlines()
      exports = [l for l in lines if l.strip().startswith('(export ')]
      data_exports = [l for l in exports if '(global ' in l]
      data_exports = [d.split()[1].strip('"') for d in data_exports]
      return data_exports

    data_exports = get_data_exports('test_dlfcn_self.wasm')
    # Certain exports are removed by wasm-emscripten-finalize, but this
    # tool is not run in all configurations, so ignore these exports.
    data_exports = [d for d in data_exports if d not in {'__start_em_asm', '__stop_em_asm'}]
    data_exports = '\n'.join(sorted(data_exports)) + '\n'
    self.assertFileContents(test_file('core/test_dlfcn_self.exports'), data_exports)

  @needs_dylink
  def test_dlfcn_unique_sig(self):
    create_file('libside.c', r'''
      #include <stdio.h>

      int myfunc(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m) {
        return 13;
      }
      ''')
    self.build_dlfcn_lib('libside.c')

    self.prep_dlfcn_main()
    create_file('main.c', r'''
      #include <assert.h>
      #include <stdio.h>
      #include <dlfcn.h>

      typedef int (*FUNCTYPE)(int, int, int, int, int, int, int, int, int, int, int, int, int);

      int main() {
        void *lib_handle;
        FUNCTYPE func_ptr;

        lib_handle = dlopen("libside.so", RTLD_NOW);
        assert(lib_handle != NULL);

        func_ptr = (FUNCTYPE)dlsym(lib_handle, "myfunc");
        assert(func_ptr != NULL);
        assert(func_ptr(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) == 13);

        puts("done");
        return 0;
      }
      ''')
    self.do_runf('main.c', 'done\n')

  @needs_dylink
  def test_dlfcn_info(self):
    create_file('libside.c', r'''
      #include <stdio.h>

      int myfunc(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m) {
        return 13;
      }
      ''')
    self.build_dlfcn_lib('libside.c')

    self.prep_dlfcn_main()
    create_file('main.c', '''
      #include <assert.h>
      #include <stdio.h>
      #include <string.h>
      #include <dlfcn.h>

      typedef int (*FUNCTYPE)(int, int, int, int, int, int, int, int, int, int, int, int, int);

      int main() {
        void *lib_handle;
        FUNCTYPE func_ptr;

        lib_handle = dlopen("libside.so", RTLD_NOW);
        assert(lib_handle != NULL);

        func_ptr = (FUNCTYPE)dlsym(lib_handle, "myfunc");
        assert(func_ptr != NULL);
        assert(func_ptr(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) == 13);

        /* Verify that we don't corrupt func_ptr when calling dladdr.  */
        Dl_info info;
        memset(&info, 0, sizeof(info));
        int rtn = dladdr(func_ptr, &info);
        assert(rtn == 0);

        assert(func_ptr != NULL);
        assert(func_ptr(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) == 13);

        puts("done");
        return 0;
      }
      ''')
    self.do_runf('main.c', 'done\n')

  @needs_dylink
  def test_dlfcn_stacks(self):
    create_file('libside.c', r'''
      #include <assert.h>
      #include <stdio.h>
      #include <string.h>

      int myfunc(const char *input) {
        char bigstack[1024] = { 0 };

        // make sure we didn't just trample the stack!
        assert(!strcmp(input, "foobar"));

        snprintf(bigstack, sizeof(bigstack), "%s", input);
        return strlen(bigstack);
      }
      ''')
    self.build_dlfcn_lib('libside.c')

    self.prep_dlfcn_main()
    create_file('main.c', '''
      #include <assert.h>
      #include <stdio.h>
      #include <dlfcn.h>
      #include <string.h>

      typedef int (*FUNCTYPE)(const char *);

      int main() {
        void *lib_handle;
        FUNCTYPE func_ptr;
        char str[128];

        snprintf(str, sizeof(str), "foobar");

        // HACK: Use strcmp in the main executable so that it doesn't get optimized out and the dynamic library
        //       is able to use it.
        assert(!strcmp(str, "foobar"));

        lib_handle = dlopen("libside.so", RTLD_NOW);
        assert(lib_handle != NULL);

        func_ptr = (FUNCTYPE)dlsym(lib_handle, "myfunc");
        assert(func_ptr != NULL);
        assert(func_ptr(str) == 6);

        puts("done");
        return 0;
      }
      ''')
    self.do_runf('main.c', 'done')

  @needs_dylink
  def test_dlfcn_funcs(self):
    create_file('libside.c', r'''
      #include <assert.h>
      #include <stdio.h>
      #include <string.h>

      typedef void (*voidfunc)(void);
      typedef void (*intfunc)(int);

      void callvoid(voidfunc f) { f(); }
      void callint(intfunc f, int x) { f(x); }

      void void_0() { printf("void 0\n"); }
      void void_1() { printf("void 1\n"); }
      voidfunc getvoid(int i) {
        switch(i) {
          case 0: return void_0;
          case 1: return void_1;
          default: return NULL;
        }
      }

      void int_0(int x) { printf("int 0 %d\n", x); }
      void int_1(int x) { printf("int 1 %d\n", x); }
      intfunc getint(int i) {
        switch(i) {
          case 0: return int_0;
          case 1: return int_1;
          default: return NULL;
        }
      }
      ''')
    self.build_dlfcn_lib('libside.c')

    self.prep_dlfcn_main()
    create_file('main.c', r'''
      #include <assert.h>
      #include <stdio.h>
      #include <dlfcn.h>

      typedef void (*voidfunc)();
      typedef void (*intfunc)(int);

      typedef void (*voidcaller)(voidfunc);
      typedef void (*intcaller)(intfunc, int);

      typedef voidfunc (*voidgetter)(int);
      typedef intfunc (*intgetter)(int);

      void void_main() { printf("void_main.\n"); }
      void int_main(int x) { printf("int_main %d\n", x); }

      int main() {
        printf("go\n");
        void *lib_handle;
        lib_handle = dlopen("libside.so", RTLD_NOW);
        assert(lib_handle != NULL);

        voidcaller callvoid = (voidcaller)dlsym(lib_handle, "callvoid");
        assert(callvoid != NULL);
        callvoid(void_main);

        intcaller callint = (intcaller)dlsym(lib_handle, "callint");
        assert(callint != NULL);
        callint(int_main, 201);

        voidgetter getvoid = (voidgetter)dlsym(lib_handle, "getvoid");
        assert(getvoid != NULL);
        callvoid(getvoid(0));
        callvoid(getvoid(1));

        intgetter getint = (intgetter)dlsym(lib_handle, "getint");
        assert(getint != NULL);
        callint(getint(0), 54);
        callint(getint(1), 9000);

        assert(getint(1000) == NULL);

        puts("ok");
        return 0;
      }
      ''')
    self.do_runf('main.c', '''go
void_main.
int_main 201
void 0
void 1
int 0 54
int 1 9000
ok
''')

  @needs_dylink
  def test_dlfcn_longjmp(self):
    create_file('libside.c', r'''
      #include <setjmp.h>
      #include <stdio.h>

      void jumpy(jmp_buf buf) {
        static int i = 0;
        i++;
        if (i == 10) longjmp(buf, i);
        printf("pre %d\n", i);
      }
      ''')
    self.build_dlfcn_lib('libside.c')

    self.prep_dlfcn_main()
    create_file('main.c', r'''
      #include <assert.h>
      #include <stdio.h>
      #include <dlfcn.h>
      #include <setjmp.h>

      typedef void (*jumpfunc)(jmp_buf);

      int main() {
        printf("go!\n");

        void *lib_handle;
        lib_handle = dlopen("libside.so", RTLD_NOW);
        assert(lib_handle != NULL);

        jumpfunc jumpy = (jumpfunc)dlsym(lib_handle, "jumpy");
        assert(jumpy);

        jmp_buf buf;
        int jmpval = setjmp(buf);
        if (jmpval == 0) {
          while (1) jumpy(buf);
        } else {
          printf("out!\n");
        }

        return 0;
      }
      ''')
    self.do_runf('main.c', '''go!
pre 1
pre 2
pre 3
pre 4
pre 5
pre 6
pre 7
pre 8
pre 9
out!
''')

  @needs_dylink
  @with_all_eh_sjlj
  def test_dlfcn_exceptions(self):
    create_file('libside.cpp', r'''
      extern "C" {
      int ok() {
        return 65;
      }
      int fail() {
        throw 123;
      }
      }
      ''')
    self.build_dlfcn_lib('libside.cpp')

    self.prep_dlfcn_main()
    create_file('main.cpp', r'''
      #include <assert.h>
      #include <stdio.h>
      #include <dlfcn.h>

      typedef int (*intfunc)();

      int main() {
        printf("go!\n");

        void *lib_handle;
        lib_handle = dlopen("libside.so", RTLD_NOW);
        assert(lib_handle != NULL);

        intfunc okk = (intfunc)dlsym(lib_handle, "ok");
        intfunc faill = (intfunc)dlsym(lib_handle, "fail");
        assert(okk && faill);

        try {
          printf("ok: %d\n", okk());
        } catch(...) {
          printf("wha\n");
          assert(false);
        }

        try {
          printf("fail: %d\n", faill());
        } catch(int x) {
          printf("caught int: %d\n", x);
        }

        try {
          try {
            printf("fail: %d\n", faill());
          } catch(double x) {
            printf("caught double: %f\n", x);
            assert(false);
          }
        } catch(int x) {
          printf("caught outer int: %d\n", x);
        }

        return 0;
      }
      ''')
    self.do_runf('main.cpp', '''\
go!
ok: 65
caught int: 123
caught outer int: 123
''')

  @needs_dylink
  @no_js_math('JS_MATH is not compatible with MAIN_MODULE=1')
  def test_dlfcn_handle_alloc(self):
    # verify that dlopen does not allocate already used handles
    create_file('a.cpp', r'''
      #include <stdio.h>

      static class A {
      public:
        A() {
          puts("a: loaded");
        }
      } _;
    ''')

    create_file('b.cpp', r'''
      #include <stdio.h>

      static class B {
      public:
        B() {
          puts("b: loaded");
        }
      } _;
    ''')

    self.build_dlfcn_lib('a.cpp', outfile='liba.so')
    self.build_dlfcn_lib('b.cpp', outfile='libb.so')

    self.set_setting('MAIN_MODULE')
    self.clear_setting('SIDE_MODULE')

    create_file('main.c', r'''
      #include <dlfcn.h>
      #include <assert.h>
      #include <stddef.h>

      int main() {
        void *liba, *libb, *liba2, *libb2;
        int err;

        liba = dlopen("liba.so", RTLD_NOW);
        assert(liba != NULL);
        libb = dlopen("libb.so", RTLD_NOW);
        assert(libb != NULL);

        // Test that opening libb a second times gives the same handle
        libb2 = dlopen("libb.so", RTLD_NOW);
        assert(libb == libb2);

        err = dlclose(liba);
        assert(!err);

        liba2 = dlopen("liba.so", RTLD_NOW);
        assert(liba2 != libb);

        return 0;
      }
      ''')
    self.do_runf('main.c', 'a: loaded\nb: loaded\n')

  @needs_dylink
  @needs_non_trapping_float_to_int
  def test_dlfcn_feature_in_lib(self):
    self.cflags.append('-mnontrapping-fptoint')

    create_file('libside.c', r'''
        int magic(float x) {
          return __builtin_wasm_trunc_saturate_s_i32_f32(x);
        }
      ''')
    self.build_dlfcn_lib('libside.c')

    self.prep_dlfcn_main()
    src = r'''
      #include <dlfcn.h>
      #include <stdio.h>
      #include <stdlib.h>

      typedef int (*fi)(float);

      int main() {
        void *lib_handle = dlopen("libside.so", RTLD_NOW);
        if (!lib_handle) {
          puts(dlerror());
          abort();
        }
        fi x = (fi)dlsym(lib_handle, "magic");
        if (!x) {
          puts(dlerror());
          abort();
        }
        printf("float: %d.\n", x(42.99));
        return 0;
      }
      '''
    self.do_run(src, 'float: 42.\n')

  @needs_dylink
  @with_asyncify_and_jspi
  def test_dlfcn_asyncify(self):
    create_file('libside.c', r'''
      #include <stdio.h>
      #include <emscripten/emscripten.h>

      int side_module_run() {
        printf("before sleep\n");
        emscripten_sleep(1000);
        printf("after sleep\n");
        return 42;
      }
      ''')
    self.build_dlfcn_lib('libside.c')

    self.prep_dlfcn_main()
    src = r'''
      #include <stdio.h>
      #include <dlfcn.h>

      typedef int (*func_t)();

      int main(int argc, char **argv) {
        void *_dlHandle = dlopen("libside.so", RTLD_NOW | RTLD_LOCAL);
        func_t my_func = (func_t)dlsym(_dlHandle, "side_module_run");
        printf("%d\n", my_func());
        return 0;
      }
      '''
    self.do_run(src, 'before sleep\nafter sleep\n42\n')

  @requires_jspi
  @needs_dylink
  @no_asan('https://github.com/emscripten-core/emscripten/issues/24589')
  def test_dlfcn_jspi(self):
    self.run_process([EMCC, '-o', 'side.so', test_file('core/test_dlfcn_jspi_side.c'), '-sSIDE_MODULE'] + self.get_cflags())
    self.do_core_test('test_dlfcn_jspi.c', cflags=['side.so', '-sMAIN_MODULE=2'])

  @needs_dylink
  def test_dlfcn_rtld_local(self):
    # Create two shared libraries that both depend on a third.
    #  liba.so -> libsub.so
    #  libb.so -> libsub.so
    create_file('liba.c', r'''
      #include <stdio.h>

      void func_sub();

      void func_a() {
        printf("func_a\n");
        // Call a function from a dependent DSO. This symbol should
        // be available here even though liba itself is loaded with RTLD_LOCAL.
        func_sub();
      }
      ''')

    create_file('libb.c', r'''
      #include <stdio.h>

      void func_sub();

      void func_b() {
        printf("func_b\n");
        // Call a function from a dependent DSO. This symbol should
        // be available here even though liba itself is loaded with RTLD_LOCAL.
        func_sub();
      }
    ''')

    create_file('libsub.c', r'''
      #include <stdio.h>

      void func_sub() {
        printf("func_sub\n");
      }
    ''')

    self.build_dlfcn_lib('libsub.c', outfile='libsub.so')
    self.build_dlfcn_lib('libb.c', outfile='libb.so', cflags=['libsub.so'])
    self.build_dlfcn_lib('liba.c', outfile='liba.so', cflags=['libsub.so'])

    self.prep_dlfcn_main(['liba.so', 'libb.so', '-L.'])
    create_file('main.c', r'''
      #include <assert.h>
      #include <dlfcn.h>
      #include <stdio.h>

      int main() {
        void* handle;
        void (*f)();

        printf("main\n");
        // Call a function from libb
        handle = dlopen("liba.so", RTLD_NOW|RTLD_LOCAL);
        assert(handle);

        f = dlsym(handle, "func_a");
        assert(f);
        f();

        // Same for libb
        handle = dlopen("libb.so", RTLD_NOW|RTLD_LOCAL);
        assert(handle);

        f = dlsym(handle, "func_b");
        assert(f);
        f();

        // Verify that symbols from all three libraries are not globally
        // visible.
        f = dlsym(RTLD_DEFAULT, "func_a");
        assert(f == NULL);
        f = dlsym(RTLD_DEFAULT, "func_b");
        assert(f == NULL);
        f = dlsym(RTLD_DEFAULT, "func_sub");
        assert(f == NULL);

        printf("done\n");
        return 0;
      }
      ''')

    self.do_runf('main.c', 'main\nfunc_a\nfunc_sub\nfunc_b\nfunc_sub\ndone\n')

  @needs_dylink
  @no_modularize_instance('uses file packager')
  def test_dlfcn_preload(self):
    # Create chain of dependencies and load the first library with preload plugin.
    # main -> libb.so -> liba.so
    create_file('liba.c', r'''
      #include <stdio.h>
      int liba_fun() {
        return 23;
      }
    ''')
    self.build_dlfcn_lib('liba.c', outfile='liba.so')

    create_file('libb.c', r'''
      #include <stdio.h>
      int liba_fun();

      int libb_fun() {
        return liba_fun()*2;
      }
    ''')
    self.build_dlfcn_lib('libb.c', outfile='libb.so', cflags=['liba.so'])

    self.prep_dlfcn_main(['--preload-file', 'libb.so', '--use-preload-plugins', '-L.', '-sAUTOLOAD_DYLIBS=0', 'libb.so'])
    create_file('main.c', r'''
      #include <assert.h>
      #include <dlfcn.h>
      #include <stdio.h>
      #include <sys/stat.h>

      int main() {
        // Check the file exists in the VFS
        struct stat statbuf;
        assert(stat("/libb.so", &statbuf) == 0);
        void *lib_handle = dlopen("/libb.so", RTLD_LOCAL | RTLD_NOW);
        assert(lib_handle);
        typedef int (*intfunc)();
        intfunc x = (intfunc)dlsym(lib_handle, "libb_fun");
        assert(x);
        assert(x() == 46);
        printf("done\n");
        return 0;

      }
    ''')
    self.do_runf('main.c', 'done\n')

  def dylink_test(self, main, side, expected=None, header=None, force_c=False,
                  main_module=2, **kwargs):
    # Same as dylink_testf but take source code in string form
    if not isinstance(side, list):
      side_file = 'libside.cpp' if not force_c else 'libside.c'
      create_file(side_file, side)
      side = side_file
    if not isinstance(main, list):
      main_file = 'main.cpp' if not force_c else 'main.c'
      create_file(main_file, main)
      main = main_file
    if header:
      create_file('header.h', header)

    return self.dylink_testf(main, side, expected, main_module=main_module, **kwargs)

  def dylink_testf(self, main, side=None, expected=None, force_c=False, main_cflags=None,
                   main_module=2,
                   so_dir='',
                   so_name='libside.so',
                   **kwargs):
    main_cflags = main_cflags or []
    if getattr(self, 'dylink_reversed', False):
      # Test the reverse case.  There we flip the role of the side module and main module.
      # - We add --no-entry since the side module doesn't have a `main`
      side, main = main, side
    self.maybe_closure()
    # Same as dylink_test but takes source code as filenames on disc.
    old_args = self.cflags.copy()
    if not expected:
      outfile = utils.replace_suffix(main, '.out')
      expected = read_file(outfile)
    if not side:
      side, ext = os.path.splitext(main)
      side += '_side' + ext

    # side settings
    old_settings = dict(self.settings_mods)
    self.clear_setting('MODULARIZE')
    self.clear_setting('MAIN_MODULE')
    self.clear_setting('SIDE_MODULE')
    so_file = os.path.join(so_dir, so_name)

    # Using -shared + -sFAKE_DYLIBS should be the same as `-sSIDE_MODULE`
    flags = ['-sSIDE_MODULE']
    if isinstance(side, list):
      # side is just a library
      self.run_process([EMCC] + side + self.get_cflags() + flags + ['-o', so_file])
    else:
      out_file = self.build(side, output_suffix='.so', cflags=flags)
      shutil.move(out_file, so_file)

    shutil.move(so_file, so_file + '.orig')

    # Verify that building with -sSIDE_MODULE is essentially the same as building with `-shared -fPIC`
    flags = ['-shared', '-fPIC']
    if isinstance(side, list):
      # side is just a library
      self.run_process([EMCC] + side + self.get_cflags() + flags + ['-o', so_file])
    else:
      out_file = self.build(side, output_suffix='.so', cflags=flags)
      shutil.move(out_file, so_file)

    self.assertEqual(read_binary(so_file), read_binary(so_file + '.orig'))
    os.remove(so_file + '.orig')

    # main settings
    self.settings_mods = old_settings
    self.set_setting('MAIN_MODULE', main_module)
    self.cflags += main_cflags
    self.cflags.append(so_file)

    if force_c:
      self.cflags.append('-nostdlib++')

    if isinstance(main, list):
      # main is just a library
      outfile = self.output_name('main')
      delete_file(outfile)
      self.run_process([EMCC] + main + self.get_cflags() + ['-o', outfile])
      self.do_run(outfile, expected, no_build=True, **kwargs)
    else:
      self.do_runf(main, expected, force_c=force_c, **kwargs)

    self.cflags = old_args

  def do_basic_dylink_test(self, **kwargs):
    self.dylink_test(r'''
      #include <stdio.h>
      #include "header.h"

      int main() {
        printf("other says %d.\n", sidey());
        return 0;
      }
    ''', '''
      #include "header.h"

      int sidey() {
        return 11;
      }
    ''', 'other says 11.', 'int sidey();', force_c=True, **kwargs)

  @needs_dylink
  @crossplatform
  def test_dylink_basics(self):
    self.do_basic_dylink_test()
    self.verify_in_strict_mode(self.output_name('main'))

  @with_dylink_reversed
  def test_dylink_basics_no_modify(self):
    if self.is_optimizing():
      self.skipTest('ERROR_ON_WASM_CHANGES_AFTER_LINK is not applicable when optimizing')
    if self.get_setting('SAFE_HEAP'):
      self.skipTest('ERROR_ON_WASM_CHANGES_AFTER_LINK is not applicable when using SAFE_HEAP')
    if self.get_setting('MEMORY64') == 2:
      self.skipTest('MEMORY64=2 always requires module re-writing')
    self.set_setting('ERROR_ON_WASM_CHANGES_AFTER_LINK')
    self.do_basic_dylink_test()

  @with_dylink_reversed
  @no_modularize_instance('DECLARE_ASM_MODULE_EXPORTS=0 is not compatible with MODULARIZE')
  def test_dylink_no_export(self):
    self.set_setting('NO_DECLARE_ASM_MODULE_EXPORTS')
    self.do_basic_dylink_test()

  @with_dylink_reversed
  def test_dylink_memory_growth(self):
    self.set_setting('ALLOW_MEMORY_GROWTH')
    self.do_basic_dylink_test()

  @with_dylink_reversed
  @no_asan('SAFE_HEAP cannot be used with ASan')
  def test_dylink_safe_heap(self):
    self.set_setting('SAFE_HEAP')
    self.do_basic_dylink_test()

  @with_dylink_reversed
  def test_dylink_locate_file(self):
    so_dir = 'so_dir'
    so_name = 'libside.so'
    os.mkdir(so_dir)
    create_file('pre.js', '''
    Module['locateFile'] = (f) => {
      if (f === '%s') {
        return '%s/' + f;
      } else {
        return f;
      }
    };
    ''' % (so_name, so_dir))
    self.do_basic_dylink_test(so_dir=so_dir, so_name=so_name, main_cflags=['--pre-js', 'pre.js', '-sINCOMING_MODULE_JS_API=locateFile'])

  @with_dylink_reversed
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26202')
  def test_dylink_function_pointer_equality(self):
    self.dylink_test(r'''
      #include <assert.h>
      #include <stdio.h>
      #include "header.h"

      int main() {
        void* puts_side = get_address();
        printf("main module address %p.\n", &puts);
        printf("side module address %p.\n", puts_side);
        assert(&puts == puts_side);
        printf("done\n");
        return 0;
      }
    ''', '''
      #include <stdio.h>
      #include "header.h"

      void* get_address() {
        return (void*)&puts;
      }
    ''', 'done\n', header='void* get_address();', force_c=True)

  @with_dylink_reversed
  def test_dylink_floats(self):
    self.dylink_test(r'''
      #include <stdio.h>
      extern float sidey();
      int main() {
        printf("other says %.2f.\n", sidey()+1);
        return 0;
      }
    ''', '''
      float sidey() { return 11.5; }
    ''', 'other says 12.50', force_c=True)

  @with_dylink_reversed
  def test_dylink_printf(self):
    self.dylink_test(r'''
      #include <stdio.h>
     void sidey();
      int main() {
        printf("hello from main\n");
        sidey();
        return 0;
      }
    ''', r'''
      #include <stdio.h>
      void sidey() {
        printf("hello from side\n");
      }
    ''', 'hello from main\nhello from side\n', force_c=True)

  # Verify that a function pointer can be passed back and forth and invoked
  # on both sides.
  @with_dylink_reversed
  def test_dylink_funcpointer(self):
    self.dylink_test(
      main=r'''
      #include <stdio.h>
      #include <assert.h>
      #include "header.h"
      intfunc sidey(intfunc f);
      void a(int arg) { printf("hello from funcptr: %d\n", arg); }
      int main() {
        intfunc b = sidey(a);
        assert(a == b);
        b(0);
        return 0;
      }
      ''',
      side='''
      #include "header.h"
      intfunc sidey(intfunc f) { f(1); return f; }
      ''',
      expected='hello from funcptr: 1\nhello from funcptr: 0\n',
      header='typedef void (*intfunc)(int );', force_c=True)

  @with_dylink_reversed
  # test dynamic linking of a module with multiple function pointers, stored
  # statically
  def test_dylink_static_funcpointers(self):
    self.dylink_test(
      main=r'''
      #include <stdio.h>
      #include "header.h"
      void areturn0() { printf("hello 0\n"); }
      void areturn1() { printf("hello 1\n"); }
      void areturn2() { printf("hello 2\n"); }
      voidfunc func_ptrs[3] = { areturn0, areturn1, areturn2 };
      int main(int argc, char **argv) {
        sidey(func_ptrs[0]);
        sidey(func_ptrs[1]);
        sidey(func_ptrs[2]);
        return 0;
      }
      ''',
      side='''
      #include "header.h"
      void sidey(voidfunc f) { f(); }
      ''',
      expected='hello 0\nhello 1\nhello 2\n',
      header='typedef void (*voidfunc)(); void sidey(voidfunc f);', force_c=True)

  @with_dylink_reversed
  def test_dylink_funcpointers_wrapper(self):
    self.dylink_test(
      main=r'''\
      #include <stdio.h>
      #include "header.h"
      int main(int argc, char **argv) {
        charfunc f1 = emscripten_run_script;
        f1("console.log('one')");
        charfunc f2 = get();
        f2("console.log('two')");
        return 0;
      }
      ''',
      side='''\
      #include "header.h"
      charfunc get() {
        return emscripten_run_script;
      }
      ''',
      expected='one\ntwo\n',
      header='''\
      #include <emscripten.h>
      typedef void (*charfunc)(const char*);
      extern charfunc get();
      ''', force_c=True)

  @with_dylink_reversed
  def test_dylink_static_funcpointer_float(self):
    self.dylink_test(
      main=r'''\
      #include <stdio.h>
      #include "header.h"
      int sidey(floatfunc f);
      float func1(float f) { printf("hello 1: %f\n", f); return 0; }
      floatfunc f1 = &func1;
      int main(int argc, char **argv) {
        printf("got: %d\n", sidey(f1));
        f1(12.34);
        return 0;
      }
      ''',
      side='''\
      #include "header.h"
      int sidey(floatfunc f) { f(56.78); return 1; }
      ''',
      expected='hello 1: 56.779999\ngot: 1\nhello 1: 12.340000\n',
      header='typedef float (*floatfunc)(float);', force_c=True)

  @with_dylink_reversed
  def test_dylink_global_init(self):
    self.dylink_test(r'''
      #include <stdio.h>
      struct Class {
        Class() { printf("a new Class\n"); }
      };
      static Class c;
      int main() {
        return 0;
      }
    ''', r'''
      void nothing() {}
    ''', 'a new Class\n')

  @with_dylink_reversed
  def test_dylink_global_inits(self):
    def test():
      self.dylink_test(header=r'''
        #include <stdio.h>
        struct Class {
          Class(const char *name) { printf("new %s\n", name); }
        };
      ''', main=r'''
        #include "header.h"
        static Class c("main");
        int main() {
          return 0;
        }
      ''', side=r'''
        #include "header.h"
        static Class c("side");
      ''', expected=['new main\nnew side\n', 'new side\nnew main\n'])
    test()

    print('check warnings')
    self.set_setting('ASSERTIONS', 2)
    test()
    # TODO: this in wasm
    # full = self.run_js('src.js')
    # self.assertNotContained('already exists', full)

  @with_dylink_reversed
  def test_dylink_i64(self):
    self.dylink_test(r'''
      #include <stdio.h>
      #include <stdint.h>
      extern int64_t sidey();
      int main() {
        printf("other says %lld.\n", sidey());
        return 0;
      }
    ''', '''
      #include <stdint.h>
      int64_t sidey() {
        return 42;
      }
    ''', 'other says 42.', force_c=True)

  @with_dylink_reversed
  @all_engines
  def test_dylink_i64_b(self):
    self.dylink_test(r'''
      #include <stdio.h>
      #include <stdint.h>
      extern int64_t sidey();
      int64_t testAdd(int64_t a) {
        return a + 1;
      }
      int64_t testAddB(int a) {
        return a + 1;
      }
      typedef int64_t (*testAddHandler)(int64_t);
      testAddHandler h = &testAdd;
      typedef int64_t (*testAddBHandler)(int);
      testAddBHandler hb = &testAddB;
      int main() {
        printf("other says %lld.\n", sidey());
        int64_t r = h(42);
        printf("my fp says: %lld.\n", r);
        int64_t rb = hb(42);
        printf("my second fp says: %lld.\n", r);
      }
    ''', '''
      #include <stdint.h>
      int64_t sidey() {
        volatile int64_t x = 0x12345678abcdef12LL;
        x += x % 17;
        x = 18 - x;
        return x;
      }
    ''', 'other says -1311768467750121224.\nmy fp says: 43.\nmy second fp says: 43.', force_c=True)

  @with_dylink_reversed
  @also_without_bigint
  def test_dylink_i64_c(self):
    self.dylink_test(r'''
      #include <stdio.h>
      #include <inttypes.h>
      #include "header.h"

      typedef int32_t (*fp_type_32)(int32_t, int32_t, int32_t);
      typedef int64_t (*fp_type_64)(int32_t, int32_t, int32_t);

      int32_t internal_function_ret_32(int32_t i, int32_t j, int32_t k) {
        return 32;
      }
      int64_t internal_function_ret_64(int32_t i, int32_t j, int32_t k) {
        return 64;
      }

      int main() {
        fp_type_32 fp32_internal = &internal_function_ret_32;
        fp_type_32 fp32_external = &function_ret_32;
        fp_type_64 fp64_external = &function_ret_64;
        fp_type_64 fp64_internal = &internal_function_ret_64;
        int32_t ires32 = fp32_internal(0,0,0);
        printf("res32 - internal %d\n", ires32);
        int32_t eres32 = fp32_external(0,0,0);
        printf("res32 - external %d\n", eres32);

        int64_t ires64 = fp64_internal(0,0,0);
        printf("res64 - internal %" PRId64 "\n", ires64);
        int64_t eres64 = fp64_external(0,0,0);
        printf("res64 - external %" PRId64 "\n", eres64);
        return 0;
      }
    ''', '''\
      #include "header.h"
      int32_t function_ret_32(int32_t i, int32_t j, int32_t k) {
        return 32;
      }
      int64_t function_ret_64(int32_t i, int32_t j, int32_t k) {
        return 64;
      }
    ''', '''\
res32 - internal 32
res32 - external 32
res64 - internal 64
res64 - external 64\n''', header='''\
      #include <emscripten.h>
      #include <stdint.h>
      EMSCRIPTEN_KEEPALIVE int32_t function_ret_32(int32_t i, int32_t j, int32_t k);
      EMSCRIPTEN_KEEPALIVE int64_t function_ret_64(int32_t i, int32_t j, int32_t k);
    ''', force_c=True)

  @also_without_bigint
  @parameterized({
    '': (False,),
    'rtld_local': (True,),
  })
  @needs_dylink
  def test_dylink_i64_invoke(self, rtld_local):
    if rtld_local:
      self.set_setting('NO_AUTOLOAD_DYLIBS')
      self.cflags.append('-DUSE_DLOPEN')
    self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
    self.dylink_test(r'''\
    #include <assert.h>
    #include <stdio.h>
    #include <stdint.h>

    #if USE_DLOPEN
    #include <dlfcn.h>
    typedef int64_t (*sidey_t)(int64_t arg);
    #else
    extern "C" int64_t sidey(int64_t arg);
    #endif

    int main(int argc, char *argv[]) {
        int64_t temp = 42;
    #if USE_DLOPEN
        void* lib = dlopen("libside.so", RTLD_LAZY);
        assert(lib);
        sidey_t sidey = (sidey_t)dlsym(lib, "sidey");
        assert(sidey);
    #endif

        printf("got %lld\n", sidey(temp));
        printf("got %lld\n", sidey(0));
        return 0;
    }''', r'''\
    #include <stdint.h>
    #include <stdio.h>
    #include <emscripten.h>

    extern "C" {

    EMSCRIPTEN_KEEPALIVE int64_t do_call(int64_t arg) {
        if (arg == 0) {
            throw 0;
        }
        return 2 * arg;
    }
    int64_t sidey(int64_t arg) {
        try {
            return do_call(arg);
        } catch(...) {
            return 0;
        }
    }
    }''', 'got 84\ngot 0')

  @with_dylink_reversed
  def test_dylink_class(self):
    self.dylink_test(header=r'''
      #include <stdio.h>
      struct Class {
        Class(const char *name);
      };
    ''', main=r'''
      #include "header.h"
      int main() {
        Class c("main");
        return 0;
      }
    ''', side=r'''
      #include "header.h"
      Class::Class(const char *name) { printf("new %s\n", name); }
    ''', expected=['new main\n'])

  @with_dylink_reversed
  def test_dylink_global_var(self):
    self.dylink_test(main=r'''
      #include <stdio.h>
      extern int foo;
      int main() {
        printf("extern is %d.\n", foo);
        return 0;
      }
    ''', side=r'''
      int foo = 123;
    ''', expected=['extern is 123.\n'], force_c=True)

  @needs_dylink
  def test_dylink_global_var_export(self):
    self.do_run(r'''
      #include <assert.h>
      #include <stdio.h>
      #include <emscripten.h>
      #include <emscripten/em_asm.h>

      EMSCRIPTEN_KEEPALIVE int my_number = 123456;

      int main(void) {
        void* js_address = EM_ASM_PTR({
          var value = HEAP32[_my_number/4];
          console.log("JS:_my_number:", _my_number, value);
          assert(value == 123456, value);
          return _my_number;
        });
        printf("C: my_number: %lu %d\n", (uintptr_t)&my_number, my_number);
        assert(js_address == &my_number);
        return 0;
      }
    ''', cflags=['-sMAIN_MODULE=2'], force_c=True)

  @with_dylink_reversed
  def test_dylink_global_var_modded(self):
    self.dylink_test(main=r'''
      #include <stdio.h>
      extern int foo;
      int main() {
        printf("extern is %d.\n", foo);
        return 0;
      }
    ''', side=r'''
      int foo = 123;
      struct Initter {
        Initter() { foo = 456; }
      };
      Initter initter;
    ''', expected=['extern is 456.\n'])

  @with_dylink_reversed
  def test_dylink_stdlib(self):
    self.dylink_test(header=r'''
      #include <math.h>
      #include <stdlib.h>
      #include <string.h>
      char *side(const char *data);
      double pow_two(double x);
    ''', main=r'''
      #include <stdio.h>
      #include "header.h"
      int main() {
        char *temp = side("hello through side\n");
        char *ret = (char*)malloc(strlen(temp)+1);
        strcpy(ret, temp);
        temp[1] = 'x';
        puts(ret);
        printf("pow_two: %d.\n", (int)pow_two(5.9));
        free(ret);
        free(temp);
        return 0;
      }
    ''', side=r'''
      #include "header.h"
      char *side(const char *data) {
        char *ret = (char*)malloc(strlen(data)+1);
        strcpy(ret, data);
        return ret;
      }
      double pow_two(double x) {
        return pow(2, x);
      }
    ''', expected=['hello through side\n\npow_two: 59.'], force_c=True)

  @with_dylink_reversed
  def test_dylink_jslib(self):
    create_file('lib.js', r'''
      addToLibrary({
        test_lib_func: (x) => x + 17.2
      });
    ''')
    self.dylink_test(header=r'''
      extern double test_lib_func(int input);
    ''', main=r'''
      #include <stdio.h>
      #include "header.h"
      extern double sidey();
      int main2() { return 11; }
      int main() {
        int input = sidey();
        double temp = test_lib_func(input);
        printf("other says %.2f\n", temp);
        printf("more: %.5f, %d\n", temp, input);
        return 0;
      }
    ''', side=r'''
      #include <stdio.h>
      #include "header.h"
      extern int main2();
      double sidey() {
        int temp = main2();
        printf("main2 said: %d\n", temp);
        printf("main2 said: %u, %c\n", temp, temp/2);
        return test_lib_func(temp);
      }
    ''', expected='other says 45.2', main_cflags=['--js-library', 'lib.js'], force_c=True)

  @with_dylink_reversed
  def test_dylink_many_postsets(self):
    NUM = 1234
    self.dylink_test(header=r'''
      #include <stdio.h>
      typedef void (*voidfunc)();
      static void simple() {
        printf("simple.\n");
      }
      static volatile voidfunc funcs[''' + str(NUM) + '] = { ' + ','.join(['simple'] * NUM) + r''' };
      static void test() {
        volatile int i = ''' + str(NUM - 1) + r''';
        funcs[i]();
        i = 0;
        funcs[i]();
      }
      extern void more();
    ''', main=r'''
      #include "header.h"
      int main() {
        test();
        more();
        return 0;
      }
    ''', side=r'''
      #include "header.h"
      void more() {
        test();
      }
    ''', expected=['simple.\nsimple.\nsimple.\nsimple.\n'], force_c=True)

  @with_dylink_reversed
  def test_dylink_postsets_chunking(self):
    self.dylink_test(header=r'''
      extern int global_var;
    ''', main=r'''
      #include <stdio.h>
      #include "header.h"

      // prepare 99 global variable with local initializer
      static int p = 1;
      #define P(x) __attribute__((used)) int *padding##x = &p;
      P(01) P(02) P(03) P(04) P(05) P(06) P(07) P(08) P(09) P(10)
      P(11) P(12) P(13) P(14) P(15) P(16) P(17) P(18) P(19) P(20)
      P(21) P(22) P(23) P(24) P(25) P(26) P(27) P(28) P(29) P(30)
      P(31) P(32) P(33) P(34) P(35) P(36) P(37) P(38) P(39) P(40)
      P(41) P(42) P(43) P(44) P(45) P(46) P(47) P(48) P(49) P(50)
      P(51) P(52) P(53) P(54) P(55) P(56) P(57) P(58) P(59) P(60)
      P(61) P(62) P(63) P(64) P(65) P(66) P(67) P(68) P(69) P(70)
      P(71) P(72) P(73) P(74) P(75) P(76) P(77) P(78) P(79) P(80)
      P(81) P(82) P(83) P(84) P(85) P(86) P(87) P(88) P(89) P(90)
      P(91) P(92) P(93) P(94) P(95) P(96) P(97) P(98) P(99)

      // prepare global variable with global initializer
      int *ptr = &global_var;

      int main(int argc, char *argv[]) {
        printf("%d\n", *ptr);
      }
    ''', side=r'''
      #include "header.h"

      int global_var = 12345;
    ''', expected=['12345\n'], force_c=True)

  @with_dylink_reversed
  @parameterized({
    'libcxx': ('libc,libc++,libmalloc,libc++abi',),
    'all': ('1',),
    'missing': ('libc,libmalloc,libc++abi', False, False, False),
    'missing_assertions': ('libc,libmalloc,libc++abi', False, False, True),
  })
  @no_js_math('JS_MATH is not compatible with MAIN_MODULE=1')
  def test_dylink_syslibs(self, syslibs, expect_pass=True, with_reversed=True, assertions=True):
    # When testing in WASMFS mode, we also need to force the WASMFS syslib into the test.
    if self.get_setting('WASMFS') and syslibs != '1':
      syslibs += ',libwasmfs'

    # one module uses libcxx, need to force its inclusion when it isn't the main
    if not with_reversed and self.dylink_reversed:
      self.skipTest('with_reversed is false')
    self.cflags.append('-Wno-deprecated')
    self.set_setting('WARN_ON_UNDEFINED_SYMBOLS', 0)

    if assertions is not None:
      self.set_setting('ASSERTIONS', int(assertions))

    if expect_pass:
      expected = 'cout hello from side'
      assert_returncode = 0
    else:
      if assertions:
        expected = 'build the MAIN_MODULE with EMCC_FORCE_STDLIBS=1 in the environment'
      else:
        expected = 'Error'
      assert_returncode = NON_ZERO

    with env_modify({'EMCC_FORCE_STDLIBS': syslibs, 'EMCC_ONLY_FORCED_STDLIBS': '1'}):
      self.dylink_test(main=r'''
        void side();
        int main() {
          side();
          return 0;
        }
      ''', side=r'''
        #include <iostream>
        void side() { std::cout << "cout hello from side\n"; }
      ''', expected=expected, main_module=1, assert_returncode=assert_returncode)

  @with_dylink_reversed
  @with_env_modify({'EMCC_FORCE_STDLIBS': 'libc++'})
  def test_dylink_iostream(self):
    self.dylink_test(header=r'''
      #include <iostream>
      #include <string>
      std::string side();
    ''', main=r'''
      #include "header.h"
      int main() {
        std::cout << "hello from main " << side() << std::endl;
        return 0;
      }
    ''', side=r'''
      #include "header.h"
      std::string side() { return "and hello from side"; }
    ''', expected=['hello from main and hello from side\n'])

  @with_dylink_reversed
  def test_dylink_dynamic_cast(self): # issue 3465
    self.dylink_test(header=r'''
      class Base {
      public:
          virtual void printName();
      };

      class Derived : public Base {
      public:
          void printName();
      };
    ''', main=r'''
      #include "header.h"
      #include <stdio.h>

      int main() {
        printf("starting main\n");

        Base *base = new Base();
        Base *derived = new Derived();
        base->printName();
        derived->printName();

        if (dynamic_cast<Derived*>(derived)) {
          printf("OK\n");
        } else {
          printf("KO\n");
        }

        delete base;
        delete derived;
        return 0;
      }
    ''', side=r'''
      #include "header.h"
      #include <stdio.h>

      void Base::printName() {
        printf("Base\n");
      }

      void Derived::printName() {
        printf("Derived\n");
      }
    ''', expected=['starting main\nBase\nDerived\nOK'])

  @with_all_eh_sjlj
  @with_dylink_reversed
  def test_dylink_raii_exceptions(self):
    self.dylink_test(main=r'''
      #include <stdio.h>
      extern int side();
      int main() {
        printf("from side: %d.\n", side());
      }
    ''', side=r'''
      #include <stdio.h>
      typedef int (*ifdi)(float, double, int);
      int func_with_special_sig(float a, double b, int c) {
        printf("special %f %f %d\n", a, b, c);
        return 1337;
      }
      struct DestructorCaller {
        ~DestructorCaller() { printf("destroy\n"); }
      };
      int side() {
        // d has a destructor that must be called on function
        // exit, which means an invoke will be used for the
        // indirect call here - and the signature of that call
        // is special and not present in the main module, so
        // it must be generated for the side module.
        DestructorCaller d;
        volatile ifdi p = func_with_special_sig;
        return p(2.18281, 3.14159, 42);
      }
    ''', expected=['special 2.182810 3.141590 42\ndestroy\nfrom side: 1337.\n'])

  @with_all_eh_sjlj
  @with_dylink_reversed
  @parameterized({
    '': ([],),
    'dyncalls': (['-sDYNCALLS'],),
  })
  def test_dylink_exceptions_try_catch(self, args):
    self.dylink_test(main=r'''
      #include <stdio.h>
      extern void side();
      int main() {
        try {
          throw 3;
        } catch (int n) {
          printf("main: caught %d\n", n);
        }
        side();
        return 0;
      }
    ''', side=r'''
      #include <stdio.h>
      void side() {
        try {
          throw 5.3f;
        } catch (float f) {
          printf("side: caught %.1f\n", f);
        }
      }
      ''', expected=['main: caught 3\nside: caught 5.3\n'], cflags=args)

  @with_all_eh_sjlj
  @with_dylink_reversed
  def test_dylink_exceptions_try_catch_2(self):
    self.dylink_test(main=r'''
      #include <stdio.h>
      extern void side_throw_int();
      int main() {
        try {
          side_throw_int();
        } catch (int n) {
          printf("main: caught %d\n", n);
        }
        return 0;
      }
      void main_throw_float() {
        throw 5.3f;
      }
    ''', side=r'''
      #include <stdio.h>
      extern void main_throw_float();
      void side_throw_int() {
        try {
          main_throw_float();
        } catch (float f) {
          printf("side: caught %.1f\n", f);
        }
        throw 3;
      }
      ''', expected=['side: caught 5.3\nmain: caught 3\n'])

  @with_all_eh_sjlj
  @needs_dylink
  @no_js_math('JS_MATH is not compatible with MAIN_MODULE=1')
  def test_dylink_exceptions_try_catch_6(self):
    create_file('main.cpp', r'''
      #include <assert.h>
      #include <stdio.h>
      #include <dlfcn.h>
      int main() {
        printf("in main\n");
        void* handle = dlopen("libside.so", RTLD_LAZY);
        assert(handle);
        void (*side)(void) = (void (*)(void))dlsym(handle, "side");
        (side)();
        return 0;
      }
    ''')

    # Create a dependency on __cxa_find_matching_catch_6 (6 = num clauses + 2)
    # which is one higher than the default set of __cxa_find_matching_catch
    # functions created in library_exceptions.js.
    # This means we end up depending on dynamic linking code to redirect
    # __cxa_find_matching_catch_6 to __cxa_find_matching_catch.
    create_file('libside.cpp', r'''
      #include <stdio.h>
      extern "C" void side() {
        printf("in side\n");
        try {
          throw 3;
        } catch (int x){
          printf("side: caught int %d\n", x);
        } catch (float x){
          printf("side: caught float %f\n", x);
        } catch (double x){
          printf("side: caught double %f\n", x);
        } catch (short x){
          printf("side: caught short %hd\n", x);
        }
      }
    ''')

    self.maybe_closure()

    # side settings
    self.clear_setting('MAIN_MODULE')
    self.set_setting('SIDE_MODULE')
    self.build('libside.cpp', output_suffix='.so')

    # main settings
    self.set_setting('MAIN_MODULE', 1)
    self.clear_setting('SIDE_MODULE')

    self.do_runf('main.cpp', 'side: caught int 3\n')

  @with_dylink_reversed
  @disabled('https://github.com/emscripten-core/emscripten/issues/12815')
  def test_dylink_hyper_dupe(self):
    self.set_setting('INITIAL_MEMORY', '64mb')
    self.set_setting('ASSERTIONS', 2)

    # test hyper-dynamic linking, and test duplicate warnings
    create_file('third.cpp', r'''
      #include <stdio.h>
      int sidef() { return 36; }
      int sideg = 49;
      int bsidef() { return 536; }
      extern void only_in_second_1(int x);
      extern int second_to_third;
      int third_to_second = 1337;

      void only_in_third_0() {
        // note we access our own globals directly, so
        // it doesn't matter that overriding failed
        printf("only_in_third_0: %d, %d, %d\n", sidef(), sideg, second_to_third);
        only_in_second_1(2112);
      }

      void only_in_third_1(int x) {
        printf("only_in_third_1: %d, %d, %d, %d\n", sidef(), sideg, second_to_third, x);
      }
    ''')
    self.run_process([EMCC, 'third.cpp', '-o', 'third.wasm', '-sSIDE_MODULE'] + self.get_cflags())
    self.dylink_test(main=r'''
      #include <stdio.h>
      #include <emscripten.h>
      extern int sidef();
      extern int sideg;
      extern int bsidef();
      extern int bsideg;
      extern void only_in_second_0();
      extern void only_in_third_0();
      int main() {
        EM_ASM({
          loadDynamicLibrary('third.wasm'); // hyper-dynamic! works at least for functions (and consts not used in same block)
        });
        printf("sidef: %d, sideg: %d.\n", sidef(), sideg);
        printf("bsidef: %d.\n", bsidef());
        only_in_second_0();
        only_in_third_0();
      }
    ''',
                     side=r'''
      #include <stdio.h>
      int sidef() { return 10; } // third will try to override these, but fail!
      int sideg = 20;
      extern void only_in_third_1(int x);
      int second_to_third = 500;
      extern int third_to_second;

      void only_in_second_0() {
        printf("only_in_second_0: %d, %d, %d\n", sidef(), sideg, third_to_second);
        only_in_third_1(1221);
      }

      void only_in_second_1(int x) {
        printf("only_in_second_1: %d, %d, %d, %d\n", sidef(), sideg, third_to_second, x);
      }
    ''',
                     expected=['sidef: 10, sideg: 20.\nbsidef: 536.\nonly_in_second_0: 10, 20, 1337\nonly_in_third_1: 36, 49, 500, 1221\nonly_in_third_0: 36, 49, 500\nonly_in_second_1: 10, 20, 1337, 2112\n'])

    print('check warnings')
    full = self.run_js('src.js')
    self.assertContained("warning: symbol '_sideg' from 'third.wasm' already exists", full)

  @needs_dylink
  @requires_node
  def test_dylink_load_compiled_side_module(self):
    self.set_setting('FORCE_FILESYSTEM')
    self.setup_nodefs_test()
    if not self.has_changed_setting('INITIAL_MEMORY'):
      self.set_setting('INITIAL_MEMORY', '64mb')
    # This test loads the module at runtime with loadWebAssemblyModule so we
    # want to suppress the automatic loading that would otherwise be done at
    # startup.
    self.set_setting('NO_AUTOLOAD_DYLIBS')

    self.dylink_test(main=r'''
      #include <stdio.h>
      #include <emscripten.h>
      extern int sidef();
      int main() {
        EM_ASM({
          var libData = FS.readFile('libside.so', {encoding: 'binary'});
          if (!(libData instanceof Uint8Array)) {
            libData = new Uint8Array(libData);
          }
          var compiledModule = new WebAssembly.Module(libData);
          var sideExports = loadWebAssemblyModule(compiledModule, {loadAsync: false, nodelete: true});
          mergeLibSymbols(sideExports, 'libside.so');
        });
        printf("sidef: %d.\n", sidef());
      }
    ''',
                     side=r'''
      #include <stdio.h>
      int sidef() { return 10; }
    ''', expected=['sidef: 10'])

  @needs_dylink
  @no_js_math('JS_MATH is not compatible with MAIN_MODULE=1')
  def test_dylink_dso_needed(self):
    def do_run(src, expected_output, cflags=None):
      create_file('main.c', src + 'int main() { return test_main(); }')
      self.do_runf('main.c', expected_output, cflags=cflags)
    self._test_dylink_dso_needed(do_run)

  @with_dylink_reversed
  def test_dylink_dot_a(self):
    # Tests that it's possible to link a .a archive into a dynamic library using
    # `-Wl,--whole-archive`
    create_file('third.c', 'int sidef() { return 36; }')
    create_file('fourth.c', 'int sideg() { return 17; }')

    self.run_process([EMCC, '-fPIC', '-c', 'third.c', '-o', 'third.o'] + self.get_cflags(compile_only=True))
    self.run_process([EMCC, '-fPIC', '-c', 'fourth.c', '-o', 'fourth.o'] + self.get_cflags(compile_only=True))
    self.run_process([EMAR, 'rc', 'libfourth.a', 'fourth.o'])

    self.dylink_test(main=r'''
      #include <stdio.h>
      #include <emscripten.h>
      int sidef();
      int sideg();
      int main() {
        printf("sidef: %d, sideg: %d.\n", sidef(), sideg());
      }
    ''',
                     # contents of libfourth.a must be included, even if they aren't referred to!
                     side=['-Wl,--whole-archive', 'libfourth.a', '-Wl,--no-whole-archive', 'third.o'],
                     expected=['sidef: 36, sideg: 17.\n'], force_c=True)

  @with_dylink_reversed
  def test_dylink_spaghetti(self):
    self.dylink_test(main=r'''
      #include <stdio.h>
      int main_x = 72;
      extern int side_x;
      int adjust = side_x + 10;
      int *ptr = &side_x;
      struct Class {
        Class() {
          printf("main init sees %d, %d, %d.\n", adjust, *ptr, main_x);
        }
      };
      Class cm;
      int main() {
        printf("main main sees %d, %d, %d.\n", adjust, *ptr, main_x);
        return 0;
      }
    ''', side=r'''
      #include <stdio.h>
      extern int main_x;
      int side_x = -534;
      int adjust2 = main_x + 10;
      int *ptr2 = &main_x;
      struct SideClass {
        SideClass() {
          printf("side init sees %d, %d, %d.\n", adjust2, *ptr2, side_x);
        }
      };
      SideClass cs;
    ''', expected=['''\
side init sees 82, 72, -534.
main init sees -524, -534, 72.
main main sees -524, -534, 72.
''', '''\
main init sees -524, -534, 72.
side init sees 82, 72, -534.
main main sees -524, -534, 72.
'''])

  @needs_make('mingw32-make')
  @needs_dylink
  @parameterized({
    'cmake': (True,),
    'configure': (False,),
  })
  def test_dylink_zlib(self, cmake):
    if cmake:
      output = self.run_process(['cmake', '--version'], stdout=PIPE).stdout
      cmake_version = output.splitlines()[0].split()[-1].strip()
      cmake_version = cmake_version.split('-')[0] # Remove trail like "4.2.0-rc3" -> "4.2.0"
      cmake_version = tuple(int(part) for part in cmake_version.split('.'))
      # We don't support dynamic linking with certain versions of cmake
      # See https://gitlab.kitware.com/cmake/cmake/-/work_items/27240
      if (cmake_version >= (4, 2, 0) and cmake_version < (4, 2, 6)) or \
         (cmake_version >= (4, 3, 0) and cmake_version < (4, 3, 3)):
        self.skipTest(f'incompatible cmake version {cmake_version}')
    zlib_archive = self.get_zlib_library(cmake=cmake, target='libz.so.1.2.5', cflags=['-fPIC'])[0]
    zlib_basename = os.path.basename(zlib_archive)
    shutil.copyfile(zlib_archive, zlib_basename)
    # example.c uses K&R style function declarations
    self.cflags.append('-Wno-deprecated-non-prototype')
    self.cflags.append('-I' + test_file('third_party/zlib'))
    self.do_runf(test_file('third_party/zlib/example.c'),
                 read_file(test_file('core/test_zlib.out')),
                 cflags=['-L.', zlib_archive])

  # @with_dylink_reversed
  # def test_dylink_bullet(self):
  #   self.cflags += ['-I' + test_file('bullet/src')]
  #   side = self.get_bullet_library(self, True)
  #   self.dylink_test(main=read_file(test_file('bullet/Demos/HelloWorld/HelloWorld.cpp')),
  #                    side=side,
  #                    expected=[read_file(test_file('bullet/output.txt')), # different roundings
  #                              read_file(test_file('bullet/output2.txt')),
  #                              read_file(test_file('bullet/output3.txt'))])

  @with_dylink_reversed
  def test_dylink_rtti(self):
    # Verify that objects created in one module and be dynamic_cast<> correctly
    # in the another module.
    # Each module will define its own copy of certain COMDAT symbols such as
    # each classs's typeinfo, but at runtime they should both use the same one.
    header = '''
    #include <cstddef>

    class Foo {
    public:
      virtual ~Foo() {}
    };

    class Bar : public Foo {
    public:
      virtual ~Bar() {}
    };

    bool is_bar(Foo* foo);
    '''

    main = '''
    #include <stdio.h>
    #include "header.h"

    int main() {
      Bar bar;
      if (!is_bar(&bar)) {
        puts("failure");
        return 1;
      }
      puts("done");
      return 0;
    }
    '''

    side = '''
    #include "header.h"

    bool is_bar(Foo* foo) {
      return dynamic_cast<Bar*>(foo) != nullptr;
    }
    '''

    self.dylink_test(main=main,
                     side=side,
                     header=header,
                     expected='done\n')

  @needs_dylink
  def test_dylink_argv_argc(self):
    # Verify that argc and argv can be sent to main when main is in a side module
    self.cflags += ['--pre-js', 'pre.js', '--no-entry', '-sINCOMING_MODULE_JS_API=arguments']
    create_file('pre.js', "Module['arguments'] = ['hello', 'world!']")
    self.dylink_test(
      '', # main module is empty.
      r'''
      #include <stdio.h>
      int main(int argc, char const *argv[]) {
        printf("%d ", argc);
        for (int i=1; i<argc; i++) printf("%s ", argv[i]);
        printf("\n");
        return 0;
      }
      ''',
      expected='3 hello world!')

  @needs_dylink
  def test_dylink_weak(self):
    # Verify that weakly defined symbols can be defined in both side module and main
    # module but that only one gets used at runtime.
    self.dylink_testf(test_file('core/test_dylink_weak.c'))

  @needs_dylink
  def test_dylink_weak_undef(self):
    self.dylink_testf(test_file('core/test_dylink_weak_undef.c'))

  @needs_dylink
  def test_dylink_weak_multilib(self):
    create_file('liba.c', '''
      #include <stdio.h>

      extern int globalA __attribute__((weak));

      void LibBFunc();

      void LibAFunc() {
        LibBFunc();
        printf("globalA %p\\n", &globalA);
      }
    ''')
    create_file('libb.c', '''
     #include <stdio.h>

     void LibBFunc() {
       printf("Hello from b\\n");
     }
    ''')
    create_file('main.c', '''
      void LibAFunc();
      int main() {
        LibAFunc();
      }
    ''')
    self.run_process([EMCC, 'libb.c', '-o', 'libb.so', '-sSIDE_MODULE'] + self.get_cflags())
    self.run_process([EMCC, 'liba.c', '-o', 'liba.so', '-sSIDE_MODULE', 'libb.so'] + self.get_cflags())
    self.do_runf('main.c', 'Hello from b\n', cflags=['-sMAIN_MODULE=2', '-L.', 'liba.so'])

  @requires_pthreads
  @needs_dylink
  def test_dylink_tls(self):
    self.cflags.append('-Wno-experimental')
    self.dylink_testf(test_file('core/test_dylink_tls.c'))

  @requires_pthreads
  @needs_dylink
  def test_dylink_tls_export(self):
    self.cflags.append('-Wno-experimental')
    self.dylink_testf(test_file('core/test_dylink_tls_export.c'))

  def test_random(self):
    src = r'''#include <stdlib.h>
#include <stdio.h>

int main()
{
    srandom(0xdeadbeef);
    printf("%ld\n", random());
}
'''
    self.do_run(src, '956867869')

  def test_rand(self):
    # llvmlibc has a different implementation for rand(), so verify
    # against a separate test output
    out_suffix = '_llvmlibc' if '-lllvmlibc' in self.cflags else ''

    self.do_core_test('test_rand.c', out_suffix=out_suffix)

  def test_strtod(self):
    self.do_core_test('test_strtod.c')

  def test_strtold(self):
    self.do_core_test('test_strtold.c')

  def test_strtok(self):
    self.do_core_test('test_strtok.c')

  def test_strtol(self):
    if self.is_wasm64():
      out_suffix = '64'
    else:
      out_suffix = ''
    self.do_core_test('test_strtol.c', out_suffix=out_suffix)

  def test_transtrcase(self):
    self.do_core_test('test_transtrcase.c')

  @also_with_wasmfs # tests EXIT_RUNTIME flushing
  @no_wasm2js('very slow to compile: https://github.com/emscripten-core/emscripten/issues/21048')
  @is_slow_test
  def test_printf(self):
    self.cflags.append('-Wno-format')
    # needs to flush stdio streams
    self.set_setting('EXIT_RUNTIME')
    self.set_setting('STACK_SIZE', '1MB')
    if self.is_wasm64():
      out_suffix = '64'
    else:
      out_suffix = ''
    self.do_runf_out_file('printf/test_printf.c', out_suffix=out_suffix)

  def test_printf_2(self):
    self.do_core_test('test_printf_2.c')

  def test_printf_float(self):
    self.do_runf_out_file('printf/test_float.c')

  def test_printf_octal(self):
    self.do_runf_out_file('printf/test_octal.c')

  def test_printf_macros(self):
    self.do_core_test('test_printf_macros.c')

  def test_vprintf(self):
    self.do_core_test('test_vprintf.c')

  def test_vsnprintf(self):
    self.do_core_test('test_vsnprintf.c')

  def test_printf_more(self):
    self.do_core_test('test_printf_more.c')

  def test_perrar(self):
    self.do_core_test('test_perrar.c')

  def test_atoX(self):
    self.do_core_test('test_atoX.c')

  def test_strstr(self):
    self.do_core_test('test_strstr.c')

  def test_fnmatch(self):
    self.do_core_test('test_fnmatch.cpp')

  def test_sscanf(self):
    self.do_core_test('test_sscanf.c')

  def test_sscanf_2(self):
    # doubles
    for ftype in ('float', 'double'):
      src = r'''
          #include <stdio.h>

          int main(){
              char strval1[] = "1.2345678901";
              char strval2[] = "1.23456789e5";
              char strval3[] = "1.23456789E5";
              char strval4[] = "1.2345678e-5";
              char strval5[] = "1.2345678E-5";
              double dblval = 1.2345678901;
              double tstval;

              sscanf(strval1, "%lf", &tstval);
              if(dblval != tstval) printf("FAIL: Values are not equal: %lf %lf\n", dblval, tstval);
              else printf("Pass: %lf %lf\n", tstval, dblval);

              sscanf(strval2, "%lf", &tstval);
              dblval = 123456.789;
              if(dblval != tstval) printf("FAIL: Values are not equal: %lf %lf\n", dblval, tstval);
              else printf("Pass: %lf %lf\n", tstval, dblval);

              sscanf(strval3, "%lf", &tstval);
              dblval = 123456.789;
              if(dblval != tstval) printf("FAIL: Values are not equal: %lf %lf\n", dblval, tstval);
              else printf("Pass: %lf %lf\n", tstval, dblval);

              sscanf(strval4, "%lf", &tstval);
              dblval = 0.000012345678;
              if(dblval != tstval) printf("FAIL: Values are not equal: %lf %lf\n", dblval, tstval);
              else printf("Pass: %lf %lf\n", tstval, dblval);

              sscanf(strval5, "%lf", &tstval);
              dblval = 0.000012345678;
              if(dblval != tstval) printf("FAIL: Values are not equal: %lf %lf\n", dblval, tstval);
              else printf("Pass: %lf %lf\n", tstval, dblval);

              return 0;
          }
        '''
      if ftype == 'float':
        self.do_run(src.replace('%lf', '%f').replace('double', 'float'), '''Pass: 1.234568 1.234568
Pass: 123456.789062 123456.789062
Pass: 123456.789062 123456.789062
Pass: 0.000012 0.000012
Pass: 0.000012 0.000012''')
      else:
        self.do_run(src, '''Pass: 1.234568 1.234568
Pass: 123456.789000 123456.789000
Pass: 123456.789000 123456.789000
Pass: 0.000012 0.000012
Pass: 0.000012 0.000012''')

  def test_sscanf_n(self):
    self.do_core_test('test_sscanf_n.c')

  def test_sscanf_whitespace(self):
    self.do_core_test('test_sscanf_whitespace.c')

  def test_sscanf_other_whitespace(self):
    # use i16s in printf
    self.set_setting('SAFE_HEAP', 0)
    self.do_core_test('test_sscanf_other_whitespace.c')

  def test_sscanf_3(self):
    self.do_core_test('test_sscanf_3.c')

  def test_sscanf_4(self):
    self.do_core_test('test_sscanf_4.c')

  def test_sscanf_5(self):
    self.do_core_test('test_sscanf_5.c')

  def test_sscanf_6(self):
    self.do_core_test('test_sscanf_6.c')

  def test_sscanf_skip(self):
    self.do_core_test('test_sscanf_skip.c')

  def test_sscanf_caps(self):
    self.do_core_test('test_sscanf_caps.c')

  def test_sscanf_hex(self):
    self.do_core_test('test_sscanf_hex.c')

  def test_sscanf_float(self):
    self.do_core_test('test_sscanf_float.c')

  def test_langinfo(self):
    self.do_core_test('test_langinfo.c')

  @no_modularize_instance('uses Module object directly')
  @no_strict('TODO: Fails in -sSTRICT mode due to an unknown reason.')
  @no_wasmfs('depends on FS.createLazyFile which WASMFS does not have')
  def test_files(self):
    # Use closure here, to test we don't break FS stuff
    if '-O3' in self.cflags and self.is_wasm2js():
      print('closure 2')
      self.cflags += ['--closure', '2'] # Use closure 2 here for some additional coverage
      # Sadly --closure=2 is not yet free of closure warnings
      # FIXME(https://github.com/emscripten-core/emscripten/issues/17080)
      self.ldflags.append('-Wno-error=closure')
    else:
      self.maybe_closure()

    self.cflags += ['--pre-js', 'pre.js', '-sINCOMING_MODULE_JS_API=preRun']
    self.set_setting('FORCE_FILESYSTEM')

    create_file('pre.js', '''
/** @suppress{checkTypes}*/
Module = {
  'noFSInit': true,
  'preRun': () => {
    FS.createLazyFile('/', 'test.file', 'test.file', true, false);
    // Test FS_* exporting
    Module['FS_createDataFile']('/', 'somefile.binary', [100, 200, 50, 25, 10, 77, 123], true, false, false);  // 200 becomes -56, since signed chars are used in memory
    var test_files_input = 'hi there!';
    var test_files_input_index = 0;
    FS.init(() => {
      return test_files_input.charCodeAt(test_files_input_index++) || null;
    });
  }
};
''')

    create_file('test.file', 'some data')

    self.do_runf_out_file('test_files.c')

  @no_wasmfs('Error: EAGAIN: resource temporarily unavailable. https://github.com/emscripten-core/emscripten/issues/25035')
  def test_module_stdin(self):
    create_file('pre.js', '''
    var data = [10, 20, 40, 30];
    Module = {
      stdin: () => { return data.pop() || null },
      stdout: (x) => out('got: ' + x)
    };
    ''')
    self.cflags += ['--pre-js', 'pre.js', '-sINCOMING_MODULE_JS_API=stdin,stdout']

    src = r'''
      #include <stdio.h>
      #include <unistd.h>

      int main () {
        char c;
        fprintf(stderr, "isatty? in=%d,out=%d,err=%d\n", isatty(fileno(stdin)), isatty(fileno(stdout)), isatty(fileno(stderr)));
        while ((c = fgetc(stdin)) != EOF) {
          putc(c, stdout);
        }
        putc('\n', stdout);
        return 0;
      }
      '''

    self.do_run(src, '''\
isatty? in=0,out=0,err=1
got: 30
got: 40
got: 20
got: 10
''')

  def test_mount(self):
    self.set_setting('FORCE_FILESYSTEM')
    if self.get_setting('WASMFS'):
      self.cflags += ['-licasefs.js']
      self.cflags += ['-ljsfilefs.js']
    self.do_runf('fs/test_mount.c', 'done\n')

  def test_getdents64(self):
    self.do_runf('fs/test_getdents64.c', '..')

  def test_getdents64_special_cases(self):
    self.do_runf_out_file('fs/test_getdents64_special_cases.c')

  def test_getcwd_with_non_ascii_name(self):
    self.do_runf_out_file('fs/test_getcwd_with_non_ascii_name.c')

  @no_wasmfs('no support for /proc/self/fd/, see https://github.com/emscripten-core/emscripten/issues/19430')
  def test_proc_self_fd(self):
    self.do_runf_out_file('fs/test_proc_self_fd.c')

  def test_fwrite_0(self):
    self.do_core_test('test_fwrite_0.c')

  @also_with_nodefs_both
  def test_fgetc_ungetc(self):
    print('TODO: update this test once the musl ungetc-on-EOF-stream bug is fixed upstream and reaches us')
    self.do_runf('stdio/test_fgetc_ungetc.c', 'done\n')

  def test_fgetc_unsigned(self):
    src = r'''
      #include <stdio.h>
      int main() {
        FILE *file = fopen("file_with_byte_234.txt", "rb");
        int c = fgetc(file);
        printf("*%d\n", c);
      }
    '''
    create_file('file_with_byte_234.txt', b'\xea', binary=True)
    self.cflags += ['--embed-file', 'file_with_byte_234.txt']
    self.do_run(src, '*234\n')

  def test_fgets_eol(self):
    src = r'''
      #include <stdio.h>
      char buf[32];
      int main() {
        const char *r = "SUCCESS";
        FILE *f = fopen("eol.txt", "r");
        while (fgets(buf, 32, f) != NULL) {
          if (buf[0] == '\0') {
            r = "FAIL";
            break;
          }
        }
        printf("%s\n", r);
        fclose(f);
        return 0;
      }
    '''
    create_file('eol.txt', b'\n', binary=True)
    self.cflags += ['--embed-file', 'eol.txt']
    self.do_run(src, 'SUCCESS\n')

  @no_wasm64('https://github.com/emscripten-core/emscripten/issues/27221')
  @no_wasm2js('Legacy JS does not support threads and atomics, which are needed by OpenMP')
  # We don't use the `requires_pthreads` decorator because we want to test that pthreads is
  # automatically enabled when OpenMP is used.
  def test_openmp_max_threads(self):
    src = r"""
      #include <omp.h>
      #include <assert.h>
      int main(void) {
        assert(omp_get_max_threads() > 0);
        return 0;
      }
    """
    # We need to explicitly add the `-Wno-pthreads-mem-growth` flag because
    # ASAN uses `-sALLOW_MEMORY_GROWTH`.
    self.do_run(src, "", cflags=['-fopenmp=libomp', '-Wno-pthreads-mem-growth'])

  def test_fscanf(self):
    create_file('three_numbers.txt', '-1 0.1 -.1')
    src = r'''
      #include <stdio.h>
      #include <assert.h>
      #include <float.h>
      int main() {
        float x = FLT_MAX, y = FLT_MAX, z = FLT_MAX;

        FILE* fp = fopen("three_numbers.txt", "r");
        if (fp) {
          int match = fscanf(fp, " %f %f %f ", &x, &y, &z);
          printf("match = %d\n", match);
          printf("x = %0.1f, y = %0.1f, z = %0.1f\n", x, y, z);
        } else {
          printf("failed to open three_numbers.txt\n");
        }
        return 0;
      }
    '''
    self.cflags += ['--embed-file', 'three_numbers.txt']
    self.do_run(src, 'match = 3\nx = -1.0, y = 0.1, z = -0.1\n')

  def test_fscanf_2(self):
    create_file('a.txt', '1/2/3 4/5/6 7/8/9\n')
    self.cflags += ['--embed-file', 'a.txt']
    self.do_run(r'''\
      #include <stdio.h>

      int main(int argv, char** argc) {
        printf("fscanf test\n");

        FILE* file = fopen("a.txt", "rb");
        int vertexIndex[4];
        int normalIndex[4];
        int uvIndex[4];

        int matches = fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d\n",
                             &vertexIndex[0], &uvIndex[0], &normalIndex[0],
                             &vertexIndex[1], &uvIndex[1], &normalIndex[1],
                             &vertexIndex[2], &uvIndex[2], &normalIndex[2],
                             &vertexIndex[3], &uvIndex[3], &normalIndex[3]);
        fclose(file);

        printf("matches: %d\n", matches);
        return 0;
      }
    ''', 'fscanf test\nmatches: 9\n')

  def test_fileno(self):
    create_file('empty.txt', '')
    src = r'''
      #include <stdio.h>
      #include <unistd.h>
      int main() {
        FILE* fp = fopen("empty.txt", "r");
        if (fp) {
            printf("%d\n", fileno(fp));
        } else {
            printf("failed to open empty.txt\n");
        }
        return 0;
      }
    '''
    self.cflags += ['--embed-file', 'empty.txt']
    self.do_run(src, '3\n')

  @also_with_noderawfs
  def test_readdir(self):
    if self.get_setting('WASMFS') and self.get_setting('NODERAWFS'):
      # WasmFS + NODERAWFS lacks ino numbers in directory listings, see
      # https://github.com/emscripten-core/emscripten/issues/19418
      # We need to tell the test we are in this mode so it can ignore them.
      self.cflags += ['-DWASMFS_NODERAWFS']
    self.do_runf_out_file('dirent/test_readdir.c')

  @also_without_bigint
  def test_readdir_empty(self):
    self.do_runf_out_file('dirent/test_readdir_empty.c')

  def test_readdir_unlink(self):
    self.do_runf_out_file('dirent/test_readdir_unlink.c')

  def test_stat(self):
    self.set_setting("FORCE_FILESYSTEM")
    self.do_runf('stat/test_stat.c', 'done\n')
    self.verify_in_strict_mode(self.output_name('test_stat'))

  def test_statx(self):
    self.set_setting("FORCE_FILESYSTEM")
    self.do_runf('stat/test_statx.c', 'done\n')

  def test_fstatat(self):
    self.do_runf('stat/test_fstatat.c', 'done\n')

  @crossplatform
  @with_all_fs
  def test_stat_chmod(self):
    nodefs = '-DNODEFS' in self.cflags or '-DNODERAWFS' in self.cflags
    if nodefs and WINDOWS:
      self.skipTest('mode bits work differently on windows')
    if nodefs and self.get_setting('WASMFS'):
      self.skipTest('test requires symlink creation which currently missing from wasmfs+noderawfs')
    self.do_runf('stat/test_chmod.c', 'done\n', cflags=['-Werror=conversion'])

  @also_with_wasmfs
  def test_stat_mknod(self):
    self.do_runf('stat/test_mknod.c', 'done\n')

  @also_with_wasmfs
  def test_fcntl(self):
    if self.get_setting('WASMFS'):
      self.cflags += ['-sFORCE_FILESYSTEM']
    self.add_pre_run("FS.createDataFile('/', 'test', 'abcdef', true, true, false);")
    self.do_runf_out_file('fcntl/test_fcntl.c')

  @crossplatform
  @also_with_nodefs_both
  @no_deno('https://github.com/emscripten-core/emscripten/issues/26235')
  def test_fcntl_open(self):
    if '-lllvmlibc' in self.cflags:
      self.skipTest('https://github.com/emscripten-core/emscripten/issues/26740')

    nodefs = '-DNODEFS' in self.cflags or '-DNODERAWFS' in self.cflags
    if nodefs and WINDOWS:
      self.skipTest('Stat mode behavior does not match on Windows')
    if '-DNODERAWFS' in self.cflags and not LINUX:
      self.skipTest('noderawfs fails here under non-linux')
    self.do_runf_out_file('fcntl/test_fcntl_open.c')

  @also_without_bigint
  def test_fcntl_misc(self):
    if self.get_setting('WASMFS'):
      self.cflags += ['-sFORCE_FILESYSTEM']
    self.add_pre_run("FS.createDataFile('/', 'test', 'abcdef', true, true, false);")
    self.do_runf_out_file('fcntl/test_fcntl_misc.c')

  def test_poll(self):
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')
    self.do_core_test('test_poll.c')

  @no_wasmfs('st.f_ffree > st.f_files, same issue than in wasmfs.test_fs_nodefs_statvfs. https://github.com/emscripten-core/emscripten/issues/25035')
  def test_statvfs(self):
    self.do_core_test('test_statvfs.c')

  def test_libgen(self):
    self.do_core_test('test_libgen.c')

  def test_utime(self):
    self.do_runf('utime/test_utime.c', 'done\n')

  @also_with_nodefs_both
  @flaky('https://github.com/emscripten-core/emscripten/issues/25280')
  def test_futimens(self):
    self.do_runf('utime/test_futimens.c', 'done\n')

  @with_both_text_decoder
  def test_utf(self):
    self.do_core_test('test_utf.c')

  @with_both_text_decoder
  def test_utf32(self):
    self.do_runf('utf32.cpp', 'OK (long).\n')

  @with_both_text_decoder
  @no_sanitize('requires libc to be built with -fshort-char')
  def test_utf32_short_wchar(self):
    if '-flto' in self.cflags or '-flto=thin' in self.cflags:
      self.skipTest('-fshort-wchar is not compatible with LTO (libraries would need rebuilding)')
    self.do_runf('utf32.cpp', 'OK (short).\n', cflags=['-fshort-wchar'])

  @with_both_text_decoder
  @crossplatform
  def test_utf16(self):
    self.do_runf('core/test_utf16.cpp', 'done\n')

  @with_both_text_decoder
  def test_utf8(self):
    self.do_runf('core/test_utf8.c', 'done\n')

  @with_both_text_decoder
  @also_without_bigint
  def test_utf8_bench(self):
    self.cflags += ['--embed-file', test_file('test_utf8_bench.txt') + '@/utf8_corpus.txt']
    self.do_runf('test_utf8_bench.c', 'done\n')

  # Test that invalid character in UTF8 does not cause decoding to crash.
  @with_both_text_decoder
  @also_with_minimal_runtime
  def test_utf8_invalid(self):
    self.do_runf('test_utf8_invalid.c', 'done\n')

  def test_utf16_bench(self):
    self.cflags += ['--embed-file', test_file('test_utf16_bench.txt') + '@/utf16_corpus.txt']
    self.do_runf('test_utf16_bench.c', 'done\n')

  def test_wprintf(self):
    self.do_core_test('test_wprintf.cpp')

  def test_write_stdout_fileno(self):
    self.do_core_test('test_write_stdout_fileno.c')
    self.do_core_test('test_write_stdout_fileno.c', args=['-sFILESYSTEM=0'])

  def test_direct_string_constant_usage(self):
    self.do_core_test('test_direct_string_constant_usage.cpp')

  def test_std_function_incomplete_return(self):
    self.do_core_test('test_std_function_incomplete_return.cpp')

  def test_istream(self):
    self.do_core_test('test_istream.cpp')

  @no_wasmfs('depends on FS.makedev which WASMFS does not have')
  def test_fs_base(self):
    self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$FS'])
    self.add_pre_run(read_file(test_file('fs/test_fs_base.js')))
    self.do_runf_out_file('fs/test_fs_base.c')

  @also_with_noderawfs
  @is_slow_test
  @requires_node
  def test_fs_nodefs_rw(self):
    if not self.get_setting('NODERAWFS'):
      self.setup_nodefs_test()
    self.maybe_closure()
    self.do_runf('fs/test_nodefs_rw.c', 'done\n')

  @also_with_noderawfs
  @requires_node
  def test_fs_nodefs_cloexec(self):
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')
    if not self.get_setting('NODERAWFS'):
      self.setup_nodefs_test()
    self.do_runf('fs/test_nodefs_cloexec.c', 'done\n')

  @also_with_noderawfs
  @requires_node
  def test_fs_nodefs_dup(self):
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')
    if not self.get_setting('NODERAWFS'):
      self.setup_nodefs_test()
    self.do_runf('fs/test_nodefs_dup.c', 'done\n')

  @requires_node
  def test_fs_nodefs_home(self):
    self.do_runf('fs/test_nodefs_home.c', 'done\n', cflags=['-sFORCE_FILESYSTEM', '-lnodefs.js'])

  @requires_node
  def test_fs_nodefs_nofollow(self):
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')
    self.do_runf('fs/test_nodefs_nofollow.c', 'done\n', cflags=['-lnodefs.js'])

  @crossplatform
  @requires_node
  @no_modularize_instance('expects js filename')
  def test_fs_nodefs_readdir(self):
    # externally setup an existing folder structure: existing/a
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')
    if not WINDOWS:
      # Add an entry that isn't a directory, file, or link to test that we handle
      # it correctly.
      os.mkfifo('named_pipe')
    os.makedirs('existing/a')
    suffix = ''
    # Windows does not add a name_pipe to test expectations.
    if self.get_setting('WASMFS'):
      suffix = '.wasmfs_win' if WINDOWS else '.wasmfs'
    elif self.is_wasm2js():
      suffix = ".wasm2js"
    self.do_runf_out_file('fs/test_nodefs_readdir.c', out_suffix=suffix, cflags=['-lnodefs.js'])

  @requires_node
  @crossplatform
  @no_wasmfs('Assertion failed: st.f_ffree <= st.f_files && "Free inodes should not exceed total inodes". https://github.com/emscripten-core/emscripten/issues/25035')
  def test_fs_nodefs_statvfs(self):
    # externally setup an existing folder structure: existing/a
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')
    os.makedirs('existing/a')
    self.do_runf('fs/test_nodefs_statvfs.c', 'done\n', cflags=['-lnodefs.js'])

  @no_windows('no symlink support on windows')
  @requires_node
  def test_fs_noderawfs_nofollow(self):
    self.set_setting('NODERAWFS')
    create_file('filename', 'foo')
    os.symlink('filename', 'linkname')
    self.do_runf('fs/test_noderawfs_nofollow.c', 'done\n', cflags=['-lnodefs.js'])

  @no_wasmfs('depends on FS.trackingDelegate which WASMFS does not have')
  def test_fs_trackingdelegate(self):
    self.do_runf_out_file('fs/test_trackingdelegate.c', cflags=['-sFS_DEBUG'])

  @with_all_fs
  def test_fs_writeFile(self):
    if self.get_setting('WASMFS'):
      self.set_setting("FORCE_FILESYSTEM")
    self.do_runf_out_file('fs/test_writeFile.cpp')

  @with_all_fs
  @crossplatform
  def test_fs_js_api(self):
    nodefs = '-DNODEFS' in self.cflags or '-DNODERAWFS' in self.cflags
    if nodefs and WINDOWS:
      self.skipTest('specific errno values differ')
    if self.get_setting('WASMFS'):
      self.set_setting("FORCE_FILESYSTEM")
    self.do_runf('fs/test_fs_js_api.c', 'done\n')

  @also_with_noderawfs
  def test_fs_write(self):
    if self.get_setting('WASMFS'):
      self.set_setting("FORCE_FILESYSTEM")
    self.do_runf_out_file('fs/test_fs_write.c')

  @with_all_fs
  def test_fs_access_mode(self):
    # Writing to an O_RDONLY fd and reading from an O_WRONLY fd must fail with
    # EBADF, consistently across all filesystems.
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')
    self.do_runf('fs/test_access_mode.c', 'done\n')

  @also_with_noderawfs
  def test_fs_emptyPath(self):
    self.do_runf_out_file('fs/test_emptyPath.c')

  @no_windows('no symlink support on windows')
  @also_with_nodefs_both
  def test_fs_link(self):
    self.do_runf('fs/test_link.c', 'done\n')

  @no_windows('no symlink support on windows')
  @also_with_nodefs_both
  def test_fs_utimensat_nofollow(self):
    self.do_runf('fs/test_utimensat_nofollow.c', 'done\n')

  @also_with_nodefs_both
  def test_fs_fadvise_fallocate(self):
    self.do_runf('fs/test_fadvise_fallocate.c', 'done\n')

  @no_windows('no uid/gid concept on windows')
  def test_fs_getuid_noderawfs(self):
    # The default backends report 0 and are covered by unistd/misc.c. This test
    # verifies NODERAWFS reports the real host process credentials.
    self.setup_noderawfs_test()
    self.do_runf('fs/test_getuid_noderawfs.c', 'done\n')

  @no_windows('https://github.com/emscripten-core/emscripten/issues/8882')
  @crossplatform
  @also_with_nodefs_both
  @no_wasmfs('Assertion failed: open("./does-not-exist/", O_CREAT, 0777) == -1 in test_fs_enotdir.c line 20. https://github.com/emscripten-core/emscripten/issues/25035')
  def test_fs_enotdir(self):
    if MACOS and '-DNODERAWFS' in self.cflags:
      self.skipTest('BSD libc sets a different errno')
    self.do_runf('fs/test_fs_enotdir.c', 'done\n')

  @also_with_noderawfs
  def test_fs_append(self):
    self.do_runf('fs/test_append.c', 'done\n')

  @with_all_fs
  def test_fs_mmap(self):
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')
    self.do_runf_out_file('fs/test_mmap.c')

  @no_wasmfs('wasmfs will (?) need a non-JS mechanism to ignore permissions during startup')
  @also_with_minimal_runtime
  def test_fs_no_main(self):
    # library_fs.js uses hooks to enable ignoring of permissions up until ATMAINs are run.  This
    # test verified that they work correctly, even in programs without a main function.
    create_file('pre.js', '''
Module.preRun = () => {
  assert(FS.ignorePermissions, "ignorePermissions not set during preRun");
}
Module.onRuntimeInitialized = () => {
  assert(!FS.ignorePermissions, "ignorePermissions not unset during onRuntimeInitialized");
  assert(_foo() == 42);
}
''')
    self.set_setting('EXPORTED_FUNCTIONS', '_foo')
    self.set_setting('FORCE_FILESYSTEM')
    self.cflags += ['--pre-js', 'pre.js', '-sINCOMING_MODULE_JS_API=preRun,onRuntimeInitialized']
    self.do_run('int foo() { return 42; }', '', force_c=True)

  @also_with_noderawfs
  def test_fs_errorstack(self):
    # Enables strict mode, which may catch some strict-mode-only errors
    # so that users can safely work with strict JavaScript if enabled.
    create_file('pre.js', '"use strict";')
    self.cflags += ['--pre-js', 'pre.js']

    self.set_setting('FORCE_FILESYSTEM')
    self.set_setting('ASSERTIONS')
    create_file('test.c', r'''
      #include <emscripten.h>
      #include <stdio.h>
      int main(void) {
        printf("Hello, world!\n"); // should work with strict mode
        EM_ASM(
          try {
            FS.readFile('/dummy.txt');
          } catch (err) {
            err.stack = err.stack; // should be writable
            throw err;
          }
        );
        return 0;
      }
    ''')
    # engines have different error stack format
    self.do_runf('test.c', 'at (Object.)?readFile', regex=True, assert_returncode=NON_ZERO)

  @also_with_noderawfs
  def test_fs_llseek(self):
    self.do_runf('fs/test_llseek.c', 'done\n', cflags=['-sFORCE_FILESYSTEM'])

  @also_with_noderawfs
  def test_fs_readv(self):
    self.do_runf('fs/test_readv.c', 'done\n', cflags=['-sFORCE_FILESYSTEM'])

  @also_with_noderawfs
  def test_fs_writev(self):
    self.do_runf('fs/test_writev.c', 'done\n', cflags=['-sFORCE_FILESYSTEM'])

  def test_fs_readv_eagain(self):
    self.do_runf('fs/test_readv_eagain.c', 'done\n', cflags=['-sFORCE_FILESYSTEM'])

  def test_fs_writev_gather(self):
    self.do_runf('fs/test_writev_gather.c', 'done\n', cflags=['-sFORCE_FILESYSTEM'])

  def test_fs_64bit(self):
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')
    self.do_runf('fs/test_64bit.c', 'done\n')

  @crossplatform
  @with_all_fs
  def test_fs_stat_unnamed_file_descriptor(self):
    self.do_runf('fs/test_stat_unnamed_file_descriptor.c', 'done\n')

  @requires_node
  @crossplatform
  @with_all_fs
  def test_fs_symlink_resolution(self):
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')
    nodefs = '-DNODEFS' in self.cflags or '-DNODERAWFS' in self.cflags
    if nodefs and WINDOWS:
      self.skipTest('No symlinks on Windows')
    self.do_runf('fs/test_fs_symlink_resolution.c', 'done\n')

  @with_all_fs
  def test_fs_rename_on_existing(self):
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')
    self.do_runf('fs/test_fs_rename_on_existing.c', 'done\n')

  @also_with_nodefs_both
  @no_windows("stat ino values don't match on windows")
  @crossplatform
  @no_wasmfs('Assertion failed: "a_ino == sta.st" in test_fs_readdir_ino_matches_stat_ino.c, line 58. https://github.com/emscripten-core/emscripten/issues/25035')
  def test_fs_readdir_ino_matches_stat_ino(self):
    self.do_runf('fs/test_fs_readdir_ino_matches_stat_ino.c', 'done\n')

  @also_with_nodefs_both
  @crossplatform
  @no_windows('https://github.com/emscripten-core/emscripten/issues/8882')
  def test_fs_mkdir_dotdot(self):
    self.do_runf('fs/test_fs_mkdir_dotdot.c', 'done\n')

  def test_sigalrm(self):
    self.do_runf('test_sigalrm.c', 'Received alarm!')
    self.do_runf('test_sigalrm.c', 'Received alarm!', cflags=['-sEXIT_RUNTIME'])

  def test_signals(self):
    self.do_core_test('test_signals.c')

  @parameterized({
    'sigint': (EM_SIGINT, 128 + EM_SIGINT, True),
    'sigabrt': (EM_SIGABRT, 1, False),
  })
  @crossplatform
  def test_sigaction_default(self, signal, exit_code, assert_identical):
    self.set_setting('EXIT_RUNTIME')
    # TODO: re-enable assertions when https://github.com/emscripten-core/emscripten/issues/20315 is fixed.
    self.set_setting('ASSERTIONS', 0)
    self.do_core_test(
      test_file('test_sigaction_default.c'),
      args=[str(signal)],
      assert_identical=assert_identical,
      assert_returncode=exit_code,
    )

  @crossplatform
  @with_all_fs
  def test_unistd_access(self):
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')
    # On windows we have slightly different output because we use the same
    # level of permissions are not available. For example, on windows
    # it's not possible to have a file that is not readable, but writable.
    # We also report all files as executable since there is no x bit
    # recorded there.
    # See https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/chmod-wchmod?view=msvc-170#remarks
    if WINDOWS and '-DNODERAWFS' in self.cflags and not self.get_setting('WASMFS'):
      out_suffix = '.win'
    else:
      out_suffix = ''
    self.do_runf_out_file('unistd/access.c', out_suffix=out_suffix)

  def test_unistd_curdir(self):
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')
    self.do_runf_out_file('unistd/curdir.c')

  @also_with_noderawfs
  def test_unistd_close(self):
    self.maybe_closure()
    self.do_runf_out_file('unistd/close.c')

  def test_unistd_fsync_stdout(self):
    self.do_runf_out_file('unistd/fsync_stdout.c')

  @also_with_noderawfs
  def test_unistd_pipe(self):
    self.do_runf('unistd/pipe.c', 'done\n')

  @also_with_noderawfs
  def test_unistd_dup(self):
    self.do_runf_out_file('unistd/dup.c')

  @with_all_fs
  def test_unistd_truncate(self):
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')
    if WINDOWS or os.geteuid() == 0:
      self.skipTest('Root access invalidates this test by being able to write on readonly files')
    self.do_runf_out_file('unistd/truncate.c')

  @also_with_standalone_wasm()
  def test_unistd_sysconf(self):
    if self.is_wasm64():
      out_suffix = '64'
    else:
      out_suffix = ''
    self.do_runf_out_file('unistd/sysconf.c', out_suffix=out_suffix)

  @no_asan('ASan alters memory layout')
  def test_unistd_sysconf_phys_pages(self):
    filename = test_file('unistd/sysconf_phys_pages.c')
    if self.get_setting('ALLOW_MEMORY_GROWTH'):
      expected = (2 * 1024 * 1024 * 1024) // webassembly.WASM_PAGE_SIZE
    elif self.has_changed_setting('INITIAL_MEMORY'):
      if self.get_setting('INITIAL_MEMORY') == '4200mb':
        expected = (4200 * 1024 * 1024) // webassembly.WASM_PAGE_SIZE
      else:
        assert self.get_setting('INITIAL_MEMORY') == '2200mb'
        expected = (2200 * 1024 * 1024) // webassembly.WASM_PAGE_SIZE
    else:
      self.set_setting('INITIAL_MEMORY', '16mb')
      expected = 16 * 1024 * 1024 // webassembly.WASM_PAGE_SIZE
    self.do_runf(filename, str(expected) + ', errno: 0')

  @no_windows('https://github.com/emscripten-core/emscripten/issues/8882')
  @crossplatform
  @with_all_fs
  def test_unistd_unlink(self):
    # symlinks on node.js on non-linux behave differently (e.g. on Windows they require administrative privileges)
    # so skip testing those bits on that combination.
    if MACOS and any(arg in self.cflags for arg in ('-DNODEFS', '-DNODERAWFS')):
      self.skipTest('only tested on linux')
    if '-DNODEFS' in self.cflags:
      if WINDOWS:
        self.cflags += ['-DNO_SYMLINK=1']
      if self.get_setting('WASMFS'):
        self.skipTest('https://github.com/emscripten-core/emscripten/issues/18112')

    # Several differences/bugs on non-linux including https://github.com/nodejs/node/issues/18014
    # TODO: NODERAWFS in WasmFS
    if '-DNODERAWFS' in self.cflags and os.geteuid() == 0:
      # 0 if root user
      self.cflags += ['-DSKIP_ACCESS_TESTS']

    self.do_runf('unistd/unlink.c', 'done\n')

  @also_with_nodefs
  def test_unistd_links(self):
    nodefs = '-DNODEFS' in self.cflags
    if nodefs and WINDOWS:
      self.skipTest('Skipping NODEFS part of this test for test_unistd_links on Windows, since it would require administrative privileges.')
      # Also, other detected discrepancies if you do end up running this test on NODEFS:
      # test expects /, but Windows gives \ as path slashes.
      # Calling readlink() on a non-link gives error 22 EINVAL on Unix, but simply error 0 OK on Windows.

    if self.get_setting('WASMFS'):
      if nodefs:
        self.skipTest('TODO: wasmfs+node')
      self.cflags += ['-sFORCE_FILESYSTEM']

    self.do_runf_out_file('unistd/links.c')

  @also_with_noderawfs
  @no_windows('TODO: Fails on Windows due to an unknown reason.')
  @no_wasmfs('Assertion failed: "r == 3" in test_unistd_write_broken_link.c line 22. https://github.com/emscripten-core/emscripten/issues/25035')
  def test_unistd_write_broken_link(self):
    self.do_runf_out_file('unistd/test_unistd_write_broken_link.c')

  @no_windows('Skipping NODEFS test, since it would require administrative privileges.')
  @requires_node
  @no_wasmfs('Assertion failed: "fd" in symlink_on_nodefs.c line 62. https://github.com/emscripten-core/emscripten/issues/25035')
  def test_unistd_symlink_on_nodefs(self):
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')

    # Also, other detected discrepancies if you do end up running this test on NODEFS:
    # test expects /, but Windows gives \ as path slashes.
    # Calling readlink() on a non-link gives error 22 EINVAL on Unix, but simply error 0 OK on Windows.
    self.do_runf_out_file('unistd/symlink_on_nodefs.c', cflags=['-lnodefs.js'])

  @also_without_bigint
  @also_with_nodefs
  def test_unistd_io(self):
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')
    self.do_runf_out_file('unistd/io.c')

  @no_windows('https://github.com/emscripten-core/emscripten/issues/8882')
  @also_with_nodefs
  @no_wasmfs('fails in testing fdatasync, tcgetpgrp and pipe. https://github.com/emscripten-core/emscripten/issues/25035')
  def test_unistd_misc(self):
    if self.get_setting('STRICT'):
      self.set_setting('ALLOW_UNIMPLEMENTED_SYSCALLS')
    self.do_runf_out_file('unistd/misc.c', interleaved_output=False)

  @also_with_standalone_wasm()
  def test_posixtime(self):
    self.do_core_test('test_posixtime.c')

  def test_uname(self):
    if self.get_setting('STRICT'):
      self.cflags += ['-lstubs']
    self.do_core_test('test_uname.c', regex=True)

  def test_unary_literal(self):
    self.do_core_test('test_unary_literal.cpp')

  @crossplatform
  # Explicitly set LANG here since new versions of node expose
  # `navigator.languages` which emscripten will honor and we
  # want the test output to be consistent.
  @with_env_modify({'LANG': 'en_US.UTF-8'})
  def test_env(self):
    self.do_core_test('test_env.c', regex=True)

  @crossplatform
  # Explicitly set LANG here since new versions of node expose
  # `navigator.languages` which emscripten will honor and we
  # want the test output to be consistent.
  @with_env_modify({'LANG': 'en_US.UTF-8'})
  def test_environ(self):
    self.do_core_test('test_environ.c', regex=True)

  def test_systypes(self):
    self.do_core_test('test_systypes.c')

  def test_stddef(self):
    self.do_core_test('test_stddef.cpp')
    self.do_core_test('test_stddef.cpp', force_c=True)

  def test_getloadavg(self):
    if self.get_setting('STRICT'):
      self.cflags += ['-lstubs']
    self.do_core_test('test_getloadavg.c')

  def test_nl_types(self):
    self.do_core_test('test_nl_types.c')

  def test_799(self):
    self.do_runf('799.cpp', '''Set PORT family: 0, port: 3979
Get PORT family: 0
PORT: 3979
''')

  def test_ctype(self):
    self.do_core_test('test_ctype.c')

  def test_strcasecmp(self):
    self.do_core_test('test_strcasecmp.c')

  @also_with_pthreads
  def test_atomic(self):
    if '-pthread' in self.cflags and self.is_wasm2js():
      self.skipTest('atomics support missing')
    self.do_core_test('test_atomic.c')

  @also_with_pthreads
  def test_atomic_c11(self):
    if '-pthread' in self.cflags and self.is_wasm2js():
      self.skipTest('atomics support missing')
    # Re-use the out file from the C++ atomic test since they should have
    # identical output.
    self.do_runf('core/test_atomic_c11.c', read_file(test_file('core/test_atomic_cxx.out')), cflags=['-Wno-atomic-alignment'])

  @also_with_pthreads
  def test_atomic_cxx(self):
    if '-pthread' in self.cflags and self.is_wasm2js():
      self.skipTest('atomics support missing')
    self.do_core_test('test_atomic_cxx.cpp')

  def test_phiundef(self):
    self.do_core_test('test_phiundef.c')

  def test_netinet_in(self):
    self.do_runf_out_file('netinet/in.cpp')

  @needs_dylink
  @no_js_math('JS_MATH is not compatible with MAIN_MODULE=1')
  def test_main_module_static_align(self):
    if self.get_setting('ALLOW_MEMORY_GROWTH'):
      self.skipTest('no shared modules with memory growth')
    self.do_core_test('test_main_module_static_align.cpp', cflags=['-sMAIN_MODULE'])

  # libc++ tests

  def test_iostream_and_determinism(self):
    create_file('src.cpp', '''
      #include <iostream>

      int main() {
        std::cout << "Hello, world!" << std::endl << 77 << "." << std::endl;
        return 0;
      }
    ''')
    js_out = self.output_name('src')

    num = 5
    for i in range(num):
      print('(iteration %d)' % i)

      # add some timing nondeterminism here, not that we need it, but whatever
      time.sleep(random.random() / (10 * num))
      self.do_runf('src.cpp', 'Hello, world!\n77.\n')

      # Verify that this build is identical to the previous one
      if os.path.exists('src.js.previous'):
        self.assertBinaryEqual(js_out, 'src.js.previous')
      shutil.copy2(js_out, 'src.js.previous')

      # Same but for the wasm file.
      if self.is_wasm():
        if os.path.exists('src.wasm.previous'):
          self.assertBinaryEqual('src.wasm', 'src.wasm.previous')
        shutil.copy2('src.wasm', 'src.wasm.previous')

  def test_stdvec(self):
    self.do_core_test('test_stdvec.cpp')

  @requires_node
  def test_random_device(self):
    self.maybe_closure()
    self.do_core_test('test_random_device.cpp')

  def test_reinterpreted_ptrs(self):
    self.do_core_test('test_reinterpreted_ptrs.cpp')

  def test_jslib(self):
    create_file('main.c', '''
      #include <stdio.h>
      void printey();
      int calcey(int x, int y);

      int main() {
        printey();
        printf("*%d*\\n", calcey(10, 22));
        return 0;
      }
    ''')
    create_file('mylib1.js', '''
      addToLibrary({
        printey: () => out('hello from lib!')
      });
    ''')
    create_file('mylib2.js', '''
      addToLibrary({
        calcey: (x, y) => x + y
      });
    ''')

    self.do_runf('main.c', 'hello from lib!\n*32*\n', cflags=['--js-library=mylib1.js', '--js-library=mylib2.js'])

  @with_env_modify({'LC_ALL': 'latin-1', 'PYTHONUTF8': '0', 'PYTHONCOERCECLOCALE': '0'})
  @crossplatform
  @no_modularize_instance('uses MODULARIZE')
  @no_omit_asm_module_exports('MODULARIZE is not compatible with DECLARE_ASM_MODULE_EXPORTS=0')
  def test_unicode_js_library(self):
    # First verify that we have correct overridden the default python file encoding.
    # The follow program should fail, assuming the above LC_CTYPE + PYTHONUTF8
    # are having the desired effect.
    # This means that files open()'d by emscripten without an explicit encoding will
    # cause this test to fail, hopefully catching any places where we forget to do this.

    # On Windows when Unicode support is enabled, this test code does not fail.
    if not (WINDOWS and self.run_process(['chcp'], stdout=PIPE, shell=True).stdout.strip() == 'Active code page: 65001'):
      create_file('expect_fail.py', 'print(len(open(r"%s").read()))' % test_file('unicode_library.js'))
      self.assert_fail([PYTHON, 'expect_fail.py'], 'UnicodeDecodeError', expect_traceback=True)

    self.cflags += ['-sMODULARIZE', '--js-library', test_file('unicode_library.js'), '--extern-post-js', test_file('modularize_post_js.js'), '--post-js', test_file('unicode_postjs.js')]
    self.do_runf_out_file('test_unicode_js_library.c')

  def test_funcptr_import_type(self):
    self.do_core_test('test_funcptr_import_type.cpp', cflags=['--js-library', test_file('core/test_funcptr_import_type.js')])

  @no_asan('ASan does not work with EXPORT_ALL')
  def test_constglobalunion(self):
    self.set_setting('EXPORT_ALL')

    self.do_run(r'''
#include <stdio.h>

struct one_const {
  long a;
};

struct two_consts {
  long a;
  long b;
};

union some_consts {
  struct one_const one;
  struct two_consts two;
};

union some_consts my_consts = {{
  1
}};

struct one_const addr_of_my_consts = {
  (long)(&my_consts)
};

int main(void) {
  printf("%li\n", (long)!!addr_of_my_consts.a);
  return 0;
}
    ''', '1')

  ### 'Medium' tests

  def test_fannkuch(self):
    results = [(1, 0), (2, 1), (3, 2), (4, 4), (5, 7), (6, 10), (7, 16), (8, 22)]
    js_out = self.build('third_party/fannkuch.c')
    for i, j in results:
      print(i, j)
      self.do_run(js_out, 'Pfannkuchen(%d) = %d.' % (i, j), args=[str(i)], no_build=True)

  def test_raytrace(self):
    # TODO: Should we remove this test?
    self.skipTest('Relies on double value rounding, extremely sensitive')

    src = read_file(test_file('third_party/raytrace.cpp')).replace('double', 'float')
    output = read_file(test_file('raytrace.ppm'))
    self.do_run(src, output, args=['3', '16'])

  @parameterized({
    '': ('double',),
    'float': ('float',),
  })
  def test_fasta(self, float_type):
    results = [('1', '''GG\nctt\n\ntgagc\n'''),
               ('20', '''GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTT\ncttBtatcatatgctaKggNcataaaSatgtaaaDcDRtBggDtctttataattcBgtcg\n\ntacgtgtagcctagtgtttgtgttgcgttatagtctatttgtggacacagtatggtcaaa\n\ntgacgtcttttgatctgacggcgttaacaaagatactctg\n'''),
               ('50', '''GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGA\nTCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACAT\ncttBtatcatatgctaKggNcataaaSatgtaaaDcDRtBggDtctttataattcBgtcg\n\ntactDtDagcctatttSVHtHttKtgtHMaSattgWaHKHttttagacatWatgtRgaaa\n\nNtactMcSMtYtcMgRtacttctWBacgaa\n\nagatactctgggcaacacacatacttctctcatgttgtttcttcggacctttcataacct\n\nttcctggcacatggttagctgcacatcacaggattgtaagggtctagtggttcagtgagc\n\nggaatatcattcgtcggtggtgttaatctatctcggtgtagcttataaatgcatccgtaa\n\ngaatattatgtttatttgtcggtacgttcatggtagtggtgtcgccgatttagacgtaaa\n\nggcatgtatg\n''')]

    orig_src = read_file(test_file('third_party/fasta.cpp'))

    src = orig_src.replace('double', float_type)
    create_file('fasta.cpp', src)
    js_out = self.build('fasta.cpp')
    for arg, output in results:
      self.do_run(js_out, output, args=[arg], no_build=True)

  @needs_non_trapping_float_to_int
  def test_fasta_nontrapping(self):
    self.cflags += ['-mnontrapping-fptoint']
    self.test_fasta()

  def test_whets(self):
    self.do_runf('third_party/whets.c', 'Single Precision C Whetstone Benchmark')

  @no_asan('depends on the specifics of memory size, which for asan we are forced to increase')
  @no_lsan('depends on the specifics of memory size, which for lsan we are forced to increase')
  def test_dlmalloc_inline(self):
    # needed with typed arrays
    if not self.has_changed_setting('INITIAL_MEMORY'):
      self.set_setting('INITIAL_MEMORY', '128mb')

    src = read_file(path_from_root('system/lib/dlmalloc.c')) + '\n\n\n' + read_file(test_file('dlmalloc_test.c'))
    self.do_run(src, '*1,0*', args=['200', '1'], force_c=True)
    self.do_run(self.output_name('src'), '*400,0*', args=['400', '400'], force_c=True, no_build=True)

  @no_asan('depends on the specifics of memory size, which for asan we are forced to increase')
  @no_lsan('depends on the specifics of memory size, which for lsan we are forced to increase')
  def test_dlmalloc(self):
    if not self.has_changed_setting('INITIAL_MEMORY'):
      self.set_setting('INITIAL_MEMORY', '128mb')

    # Linked version
    self.do_runf('dlmalloc_test.c', '*1,0*', args=['200', '1'])
    self.do_run(self.output_name('dlmalloc_test'), '*400,0*', args=['400', '400'], no_build=True)

    out_js = self.output_name('dlmalloc_test')
    self.run_process([EMCC, test_file('dlmalloc_test.c'), '-sINITIAL_MEMORY=128MB', '-o', out_js] + self.get_cflags())

    self.do_run(out_js, '*1,0*', args=['200', '1'], no_build=True)
    self.do_run(out_js, '*400,0*', args=['400', '400'], no_build=True)

    # The same for new and all its variants
    src = read_file(test_file('new.cpp'))
    for new, delete in [
      ('malloc(100)', 'free'),
      ('new char[100]', 'delete[]'),
      ('new Structy', 'delete'),
      ('new int', 'delete'),
      ('new Structy[10]', 'delete[]'),
    ]:
      self.do_run(src.replace('{{{ NEW }}}', new).replace('{{{ DELETE }}}', delete), '*1,0*')

  # Tests that a large allocation should gracefully fail
  @no_asan('the memory size limit here is too small for asan')
  @no_lsan('the memory size limit here is too small for lsan')
  @no_highmem('output is sensitive to absolute data layout')
  def test_dlmalloc_large(self):
    self.cflags += ['-sABORTING_MALLOC=0', '-sALLOW_MEMORY_GROWTH=1', '-sMAXIMUM_MEMORY=128MB']
    self.do_runf('dlmalloc_test_large.c', '0 0 0 1')

  @no_asan('asan also changes malloc, and that ends up linking in new twice')
  @no_lsan('lsan also changes malloc, and that ends up linking in new twice')
  def test_dlmalloc_partial(self):
    # present part of the symbols of dlmalloc, not all
    src = read_file(test_file('new.cpp')).replace('{{{ NEW }}}', 'new int').replace('{{{ DELETE }}}', 'delete') + '''
#include <emscripten/console.h>
#include <new>

void* operator new(size_t size) {
  emscripten_console_log("new!");
  return malloc(size);
}
'''
    self.do_run(src, 'new!\n*1,0*')

  @no_asan('asan also changes malloc, and that ends up linking in new twice')
  @no_lsan('lsan also changes malloc, and that ends up linking in new twice')
  def test_dlmalloc_partial_2(self):
    if 'SAFE_HEAP' in str(self.cflags):
      self.skipTest('we do unsafe stuff here')
    # present part of the symbols of dlmalloc, not all. malloc is harder to link than new which is weak.
    self.do_core_test('test_dlmalloc_partial_2.c', assert_returncode=NON_ZERO)

  def test_libcxx(self):
    self.do_runf('core/test_libcxx_hash.cpp',
                 'june -> 30\nPrevious (in alphabetical order) is july\nNext (in alphabetical order) is march')

    self.do_run('''
      #include <set>
      #include <stdio.h>
      int main() {
        std::set<int> fetchOriginatorNums;
        fetchOriginatorNums.insert(171);
        printf("Hello, world!\\n");
        return 0;
      }
      ''', 'Hello, world!')

  def test_typeid(self):
    self.do_core_test('test_typeid.cpp')

  def test_static_variable(self):
    # EXIT_RUNTIME needed since the test uses atexit
    self.do_core_test('test_static_variable.cpp', cflags=['-sEXIT_RUNTIME'])

  def test_fakestat(self):
    self.do_core_test('test_fakestat.c')

  @also_with_standalone_wasm()
  def test_mmap_anon(self):
    # ASan needs more memory, but that is set up separately
    if '-fsanitize=address' not in self.cflags and not self.has_changed_setting('INITIAL_MEMORY'):
      self.set_setting('INITIAL_MEMORY', '128mb')
    self.do_core_test('test_mmap_anon.c')

  @requires_pthreads
  def test_mmap_anon_pthreads(self):
    # Same test with threading enabled so give is some basic sanity
    # checks of the locking on the internal data structures.
    if not self.has_changed_setting('INITIAL_MEMORY'):
      self.set_setting('INITIAL_MEMORY', '64mb')
    self.do_core_test('test_mmap_anon.c', cflags=['-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'])

  @no_lsan('Test code contains memory leaks')
  @also_with_asyncify_and_jspi
  def test_cubescript(self):
    # uses register keyword
    self.cflags += ['-std=c++03', '-Wno-dynamic-class-memaccess', '-I', test_file('third_party/cubescript')]
    self.maybe_closure()
    # Test code contains memory leaks
    if '-fsanitize=address' in self.cflags:
      self.cflags += ['--pre-js', test_file('asan-no-leak.js')]

    self.do_runf('third_party/cubescript/command.cpp', '*\nTemp is 33\n9\n5\nhello, everyone\n*')

  @needs_dylink
  def test_relocatable_void_function(self):
    self.do_core_test('test_relocatable_void_function.c', cflags=['-sMAIN_MODULE=2'])

  @wasm_simd
  @parameterized({
    '': ([],),
    'unsigned_char': (['-funsigned-char'],),
  })
  def test_wasm_intrinsics_simd(self, args):
    # These flags need to go first so that they come before any existing `-Wno-..` flags
    self.cflags.insert(0, '-Wpedantic')
    self.cflags.insert(0, '-Wall')
    self.do_runf('test_wasm_intrinsics_simd.c', 'Success!', cflags=args)

  # Tests invoking the NEON SIMD API via arm_neon.h header
  @wasm_simd
  def test_neon_wasm_simd(self):
    self.do_runf('neon/test_neon_wasm_simd.cpp', 'Success!', cflags=['-Wno-c++11-narrowing', '-mfpu=neon', '-msimd128', '-DSIMDE_NO_INLINE'])

  # Tests invoking the SIMD API via x86 SSE1 xmmintrin.h header (_mm_x() functions)
  @wasm_simd
  @crossplatform
  @requires_native_clang
  @requires_x64_cpu
  @no_safe_heap('has unaligned 64-bit operations in wasm')
  @no_ubsan('test contains UB')
  @parameterized({
    '': ([],),
    'nontrapping': (['-mnontrapping-fptoint'],),
  })
  @no_big_endian('SIMD support is currently not compatible with big endian')
  def test_sse1(self, args):
    src = test_file('sse/test_sse1.cpp')
    self.run_process([shared.CLANG_CXX, src, '-msse', '-o', 'test_sse1', '-D_CRT_SECURE_NO_WARNINGS=1'] + clang_native.get_clang_native_args(), stdout=PIPE)
    native_result = self.run_process('./test_sse1', stdout=PIPE).stdout

    self.maybe_closure()
    self.do_runf(src, native_result, cflags=['-I' + test_file('sse'), '-msse'] + args)

  # Tests invoking the SIMD API via x86 SSE2 emmintrin.h header (_mm_x() functions)
  @wasm_simd
  @requires_native_clang
  @requires_x64_cpu
  @no_safe_heap('has unaligned 64-bit operations in wasm')
  @is_slow_test
  @no_ubsan('https://github.com/emscripten-core/emscripten/issues/19688')
  @no_asan('local count too large')
  @parameterized({
    '': ([],),
    'nontrapping': (['-mnontrapping-fptoint'],),
  })
  @no_big_endian('SIMD support is currently not compatible with big endian')
  def test_sse2(self, args):
    src = test_file('sse/test_sse2.cpp')
    self.run_process([shared.CLANG_CXX, src, '-msse2', '-Wno-argument-outside-range', '-o', 'test_sse2', '-D_CRT_SECURE_NO_WARNINGS=1'] + clang_native.get_clang_native_args(), stdout=PIPE)
    native_result = self.run_process('./test_sse2', stdout=PIPE).stdout

    self.cflags += ['-I' + test_file('sse'), '-msse2', '-fno-inline-functions', '-Wno-argument-outside-range', '-sSTACK_SIZE=1MB'] + args
    self.maybe_closure()
    self.do_runf(src, native_result)

  # Tests invoking the SIMD API via x86 SSE3 pmmintrin.h header (_mm_x() functions)
  @wasm_simd
  @requires_native_clang
  @requires_x64_cpu
  @no_big_endian('SIMD support is currently not compatible with big endian')
  def test_sse3(self):
    src = test_file('sse/test_sse3.cpp')
    self.run_process([shared.CLANG_CXX, src, '-msse3', '-Wno-argument-outside-range', '-o', 'test_sse3', '-D_CRT_SECURE_NO_WARNINGS=1'] + clang_native.get_clang_native_args(), stdout=PIPE)
    native_result = self.run_process('./test_sse3', stdout=PIPE).stdout

    self.cflags += ['-I' + test_file('sse'), '-msse3', '-Wno-argument-outside-range']
    self.maybe_closure()
    self.do_runf(src, native_result)

  # Tests invoking the SIMD API via x86 SSSE3 tmmintrin.h header (_mm_x() functions)
  @wasm_simd
  @requires_native_clang
  @requires_x64_cpu
  @no_big_endian('SIMD support is currently not compatible with big endian')
  def test_ssse3(self):
    src = test_file('sse/test_ssse3.cpp')
    self.run_process([shared.CLANG_CXX, src, '-mssse3', '-Wno-argument-outside-range', '-o', 'test_ssse3', '-D_CRT_SECURE_NO_WARNINGS=1'] + clang_native.get_clang_native_args(), stdout=PIPE)
    native_result = self.run_process('./test_ssse3', stdout=PIPE).stdout

    self.cflags += ['-I' + test_file('sse'), '-mssse3', '-Wno-argument-outside-range']
    self.maybe_closure()
    self.do_runf(src, native_result)

  # Tests invoking the SIMD API via x86 SSE4.1 smmintrin.h header (_mm_x() functions)
  @no_ubsan('https://github.com/emscripten-core/emscripten/issues/19749')
  @wasm_simd
  @requires_native_clang
  @requires_x64_cpu
  @is_slow_test
  @no_big_endian('SIMD support is currently not compatible with big endian')
  def test_sse4_1(self):
    src = test_file('sse/test_sse4_1.cpp')
    # Run with inlining disabled to avoid slow LLVM behavior with lots of macro expanded loops inside a function body.
    self.run_process([shared.CLANG_CXX, src, '-msse4.1', '-fno-inline-functions', '-Wno-argument-outside-range', '-o', 'test_sse4_1', '-D_CRT_SECURE_NO_WARNINGS=1'] + clang_native.get_clang_native_args(), stdout=PIPE)
    native_result = self.run_process('./test_sse4_1', stdout=PIPE).stdout

    self.cflags += ['-I' + test_file('sse'), '-msse4.1', '-fno-inline-functions', '-Wno-argument-outside-range', '-sSTACK_SIZE=1MB']
    self.maybe_closure()
    self.do_runf(src, native_result)

  # Tests invoking the SIMD API via x86 SSE4.2 nmmintrin.h header (_mm_x() functions)
  @wasm_simd
  @requires_native_clang
  @requires_x64_cpu
  @parameterized({
    '': (False,),
    '2': (True,),
  })
  @no_big_endian('SIMD support is currently not compatible with big endian')
  def test_sse4(self, use_4_2):
    msse4 = '-msse4.2' if use_4_2 else '-msse4'
    src = test_file('sse/test_sse4_2.cpp')
    self.run_process([shared.CLANG_CXX, src, msse4, '-Wno-argument-outside-range', '-o', 'test_sse4_2', '-D_CRT_SECURE_NO_WARNINGS=1'] + clang_native.get_clang_native_args(), stdout=PIPE)
    native_result = self.run_process('./test_sse4_2', stdout=PIPE).stdout

    self.cflags += ['-I' + test_file('sse'), msse4, '-Wno-argument-outside-range']
    self.maybe_closure()
    self.do_runf(src, native_result)

  # Tests invoking the SIMD API via x86 AVX avxintrin.h header (_mm_x() functions)
  @wasm_simd
  @requires_native_clang
  @requires_x64_cpu
  @is_slow_test
  @no_asan('local count too large')
  @no_ubsan('local count too large')
  @parameterized({
    '': ([],),
    'nontrapping': (['-mnontrapping-fptoint'],),
  })
  @no_big_endian('SIMD support is currently not compatible with big endian')
  def test_avx(self, args):
    src = test_file('sse/test_avx.cpp')
    self.run_process([shared.CLANG_CXX, src, '-mavx', '-Wno-argument-outside-range', '-Wpedantic', '-o', 'test_avx', '-D_CRT_SECURE_NO_WARNINGS=1'] + clang_native.get_clang_native_args(), stdout=PIPE)
    native_result = self.run_process('./test_avx', stdout=PIPE).stdout

    self.cflags += ['-I' + test_file('sse'), '-mavx', '-fno-inline-functions', '-Wno-argument-outside-range', '-sSTACK_SIZE=1MB'] + args
    self.maybe_closure()
    self.do_runf(src, native_result)

  # Tests invoking the SIMD API via x86 AVX2 avx2intrin.h header (_mm_x()/_mm256_x() functions)
  @wasm_simd
  @requires_native_clang
  @requires_x64_cpu
  @is_slow_test
  @no_asan('local count too large')
  @no_ubsan('local count too large')
  @parameterized({
    '': ([],),
    'nontrapping': (['-mnontrapping-fptoint'],),
  })
  @no_big_endian('SIMD support is currently not compatible with big endian')
  def test_avx2(self, args):
    src = test_file('sse/test_avx2.cpp')
    self.run_process([shared.CLANG_CXX, src, '-mavx2', '-Wno-argument-outside-range', '-Wpedantic', '-o', 'test_avx2', '-D_CRT_SECURE_NO_WARNINGS=1'] + clang_native.get_clang_native_args(), stdout=PIPE)
    native_result = self.run_process('./test_avx2', stdout=PIPE).stdout

    self.cflags += ['-I' + test_file('sse'), '-mavx2', '-Wno-argument-outside-range', '-sSTACK_SIZE=1MB'] + args
    self.maybe_closure()
    self.do_runf(src, native_result)

  @wasm_simd
  @no_big_endian('SIMD support is currently not compatible with big endian')
  def test_fma(self):
    self.do_runf('sse/test_fma.cpp', 'All FMA tests PASSED',
                 cflags=['-mavx', '-mfma', '-sSTACK_SIZE=1MB'])

  # Exhaustive FMA test with relaxed SIMD — compared against native x86.
  # With -mrelaxed-simd on an x86 host, Wasm relaxed SIMD FMA lowers to a
  # hardware fused operation that matches native x86 FMA bit-for-bit. This
  # test is gated to x64 CPUs (@requires_x64_cpu) to guarantee that fusion.
  @wasm_relaxed_simd
  @requires_native_clang
  @requires_x64_cpu
  @is_slow_test
  @no_big_endian('SIMD support is currently not compatible with big endian')
  def test_fma_relaxed(self):
    src = test_file('sse/test_fma_relaxed.cpp')
    self.run_process([shared.CLANG_CXX, src, '-mfma', '-mavx', '-o', 'test_fma_relaxed', '-D_CRT_SECURE_NO_WARNINGS=1'] + clang_native.get_clang_native_args())
    native_result = self.run_process('./test_fma_relaxed', stdout=PIPE).stdout

    self.do_runf(src, native_result,
                 cflags=['-I' + test_file('sse'), '-mavx', '-mfma', '-sSTACK_SIZE=1MB'])

  @wasm_simd
  def test_sse_diagnostics(self):
    self.cflags.remove('-Werror')
    src = test_file('sse/test_sse_diagnostic.cpp')

    p = self.run_process(
      [shared.EMXX, src, '-msse', '-DWASM_SIMD_COMPAT_SLOW'] + self.get_cflags(),
      stderr=PIPE)
    self.assertContained('Instruction emulated via slow path.', p.stderr)

  @wasm_relaxed_simd
  def test_relaxed_simd_implies_simd128(self):
    # When using -msse, one has to also add -msimd128.
    # This test verifies passing -mrelaxed-simd also implies -msimd128.
    self.do_runf_out_file('sse/hello_sse.cpp', cflags=['-msse'])

  @no_asan('call stack exceeded on some versions of node')
  def test_gcc_unmangler(self):
    self.cflags += ['-I' + test_file('third_party/libiberty')]

    self.do_runf('third_party/libiberty/cp-demangle.c', '*d_demangle(char const*, int, unsigned int*)*', args=['_ZL10d_demanglePKciPj'])

  @no_asan('issues with freetype itself')
  @no_strict('autoconfiguring is not compatible with STRICT')
  @needs_make('configure script')
  @is_slow_test
  def test_freetype(self):
    # Not needed for js, but useful for debugging
    copy_asset('freetype/LiberationSansBold.ttf', 'font.ttf')
    ftlib = self.get_freetype_library()

    if self.get_setting('WASMFS'):
      self.cflags += ['-sFORCE_FILESYSTEM']

    self.add_pre_run("FS.createDataFile('/', 'font.ttf', %s, true, false, false);" % str(
      list(bytearray(read_binary(test_file('freetype/LiberationSansBold.ttf')))),
    ))

    # Main
    self.do_runf_out_file('freetype/main.c',
                                 args=['font.ttf', 'test!', '150', '120', '25'],
                                 libraries=ftlib,
                                 includes=[test_file('third_party/freetype/include')])

    # github issue 324
    print('[issue 324]')
    self.do_runf_out_file('freetype/main_2.c',
                                 args=['font.ttf', 'w', '32', '32', '25'],
                                 libraries=ftlib,
                                 includes=[test_file('third_party/freetype/include')])

    print('[issue 324 case 2]')
    self.do_runf_out_file('freetype/main_3.c',
                                 args=['font.ttf', 'W', '32', '32', '0'],
                                 libraries=ftlib,
                                 includes=[test_file('third_party/freetype/include')])

    print('[issue 324 case 3]')
    self.do_runf_out_file('freetype/main_3.c',
                                 out_suffix='_alt',
                                 args=['font.ttf', 'ea', '40', '32', '0'],
                                 libraries=ftlib,
                                 includes=[test_file('third_party/freetype/include')])

  @no_asan('local count too large for VMs')
  @no_ubsan('local count too large for VMs')
  @is_slow_test
  @also_with_wasmfs
  @also_with_pthreads
  def test_sqlite(self):
    if self.get_setting('STRICT'):
      self.cflags += ['-lstubs']
    self.do_runf_out_file('sqlite/test.c', cflags=['-sUSE_SQLITE3'])

  @needs_make('mingw32-make')
  @is_slow_test
  @parameterized({
    'cmake': (True,),
    'configure': (False,),
  })
  def test_zlib(self, use_cmake):
    if WINDOWS and not use_cmake:
      self.skipTest("Windows cannot run configure sh scripts")

    self.maybe_closure()
    if '-g' in self.cflags:
      self.cflags.append('-gsource-map') # more source maps coverage

    zlib = self.get_zlib_library(use_cmake)

    # example.c uses K&R style function declarations
    self.cflags += ['-Wno-deprecated-non-prototype', '-Wno-unused-command-line-argument']
    self.do_core_test('test_zlib.c', libraries=zlib, includes=[test_file('third_party/zlib')])

  @needs_make('make')
  @is_slow_test
  @no_ubsan('it seems that bullet contains UB')
  @parameterized({
    'cmake': (True,),
    'autoconf': (False,),
  })
  # Called thus so it runs late in the alphabetical cycle... it is long
  def test_bullet(self, use_cmake):
    if not use_cmake:
      if WINDOWS:
        self.skipTest('Windows cannot run configure sh scripts')
      if self.get_setting('STRICT'):
        self.skipTest('autoconfiguring is not compatible with STRICT')

    self.cflags += [
      '-Wno-c++11-narrowing',
      '-Wno-deprecated-register',
      '-Wno-writable-strings',
      '-Wno-shift-negative-value',
      '-Wno-format',
      '-Wno-bitfield-constant-conversion',
      '-Wno-int-to-void-pointer-cast',
      '-Wno-nontrivial-memaccess',
    ]

    # extra testing for ASSERTIONS == 2
    if use_cmake:
      self.set_setting('ASSERTIONS', 2)
      self.cflags.append('-Wno-unused-command-line-argument')

    self.do_runf('third_party/bullet/Demos/HelloWorld/HelloWorld.cpp',
                 [read_file(test_file('bullet/output.txt')), # different roundings
                  read_file(test_file('bullet/output2.txt')),
                  read_file(test_file('bullet/output3.txt')),
                  read_file(test_file('bullet/output4.txt'))],
                 libraries=self.get_bullet_library(use_cmake),
                 includes=[test_file('third_party/bullet/src')])

  @no_asan('issues with freetype itself')
  @no_ubsan('local count too large')
  @no_lsan('output differs')
  @needs_make('depends on freetype')
  @no_4gb('runs out of memory')
  @is_slow_test
  @crossplatform
  @no_wasmfs('depends on MEMFS which WASMFS does not have')
  @no_strict('autoconfiguring is not compatible with STRICT')
  @no_big_endian('SUPPORT_BIG_ENDIAN is not propagated')
  def test_poppler(self):
    # See https://github.com/emscripten-core/emscripten/issues/20757
    self.cflags.extend(['-Wno-deprecated-declarations', '-Wno-nontrivial-memaccess'])
    poppler = self.get_poppler_library()
    copy_asset('poppler/paper.pdf')

    create_file('pre.js', '''
    Module.preRun = () => {
      FS.createDataFile('/', 'paper.pdf', readBinary('paper.pdf'), true, false, false);
    };
    Module.postRun = () => {
      var FileData = Array.from(MEMFS.getFileDataAsTypedArray(FS.root.contents['filename-1.ppm']));
      out("Data: " + JSON.stringify(FileData));
    };
    ''')
    self.cflags += ['--pre-js', 'pre.js', '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$unSign', '-sINCOMING_MODULE_JS_API=preRun,postRun']

    ppm_data = str(list(read_binary(test_file('poppler/ref.ppm'))))
    self.do_run('', 'Data: ' + ppm_data.replace(' ', ''),
                libraries=poppler,
                args=['-scale-to', '512', 'paper.pdf', 'filename'])

  @needs_make('make')
  @is_slow_test
  def test_openjpeg(self):
    if self.get_setting('WASMFS'):
      self.set_setting('FORCE_FILESYSTEM')

    def line_splitter(data):
      out = ''
      counter = 0

      for ch in data:
        out += ch
        if ch == ' ' and counter > 60:
          out += '\n'
          counter = 0
        else:
          counter += 1

      return out

    original_j2k = test_file('openjpeg/syntensity_lobby_s.j2k')
    image_bytes = list(bytearray(read_binary(original_j2k)))
    create_file('pre.js', """
      Module.preRun = () => FS.createDataFile('/', 'image.j2k', %s, true, false, false);
      Module.postRun = () => {
        out('Data: ' + JSON.stringify(Array.from(FS.readFile('image.raw'))));
      };
      """ % line_splitter(str(image_bytes)))

    # ensure libpng is built so that openjpeg's configure step can detect it.
    # If we don't do this then we don't know what the state of the cache will be
    # and this test would different non-deterministic results based on, for example,
    # what other tests had previously run.
    builder_cmd = [EMBUILDER, 'build', 'libpng']
    if self.is_wasm64():
      builder_cmd.append('--wasm64')
      self.cflags.append('-Wno-pointer-to-int-cast')
    self.run_process(builder_cmd)
    lib = self.get_library('third_party/openjpeg',
                           ['codec/CMakeFiles/j2k_to_image.dir/index.c.o',
                            'codec/CMakeFiles/j2k_to_image.dir/convert.c.o',
                            'codec/CMakeFiles/j2k_to_image.dir/__/common/color.c.o',
                            'codec/CMakeFiles/j2k_to_image.dir/__/common/getopt.c.o',
                            'bin/libopenjpeg.a'],
                           configure=['cmake', '.', '-DBUILD_SHARED_LIBS=OFF'],
                           # configure_args=['--enable-tiff=no', '--enable-jp3d=no', '--enable-png=no'],
                           make_args=[]) # no -j 2, since parallel builds can fail

    # We use doubles in JS, so we get slightly different values than native code. So we
    # check our output by comparing the average pixel difference
    def image_compare(output):
      # Get the image generated by JS, from the JSON.stringify'd array
      m = re.search(r'\[[\d, -]*\]', output)

      self.assertIsNotNone(m, 'Failed to find proper image output in: ' + output)
      # Evaluate the output as a python array
      js_data = eval(m.group(0))

      js_data = [x if x >= 0 else 256 + x for x in js_data] # Our output may be signed, so unsign it
      # Get the correct output
      true_data = bytearray(read_binary(test_file('openjpeg/syntensity_lobby_s.raw')))

      # Compare them
      self.assertEqual(len(js_data), len(true_data))
      num = len(js_data)
      diff_total = js_total = true_total = 0
      for i in range(num):
        js_total += js_data[i]
        true_total += true_data[i]
        diff_total += abs(js_data[i] - true_data[i])
      js_mean = js_total / float(num)
      true_mean = true_total / float(num)
      diff_mean = diff_total / float(num)

      image_mean = 83.265
      # print '[image stats:', js_mean, image_mean, true_mean, diff_mean, num, ']'
      assert abs(js_mean - image_mean) < 0.01, [js_mean, image_mean]
      assert abs(true_mean - image_mean) < 0.01, [true_mean, image_mean]
      assert diff_mean < 0.01, diff_mean

    self.cflags += ['--pre-js', 'pre.js', '-sINCOMING_MODULE_JS_API=preRun,postRun']

    output = self.do_runf('third_party/openjpeg/codec/j2k_to_image.c',
                          'Successfully generated', # The real test for valid output is in image_compare
                          args='-i image.j2k -o image.raw'.split(),
                          cflags=['-sUSE_LIBPNG'],
                          libraries=lib,
                          includes=[test_file('third_party/openjpeg/libopenjpeg'),
                                    test_file('third_party/openjpeg/codec'),
                                    test_file('third_party/openjpeg/common'),
                                    Path(self.get_build_dir(), 'third_party/openjpeg')])
    image_compare(output)

  @also_with_standalone_wasm(impure=True)
  @no_asan('autodebug logging interferes with asan')
  @with_env_modify({'EMCC_AUTODEBUG': '1'})
  def test_autodebug_wasm(self):
    # failed to asynchronously prepare wasm: LinkError: WebAssembly.instantiate(): Import #13 module="env" function="get_v128": function import requires a callable
    if '-msimd128' in self.cflags or '-lllvmlibc' in self.cflags:
      self.skipTest('https://github.com/emscripten-core/emscripten/issues/25001')

    # Even though the test itself doesn't directly use reference types,
    # Binaryen's '--instrument-locals' will add their logging functions if
    # reference-types is enabled. So make sure this test passes when
    # reference-types feature is enabled as well.
    self.node_args += shared.node_reference_types_flags(get_nodejs())
    output = self.do_runf('core/test_autodebug.c', 'done\n', cflags=['-mreference-types'])
    # test that the program both works and also emits some of the logging
    # (but without the specific output, as it is logging the actual locals
    # used and so forth, which will change between opt modes and updates of
    # llvm etc.)
    for msg in ('log_execution', 'get_i32', 'set_i32', 'load_ptr', 'load_val', 'store_ptr', 'store_val'):
      self.assertIn(msg, output)

  ### Integration tests

  @crossplatform
  @no_modularize_instance('ccall is not compatible with MODULARIZE=instance')
  def test_ccall(self):
    self.set_setting('EXPORTED_RUNTIME_METHODS', ['ccall', 'cwrap', 'STACK_SIZE'])
    self.set_setting('WASM_ASYNC_COMPILATION', 0)
    create_file('post.js', '''
      out('*');
      var ret;
      ret = Module['ccall']('get_int', 'number'); out([typeof ret, ret].join(','));
      ret = ccall('get_float', 'number'); out([typeof ret, ret.toFixed(2)].join(','));
      ret = ccall('get_bool', 'boolean'); out([typeof ret, ret].join(','));
      ret = ccall('get_string', 'string'); out([typeof ret, ret].join(','));
      ret = ccall('print_int', null, ['number'], [12]); out(typeof ret);
      ret = ccall('print_float', null, ['number'], [14.56]); out(typeof ret);
      ret = ccall('print_bool', null, ['boolean'], [true]); out(typeof ret);
      ret = ccall('print_string', null, ['string'], ["cheez"]); out(typeof ret);
      ret = ccall('print_string', null, ['array'], [[97, 114, 114, 45, 97, 121, 0]]); out(typeof ret); // JS array
      ret = ccall('print_string', null, ['array'], [new Uint8Array([97, 114, 114, 45, 97, 121, 0])]); out(typeof ret); // typed array
      ret = ccall('multi', 'number', ['number', 'number', 'number', 'string'], [2, 1.4, 3, 'more']); out([typeof ret, ret].join(','));
      var p = ccall('malloc', 'pointer', ['number'], [4]);
      setValue(p, 650, 'i32');
      ret = ccall('pointer', 'pointer', ['pointer'], [p]); out([typeof ret, getValue(ret, 'i32')].join(','));
      out('*');
      // part 2: cwrap
      var noThirdParam = Module['cwrap']('get_int', 'number');
      out(noThirdParam());
      var multi = Module['cwrap']('multi', 'number', ['number', 'number', 'number', 'string']);
      out(multi(2, 1.4, 3, 'atr'));
      out(multi(8, 5.4, 4, 'bret'));
      out('*');
      // part 3: avoid stack explosion and check it's restored correctly
      for (var i = 0; i < STACK_SIZE/60; i++) {
        ccall('multi', 'number', ['number', 'number', 'number', 'string'], [0, 0, 0, '123456789012345678901234567890123456789012345678901234567890']);
      }
      out('stack is ok.');
      ccall('call_ccall_again', null);
      ''')
    self.cflags += ['--post-js', 'post.js', '-Wno-return-stack-address']

    self.set_setting('EXPORTED_FUNCTIONS', ['_get_int', '_get_float', '_get_bool', '_get_string', '_print_int', '_print_float', '_print_bool', '_print_string', '_multi', '_pointer', '_call_ccall_again', '_malloc'])
    self.maybe_closure()
    self.do_core_test('test_ccall.cpp')

  @no_modularize_instance('ccall is not compatible with MODULARIZE=instance')
  def test_ccall_cwrap_fast_path(self):
    self.set_setting('EXPORTED_RUNTIME_METHODS', ['ccall', 'cwrap'])
    self.set_setting('WASM_ASYNC_COMPILATION', 0)
    self.set_setting('ASSERTIONS', 0)
    create_file('post.js', '''
      var printBool = Module['cwrap']('print_bool', null, ['boolean']);
      out(Module['_print_bool'] === printBool); // the function should be the exact raw function in the module rather than a wrapped one
      ''')

    self.set_setting('EXPORTED_FUNCTIONS', ['_print_bool'])
    self.do_runf('core/test_ccall.cpp', 'true', cflags=['--post-js=post.js', '-Wno-return-stack-address'])

  @no_modularize_instance('ccall is not yet compatible with MODULARIZE=instance')
  def test_ccall_return_pointer(self):
    if self.get_setting('MINIMAL_RUNTIME'):
      self.skipTest('ccall is not available in MINIMAL_RUNTIME')
    self.do_core_test('test_ccall_return_pointer.c', cflags=['-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$ccall'])

  @no_modularize_instance('uses Module object directly')
  def test_EXPORTED_RUNTIME_METHODS(self):
    self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$dynCall', '$ASSERTIONS'])
    self.do_core_test('EXPORTED_RUNTIME_METHODS.c')
    # test dyncall (and other runtime methods) can be exported
    self.set_setting('EXPORTED_RUNTIME_METHODS', ['dynCall', 'addFunction', 'lengthBytesUTF8', 'getTempRet0', 'setTempRet0'])
    self.do_core_test('EXPORTED_RUNTIME_METHODS.c', cflags=['-DEXPORTED'])

  @also_with_minimal_runtime
  def test_dyncall_specific(self):
    if self.get_setting('WASM_BIGINT') != 0 and not self.is_wasm2js():
      # define DYNCALLS because this test does test calling them directly, and
      # in WASM_BIGINT mode we do not enable them by default (since we can do
      # more without them - we don't need to legalize)
      self.cflags += ['-sDYNCALLS', '-DWASM_BIGINT']
    cases = [
        ('DIRECT', []),
        ('DYNAMIC_SIG', ['-sDYNCALLS']),
      ]
    if self.get_setting('MINIMAL_RUNTIME') == 0:
      cases += [
        ('EXPORTED', []),
        ('EXPORTED_DYNAMIC_SIG', ['-sDYNCALLS', '-sEXPORTED_RUNTIME_METHODS=dynCall']),
        ('FROM_OUTSIDE', ['-sEXPORTED_RUNTIME_METHODS=dynCall_iiji']),
      ]

    for which, extra_args in cases:
      print(str(extra_args) + ' ' + which)
      self.do_core_test('test_dyncall_specific.c', cflags=['-D' + which] + extra_args)

  def test_dyncall_ptr_handling(self):
    self.do_core_test('test_dyncall_ptr_handling.c', cflags=['--js-library', test_file('core/test_dyncall_ptr_handling.js')])

  @parameterized({
    '': ([],),
    'legacy': (['-sDYNCALLS'],),
  })
  def test_dyncall_pointers(self, args):
    self.do_core_test('test_dyncall_pointers.c', cflags=args)

  @also_without_bigint
  @no_modularize_instance('uses Module object directly')
  def test_getValue_setValue(self):
    # these used to be exported, but no longer are by default
    def test(args=None, asserts=False):
      if asserts:
        out_suffix = '_assert'
      else:
        out_suffix = ''
        if self.is_wasm64():
          out_suffix += '64'
        if self.get_setting('WASM_BIGINT') == 0:
          out_suffix += '_nobigint'
      assert_returncode = 0 if not asserts else NON_ZERO
      self.do_core_test('test_getValue_setValue.cpp',
                        out_suffix=out_suffix,
                        assert_returncode=assert_returncode,
                        cflags=args)

    if self.get_setting('WASM_BIGINT') != 0:
      self.cflags += ['-DWASM_BIGINT']
      if self.is_wasm2js():
        self.skipTest('WASM_BIGINT is not compatible with WASM2JS')

    # see that direct usage (not on module) works. we don't export, but the use
    # keeps it alive through JSDCE
    test(args=['-DDIRECT'])

    # Test with ASSERTIONS=2 where we check the limits of value passed to setValue.
    self.set_setting('ASSERTIONS', 2)
    test(args=['-DDIRECT', '-DASSERTIONS_2'])

    # see that with assertions, we get a nice error message
    self.set_setting('EXPORTED_RUNTIME_METHODS', [])
    self.set_setting('ASSERTIONS')
    test(asserts=True)
    self.set_setting('ASSERTIONS', 0)

    # see that when we export them, things work on the module
    self.set_setting('EXPORTED_RUNTIME_METHODS', ['getValue', 'setValue'])
    test()

  @parameterized({
    '': ([],),
    'files': (['-DUSE_FILES'],),
  })
  @no_modularize_instance('uses Module object directly')
  @no_wasmfs('depends on MEMFS which WASMFS does not have')
  def test_FS_exports(self, extra_args):
    # these used to be exported, but no longer are by default
    def test(output_prefix='', args=None, assert_returncode=0):
      args += extra_args
      print(args)
      self.do_runf('core/FS_exports.cpp',
                   (read_file(test_file('core/FS_exports' + output_prefix + '.out')),
                    read_file(test_file('core/FS_exports' + output_prefix + '_2.out'))),
                   assert_returncode=assert_returncode, cflags=args)

    # see that direct usage (not on module) works. we don't export, but the use
    # keeps it alive through JSDCE
    test(args=['-DDIRECT', '-sFORCE_FILESYSTEM'])
    # see that with assertions, we get a nice error message
    self.set_setting('EXPORTED_RUNTIME_METHODS', [])
    self.set_setting('ASSERTIONS')
    test('_assert', args=[], assert_returncode=NON_ZERO)
    self.set_setting('ASSERTIONS', 0)
    # see that when we export them, things work on the module
    self.set_setting('EXPORTED_RUNTIME_METHODS', ['FS_createDataFile'])
    test(args=['-sFORCE_FILESYSTEM'])

  def test_linker_response_file(self):
    objfile = 'response_file.o'
    out_js = self.output_name('response_file')
    self.run_process([EMCC, '-c', test_file('hello_world.cpp'), '-o', objfile] + self.get_cflags(compile_only=True))
    # This should expand into -Wl,--start-group <objfile> -Wl,--end-group
    response_data = '--start-group ' + objfile + ' --end-group'
    create_file('rsp_file', response_data.replace('\\', '\\\\'))
    self.run_process([EMCC, "-Wl,@rsp_file", '-o', out_js] + self.get_cflags())
    self.do_run(out_js, 'Hello, world!', no_build=True)

  @no_modularize_instance('uses Module object directly')
  def test_exported_response(self):
    src = r'''
      #include <stdio.h>
      #include <stdlib.h>
      #include <emscripten.h>

      extern "C" {
        int other_function() { return 5; }
      }

      int main() {
        int x = EM_ASM_INT({ return Module._other_function() });
        emscripten_run_script_string(""); // Add a reference to a symbol that exists in src/deps_info.json to uncover issue #2836 in the test suite.
        printf("waka %d!\n", x);
        return 0;
      }
    '''
    create_file('exps', '_main\n_other_function\n')

    self.set_setting('EXPORTED_FUNCTIONS', '@exps')
    self.do_run(src, '''waka 5!''')
    assert 'other_function' in read_file('src.js')

  @no_modularize_instance('uses Module object directly')
  def test_large_exported_response(self):
    src = r'''
      #include <stdio.h>
      #include <stdlib.h>
      #include <emscripten.h>

      extern "C" {
      '''

    rsp_file_lines = []
    num_exports = 5000
    count = 0
    while count < num_exports:
      src += 'int exported_func_from_response_file_%d () { return %d;}\n' % (count, count)
      rsp_file_lines.append('_exported_func_from_response_file_%d' % count)
      count += 1

    src += r'''
      }

      int main() {
        int x = EM_ASM_INT({ return Module._exported_func_from_response_file_4999() });
        // Add a reference to a symbol that exists in src/deps_info.json to uncover
        // issue #2836 in the test suite.
        emscripten_run_script_string("");
        printf("waka %d!\n", x);
        return 0;
      }
    '''

    rsp_file_lines.append('_main')
    create_file('large_exported_response.json', '\n'.join(rsp_file_lines) + '\n')

    self.set_setting('EXPORTED_FUNCTIONS', '@large_exported_response.json')
    self.do_run(src, 'waka 4999!')

  def test_emulate_function_pointer_casts(self):
    # Forcibly disable EXIT_RUNTIME due to:
    # https://github.com/emscripten-core/emscripten/issues/15081
    self.set_setting('EXIT_RUNTIME', 0)
    self.set_setting('EMULATE_FUNCTION_POINTER_CASTS')
    self.do_core_test('test_emulate_function_pointer_casts.cpp', cflags=['-Wno-deprecated'])

  def test_emulate_function_pointer_casts_directize(self):
    # Regression test: directize (inside -O2) must not replace type-mismatched
    # call_indirect with unreachable when fpcast-emu will fix them.
    # https://github.com/WebAssembly/binaryen/pull/8475
    self.do_core_test('test_emulate_function_pointer_casts_directize.c', cflags=['-sEMULATE_FUNCTION_POINTER_CASTS'])

  @no_wasm2js('TODO: nicely printed names in wasm2js')
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26197')
  @parameterized({
    'normal': ([],),
    'noexcept': (['-fno-exceptions'],),
  })
  def test_demangle_stacks(self, extra_args):
    self.cflags += extra_args
    self.set_setting('ASSERTIONS')
    # disable aggressive inlining in binaryen
    self.set_setting('BINARYEN_EXTRA_PASSES', '--one-caller-inline-max-function-size=1')
    # ensure function names are preserved
    self.cflags += ['--profiling-funcs']
    self.do_core_test('test_demangle_stacks.cpp', assert_returncode=NON_ZERO)

    # there should be a name section in the file
    with webassembly.Module('test_demangle_stacks.wasm') as m:
      self.assertTrue(m.has_name_section())

    print('without assertions, the stack is not printed, but a message suggesting assertions is')
    self.set_setting('ASSERTIONS', 0)
    self.do_core_test('test_demangle_stacks_noassert.cpp', assert_returncode=NON_ZERO)

  def test_demangle_stacks_symbol_map(self):
    # disable aggressive inlining in binaryen
    self.set_setting('BINARYEN_EXTRA_PASSES', '--one-caller-inline-max-function-size=1')
    self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', '$jsStackTrace')

    self.set_setting('ENVIRONMENT', 'node,shell')
    if '-O' not in str(self.cflags) or '-O0' in self.cflags or '-O1' in self.cflags or '-g' in self.cflags:
      self.skipTest("without opts, we don't emit a symbol map")
    self.do_runf('core/test_demangle_stacks.cpp', 'Aborted', assert_returncode=NON_ZERO, cflags=['--emit-symbol-map'])
    # make sure the shortened name is the right one
    full_aborter = None
    short_aborter = None
    for line in read_file('test_demangle_stacks.js.symbols').splitlines():
      if ':' not in line:
        continue
      # split by the first ':' (wasm backend demangling may include more :'s later on)
      short, full = line.split(':', 1)
      if 'Aborter' in full:
        short_aborter = short
        full_aborter = full
    self.assertIsNotNone(full_aborter)
    self.assertIsNotNone(short_aborter)
    print('full:', full_aborter, 'short:', short_aborter)
    if config.SPIDERMONKEY_ENGINE:
      output = self.run_js('test_demangle_stacks.js', engine=config.SPIDERMONKEY_ENGINE, assert_returncode=NON_ZERO)
      # we may see the full one, if -g, or the short one if not
      if ' ' + short_aborter + ' ' not in output and ' ' + full_aborter + ' ' not in output:
        # stack traces may also be ' name ' or 'name@' etc
        if '\n' + short_aborter + ' ' not in output and '\n' + full_aborter + ' ' not in output and 'wasm-function[' + short_aborter + ']' not in output:
          if '\n' + short_aborter + '@' not in output and '\n' + full_aborter + '@' not in output:
            self.assertContained(' ' + short_aborter + ' ' + '\n' + ' ' + full_aborter + ' ', output)

  @no_safe_heap('tracing from sbrk into JS leads to an infinite loop')
  def test_tracing(self):
    self.do_core_test('test_tracing.c', cflags=['--tracing'])

  @no_wasm2js('eval_ctors not supported yet')
  @also_with_standalone_wasm()
  def test_eval_ctors(self):
    if '-O2' not in str(self.cflags) or '-O1' in str(self.cflags):
      self.skipTest('need opts')

    print('leave printf in ctor')
    self.set_setting('EVAL_CTORS')
    self.do_run(r'''
      #include <stdio.h>
      struct C {
        C() { printf("constructing!\n"); } // don't remove this!
      };
      C c;
      int main() {}
    ''', "constructing!\n")

    def do_test(test, level=1, prefix='src'):
      def get_code_size():
        if self.is_wasm():
          # this also includes the memory, but it is close enough for our
          # purposes
          return self.measure_wasm_code_lines(prefix + '.wasm')
        else:
          return os.path.getsize(prefix + '.js')

      self.set_setting('EVAL_CTORS', level)
      test()
      ec_code_size = get_code_size()
      self.clear_setting('EVAL_CTORS')
      test()
      code_size = get_code_size()
      print('code:', code_size, '=>', ec_code_size)
      self.assertLess(ec_code_size, code_size)

    print('remove ctor of just assigns to memory')

    def test1():
      self.do_run(r'''
        #include <stdio.h>
        struct C {
          int x;
          C() {
            volatile int y = 10;
            y++;
            x = y;
          }
        };
        C c;
        int main() {
          printf("x: %d\n", c.x);
        }
      ''', "x: 11\n")

    do_test(test1)

    print('libcxx - remove 2 ctors from iostream code')

    def test2():
      self.do_runf('hello_libcxx.cpp', 'Hello, world!')

    # in standalone more there is more usage of WASI APIs, which mode 2 is
    # needed to avoid in order to fully optimize, so do not test mode 1 in
    # that mode.
    if not self.get_setting('STANDALONE_WASM'):
      do_test(test2, level=1, prefix='hello_libcxx')

    do_test(test2, level=2, prefix='hello_libcxx')

  @parameterized({
    '': (['-lembind'],),
    'flag': (['--bind'],),
    'legacy': (['--bind', '-sLEGACY_VM_SUPPORT'],),
    'no_dynamic': (['--bind', '-sDYNAMIC_EXECUTION=0', '-sLEGACY_VM_SUPPORT'],),
  })
  def test_embind_val_basics(self, args):
    if '-sLEGACY_VM_SUPPORT' in args:
      if self.get_setting('MODULARIZE') == 'instance' or self.get_setting('WASM_ESM_INTEGRATION'):
        self.skipTest('LEGACY_VM_SUPPORT is not compatible with EXPORT_ES6')
      if self.is_wasm64():
        self.skipTest('LEGACY_VM_SUPPORT is not compatible with wasm64')
    self.maybe_closure()
    self.do_runf_out_file('embind/test_embind_val_basics.cpp', cflags=args)

  @requires_pthreads
  def test_embind_basics(self):
    self.maybe_closure()
    self.cflags += [
      '-lembind', '--post-js', 'post.js',
      # for extra coverage, test using pthreads
      '-pthread', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME',
    ]
    create_file('post.js', '''
      function printLerp() {
        out('lerp ' + Module['lerp'](100, 200, 66) + '.');
      }
    ''')
    create_file('test.cpp', r'''
      #include <stdio.h>
      #include <emscripten.h>
      #include <emscripten/bind.h>
      #include <emscripten/console.h>
      using namespace emscripten;
      int lerp(int a, int b, int t) {
        return (100 - t) * a + t * b;
      }
      EMSCRIPTEN_BINDINGS(my_module) {
        emscripten_err("test bindings");
        function("lerp", &lerp);
      }
      int main(int argc, char **argv) {
        EM_ASM(printLerp());
        return 0;
      }
    ''')
    self.do_runf('test.cpp', 'lerp 166')

  def test_embind_unbound_types(self):
    self.cflags += ['-lembind', '--post-js', 'post.js']
    create_file('post.js', '''
      function ready() {
        try {
          Module['compute'](new Uint8Array([1,2,3]));
        } catch(e) {
          out(e);
        }
      }
    ''')
    create_file('test.cpp', r'''
      #include <emscripten.h>
      #include <emscripten/bind.h>
      using namespace emscripten;
      int compute(int array[]) {
          return 0;
      }
      EMSCRIPTEN_BINDINGS(my_module) {
          function("compute", &compute, allow_raw_pointers());
      }
      int main(int argc, char **argv) {
          EM_ASM(ready());
          return 0;
      }
    ''')
    self.do_runf('test.cpp', '(error|UnboundTypeError): Cannot call compute due to unbound types: Pi', regex=True)

  @no_big_endian("Accessing the array directly is not available on big endian system")
  def test_embind_memory_view(self):
    create_file('post.js', '''
      function printFirstElement() {
        out(Module['getBufferView']()[0]);
      }
    ''')
    create_file('test.cpp', r'''
      #include <emscripten.h>
      #include <emscripten/bind.h>
      #include <emscripten/val.h>
      #include <stdio.h>
      using namespace emscripten;

      const size_t kBufferSize = 1024;
      double buffer[kBufferSize];
      val getBufferView(void) {
          val v = val(typed_memory_view(kBufferSize, buffer));
          return v;
      }
      EMSCRIPTEN_BINDINGS(my_module) {
          function("getBufferView", &getBufferView);
      }

      int main(int argc, char **argv) {
        buffer[0] = 107;
        EM_ASM(printFirstElement());
        return 0;
      }
    ''')
    self.do_runf('test.cpp', '107', cflags=['-lembind', '--post-js', 'post.js'])

  def test_embind_memory_view_be(self):
    create_file('post.js', '''
      function printFirstElement() {
        const b = Module['getBufferView']();
        out(new DataView(b.buffer, b.byteOffset).getFloat64(0, true));
      }
    ''')
    create_file('test.cpp', r'''
      #include <emscripten.h>
      #include <emscripten/bind.h>
      #include <emscripten/val.h>
      #include <stdio.h>
      using namespace emscripten;

      const size_t kBufferSize = 1024;
      double buffer[kBufferSize];
      val getBufferView(void) {
          val v = val(typed_memory_view(kBufferSize, buffer));
          return v;
      }
      EMSCRIPTEN_BINDINGS(my_module) {
          function("getBufferView", &getBufferView);
      }

      int main(int argc, char **argv) {
        buffer[0] = 107;
        EM_ASM(printFirstElement());
        return 0;
      }
    ''')
    self.do_runf('test.cpp', '107', cflags=['-lembind', '--post-js', 'post.js'])

  def test_embind_inheritance(self):
    self.do_core_test('test_embind_inheritance.cpp', cflags=['-lembind'])

  def test_embind_custom_marshal(self):
    self.cflags += ['-lembind', '--pre-js', test_file('embind/test_custom_marshal.js')]
    self.do_runf_out_file('embind/test_custom_marshal.cpp', assert_identical=True)

  def test_embind_float_constants(self):
    self.do_runf_out_file('embind/test_float_constants.cpp', cflags=['-lembind'])

  def test_embind_negative_constants(self):
    self.do_runf_out_file('embind/test_negative_constants.cpp', cflags=['-lembind'])

  @also_without_bigint
  def test_embind_unsigned(self):
    self.do_runf_out_file('embind/test_unsigned.cpp', cflags=['-lembind'])

  def test_embind_val(self):
    self.do_runf_out_file('embind/test_val.cpp', cflags=['-lembind'])

  @no_big_endian("Incompatible with SUPPORT_BIG_ENDIAN")
  def test_embind_val_read_pointer(self):
    self.do_runf('embind/test_val_read_pointer.cpp', cflags=['-lembind'])

  def test_embind_val_assignment(self):
    self.assert_fail([EMCC, test_file('embind/test_val_assignment.cpp'), '-lembind', '-c'], 'candidate function not viable: expects an lvalue for object argument')

  @requires_pthreads
  def test_embind_val_cross_thread(self):
    create_file('test_embind_val_cross_thread.cpp', r'''
      #include <emscripten.h>
      #include <emscripten/val.h>
      #include <thread>
      #include <stdio.h>

      using emscripten::val;

      int main(int argc, char **argv) {
        // Store a value handle from the main thread.
        val value(0);
        std::thread([&] {
          // Set to a value handle from a different thread.
          value = val(1);
        }).join();
        // Try to access the stored handle from the main thread.
        // Without the check (if compiled with -DNDEBUG) this will incorrectly
        // print "0" instead of "1" since the handle with the same ID
        // resolves to different values on different threads.
        printf("%d\n", value.as<int>());
      }
    ''')
    self.do_runf('test_embind_val_cross_thread.cpp', 'val accessed from wrong thread', assert_returncode=NON_ZERO, cflags=['--bind'])

  @requires_pthreads
  def test_embind_val_cross_thread_deleted(self):
    create_file('test_embind_val_cross_thread.cpp', r'''
      #include <emscripten.h>
      #include <emscripten/val.h>
      #include <thread>
      #include <stdio.h>
      #include <optional>

      using emscripten::val;

      int main(int argc, char **argv) {
        // Create a storage for value handles on the main thread.
        std::optional<val> opt_value;
        std::thread([&] {
          // Set to a value handle from a different thread.
          val& value = opt_value.emplace(1);
          // Move out from the optional storage so that we free the value on the same thread.
          val moved_out = std::move(value);
        }).join();
        // Now std::optional is initialized but with a deleted value handle.
        // There should be no cross-thread error here when it tries to free that value,
        // because the value has already been deleted on the correct thread.
      }
    ''')
    self.do_runf('test_embind_val_cross_thread.cpp', cflags=['--bind'])

  def test_embind_val_coro(self):
    create_file('pre.js', r'''Module.onRuntimeInitialized = () => {
      Module.asyncCoro().then(console.log);
    }''')
    self.cflags += ['-std=c++20', '--bind', '--pre-js=pre.js', '-sINCOMING_MODULE_JS_API=onRuntimeInitialized', '--no-entry']
    self.do_runf('embind/test_val_coro.cpp', '34\n')

  def test_embind_val_coro_propagate_cpp_exception(self):
    self.set_setting('EXCEPTION_STACK_TRACES') # For err.stack
    create_file('pre.js', r'''Module.onRuntimeInitialized = () => {
      Module.throwingCoro().then(
        console.log,
        err => console.error(`rejected with: ${err.stack}`)
      );
    }''')
    self.cflags += ['-std=c++20', '--bind', '--pre-js=pre.js', '-fexceptions', '-sINCOMING_MODULE_JS_API=onRuntimeInitialized', '--no-entry']
    self.do_runf('embind/test_val_coro.cpp', 'rejected with: std::runtime_error: bang from throwingCoro!\n')

  @parameterized({
    'emscripten_eh': (['-fexceptions'],),
    'disable_catching': ([],), # Use defaults: DISABLE_EXCEPTION_CATCHING, NO_DISABLE_EXCEPTION_THROWING
    'no_exceptions': (['-fno-exceptions'],),
  })
  def test_embind_val_coro_propagate_js_error(self, extra_flags):
    create_file('pre.js', r'''Module.onRuntimeInitialized = () => {
      Module.failingPromise().then(
        console.log,
        err => console.error(`rejected with: ${err.message}`)
      );
    }''')
    self.cflags += ['-std=c++20', '--bind', '--pre-js=pre.js', *extra_flags, '-sINCOMING_MODULE_JS_API=onRuntimeInitialized', '--no-entry']
    self.do_runf('embind/test_val_coro_noexcept.cpp', 'rejected with: bang from JS promise!\n')

  @parameterized({
    'emscripten_eh': (['-fexceptions'],),
    'wasm_eh': (['-fwasm-exceptions'],),
  })
  def test_embind_val_coro_catch_cpp_exception(self, extra_flags):
    if self.is_wasm2js() and '-fwasm-exceptions' in extra_flags:
      self.skipTest('wasm2js does not support WASM exceptions')
    self.set_setting('EXCEPTION_STACK_TRACES') # For debugging
    create_file('pre.js', r'''Module.onRuntimeInitialized = () => {
      Module.catchCppExceptionPromise().then(console.log);
    }''')
    self.cflags += ['-std=c++20', '--bind', '--pre-js=pre.js', *extra_flags, '-sINCOMING_MODULE_JS_API=onRuntimeInitialized', '--no-entry']
    self.do_runf('embind/test_val_coro.cpp', 'successfully caught!\n')

  def test_embind_val_coro_await_in_non_val_coro(self):
    create_file('pre.js', r'''Module.onRuntimeInitialized = () => {
      Module.awaitInNonValCoro();
    }''')
    self.cflags += ['-std=c++20', '--bind', '--pre-js=pre.js', '-sINCOMING_MODULE_JS_API=onRuntimeInitialized', '--no-entry']
    self.do_runf('embind/test_val_coro.cpp', '42\n')

  def test_embind_dynamic_initialization(self):
    self.cflags += ['-lembind']
    self.do_runf_out_file('embind/test_dynamic_initialization.cpp')

  @no_wasm2js('wasm_bigint')
  @parameterized({
    '': (False,),
    'safe_heap': (True,),
  })
  @no_big_endian("Calling TypedArray.set directly is not available on big endian system")
  def test_embind_i64_val(self, safe_heap):
    if safe_heap and '-fsanitize=address' in self.cflags:
      self.skipTest('asan does not work with SAFE_HEAP')
    self.set_setting('SAFE_HEAP', safe_heap)
    out_suffix = '64' if self.is_wasm64() else ''
    self.do_runf_out_file('embind/test_i64_val.cpp', assert_identical=True, out_suffix=out_suffix, cflags=['-lembind'])

  @no_wasm2js('wasm_bigint')
  def test_embind_i64_binding(self):
    self.cflags += ['-lembind', '--js-library', test_file('embind/test_i64_binding.js')]
    self.do_runf_out_file('embind/test_i64_binding.cpp', assert_identical=True)

  def test_embind_no_rtti(self):
    create_file('main.cpp', r'''
      #include <emscripten.h>
      #include <emscripten/bind.h>
      #include <emscripten/val.h>
      #include <stdio.h>

      EM_JS(void, calltest, (), {
        out("dotest returned: " + Module["dotest"]());
      });

      int main(int argc, char** argv){
        printf("418\n");
        calltest();
        return 0;
      }

      int test() {
        return 42;
      }

      EMSCRIPTEN_BINDINGS(my_module) {
        emscripten::function("dotest", &test);
      }
    ''')
    self.cflags += ['-lembind', '-fno-rtti', '-DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0']
    self.do_runf('main.cpp', '418\ndotest returned: 42\n')

  @parameterized({
    '': ([],),
    'no_rtti': (['-fno-rtti', '-DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0'],),
  })
  def test_embind_polymorphic_class(self, args):
    self.do_core_test('test_embind_polymorphic_class_no_rtti.cpp', cflags=args + ['-lembind'])

  def test_embind_no_rtti_followed_by_rtti(self):
    src = r'''
      #include <emscripten.h>
      #include <emscripten/bind.h>
      #include <emscripten/val.h>
      #include <stdio.h>

      EM_JS(void, calltest, (), {
        out("dotest returned: " + Module["dotest"]());
      });

      int main(int argc, char** argv){
        printf("418\n");
        calltest();
        return 0;
      }

      int test() {
        return 42;
      }

      EMSCRIPTEN_BINDINGS(my_module) {
        emscripten::function("dotest", &test);
      }
    '''
    self.do_run(src, '418\ndotest returned: 42\n', cflags=['-lembind', '-fno-rtti', '-frtti'])

  @requires_wasm_workers
  def test_embind_wasm_workers(self):
    self.do_runf_out_file('embind/test_embind_wasm_workers.cpp', cflags=['-lembind', '-sWASM_WORKERS'])

  @with_all_eh_sjlj
  def test_embind_throw_cpp_exception(self):
    self.do_runf_out_file('embind/test_embind_throw_cpp_exception.cpp', cflags=['-lembind', '-std=c++20'])

  @with_all_eh_sjlj
  def test_embind_throw_val_uncaught_and_refcount(self):
    self.do_runf_out_file('embind/test_embind_throw_val_uncaught_and_refcount.cpp', cflags=['-lembind', '-std=c++20'])

  @parameterized({
    '': ('DEFAULT', False),
    'all': ('ALL', False),
    'fast': ('FAST', False),
    'all_growth': ('ALL', True),
  })
  @no_modularize_instance('uses Module global')
  @no_strict('TODO: Fails in -sSTRICT mode due to an unknown reason.')
  def test_webidl(self, mode, allow_memory_growth):
    self.set_setting('WASM_ASYNC_COMPILATION', 0)
    if self.maybe_closure():
      # avoid closure minified names competing with our test code in the global name space
      self.set_setting('MODULARIZE')
      self.set_setting('EXPORT_NAME', 'createModule')
    else:
      self.set_setting('WASM_ASYNC_COMPILATION', 0)

    # Force IDL checks mode
    if self.is_wasm64():
      args = ['--wasm64']
    else:
      args = []
    with env_modify({'IDL_CHECKS': mode}):
      self.run_process([WEBIDL_BINDER, test_file('webidl/test.idl'), 'glue'] + args)
    self.assertExists('glue.cpp')
    self.assertExists('glue.js')

    post_js = '\n\n'
    if self.get_setting('MODULARIZE'):
      post_js += 'var TheModule = createModule();\n'
    else:
      post_js += 'var TheModule = Module;\n'
    post_js += '\n\n'
    if allow_memory_growth:
      post_js += "var isMemoryGrowthAllowed = true;\n"
    else:
      post_js += "var isMemoryGrowthAllowed = false;\n"
    post_js += read_file(test_file('webidl/post.js'))
    post_js += '\n\n'
    create_file('extern-post.js', post_js)

    # Export things on "TheModule". This matches the typical use pattern of
    # the bound library being used as Box2D.* or Ammo.*, and we cannot rely
    # on "Module" being always present (closure may remove it).
    self.cflags += ['-sEXPORTED_FUNCTIONS=_malloc,_free', '-sEXPORTED_RUNTIME_METHODS=HEAP8,stringToUTF8', '--post-js=glue.js', '--extern-post-js=extern-post.js']
    if mode == 'ALL':
      self.cflags += ['-sASSERTIONS']
    if allow_memory_growth:
      self.set_setting('ALLOW_MEMORY_GROWTH')
      if self.get_setting('INITIAL_MEMORY') == '4200mb':
        self.set_setting('MAXIMUM_MEMORY', '4300mb')

    self.do_runf_out_file(test_file('webidl/test.cpp'), out_suffix='_' + mode, includes=['.'])

  # Test that we can perform fully-synchronous initialization when combining
  # WASM_ASYNC_COMPILATION=0 + PTHREAD_POOL_DELAY_LOAD=1.  Also checks that
  # PTHREAD_POOL_DELAY_LOAD=1 adds a pthreadPoolReady promise that users
  # can wait on for pthread initialization.
  @requires_pthreads
  @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with WASM_ASYNC_COMPILATION=0')
  def test_embind_sync_if_pthread_delayed(self):
    self.set_setting('WASM_ASYNC_COMPILATION', 0)
    self.set_setting('PTHREAD_POOL_DELAY_LOAD', 1)
    self.set_setting('PTHREAD_POOL_SIZE', 1)
    self.cflags += ['-lembind', '--post-js=' + test_file('core/pthread/test_embind_sync_if_pthread_delayed.post.js'), '--no-entry']
    self.do_core_test('pthread/test_embind_sync_if_pthread_delayed.cpp')

  ### Tests for tools

  @no_wasm2js('TODO: source maps in wasm2js')
  @also_with_minimal_runtime
  @requires_node
  @requires_dev_dependency('source-map')
  def test_source_map(self):
    if '-g' not in self.cflags:
      self.cflags.append('-g')

    src = '''
      #include <stdio.h>
      #include <assert.h>

      __attribute__((noinline)) int foo() {
        printf("hi"); // line 6
        return 1; // line 7
      }

      int main() {
        printf("%d", foo()); // line 11
        return 0; // line 12
      }
    '''
    create_file('src.cpp', src)

    out_filename = self.output_name('a.out')
    wasm_filename = 'a.out.wasm'
    no_maps_filename = 'no-maps.out.js'

    assert '-gsource-map' not in self.cflags
    self.emcc('src.cpp', ['-o', out_filename])
    # the file name may find its way into the generated code, so make sure we
    # can do an apples-to-apples comparison by compiling with the same file name
    shutil.move(out_filename, no_maps_filename)
    no_maps_file = read_file(no_maps_filename)
    no_maps_file = re.sub(' *//[@#].*$', '', no_maps_file, flags=re.MULTILINE)
    self.cflags.append('-gsource-map')

    self.emcc(os.path.abspath('src.cpp'), ['-o', out_filename])
    map_referent = out_filename if self.is_wasm2js() else wasm_filename
    # after removing the @line and @sourceMappingURL comments, the build
    # result should be identical to the non-source-mapped debug version.
    # this is worth checking because the parser AST swaps strings for token
    # objects when generating source maps, so we want to make sure the
    # optimizer can deal with both types.
    map_filename = map_referent + '.map'

    data = json.loads(utils.read_file(map_filename))
    if hasattr(data, 'file'):
      # the file attribute is optional, but if it is present it needs to refer
      # the output file.
      self.assertPathsIdentical(map_referent, data['file'])
    self.assertGreater(len(data['sources']), 1)
    self.assertContained('src.cpp', data['sources'])
    src_index = data['sources'].index('src.cpp')
    if hasattr(data, 'sourcesContent'):
      # the sourcesContent attribute is optional, but if it is present it
      # needs to contain valid source text.
      self.assertTextDataIdentical(src, data['sourcesContent'][src_index])
    mappings = json.loads(self.run_js(
      path_from_root('test/sourcemap2json.js'),
      args=[map_filename]))
    seen_lines = set()
    for m in mappings:
      if m['source'] == 'src.cpp':
        seen_lines.add(m['originalLine'])
    # ensure that all the 'meaningful' lines in the original code get mapped
    # when optimizing, the binaryen optimizer may remove some of them (by inlining, etc.)
    if self.is_optimizing():
      self.assertTrue(seen_lines.issuperset([11, 12]), seen_lines)
    else:
      self.assertTrue(seen_lines.issuperset([6, 7, 11, 12]), seen_lines)

  @needs_dylink
  def test_embind_dylink_visibility_hidden(self):
    # Check that embind is usable from a library built with "-fvisibility=hidden"

    create_file('libside.cpp', r'''
      #include <emscripten/val.h>
      #define EXPORT __attribute__((visibility("default")))
      using namespace emscripten;
      EXPORT void liba_fun() {
        unsigned char buffer[1];
        val view(typed_memory_view(1, buffer));
      }
    ''')
    self.build_dlfcn_lib('libside.cpp', cflags=['-fvisibility=hidden'])

    self.prep_dlfcn_main()
    self.clear_setting('NO_AUTOLOAD_DYLIBS')
    create_file('main.cpp', r'''
      #include <stdio.h>
      #include <emscripten/val.h>
      using namespace emscripten;
      void liba_fun();
      int main() {
        liba_fun();
        printf("done\n");
        return 0;
      }
    ''')
    self.do_runf('main.cpp', 'done\n', cflags=['--bind'])

  @no_wasm2js('TODO: source maps in wasm2js')
  @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with dwarf output')
  def test_dwarf(self):
    self.cflags.append('-g')

    copy_asset('core/test_dwarf.c')

    self.emcc('test_dwarf.c')

    out = self.run_process([shared.LLVM_DWARFDUMP, 'a.out.wasm', '-all'], stdout=PIPE).stdout

    # parse the sections
    sections = {}

    lines = out.splitlines()
    # Add a sentinel to ensure the last section gets flushed properly
    lines += [' dummy contents:']

    curr_section_name = ''
    curr_section_start = -1
    for i, line in enumerate(lines):
      if ' contents:' in line:
        if curr_section_start >= 0:
          # a new section, a line like ".debug_str contents:"
          sections[curr_section_name] = '\n'.join(lines[curr_section_start:i])
        curr_section_name = line.split(' ')[0]
        curr_section_start = i + 1

    # make sure the right sections exist
    self.assertIn('.debug_abbrev', sections)
    self.assertIn('.debug_info', sections)
    self.assertIn('.debug_line', sections)
    self.assertIn('.debug_str', sections)
    self.assertIn('.debug_ranges', sections)

    # verify some content in the sections
    self.assertIn('"test_dwarf.c"', sections['.debug_info'])
    # the line section looks like this:
    # Address            Line   Column File   ISA Discriminator Flags
    # ------------------ ------ ------ ------ --- ------------- -------------
    # 0x000000000000000b      5      0      3   0             0  is_stmt
    src_to_addr = {}
    found_dwarf_c = False
    for line in sections['.debug_line'].splitlines():
      if 'name: "test_dwarf.c"' in line:
        found_dwarf_c = True
      if not found_dwarf_c:
        continue
      if 'debug_line' in line:
        break
      if line.startswith('0x'):
        while '  ' in line:
          line = line.replace('  ', ' ')
        addr, line, col = line.split(' ')[:3]
        key = (int(line), int(col))
        src_to_addr.setdefault(key, []).append(addr)

    # each of the calls must remain in the binary, and be mapped
    self.assertIn((6, 3), src_to_addr)
    self.assertIn((7, 3), src_to_addr)
    self.assertIn((8, 3), src_to_addr)

    def get_dwarf_addr(line, col):
      addrs = src_to_addr[line, col]
      # we assume the simple calls have one address
      self.assertEqual(len(addrs), 1)
      return int(addrs[0], 0)

    # the lines must appear in sequence (as calls to JS, the optimizer cannot
    # reorder them)
    self.assertLess(get_dwarf_addr(6, 3), get_dwarf_addr(7, 3))
    self.assertLess(get_dwarf_addr(7, 3), get_dwarf_addr(8, 3))

    # Get the wat, printing with -g which has binary offsets
    wat = self.run_process([os.path.join(building.get_binaryen_bin(), 'wasm-opt'),
                           'a.out.wasm', '-g', '--print', '-all'], stdout=PIPE).stdout

    # We expect to see a pattern like this in optimized builds (there isn't
    # much that can change with such calls to JS (they can't be reordered or
    # anything else):
    #
    #   ;; code offset: 0x?
    #   (drop
    #    ;; code offset: 0x?
    #    (call $out_to_js
    #     ;; code offset: 0x?
    #     (local.get ?) or (i32.const ?)
    #    )
    #   )
    #
    # In the stacky stream of instructions form, it is
    #
    #   local.get or i32.const
    #   call $out_to_js
    #   drop
    #
    # However, in an unoptimized build the constant may be assigned earlier in
    # some other manner, so stop here.
    if not self.is_optimizing():
      return

    # get_wat_addr gets the address of one of the 3 interesting calls, by its
    # index (0,1,2).
    def get_wat_addr(call_index):
      # find the call_index-th call
      call_loc = -1
      for _ in range(call_index + 1):
        call_loc = wat.find('call $out_to_js', call_loc + 1)
        assert call_loc > 0
      # the call begins with the local.get/i32.const printed below it, which is
      # the first instruction in the stream, so it has the lowest address
      start_addr_loc = wat.find('0x', call_loc)
      assert start_addr_loc > 0
      start_addr_loc_end = wat.find('\n', start_addr_loc)
      start_addr = int(wat[start_addr_loc:start_addr_loc_end], 0)
      # the call ends with the drop, which is the last in the stream, at the
      # highest address
      end_addr_loc = wat.rfind('drop', 0, call_loc)
      assert end_addr_loc > 0
      end_addr_loc = wat.rfind('0x', 0, end_addr_loc)
      assert end_addr_loc > 0
      end_addr_loc_end = wat.find('\n', end_addr_loc)
      assert end_addr_loc_end > 0
      end_addr = int(wat[end_addr_loc:end_addr_loc_end], 0)
      return (start_addr, end_addr)

    # match up the DWARF and the wat
    for i in range(3):
      dwarf_addr = get_dwarf_addr(6 + i, 3)
      start_wat_addr, end_wat_addr = get_wat_addr(i)
      # the dwarf may match any of the 3 instructions that form the stream of
      # of instructions implementing the call in the source code, in theory
      self.assertLessEqual(start_wat_addr, dwarf_addr)
      self.assertLessEqual(dwarf_addr, end_wat_addr)

  @no_omit_asm_module_exports('MODULARIZE is not compatible with DECLARE_ASM_MODULE_EXPORTS=0')
  @no_modularize_instance('uses -sMODULARIZE')
  def test_modularize_closure_pre(self):
    # test that the combination of modularize + closure + pre-js works. in that mode,
    # closure should not minify the Module object in a way that the pre-js cannot use it.
    if self.is_wasm2js():
      # TODO(sbc): Fix closure warnings with MODULARIZE + WASM=0
      self.ldflags.append('-Wno-error=closure')

    self.cflags += [
      '--pre-js', test_file('core/modularize_closure_pre.js'),
      '--extern-post-js', test_file('modularize_post_js.js'),
      '--closure=1',
      '-g1',
      '-sMODULARIZE',
    ]
    self.do_core_test('modularize_closure_pre.c')

  @no_wasm2js('symbol names look different wasm2js backtraces')
  @no_modularize_instance('assumes .js output filename')
  @also_without_bigint
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26197')
  def test_emscripten_log(self):
    self.cflags += ['-g', '-DRUN_FROM_JS_SHELL', '-Wno-deprecated-pragma']
    if self.maybe_closure():
      self.cflags += ['-g1'] # extra testing
    self.do_runf_out_file('test_emscripten_log.cpp', interleaved_output=False)

  def test_float_literals(self):
    self.do_runf_out_file('test_float_literals.cpp')

  def test_exit_status(self):
    # needs to flush stdio streams
    self.set_setting('EXIT_RUNTIME')
    create_file('exit.c', r'''
      #include <stdio.h>
      #include <assert.h>
      #include <stdlib.h>
      #include <unistd.h>

      static void cleanup() {
        #ifndef NORMAL_EXIT
        assert(0 && "cleanup should only be called from normal exit()");
        #endif
        printf("cleanup\n");
      }

      int main() {
        atexit(cleanup); // this atexit should still be called
        printf("Hello, world!\n");
        // Unusual exit status to make sure it's working!
        #if defined(NORMAL_EXIT)
          exit(117);
        #elif defined(UNDER_EXIT)
          _exit(118);
        #elif defined(CAPITAL_EXIT)
          _Exit(119);
        #endif
      }
    ''')
    create_file('pre.js', '''
      Module.onExit = (status) => {
        out('I see exit status: ' + status);
        // The EXITSTATUS global should match what we are passed
        assert(status == EXITSTATUS);
      };
    ''')
    self.cflags += ['--pre-js', 'pre.js', '-sINCOMING_MODULE_JS_API=onExit']
    print('.. exit')
    self.do_runf('exit.c', 'Hello, world!\ncleanup\nI see exit status: 117', assert_returncode=117, cflags=['-DNORMAL_EXIT'])
    print('.. _exit')
    self.do_runf('exit.c', 'Hello, world!\nI see exit status: 118', assert_returncode=118, cflags=['-DUNDER_EXIT'])
    print('.. _Exit')
    self.do_runf('exit.c', 'Hello, world!\nI see exit status: 119', assert_returncode=119, cflags=['-DCAPITAL_EXIT'])

  def test_minmax(self):
    self.do_runf('test_minmax.c', 'NAN != NAN\nSuccess!')

  def test_localeconv(self):
    self.do_core_test('test_localeconv.c')

  def test_newlocale(self):
    self.do_core_test('test_newlocale.c')

  def test_setlocale(self):
    self.do_core_test('test_setlocale.c')

  def test_vswprintf_utf8(self):
    self.do_core_test('test_vswprintf_utf8.c')

  # Test async sleeps in the presence of invoke_* calls, which can happen with
  # longjmp or exceptions.
  @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with ASYNCIFY=1')
  def test_asyncify_longjmp(self):
    self.set_setting('ASYNCIFY')
    self.set_setting('STRICT')
    self.do_core_test('test_asyncify_longjmp.c')

  # Test that a main with arguments is automatically asyncified.
  @with_asyncify_and_jspi
  def test_async_main(self):
    create_file('main.c', r'''
#include <stdio.h>
#include <emscripten.h>
int main(int argc, char **argv) {
  emscripten_sleep(1);
  printf("argc=%d argv=%s\n", argc, argv[1]);
}
''')

    self.do_runf('main.c', 'argc=2 argv=hello', args=['hello'])

  @with_asyncify_and_jspi
  def test_async_hello(self):
    # needs to flush stdio streams
    self.set_setting('EXIT_RUNTIME')

    create_file('main.c', r'''
#include <stdio.h>
#include <emscripten.h>
void f(void *p) {
  *(int*)p = 99;
  printf("!");
}
int main() {
  int i = 0;
  printf("Hello");
  emscripten_async_call(f, &i, 1);
  printf("World");
  emscripten_sleep(100);
  printf("%d\n", i);
}
''')

    self.do_runf('main.c', 'HelloWorld!99')

  @with_asyncify_and_jspi
  def test_async_loop(self):
    create_file('main.c', r'''
#include <stdio.h>
#include <emscripten.h>
int main() {
  for (int i = 0; i < 5; i++) {
    emscripten_sleep(1);
    printf("hello %d\n", i);
  }
}
''')

    self.do_runf('main.c', 'hello 0\nhello 1\nhello 2\nhello 3\nhello 4\n')

  @no_modularize_instance('ccall is not compatible with MODULARIZE=instance')
  def test_async_ccall_bad(self):
    # check bad ccall use
    # needs to flush stdio streams
    self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$ccall'])
    self.set_setting('ASYNCIFY')
    self.set_setting('ASSERTIONS')
    self.set_setting('INVOKE_RUN', 0)
    create_file('main.c', r'''
#include <stdio.h>
#include <emscripten.h>
int main() {
  printf("Hello");
  emscripten_sleep(100);
  printf("World\n");
}
''')
    create_file('pre.js', '''
Module.onRuntimeInitialized = () => {
  try {
    ccall('main', 'number', ['number', 'string'], [2, 'waka']);
    var never = true;
  } catch(e) {
    out(e);
    assert(!never);
  }
};
''')
    self.cflags += ['--pre-js', 'pre.js', '-sINCOMING_MODULE_JS_API=onRuntimeInitialized']
    self.do_runf('main.c', 'The call to main is running asynchronously.')

  @with_asyncify_and_jspi
  @no_modularize_instance('ccall is not compatible with MODULARIZE=instance')
  def test_async_ccall_good(self):
    # check reasonable ccall use
    self.set_setting('ASYNCIFY')
    self.set_setting('ASSERTIONS')
    self.set_setting('INVOKE_RUN', 0)
    self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$ccall'])
    create_file('main.c', r'''
#include <stdio.h>
#include <emscripten.h>
int main() {
  printf("Hello");
  emscripten_sleep(100);
  printf("World\n");
}
''')
    create_file('pre.js', '''
Module.onRuntimeInitialized = () => {
  ccall('main', null, ['number', 'string'], [2, 'waka'], { async: true });
};
''')
    self.cflags += ['--pre-js', 'pre.js', '-sINCOMING_MODULE_JS_API=onRuntimeInitialized']
    self.do_runf('main.c', 'HelloWorld')

  @parameterized({
    '': (False,),
    'exit_runtime': (True,),
  })
  @with_asyncify_and_jspi
  @no_modularize_instance('ccall is not compatible with MODULARIZE=instance')
  def test_async_ccall_promise(self, exit_runtime):
    if self.get_setting('JSPI'):
      self.set_setting('JSPI_EXPORTS', ['stringf', 'floatf'])
    self.set_setting('ASSERTIONS')
    self.set_setting('INVOKE_RUN', 0)
    self.set_setting('EXIT_RUNTIME', exit_runtime)
    self.set_setting('EXPORTED_FUNCTIONS', ['_stringf', '_floatf'])
    self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$maybeExit', '$ccall'])
    create_file('main.c', r'''
#include <stdio.h>
#include <emscripten.h>
const char* stringf(char* param) {
  emscripten_sleep(20);
  printf("stringf: %s", param);
  return "second";
}
double floatf() {
  emscripten_sleep(20);
  emscripten_sleep(20);
  return 6.4;
}
''')
    create_file('pre.js', r'''
Module.onRuntimeInitialized = () => {
  runtimeKeepalivePush();
  ccall('stringf', 'string', ['string'], ['first\n'], { async: true })
    .then(function(val) {
      out(val);
      ccall('floatf', 'number', null, null, { async: true }).then(function(arg) {
        out(arg);
        runtimeKeepalivePop();
        maybeExit();
      });
    });
};
''')
    self.cflags += ['--pre-js', 'pre.js', '-sINCOMING_MODULE_JS_API=onRuntimeInitialized']
    self.do_runf('main.c', 'stringf: first\nsecond\n6.4')

  @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with ASYNCIFY=1')
  def test_fibers_asyncify(self):
    self.set_setting('ASYNCIFY')
    self.maybe_closure()
    self.do_runf('test_fibers.cpp', '*leaf-0-100-1-101-1-102-2-103-3-104-5-105-8-106-13-107-21-108-34-109-*')

  @with_asyncify_and_jspi
  def test_asyncify_unused(self):
    # test a program not using asyncify, but the pref is set
    self.do_core_test('test_hello_world.c')

  @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with ASYNCIFY=1')
  @parameterized({
    'normal': ([], True),
    'removelist_a': (['-sASYNCIFY_REMOVE=["foo(int, double)"]'], False),
    'removelist_b': (['-sASYNCIFY_REMOVE=["bar()"]'], True),
    'removelist_c': (['-sASYNCIFY_REMOVE=["baz()"]'], False),
    'onlylist_a': (['-sASYNCIFY_ONLY=["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()","bar()"]'], True),
    'onlylist_b': (['-sASYNCIFY_ONLY=["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()"]'], True),
    'onlylist_c': (['-sASYNCIFY_ONLY=["main","__original_main","foo(int, double)","baz()","c_baz"]'], False),
    'onlylist_d': (['-sASYNCIFY_ONLY=["foo(int, double)","baz()","c_baz","Structy::funcy()"]'], False),
    'onlylist_b_response': ([], True,  'main\n__original_main\nfoo(int, double)\nbaz()\nc_baz\nStructy::funcy()\n'),
    'onlylist_c_response': ([], False, 'main\n__original_main\nfoo(int, double)\nbaz()\nc_baz\n'),
  })
  def test_asyncify_lists(self, args, should_pass, response=None):
    if response is not None:
      create_file('response.file', response)
      self.set_setting('ASYNCIFY_ONLY', '@response.file')
    self.set_setting('ASYNCIFY')
    self.cflags += args

    if should_pass:
      self.do_core_test('test_asyncify_lists.cpp', assert_identical=True)
    else:
      self.do_runf('core/test_asyncify_lists.cpp', ('RuntimeError', 'Thrown at'), assert_returncode=NON_ZERO)

    # use of ASYNCIFY_* options may require intermediate debug info. that should
    # not end up emitted in the final binary
    if self.is_wasm():
      filename = 'test_asyncify_lists.wasm'
      # there should be no name section. sanitizers, however, always enable that
      if not is_sanitizing(self.cflags) and '--profiling-funcs' not in self.cflags and '-g' not in self.cflags:
        with webassembly.Module(filename) as m:
          self.assertFalse(m.has_name_section())
      # in a fully-optimized build, imports and exports are minified too and we
      # can verify that our function names appear nowhere
      if '-O3' in self.cflags:
        self.assertFalse(b'__wasm_call_ctors' in read_binary(filename))
      elif '-O0' in self.cflags:
        # However, sanity check that in core0 test, we do see this symbol.
        self.assertTrue(b'__wasm_call_ctors' in read_binary(filename))

  @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with ASYNCIFY')
  @parameterized({
    'normal': ([], True),
    'ignoreindirect': (['-sASYNCIFY_IGNORE_INDIRECT'], False),
    'add': (['-sASYNCIFY_IGNORE_INDIRECT', '-sASYNCIFY_ADD=["virt()"]'], True),
    # If ASYNCIFY_PROPAGATE_ADD is disabled then we must specify the callers of
    # virt() manually, rather than have them inferred automatically.
    'add_no_prop': (['-sASYNCIFY_IGNORE_INDIRECT', '-sASYNCIFY_ADD=["__original_main","main","virt()"]', '-sASYNCIFY_PROPAGATE_ADD=0'], True),
  })
  @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with ASYNCIFY=1')
  def test_asyncify_indirect_lists(self, args, should_pass):
    self.set_setting('ASYNCIFY')
    if '-flto' in str(self.cflags):
      # LTO ends up inlining virt(), so ASYNCIFY_ADD does not work as expected.
      # If wasm-opt were aware of LLVM's no-inline mark this would not happen.
      self.skipTest('https://github.com/emscripten-core/emscripten/issues/21757')
    try:
      self.do_core_test('test_asyncify_indirect_lists.cpp', assert_identical=True, cflags=args)
      if not should_pass:
        should_pass = True
        raise Exception('should not have passed')
    except Exception:
      if should_pass:
        raise

  @with_dylink_reversed
  @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with ASYNCIFY=1')
  def test_asyncify_side_module(self):
    self.set_setting('ASYNCIFY')
    self.set_setting('ASYNCIFY_IMPORTS', ['my_sleep'])
    self.dylink_test(r'''
      #include <stdio.h>
      #include "header.h"

      int main() {
        printf("before sleep\n");
        my_sleep(1);
        printf("after sleep\n");
        return 0;
      }
    ''', r'''
      #include <stdio.h>
      #include <emscripten.h>
      #include "header.h"

      void my_sleep(int milli_seconds) {
        // put variable onto stack
        volatile int value = 42;
        printf("%d\n", value);
        emscripten_sleep(milli_seconds);
        // variable on stack in side module function should be restored.
        printf("%d\n", value);
      }
    ''', 'before sleep\n42\n42\nafter sleep\n', header='void my_sleep(int);', force_c=True)

  @no_asan('asyncify stack operations confuse asan')
  @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with ASYNCIFY=1')
  def test_emscripten_scan_registers(self):
    self.set_setting('ASYNCIFY')
    self.do_core_test('test_emscripten_scan_registers.cpp')

  @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with ASYNCIFY=1')
  def test_asyncify_assertions(self):
    self.set_setting('ASYNCIFY')
    self.set_setting('ASYNCIFY_IMPORTS', ['suspend'])
    self.set_setting('ASSERTIONS')
    self.do_core_test('test_asyncify_assertions.c', assert_returncode=NON_ZERO)

  @no_lsan('leaks asyncify stack during exit')
  @no_asan('leaks asyncify stack during exit')
  @no_esm_integration('WASM_ESM_INTEGRATION is not compatible with ASYNCIFY=1')
  def test_asyncify_during_exit(self):
    self.set_setting('ASYNCIFY')
    self.set_setting('ASSERTIONS')
    self.set_setting('EXIT_RUNTIME', 1)
    self.do_core_test('test_asyncify_during_exit.cpp', assert_returncode=NON_ZERO)
    print('NO_ASYNC')
    self.do_core_test('test_asyncify_during_exit.cpp', cflags=['-DNO_ASYNC'], out_suffix='_no_async')

  @no_asan('asyncify stack operations confuse asan')
  @no_lsan('undefined symbol __global_base')
  @no_wasm2js('dynamic linking support in wasm2js')
  @with_asyncify_and_jspi
  @needs_dylink
  def test_asyncify_main_module(self):
    self.set_setting('MAIN_MODULE', 2)
    self.do_core_test('test_hello_world.c')

  # Include @requires_node_25 explictly here so that this test will be disabled
  # by EMTEST_SKIP_NODE_25.  Without this, the `requires_pthreads` and `requires_jspi` can
  # end with conflicting requirements because we often run with both v8 (which satisfies
  # the `requires_jspi` part have node 22 (which satisfies the `requires_pthreads` part).
  # FIXME: This should not be needed.
  @requires_node_25
  @requires_pthreads
  @requires_jspi
  def test_pthread_join_and_asyncify(self):
    # TODO Test with ASYNCIFY=1 https://github.com/emscripten-core/emscripten/issues/17552
    self.do_runf('core/test_pthread_join_and_asyncify.c', 'join returned -> 42\n',
                 cflags=['-sJSPI', '-sEXIT_RUNTIME=1', '-pthread', '-sPROXY_TO_PTHREAD'])

  # Test basic wasm2js functionality in all core compilation modes.
  @no_sanitize('no wasm2js support yet in sanitizers')
  @requires_wasm2js
  @no_big_endian('wasm2js is currently not compatible with big endian')
  def test_wasm2js(self):
    if self.is_wasm2js():
      self.skipTest('redundant to test wasm2js in wasm2js* mode')
    self.set_setting('WASM', 0)
    self.do_core_test('test_hello_world.c')
    self.assertNotExists('test_hello_world.js.mem')

  @no_asan('no wasm2js support yet in asan')
  @requires_wasm2js
  @also_with_minimal_runtime
  @no_big_endian('wasm2js is currently not compatible with big endian')
  def test_wasm2js_fallback(self):
    if self.is_wasm2js():
      self.skipTest('redundant to test wasm2js in wasm2js* mode')

    self.run_process([EMCC, test_file('hello_world_small.c'), '-sWASM=2'])

    # First run with WebAssembly support enabled
    # Move the Wasm2js fallback away to test it is not accidentally getting loaded.
    os.rename('a.out.wasm.js', 'a.out.wasm.js.unused')
    self.assertContained('hello!', self.run_js('a.out.js'))
    os.rename('a.out.wasm.js.unused', 'a.out.wasm.js')

    # Then disable WebAssembly support in VM, and try again.. Should still work with Wasm2JS fallback.
    create_file('b.out.js', 'WebAssembly = undefined;\n' + read_file('a.out.js'))
    os.remove('a.out.wasm') # Also delete the Wasm file to test that it is not attempted to be loaded.
    self.assertContained('hello!', self.run_js('b.out.js'))

  def test_cxx_self_assign(self):
    # See https://github.com/emscripten-core/emscripten/pull/2688 and http://llvm.org/bugs/show_bug.cgi?id=18735
    self.do_run(r'''
      #include <map>
      #include <stdio.h>

      int main() {
        std::map<int, int> m;
        m[0] = 1;
        m = m;
        // size should still be one after self assignment
        if (m.size() == 1) {
          printf("ok.\n");
        }
      }
    ''', 'ok.')

  def test_memprof_requirements(self):
    # This test checks for the global variables required to run the memory
    # profiler.  It would fail if these variables were made no longer global
    # or if their identifiers were changed.
    create_file('pre.js', '''
      Module = {
        onRuntimeInitialized: () => {
          assert(typeof _emscripten_stack_get_base === 'function');
          assert(typeof _emscripten_stack_get_end === 'function');
          assert(typeof _emscripten_stack_get_current === 'function');
          assert(typeof ___heap_base === 'number');
          assert(___heap_base > 0);
          out('able to run memprof');
        }
      };
    ''')
    self.do_runf('hello_world.c', 'able to run memprof', cflags=['--memoryprofiler', '--pre-js=pre.js', '-sINCOMING_MODULE_JS_API=onRuntimeInitialized'])

  @no_wasmfs('depends on MEMFS which WASMFS does not have')
  def test_fs_dict(self):
    self.set_setting('FORCE_FILESYSTEM')
    create_file('pre.js', '''
      Module.preRun = () => {
        out(typeof FS.filesystems['MEMFS']);
        out(typeof FS.filesystems['IDBFS']);
        out(typeof FS.filesystems['NODEFS']);
        // Globals
        out(typeof MEMFS);
        out(typeof IDBFS);
        out(typeof NODEFS);
      };
    ''')
    self.cflags += ['-lidbfs.js', '-lnodefs.js', '--pre-js', 'pre.js', '-sINCOMING_MODULE_JS_API=preRun']
    self.do_run('int main() { return 0; }', 'object\nobject\nobject\nobject\nobject\nobject')

  @no_wasmfs('depends on MEMFS which WASMFS does not have')
  def test_fs_dict_none(self):
    # if IDBFS and NODEFS are not enabled, they are not present.
    self.set_setting('FORCE_FILESYSTEM')
    self.set_setting('ASSERTIONS')
    create_file('pre.js', '''
      Module.preRun = () => {
        out(typeof FS.filesystems['MEMFS']);
        out(typeof FS.filesystems['IDBFS']);
        out(typeof FS.filesystems['NODEFS']);
        // Globals
        out(typeof MEMFS);
        out(IDBFS);
        out(NODEFS);
        FS.mkdir('/working1');
        try {
          FS.mount(IDBFS, {}, '/working1');
        } catch (e) {
          out('|' + e + '|');
        }
      };
    ''')
    self.cflags += ['--pre-js', 'pre.js', '-sINCOMING_MODULE_JS_API=preRun']
    expected = '''\
object
undefined
undefined
object
IDBFS is no longer included by default; build with -lidbfs.js
NODEFS is no longer included by default; build with -lnodefs.js
|IDBFS is no longer included by default; build with -lidbfs.js|'''
    self.do_run('int main() { return 0; }', expected)

  def test_stack_overflow_check(self):
    self.set_setting('STACK_SIZE', 1048576)
    self.set_setting('STACK_OVERFLOW_CHECK', 2)
    self.do_runf('stack_overflow.cpp', 'stack overflow', assert_returncode=NON_ZERO)

    self.cflags += ['-DONE_BIG_STRING']
    self.do_runf('stack_overflow.cpp', 'stack overflow', assert_returncode=NON_ZERO)

    # ASSERTIONS=2 implies STACK_OVERFLOW_CHECK=2
    self.clear_setting('STACK_OVERFLOW_CHECK')
    self.set_setting('ASSERTIONS', 2)
    self.do_runf('stack_overflow.cpp', 'stack overflow', assert_returncode=NON_ZERO)

  @requires_pthreads
  def test_binaryen_2170_emscripten_atomic_cas_u8(self):
    self.do_runf_out_file('binaryen_2170_emscripten_atomic_cas_u8.cpp', cflags=['-pthread'])

  @also_with_standalone_wasm()
  def test_sbrk(self):
    self.do_core_test('test_sbrk_brk.c')
    self.do_core_test('test_sbrk_brk.c', cflags=['-sALLOW_MEMORY_GROWTH'])

  def test_brk(self):
    self.do_core_test('test_sbrk_brk.c', cflags=['-DTEST_BRK=1'])

  # Tests that we can use the dlmalloc mallinfo() function to obtain information
  # about malloc()ed blocks and compute how much memory is used/freed.
  @no_asan('mallinfo is not part of ASan malloc')
  @no_lsan('mallinfo is not part of LSan malloc')
  def test_mallinfo(self):
    self.do_core_test('test_mallinfo.c')

  @no_asan('cannot replace malloc/free with ASan')
  @no_lsan('cannot replace malloc/free with LSan')
  @parameterized({
    '': ([],),
    'emmalloc': (['-sMALLOC=emmalloc'],),
    # TODO: investigate removing `-sABORTING_MALLOC=0`, as it's only needed for `wasm64_4gb.test_wrap_malloc_mimalloc`
    'mimalloc': (['-sMALLOC=mimalloc', '-sABORTING_MALLOC=0'],),
  })
  def test_wrap_malloc(self, args):
    self.do_runf('core/test_wrap_malloc.c', 'done\n', cflags=args)

  def test_environment(self):
    self.set_setting('ASSERTIONS')

    def test(assert_returncode=0):
      self.do_core_test('test_hello_world.c', assert_returncode=assert_returncode)
      if self.get_setting('WASM_ESM_INTEGRATION'):
        js = read_file(self.output_name('test_hello_world.support'))
      else:
        js = read_file(self.output_name('test_hello_world'))
      assert ('require(' in js) == ('node' in self.get_setting('ENVIRONMENT')), 'we should have require() calls only if node js specified'

    for engine in self.js_engines:
      print(f'engine: {engine}')
      # set us to test in just this engine
      self.require_engine(engine, force=True)
      # tell the compiler to build with just that engine
      if engine_is_node(engine) or engine_is_bun(engine):
        right = 'node'
        wrong = 'shell'
      else:
        right = 'shell'
        wrong = 'node'
      # test with the right env
      self.set_setting('ENVIRONMENT', right)
      print('right ENVIRONMENT =', self.get_setting('ENVIRONMENT'))
      test()
      # test with the wrong env
      self.set_setting('ENVIRONMENT', wrong)
      print('wrong ENVIRONMENT =', self.get_setting('ENVIRONMENT'))
      try:
        test(assert_returncode=NON_ZERO)
        raise Exception('unexpected success')
      except Exception as e:
        self.assertContained(['environment detected but not enabled at build time', 'not compiled for this environment'], str(e))
      # test with a combined env
      self.set_setting('ENVIRONMENT', right + ',' + wrong)
      print('both ENVIRONMENT =', self.get_setting('ENVIRONMENT'))
      test()

  @requires_node
  def test_postrun_exception(self):
    # verify that an exception thrown in postRun() will not trigger the
    # compilation failed handler, and will be printed to stderr.
    self.add_post_run('ThisFunctionDoesNotExist()')
    out_js = self.build('core/test_hello_world.c')
    output = self.run_js(out_js, assert_returncode=NON_ZERO)
    self.assertStartswith(output, 'Hello, world!')
    self.assertContained('ThisFunctionDoesNotExist is not defined', output)

  def test_postrun_exit_runtime(self):
    create_file('pre.js', "Module['postRun'] = () => err('post run\\n');")
    self.set_setting('EXIT_RUNTIME')
    self.cflags += ['--pre-js=pre.js', '-sINCOMING_MODULE_JS_API=postRun']
    self.do_runf('hello_world.c', 'post run')

  # Tests that building with -sDECLARE_ASM_MODULE_EXPORTS=0 works
  @also_with_minimal_runtime
  @no_modularize_instance('DECLARE_ASM_MODULE_EXPORTS=0 is not compatible with MODULARIZE')
  def test_no_declare_asm_module_exports(self):
    self.set_setting('DECLARE_ASM_MODULE_EXPORTS', 0)
    self.set_setting('WASM_ASYNC_COMPILATION', 0)
    self.maybe_closure()
    self.do_runf('declare_asm_module_exports.c', 'jsFunction: 1')
    js = read_file('declare_asm_module_exports.js')
    occurrences = js.count('cFunction')
    if self.is_optimizing() and '-g' not in self.cflags:
      # In optimized builds only the single reference cFunction that exists in the EM_ASM should exist
      if self.is_wasm():
        self.assertEqual(occurrences, 1)
      else:
        # With js the asm module itself also contains a reference for the cFunction name
        self.assertEqual(occurrences, 2)
    else:
      print(occurrences)

  # Tests that -sMINIMAL_RUNTIME works well in different build modes
  @no_modularize_instance('MODULARIZE=instance is not compatible with MINIMAL_RUNTIME')
  @parameterized({
    '': ([],),
    'streaming_inst': (['-sMINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION'],),
    'no_export': (['-sDECLARE_ASM_MODULE_EXPORTS=0'],),
  })
  @requires_node  # TODO: Support for non-Node.js shells under MINIMAL_RUNTIME
  def test_minimal_runtime_hello_world(self, args):
    self.maybe_closure()
    self.cflags += ['--pre-js', test_file('minimal_runtime_exit_handling.js')]
    self.do_runf('hello_world_small.c', 'hello!', cflags=['-sMINIMAL_RUNTIME'] + args)

  # Test that printf() works in MINIMAL_RUNTIME=1
  @no_wasmfs('https://github.com/emscripten-core/emscripten/issues/16816')
  @no_modularize_instance('MODULARIZE=instance is not compatible with MINIMAL_RUNTIME')
  @parameterized({
    'fs': ('FORCE_FILESYSTEM',),
    'nofs': ('NO_FILESYSTEM',),
  })
  def test_minimal_runtime_hello_printf(self, extra_setting):
    self.set_setting('MINIMAL_RUNTIME')
    self.cflags += ['--pre-js', test_file('minimal_runtime_exit_handling.js')]
    self.set_setting(extra_setting)
    # $FS is not fully compatible with MINIMAL_RUNTIME so fails with closure
    # compiler.  lsan also pulls in $FS
    if '-fsanitize=leak' not in self.cflags and extra_setting != 'FORCE_FILESYSTEM':
      self.maybe_closure()
    self.do_runf_out_file('hello_world.c')

  # Tests that -sMINIMAL_RUNTIME works well with SAFE_HEAP
  @no_modularize_instance('MODULARIZE=instance is not compatible with MINIMAL_RUNTIME')
  @no_asan('SAFE_HEAP cannot be used with ASan')
  def test_minimal_runtime_safe_heap(self):
    self.set_setting('MINIMAL_RUNTIME')
    self.cflags += ['--pre-js', test_file('minimal_runtime_exit_handling.js')]
    self.set_setting('SAFE_HEAP')
    # $FS is not fully compatible with MINIMAL_RUNTIME so fails with closure
    # compiler.
    # lsan pulls in $FS
    if '-fsanitize=leak' not in self.cflags:
      self.maybe_closure()
    self.do_runf('hello_world_small.c', 'hello')

  # Tests global initializer with -sMINIMAL_RUNTIME
  @no_modularize_instance('MODULARIZE=instance is not compatible with MINIMAL_RUNTIME')
  def test_minimal_runtime_global_initializer(self):
    self.set_setting('MINIMAL_RUNTIME')
    self.cflags += ['--pre-js', test_file('minimal_runtime_exit_handling.js')]
    self.maybe_closure()
    self.do_runf('test_global_initializer.cpp', 't1 > t0: 1')

  @no_wasm2js('wasm2js does not support PROXY_TO_PTHREAD (custom section support)')
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26197')
  def test_return_address(self):
    if not self.is_optimizing() and ('-flto' in self.cflags or '-flto=thin' in self.cflags):
      self.skipTest('https://github.com/emscripten-core/emscripten/issues/25015')

    self.do_runf('core/test_return_address.c', 'passed', cflags=['-g'])

  @no_wasm2js('TODO: sanitizers in wasm2js')
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  @no_asan('-fsanitize-minimal-runtime cannot be used with ASan')
  @no_lsan('-fsanitize-minimal-runtime cannot be used with LSan')
  def test_ubsan_minimal_too_many_errors(self):
    self.cflags += ['-fsanitize=undefined', '-fsanitize-minimal-runtime']
    self.do_runf('core/test_ubsan_minimal_too_many_errors.c',
                 expected_output='ubsan: add-overflow by 0x[0-9a-f]*\n' * 20 + 'ubsan: too many errors\n',
                 regex=True)

  @no_wasm2js('TODO: sanitizers in wasm2js')
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  @no_asan('-fsanitize-minimal-runtime cannot be used with ASan')
  @no_lsan('-fsanitize-minimal-runtime cannot be used with LSan')
  def test_ubsan_minimal_errors_same_place(self):
    self.cflags += ['-fsanitize=undefined', '-fsanitize-minimal-runtime']
    self.do_runf('core/test_ubsan_minimal_errors_same_place.c',
                 expected_output='ubsan: add-overflow by 0x[0-9a-z]*\n' * 5,
                 regex=True)

  @parameterized({
    'fsanitize_undefined': (['-fsanitize=undefined'],),
    'fsanitize_integer': (['-fsanitize=integer'],),
    'fsanitize_overflow': (['-fsanitize=signed-integer-overflow'],),
  })
  @no_wasm2js('TODO: sanitizers in wasm2js')
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  def test_ubsan_full_overflow(self, args):
    self.do_runf(
      'core/test_ubsan_full_overflow.c',
      assert_all=True,
      expected_output=[
        ".c:3:5: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'",
        ".c:7:7: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'",
      ],
      cflags=args)

  @parameterized({
    'fsanitize_undefined': (['-fsanitize=undefined'],),
    'fsanitize_return': (['-fsanitize=return'],),
  })
  @no_wasm2js('TODO: sanitizers in wasm2js')
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  def test_ubsan_full_no_return(self, args):
    self.do_runf('core/test_ubsan_full_no_return.cpp',
                 expected_output='.cpp:1:5: runtime error: execution reached the end of a value-returning function without returning a value',
                 assert_returncode=NON_ZERO,
                 cflags=['-Wno-return-type'] + args)

  @parameterized({
    'fsanitize_undefined': (['-fsanitize=undefined'],),
    'fsanitize_integer': (['-fsanitize=integer'],),
    'fsanitize_shift': (['-fsanitize=shift'],),
  })
  @no_wasm2js('TODO: sanitizers in wasm2js')
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  def test_ubsan_full_left_shift(self, args):
    self.do_runf(
      'core/test_ubsan_full_left_shift.c',
      assert_all=True,
      cflags=args,
      expected_output=[
        '.c:3:5: runtime error: left shift of negative value -1',
        ".c:7:5: runtime error: left shift of 16 by 29 places cannot be represented in type 'int'",
      ])

  @parameterized({
    'fsanitize_undefined': (['-fsanitize=undefined'],),
    'fsanitize_null': (['-fsanitize=null'],),
    'dylink': (['-fsanitize=null', '-sMAIN_MODULE=2'],),
  })
  @no_wasm2js('TODO: sanitizers in wasm2js')
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  def test_ubsan_full_null_ref(self, args):
    if '-sMAIN_MODULE=2' in args:
      self.check_dylink()
    if is_sanitizing(self.cflags):
      self.skipTest('test is specific to null sanitizer')
    self.do_runf(
      'core/test_ubsan_full_null_ref.cpp',
      assert_all=True,
      cflags=args,
      expected_output=[
        ".cpp:3:12: runtime error: reference binding to null pointer of type 'int'",
        ".cpp:4:13: runtime error: reference binding to null pointer of type 'int'",
        ".cpp:5:14: runtime error: reference binding to null pointer of type 'int'",
      ])

  @no_wasm2js('TODO: sanitizers in wasm2js')
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  def test_sanitize_vptr(self):
    self.do_runf(
      'core/test_sanitize_vptr.cpp',
      cflags=['-fsanitize=vptr'],
      assert_all=True,
      expected_output=[
        ".cpp:18:10: runtime error: downcast of address",
        "which does not point to an object of type 'R'",
      ])

  # The sanitizer runtime can symbolize based on dwarf, a name section, a sourcemap,
  # or a combination.
  @parameterized({
    'g': (['-g'], [
      ".cpp:3:12: runtime error: reference binding to null pointer of type 'int'",
      'in main',
    ]),
    'g2': (['-g2'], [
      ".cpp:3:12: runtime error: reference binding to null pointer of type 'int'",
      'in main',
    ]),
    'gsource_map': (['-gsource-map'], [
      ".cpp:3:12: runtime error: reference binding to null pointer of type 'int'",
      '.cpp:3:8',
    ]),
    'g4': (['-gsource-map', '-g2'], [
      ".cpp:3:12: runtime error: reference binding to null pointer of type 'int'",
      'in main ',
      '.cpp:3:8',
    ]),
  })
  @no_wasm2js('TODO: sanitizers in wasm2js')
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  @no_esm_integration('https://github.com/emscripten-core/emscripten/issues/26073')
  def test_ubsan_full_stack_trace(self, args, expected_output):
    if '-gsource-map' in args:
      if self.is_wasm2js():
        self.skipTest('wasm2js has no source map support')
      elif self.get_setting('EVAL_CTORS'):
        self.skipTest('EVAL_CTORS does not support source maps')

    create_file('pre.js', 'Module.UBSAN_OPTIONS = "print_stacktrace=1";')
    self.cflags += ['-fsanitize=null', '--pre-js=pre.js']
    self.set_setting('ALLOW_MEMORY_GROWTH')
    self.do_runf('core/test_ubsan_full_null_ref.cpp',
                 assert_all=True, expected_output=expected_output, cflags=args)

  @no_wasm2js('TODO: sanitizers in wasm2js')
  def test_ubsan_typeinfo_eq(self):
    # https://github.com/emscripten-core/emscripten/issues/13330
    src = r'''
      #include <typeinfo>
      #include <stdio.h>
      int main() {
        int mismatch = typeid(int) != typeid(int);
        printf("ok\n");
        return mismatch;
      }
      '''
    self.do_run(src, 'ok\n', cflags=['-fsanitize=undefined'])

  def test_template_class_deduction(self):
    self.do_core_test('test_template_class_deduction.cpp', cflags=['-std=c++17'])

  @asan
  @parameterized({
    'c': ['test_asan_no_error.c'],
    'cpp': ['test_asan_no_error.cpp'],
  })
  def test_asan_no_error(self, name):
    self.do_runf('core/' + name, 'done\n', cflags=['-fsanitize=address'])

  # note: these tests have things like -fno-builtin-memset in order to avoid
  # clang optimizing things away. for example, a memset might be optimized into
  # stores, and then the stores identified as dead, which leaves nothing for
  # asan to test. here we want to test asan itself, so we work around that.
  @asan
  @parameterized({
    'use_after_free_c': ('test_asan_use_after_free.c', [
      'AddressSanitizer: heap-use-after-free on address',
    ]),
    'use_after_free_cpp': ('test_asan_use_after_free.cpp', [
      'AddressSanitizer: heap-use-after-free on address',
    ]),
    'use_after_return': ('test_asan_use_after_return.c', [
      'AddressSanitizer: stack-use-after-return on address',
    ], ['-Wno-return-stack-address']),
    'static_buffer_overflow': ('test_asan_static_buffer_overflow.c', [
      'AddressSanitizer: global-buffer-overflow on address',
    ], ['-fno-builtin-memset']),
    'heap_buffer_overflow_c': ('test_asan_heap_buffer_overflow.c', [
      'AddressSanitizer: heap-buffer-overflow on address',
    ], ['-fno-builtin-memset']),
    'heap_buffer_overflow_cpp': ('test_asan_heap_buffer_overflow.cpp', [
      'AddressSanitizer: heap-buffer-overflow on address',
    ], ['-fno-builtin-memset']),
    'stack_buffer_overflow': ('test_asan_stack_buffer_overflow.c', [
      'AddressSanitizer: stack-buffer-overflow',
    ], ['-fno-builtin-memset']),
    'stack_buffer_overflow_js': ('test_asan_stack_buffer_overflow_js.c', [
      'AddressSanitizer: stack-buffer-overflow',
    ], ['-fno-builtin-memset']),
    'bitfield_unround_size': ('test_asan_bitfield_unround_size.c', [
      'AddressSanitizer: stack-buffer-overflow',
    ], ['-fno-builtin-memset']),
    'bitfield_unround_offset': ('test_asan_bitfield_unround_offset.c', [
      'AddressSanitizer: stack-buffer-overflow',
    ], ['-fno-builtin-memset']),
    'bitfield_round': ('test_asan_bitfield_round.c', [
      'AddressSanitizer: stack-buffer-overflow',
    ], ['-fno-builtin-memset']),
    'memset_null': ('test_asan_memset_null.c', [
      'AddressSanitizer: null-pointer-dereference on address 0x0*1',
    ], ['-fno-builtin-memset']),
    'memset_freed': ('test_asan_memset_freed.c', [
      'AddressSanitizer: heap-use-after-free on address',
    ], ['-fno-builtin-memset']),
    'strcpy': ('test_asan_strcpy.c', [
      'AddressSanitizer: heap-buffer-overflow on address',
    ], ['-fno-builtin-strcpy']),
    'memcpy': ('test_asan_memcpy.c', [
      'AddressSanitizer: heap-buffer-overflow on address',
    ], ['-fno-builtin-memcpy']),
    'memchr': ('test_asan_memchr.c', [
      'AddressSanitizer: global-buffer-overflow on address',
    ], ['-fno-builtin-memchr']),
    'vector': ('test_asan_vector.cpp', [
      'AddressSanitizer: container-overflow on address',
    ]),
  })
  def test_asan(self, name, expected_output, cflags=None):
    if '-Oz' in self.cflags:
      self.skipTest('-Oz breaks source maps')

    self.set_setting('ALLOW_MEMORY_GROWTH')
    self.set_setting('INITIAL_MEMORY', '300mb')
    if cflags:
      self.cflags += cflags
    self.do_runf('core/' + name,
                 expected_output=expected_output, assert_all=True, regex=True,
                 cflags=['-fsanitize=address'],
                 check_for_error=False, assert_returncode=NON_ZERO)

  @asan
  def test_asan_js_stack_op(self):
    self.set_setting('ALLOW_MEMORY_GROWTH')
    self.set_setting('INITIAL_MEMORY', '300mb')
    self.do_runf('core/test_asan_js_stack_op.c', 'Hello, World!', cflags=['-fsanitize=address'])

  @asan
  def test_asan_api(self):
    self.set_setting('INITIAL_MEMORY', '300mb')
    self.do_core_test('test_asan_api.c', cflags=['-fsanitize=address'])

  @asan
  @no_omit_asm_module_exports('MODULARIZE is not compatible with DECLARE_ASM_MODULE_EXPORTS=0')
  @no_modularize_instance('uses MODULARIZE')
  def test_asan_modularized_with_closure(self):
    # the bug is that createModule() returns undefined, instead of the
    # proper Promise object.
    create_file('post.js', 'if (!(createModule() instanceof Promise)) throw `Promise was not returned (${typeof createModule()})`;\n')
    self.set_setting('MODULARIZE')
    self.set_setting('EXPORT_NAME', 'createModule')
    self.set_setting('USE_CLOSURE_COMPILER')
    self.set_setting('ALLOW_MEMORY_GROWTH')
    self.set_setting('INITIAL_MEMORY', '300mb')
    self.do_runf_out_file('hello_world.c', cflags=['-fsanitize=address', '--extern-post-js=post.js'])

  def test_safe_stack(self):
    self.set_setting('STACK_OVERFLOW_CHECK', 2)
    self.set_setting('STACK_SIZE', 1024)
    if self.is_optimizing():
      expected = [r'stack overflow \(Attempt to set SP to 0x[0-9a-fA-F]+, with stack limits \[0x[0-9a-fA-F]+ - 0x[0-9a-fA-F]+\]\)']
    else:
      expected = [r'stack overflow \(Attempt to set SP to 0x[0-9a-fA-F]+, with stack limits \[0x[0-9a-fA-F]+ - 0x[0-9a-fA-F]+\]\)',
                  '__handle_stack_overflow']
    self.do_runf('core/test_safe_stack.c',
                 expected_output=expected,
                 regex=True,
                 assert_all=True,
                 assert_returncode=NON_ZERO)

  @requires_pthreads
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26199')
  def test_safe_stack_pthread(self):
    self.set_setting('STACK_OVERFLOW_CHECK', 2)
    self.set_setting('STACK_SIZE', 65536)
    self.set_setting('PROXY_TO_PTHREAD')
    if self.is_optimizing():
      expected = ['stack overflow']
    else:
      expected = ['stack overflow', '__handle_stack_overflow']
    self.do_runf('core/test_safe_stack.c',
                 cflags=['-pthread'],
                 expected_output=expected,
                 assert_returncode=NON_ZERO, assert_all=True)

  def test_safe_stack_alloca(self):
    self.set_setting('STACK_OVERFLOW_CHECK', 2)
    self.set_setting('STACK_SIZE', 65536)
    if self.is_optimizing():
      expected = ['stack overflow']
    else:
      expected = ['stack overflow', '__handle_stack_overflow']
    self.do_runf('core/test_safe_stack_alloca.c',
                 expected_output=expected,
                 assert_returncode=NON_ZERO, assert_all=True)

  @with_dylink_reversed
  def test_safe_stack_dylink(self):
    self.set_setting('STACK_OVERFLOW_CHECK', 2)
    self.set_setting('STACK_SIZE', 65536)
    self.dylink_test(r'''
      #include <stdio.h>
      extern void sidey();
      int main() {
        sidey();
      }
    ''', '''
      #include <string.h>

      static long accumulator = 0;

      int f(int *b) {
        // Infinite recursion while recording stack pointer locations
        // so that compiler can't eliminate the stack allocs.
        accumulator += (long)b;
        int a[1024];
        return f(a);
      }

      void sidey() {
        f(NULL);
      }
    ''', ['stack overflow', '__handle_stack_overflow'], assert_returncode=NON_ZERO, force_c=True)

  def test_fpic_static(self):
    self.do_core_test('test_hello_world.c', cflags=['-fPIC'])

  # Marked as impure since we don't have a wasi-threads is still
  # a WIP.
  # Test is disabled on standalone because of flakes, see
  # https://github.com/emscripten-core/emscripten/issues/18405
  # @also_with_standalone_wasm(impure=True)
  @parameterized({
    '': ([],),
    'sync_instantiation': (['-sWASM_ASYNC_COMPILATION=0'],),
    'proxied': (['-sPROXY_TO_PTHREAD'],),
  })
  @requires_pthreads
  @crossplatform
  def test_pthread_create(self, args):
    if self.get_setting('WASM_ESM_INTEGRATION') and '-sWASM_ASYNC_COMPILATION=0' in args:
      self.skipTest('WASM_ESM_INTEGRATION is not compatible with WASM_ASYNC_COMPILATION=0')
    self.set_setting('ENVIRONMENT', 'node')
    self.set_setting('STRICT')
    # Under node we can always synchronously create threads.
    self.do_core_test('pthread/create.c', cflags=args + ['-DALLOW_SYNC'])

  @requires_pthreads
  @parameterized({
    '': ([],),
    'proxied': (['-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'],),
  })
  def test_pthread_c11_threads(self, args):
    self.set_setting('PTHREADS_DEBUG')
    if not self.has_changed_setting('INITIAL_MEMORY'):
      self.set_setting('INITIAL_MEMORY', '64mb')
    self.set_setting('ENVIRONMENT', 'node')
    self.do_runf_out_file('pthread/test_pthread_c11_threads.c', cflags=args)

  @requires_pthreads
  def test_pthread_cxx_threads(self):
    self.do_runf_out_file('pthread/test_pthread_cxx_threads.cpp')

  @requires_pthreads
  def test_pthread_busy_wait(self):
    self.do_runf_out_file('pthread/test_pthread_busy_wait.cpp')

  @requires_pthreads
  def test_pthread_busy_wait_atexit(self):
    self.set_setting('EXIT_RUNTIME')
    self.do_runf_out_file('pthread/test_pthread_busy_wait_atexit.cpp')

  @requires_pthreads
  def test_pthread_create_embind_stack_check(self):
    # embind should work with stack overflow checks (see #12356)
    self.set_setting('STACK_OVERFLOW_CHECK', 2)
    self.do_core_test('pthread/create.c', cflags=['-sDEFAULT_TO_CXX', '-lembind'])

  @requires_pthreads
  def test_pthread_exceptions(self):
    self.do_core_test('pthread/exceptions.cpp', cflags=['-fexceptions'])

  @requires_pthreads
  def test_pthread_exit_process(self):
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    self.cflags += ['-DEXIT_RUNTIME', '--pre-js', test_file('core/pthread/test_pthread_exit_runtime.pre.js'), '-sINCOMING_MODULE_JS_API=onRuntimeInitialized,onExit']
    self.do_core_test('pthread/test_pthread_exit_runtime.c', assert_returncode=42)

  @requires_pthreads
  def test_pthread_keepalive(self):
    self.do_core_test('pthread/test_pthread_keepalive.c')

  @requires_pthreads
  def test_pthread_weak_ref(self):
    self.do_core_test('pthread/test_pthread_weak_ref.c')

  @requires_pthreads
  def test_pthread_exit_main(self):
    self.do_core_test('pthread/test_pthread_exit_main.c')

  def test_pthread_exit_main_stub(self):
    self.do_core_test('pthread/test_pthread_exit_main.c')

  @requires_pthreads
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26199')
  def test_pthread_unhandledrejection(self):
    # Check that an unhandled promise rejection is propagated to the main thread
    # as an error.
    self.set_setting('PROXY_TO_PTHREAD')
    self.cflags += ['--post-js', test_file('pthread/test_pthread_unhandledrejection.post.js')]
    self.do_runf('pthread/test_pthread_unhandledrejection.c', 'passed')

  @requires_pthreads
  @no_wasm2js('wasm2js does not support PROXY_TO_PTHREAD (custom section support)')
  @also_with_modularize
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26197')
  def test_pthread_return_address(self):
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    if '-sMODULARIZE' in self.cflags:
      self.set_setting('EXPORT_NAME', 'foo')
    self.do_runf('core/test_return_address.c', 'passed', cflags=['-g'])

  @also_with_pthreads
  def test_emscripten_atomics(self):
    self.do_core_test('pthread/test_emscripten_atomics.c')

  @requires_pthreads
  def test_emscripten_futex_api_basics(self):
    # This test explicitly checks behavior of passing NULL to emscripten_futex_wake() so
    # need to disable the `-Wno-nonnull` to disabled these warnings.
    self.do_core_test('pthread/emscripten_futex_api_basics.c', cflags=['-pthread', '-Wno-nonnull'])

  @requires_pthreads
  def test_stdio_locking(self):
    self.do_core_test('test_stdio_locking.c')

  @requires_wasm_workers
  def test_stdio_locking_ww(self):
    # Note: do not combine with test_stdio_locking above because we want to test standalone
    # wasm workers here and `@requires_pthreads` would prevent that.
    self.do_core_test('test_stdio_locking.c', cflags=['-sWASM_WORKERS'])

  @with_dylink_reversed
  @requires_pthreads
  @flaky('Test asani.test_pthread_dylink_basics is flaky due to some kind of racy interaction with asan + -sPROXY_TO_PTHREAD. https://github.com/emscripten-core/emscripten/issues/25211')
  def test_pthread_dylink_basics(self):
    self.cflags.append('-Wno-experimental')
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    self.do_basic_dylink_test()

  @needs_dylink
  @requires_pthreads
  def test_pthread_dylink(self):
    self.cflags += ['-Wno-experimental', '-pthread']
    main = test_file('core/pthread/test_pthread_dylink.c')

    # test with a long .so name, as a regression test for
    # https://github.com/emscripten-core/emscripten/issues/14833
    # where we had a bug with long names + TextDecoder + pthreads + dylink
    very_long_name = 'very_very_very_very_very_very_very_very_very_long.so'

    self.dylink_testf(main, so_name=very_long_name)

  @needs_dylink
  @parameterized({
    '': (['-sNO_AUTOLOAD_DYLIBS'],),
    'autoload': ([],),
  })
  @requires_pthreads
  def test_pthread_dylink_entry_point(self, args):
    self.cflags += ['-Wno-experimental', '-pthread']
    main = test_file('core/pthread/test_pthread_dylink_entry_point.c')
    self.dylink_testf(main, cflags=args)

  @needs_dylink
  @requires_pthreads
  def test_pthread_dylink_exceptions(self):
    self.cflags += ['-Wno-experimental', '-pthread', '-fexceptions']
    self.dylink_testf(test_file('core/pthread/test_pthread_dylink_exceptions.cpp'))

  @needs_dylink
  @parameterized({
    '': ([],),
    'proxied': (['-sPROXY_TO_PTHREAD'],),
  })
  @requires_pthreads
  def test_pthread_dlopen(self, args):
    self.cflags += ['-Wno-experimental', '-pthread']
    self.build_dlfcn_lib(test_file('core/pthread/test_pthread_dlopen_side.c'))

    self.cflags += ['--embed-file', 'libside.so@libside.so']
    self.prep_dlfcn_main()
    self.set_setting('EXIT_RUNTIME')
    self.do_runf('core/pthread/test_pthread_dlopen.c',
                 ['side module ctor', 'done\n', 'side module atexit'],
                 assert_all=True, cflags=args)

  @needs_dylink
  @requires_pthreads
  @flaky('https://github.com/emscripten-core/emscripten/issues/18887')
  def test_pthread_dlopen_many(self):
    # In other suites, this test is flaky.. but in Wasm64 suite, it is failing
    # so much to overcome even the flaky retry count.
    if self.is_wasm64():
     self.skipTest('https://github.com/emscripten-core/emscripten/issues/18887')

    nthreads = 10
    self.cflags += ['-Wno-experimental', '-pthread']
    self.build_dlfcn_lib(test_file('core/pthread/test_pthread_dlopen_side.c'))
    for i in range(nthreads):
      shutil.copy('libside.so', f'libside{i}.so')

    self.prep_dlfcn_main()
    self.set_setting('EXIT_RUNTIME')
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', 'jslib_func')
    create_file('lib.js', r'''
      addToLibrary({
        jslib_func__sig: 'v',
        jslib_func: () => err('hello from js')
      });
    ''')
    self.do_runf('core/pthread/test_pthread_dlopen_many.c',
                 ['side module ctor', 'done\n', 'side module atexit'],
                 cflags=[f'-DNUM_THREADS={nthreads}', '--js-library=lib.js'],
                 assert_all=True)

  @needs_dylink
  @requires_pthreads
  def test_pthread_dlsym(self):
    self.cflags += ['-Wno-experimental', '-pthread']
    self.build_dlfcn_lib(test_file('core/pthread/test_pthread_dlsym_side.c'))

    self.prep_dlfcn_main()
    self.set_setting('EXIT_RUNTIME')
    self.set_setting('PROXY_TO_PTHREAD')
    self.do_runf('core/pthread/test_pthread_dlsym.c')

  @needs_dylink
  @requires_pthreads
  def test_pthread_dylink_tls(self):
    self.cflags += ['-Wno-experimental', '-pthread']
    self.dylink_testf(test_file('core/pthread/test_pthread_dylink_tls.c'))

  @needs_dylink
  @requires_pthreads
  def test_pthread_dylink_longjmp(self):
    self.cflags += ['-Wno-experimental', '-pthread']
    self.dylink_testf(test_file('core/pthread/test_pthread_dylink_longjmp.c'))

  @needs_dylink
  @requires_pthreads
  @no_js_math('JS_MATH is not compatible with MAIN_MODULE=1')
  def test_pthread_dylink_main_module_1(self):
    # TODO: For some reason, -lhtml5 must be passed in -sSTRICT mode, but can NOT
    # be passed when not compiling in -sSTRICT mode. That does not seem intentional?
    if self.get_setting('STRICT'):
      self.cflags += ['-lhtml5']
    self.set_setting('MAIN_MODULE')
    self.do_runf('hello_world.c', cflags=['-Wno-experimental', '-pthread'])

  @with_dylink_reversed
  @parameterized({
    '': ([],),
    'pthreads': (['-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME', '-pthread', '-Wno-experimental'],),
  })
  def test_Module_dynamicLibraries(self, args):
    # test that Module.dynamicLibraries works with pthreads
    self.cflags += args
    self.cflags += ['--pre-js', 'pre.js', '-sINCOMING_MODULE_JS_API=dynamicLibraries']
    self.cflags += ['--js-library', 'lib.js']
    # This test is for setting dynamicLibraries at runtime, so we don't
    # want emscripten loading `libside.so` automatically (which it would
    # do without this setting)
    self.set_setting('NO_AUTOLOAD_DYLIBS')

    create_file('pre.js', '''
      if (typeof ENVIRONMENT_IS_PTHREAD == 'undefined' || !ENVIRONMENT_IS_PTHREAD) {
        // Load libside.so on the main thread, this would be equivalent to
        // defining it outside the module (e.g. in MODULARIZE mode).
        Module['dynamicLibraries'] = ['libside.so'];
      }
    ''')

    create_file('lib.js', '''
      addToLibrary({
        mainCallback: () => {
#if PTHREADS
          err('sharedModules: ' + Object.keys(sharedModules));
          assert('libside.so' in sharedModules);
          assert(sharedModules['libside.so'] instanceof WebAssembly.Module);
#endif
        },
      })
    ''')

    if args:
      self.require_pthreads()

    self.dylink_test(
      r'''
        void mainCallback();

        #include <stdio.h>
        int side();
        int main() {
          mainCallback();
          printf("result is %d\n", side());
          return 0;
        }
      ''',
      r'''
        int side() { return 42; }
      ''',
      'result is 42',
      force_c=True)

  # Tests the emscripten_get_exported_function() API.
  @also_with_minimal_runtime
  @no_esm_integration('depends on wasmExports')
  def test_get_exported_function(self):
    self.do_core_test('test_get_exported_function.cpp', cflags=['-sALLOW_TABLE_GROWTH', '-lexports.js'])

  # Marked as impure since the WASI reactor modules (modules without main)
  # are not yet suppored by the wasm engines we test against.
  @also_with_standalone_wasm(impure=True)
  def test_undefined_main(self):
    if self.get_setting('STANDALONE_WASM'):
      # In standalone we don't support implicitly building without main.  The user has to explicitly
      # opt out (see below).
      self.assert_fail([EMCC, test_file('core/test_ctors_no_main.cpp')] + self.get_cflags(), 'undefined symbol: main')
    elif not self.get_setting('STRICT'):
      # Traditionally in emscripten we allow main to be implicitly undefined.  This allows programs
      # with a main and libraries without a main to be compiled identically.
      # However we are trying to move away from that model to a more explicit opt-out model. See:
      # https://github.com/emscripten-core/emscripten/issues/9640
      self.do_core_test('test_ctors_no_main.cpp')

      # Disabling IGNORE_MISSING_MAIN should cause link to fail due to missing main
      self.set_setting('IGNORE_MISSING_MAIN', 0)
      expected = 'error: entry symbol not defined (pass --no-entry to suppress): main'
      self.assert_fail([EMCC, test_file('core/test_ctors_no_main.cpp')] + self.get_cflags(), expected)

      # In non-standalone mode exporting an empty list of functions signal that we don't
      # have a main and so should not generate an error.
      self.set_setting('EXPORTED_FUNCTIONS', [])
      self.do_core_test('test_ctors_no_main.cpp')
      self.clear_setting('EXPORTED_FUNCTIONS')

  # Marked as impure since the WASI reactor modules (modules without main)
  # are not yet supported by the wasm engines we test against.
  @also_with_standalone_wasm(impure=True)
  def test_undefined_main_explicit(self):
    # If we pass --no-entry this test should compile without issue
    self.do_core_test('test_ctors_no_main.cpp', cflags=['--no-entry'])

  def test_undefined_main_wasm_output(self):
    if not can_do_standalone(self):
      self.skipTest('standalone mode only')
    self.assert_fail([EMCC, '-o', 'out.wasm', test_file('core/test_ctors_no_main.cpp')] + self.get_cflags(), 'undefined symbol: main')

  @no_2gb('crashed wasmtime')
  def test_export_start(self):
    if not can_do_standalone(self):
      self.skipTest('standalone mode only')
    self.set_setting('STANDALONE_WASM')
    self.set_setting('EXPORTED_FUNCTIONS', ['__start'])
    self.do_core_test('test_hello_world.c')

  # Tests the operation of API found in #include <emscripten/math.h>
  def test_emscripten_math(self):
    self.do_core_test('test_emscripten_math.c')

  # Tests <emscripten/stack.h> API
  @no_asan('stack allocation sizes are no longer predictable')
  def test_emscripten_stack(self):
    self.set_setting('STACK_SIZE', 4 * 1024 * 1024)
    self.do_core_test('test_stack_get_free.c')

  # Tests settings.ABORT_ON_WASM_EXCEPTIONS
  @no_modularize_instance('ccall is not compatible with MODULARIZE=instance')
  def test_abort_on_exceptions(self):
    self.set_setting('ABORT_ON_WASM_EXCEPTIONS')
    self.set_setting('ALLOW_TABLE_GROWTH')
    self.set_setting('EXPORTED_RUNTIME_METHODS', ['ccall', 'cwrap'])
    self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$addFunction', '$addOnPostRun'])
    self.cflags += ['-lembind', '--post-js', test_file('core/test_abort_on_exceptions_post.js')]
    self.do_core_test('test_abort_on_exceptions.cpp', interleaved_output=False)

  @no_esm_integration('ABORT_ON_WASM_EXCEPTIONS is not compatible with WASM_ESM_INTEGRATION')
  def test_abort_on_exceptions_main(self):
    # The unhandled exception wrappers should not kick in for exceptions thrown during main
    self.set_setting('ABORT_ON_WASM_EXCEPTIONS')
    output = self.do_runf('core/test_abort_on_exceptions_main.c', assert_returncode=NON_ZERO, cflags=['--minify=0'])
    # The exception should make it all the way out
    self.assertContained('[Ee]rror: crash', output, regex=True)
    # And not be translated into abort by makeAbortWrapper
    self.assertNotContained('unhandled exception', output)
    self.assertNotContained('Aborted', output)

  @requires_pthreads
  @no_esm_integration('ABORT_ON_WASM_EXCEPTIONS is not compatible with WASM_ESM_INTEGRATION')
  def test_abort_on_exceptions_pthreads(self):
    self.set_setting('ABORT_ON_WASM_EXCEPTIONS')
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    self.do_core_test('test_hello_world.c')

  @needs_dylink
  @no_js_math('JS_MATH is not compatible with MAIN_MODULE=1')
  def test_gl_main_module(self):
    # TODO: For some reason, -lGL must be passed in -sSTRICT mode, but can NOT
    # be passed when not compiling in -sSTRICT mode. That does not seem intentional?
    if self.get_setting('STRICT'):
      self.cflags += ['-lGL']
    self.set_setting('MAIN_MODULE')
    self.do_runf('core/test_gl_get_proc_address.c', cflags=['-sGL_ENABLE_GET_PROC_ADDRESS'])

  @needs_dylink
  def test_main_module_js_symbol(self):
    self.set_setting('MAIN_MODULE', 2)
    self.do_runf('core/test_main_module_js_symbol.c', cflags=['--js-library', test_file('core/test_main_module_js_symbol.js')])

  def test_emscripten_async_call(self):
    # Depends on `atexit`
    self.set_setting('EXIT_RUNTIME')
    self.do_core_test('test_emscripten_async_call.c')

  @no_asan('asyncify stack operations confuse asan')
  @no_modularize_instance('ASYNCIFY=1 requires DYNCALLS')
  @parameterized({
    '': ([],),
    'no_dynamic_execution': (['-sDYNAMIC_EXECUTION=0'],),
  })
  def test_embind_lib_with_asyncify(self, args):
    self.cflags += [
      '-lembind',
      '-sASYNCIFY',
      '-sASYNCIFY_IMPORTS=sleep_and_return',
      '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$ASSERTIONS',
      '--post-js', test_file('core/embind_lib_with_asyncify.test.js'),
      '--no-entry',
      '-sINCOMING_MODULE_JS_API=onRuntimeInitialized',
      '-sNO_EXIT_RUNTIME',
    ] + args
    # This error here is because delayed_throw occurs during execution, and
    # even though it is caught, its influence on the returncode persists.
    # TODO: Should that be changed?
    self.do_core_test('embind_lib_with_asyncify.cpp', assert_returncode=NON_ZERO)

  @no_asan('asyncify stack operations confuse asan')
  @with_asyncify_and_jspi
  @no_modularize_instance('uses ccall')
  def test_em_async_js(self):
    self.set_setting('EXPORTED_RUNTIME_METHODS', 'ccall')
    self.maybe_closure()
    self.do_core_test('test_em_async_js.c')

  @no_wasm2js('wasm2js does not support reference types')
  @no_sanitize('.s files cannot be sanitized')
  def test_externref(self):
    self.run_process([EMCC, '-c', test_file('core/test_externref.s'), '-o', 'asm.o'] + self.get_cflags(asm_only=True))
    self.cflags += ['--js-library', test_file('core/test_externref.js')]
    self.cflags += ['-mreference-types']
    self.do_core_test('test_externref.c', libraries=['asm.o'])

  @parameterized({
    '': [False],
    'dynlink': [True],
  })
  @requires_node
  @no_wasm2js('wasm2js does not support reference types')
  @no_asan('https://github.com/llvm/llvm-project/pull/83196')
  def test_externref_emjs(self, dynlink):
    self.node_args += shared.node_reference_types_flags(get_nodejs())
    if dynlink:
      self.check_dylink()
      self.set_setting('MAIN_MODULE', 2)
    self.do_core_test('test_externref_emjs.c', cflags=['-mreference-types'])

  @parameterized({
    '': [False],
    'dylink': [True],
  })
  @no_esm_integration('https://github.com/emscripten-core/emscripten/issues/25543')
  @no_omit_asm_module_exports('https://github.com/emscripten-core/emscripten/issues/25550')
  def test_wasm_global(self, dynlink):
    if '-flto' in self.cflags or '-flto=thin' in self.cflags:
      self.skipTest('https://github.com/emscripten-core/emscripten/issues/25555')
    if self.is_wasm2js() and self.is_optimizing():
      self.skipTest('https://github.com/emscripten-core/emscripten/issues/25550')
    if dynlink:
      self.check_dylink()
      self.set_setting('MAIN_MODULE', 2)
    if self.get_setting('WASM_ESM_INTEGRATION'):
      self.cflags.append('-DESM_INTEGRATION')
    self.do_core_test('test_wasm_global.c', cflags=['-sEXPORTED_FUNCTIONS=_main,_my_global'])

  def test_syscall_intercept(self):
    self.do_core_test('test_syscall_intercept.c')

  @requires_pthreads
  @no_wasmfs('https://github.com/emscripten-core/emscripten/issues/26736')
  def test_select_blocking(self):
    self.do_runf('core/test_select_blocking.c', cflags=['-pthread', '-sPROXY_TO_PTHREAD=1', '-sEXIT_RUNTIME=1'])

  @requires_pthreads
  @no_wasmfs('https://github.com/emscripten-core/emscripten/issues/26736')
  def test_pselect_blocking(self):
    self.do_runf('core/test_pselect_blocking.c', cflags=['-pthread', '-sPROXY_TO_PTHREAD=1', '-sEXIT_RUNTIME=1'])

  @requires_pthreads
  @no_wasmfs('https://github.com/emscripten-core/emscripten/issues/26736')
  def test_poll_blocking(self):
    self.do_runf('core/test_poll_blocking.c', cflags=['-pthread', '-sPROXY_TO_PTHREAD=1', '-sEXIT_RUNTIME=1'])

  @requires_pthreads
  @no_wasmfs('https://github.com/emscripten-core/emscripten/issues/26736')
  def test_ppoll_blocking(self):
    self.do_runf('core/test_ppoll_blocking.c', cflags=['-pthread', '-sPROXY_TO_PTHREAD=1', '-sEXIT_RUNTIME=1'])

  @with_asyncify_and_jspi
  @no_wasmfs('https://github.com/emscripten-core/emscripten/issues/26736')
  def test_poll_blocking_asyncify(self):
    if self.get_setting('JSPI') and engine_is_v8(self.get_current_js_engine()):
      self.skipTest('test requires setTimeout which is not supported under v8')
    self.do_runf('core/test_poll_blocking_asyncify.c', 'done\n')

  # Include @requires_node_25 explictly here so that this test will be disabled
  # by EMTEST_SKIP_NODE_25.  Without this, the `requires_pthreads` and `requires_jspi` can
  # end with conflicting requirements because we often run with both v8 (which satisfies
  # the `requires_jspi` part have node 22 (which satisfies the `requires_pthreads` part).
  # FIXME: This should not be needed.
  @requires_node_25
  @with_asyncify_and_jspi
  @requires_pthreads
  @no_wasmfs('https://github.com/emscripten-core/emscripten/issues/26736')
  def test_poll_blocking_asyncify_pthread(self):
    if self.get_setting('JSPI') and engine_is_v8(self.get_current_js_engine()):
      self.skipTest('test requires setTimeout which is not supported under v8')
    self.do_runf('core/test_poll_blocking.c', cflags=['-pthread', '-sPROXY_TO_PTHREAD=1', '-sEXIT_RUNTIME=1'])

  @parameterized({
    '': ([],),
    'pthread': (['-pthread'],),
  })
  def test_pipe_select(self, args):
    if args:
      self.require_pthreads()
    self.do_runf('core/test_pipe_select.c', cflags=args)

  def test_pipe_pollhup(self):
    self.do_runf('core/test_pipe_pollhup.c', 'done\n')

  @also_without_bigint
  def test_jslib_i64_params(self):
    # Tests the defineI64Param and receiveI64ParamAsI53 helpers that are
    # used to receive i64 argument in syscalls.
    self.do_core_test('test_jslib_i64_params.c', cflags=['--js-library=' + test_file('core/test_jslib_i64_params.js')])

  def test_main_reads_args(self):
    self.run_process([EMCC, '-c', test_file('core/test_main_reads_args_real.c'), '-o', 'real.o'] + self.get_cflags(compile_only=True))
    self.do_core_test('test_main_reads_args.c', cflags=['real.o'], regex=True)

  @requires_node
  def test_promise(self):
    self.do_core_test('test_promise.c')

  @with_asyncify_and_jspi
  def test_promise_await(self):
    self.do_core_test('test_promise_await.c')

  def test_promise_await_error(self):
    # Check that the API is not available when ASYNCIFY is not set
    self.do_runf('core/test_promise_await.c', 'emscripten_promise_await is only available with ASYNCIFY',
                 assert_returncode=NON_ZERO)

  # Include @requires_node_25 explictly here so that this test will be disabled
  # by EMTEST_SKIP_NODE_25.  Without this, the `requires_node` and `requires_jspi` can
  # end with conflicting requirements because we often run with both v8 (which satisfies
  # the `requires_jspi` part have node (which satisfies the `requires_node` part).
  # FIXME: This should not be needed.
  @requires_node_25
  @with_asyncify_and_jspi
  def test_promise_await_unchecked_rejected(self):
    create_file('test.c', r'''
#include <emscripten/promise.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
  em_promise_t p = emscripten_promise_create();
  emscripten_promise_resolve(p, EM_PROMISE_REJECT, (void*)45);

  printf("waiting on promise (unchecked, rejected)\n");
  (void)emscripten_promise_await_unchecked(p);
  printf("ERROR: should not be reached\n");
  __builtin_abort();
  return 1;
}
''')
    # We expect an unhandled rejection, which in Node.js results in a non-zero exit code.
    self.do_runf('test.c', 'UnhandledPromiseRejection', assert_returncode=NON_ZERO)

  @no_modularize_instance('uses Module object directly')
  def test_emscripten_async_load_script(self):
    create_file('script1.js', 'Module._set(456);''')
    create_file('file1.txt', 'first')
    create_file('file2.txt', 'second')
    # `--from-emcc` needed here otherwise the output defines `var Module =` which will shadow the
    # global `Module`.
    self.run_process([FILE_PACKAGER, 'test.data', '--preload', 'file1.txt', 'file2.txt', '--from-emcc', '--js-output=script2.js'])
    self.do_runf('test_emscripten_async_load_script.c', cflags=['-sFORCE_FILESYSTEM'])

  @also_with_minimal_runtime
  @also_with_modularize
  @requires_wasm_workers
  def test_wasm_worker_hello(self):
    if self.is_wasm2js() and '-sMODULARIZE' in self.cflags:
      self.skipTest('WASM2JS + MODULARIZE + WASM_WORKERS is not supported')
    self.maybe_closure()
    self.do_runf_out_file('wasm_worker/hello_wasm_worker.c', cflags=['-sWASM_WORKERS'])

  @requires_wasm_workers
  def test_wasm_worker_exceptions(self):
    self.do_runf('wasm_worker/test_wasm_worker_exceptions.c', 'worker sent an error! Aborted', assert_returncode=NON_ZERO, cflags=['-sWASM_WORKERS'])

  @requires_wasm_workers
  def test_wasm_worker_malloc(self):
    self.do_runf_out_file('wasm_worker/malloc_wasm_worker.c', cflags=['-sWASM_WORKERS'])

  @requires_wasm_workers
  def test_wasm_worker_runtime_debug(self):
    self.do_runf('wasm_worker/hello_wasm_worker.c', 'wasm worker starting ...', cflags=['-sWASM_WORKERS', '-sRUNTIME_DEBUG'])

  @requires_wasm_workers
  @also_with_pthreads
  def test_wasm_worker_futex_wait(self):
    self.do_runf('wasm_worker/wasm_worker_futex_wait.c', cflags=['-sWASM_WORKERS'])

  @requires_wasm_workers
  def test_wasm_worker_wait_async(self):
    self.do_runf('atomic/test_wait_async.c', cflags=['-sWASM_WORKERS'])

  @parameterized({
    '': ([],),
    'imported_memory': (['-sIMPORTED_MEMORY'],),
  })
  @esm_integration
  def test_esm_integration_main(self, args):
    self.do_runf_out_file('hello_world.c', cflags=args)

  @esm_integration
  def test_esm_integration(self):
    # TODO(sbc): WASM_ESM_INTEGRATION doesn't currently work with closure.
    # self.maybe_closure()
    self.run_process([EMCC, '-o', 'hello_world.mjs', '-sINCOMING_MODULE_JS_API=arguments', '-sEXPORTED_RUNTIME_METHODS=err', '-sEXPORTED_FUNCTIONS=_main,stringToNewUTF8', test_file('core/test_esm_integration.c')] + self.get_cflags())
    create_file('runner.mjs', '''
      import init, { err, stringToNewUTF8, _main, _foo } from "./hello_world.mjs";
      await init({arguments: ['foo', 'bar']});
      err('this is a pointer:', stringToNewUTF8('hello'));
    ''')
    self.assertContained('Hello, world! (3)', self.run_js('runner.mjs'))
    self.assertFilesMatch(test_file('core/test_esm_integration.expected.mjs'), 'hello_world.mjs')

  @no_omit_asm_module_exports('MODULARIZE is not compatible with DECLARE_ASM_MODULE_EXPORTS=0')
  @no_strict_js('EXPORT_ES6 is not compatible with STRICT_JS')
  def test_modularize_instance_hello(self):
    self.do_core_test('test_hello_world.c', cflags=['-sMODULARIZE=instance', '-Wno-experimental'])

  @no_omit_asm_module_exports('MODULARIZE is not compatible with DECLARE_ASM_MODULE_EXPORTS=0')
  @no_strict_js('EXPORT_ES6 is not compatible with STRICT_JS')
  @also_with_pthreads
  def test_modularize_instance(self):
    create_file('library.js', '''\
    addToLibrary({
      $baz: () => console.log('baz'),
      $qux: () => console.log('qux'),
    });''')
    self.run_process([EMCC, test_file('modularize_instance.c'),
                      '-sMODULARIZE=instance',
                      '-Wno-experimental',
                      '-sEXPORTED_RUNTIME_METHODS=baz,addOnExit,HEAP32,runtimeKeepalivePush,runtimeKeepalivePop',
                      '-sEXPORTED_FUNCTIONS=_bar,_main,qux',
                      '--js-library', 'library.js',
                      '-o', 'modularize_instance.mjs'] + self.get_cflags())

    create_file('runner.mjs', '''
      import { strict as assert } from 'assert';
      import init, { _foo as foo, _bar as bar, baz, qux, addOnExit, HEAP32, runtimeKeepalivePush, runtimeKeepalivePop } from "./modularize_instance.mjs";
      // Keep the runtime alive for asan when EXIT_RUNTIME=1.
      runtimeKeepalivePush();
      await init();
      foo(); // exported with EMSCRIPTEN_KEEPALIVE
      bar(); // exported with EXPORTED_FUNCTIONS
      baz(); // exported library function with EXPORTED_RUNTIME_METHODS
      qux(); // exported library function with EXPORTED_FUNCTIONS
      assert(typeof addOnExit === 'function'); // exported runtime function with EXPORTED_RUNTIME_METHODS
      assert(typeof HEAP32 === 'object'); // exported runtime value by default
      runtimeKeepalivePop();
    ''')

    self.assertContained('main1\nmain2\nfoo\nbar\nbaz\n', self.run_js('runner.mjs'))

  @no_omit_asm_module_exports('MODULARIZE is not compatible with DECLARE_ASM_MODULE_EXPORTS=0')
  @no_4gb('EMBIND_AOT can\'t lower 4gb')
  @no_strict_js('EXPORT_ES6 is not compatible with STRICT_JS')
  def test_modularize_instance_embind(self):
    self.run_process([EMXX, test_file('modularize_instance_embind.cpp'),
                      '-sMODULARIZE=instance',
                      '-Wno-experimental',
                      '-lembind',
                      '-sEXPORTED_RUNTIME_METHODS=runtimeKeepalivePush,runtimeKeepalivePop',
                      '-sEMBIND_AOT',
                      '-o', 'modularize_instance_embind.mjs'] + self.get_cflags())

    create_file('runner.mjs', '''
      import init, { foo, Bar, runtimeKeepalivePush, runtimeKeepalivePop } from "./modularize_instance_embind.mjs";
      // Keep the runtime alive for asan when EXIT_RUNTIME=1
      runtimeKeepalivePush();
      await init();
      foo();
      const bar = new Bar();
      bar.print();
      bar.delete();
      runtimeKeepalivePop();
    ''')

    self.assertContained('main\nfoo\nbar\n', self.run_js('runner.mjs'))

  @disabled('https://github.com/emscripten-core/emscripten/issues/27223')
  @no_esm_integration('fcoverage is not compatible with WASM_ESM_INTEGRATION')
  @no_wasm2js('wasm binary required to produce code coverage results with llvm-cov')
  def test_fcoverage_mapping(self):
    expected = '''\
    1|       |/*
    2|       | * Copyright 2016 The Emscripten Authors.  All rights reserved.
    3|       | * Emscripten is available under two separate licenses, the MIT license and the
    4|       | * University of Illinois/NCSA Open Source License.  Both these licenses can be
    5|       | * found in the LICENSE file.
    6|       | */
    7|       |
    8|       |#include <stdio.h>
    9|       |
   10|      1|int main() {
   11|      1|  printf("Hello, world!\\n");
   12|      1|  return 0;
   13|      1|}

'''
    self.set_setting('NODERAWFS')
    self.set_setting('EXIT_RUNTIME')
    self.do_core_test('test_hello_world.c', cflags=['-fprofile-instr-generate', '-fcoverage-mapping', '-g'])
    self.assertExists('default.profraw')
    self.run_process([LLVM_PROFDATA, 'merge', '-sparse', 'default.profraw', '-o', 'out.profdata'])
    self.assertExists('out.profdata')
    self.assertEqual(expected, self.run_process([LLVM_COV, 'show', 'test_hello_world.wasm', '-instr-profile=out.profdata'], stdout=PIPE).stdout)


# Generate tests for everything
def make_run(name, cflags=None, settings=None, env=None,
             v8_args=None, node_args=None,
             require_wasm64=False,
             init=None):
  if cflags is None:
    cflags = {}
  if env is None:
    env = {}
  if settings is None:
    settings = {}
  if settings:
    # Until we create a way to specify link-time settings separately from compile-time settings
    # we need to pass this flag here to avoid warnings from compile-only commands.
    cflags.append('-Wno-unused-command-line-argument')

  TT = type(name, (TestCoreBase,), dict(run_name=name, env=env, __module__=__name__))  # ruff: ignore[unnecessary-collection-call, undefined-name]

  def tearDown(self):
    try:
      super(TT, self).tearDown()
    finally:
      for k in self.env.keys():
        del os.environ[k]

  TT.tearDown = tearDown

  def setUp(self):
    assert self.__class__.doneSetup
    super(TT, self).setUp()
    for k, v in self.env.items():
      assert k not in os.environ, k + ' should not be in environment'
      os.environ[k] = v

    os.chdir(self.get_dir()) # Ensure the directory exists and go there

    for k, v in settings.items():
      self.set_setting(k, v)

    self.cflags += cflags

    if node_args:
      self.node_args += node_args

    if v8_args:
      self.v8_args += v8_args

    if require_wasm64:
      self.require_wasm64()

    if init:
      init(self)

  TT.setUp = setUp

  return TT


# Note: We add --profiling-funcs to many of these modes (especially
# modes under active development) since it makes debugging test
# failures easier.  The downside of this approach is that we are not
# testing the default mode (i.e. without `--profiling-funcs`).  See:
# https://github.com/emscripten-core/emscripten/pull/15480

# Main wasm test modes
core0 = make_run('core0', cflags=['-O0'])
core0g = make_run('core0g', cflags=['-O0', '-g'])
core1 = make_run('core1', cflags=['-O1'])
core2 = make_run('core2', cflags=['-O2'])
core2g = make_run('core2g', cflags=['-O2', '-g'])
core3 = make_run('core3', cflags=['-O3'])
cores = make_run('cores', cflags=['-Os'])
corez = make_run('corez', cflags=['-Oz'])

# Test >2gb memory addresses
core_2gb = make_run('core_2gb', cflags=['--profiling-funcs'],
                    settings={'INITIAL_MEMORY': '2200mb', 'GLOBAL_BASE': '2gb'})

wasm64 = make_run('wasm64', cflags=['--profiling-funcs', '-m64'], require_wasm64=True)
wasm64_O2 = make_run('wasm64_O2', cflags=['-O2', '-m64'], require_wasm64=True)
# Run the wasm64 tests with all memory offsets > 4gb.  Be careful running this test
# suite with any kind of parallelism.
wasm64_4gb = make_run('wasm64_4gb', cflags=['-m64', '--profiling-funcs'],
                      settings={'INITIAL_MEMORY': '4200mb', 'GLOBAL_BASE': '4gb'},
                      require_wasm64=True)
# MEMORY64=2, or "lowered"
wasm64l = make_run('wasm64l', cflags=['-m64', '-O1', '--profiling-funcs'],
                   settings={'MEMORY64': 2})

lto0 = make_run('lto0', cflags=['-flto', '-O0'])
lto1 = make_run('lto1', cflags=['-flto', '-O1'])
lto2 = make_run('lto2', cflags=['-flto', '-O2'])
lto3 = make_run('lto3', cflags=['-flto', '-O3'])
ltos = make_run('ltos', cflags=['-flto', '-Os'])
ltoz = make_run('ltoz', cflags=['-flto', '-Oz'])

thinlto0 = make_run('thinlto0', cflags=['-flto=thin', '-O0'])
thinlto1 = make_run('thinlto1', cflags=['-flto=thin', '-O1'])
thinlto2 = make_run('thinlto2', cflags=['-flto=thin', '-O2'])
thinlto3 = make_run('thinlto3', cflags=['-flto=thin', '-O3'])
thinltos = make_run('thinltos', cflags=['-flto=thin', '-Os'])
thinltoz = make_run('thinltoz', cflags=['-flto=thin', '-Oz'])

wasm2js0 = make_run('wasm2js0', cflags=['-O0'], settings={'WASM': 0})
wasm2js1 = make_run('wasm2js1', cflags=['-O1'], settings={'WASM': 0})
wasm2js2 = make_run('wasm2js2', cflags=['-O2'], settings={'WASM': 0})
wasm2js3 = make_run('wasm2js3', cflags=['-O3'], settings={'WASM': 0})
wasm2jss = make_run('wasm2jss', cflags=['-Os'], settings={'WASM': 0})
wasm2jsz = make_run('wasm2jsz', cflags=['-Oz'], settings={'WASM': 0})

# Secondary test modes - run directly when there is a specific need

# features

simd2 = make_run('simd2', cflags=['-O2', '-msimd128'])
bulkmem2 = make_run('bulkmem2', cflags=['-O2', '-mbulk-memory'])
wasmfs = make_run('wasmfs', cflags=['-O2', '-DWASMFS'], settings={'WASMFS': 1})

# SAFE_HEAP/STACK_OVERFLOW_CHECK
core0s = make_run('core0s', cflags=['-g'], settings={'SAFE_HEAP': 1})
core2s = make_run('core2s', cflags=['-O2'], settings={'SAFE_HEAP': 1})
core0ss = make_run('core0ss', cflags=['-g'], settings={'STACK_OVERFLOW_CHECK': 2})
core2ss = make_run('core2ss', cflags=['-O2'], settings={'STACK_OVERFLOW_CHECK': 2})

esm_integration = make_run('esm_integration', init=lambda self: self.setup_esm_integration())
instance = make_run('instance', cflags=['-Wno-experimental'], settings={'MODULARIZE': 'instance'})

# Add DEFAULT_TO_CXX=0
strict = make_run('strict', cflags=[], settings={'STRICT': 1})
strict_js = make_run('strict_js', cflags=[], settings={'STRICT_JS': 1})

ubsan = make_run('ubsan', cflags=['-fsanitize=undefined', '--profiling'])
lsan = make_run('lsan', cflags=['-fsanitize=leak', '--profiling'], settings={'ALLOW_MEMORY_GROWTH': 1})
asan = make_run('asan', cflags=['-fsanitize=address', '--profiling'], settings={'ALLOW_MEMORY_GROWTH': 1})
asani = make_run('asani', cflags=['-fsanitize=address', '--profiling', '--pre-js', os.path.join(os.path.dirname(__file__), 'asan-no-leak.js')],
                 settings={'ALLOW_MEMORY_GROWTH': 1})

# Experimental modes (not tested by CI)
minimal0 = make_run('minimal0', cflags=['-g'], settings={'MINIMAL_RUNTIME': 1})
llvmlibc = make_run('llvmlibc', cflags=['-lllvmlibc'])

# To run the big endian test suite (supported by emsdk on a little endian Linux host only):
# 1. sudo apt install -y qemu-user libc6-s390x-cross libstdc++6-s390x-cross
# 2. install emsdk with big-endian node: `git clone https://github.com/emscripten-core/emsdk.git`
#    and `./emsdk install sdk-main-64bit node-big-endian-crosscompile-22.16.0-64bit`
# 3. activate emsdk tools: `./emsdk activate sdk-main-64bit node-big-endian-crosscompile-22.16.0-64bit`
# 4. enter emsdk environment in current terminal with `source ./emsdk_env.sh`
# 5. run some tests in big endian mode: `cd emscripten/main` to enter Emscripten root directory, and run
#       `test/runner bigendian0` to run all tests, or a single test with
#       `test/runner bigendian0.test_jslib_i64_params`

# This setup will still use the native x64 Node.js in Emscripten internal use to compile code, but
# runs all unit tests via qemu on the s390x big endian version of Node.js.
bigendian0 = make_run('bigendian0', cflags=['-O0', '-Wno-experimental'], settings={'SUPPORT_BIG_ENDIAN': 1})
bigendian1 = make_run('bigendian1', cflags=['-O1', '-Wno-experimental'], settings={'SUPPORT_BIG_ENDIAN': 1})
bigendian2 = make_run('bigendian2', cflags=['-O2', '-Wno-experimental'], settings={'SUPPORT_BIG_ENDIAN': 1})
bigendian3 = make_run('bigendian3', cflags=['-O3', '-Wno-experimental'], settings={'SUPPORT_BIG_ENDIAN': 1})
bigendians = make_run('bigendians', cflags=['-Os', '-Wno-experimental'], settings={'SUPPORT_BIG_ENDIAN': 1})
bigendianz = make_run('bigendianz', cflags=['-Oz', '-Wno-experimental'], settings={'SUPPORT_BIG_ENDIAN': 1})

omitexports0 = make_run('omitexports0', cflags=['-O0'], settings={'DECLARE_ASM_MODULE_EXPORTS': 0})

jsmathz = make_run('jsmathz', cflags=['-Oz'], settings={'JS_MATH': 1})

# TestCoreBase is just a shape for the specific subclasses, we don't test it itself
del TestCoreBase
