Skip to content

Commit 6bbf7f0

Browse files
authored
[WebAssembly] Add assembly support for final EH proposal (#107917)
This adds the basic assembly generation support for the final EH proposal, which was newly adopted in Sep 2023 and advanced into Phase 4 in Jul 2024: https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md This adds support for the generation of new `try_table` and `throw_ref` instruction in .s asesmbly format. This does NOT yet include - Block annotation comment generation for .s format - .o object file generation - .s assembly parsing - Type checking (AsmTypeCheck) - Disassembler - Fixing unwind mismatches in CFGStackify These will be added as follow-up PRs. --- The format for `TRY_TABLE`, both for `MachineInstr` and `MCInst`, is as follows: ``` TRY_TABLE type number_of_catches catch_clauses* ``` where `catch_clause` is ``` catch_opcode tag+ destination ``` `catch_opcode` should be one of 0/1/2/3, which denotes `CATCH`/`CATCH_REF`/`CATCH_ALL`/`CATCH_ALL_REF` respectively. (See `BinaryFormat/Wasm.h`) `tag` exists when the catch is one of `CATCH` or `CATCH_REF`. The MIR format is printed as just the list of raw operands. The (stack-based) assembly instruction supports pretty-printing, including printing `catch` clauses by name, in InstPrinter. In addition to the new instructions `TRY_TABLE` and `THROW_REF`, this adds four pseudo instructions: `CATCH`, `CATCH_REF`, `CATCH_ALL`, and `CATCH_ALL_REF`. These are pseudo instructions to simulate block return values of `catch`, `catch_ref`, `catch_all`, `catch_all_ref` clauses in `try_table` respectively, given that we don't support block return values except for one case (`fixEndsAtEndOfFunction` in CFGStackify). These will be omitted when we lower the instructions to `MCInst` at the end. LateEHPrepare now will have one more stage to covert `CATCH`/`CATCH_ALL`s to `CATCH_REF`/`CATCH_ALL_REF`s when there is a `RETHROW` to rethrow its exception. The pass also converts `RETHROW`s into `THROW_REF`. Note that we still use `RETHROW` as an interim pseudo instruction until we convert them to `THROW_REF` in LateEHPrepare. CFGStackify has a new `placeTryTableMarker` function, which places `try_table`/`end_try_table` markers with a necessary `catch` clause and also `block`/`end_block` markers for the destination of the `catch` clause. In MCInstLower, now we need to support one more case for the multivalue block signature (`catch_ref`'s destination's `(i32, exnref)` return type). InstPrinter has a new routine to print the `catch_list` type, which is used to print `try_table` instructions. The new test, `exception.ll`'s source is the same as `exception-legacy.ll`, with the FileCheck expectations changed. One difference is the commands in this file have `-wasm-enable-exnref` to test the new format, and don't have `-wasm-disable-explicit-locals -wasm-keep-registers`, because the new custom InstPrinter routine to print `catch_list` only works for the stack-based instructions (`_S`), and we can't use `-wasm-keep-registers` for them. As in `exception-legacy.ll`, the FileCheck lines for the new tests do not contain the whole program; they mostly contain only the control flow instructions for readability.
1 parent 3dad29b commit 6bbf7f0

15 files changed

+1009
-41
lines changed

llvm/include/llvm/BinaryFormat/Wasm.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,14 @@ enum : unsigned {
144144
WASM_OPCODE_I32_RMW_CMPXCHG = 0x48,
145145
};
146146

147+
// Sub-opcodes for catch clauses in a try_table instruction
148+
enum : unsigned {
149+
WASM_OPCODE_CATCH = 0x00,
150+
WASM_OPCODE_CATCH_REF = 0x01,
151+
WASM_OPCODE_CATCH_ALL = 0x02,
152+
WASM_OPCODE_CATCH_ALL_REF = 0x03,
153+
};
154+
147155
enum : unsigned {
148156
WASM_LIMITS_FLAG_NONE = 0x0,
149157
WASM_LIMITS_FLAG_HAS_MAX = 0x1,

llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ namespace {
4545
/// WebAssemblyOperand - Instances of this class represent the operands in a
4646
/// parsed Wasm machine instruction.
4747
struct WebAssemblyOperand : public MCParsedAsmOperand {
48-
enum KindTy { Token, Integer, Float, Symbol, BrList } Kind;
48+
enum KindTy { Token, Integer, Float, Symbol, BrList, CatchList } Kind;
4949

5050
SMLoc StartLoc, EndLoc;
5151

@@ -99,6 +99,7 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
9999
bool isMem() const override { return false; }
100100
bool isReg() const override { return false; }
101101
bool isBrList() const { return Kind == BrList; }
102+
bool isCatchList() const { return Kind == CatchList; }
102103

103104
MCRegister getReg() const override {
104105
llvm_unreachable("Assembly inspects a register operand");
@@ -151,6 +152,10 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
151152
Inst.addOperand(MCOperand::createImm(Br));
152153
}
153154

155+
void addCatchListOperands(MCInst &Inst, unsigned N) const {
156+
// TODO
157+
}
158+
154159
void print(raw_ostream &OS) const override {
155160
switch (Kind) {
156161
case Token:
@@ -168,6 +173,9 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
168173
case BrList:
169174
OS << "BrList:" << BrL.List.size();
170175
break;
176+
case CatchList:
177+
// TODO
178+
break;
171179
}
172180
}
173181
};

llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyInstPrinter.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,3 +367,44 @@ void WebAssemblyInstPrinter::printWebAssemblySignatureOperand(const MCInst *MI,
367367
}
368368
}
369369
}
370+
371+
void WebAssemblyInstPrinter::printCatchList(const MCInst *MI, unsigned OpNo,
372+
raw_ostream &O) {
373+
unsigned OpIdx = OpNo;
374+
const MCOperand &Op = MI->getOperand(OpIdx++);
375+
unsigned NumCatches = Op.getImm();
376+
377+
auto PrintTagOp = [&](const MCOperand &Op) {
378+
const MCSymbolRefExpr *TagExpr = nullptr;
379+
const MCSymbolWasm *TagSym = nullptr;
380+
assert(Op.isExpr());
381+
TagExpr = dyn_cast<MCSymbolRefExpr>(Op.getExpr());
382+
TagSym = cast<MCSymbolWasm>(&TagExpr->getSymbol());
383+
O << TagSym->getName() << " ";
384+
};
385+
386+
for (unsigned I = 0; I < NumCatches; I++) {
387+
const MCOperand &Op = MI->getOperand(OpIdx++);
388+
O << "(";
389+
switch (Op.getImm()) {
390+
case wasm::WASM_OPCODE_CATCH:
391+
O << "catch ";
392+
PrintTagOp(MI->getOperand(OpIdx++));
393+
break;
394+
case wasm::WASM_OPCODE_CATCH_REF:
395+
O << "catch_ref ";
396+
PrintTagOp(MI->getOperand(OpIdx++));
397+
break;
398+
case wasm::WASM_OPCODE_CATCH_ALL:
399+
O << "catch_all ";
400+
break;
401+
case wasm::WASM_OPCODE_CATCH_ALL_REF:
402+
O << "catch_all_ref ";
403+
break;
404+
}
405+
O << MI->getOperand(OpIdx++).getImm(); // destination
406+
O << ")";
407+
if (I < NumCatches - 1)
408+
O << " ";
409+
}
410+
}

llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyInstPrinter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class WebAssemblyInstPrinter final : public MCInstPrinter {
4747
raw_ostream &O);
4848
void printWebAssemblySignatureOperand(const MCInst *MI, unsigned OpNo,
4949
raw_ostream &O);
50+
void printCatchList(const MCInst *MI, unsigned OpNo, raw_ostream &O);
5051

5152
// Autogenerated by tblgen.
5253
std::pair<const char *, uint64_t> getMnemonic(const MCInst *MI) override;

llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ enum OperandType {
8787
OPERAND_BRLIST,
8888
/// 32-bit unsigned table number.
8989
OPERAND_TABLE,
90+
/// A list of catch clauses for try_table.
91+
OPERAND_CATCH_LIST,
9092
};
9193
} // end namespace WebAssembly
9294

@@ -119,6 +121,10 @@ enum TOF {
119121
// address relative the __table_base wasm global.
120122
// Only applicable to function symbols.
121123
MO_TABLE_BASE_REL,
124+
125+
// On a block signature operand this indicates that this is a destination
126+
// block of a (catch_ref) clause in try_table.
127+
MO_CATCH_BLOCK_SIG,
122128
};
123129

124130
} // end namespace WebAssemblyII
@@ -462,6 +468,22 @@ inline bool isMarker(unsigned Opc) {
462468
case WebAssembly::TRY_S:
463469
case WebAssembly::END_TRY:
464470
case WebAssembly::END_TRY_S:
471+
case WebAssembly::TRY_TABLE:
472+
case WebAssembly::TRY_TABLE_S:
473+
case WebAssembly::END_TRY_TABLE:
474+
case WebAssembly::END_TRY_TABLE_S:
475+
return true;
476+
default:
477+
return false;
478+
}
479+
}
480+
481+
inline bool isTry(unsigned Opc) {
482+
switch (Opc) {
483+
case WebAssembly::TRY:
484+
case WebAssembly::TRY_S:
485+
case WebAssembly::TRY_TABLE:
486+
case WebAssembly::TRY_TABLE_S:
465487
return true;
466488
default:
467489
return false;
@@ -474,6 +496,14 @@ inline bool isCatch(unsigned Opc) {
474496
case WebAssembly::CATCH_LEGACY_S:
475497
case WebAssembly::CATCH_ALL_LEGACY:
476498
case WebAssembly::CATCH_ALL_LEGACY_S:
499+
case WebAssembly::CATCH:
500+
case WebAssembly::CATCH_S:
501+
case WebAssembly::CATCH_REF:
502+
case WebAssembly::CATCH_REF_S:
503+
case WebAssembly::CATCH_ALL:
504+
case WebAssembly::CATCH_ALL_S:
505+
case WebAssembly::CATCH_ALL_REF:
506+
case WebAssembly::CATCH_ALL_REF_S:
477507
return true;
478508
default:
479509
return false;

llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTypeUtilities.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,15 @@ enum class BlockType : unsigned {
3333
Externref = unsigned(wasm::ValType::EXTERNREF),
3434
Funcref = unsigned(wasm::ValType::FUNCREF),
3535
Exnref = unsigned(wasm::ValType::EXNREF),
36-
// Multivalue blocks (and other non-void blocks) are only emitted when the
37-
// blocks will never be exited and are at the ends of functions (see
38-
// WebAssemblyCFGStackify::fixEndsAtEndOfFunction). They also are never made
39-
// to pop values off the stack, so the exact multivalue signature can always
40-
// be inferred from the return type of the parent function in MCInstLower.
36+
// Multivalue blocks are emitted in two cases:
37+
// 1. When the blocks will never be exited and are at the ends of functions
38+
// (see WebAssemblyCFGStackify::fixEndsAtEndOfFunction). In this case the
39+
// exact multivalue signature can always be inferred from the return type
40+
// of the parent function.
41+
// 2. (catch_ref ...) clause in try_table instruction. Currently all tags we
42+
// support (cpp_exception and c_longjmp) throws a single i32, so the
43+
// multivalue signature for this case will be (i32, exnref).
44+
// The real multivalue siganture will be added in MCInstLower.
4145
Multivalue = 0xffff,
4246
};
4347

llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,17 @@ void WebAssemblyAsmPrinter::emitInstruction(const MachineInstr *MI) {
683683
// This is a compiler barrier that prevents instruction reordering during
684684
// backend compilation, and should not be emitted.
685685
break;
686+
case WebAssembly::CATCH:
687+
case WebAssembly::CATCH_S:
688+
case WebAssembly::CATCH_REF:
689+
case WebAssembly::CATCH_REF_S:
690+
case WebAssembly::CATCH_ALL:
691+
case WebAssembly::CATCH_ALL_S:
692+
case WebAssembly::CATCH_ALL_REF:
693+
case WebAssembly::CATCH_ALL_REF_S:
694+
// These are pseudo instructions to represent catch clauses in try_table
695+
// instruction to simulate block return values.
696+
break;
686697
default: {
687698
WebAssemblyMCInstLower MCInstLowering(OutContext, *this);
688699
MCInst TmpInst;

0 commit comments

Comments
 (0)