https://github.com/hmelder created https://github.com/llvm/llvm-project/pull/169043
This pull request adds initial support for compiling Objective-C to WebAssembly. I tested my changes with [libobjc2](https://github.com/gnustep/libobjc2) and the [swift-corelibs-blocksruntime](https://github.com/swiftlang/swift-corelibs-blocksruntime/pull/7). There are two outstanding issues, which I cannot fix as deeper knowledge of the subsystems is required: 1. Symbols marked as explicitly hidden in code generation are exported 2. Clang crashes in SelectionDAG when compiling an Objective-C try/catch block with `-fwasm-exceptions` ### First Issue Emscripten is processing the generated `.wasm` file in `emscripten.py` and checks if all exported symbols are valid javascript identifiers ([tools/js_manipulation.py#L104](https://github.com/emscripten-core/emscripten/blob/e43e97f16fd0ed82bb0a1f25d6e1b28b2c8c08c3/tools/js_manipulation.py#L104)). However, hidden symbols such as `.objc_init` are intentionally an invalid C identifier. The core of the problem is that symbols with the `WASM_SYMBOL_NO_STRIP` attribute are exported when targeting Emscripten (https://reviews.llvm.org/D62542). This attribute is added to the symbol during relocation in `WasmObjectWriter::recordRelocation`. So we are accidentally exporting a lot of hidden symbols and not only ones generated by ObjC CG... I'm currently hacking around this by not exporting no-strip symbols. This is the default behaviour for Wasm. ### Second Issue Here is a minimal example that triggers the crash. ```objc #include<stdio.h> int main(void) { int ret = 0; @try { } @catch (id a) { ret = 1; puts("abc"); } return ret; } ``` The following assertion is triggered: ``` clang: /home/vm/llvm-project/llvm/lib/Target/WebAssembly/WebAssemblyExceptionInfo.cpp:124: void llvm::WebAssemblyExceptionInfo::recalculate(MachineFunction &, MachineDominatorTree &, const MachineDominanceFrontier &): Assertion `EHInfo' failed. ``` Here is the crash report [main-c3884.zip](https://github.com/user-attachments/files/23677308/main-c3884.zip). You can use `emcc` with a modified LLVM build by exporting `EM_LLVM_ROOT` before sourcing `emsdk/emsdk_env.sh`: ```sh emcc -fobjc-runtime=gnustep-2.2 -fwasm-exceptions -c main.m ``` or just invoke clang directly: ```sh /home/vm/llvm-build-wasm/bin/clang -target wasm32-unknown-emscripten -mllvm -combiner-global-alias-analysis=false -mllvm -wasm-enable-sjlj -mllvm -wasm-use-legacy-eh=false -mllvm -disable-lsr --sysroot=/home/vm/emsdk/upstream/emscripten/cache/sysroot -DEMSCRIPTEN -fobjc-runtime=gnustep-2.2 -fwasm-exceptions -c main.m ``` ## Building libobjc2 and the BlocksRuntime ### Building the BlocksRuntime ```sh cmake -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_INSTALL_PREFIX=/home/vm/demo-install -DCMAKE_BUILD_TYPE=Debug -B build -G Ninja ``` ### Building libobjc2 ```sh cmake -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_INSTALL_PREFIX=/home/vm/demo-install -DBlocksRuntime_LIBRARIES=/home/vm/demo-install/lib/libBlocksRuntime.a -DBlocksRuntime_INCLUDE_DIR=/home/vm/demo-install/include/BlocksRuntime -DEMBEDDED_BLOCKS_RUNTIME=OFF -DTESTS=OFF -B build -DCMAKE_BUILD_TYPE=Debug -G Ninja ``` >From a07542b0e2274e36a9276ce034697ebcd0d0fee8 Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Fri, 21 Nov 2025 10:42:47 +0000 Subject: [PATCH 1/4] [clang] Whitelist wasm when targeting GNUstep 2.x --- clang/lib/Driver/ToolChains/Clang.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 30d3e5293a31b..6cbec5e17ae1a 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -8001,7 +8001,8 @@ ObjCRuntime Clang::AddObjCRuntimeArgs(const ArgList &args, if ((runtime.getKind() == ObjCRuntime::GNUstep) && (runtime.getVersion() >= VersionTuple(2, 0))) if (!getToolChain().getTriple().isOSBinFormatELF() && - !getToolChain().getTriple().isOSBinFormatCOFF()) { + !getToolChain().getTriple().isOSBinFormatCOFF() && + !getToolChain().getTriple().isOSBinFormatWasm()) { getToolChain().getDriver().Diag( diag::err_drv_gnustep_objc_runtime_incompatible_binary) << runtime.getVersion().getMajor(); >From 90a6ea80ab587a670040c6ec7c574ae29419fb02 Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Fri, 21 Nov 2025 10:50:35 +0000 Subject: [PATCH 2/4] [CodeGen][ObjC] Mangle public symbols for wasm Emscripten requires that exported symbols. See https://github.com/emscripten-core/emscripten/pull/23563. --- clang/lib/CodeGen/CGObjCGNU.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp index 06643d4bdc211..d86aed0d977fa 100644 --- a/clang/lib/CodeGen/CGObjCGNU.cpp +++ b/clang/lib/CodeGen/CGObjCGNU.cpp @@ -179,8 +179,15 @@ class CGObjCGNU : public CGObjCRuntime { (R.getVersion() >= VersionTuple(major, minor)); } - std::string ManglePublicSymbol(StringRef Name) { - return (StringRef(CGM.getTriple().isOSBinFormatCOFF() ? "$_" : "._") + Name).str(); + const std::string ManglePublicSymbol(StringRef Name) { + auto triple = CGM.getTriple(); + + // Exported symbols in Emscripten must be a valid Javascript identifier. + if (triple.isOSBinFormatCOFF() || triple.isOSBinFormatWasm()) { + return (StringRef("$_") + Name).str(); + } else { + return (StringRef("._") + Name).str(); + } } std::string SymbolForProtocol(Twine Name) { >From c1b0453c01e3a1a4f41b413eb6b1c6dfd61dc828 Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Fri, 21 Nov 2025 13:04:33 +0000 Subject: [PATCH 3/4] [CodeGen][ObjC] Fix class_registerAlias_np signature --- clang/lib/CodeGen/CGObjCGNU.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp index d86aed0d977fa..3b9f9f306829d 100644 --- a/clang/lib/CodeGen/CGObjCGNU.cpp +++ b/clang/lib/CodeGen/CGObjCGNU.cpp @@ -4113,8 +4113,7 @@ llvm::Function *CGObjCGNU::ModuleInitFunction() { if (!ClassAliases.empty()) { llvm::Type *ArgTypes[2] = {PtrTy, PtrToInt8Ty}; llvm::FunctionType *RegisterAliasTy = - llvm::FunctionType::get(Builder.getVoidTy(), - ArgTypes, false); + llvm::FunctionType::get(BoolTy, ArgTypes, false); llvm::Function *RegisterAlias = llvm::Function::Create( RegisterAliasTy, llvm::GlobalValue::ExternalWeakLinkage, "class_registerAlias_np", >From 0233bd22d901de804558cd40435a7c6c0fa770c7 Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Fri, 21 Nov 2025 13:07:10 +0000 Subject: [PATCH 4/4] [Wasm] HACK: Do not export hidden symbols --- llvm/lib/MC/WasmObjectWriter.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/llvm/lib/MC/WasmObjectWriter.cpp b/llvm/lib/MC/WasmObjectWriter.cpp index 15590b31fd07f..d882146e21b8a 100644 --- a/llvm/lib/MC/WasmObjectWriter.cpp +++ b/llvm/lib/MC/WasmObjectWriter.cpp @@ -1794,9 +1794,6 @@ uint64_t WasmObjectWriter::writeOneObject(MCAssembler &Asm, Flags |= wasm::WASM_SYMBOL_UNDEFINED; if (WS.isNoStrip()) { Flags |= wasm::WASM_SYMBOL_NO_STRIP; - if (isEmscripten()) { - Flags |= wasm::WASM_SYMBOL_EXPORTED; - } } if (WS.hasImportName()) Flags |= wasm::WASM_SYMBOL_EXPLICIT_NAME; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
