[llvm] [WebAssembly] Support assembly parsing for new EH (PR #108668)
Heejin Ahn via llvm-commits
llvm-commits at lists.llvm.org
Sun Sep 15 02:35:04 PDT 2024
https://github.com/aheejin updated https://github.com/llvm/llvm-project/pull/108668
>From 04640a300dd5ef5587f593ff82feebc0d5c0fa4e Mon Sep 17 00:00:00 2001
From: Heejin Ahn <aheejin at gmail.com>
Date: Tue, 10 Sep 2024 04:22:35 +0000
Subject: [PATCH 1/4] [WebAssembly] Support assembly parsing for new EH
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 this
file with the existing
https://github.com/llvm/llvm-project/blob/main/llvm/test/MC/WebAssembly/eh-assembly.s.
(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.)
---
.../AsmParser/WebAssemblyAsmParser.cpp | 147 ++++++++++++++++--
llvm/test/MC/WebAssembly/eh-assembly-new.s | 146 +++++++++++++++++
2 files changed, 283 insertions(+), 10 deletions(-)
create mode 100644 llvm/test/MC/WebAssembly/eh-assembly-new.s
diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
index 5299e6ea06f0bd..03ea5b09c4fd4a 100644
--- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
+++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
@@ -69,12 +69,23 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
std::vector<unsigned> List;
};
+ struct CaLOpElem {
+ int64_t Opcode;
+ const MCExpr *Tag;
+ int64_t Dest;
+ };
+
+ struct CaLOp {
+ std::vector<CaLOpElem> List;
+ };
+
union {
struct TokOp Tok;
struct IntOp Int;
struct FltOp Flt;
struct SymOp Sym;
struct BrLOp BrL;
+ struct CaLOp CaL;
};
WebAssemblyOperand(SMLoc Start, SMLoc End, TokOp T)
@@ -85,12 +96,16 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
: Kind(Float), StartLoc(Start), EndLoc(End), Flt(F) {}
WebAssemblyOperand(SMLoc Start, SMLoc End, SymOp S)
: Kind(Symbol), StartLoc(Start), EndLoc(End), Sym(S) {}
- WebAssemblyOperand(SMLoc Start, SMLoc End)
- : Kind(BrList), StartLoc(Start), EndLoc(End), BrL() {}
+ WebAssemblyOperand(SMLoc Start, SMLoc End, BrLOp B)
+ : Kind(BrList), StartLoc(Start), EndLoc(End), BrL(B) {}
+ WebAssemblyOperand(SMLoc Start, SMLoc End, CaLOp C)
+ : Kind(CatchList), StartLoc(Start), EndLoc(End), CaL(C) {}
~WebAssemblyOperand() {
if (isBrList())
BrL.~BrLOp();
+ if (isCatchList())
+ CaL.~CaLOp();
}
bool isToken() const override { return Kind == Token; }
@@ -153,7 +168,15 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
}
void addCatchListOperands(MCInst &Inst, unsigned N) const {
- // TODO
+ assert(N == 1 && isCatchList() && "Invalid CatchList!");
+ Inst.addOperand(MCOperand::createImm(CaL.List.size()));
+ for (auto Ca : CaL.List) {
+ Inst.addOperand(MCOperand::createImm(Ca.Opcode));
+ if (Ca.Opcode == wasm::WASM_OPCODE_CATCH ||
+ Ca.Opcode == wasm::WASM_OPCODE_CATCH_REF)
+ Inst.addOperand(MCOperand::createExpr(Ca.Tag));
+ Inst.addOperand(MCOperand::createImm(Ca.Dest));
+ }
}
void print(raw_ostream &OS) const override {
@@ -174,7 +197,7 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
OS << "BrList:" << BrL.List.size();
break;
case CatchList:
- // TODO
+ OS << "CaList:" << CaL.List.size();
break;
}
}
@@ -228,6 +251,7 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
Loop,
Try,
CatchAll,
+ TryTable,
If,
Else,
Undefined,
@@ -304,6 +328,8 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
return {"try", "end_try/delegate"};
case CatchAll:
return {"catch_all", "end_try"};
+ case TryTable:
+ return {"try_table", "end_try_table"};
case If:
return {"if", "end_if"};
case Else:
@@ -571,6 +597,7 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
// proper nesting.
bool ExpectBlockType = false;
bool ExpectFuncType = false;
+ bool ExpectCatchList = false;
std::unique_ptr<WebAssemblyOperand> FunctionTable;
if (Name == "block") {
push(Block);
@@ -593,12 +620,19 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
} else if (Name == "catch_all") {
if (popAndPushWithSameSignature(Name, Try, CatchAll))
return true;
+ } else if (Name == "try_table") {
+ push(TryTable);
+ ExpectBlockType = true;
+ ExpectCatchList = true;
} else if (Name == "end_if") {
if (pop(Name, If, Else))
return true;
} else if (Name == "end_try") {
if (pop(Name, Try, CatchAll))
return true;
+ } else if (Name == "end_try_table") {
+ if (pop(Name, TryTable))
+ return true;
} else if (Name == "delegate") {
if (pop(Name, Try))
return true;
@@ -622,7 +656,18 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
ExpectFuncType = true;
}
- if (ExpectFuncType || (ExpectBlockType && Lexer.is(AsmToken::LParen))) {
+ // Returns true if the next tokens are a catch clause
+ auto PeekCatchList = [&]() {
+ if (Lexer.isNot(AsmToken::LParen))
+ return false;
+ AsmToken NextTok = Lexer.peekTok();
+ return NextTok.getKind() == AsmToken::Identifier &&
+ NextTok.getIdentifier().starts_with("catch");
+ };
+
+ // Parse a multivalue block type
+ if (ExpectFuncType ||
+ (Lexer.is(AsmToken::LParen) && ExpectBlockType && !PeekCatchList())) {
// This has a special TYPEINDEX operand which in text we
// represent as a signature, such that we can re-build this signature,
// attach it to an anonymous symbol, which is what WasmObjectWriter
@@ -648,6 +693,23 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
Loc.getLoc(), Loc.getEndLoc(), WebAssemblyOperand::SymOp{Expr}));
}
+ // If we are expecting a catch clause list, try to parse it here.
+ //
+ // If there is a multivalue block return type before this catch list, it
+ // should have been parsed above. If there is no return type before
+ // encountering this catch list, this means the type is void.
+ // The case when there is a single block return value and then a catch list
+ // will be handled below in the 'while' loop.
+ if (ExpectCatchList && PeekCatchList()) {
+ if (ExpectBlockType) {
+ ExpectBlockType = false;
+ addBlockTypeOperand(Operands, NameLoc, WebAssembly::BlockType::Void);
+ }
+ if (parseCatchList(Operands))
+ return true;
+ ExpectCatchList = false;
+ }
+
while (Lexer.isNot(AsmToken::EndOfStatement)) {
auto &Tok = Lexer.getTok();
switch (Tok.getKind()) {
@@ -661,7 +723,15 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
if (BT == WebAssembly::BlockType::Invalid)
return error("Unknown block type: ", Id);
addBlockTypeOperand(Operands, NameLoc, BT);
+ ExpectBlockType = false;
Parser.Lex();
+ // Now that we've parsed a single block return type, if we are
+ // expecting a catch clause list, try to parse it.
+ if (ExpectCatchList && PeekCatchList()) {
+ if (parseCatchList(Operands))
+ return true;
+ ExpectCatchList = false;
+ }
} else {
// Assume this identifier is a label.
const MCExpr *Val;
@@ -703,8 +773,8 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
}
case AsmToken::LCurly: {
Parser.Lex();
- auto Op =
- std::make_unique<WebAssemblyOperand>(Tok.getLoc(), Tok.getEndLoc());
+ auto Op = std::make_unique<WebAssemblyOperand>(
+ Tok.getLoc(), Tok.getEndLoc(), WebAssemblyOperand::BrLOp{});
if (!Lexer.is(AsmToken::RCurly))
for (;;) {
Op->BrL.List.push_back(Lexer.getTok().getIntVal());
@@ -724,10 +794,18 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
return true;
}
}
- if (ExpectBlockType && Operands.size() == 1) {
- // Support blocks with no operands as default to void.
+
+ // If we are still expecting to parse a block type or a catch list at this
+ // point, we set them to the default/empty state.
+
+ // Support blocks with no operands as default to void.
+ if (ExpectBlockType)
addBlockTypeOperand(Operands, NameLoc, WebAssembly::BlockType::Void);
- }
+ // If no catch list has been parsed, add an empty catch list operand.
+ if (ExpectCatchList)
+ Operands.push_back(std::make_unique<WebAssemblyOperand>(
+ NameLoc, NameLoc, WebAssemblyOperand::CaLOp{}));
+
if (FunctionTable)
Operands.push_back(std::move(FunctionTable));
Parser.Lex();
@@ -752,6 +830,55 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
return false;
}
+ bool parseCatchList(OperandVector &Operands) {
+ auto Op = std::make_unique<WebAssemblyOperand>(
+ Lexer.getTok().getLoc(), SMLoc(), WebAssemblyOperand::CaLOp{});
+ SMLoc EndLoc;
+
+ while (Lexer.is(AsmToken::LParen)) {
+ if (expect(AsmToken::LParen, "("))
+ return true;
+
+ auto CatchStr = expectIdent();
+ if (CatchStr.empty())
+ return true;
+ int64_t CatchOpcode =
+ StringSwitch<int64_t>(CatchStr)
+ .Case("catch", wasm::WASM_OPCODE_CATCH)
+ .Case("catch_ref", wasm::WASM_OPCODE_CATCH_REF)
+ .Case("catch_all", wasm::WASM_OPCODE_CATCH_ALL)
+ .Case("catch_all_ref", wasm::WASM_OPCODE_CATCH_ALL_REF)
+ .Default(-1);
+ if (CatchOpcode == -1)
+ return error(
+ "Expected catch/catch_ref/catch_all/catch_all_ref, instead got: " +
+ CatchStr);
+
+ const MCExpr *Tag = nullptr;
+ if (CatchOpcode == wasm::WASM_OPCODE_CATCH ||
+ CatchOpcode == wasm::WASM_OPCODE_CATCH_REF) {
+ if (Parser.parseExpression(Tag))
+ return error("Cannot parse symbol: ", Lexer.getTok());
+ }
+
+ auto &DestTok = Lexer.getTok();
+ if (DestTok.isNot(AsmToken::Integer))
+ return error("Expected integer constant, instead got: ", DestTok);
+ int64_t Dest = DestTok.getIntVal();
+ Parser.Lex();
+
+ EndLoc = Lexer.getTok().getEndLoc();
+ if (expect(AsmToken::RParen, ")"))
+ return true;
+
+ Op->CaL.List.push_back({CatchOpcode, Tag, Dest});
+ }
+
+ Op->EndLoc = EndLoc;
+ Operands.push_back(std::move(Op));
+ return false;
+ }
+
bool CheckDataSection() {
if (CurrentState != DataSection) {
auto WS = cast<MCSectionWasm>(getStreamer().getCurrentSectionOnly());
diff --git a/llvm/test/MC/WebAssembly/eh-assembly-new.s b/llvm/test/MC/WebAssembly/eh-assembly-new.s
new file mode 100644
index 00000000000000..8069b666d73e53
--- /dev/null
+++ b/llvm/test/MC/WebAssembly/eh-assembly-new.s
@@ -0,0 +1,146 @@
+# RUN: llvm-mc -triple=wasm32-unknown-unknown -mattr=+exception-handling --no-type-check < %s | FileCheck %s
+
+ .tagtype __cpp_exception i32
+ .tagtype __c_longjmp i32
+ .functype eh_test () -> ()
+ .functype foo () -> ()
+
+eh_test:
+ # try_table with all four kinds of catch clauses
+ block exnref
+ block
+ block () -> (i32, exnref)
+ block i32
+ try_table (catch __cpp_exception 0) (catch_ref __c_longjmp 1) (catch_all 2) (catch_all_ref 3)
+ i32.const 0
+ throw __cpp_exception
+ end_try_table
+ end_block
+ drop
+ end_block
+ drop
+ drop
+ end_block
+ end_block
+ drop
+
+ # You can use the same kind of catch clause more than once
+ block
+ block exnref
+ block
+ try_table (catch_all 0) (catch_all_ref 1) (catch_all 2)
+ call foo
+ end_try_table
+ end_block
+ end_block
+ drop
+ end_block
+
+ # Two catch clauses targeting the same block
+ block
+ try_table (catch_all 0) (catch_all 0)
+ end_try_table
+ end_block
+
+ # try_table with a return type
+ block
+ try_table f32 (catch_all 0)
+ f32.const 0.0
+ end_try_table
+ drop
+ end_block
+
+ # try_table with a multivalue type return
+ block
+ try_table () -> (i32, f32) (catch_all 0)
+ i32.const 0
+ f32.const 0.0
+ end_try_table
+ drop
+ drop
+ end_block
+
+ # catch-less try_tables
+ try_table
+ call foo
+ end_try_table
+
+ try_table i32
+ i32.const 0
+ end_try_table
+ drop
+
+ try_table () -> (i32, f32)
+ i32.const 0
+ f32.const 0.0
+ end_try_table
+ drop
+ drop
+
+ end_function
+
+# CHECK-LABEL: eh_test:
+# CHECK-NEXT: block exnref
+# CHECK-NEXT: block
+# CHECK-NEXT: block () -> (i32, exnref)
+# CHECK-NEXT: block i32
+# CHECK-NEXT: try_table (catch __cpp_exception 0) (catch_ref __c_longjmp 1) (catch_all 2) (catch_all_ref 3)
+# CHECK: i32.const 0
+# CHECK-NEXT: throw __cpp_exception
+# CHECK-NEXT: end_try_table
+# CHECK-NEXT: end_block
+# CHECK-NEXT: drop
+# CHECK-NEXT: end_block
+# CHECK-NEXT: drop
+# CHECK-NEXT: drop
+# CHECK-NEXT: end_block
+# CHECK-NEXT: end_block
+# CHECK-NEXT: drop
+
+# CHECK: block
+# CHECK-NEXT: block exnref
+# CHECK-NEXT: block
+# CHECK-NEXT: try_table (catch_all 0) (catch_all_ref 1) (catch_all 2)
+# CHECK: call foo
+# CHECK-NEXT: end_try_table
+# CHECK-NEXT: end_block
+# CHECK-NEXT: end_block
+# CHECK-NEXT: drop
+# CHECK-NEXT: end_block
+
+# CHECK: block
+# CHECK-NEXT: try_table (catch_all 0) (catch_all 0)
+# CHECK: end_try_table
+# CHECK-NEXT: end_block
+
+# CHECK: block
+# CHECK-NEXT: try_table f32 (catch_all 0)
+# CHECK: f32.const 0x0p0
+# CHECK-NEXT: end_try_table
+# CHECK-NEXT: drop
+# CHECK-NEXT: end_block
+
+# CHECK: block
+# CHECK-NEXT: try_table () -> (i32, f32) (catch_all 0)
+# CHECK: i32.const 0
+# CHECK-NEXT: f32.const 0x0p0
+# CHECK-NEXT: end_try_table
+# CHECK-NEXT: drop
+# CHECK-NEXT: drop
+# CHECK-NEXT: end_block
+
+# CHECK: try_table
+# CHECK-NEXT: call foo
+# CHECK-NEXT: end_try_table
+
+# CHECK: try_table i32
+# CHECK-NEXT: i32.const 0
+# CHECK-NEXT: end_try_table
+# CHECK-NEXT: drop
+
+# CHECK: try_table () -> (i32, f32)
+# CHECK-NEXT: i32.const 0
+# CHECK-NEXT: f32.const 0x0p0
+# CHECK-NEXT: end_try_table
+# CHECK-NEXT: drop
+# CHECK-NEXT: drop
>From cbcf191ed3f34a61211f1a1563966dc4bd695f3a Mon Sep 17 00:00:00 2001
From: Heejin Ahn <aheejin at gmail.com>
Date: Sat, 14 Sep 2024 02:09:11 +0000
Subject: [PATCH 2/4] Add throw_ref to tests
---
llvm/test/MC/WebAssembly/eh-assembly-new.s | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/llvm/test/MC/WebAssembly/eh-assembly-new.s b/llvm/test/MC/WebAssembly/eh-assembly-new.s
index 8069b666d73e53..c13ee8ecb30d91 100644
--- a/llvm/test/MC/WebAssembly/eh-assembly-new.s
+++ b/llvm/test/MC/WebAssembly/eh-assembly-new.s
@@ -19,9 +19,10 @@ eh_test:
drop
end_block
drop
- drop
+ throw_ref
end_block
end_block
+ throw_ref
drop
# You can use the same kind of catch clause more than once
@@ -92,10 +93,10 @@ eh_test:
# CHECK-NEXT: drop
# CHECK-NEXT: end_block
# CHECK-NEXT: drop
-# CHECK-NEXT: drop
+# CHECK-NEXT: throw_ref
# CHECK-NEXT: end_block
# CHECK-NEXT: end_block
-# CHECK-NEXT: drop
+# CHECK-NEXT: throw_ref
# CHECK: block
# CHECK-NEXT: block exnref
>From dc379e7c2f6f6bb96c245375ed390404e399291e Mon Sep 17 00:00:00 2001
From: Heejin Ahn <aheejin at gmail.com>
Date: Sun, 15 Sep 2024 07:09:15 +0000
Subject: [PATCH 3/4] test fix
---
llvm/test/MC/WebAssembly/eh-assembly-new.s | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/llvm/test/MC/WebAssembly/eh-assembly-new.s b/llvm/test/MC/WebAssembly/eh-assembly-new.s
index c13ee8ecb30d91..4ecb8c3b0dd875 100644
--- a/llvm/test/MC/WebAssembly/eh-assembly-new.s
+++ b/llvm/test/MC/WebAssembly/eh-assembly-new.s
@@ -15,14 +15,15 @@ eh_test:
i32.const 0
throw __cpp_exception
end_try_table
+ return
end_block
drop
+ return
end_block
- drop
throw_ref
+ drop
end_block
end_block
- throw_ref
drop
# You can use the same kind of catch clause more than once
@@ -33,6 +34,7 @@ eh_test:
call foo
end_try_table
end_block
+ return
end_block
drop
end_block
@@ -89,14 +91,16 @@ eh_test:
# CHECK: i32.const 0
# CHECK-NEXT: throw __cpp_exception
# CHECK-NEXT: end_try_table
+# CHECK-NEXT: return
# CHECK-NEXT: end_block
# CHECK-NEXT: drop
+# CHECK-NEXT: return
# CHECK-NEXT: end_block
-# CHECK-NEXT: drop
# CHECK-NEXT: throw_ref
+# CHECK-NEXT: drop
# CHECK-NEXT: end_block
# CHECK-NEXT: end_block
-# CHECK-NEXT: throw_ref
+# CHECK-NEXT: drop
# CHECK: block
# CHECK-NEXT: block exnref
@@ -105,6 +109,7 @@ eh_test:
# CHECK: call foo
# CHECK-NEXT: end_try_table
# CHECK-NEXT: end_block
+# CHECK-NEXT: return
# CHECK-NEXT: end_block
# CHECK-NEXT: drop
# CHECK-NEXT: end_block
>From 558f3a5380d5e4b6a4d661d7717a9347e5f28b02 Mon Sep 17 00:00:00 2001
From: Heejin Ahn <aheejin at gmail.com>
Date: Sun, 15 Sep 2024 09:34:43 +0000
Subject: [PATCH 4/4] functype location
---
llvm/test/MC/WebAssembly/eh-assembly-new.s | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/llvm/test/MC/WebAssembly/eh-assembly-new.s b/llvm/test/MC/WebAssembly/eh-assembly-new.s
index 4ecb8c3b0dd875..0bae4a3512235f 100644
--- a/llvm/test/MC/WebAssembly/eh-assembly-new.s
+++ b/llvm/test/MC/WebAssembly/eh-assembly-new.s
@@ -2,10 +2,11 @@
.tagtype __cpp_exception i32
.tagtype __c_longjmp i32
- .functype eh_test () -> ()
.functype foo () -> ()
eh_test:
+ .functype eh_test () -> ()
+
# try_table with all four kinds of catch clauses
block exnref
block
More information about the llvm-commits
mailing list