Skip to content

Commit defb8fb

Browse files
authored
[WebAssembly] Support assembly parsing for new EH (#108668)
This adds assembly parsing support for the new EH (exnref) proposal. `try_table` parsing is a little tricky because catch clause lists use `()` and the multivalue block return types also use `()`. This handles all combinations below: - No return type (void) + no catch list - No return type (void) + catch list - Single return type + no catch list - Single return type + catch list - Multivalue return type + no catch list - Multivalue return type + catch list This does not include AsmTypeCheck support yet. That's the reason why this adds a new test file and use `--no-type-check` in the command line. After the type checker is added as a follow-up, I plan to merge https://github.com/llvm/llvm-project/blob/main/llvm/test/MC/WebAssembly/eh-assembly-legacy.s with this file. (Turning on `-mattr=+exception-handling` adds support for all legacy and new EH instructions in the assembly. `-wasm-enable-exnref` in `llc` only controls which instructions to generate and it doesn't affect `llvm-mc` and assembly parsing.)
1 parent c4a42f6 commit defb8fb

File tree

4 files changed

+322
-14
lines changed

4 files changed

+322
-14
lines changed

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

+137-10
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,23 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
6969
std::vector<unsigned> List;
7070
};
7171

72+
struct CaLOpElem {
73+
uint8_t Opcode;
74+
const MCExpr *Tag;
75+
unsigned Dest;
76+
};
77+
78+
struct CaLOp {
79+
std::vector<CaLOpElem> List;
80+
};
81+
7282
union {
7383
struct TokOp Tok;
7484
struct IntOp Int;
7585
struct FltOp Flt;
7686
struct SymOp Sym;
7787
struct BrLOp BrL;
88+
struct CaLOp CaL;
7889
};
7990

8091
WebAssemblyOperand(SMLoc Start, SMLoc End, TokOp T)
@@ -85,12 +96,16 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
8596
: Kind(Float), StartLoc(Start), EndLoc(End), Flt(F) {}
8697
WebAssemblyOperand(SMLoc Start, SMLoc End, SymOp S)
8798
: Kind(Symbol), StartLoc(Start), EndLoc(End), Sym(S) {}
88-
WebAssemblyOperand(SMLoc Start, SMLoc End)
89-
: Kind(BrList), StartLoc(Start), EndLoc(End), BrL() {}
99+
WebAssemblyOperand(SMLoc Start, SMLoc End, BrLOp B)
100+
: Kind(BrList), StartLoc(Start), EndLoc(End), BrL(B) {}
101+
WebAssemblyOperand(SMLoc Start, SMLoc End, CaLOp C)
102+
: Kind(CatchList), StartLoc(Start), EndLoc(End), CaL(C) {}
90103

91104
~WebAssemblyOperand() {
92105
if (isBrList())
93106
BrL.~BrLOp();
107+
if (isCatchList())
108+
CaL.~CaLOp();
94109
}
95110

96111
bool isToken() const override { return Kind == Token; }
@@ -153,7 +168,15 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
153168
}
154169

155170
void addCatchListOperands(MCInst &Inst, unsigned N) const {
156-
// TODO
171+
assert(N == 1 && isCatchList() && "Invalid CatchList!");
172+
Inst.addOperand(MCOperand::createImm(CaL.List.size()));
173+
for (auto Ca : CaL.List) {
174+
Inst.addOperand(MCOperand::createImm(Ca.Opcode));
175+
if (Ca.Opcode == wasm::WASM_OPCODE_CATCH ||
176+
Ca.Opcode == wasm::WASM_OPCODE_CATCH_REF)
177+
Inst.addOperand(MCOperand::createExpr(Ca.Tag));
178+
Inst.addOperand(MCOperand::createImm(Ca.Dest));
179+
}
157180
}
158181

159182
void print(raw_ostream &OS) const override {
@@ -174,7 +197,7 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
174197
OS << "BrList:" << BrL.List.size();
175198
break;
176199
case CatchList:
177-
// TODO
200+
OS << "CaList:" << CaL.List.size();
178201
break;
179202
}
180203
}
@@ -228,6 +251,7 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
228251
Loop,
229252
Try,
230253
CatchAll,
254+
TryTable,
231255
If,
232256
Else,
233257
Undefined,
@@ -304,6 +328,8 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
304328
return {"try", "end_try/delegate"};
305329
case CatchAll:
306330
return {"catch_all", "end_try"};
331+
case TryTable:
332+
return {"try_table", "end_try_table"};
307333
case If:
308334
return {"if", "end_if"};
309335
case Else:
@@ -571,6 +597,7 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
571597
// proper nesting.
572598
bool ExpectBlockType = false;
573599
bool ExpectFuncType = false;
600+
bool ExpectCatchList = false;
574601
std::unique_ptr<WebAssemblyOperand> FunctionTable;
575602
if (Name == "block") {
576603
push(Block);
@@ -593,12 +620,19 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
593620
} else if (Name == "catch_all") {
594621
if (popAndPushWithSameSignature(Name, Try, CatchAll))
595622
return true;
623+
} else if (Name == "try_table") {
624+
push(TryTable);
625+
ExpectBlockType = true;
626+
ExpectCatchList = true;
596627
} else if (Name == "end_if") {
597628
if (pop(Name, If, Else))
598629
return true;
599630
} else if (Name == "end_try") {
600631
if (pop(Name, Try, CatchAll))
601632
return true;
633+
} else if (Name == "end_try_table") {
634+
if (pop(Name, TryTable))
635+
return true;
602636
} else if (Name == "delegate") {
603637
if (pop(Name, Try))
604638
return true;
@@ -622,7 +656,18 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
622656
ExpectFuncType = true;
623657
}
624658

625-
if (ExpectFuncType || (ExpectBlockType && Lexer.is(AsmToken::LParen))) {
659+
// Returns true if the next tokens are a catch clause
660+
auto PeekCatchList = [&]() {
661+
if (Lexer.isNot(AsmToken::LParen))
662+
return false;
663+
AsmToken NextTok = Lexer.peekTok();
664+
return NextTok.getKind() == AsmToken::Identifier &&
665+
NextTok.getIdentifier().starts_with("catch");
666+
};
667+
668+
// Parse a multivalue block type
669+
if (ExpectFuncType ||
670+
(Lexer.is(AsmToken::LParen) && ExpectBlockType && !PeekCatchList())) {
626671
// This has a special TYPEINDEX operand which in text we
627672
// represent as a signature, such that we can re-build this signature,
628673
// attach it to an anonymous symbol, which is what WasmObjectWriter
@@ -648,6 +693,23 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
648693
Loc.getLoc(), Loc.getEndLoc(), WebAssemblyOperand::SymOp{Expr}));
649694
}
650695

696+
// If we are expecting a catch clause list, try to parse it here.
697+
//
698+
// If there is a multivalue block return type before this catch list, it
699+
// should have been parsed above. If there is no return type before
700+
// encountering this catch list, this means the type is void.
701+
// The case when there is a single block return value and then a catch list
702+
// will be handled below in the 'while' loop.
703+
if (ExpectCatchList && PeekCatchList()) {
704+
if (ExpectBlockType) {
705+
ExpectBlockType = false;
706+
addBlockTypeOperand(Operands, NameLoc, WebAssembly::BlockType::Void);
707+
}
708+
if (parseCatchList(Operands))
709+
return true;
710+
ExpectCatchList = false;
711+
}
712+
651713
while (Lexer.isNot(AsmToken::EndOfStatement)) {
652714
auto &Tok = Lexer.getTok();
653715
switch (Tok.getKind()) {
@@ -661,7 +723,15 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
661723
if (BT == WebAssembly::BlockType::Invalid)
662724
return error("Unknown block type: ", Id);
663725
addBlockTypeOperand(Operands, NameLoc, BT);
726+
ExpectBlockType = false;
664727
Parser.Lex();
728+
// Now that we've parsed a single block return type, if we are
729+
// expecting a catch clause list, try to parse it.
730+
if (ExpectCatchList && PeekCatchList()) {
731+
if (parseCatchList(Operands))
732+
return true;
733+
ExpectCatchList = false;
734+
}
665735
} else {
666736
// Assume this identifier is a label.
667737
const MCExpr *Val;
@@ -703,8 +773,8 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
703773
}
704774
case AsmToken::LCurly: {
705775
Parser.Lex();
706-
auto Op =
707-
std::make_unique<WebAssemblyOperand>(Tok.getLoc(), Tok.getEndLoc());
776+
auto Op = std::make_unique<WebAssemblyOperand>(
777+
Tok.getLoc(), Tok.getEndLoc(), WebAssemblyOperand::BrLOp{});
708778
if (!Lexer.is(AsmToken::RCurly))
709779
for (;;) {
710780
Op->BrL.List.push_back(Lexer.getTok().getIntVal());
@@ -724,10 +794,18 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
724794
return true;
725795
}
726796
}
727-
if (ExpectBlockType && Operands.size() == 1) {
728-
// Support blocks with no operands as default to void.
797+
798+
// If we are still expecting to parse a block type or a catch list at this
799+
// point, we set them to the default/empty state.
800+
801+
// Support blocks with no operands as default to void.
802+
if (ExpectBlockType)
729803
addBlockTypeOperand(Operands, NameLoc, WebAssembly::BlockType::Void);
730-
}
804+
// If no catch list has been parsed, add an empty catch list operand.
805+
if (ExpectCatchList)
806+
Operands.push_back(std::make_unique<WebAssemblyOperand>(
807+
NameLoc, NameLoc, WebAssemblyOperand::CaLOp{}));
808+
731809
if (FunctionTable)
732810
Operands.push_back(std::move(FunctionTable));
733811
Parser.Lex();
@@ -752,6 +830,55 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
752830
return false;
753831
}
754832

833+
bool parseCatchList(OperandVector &Operands) {
834+
auto Op = std::make_unique<WebAssemblyOperand>(
835+
Lexer.getTok().getLoc(), SMLoc(), WebAssemblyOperand::CaLOp{});
836+
SMLoc EndLoc;
837+
838+
while (Lexer.is(AsmToken::LParen)) {
839+
if (expect(AsmToken::LParen, "("))
840+
return true;
841+
842+
auto CatchStr = expectIdent();
843+
if (CatchStr.empty())
844+
return true;
845+
uint8_t CatchOpcode =
846+
StringSwitch<uint8_t>(CatchStr)
847+
.Case("catch", wasm::WASM_OPCODE_CATCH)
848+
.Case("catch_ref", wasm::WASM_OPCODE_CATCH_REF)
849+
.Case("catch_all", wasm::WASM_OPCODE_CATCH_ALL)
850+
.Case("catch_all_ref", wasm::WASM_OPCODE_CATCH_ALL_REF)
851+
.Default(0xff);
852+
if (CatchOpcode == 0xff)
853+
return error(
854+
"Expected catch/catch_ref/catch_all/catch_all_ref, instead got: " +
855+
CatchStr);
856+
857+
const MCExpr *Tag = nullptr;
858+
if (CatchOpcode == wasm::WASM_OPCODE_CATCH ||
859+
CatchOpcode == wasm::WASM_OPCODE_CATCH_REF) {
860+
if (Parser.parseExpression(Tag))
861+
return error("Cannot parse symbol: ", Lexer.getTok());
862+
}
863+
864+
auto &DestTok = Lexer.getTok();
865+
if (DestTok.isNot(AsmToken::Integer))
866+
return error("Expected integer constant, instead got: ", DestTok);
867+
unsigned Dest = DestTok.getIntVal();
868+
Parser.Lex();
869+
870+
EndLoc = Lexer.getTok().getEndLoc();
871+
if (expect(AsmToken::RParen, ")"))
872+
return true;
873+
874+
Op->CaL.List.push_back({CatchOpcode, Tag, Dest});
875+
}
876+
877+
Op->EndLoc = EndLoc;
878+
Operands.push_back(std::move(Op));
879+
return false;
880+
}
881+
755882
bool CheckDataSection() {
756883
if (CurrentState != DataSection) {
757884
auto WS = cast<MCSectionWasm>(getStreamer().getCurrentSectionOnly());

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ void WebAssemblyInstPrinter::printCatchList(const MCInst *MI, unsigned OpNo,
378378
const MCSymbolRefExpr *TagExpr = nullptr;
379379
const MCSymbolWasm *TagSym = nullptr;
380380
if (Op.isExpr()) {
381-
TagExpr = dyn_cast<MCSymbolRefExpr>(Op.getExpr());
381+
TagExpr = cast<MCSymbolRefExpr>(Op.getExpr());
382382
TagSym = cast<MCSymbolWasm>(&TagExpr->getSymbol());
383383
O << TagSym->getName() << " ";
384384
} else {

llvm/test/MC/WebAssembly/basic-assembly-errors.s

+29-3
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,37 @@ test0:
2626
catch_all
2727
# CHECK: error: Block construct type mismatch, expected: end_try, instead got: catch_all
2828
end
29-
# CHECK: Block construct type mismatch, expected: end_try, instead got: end_function
29+
30+
# CHECK: error: Expected integer constant, instead got: )
31+
try_table (catch __cpp_exception)
32+
end_try_table
33+
34+
block
35+
# CHECK: error: invalid operand for instruction
36+
try_table (catch_all 0) i32
37+
i32.const 0
38+
end_try_table
39+
drop
40+
end_block
41+
42+
block
43+
# CHECK: error: Expected identifier, got: )
44+
try_table (catch_all 0) () -> (i32, i32)
45+
i32.const 0
46+
i32.const 0
47+
end_try_table
48+
drop
49+
drop
50+
end_block
51+
52+
# CHECK: error: unknown type: not_catch
53+
try_table (not_catch 0)
54+
55+
# CHECK: Block construct type mismatch, expected: end_try_table, instead got: end_function
56+
end_function
57+
# CHECK: error: Unmatched block construct(s) at function end: try_table
3058
# CHECK: error: Unmatched block construct(s) at function end: catch_all
3159
# CHECK: error: Unmatched block construct(s) at function end: loop
3260
# CHECK: error: Unmatched block construct(s) at function end: try
3361
# CHECK: error: Unmatched block construct(s) at function end: block
3462
# CHECK: error: Unmatched block construct(s) at function end: function
35-
end_function
36-

0 commit comments

Comments
 (0)