
This supports bitcode compilation using `clang -fwasm-exceptions`. --- The current situation: Currently the backend requires two options for Wasm EH: `-wasm-enable-eh` and `-exception-model=wasm`. Wasm SjLj requires two options as well: `-wasm-enable-sjlj` and `-exception-model=wasm`. When using Wasm EH via Emscripten, you only need to pass `-fwasm-exceptions`, and these options will be added within the clang driver. This description will focus on the case of Wasm EH going forward, but Wasm SjLj's case is similar. When you pass `-fwasm-exceptions` to emcc and clang driver, the clang driver adds these options to the command line that calls the clang frontend (`clang -cc1`): `-mllvm -wasm-enable-eh` and `-exception-model=wasm`. `-wasm-enable-eh` is prefixed with `-mllvm`, so it is passed as is to the backend. But `-exception-model` is parsed and processed within the clang frontend and stored in `LangOptions` class. This info is later transferred to `TargetOptions` class, and then eventually passed to `MCAsmInfo` class. All LLVM code queries this `MCAsmInfo` to get the exception model. --- Problem: The problem is the whole `LangOptions` processing is bypassed when compiling bitcode, so the information transfer of `LangOptions` -> `TargetOptions` -> `MCAsmInfo` does not happen. They are all set to `ExceptionHandling::None`, which is the default value. --- What other targets do, and why we can't do the same: Other targets support bitcode compilation by the clang driver, but they can do that by using different triples. For example, X86 target supports multiple triples, each of which has its own subclass of `MCAsmInfo`, so it can hardcode the appropriate exception model within those subclasses' constructors. But we don't have separate triples for each exception mode: none, emscripten, and wasm. --- What this CL does: If we can figure out whether `-wasm-enable-eh` is passed to the backend, we can programatically set the exception model from the backend, rather than requiring it to be passed. So we check `WasmEnableEH` and `WasmEnableSjLj` variables, which are `cl::opt` for `-wasm-enable-eh` and `-wasm-enable-sjlj`, in `WebAssemblyMCAsmInfo` constructor, and if either of them is set, we set `MCAsmInfo.ExceptionType` to Wasm. `TargetOptions` cannot be updated there, so we make sure they are the same later. Fixes https://github.com/emscripten-core/emscripten/issues/15712. Reviewed By: dschuff Differential Revision: https://reviews.llvm.org/D115893
182 lines
6.6 KiB
C++
182 lines
6.6 KiB
C++
//===-- WebAssemblyUtilities.cpp - WebAssembly Utility Functions ----------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file
|
|
/// This file implements several utility functions for WebAssembly.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "WebAssemblyUtilities.h"
|
|
#include "WebAssemblyMachineFunctionInfo.h"
|
|
#include "llvm/CodeGen/MachineInstr.h"
|
|
#include "llvm/CodeGen/MachineLoopInfo.h"
|
|
#include "llvm/MC/MCContext.h"
|
|
using namespace llvm;
|
|
|
|
// Exception handling & setjmp-longjmp handling related options. These are
|
|
// defined here to be shared between WebAssembly and its subdirectories.
|
|
|
|
// Emscripten's asm.js-style exception handling
|
|
cl::opt<bool> WebAssembly::WasmEnableEmEH(
|
|
"enable-emscripten-cxx-exceptions",
|
|
cl::desc("WebAssembly Emscripten-style exception handling"),
|
|
cl::init(false));
|
|
// Emscripten's asm.js-style setjmp/longjmp handling
|
|
cl::opt<bool> WebAssembly::WasmEnableEmSjLj(
|
|
"enable-emscripten-sjlj",
|
|
cl::desc("WebAssembly Emscripten-style setjmp/longjmp handling"),
|
|
cl::init(false));
|
|
// Exception handling using wasm EH instructions
|
|
cl::opt<bool>
|
|
WebAssembly::WasmEnableEH("wasm-enable-eh",
|
|
cl::desc("WebAssembly exception handling"),
|
|
cl::init(false));
|
|
// setjmp/longjmp handling using wasm EH instrutions
|
|
cl::opt<bool>
|
|
WebAssembly::WasmEnableSjLj("wasm-enable-sjlj",
|
|
cl::desc("WebAssembly setjmp/longjmp handling"),
|
|
cl::init(false));
|
|
|
|
// Function names in libc++abi and libunwind
|
|
const char *const WebAssembly::CxaBeginCatchFn = "__cxa_begin_catch";
|
|
const char *const WebAssembly::CxaRethrowFn = "__cxa_rethrow";
|
|
const char *const WebAssembly::StdTerminateFn = "_ZSt9terminatev";
|
|
const char *const WebAssembly::PersonalityWrapperFn =
|
|
"_Unwind_Wasm_CallPersonality";
|
|
|
|
/// Test whether MI is a child of some other node in an expression tree.
|
|
bool WebAssembly::isChild(const MachineInstr &MI,
|
|
const WebAssemblyFunctionInfo &MFI) {
|
|
if (MI.getNumOperands() == 0)
|
|
return false;
|
|
const MachineOperand &MO = MI.getOperand(0);
|
|
if (!MO.isReg() || MO.isImplicit() || !MO.isDef())
|
|
return false;
|
|
Register Reg = MO.getReg();
|
|
return Register::isVirtualRegister(Reg) && MFI.isVRegStackified(Reg);
|
|
}
|
|
|
|
bool WebAssembly::mayThrow(const MachineInstr &MI) {
|
|
switch (MI.getOpcode()) {
|
|
case WebAssembly::THROW:
|
|
case WebAssembly::THROW_S:
|
|
case WebAssembly::RETHROW:
|
|
case WebAssembly::RETHROW_S:
|
|
return true;
|
|
}
|
|
if (isCallIndirect(MI.getOpcode()))
|
|
return true;
|
|
if (!MI.isCall())
|
|
return false;
|
|
|
|
const MachineOperand &MO = getCalleeOp(MI);
|
|
assert(MO.isGlobal() || MO.isSymbol());
|
|
|
|
if (MO.isSymbol()) {
|
|
// Some intrinsics are lowered to calls to external symbols, which are then
|
|
// lowered to calls to library functions. Most of libcalls don't throw, but
|
|
// we only list some of them here now.
|
|
// TODO Consider adding 'nounwind' info in TargetLowering::CallLoweringInfo
|
|
// instead for more accurate info.
|
|
const char *Name = MO.getSymbolName();
|
|
if (strcmp(Name, "memcpy") == 0 || strcmp(Name, "memmove") == 0 ||
|
|
strcmp(Name, "memset") == 0)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
const auto *F = dyn_cast<Function>(MO.getGlobal());
|
|
if (!F)
|
|
return true;
|
|
if (F->doesNotThrow())
|
|
return false;
|
|
// These functions never throw
|
|
if (F->getName() == CxaBeginCatchFn || F->getName() == PersonalityWrapperFn ||
|
|
F->getName() == StdTerminateFn)
|
|
return false;
|
|
|
|
// TODO Can we exclude call instructions that are marked as 'nounwind' in the
|
|
// original LLVm IR? (Even when the callee may throw)
|
|
return true;
|
|
}
|
|
|
|
const MachineOperand &WebAssembly::getCalleeOp(const MachineInstr &MI) {
|
|
switch (MI.getOpcode()) {
|
|
case WebAssembly::CALL:
|
|
case WebAssembly::CALL_S:
|
|
case WebAssembly::RET_CALL:
|
|
case WebAssembly::RET_CALL_S:
|
|
return MI.getOperand(MI.getNumExplicitDefs());
|
|
case WebAssembly::CALL_INDIRECT:
|
|
case WebAssembly::CALL_INDIRECT_S:
|
|
case WebAssembly::RET_CALL_INDIRECT:
|
|
case WebAssembly::RET_CALL_INDIRECT_S:
|
|
return MI.getOperand(MI.getNumExplicitOperands() - 1);
|
|
default:
|
|
llvm_unreachable("Not a call instruction");
|
|
}
|
|
}
|
|
|
|
MCSymbolWasm *WebAssembly::getOrCreateFunctionTableSymbol(
|
|
MCContext &Ctx, const WebAssemblySubtarget *Subtarget) {
|
|
StringRef Name = "__indirect_function_table";
|
|
MCSymbolWasm *Sym = cast_or_null<MCSymbolWasm>(Ctx.lookupSymbol(Name));
|
|
if (Sym) {
|
|
if (!Sym->isFunctionTable())
|
|
Ctx.reportError(SMLoc(), "symbol is not a wasm funcref table");
|
|
} else {
|
|
Sym = cast<MCSymbolWasm>(Ctx.getOrCreateSymbol(Name));
|
|
Sym->setFunctionTable();
|
|
// The default function table is synthesized by the linker.
|
|
Sym->setUndefined();
|
|
}
|
|
// MVP object files can't have symtab entries for tables.
|
|
if (!(Subtarget && Subtarget->hasReferenceTypes()))
|
|
Sym->setOmitFromLinkingSection();
|
|
return Sym;
|
|
}
|
|
|
|
MCSymbolWasm *WebAssembly::getOrCreateFuncrefCallTableSymbol(
|
|
MCContext &Ctx, const WebAssemblySubtarget *Subtarget) {
|
|
StringRef Name = "__funcref_call_table";
|
|
MCSymbolWasm *Sym = cast_or_null<MCSymbolWasm>(Ctx.lookupSymbol(Name));
|
|
if (Sym) {
|
|
if (!Sym->isFunctionTable())
|
|
Ctx.reportError(SMLoc(), "symbol is not a wasm funcref table");
|
|
} else {
|
|
Sym = cast<MCSymbolWasm>(Ctx.getOrCreateSymbol(Name));
|
|
|
|
// Setting Weak ensure only one table is left after linking when multiple
|
|
// modules define the table.
|
|
Sym->setWeak(true);
|
|
|
|
wasm::WasmLimits Limits = {0, 1, 1};
|
|
wasm::WasmTableType TableType = {wasm::WASM_TYPE_FUNCREF, Limits};
|
|
Sym->setType(wasm::WASM_SYMBOL_TYPE_TABLE);
|
|
Sym->setTableType(TableType);
|
|
}
|
|
// MVP object files can't have symtab entries for tables.
|
|
if (!(Subtarget && Subtarget->hasReferenceTypes()))
|
|
Sym->setOmitFromLinkingSection();
|
|
return Sym;
|
|
}
|
|
|
|
// Find a catch instruction from an EH pad.
|
|
MachineInstr *WebAssembly::findCatch(MachineBasicBlock *EHPad) {
|
|
assert(EHPad->isEHPad());
|
|
auto Pos = EHPad->begin();
|
|
// Skip any label or debug instructions. Also skip 'end' marker instructions
|
|
// that may exist after marker placement in CFGStackify.
|
|
while (Pos != EHPad->end() &&
|
|
(Pos->isLabel() || Pos->isDebugInstr() || isMarker(Pos->getOpcode())))
|
|
Pos++;
|
|
if (Pos != EHPad->end() && WebAssembly::isCatch(Pos->getOpcode()))
|
|
return &*Pos;
|
|
return nullptr;
|
|
}
|