Skip to content

Commit

Permalink
[WebAssembly] Add assembly support for final EH proposal (llvm#107917)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
aheejin authored Sep 11, 2024
1 parent 3dad29b commit 6bbf7f0
Show file tree
Hide file tree
Showing 15 changed files with 1,009 additions and 41 deletions.
8 changes: 8 additions & 0 deletions llvm/include/llvm/BinaryFormat/Wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ enum : unsigned {
WASM_OPCODE_I32_RMW_CMPXCHG = 0x48,
};

// Sub-opcodes for catch clauses in a try_table instruction
enum : unsigned {
WASM_OPCODE_CATCH = 0x00,
WASM_OPCODE_CATCH_REF = 0x01,
WASM_OPCODE_CATCH_ALL = 0x02,
WASM_OPCODE_CATCH_ALL_REF = 0x03,
};

enum : unsigned {
WASM_LIMITS_FLAG_NONE = 0x0,
WASM_LIMITS_FLAG_HAS_MAX = 0x1,
Expand Down
10 changes: 9 additions & 1 deletion llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ namespace {
/// WebAssemblyOperand - Instances of this class represent the operands in a
/// parsed Wasm machine instruction.
struct WebAssemblyOperand : public MCParsedAsmOperand {
enum KindTy { Token, Integer, Float, Symbol, BrList } Kind;
enum KindTy { Token, Integer, Float, Symbol, BrList, CatchList } Kind;

SMLoc StartLoc, EndLoc;

Expand Down Expand Up @@ -99,6 +99,7 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
bool isMem() const override { return false; }
bool isReg() const override { return false; }
bool isBrList() const { return Kind == BrList; }
bool isCatchList() const { return Kind == CatchList; }

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

void addCatchListOperands(MCInst &Inst, unsigned N) const {
// TODO
}

void print(raw_ostream &OS) const override {
switch (Kind) {
case Token:
Expand All @@ -168,6 +173,9 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
case BrList:
OS << "BrList:" << BrL.List.size();
break;
case CatchList:
// TODO
break;
}
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,3 +367,44 @@ void WebAssemblyInstPrinter::printWebAssemblySignatureOperand(const MCInst *MI,
}
}
}

void WebAssemblyInstPrinter::printCatchList(const MCInst *MI, unsigned OpNo,
raw_ostream &O) {
unsigned OpIdx = OpNo;
const MCOperand &Op = MI->getOperand(OpIdx++);
unsigned NumCatches = Op.getImm();

auto PrintTagOp = [&](const MCOperand &Op) {
const MCSymbolRefExpr *TagExpr = nullptr;
const MCSymbolWasm *TagSym = nullptr;
assert(Op.isExpr());
TagExpr = dyn_cast<MCSymbolRefExpr>(Op.getExpr());
TagSym = cast<MCSymbolWasm>(&TagExpr->getSymbol());
O << TagSym->getName() << " ";
};

for (unsigned I = 0; I < NumCatches; I++) {
const MCOperand &Op = MI->getOperand(OpIdx++);
O << "(";
switch (Op.getImm()) {
case wasm::WASM_OPCODE_CATCH:
O << "catch ";
PrintTagOp(MI->getOperand(OpIdx++));
break;
case wasm::WASM_OPCODE_CATCH_REF:
O << "catch_ref ";
PrintTagOp(MI->getOperand(OpIdx++));
break;
case wasm::WASM_OPCODE_CATCH_ALL:
O << "catch_all ";
break;
case wasm::WASM_OPCODE_CATCH_ALL_REF:
O << "catch_all_ref ";
break;
}
O << MI->getOperand(OpIdx++).getImm(); // destination
O << ")";
if (I < NumCatches - 1)
O << " ";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class WebAssemblyInstPrinter final : public MCInstPrinter {
raw_ostream &O);
void printWebAssemblySignatureOperand(const MCInst *MI, unsigned OpNo,
raw_ostream &O);
void printCatchList(const MCInst *MI, unsigned OpNo, raw_ostream &O);

// Autogenerated by tblgen.
std::pair<const char *, uint64_t> getMnemonic(const MCInst *MI) override;
Expand Down
30 changes: 30 additions & 0 deletions llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ enum OperandType {
OPERAND_BRLIST,
/// 32-bit unsigned table number.
OPERAND_TABLE,
/// A list of catch clauses for try_table.
OPERAND_CATCH_LIST,
};
} // end namespace WebAssembly

Expand Down Expand Up @@ -119,6 +121,10 @@ enum TOF {
// address relative the __table_base wasm global.
// Only applicable to function symbols.
MO_TABLE_BASE_REL,

// On a block signature operand this indicates that this is a destination
// block of a (catch_ref) clause in try_table.
MO_CATCH_BLOCK_SIG,
};

} // end namespace WebAssemblyII
Expand Down Expand Up @@ -462,6 +468,22 @@ inline bool isMarker(unsigned Opc) {
case WebAssembly::TRY_S:
case WebAssembly::END_TRY:
case WebAssembly::END_TRY_S:
case WebAssembly::TRY_TABLE:
case WebAssembly::TRY_TABLE_S:
case WebAssembly::END_TRY_TABLE:
case WebAssembly::END_TRY_TABLE_S:
return true;
default:
return false;
}
}

inline bool isTry(unsigned Opc) {
switch (Opc) {
case WebAssembly::TRY:
case WebAssembly::TRY_S:
case WebAssembly::TRY_TABLE:
case WebAssembly::TRY_TABLE_S:
return true;
default:
return false;
Expand All @@ -474,6 +496,14 @@ inline bool isCatch(unsigned Opc) {
case WebAssembly::CATCH_LEGACY_S:
case WebAssembly::CATCH_ALL_LEGACY:
case WebAssembly::CATCH_ALL_LEGACY_S:
case WebAssembly::CATCH:
case WebAssembly::CATCH_S:
case WebAssembly::CATCH_REF:
case WebAssembly::CATCH_REF_S:
case WebAssembly::CATCH_ALL:
case WebAssembly::CATCH_ALL_S:
case WebAssembly::CATCH_ALL_REF:
case WebAssembly::CATCH_ALL_REF_S:
return true;
default:
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,15 @@ enum class BlockType : unsigned {
Externref = unsigned(wasm::ValType::EXTERNREF),
Funcref = unsigned(wasm::ValType::FUNCREF),
Exnref = unsigned(wasm::ValType::EXNREF),
// Multivalue blocks (and other non-void blocks) are only emitted when the
// blocks will never be exited and are at the ends of functions (see
// WebAssemblyCFGStackify::fixEndsAtEndOfFunction). They also are never made
// to pop values off the stack, so the exact multivalue signature can always
// be inferred from the return type of the parent function in MCInstLower.
// Multivalue blocks are emitted in two cases:
// 1. When the blocks will never be exited and are at the ends of functions
// (see WebAssemblyCFGStackify::fixEndsAtEndOfFunction). In this case the
// exact multivalue signature can always be inferred from the return type
// of the parent function.
// 2. (catch_ref ...) clause in try_table instruction. Currently all tags we
// support (cpp_exception and c_longjmp) throws a single i32, so the
// multivalue signature for this case will be (i32, exnref).
// The real multivalue siganture will be added in MCInstLower.
Multivalue = 0xffff,
};

Expand Down
11 changes: 11 additions & 0 deletions llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,17 @@ void WebAssemblyAsmPrinter::emitInstruction(const MachineInstr *MI) {
// This is a compiler barrier that prevents instruction reordering during
// backend compilation, and should not be emitted.
break;
case WebAssembly::CATCH:
case WebAssembly::CATCH_S:
case WebAssembly::CATCH_REF:
case WebAssembly::CATCH_REF_S:
case WebAssembly::CATCH_ALL:
case WebAssembly::CATCH_ALL_S:
case WebAssembly::CATCH_ALL_REF:
case WebAssembly::CATCH_ALL_REF_S:
// These are pseudo instructions to represent catch clauses in try_table
// instruction to simulate block return values.
break;
default: {
WebAssemblyMCInstLower MCInstLowering(OutContext, *this);
MCInst TmpInst;
Expand Down
Loading

0 comments on commit 6bbf7f0

Please sign in to comment.