Index: include/llvm/MC/MCParser/MCTargetAsmParser.h =================================================================== --- include/llvm/MC/MCParser/MCTargetAsmParser.h +++ include/llvm/MC/MCParser/MCTargetAsmParser.h @@ -490,6 +490,9 @@ MCContext &Ctx) { return nullptr; } + + // For any checks or cleanups at the end of parsing. + virtual void onEndOfFile() {} }; } // end namespace llvm Index: lib/MC/MCParser/AsmParser.cpp =================================================================== --- lib/MC/MCParser/AsmParser.cpp +++ lib/MC/MCParser/AsmParser.cpp @@ -898,6 +898,9 @@ eatToEndOfStatement(); } + getTargetParser().onEndOfFile(); + printPendingErrors(); + // All errors should have been emitted. assert(!hasPendingError() && "unexpected error from parseStatement"); Index: lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp =================================================================== --- lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp +++ lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp @@ -172,6 +172,18 @@ Instructions, } CurrentState = FileStart; + // For ensuring blocks are properly nested. + enum NestingType { + Function, + Block, + Loop, + Try, + If, + Else, + Undefined, + }; + std::vector NestingStack; + // We track this to see if a .functype following a label is the same, // as this is how we recognize the start of a function. MCSymbol *LastLabel = nullptr; @@ -184,10 +196,6 @@ setAvailableFeatures(ComputeAvailableFeatures(STI.getFeatureBits())); } - void addSignature(std::unique_ptr &&Sig) { - Signatures.push_back(std::move(Sig)); - } - #define GET_ASSEMBLER_HEADER #include "WebAssemblyGenAsmMatcher.inc" @@ -197,10 +205,59 @@ llvm_unreachable("ParseRegister is not implemented."); } - bool error(const StringRef &Msg, const AsmToken &Tok) { + bool error(const Twine &Msg, const AsmToken &Tok) { return Parser.Error(Tok.getLoc(), Msg + Tok.getString()); } + bool error(const Twine &Msg) { + return Parser.Error(Lexer.getTok().getLoc(), Msg); + } + + void addSignature(std::unique_ptr &&Sig) { + Signatures.push_back(std::move(Sig)); + } + + StringRef nestingString(NestingType NT) { + switch (NT) { + case Function: + return "function"; + case Block: + return "block"; + case Loop: + return "loop"; + case Try: + return "try"; + case If: + return "if"; + case Else: + return "else"; + default: + llvm_unreachable("unknown NestingType"); + } + } + + void push(NestingType NT) { NestingStack.push_back(NT); } + + bool pop(NestingType NT1, NestingType NT2 = Undefined) { + if (NestingStack.empty()) + return error(Twine("End of block construct with no start: ") + + nestingString(NT1) + + (NT2 == Undefined ? "" : Twine(" or ", nestingString(NT2)))); + auto Top = NestingStack.back(); + NestingStack.pop_back(); + if (Top != NT1 && Top != NT2) + return error(Twine("Block construct type mismatch, expected: ") + + nestingString(Top) + ", instead got: " + nestingString(NT1) + + (NT2 == Undefined ? "" : Twine(" or ", nestingString(NT2)))); + return false; + } + + bool ensureEmptyNestingStack() { + return !NestingStack.empty() && + error(Twine("Unmatched block construct(s) at function end: ") + + nestingString(NestingStack.back())); + } + bool isNext(AsmToken::TokenKind Kind) { auto Ok = Lexer.is(Kind); if (Ok) @@ -327,6 +384,45 @@ // If no '.', there is no type prefix. auto BaseName = NamePair.second.empty() ? NamePair.first : NamePair.second; + // If this instruction is part of a control flow structure, ensure + // proper nesting. + if (BaseName == "block") { + push(Block); + } else if (BaseName == "loop") { + push(Loop); + } else if (BaseName == "try") { + push(Try); + } else if (BaseName == "if") { + push(If); + } else if (BaseName == "else") { + if (pop(If)) + return true; + push(Else); + } else if (BaseName == "catch") { + if (pop(Try)) + return true; + push(Try); + } else if (BaseName == "catch_all") { + if (pop(Try)) + return true; + push(Try); + } else if (BaseName == "end_if") { + if (pop(If, Else)) + return true; + } else if (BaseName == "end_try") { + if (pop(Try)) + return true; + } else if (BaseName == "end_loop") { + if (pop(Loop)) + return true; + } else if (BaseName == "end_block") { + if (pop(Block)) + return true; + } else if (BaseName == "end_function") { + if (pop(Function) || ensureEmptyNestingStack()) + return true; + } + while (Lexer.isNot(AsmToken::EndOfStatement)) { auto &Tok = Lexer.getTok(); switch (Tok.getKind()) { @@ -476,7 +572,10 @@ TOut.getStreamer().getContext().getOrCreateSymbol(SymName)); if (CurrentState == Label && WasmSym == LastLabel) { // This .functype indicates a start of a function. + if (ensureEmptyNestingStack()) + return true; CurrentState = FunctionStart; + push(Function); } auto Signature = make_unique(); if (parseSignature(Signature.get())) @@ -565,6 +664,8 @@ } llvm_unreachable("Implement any new match types added!"); } + + void onEndOfFile() override { ensureEmptyNestingStack(); } }; } // end anonymous namespace Index: test/MC/WebAssembly/basic-assembly-errors.s =================================================================== --- /dev/null +++ test/MC/WebAssembly/basic-assembly-errors.s @@ -0,0 +1,22 @@ +# RUN: (llvm-mc -triple=wasm32-unknown-unknown -mattr=+simd128,+nontrapping-fptoint,+exception-handling < %s 2>&1 || true) | FileCheck %s + + .text + .section .text.main,"",@ + .type test0,@function + end_try # missing start +test0: + .functype test0 () -> () + end_loop # mismatched start + block + end_if # mismatched start + try # missing end: unmatched + if # missing end: mismatched + end_function +.Lfunc_end0: + .size test0, .Lfunc_end0-test0 + +# CHECK: End of block construct with no start: try +# CHECK: Block construct type mismatch, expected: function, instead got: loop +# CHECK: Block construct type mismatch, expected: block, instead got: if or else +# CHECK: Block construct type mismatch, expected: if, instead got: function +# CHECK: error: Unmatched block construct(s) at function end: try Index: test/MC/WebAssembly/basic-assembly.s =================================================================== --- test/MC/WebAssembly/basic-assembly.s +++ test/MC/WebAssembly/basic-assembly.s @@ -59,6 +59,11 @@ end_block # default jumps here. i32.const 3 end_block # "switch" exit. + #if # These are not in tablegen defs yet.. + #if + #end_if + #else + #end_if f32x4.add # Test correct parsing of instructions with / and : in them: # TODO: enable once instruction has been added. @@ -129,6 +134,11 @@ # CHECK-NEXT: end_block # label3: # CHECK-NEXT: i32.const 3 # CHECK-NEXT: end_block # label2: +# DONT-CHECK-NEXT: if +# DONT-CHECK-NEXT: if +# DONT-CHECK-NEXT: end_if +# DONT-CHECK-NEXT: else +# DONT-CHECK-NEXT: end_if # CHECK-NEXT: f32x4.add # CHECK-NEXT: i32.trunc_s/f32 # CHECK-NEXT: try Index: test/MC/WebAssembly/simd-encodings.s =================================================================== --- test/MC/WebAssembly/simd-encodings.s +++ test/MC/WebAssembly/simd-encodings.s @@ -1,5 +1,8 @@ # RUN: llvm-mc -show-encoding -triple=wasm32-unkown-unknown -mattr=+sign-ext,+simd128 < %s | FileCheck %s +main: + .functype main () -> () + # CHECK: v128.load 48:p2align=0 # encoding: [0xfd,0x00,0x00,0x30] v128.load 48