diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp index 95db5500b0e1b..5ea6c6bc0758b 100644 --- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp +++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp @@ -498,7 +498,9 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser { void addBlockTypeOperand(OperandVector &Operands, SMLoc NameLoc, WebAssembly::BlockType BT) { - if (BT != WebAssembly::BlockType::Void) { + if (BT == WebAssembly::BlockType::Void) { + TC.setLastSig(wasm::WasmSignature{}); + } else { wasm::WasmSignature Sig({static_cast(BT)}, {}); TC.setLastSig(Sig); NestingStack.back().Sig = Sig; @@ -1002,7 +1004,8 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser { auto *Signature = Ctx.createWasmSignature(); if (parseSignature(Signature)) return ParseStatus::Failure; - TC.funcDecl(*Signature); + if (CurrentState == FunctionStart) + TC.funcDecl(*Signature); WasmSym->setSignature(Signature); WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION); TOut.emitFunctionType(WasmSym); diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp index 6c71460201537..0ac67619691a3 100644 --- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp +++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp @@ -50,8 +50,7 @@ WebAssemblyAsmTypeCheck::WebAssemblyAsmTypeCheck(MCAsmParser &Parser, void WebAssemblyAsmTypeCheck::funcDecl(const wasm::WasmSignature &Sig) { LocalTypes.assign(Sig.Params.begin(), Sig.Params.end()); - ReturnTypes.assign(Sig.Returns.begin(), Sig.Returns.end()); - BrStack.emplace_back(Sig.Returns.begin(), Sig.Returns.end()); + BlockInfoStack.push_back({Sig, 0, false}); } void WebAssemblyAsmTypeCheck::localDecl( @@ -64,14 +63,15 @@ void WebAssemblyAsmTypeCheck::dumpTypeStack(Twine Msg) { } bool WebAssemblyAsmTypeCheck::typeError(SMLoc ErrorLoc, const Twine &Msg) { - // If we're currently in unreachable code, we suppress errors completely. - if (Unreachable) - return false; dumpTypeStack("current stack: "); return Parser.Error(ErrorLoc, Msg); } bool WebAssemblyAsmTypeCheck::match(StackType TypeA, StackType TypeB) { + // These should have been filtered out in checkTypes() + assert(!std::get_if(&TypeA) && + !std::get_if(&TypeB)); + if (TypeA == TypeB) return false; if (std::get_if(&TypeA) || std::get_if(&TypeB)) @@ -90,6 +90,10 @@ std::string WebAssemblyAsmTypeCheck::getTypesString(ArrayRef Types, size_t StartPos) { SmallVector TypeStrs; for (auto I = Types.size(); I > StartPos; I--) { + if (std::get_if(&Types[I - 1])) { + TypeStrs.push_back("..."); + break; + } if (std::get_if(&Types[I - 1])) TypeStrs.push_back("any"); else if (std::get_if(&Types[I - 1])) @@ -131,29 +135,48 @@ bool WebAssemblyAsmTypeCheck::checkTypes(SMLoc ErrorLoc, bool ExactMatch) { auto StackI = Stack.size(); auto TypeI = Types.size(); + assert(!BlockInfoStack.empty()); + auto BlockStackStartPos = BlockInfoStack.back().StackStartPos; bool Error = false; + bool PolymorphicStack = false; // Compare elements one by one from the stack top - for (; StackI > 0 && TypeI > 0; StackI--, TypeI--) { + for (;StackI > BlockStackStartPos && TypeI > 0; StackI--, TypeI--) { + // If the stack is polymorphic, we assume all types in 'Types' have been + // compared and matched + if (std::get_if(&Stack[StackI - 1])) { + TypeI = 0; + break; + } if (match(Stack[StackI - 1], Types[TypeI - 1])) { Error = true; break; } } + + // If the stack top is polymorphic, the stack is in the polymorphic state. + if (StackI > BlockStackStartPos && + std::get_if(&Stack[StackI - 1])) + PolymorphicStack = true; + // Even if no match failure has happened in the loop above, if not all // elements of Types has been matched, that means we don't have enough // elements on the stack. // // Also, if not all elements of the Stack has been matched and when - // 'ExactMatch' is true, that means we have superfluous elements remaining on - // the stack (e.g. at the end of a function). - if (TypeI > 0 || (ExactMatch && StackI > 0)) + // 'ExactMatch' is true and the current stack is not polymorphic, that means + // we have superfluous elements remaining on the stack (e.g. at the end of a + // function). + if (TypeI > 0 || + (ExactMatch && !PolymorphicStack && StackI > BlockStackStartPos)) Error = true; if (!Error) return false; - auto StackStartPos = - ExactMatch ? 0 : std::max(0, (int)Stack.size() - (int)Types.size()); + auto StackStartPos = ExactMatch + ? BlockStackStartPos + : std::max((int)BlockStackStartPos, + (int)Stack.size() - (int)Types.size()); return typeError(ErrorLoc, "type mismatch, expected " + getTypesString(Types, 0) + " but got " + getTypesString(Stack, StackStartPos)); @@ -169,9 +192,13 @@ bool WebAssemblyAsmTypeCheck::popTypes(SMLoc ErrorLoc, ArrayRef Types, bool ExactMatch) { bool Error = checkTypes(ErrorLoc, Types, ExactMatch); - auto NumPops = std::min(Stack.size(), Types.size()); - for (size_t I = 0, E = NumPops; I != E; I++) + auto NumPops = std::min(Stack.size() - BlockInfoStack.back().StackStartPos, + Types.size()); + for (size_t I = 0, E = NumPops; I != E; I++) { + if (std::get_if(&Stack.back())) + break; Stack.pop_back(); + } return Error; } @@ -201,25 +228,6 @@ bool WebAssemblyAsmTypeCheck::getLocal(SMLoc ErrorLoc, const MCOperand &LocalOp, return false; } -bool WebAssemblyAsmTypeCheck::checkBr(SMLoc ErrorLoc, size_t Level) { - if (Level >= BrStack.size()) - return typeError(ErrorLoc, - StringRef("br: invalid depth ") + std::to_string(Level)); - const SmallVector &Expected = - BrStack[BrStack.size() - Level - 1]; - return checkTypes(ErrorLoc, Expected); - return false; -} - -bool WebAssemblyAsmTypeCheck::checkEnd(SMLoc ErrorLoc, bool PopVals) { - if (!PopVals) - BrStack.pop_back(); - - if (PopVals) - return popTypes(ErrorLoc, LastSig.Returns); - return checkTypes(ErrorLoc, LastSig.Returns); -} - bool WebAssemblyAsmTypeCheck::checkSig(SMLoc ErrorLoc, const wasm::WasmSignature &Sig) { bool Error = popTypes(ErrorLoc, Sig.Params); @@ -308,10 +316,11 @@ bool WebAssemblyAsmTypeCheck::getSignature(SMLoc ErrorLoc, return false; } -bool WebAssemblyAsmTypeCheck::endOfFunction(SMLoc ErrorLoc, bool ExactMatch) { - bool Error = popTypes(ErrorLoc, ReturnTypes, ExactMatch); - Unreachable = true; - return Error; +bool WebAssemblyAsmTypeCheck::endOfFunction(SMLoc ErrorLoc, + bool ExactMatch) { + assert(!BlockInfoStack.empty()); + const auto &FuncInfo = BlockInfoStack[0]; + return checkTypes(ErrorLoc, FuncInfo.Sig.Returns, ExactMatch); } bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst, @@ -453,51 +462,90 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst, } if (Name == "try" || Name == "block" || Name == "loop" || Name == "if") { - if (Name == "loop") - BrStack.emplace_back(LastSig.Params.begin(), LastSig.Params.end()); - else - BrStack.emplace_back(LastSig.Returns.begin(), LastSig.Returns.end()); - if (Name == "if" && popType(ErrorLoc, wasm::ValType::I32)) - return true; - return false; + bool Error = Name == "if" && popType(ErrorLoc, wasm::ValType::I32); + // Pop block input parameters and check their types are correct + Error |= popTypes(ErrorLoc, LastSig.Params); + // Push a new block info + BlockInfoStack.push_back({LastSig, Stack.size(), Name == "loop"}); + // Push back block input parameters + pushTypes(LastSig.Params); + return Error; } if (Name == "end_block" || Name == "end_loop" || Name == "end_if" || Name == "else" || Name == "end_try" || Name == "catch" || Name == "catch_all" || Name == "delegate") { - bool Error = checkEnd(ErrorLoc, Name == "else" || Name == "catch" || - Name == "catch_all"); - Unreachable = false; - if (Name == "catch") { + assert(!BlockInfoStack.empty()); + // Check if the types on the stack match with the block return type + const auto &LastBlockInfo = BlockInfoStack.back(); + bool Error = checkTypes(ErrorLoc, LastBlockInfo.Sig.Returns, true); + // Pop all types added to the stack for the current block level + Stack.truncate(LastBlockInfo.StackStartPos); + if (Name == "else") { + // 'else' expects the block input parameters to be on the stack, in the + // same way we entered 'if' + pushTypes(LastBlockInfo.Sig.Params); + } else if (Name == "catch") { + // 'catch' instruction pushes values whose types are specified in the + // tag's 'params' part const wasm::WasmSignature *Sig = nullptr; if (!getSignature(Operands[1]->getStartLoc(), Inst.getOperand(0), wasm::WASM_SYMBOL_TYPE_TAG, Sig)) - // catch instruction pushes values whose types are specified in the - // tag's "params" part pushTypes(Sig->Params); else Error = true; + } else if (Name == "catch_all") { + // 'catch_all' does not push anything onto the stack + } else { + // For normal end markers, push block return value types onto the stack + // and pop the block info + pushTypes(LastBlockInfo.Sig.Returns); + BlockInfoStack.pop_back(); } return Error; } - if (Name == "br") { + if (Name == "br" || Name == "br_if") { + bool Error = false; + if (Name == "br_if") + Error |= popType(ErrorLoc, wasm::ValType::I32); // cond const MCOperand &Operand = Inst.getOperand(0); - if (!Operand.isImm()) - return true; - return checkBr(ErrorLoc, static_cast(Operand.getImm())); + if (Operand.isImm()) { + unsigned Level = Operand.getImm(); + if (Level < BlockInfoStack.size()) { + const auto &DestBlockInfo = + BlockInfoStack[BlockInfoStack.size() - Level - 1]; + if (DestBlockInfo.IsLoop) + Error |= checkTypes(ErrorLoc, DestBlockInfo.Sig.Params, false); + else + Error |= checkTypes(ErrorLoc, DestBlockInfo.Sig.Returns, false); + } else { + Error = typeError(ErrorLoc, StringRef("br: invalid depth ") + + std::to_string(Level)); + } + } else { + Error = + typeError(Operands[1]->getStartLoc(), "depth should be an integer"); + } + if (Name == "br") + pushType(Polymorphic{}); + return Error; } if (Name == "return") { - return endOfFunction(ErrorLoc, false); + bool Error = endOfFunction(ErrorLoc, false); + pushType(Polymorphic{}); + return Error; } if (Name == "call_indirect" || Name == "return_call_indirect") { // Function value. bool Error = popType(ErrorLoc, wasm::ValType::I32); Error |= checkSig(ErrorLoc, LastSig); - if (Name == "return_call_indirect" && endOfFunction(ErrorLoc, false)) - return true; + if (Name == "return_call_indirect") { + Error |= endOfFunction(ErrorLoc, false); + pushType(Polymorphic{}); + } return Error; } @@ -509,13 +557,15 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst, Error |= checkSig(ErrorLoc, *Sig); else Error = true; - if (Name == "return_call" && endOfFunction(ErrorLoc, false)) - return true; + if (Name == "return_call") { + Error |= endOfFunction(ErrorLoc, false); + pushType(Polymorphic{}); + } return Error; } if (Name == "unreachable") { - Unreachable = true; + pushType(Polymorphic{}); return false; } @@ -526,11 +576,15 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst, } if (Name == "throw") { + bool Error = false; const wasm::WasmSignature *Sig = nullptr; if (!getSignature(Operands[1]->getStartLoc(), Inst.getOperand(0), wasm::WASM_SYMBOL_TYPE_TAG, Sig)) - return checkSig(ErrorLoc, *Sig); - return true; + Error |= checkSig(ErrorLoc, *Sig); + else + Error = true; + pushType(Polymorphic{}); + return Error; } // The current instruction is a stack instruction which doesn't have diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.h b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.h index df063d749e3b4..596fb27bce94e 100644 --- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.h +++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.h @@ -31,13 +31,17 @@ class WebAssemblyAsmTypeCheck final { struct Ref : public std::monostate {}; struct Any : public std::monostate {}; - using StackType = std::variant; + struct Polymorphic : public std::monostate {}; + using StackType = std::variant; SmallVector Stack; - SmallVector, 8> BrStack; + struct BlockInfo { + wasm::WasmSignature Sig; + size_t StackStartPos; + bool IsLoop; + }; + SmallVector BlockInfoStack; SmallVector LocalTypes; - SmallVector ReturnTypes; wasm::WasmSignature LastSig; - bool Unreachable = false; bool Is64; // checkTypes checks 'Types' against the value stack. popTypes checks 'Types' @@ -68,8 +72,6 @@ class WebAssemblyAsmTypeCheck final { void dumpTypeStack(Twine Msg); bool typeError(SMLoc ErrorLoc, const Twine &Msg); bool getLocal(SMLoc ErrorLoc, const MCOperand &LocalOp, wasm::ValType &Type); - bool checkEnd(SMLoc ErrorLoc, bool PopVals = false); - bool checkBr(SMLoc ErrorLoc, size_t Level); bool checkSig(SMLoc ErrorLoc, const wasm::WasmSignature &Sig); bool getSymRef(SMLoc ErrorLoc, const MCOperand &SymOp, const MCSymbolRefExpr *&SymRef); @@ -91,10 +93,8 @@ class WebAssemblyAsmTypeCheck final { void clear() { Stack.clear(); - BrStack.clear(); + BlockInfoStack.clear(); LocalTypes.clear(); - ReturnTypes.clear(); - Unreachable = false; } }; diff --git a/llvm/test/MC/WebAssembly/basic-assembly.s b/llvm/test/MC/WebAssembly/basic-assembly.s index db7ccc9759bec..2028fcea6aeae 100644 --- a/llvm/test/MC/WebAssembly/basic-assembly.s +++ b/llvm/test/MC/WebAssembly/basic-assembly.s @@ -82,11 +82,13 @@ test0: i32.const 3 end_block # "switch" exit. if # void + i32.const 0 if i32 + i32.const 0 end_if + drop else end_if - drop block void i32.const 2 return @@ -222,11 +224,13 @@ empty_exnref_table: # CHECK-NEXT: i32.const 3 # CHECK-NEXT: end_block # label2: # CHECK-NEXT: if +# CHECK-NEXT: i32.const 0 # CHECK-NEXT: if i32 +# CHECK-NEXT: i32.const 0 # CHECK-NEXT: end_if +# CHECK-NEXT: drop # CHECK-NEXT: else # CHECK-NEXT: end_if -# CHECK-NEXT: drop # CHECK-NEXT: block # CHECK-NEXT: i32.const 2 # CHECK-NEXT: return diff --git a/llvm/test/MC/WebAssembly/block-assembly.s b/llvm/test/MC/WebAssembly/block-assembly.s new file mode 100644 index 0000000000000..f97a77b99e1f8 --- /dev/null +++ b/llvm/test/MC/WebAssembly/block-assembly.s @@ -0,0 +1,182 @@ +# RUN: llvm-mc -triple=wasm32-unknown-unknown -mattr=+exception-handling < %s | FileCheck %s +# Check that it converts to .o without errors, but don't check any output: +# RUN: llvm-mc -triple=wasm32-unknown-unknown -mattr=+exception-handling -filetype=obj -o %t.o < %s + + .tagtype __cpp_exception i32 + +block_branch_test: + .functype block_branch_test () -> () + + # Block input paramter / return tests + + i32.const 0 + block (i32) -> (i32) + end_block + drop + + i32.const 0 + i64.const 0 + block (i32, i64) -> (i32, f32) + drop + f32.const 0.0 + end_block + drop + drop + + i32.const 0 + loop (i32) -> (f32) + drop + f32.const 0.0 + end_loop + drop + + i32.const 0 + i32.const 0 + if (i32) -> (i32) + else + i32.popcnt + end_if + drop + + try i32 + i32.const 0 + catch __cpp_exception + i32.clz + catch_all + i32.const 5 + end_try + drop + + i32.const 0 + block (i32) -> (i32) + block (i32) -> (f32) + drop + f32.const 0.0 + end_block + drop + i32.const 0 + end_block + drop + + # Branch tests + + block f32 + f32.const 0.0 + i32.const 0 + br_if 0 + f32.const 1.0 + br 0 + # After 'br', we can pop any values from the polymorphic stack + i32.add + i32.sub + i32.mul + drop + end_block + drop + + block () -> (f32, f64) + f32.const 0.0 + f64.const 0.0 + i32.const 0 + br_if 0 + block (f32, f64) -> (f32, f64) + i32.const 1 + br_if 0 + end_block + end_block + drop + drop + + # Withina loop, branches target the start of the loop + i32.const 0 + loop (i32) -> () + i32.const 1 + br 0 + end_loop + + end_function + +# CHECK-LABEL: block_branch_test + +# CHECK: i32.const 0 +# CHECK-NEXT: block (i32) -> (i32) +# CHECK-NEXT: end_block # label0: +# CHECK-NEXT: drop + +# CHECK: i32.const 0 +# CHECK-NEXT: i64.const 0 +# CHECK-NEXT: block (i32, i64) -> (i32, f32) +# CHECK-NEXT: drop +# CHECK-NEXT: f32.const 0x0p0 +# CHECK-NEXT: end_block # label1: +# CHECK-NEXT: drop +# CHECK-NEXT: drop + +# CHECK: i32.const 0 +# CHECK-NEXT: loop (i32) -> (f32) # label2: +# CHECK-NEXT: drop +# CHECK-NEXT: f32.const 0x0p0 +# CHECK-NEXT: end_loop +# CHECK-NEXT: drop + +# CHECK: i32.const 0 +# CHECK-NEXT: i32.const 0 +# CHECK-NEXT: if (i32) -> (i32) +# CHECK-NEXT: else +# CHECK-NEXT: i32.popcnt +# CHECK-NEXT: end_if +# CHECK-NEXT: drop + +# CHECK: try i32 +# CHECK-NEXT: i32.const 0 +# CHECK-NEXT: catch __cpp_exception # catch3: +# CHECK-NEXT: i32.clz +# CHECK-NEXT: catch_all +# CHECK-NEXT: i32.const 5 +# CHECK-NEXT: end_try # label3: +# CHECK-NEXT: drop + +# CHECK: i32.const 0 +# CHECK-NEXT: block (i32) -> (i32) +# CHECK-NEXT: block (i32) -> (f32) +# CHECK-NEXT: drop +# CHECK-NEXT: f32.const 0x0p0 +# CHECK-NEXT: end_block # label5: +# CHECK-NEXT: drop +# CHECK-NEXT: i32.const 0 +# CHECK-NEXT: end_block # label4: +# CHECK-NEXT: drop + +# CHECK: block f32 +# CHECK-NEXT: f32.const 0x0p0 +# CHECK-NEXT: i32.const 0 +# CHECK-NEXT: br_if 0 # 0: down to label6 +# CHECK-NEXT: f32.const 0x1p0 +# CHECK-NEXT: br 0 # 0: down to label6 +# CHECK-NEXT: i32.add +# CHECK-NEXT: i32.sub +# CHECK-NEXT: i32.mul +# CHECK-NEXT: drop +# CHECK-NEXT: end_block # label6: +# CHECK-NEXT: drop + +# CHECK: block () -> (f32, f64) +# CHECK-NEXT: f32.const 0x0p0 +# CHECK-NEXT: f64.const 0x0p0 +# CHECK-NEXT: i32.const 0 +# CHECK-NEXT: br_if 0 # 0: down to label7 +# CHECK-NEXT: block (f32, f64) -> (f32, f64) +# CHECK-NEXT: i32.const 1 +# CHECK-NEXT: br_if 0 # 0: down to label8 +# CHECK-NEXT: end_block # label8: +# CHECK-NEXT: end_block # label7: +# CHECK-NEXT: drop +# CHECK-NEXT: drop + +# CHECK: i32.const 0 +# CHECK-NEXT: loop (i32) -> () # label9: +# CHECK-NEXT: i32.const 1 +# CHECK-NEXT: br 0 # 0: up to label9 +# CHECK-NEXT: end_loop + +# CHECK: end_function diff --git a/llvm/test/MC/WebAssembly/reference-types.s b/llvm/test/MC/WebAssembly/reference-types.s index 2f8bfba68dcea..cfadede8295ef 100644 --- a/llvm/test/MC/WebAssembly/reference-types.s +++ b/llvm/test/MC/WebAssembly/reference-types.s @@ -76,14 +76,17 @@ ref_select_test: # CHECK: block externref # CHECK: block exnref ref_block_test: - .functype ref_block_test () -> (exnref, externref, funcref) + .functype ref_block_test () -> () block funcref block externref block exnref ref.null_exn end_block + drop ref.null_extern end_block + drop ref.null_func end_block + drop end_function diff --git a/llvm/test/MC/WebAssembly/type-checker-errors.s b/llvm/test/MC/WebAssembly/type-checker-errors.s index d81c5aff0a7e9..df537a9ba5d0a 100644 --- a/llvm/test/MC/WebAssembly/type-checker-errors.s +++ b/llvm/test/MC/WebAssembly/type-checker-errors.s @@ -620,9 +620,8 @@ catch_superfluous_value_at_end: .functype catch_superfluous_value_at_end () -> () try catch tag_i32 - end_try -# FIXME: Superfluous value should be caught at end_try? # CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [] but got [i32] + end_try end_function ref_is_null_empty_stack_while_popping: @@ -925,3 +924,23 @@ any_value_on_stack: drop end_function + +block_param_and_return: + .functype block_param_and_return () -> () +# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got [] + block (i32) -> (f32) +# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [f32] but got [i32] + end_block +# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got [f32] + i32.popcnt + drop + + block f32 + f32.const 0.0 + br 0 + i32.const 0 +# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [f32] but got [..., i32] + end_block + +# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [] but got [f32] + end_function