diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp index 5299e6ea06f0bd..4fef5fa0ef2208 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 List; }; + struct CaLOpElem { + uint8_t Opcode; + const MCExpr *Tag; + unsigned Dest; + }; + + struct CaLOp { + std::vector 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 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(Tok.getLoc(), Tok.getEndLoc()); + auto Op = std::make_unique( + 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( + 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( + 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; + uint8_t CatchOpcode = + StringSwitch(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(0xff); + if (CatchOpcode == 0xff) + 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); + unsigned 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(getStreamer().getCurrentSectionOnly()); diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyInstPrinter.cpp b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyInstPrinter.cpp index 7255195fbaab31..215722204ba4b3 100644 --- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyInstPrinter.cpp +++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyInstPrinter.cpp @@ -378,7 +378,7 @@ void WebAssemblyInstPrinter::printCatchList(const MCInst *MI, unsigned OpNo, const MCSymbolRefExpr *TagExpr = nullptr; const MCSymbolWasm *TagSym = nullptr; if (Op.isExpr()) { - TagExpr = dyn_cast(Op.getExpr()); + TagExpr = cast(Op.getExpr()); TagSym = cast(&TagExpr->getSymbol()); O << TagSym->getName() << " "; } else { diff --git a/llvm/test/MC/WebAssembly/basic-assembly-errors.s b/llvm/test/MC/WebAssembly/basic-assembly-errors.s index aab3b5b1a9028e..6699b66d04a150 100644 --- a/llvm/test/MC/WebAssembly/basic-assembly-errors.s +++ b/llvm/test/MC/WebAssembly/basic-assembly-errors.s @@ -26,11 +26,37 @@ test0: catch_all # CHECK: error: Block construct type mismatch, expected: end_try, instead got: catch_all end -# CHECK: Block construct type mismatch, expected: end_try, instead got: end_function + +# CHECK: error: Expected integer constant, instead got: ) + try_table (catch __cpp_exception) + end_try_table + + block +# CHECK: error: invalid operand for instruction + try_table (catch_all 0) i32 + i32.const 0 + end_try_table + drop + end_block + + block +# CHECK: error: Expected identifier, got: ) + try_table (catch_all 0) () -> (i32, i32) + i32.const 0 + i32.const 0 + end_try_table + drop + drop + end_block + +# CHECK: error: unknown type: not_catch + try_table (not_catch 0) + +# CHECK: Block construct type mismatch, expected: end_try_table, instead got: end_function + end_function +# CHECK: error: Unmatched block construct(s) at function end: try_table # CHECK: error: Unmatched block construct(s) at function end: catch_all # CHECK: error: Unmatched block construct(s) at function end: loop # CHECK: error: Unmatched block construct(s) at function end: try # CHECK: error: Unmatched block construct(s) at function end: block # CHECK: error: Unmatched block construct(s) at function end: function - end_function - diff --git a/llvm/test/MC/WebAssembly/eh-assembly.s b/llvm/test/MC/WebAssembly/eh-assembly.s new file mode 100644 index 00000000000000..a769bc447d0ba9 --- /dev/null +++ b/llvm/test/MC/WebAssembly/eh-assembly.s @@ -0,0 +1,155 @@ +# 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 foo () -> () + +eh_test: + .functype 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 + return + end_block + drop + return + end_block + throw_ref + drop + end_block + return + 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 + return + 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: 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: return +# CHECK-NEXT: end_block +# CHECK-NEXT: drop +# CHECK-NEXT: return +# CHECK-NEXT: end_block +# CHECK-NEXT: throw_ref +# CHECK-NEXT: drop +# CHECK-NEXT: end_block +# CHECK-NEXT: return +# 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: return +# 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