From bb9b3e5226d9f3bd0e48c4230b0742e55cd0c3f8 Mon Sep 17 00:00:00 2001 From: leehyeonseop Date: Mon, 20 Apr 2026 04:00:53 +0000 Subject: [PATCH 1/3] freetype port: add variant for standardized Wasm EH (WASM_LEGACY_EXCEPTIONS=0) --- tools/ports/freetype.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/ports/freetype.py b/tools/ports/freetype.py index d99a6c0059975..13266fda2e9ff 100644 --- a/tools/ports/freetype.py +++ b/tools/ports/freetype.py @@ -10,7 +10,10 @@ PKG_VERSION = '26.2.20' HASH = 'ce413487c24e689631d705f53b64725256f89fffe9aade7cf07bbd785a9cd49eb6b8d2297a55554f3fee0a50b17e8af78f505cdab565768afab833794f968c2f' -variants = {'freetype-legacysjlj': {'SUPPORT_LONGJMP': 'wasm', 'WASM_LEGACY_EXCEPTIONS': 1}} +variants = { + 'freetype-legacysjlj': {'SUPPORT_LONGJMP': 'wasm', 'WASM_LEGACY_EXCEPTIONS': 1}, + 'freetype-wasmsjlj': {'SUPPORT_LONGJMP': 'wasm', 'WASM_LEGACY_EXCEPTIONS': 0}, +} deps = ['zlib'] @@ -20,7 +23,10 @@ def needed(settings): def get_lib_name(settings): if settings.SUPPORT_LONGJMP == 'wasm': - return 'libfreetype-legacysjlj.a' + if settings.WASM_LEGACY_EXCEPTIONS: + return 'libfreetype-legacysjlj.a' + else: + return 'libfreetype-wasmsjlj.a' else: return 'libfreetype.a' @@ -97,6 +103,9 @@ def create(final): if settings.SUPPORT_LONGJMP == 'wasm': flags.append('-sSUPPORT_LONGJMP=wasm') + if not settings.WASM_LEGACY_EXCEPTIONS: + flags.append('-fwasm-exceptions') + flags.append('-sWASM_LEGACY_EXCEPTIONS=0') ports.make_pkg_config('freetype2', PKG_VERSION, '-sUSE_FREETYPE') ports.build_port(source_path, final, 'freetype', flags=flags, srcs=srcs) From 87ffe6a60b9b397c3059be19cf3c34fde521d134 Mon Sep 17 00:00:00 2001 From: leehyeonseop Date: Tue, 21 Apr 2026 02:32:12 +0000 Subject: [PATCH 2/3] add test for freetype port with standardized Wasm EH Verify that the correct port variant (libfreetype-wasmsjlj.a) is selected when building with -fwasm-exceptions -sWASM_LEGACY_EXCEPTIONS=0 -sSUPPORT_LONGJMP=wasm. Without the fix, libfreetype-legacysjlj.a is used instead, producing a binary with mixed legacy and new EH instructions that fails at instantiation time in browsers. --- test/test_freetype_exc.cpp | 31 +++++++++++++++++++++++++++++++ test/test_other.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 test/test_freetype_exc.cpp diff --git a/test/test_freetype_exc.cpp b/test/test_freetype_exc.cpp new file mode 100644 index 0000000000000..dfe45d4ed2075 --- /dev/null +++ b/test/test_freetype_exc.cpp @@ -0,0 +1,31 @@ +/* + * Minimal test that links FreeType and exercises C++ exception handling. + * When the freetype port is built with legacy EH but the application uses + * standardized Wasm EH (-sWASM_LEGACY_EXCEPTIONS=0), the resulting .wasm + * contains a mix of legacy and new EH instructions and fails at + * instantiation time. + */ +#include +#include +#include +#include FT_FREETYPE_H + +int main() { + // Exercise C++ exception handling so that new-EH instructions are emitted. + try { + throw std::runtime_error("test"); + } catch (const std::exception& e) { + printf("caught: %s\n", e.what()); + } + + // Also call into FreeType so it is actually linked. + FT_Library library; + FT_Error error = FT_Init_FreeType(&library); + if (error) { + printf("FT_Init_FreeType failed: %d\n", error); + return 1; + } + printf("FreeType initialized successfully\n"); + FT_Done_FreeType(library); + return 0; +} diff --git a/test/test_other.py b/test/test_other.py index 409547a34de02..21e556c08fd7c 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -2632,6 +2632,39 @@ def test_freetype_with_pthreads(self): # Verify that freetype supports compilation requiring pthreads self.emcc('test_freetype.c', ['-pthread', '-sUSE_FREETYPE', '-o', 'a.out.js']) + @requires_network + def test_freetype_wasm_eh(self): + # Verify that the freetype port selects the correct new-EH variant + # (libfreetype-wasmsjlj.a) when using standardized Wasm EH combined with + # SUPPORT_LONGJMP=wasm. Without the new-EH variant the port falls back to + # libfreetype-legacysjlj.a which is built with legacy EH instructions, + # causing "module uses a mix of legacy and new exception handling + # instructions" at instantiation time in browsers. + # Node.js with --experimental-wasm-exnref tolerates mixed EH, so we + # verify correctness by checking the cached library name and also + # confirming the program runs. + self.require_wasm_eh() + + # Build with -v and capture stderr to verify the correct variant is used. + proc = self.run_process([ + EMCC, '-v', + test_file('test_freetype_exc.cpp'), + '-fwasm-exceptions', + '-sUSE_FREETYPE', + '-sSUPPORT_LONGJMP=wasm', + '-sWASM_LEGACY_EXCEPTIONS=0', + '-o', 'test_out.js', + ], stderr=PIPE) + self.assertContained('libfreetype-wasmsjlj', proc.stderr) + self.assertNotContained('libfreetype-legacysjlj', proc.stderr) + + # Also verify the program actually runs. + self.do_runf('test_freetype_exc.cpp', 'caught: test\nFreeType initialized successfully\n', cflags=[ + '-fwasm-exceptions', + '-sUSE_FREETYPE', + '-sSUPPORT_LONGJMP=wasm', + ]) + @requires_network def test_icu(self): self.set_setting('USE_ICU') From 16a6301912bdabe1f6c8542b7f8919ffcf47fcc6 Mon Sep 17 00:00:00 2001 From: leehyeonseop Date: Tue, 21 Apr 2026 04:22:15 +0000 Subject: [PATCH 3/3] drop redundant -fwasm-exceptions from freetype port flags -sSUPPORT_LONGJMP=wasm already implies the necessary backend flags (-exception-model=wasm, -wasm-use-legacy-eh=0) so -fwasm-exceptions is not needed for this pure-C library. --- tools/ports/freetype.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/ports/freetype.py b/tools/ports/freetype.py index 13266fda2e9ff..9dae17b25690e 100644 --- a/tools/ports/freetype.py +++ b/tools/ports/freetype.py @@ -104,7 +104,6 @@ def create(final): if settings.SUPPORT_LONGJMP == 'wasm': flags.append('-sSUPPORT_LONGJMP=wasm') if not settings.WASM_LEGACY_EXCEPTIONS: - flags.append('-fwasm-exceptions') flags.append('-sWASM_LEGACY_EXCEPTIONS=0') ports.make_pkg_config('freetype2', PKG_VERSION, '-sUSE_FREETYPE')