[llvm] [WebAssembly] Unify type checking in AsmTypeCheck (PR #110094)
Heejin Ahn via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 26 02:26:45 PDT 2024
https://github.com/aheejin created https://github.com/llvm/llvm-project/pull/110094
This unifies the way we check types in various places in AsmTypeCheck. The objectives of this PR are:
- We now use `checkTypes` for all type checking and `checkAndPopTypes` for type checking + popping. All other functions are helper functions to call these two functions.
- We now support comparisons of types between vectors. This lets us printing error messages in more readable way. When an instruction takes [i32, i64] but the stack top is [f32, f64], now instead of
```console
error: type mismatch, expected i64 but got f64
error: type mismatch, expected i32 but got f32
```
we can print this
```console
error: type mismatch, expected [i32, i64] but got [f32, f64]
```
which is also the format Wabt checker prints. This also helps printing more meaningful messages when there are superfluous values on the stack at the end of the function, such as:
```console
error: type mismatch, expected [] but got [i32, exnref]
```
Actually, many instructions are not utilizing this batch printing now, which still causes multiple error messages to be printed for a single instruction. This will be improved in a follow-up.
- The value stack now supports `Any` and `Ref`. There are instructions that requires the type to be anything. Also instructions like `ref.is_null` requires the type to be any reference types. Type comparison function will handle this types accordingly, meaning `match(I32, Any)` or `match(externref, Ref)` will succeed.
The changes in `type-checker-errors.s` are mostly the message format changes. One downside of the new message format is that it doesn't have instruction names in it. I plan to improve that in a potential follow-up.
This also made some modifications in the instructions in `type-checker-errors.s`. Currently, except for a few functions I've recently added at the end, each function tests for a single error, because the type checker used to bail out after the first error until #109705. But many functions included multiple errors anyway, which I don't think was the intention of the original writer. So I added some instructions to remove the other errors which are not being tested. (In some cases I added more error checking lines instead, when I felt that could be relevant.)
Thanks to the new `ExactMatch` option in `checkTypes` function family, we now can distinguish the cases when to check against only the top of the value stack and when to check against the whole stack (e.g. to check whether we have any superfluous values remaining at the end of the function). `return` or `return_call(_indirect)` can set `ExactMatch` to `false` because they don't care about the superfluous values. This makes `type-checker-return.s` succeed and I was able to remove the `FIXME`.
This is the basis of the PR that fixes block parameter/return type handling in the checker, but does not yet include the actual block-related functionality, which will be submitted separately after this PR.
>From f2f5ac95c3a1aae8e6936d3bdb26b440031a1f2f Mon Sep 17 00:00:00 2001
From: Heejin Ahn <aheejin at gmail.com>
Date: Wed, 25 Sep 2024 01:47:41 +0000
Subject: [PATCH] [WebAssembly] Unify type checking in AsmTypeCheck
This unifies the way we check types in various places in AsmTypeCheck.
The objectives of this PR are:
- We now use `checkTypes` for all type checking and `checkAndPopTypes`
for type checking + popping. All other functions are helper functions
to call these two functions.
- We now support comparisons of types between vectors. This lets us
printing error messages in more readable way. When an instruction
takes [i32, i64] but the stack top is [f32, f64], now instead of
```console
error: type mismatch, expected i64 but got f64
error: type mismatch, expected i32 but got f32
```
we can print this
```console
error: type mismatch, expected [i32, i64] but got [f32, f64]
```
which is also the format Wabt checker prints.
This also helps printing more meaningful messages when there are
superfluous values on the stack at the end of the function, such as:
```console
error: type mismatch, expected [] but got [i32, exnref]
```
Actually, many instructions are not utilizing this batch printing now,
which still causes multiple error messages to be printed for a single
instruction. This will be improved in a follow-up.
- The value stack now supports `Any` and `Ref`. There are instructions
that requires the type to be anything. Also instructions like
`ref.is_null` requires the type to be any reference types. Type
comparison function will handle this types accordingly, meaning
`match(I32, Any)` or `match(externref, Ref)` will succeed.
The changes in `type-checker-errors.s` are mostly the message format
changes. One downside of the new message format is that it doesn't have
instruction names in it. I plan to improve that in a potential
follow-up.
This also made some modifications in the instructions in
`type-checker-errors.s`. Currently, except for a few functions I've
recently added at the end, each function tests for a single error,
because the type checker used to bail out after the first error until
#109705. But many functions included multiple errors anyway, which I
don't think was the intention of the original writer. So I added some
instructions to remove the other errors which are not being tested. (In
some cases I added more error checking lines instead, when I felt that
could be relevant.)
Thanks to the new `ExactMatch` option in `checkTypes` function family,
we now can distinguish the cases when to check against only the top of
the value stack and when to check against the whole stack (e.g. to check
whether we have any superfluous values remaining at the end of the
function). `return` or `return_call(_indirect)` can set `ExactMatch` to
`false` because they don't care about the superfluous values. This makes
`type-checker-return.s` succeed and I was able to remove the `FIXME`.
This is the basis of the PR that fixes block parameter/return type
handling in the checker, but does not yet include the actual
block-related functionality, which will be submitted separately after
this PR.
---
.../AsmParser/WebAssemblyAsmParser.cpp | 2 +-
.../AsmParser/WebAssemblyAsmTypeCheck.cpp | 238 ++++++++++--------
.../AsmParser/WebAssemblyAsmTypeCheck.h | 31 ++-
llvm/test/MC/WebAssembly/basic-assembly.s | 10 +
.../test/MC/WebAssembly/type-checker-errors.s | 220 +++++++++-------
.../test/MC/WebAssembly/type-checker-return.s | 5 -
6 files changed, 304 insertions(+), 202 deletions(-)
diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
index 129fdaf37fc0d8..95db5500b0e1b1 100644
--- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
+++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
@@ -1255,7 +1255,7 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
void onEndOfFunction(SMLoc ErrorLoc) {
if (!SkipTypeCheck)
- TC.endOfFunction(ErrorLoc);
+ TC.endOfFunction(ErrorLoc, true);
// Reset the type checker state.
TC.clear();
}
diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp
index 8b1e1dca4f8474..2f000354182fcb 100644
--- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp
+++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp
@@ -33,6 +33,7 @@
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/SourceMgr.h"
+#include <sstream>
using namespace llvm;
@@ -59,14 +60,7 @@ void WebAssemblyAsmTypeCheck::localDecl(
}
void WebAssemblyAsmTypeCheck::dumpTypeStack(Twine Msg) {
- LLVM_DEBUG({
- std::string s;
- for (auto VT : Stack) {
- s += WebAssembly::typeToString(VT);
- s += " ";
- }
- dbgs() << Msg << s << '\n';
- });
+ LLVM_DEBUG({ dbgs() << Msg << getTypesString(Stack, 0); });
}
bool WebAssemblyAsmTypeCheck::typeError(SMLoc ErrorLoc, const Twine &Msg) {
@@ -77,34 +71,119 @@ bool WebAssemblyAsmTypeCheck::typeError(SMLoc ErrorLoc, const Twine &Msg) {
return Parser.Error(ErrorLoc, Msg);
}
-bool WebAssemblyAsmTypeCheck::popType(SMLoc ErrorLoc,
- std::optional<wasm::ValType> EVT) {
- if (Stack.empty()) {
- return typeError(ErrorLoc,
- EVT ? StringRef("empty stack while popping ") +
- WebAssembly::typeToString(*EVT)
- : StringRef("empty stack while popping value"));
+bool WebAssemblyAsmTypeCheck::match(StackType TypeA, StackType TypeB) {
+ if (TypeA == TypeB)
+ return false;
+ if (std::get_if<Any>(&TypeA) || std::get_if<Any>(&TypeB))
+ return false;
+
+ if (std::get_if<Ref>(&TypeB))
+ std::swap(TypeA, TypeB);
+ assert(std::get_if<wasm::ValType>(&TypeB));
+ if (std::get_if<Ref>(&TypeA) &&
+ WebAssembly::isRefType(std::get<wasm::ValType>(TypeB)))
+ return false;
+ return true;
+}
+
+std::string WebAssemblyAsmTypeCheck::getTypesString(ArrayRef<StackType> Types,
+ size_t StartPos) {
+ SmallVector<std::string, 4> Reverse;
+ for (auto I = Types.size(); I > StartPos; I--) {
+ if (std::get_if<Any>(&Types[I - 1]))
+ Reverse.push_back("any");
+ else if (std::get_if<Ref>(&Types[I - 1]))
+ Reverse.push_back("ref");
+ else
+ Reverse.push_back(
+ WebAssembly::typeToString(std::get<wasm::ValType>(Types[I - 1])));
}
- auto PVT = Stack.pop_back_val();
- if (EVT && *EVT != PVT) {
- return typeError(ErrorLoc,
- StringRef("popped ") + WebAssembly::typeToString(PVT) +
- ", expected " + WebAssembly::typeToString(*EVT));
+
+ std::stringstream SS;
+ SS << "[";
+ bool First = true;
+ for (auto It = Reverse.rbegin(); It != Reverse.rend(); ++It) {
+ if (!First)
+ SS << ", ";
+ SS << *It;
+ First = false;
}
- return false;
+ SS << "]";
+ return SS.str();
}
-bool WebAssemblyAsmTypeCheck::popRefType(SMLoc ErrorLoc) {
- if (Stack.empty()) {
- return typeError(ErrorLoc, StringRef("empty stack while popping reftype"));
- }
- auto PVT = Stack.pop_back_val();
- if (!WebAssembly::isRefType(PVT)) {
- return typeError(ErrorLoc, StringRef("popped ") +
- WebAssembly::typeToString(PVT) +
- ", expected reftype");
+SmallVector<WebAssemblyAsmTypeCheck::StackType, 4>
+WebAssemblyAsmTypeCheck::valTypeToStackType(ArrayRef<wasm::ValType> ValTypes) {
+ SmallVector<StackType, 4> Types(ValTypes.size());
+ std::transform(ValTypes.begin(), ValTypes.end(), Types.begin(),
+ [](wasm::ValType Val) -> StackType { return Val; });
+ return Types;
+}
+
+bool WebAssemblyAsmTypeCheck::checkTypes(SMLoc ErrorLoc,
+ ArrayRef<wasm::ValType> ValTypes,
+ bool ExactMatch) {
+ return checkTypes(ErrorLoc, valTypeToStackType(ValTypes), ExactMatch);
+}
+
+bool WebAssemblyAsmTypeCheck::checkTypes(SMLoc ErrorLoc,
+ ArrayRef<StackType> Types,
+ bool ExactMatch) {
+ auto StackI = Stack.size();
+ auto TypeI = Types.size();
+ bool Error = false;
+ for (; StackI > 0 && TypeI > 0; StackI--, TypeI--) {
+ if (match(Stack[StackI - 1], Types[TypeI - 1])) {
+ Error = true;
+ break;
+ }
}
- return false;
+ if (TypeI > 0 || (ExactMatch && StackI > 0))
+ Error = true;
+
+ if (!Error)
+ return false;
+
+ auto StackStartPos =
+ ExactMatch ? 0 : std::max(0, (int)Stack.size() - (int)Types.size());
+ return typeError(ErrorLoc, "type mismatch, expected " +
+ getTypesString(Types, 0) + " but got " +
+ getTypesString(Stack, StackStartPos));
+}
+
+bool WebAssemblyAsmTypeCheck::checkAndPopTypes(SMLoc ErrorLoc,
+ ArrayRef<wasm::ValType> ValTypes,
+ bool ExactMatch) {
+ SmallVector<StackType, 4> Types(ValTypes.size());
+ std::transform(ValTypes.begin(), ValTypes.end(), Types.begin(),
+ [](wasm::ValType Val) -> StackType { return Val; });
+ return checkAndPopTypes(ErrorLoc, Types, ExactMatch);
+}
+
+bool WebAssemblyAsmTypeCheck::checkAndPopTypes(SMLoc ErrorLoc,
+ ArrayRef<StackType> 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++)
+ Stack.pop_back();
+ return Error;
+}
+
+bool WebAssemblyAsmTypeCheck::popType(SMLoc ErrorLoc, StackType Type) {
+ return checkAndPopTypes(ErrorLoc, {Type}, false);
+}
+
+bool WebAssemblyAsmTypeCheck::popRefType(SMLoc ErrorLoc) {
+ return popType(ErrorLoc, Ref{});
+}
+
+bool WebAssemblyAsmTypeCheck::popAnyType(SMLoc ErrorLoc) {
+ return popType(ErrorLoc, Any{});
+}
+
+void WebAssemblyAsmTypeCheck::pushTypes(ArrayRef<wasm::ValType> ValTypes) {
+ Stack.append(valTypeToStackType(ValTypes));
}
bool WebAssemblyAsmTypeCheck::getLocal(SMLoc ErrorLoc, const MCOperand &LocalOp,
@@ -117,59 +196,29 @@ bool WebAssemblyAsmTypeCheck::getLocal(SMLoc ErrorLoc, const MCOperand &LocalOp,
return false;
}
-static std::optional<std::string>
-checkStackTop(const SmallVectorImpl<wasm::ValType> &ExpectedStackTop,
- const SmallVectorImpl<wasm::ValType> &Got) {
- for (size_t I = 0; I < ExpectedStackTop.size(); I++) {
- auto EVT = ExpectedStackTop[I];
- auto PVT = Got[Got.size() - ExpectedStackTop.size() + I];
- if (PVT != EVT)
- return std::string{"got "} + WebAssembly::typeToString(PVT) +
- ", expected " + WebAssembly::typeToString(EVT);
- }
- return std::nullopt;
-}
-
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<wasm::ValType, 4> &Expected =
BrStack[BrStack.size() - Level - 1];
- if (Expected.size() > Stack.size())
- return typeError(ErrorLoc, "br: insufficient values on the type stack");
- auto IsStackTopInvalid = checkStackTop(Expected, Stack);
- if (IsStackTopInvalid)
- return typeError(ErrorLoc, "br " + IsStackTopInvalid.value());
+ return checkTypes(ErrorLoc, Expected, false);
return false;
}
bool WebAssemblyAsmTypeCheck::checkEnd(SMLoc ErrorLoc, bool PopVals) {
if (!PopVals)
BrStack.pop_back();
- if (LastSig.Returns.size() > Stack.size())
- return typeError(ErrorLoc, "end: insufficient values on the type stack");
- if (PopVals) {
- for (auto VT : llvm::reverse(LastSig.Returns)) {
- if (popType(ErrorLoc, VT))
- return true;
- }
- return false;
- }
-
- auto IsStackTopInvalid = checkStackTop(LastSig.Returns, Stack);
- if (IsStackTopInvalid)
- return typeError(ErrorLoc, "end " + IsStackTopInvalid.value());
- return false;
+ if (PopVals)
+ return checkAndPopTypes(ErrorLoc, LastSig.Returns, false);
+ return checkTypes(ErrorLoc, LastSig.Returns, false);
}
bool WebAssemblyAsmTypeCheck::checkSig(SMLoc ErrorLoc,
const wasm::WasmSignature &Sig) {
- bool Error = false;
- for (auto VT : llvm::reverse(Sig.Params))
- Error |= popType(ErrorLoc, VT);
- Stack.insert(Stack.end(), Sig.Returns.begin(), Sig.Returns.end());
+ bool Error = checkAndPopTypes(ErrorLoc, Sig.Params, false);
+ pushTypes(Sig.Returns);
return Error;
}
@@ -246,7 +295,7 @@ bool WebAssemblyAsmTypeCheck::getSignature(SMLoc ErrorLoc,
TypeName = "tag";
break;
default:
- return true;
+ assert(false);
}
return typeError(ErrorLoc, StringRef("symbol ") + WasmSym->getName() +
": missing ." + TypeName + "type");
@@ -254,15 +303,8 @@ bool WebAssemblyAsmTypeCheck::getSignature(SMLoc ErrorLoc,
return false;
}
-bool WebAssemblyAsmTypeCheck::endOfFunction(SMLoc ErrorLoc) {
- bool Error = false;
- // Check the return types.
- for (auto RVT : llvm::reverse(ReturnTypes))
- Error |= popType(ErrorLoc, RVT);
- if (!Stack.empty()) {
- return typeError(ErrorLoc, std::to_string(Stack.size()) +
- " superfluous return values");
- }
+bool WebAssemblyAsmTypeCheck::endOfFunction(SMLoc ErrorLoc, bool ExactMatch) {
+ bool Error = checkTypes(ErrorLoc, ReturnTypes, ExactMatch);
Unreachable = true;
return Error;
}
@@ -276,7 +318,7 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
if (Name == "local.get") {
if (!getLocal(Operands[1]->getStartLoc(), Inst.getOperand(0), Type)) {
- Stack.push_back(Type);
+ pushType(Type);
return false;
}
return true;
@@ -291,7 +333,7 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
if (Name == "local.tee") {
if (!getLocal(Operands[1]->getStartLoc(), Inst.getOperand(0), Type)) {
bool Error = popType(ErrorLoc, Type);
- Stack.push_back(Type);
+ pushType(Type);
return Error;
}
return true;
@@ -299,7 +341,7 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
if (Name == "global.get") {
if (!getGlobal(Operands[1]->getStartLoc(), Inst.getOperand(0), Type)) {
- Stack.push_back(Type);
+ pushType(Type);
return false;
}
return true;
@@ -314,7 +356,7 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
if (Name == "table.get") {
bool Error = popType(ErrorLoc, wasm::ValType::I32);
if (!getTable(Operands[1]->getStartLoc(), Inst.getOperand(0), Type)) {
- Stack.push_back(Type);
+ pushType(Type);
return Error;
}
return true;
@@ -332,7 +374,7 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
if (Name == "table.size") {
bool Error = getTable(Operands[1]->getStartLoc(), Inst.getOperand(0), Type);
- Stack.push_back(wasm::ValType::I32);
+ pushType(wasm::ValType::I32);
return Error;
}
@@ -342,7 +384,7 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
Error |= popType(ErrorLoc, Type);
else
Error = true;
- Stack.push_back(wasm::ValType::I32);
+ pushType(wasm::ValType::I32);
return Error;
}
@@ -381,7 +423,7 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
}
if (Name == "drop") {
- return popType(ErrorLoc, {});
+ return popType(ErrorLoc, Any{});
}
if (Name == "try" || Name == "block" || Name == "loop" || Name == "if") {
@@ -406,7 +448,7 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
wasm::WASM_SYMBOL_TYPE_TAG, Sig))
// catch instruction pushes values whose types are specified in the
// tag's "params" part
- Stack.insert(Stack.end(), Sig->Params.begin(), Sig->Params.end());
+ pushTypes(Sig->Params);
else
Error = true;
}
@@ -421,14 +463,14 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
}
if (Name == "return") {
- return endOfFunction(ErrorLoc);
+ return endOfFunction(ErrorLoc, false);
}
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))
+ if (Name == "return_call_indirect" && endOfFunction(ErrorLoc, false))
return true;
return Error;
}
@@ -441,7 +483,7 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
Error |= checkSig(ErrorLoc, *Sig);
else
Error = true;
- if (Name == "return_call" && endOfFunction(ErrorLoc))
+ if (Name == "return_call" && endOfFunction(ErrorLoc, false))
return true;
return Error;
}
@@ -453,7 +495,7 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
if (Name == "ref.is_null") {
bool Error = popRefType(ErrorLoc);
- Stack.push_back(wasm::ValType::I32);
+ pushType(wasm::ValType::I32);
return Error;
}
@@ -471,22 +513,22 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
auto RegOpc = WebAssembly::getRegisterOpcode(Opc);
assert(RegOpc != -1 && "Failed to get register version of MC instruction");
const auto &II = MII.get(RegOpc);
- bool Error = false;
// First pop all the uses off the stack and check them.
- for (unsigned I = II.getNumOperands(); I > II.getNumDefs(); I--) {
- const auto &Op = II.operands()[I - 1];
- if (Op.OperandType == MCOI::OPERAND_REGISTER) {
- auto VT = WebAssembly::regClassToValType(Op.RegClass);
- Error |= popType(ErrorLoc, VT);
- }
+ SmallVector<wasm::ValType, 4> PopTypes;
+ for (unsigned I = II.getNumDefs(); I < II.getNumOperands(); I++) {
+ const auto &Op = II.operands()[I];
+ if (Op.OperandType == MCOI::OPERAND_REGISTER)
+ PopTypes.push_back(WebAssembly::regClassToValType(Op.RegClass));
}
+ bool Error = checkAndPopTypes(ErrorLoc, PopTypes, false);
+ SmallVector<wasm::ValType, 4> PushTypes;
// Now push all the defs onto the stack.
for (unsigned I = 0; I < II.getNumDefs(); I++) {
const auto &Op = II.operands()[I];
assert(Op.OperandType == MCOI::OPERAND_REGISTER && "Register expected");
- auto VT = WebAssembly::regClassToValType(Op.RegClass);
- Stack.push_back(VT);
+ PushTypes.push_back(WebAssembly::regClassToValType(Op.RegClass));
}
+ pushTypes(PushTypes);
return Error;
}
diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.h b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.h
index 972162d3e02f46..9fd35a26f30e50 100644
--- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.h
+++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.h
@@ -21,6 +21,7 @@
#include "llvm/MC/MCParser/MCAsmParser.h"
#include "llvm/MC/MCParser/MCTargetAsmParser.h"
#include "llvm/MC/MCSymbol.h"
+#include <variant>
namespace llvm {
@@ -28,7 +29,10 @@ class WebAssemblyAsmTypeCheck final {
MCAsmParser &Parser;
const MCInstrInfo &MII;
- SmallVector<wasm::ValType, 8> Stack;
+ struct Ref : public std::monostate {};
+ struct Any : public std::monostate {};
+ using StackType = std::variant<wasm::ValType, Ref, Any>;
+ SmallVector<StackType, 16> Stack;
SmallVector<SmallVector<wasm::ValType, 4>, 8> BrStack;
SmallVector<wasm::ValType, 16> LocalTypes;
SmallVector<wasm::ValType, 4> ReturnTypes;
@@ -36,10 +40,29 @@ class WebAssemblyAsmTypeCheck final {
bool Unreachable = false;
bool Is64;
+ // If ExactMatch is true, 'Types' will be compared against not only the top of
+ // the value stack but the whole remaining value stack
+ // (TODO: This should be the whole remaining value stack "at the the current
+ // block level", which has not been implemented yet)
+ bool checkTypes(SMLoc ErrorLoc, ArrayRef<wasm::ValType> Types,
+ bool ExactMatch);
+ bool checkTypes(SMLoc ErrorLoc, ArrayRef<StackType> Types, bool ExactMatch);
+ bool checkAndPopTypes(SMLoc ErrorLoc, ArrayRef<wasm::ValType> Types,
+ bool ExactMatch);
+ bool checkAndPopTypes(SMLoc ErrorLoc, ArrayRef<StackType> Types,
+ bool ExactMatch);
+ bool popType(SMLoc ErrorLoc, StackType Type);
+ bool popRefType(SMLoc ErrorLoc);
+ bool popAnyType(SMLoc ErrorLoc);
+ void pushTypes(ArrayRef<wasm::ValType> Types);
+ void pushType(StackType Type) { Stack.push_back(Type); }
+ bool match(StackType TypeA, StackType TypeB);
+ std::string getTypesString(ArrayRef<StackType> Types, size_t StartPos);
+ SmallVector<StackType, 4>
+ valTypeToStackType(ArrayRef<wasm::ValType> ValTypes);
+
void dumpTypeStack(Twine Msg);
bool typeError(SMLoc ErrorLoc, const Twine &Msg);
- bool popType(SMLoc ErrorLoc, std::optional<wasm::ValType> EVT);
- bool popRefType(SMLoc ErrorLoc);
bool getLocal(SMLoc ErrorLoc, const MCOperand &LocalOp, wasm::ValType &Type);
bool checkEnd(SMLoc ErrorLoc, bool PopVals = false);
bool checkBr(SMLoc ErrorLoc, size_t Level);
@@ -59,7 +82,7 @@ class WebAssemblyAsmTypeCheck final {
void funcDecl(const wasm::WasmSignature &Sig);
void localDecl(const SmallVectorImpl<wasm::ValType> &Locals);
void setLastSig(const wasm::WasmSignature &Sig) { LastSig = Sig; }
- bool endOfFunction(SMLoc ErrorLoc);
+ bool endOfFunction(SMLoc ErrorLoc, bool ExactMatch);
bool typeCheck(SMLoc ErrorLoc, const MCInst &Inst, OperandVector &Operands);
void clear() {
diff --git a/llvm/test/MC/WebAssembly/basic-assembly.s b/llvm/test/MC/WebAssembly/basic-assembly.s
index db7ccc9759beca..6cca87d77c20f5 100644
--- a/llvm/test/MC/WebAssembly/basic-assembly.s
+++ b/llvm/test/MC/WebAssembly/basic-assembly.s
@@ -119,6 +119,13 @@ test0:
#i32.trunc_sat_f32_s
global.get __stack_pointer
global.set __stack_pointer
+ # FIXME Currently block parameter and return types are not handled
+ # correctly, causing some types to remain on the stack. This test will be
+ # fixed to be valid with the follow-up PR. Until then, to suppress the
+ # return type error, we add some drops here.
+ drop
+ drop
+ drop
end_function
.section .rodata..L.str,"",@
@@ -255,6 +262,9 @@ empty_exnref_table:
# CHECK-NEXT: .LBB0_4:
# CHECK-NEXT: global.get __stack_pointer
# CHECK-NEXT: global.set __stack_pointer
+# CHECK-NEXT: drop
+# CHECK-NEXT: drop
+# CHECK-NEXT: drop
# CHECK-NEXT: end_function
# CHECK: .section .rodata..L.str,"",@
diff --git a/llvm/test/MC/WebAssembly/type-checker-errors.s b/llvm/test/MC/WebAssembly/type-checker-errors.s
index 3106fe76c8449f..5fdc2f56daf57b 100644
--- a/llvm/test/MC/WebAssembly/type-checker-errors.s
+++ b/llvm/test/MC/WebAssembly/type-checker-errors.s
@@ -19,7 +19,7 @@ local_set_no_local_type:
local_set_empty_stack_while_popping:
.functype local_set_empty_stack_while_popping () -> ()
.local i32
-# CHECK: [[@LINE+1]]:3: error: empty stack while popping i32
+# CHECK: [[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
local.set 0
end_function
@@ -27,7 +27,7 @@ local_set_type_mismatch:
.functype local_set_type_mismatch () -> ()
.local i32
f32.const 1.0
-# CHECK: [[@LINE+1]]:3: error: popped f32, expected i32
+# CHECK: [[@LINE+1]]:3: error: type mismatch, expected [i32] but got [f32]
local.set 0
end_function
@@ -40,7 +40,7 @@ local_tee_no_local_type:
local_tee_empty_stack_while_popping:
.functype local_tee_empty_stack_while_popping () -> ()
.local f32
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping f32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [f32] but got []
local.tee 0
end_function
@@ -48,8 +48,9 @@ local_tee_type_mismatch:
.functype local_tee_type_mismatch () -> ()
.local f32
i32.const 1
-# CHECK: :[[@LINE+1]]:3: error: popped i32, expected f32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [f32] but got [i32]
local.tee 0
+ drop
end_function
global_get_missing_globaltype:
@@ -79,7 +80,7 @@ global_set_expected_expression_operand:
global_set_empty_stack_while_popping:
.functype global_set_empty_stack_while_popping () -> ()
.globaltype valid_global, i64
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping i64
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i64] but got []
global.set valid_global
end_function
@@ -87,7 +88,7 @@ global_set_type_mismatch:
.functype global_set_type_mismatch () -> ()
.globaltype valid_global, i64
i32.const 1
-# CHECK: :[[@LINE+1]]:3: error: popped i32, expected i64
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i64] but got [i32]
global.set valid_global
end_function
@@ -109,46 +110,52 @@ table_get_missing_tabletype:
table_get_empty_stack_while_popping:
.functype table_get_empty_stack_while_popping () -> ()
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
table.get valid_table
+ drop
end_function
table_get_type_mismatch:
.functype table_get_type_mismatch () -> ()
f32.const 1.0
-# CHECK: :[[@LINE+1]]:3: error: popped f32, expected i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got [f32]
table.get valid_table
+ drop
end_function
table_set_expected_expression_operand:
.functype table_set_expected_expression_operand () -> ()
+ i32.const 0
# CHECK: :[[@LINE+1]]:13: error: expected expression operand
table.set 1
end_function
table_set_missing_tabletype:
.functype table_set_missing_tabletype () -> ()
+ i32.const 0
# CHECK: :[[@LINE+1]]:13: error: symbol foo: missing .tabletype
table.set foo
end_function
table_set_empty_stack_while_popping_1:
.functype table_set_empty_stack_while_popping_1 () -> ()
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping externref
+# CHECK: :[[@LINE+2]]:3: error: type mismatch, expected [externref] but got []
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
table.set valid_table
end_function
table_set_empty_stack_while_popping_2:
.functype table_set_empty_stack_while_popping_2 (externref) -> ()
local.get 0
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
table.set valid_table
end_function
table_set_type_mismatch_1:
.functype table_set_type_mismatch_1 () -> ()
+ i32.const 0
ref.null_func
-# CHECK: :[[@LINE+1]]:3: error: popped funcref, expected externref
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [externref] but got [funcref]
table.set valid_table
end_function
@@ -156,32 +163,41 @@ table_set_type_mismatch_2:
.functype table_set_type_mismatch_2 () -> ()
f32.const 1.0
ref.null_extern
-# CHECK: :[[@LINE+1]]:3: error: popped f32, expected i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got [f32]
table.set valid_table
end_function
table_fill_expected_expression_operand:
.functype table_fill_expected_expression_operand () -> ()
+ i32.const 0
+ ref.null_extern
+ i32.const 4
# CHECK: :[[@LINE+1]]:14: error: expected expression operand
table.fill 1
end_function
table_fill_missing_tabletype:
.functype table_fill_missing_tabletype () -> ()
+ i32.const 0
+ ref.null_extern
+ i32.const 4
# CHECK: :[[@LINE+1]]:14: error: symbol foo: missing .tabletype
table.fill foo
end_function
table_fill_empty_stack_while_popping_1:
.functype table_fill_empty_stack_while_popping_1 () -> ()
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping i32
+# CHECK: :[[@LINE+3]]:3: error: type mismatch, expected [i32] but got []
+# CHECK: :[[@LINE+2]]:3: error: type mismatch, expected [externref] but got []
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
table.fill valid_table
end_function
table_fill_empty_stack_while_popping_2:
.functype table_fill_empty_stack_while_popping_2 (i32) -> ()
local.get 0
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping externref
+# CHECK: :[[@LINE+2]]:3: error: type mismatch, expected [externref] but got []
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
table.fill valid_table
end_function
@@ -189,22 +205,25 @@ table_fill_empty_stack_while_popping_3:
.functype table_fill_empty_stack_while_popping_3 (i32, externref) -> ()
local.get 1
local.get 0
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
table.fill valid_table
end_function
table_fill_type_mismatch_1:
.functype table_fill_type_mismatch_1 () -> ()
+ i32.const 0
+ ref.null_extern
ref.null_func
-# CHECK: :[[@LINE+1]]:3: error: popped funcref, expected i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got [funcref]
table.fill valid_table
end_function
table_fill_type_mismatch_2:
.functype table_fill_type_mismatch_2 () -> ()
+ i32.const 0
ref.null_func
i32.const 1
-# CHECK: [[@LINE+1]]:3: error: popped funcref, expected externref
+# CHECK: [[@LINE+1]]:3: error: type mismatch, expected [externref] but got [funcref]
table.fill valid_table
end_function
@@ -213,23 +232,16 @@ table_fill_type_mismatch_3:
f32.const 2.0
ref.null_extern
i32.const 1
-# CHECK: :[[@LINE+1]]:3: error: popped f32, expected i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got [f32]
table.fill valid_table
end_function
table_fill_type_mismatch_4:
.functype table_fill_type_mismatch_4 () -> ()
- ref.null_exn
i32.const 1
-# CHECK: [[@LINE+1]]:3: error: popped exnref, expected externref
- table.fill valid_table
- end_function
-
-table_fill_type_mismatch_5:
- .functype table_fill_type_mismatch_5 () -> ()
ref.null_exn
i32.const 1
-# CHECK: [[@LINE+1]]:3: error: popped exnref, expected externref
+# CHECK: [[@LINE+1]]:3: error: type mismatch, expected [externref] but got [exnref]
table.fill valid_table
end_function
@@ -244,14 +256,15 @@ table_grow_non_exist_table:
table_grow_type_mismatch_1:
.functype table_grow_type_mismatch_1 (externref, i32) -> (i32)
local.get 1
-# CHECK: [[@LINE+1]]:3: error: empty stack while popping externref
+# CHECK: [[@LINE+1]]:3: error: type mismatch, expected [externref] but got []
table.grow valid_table
end_function
table_grow_type_mismatch_2:
.functype table_grow_type_mismatch_2 (externref, i32) -> (i32)
local.get 0
-# CHECK: [[@LINE+1]]:3: error: popped externref, expected i32
+ local.get 0
+# CHECK: [[@LINE+1]]:3: error: type mismatch, expected [i32] but got [externref]
table.grow valid_table
end_function
@@ -260,57 +273,62 @@ table_grow_wrong_result:
local.get 0
local.get 1
table.grow valid_table
-# CHECK: [[@LINE+1]]:3: error: popped i32, expected f32
+# CHECK: [[@LINE+1]]:3: error: type mismatch, expected [f32] but got [i32]
end_function
drop_empty_stack_while_popping:
.functype drop_empty_stack_while_popping () -> ()
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping value
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [any] but got []
drop
end_function
end_block_insufficient_values_on_stack_1:
.functype end_block_insufficient_values_on_stack_1 () -> ()
block i32
-# CHECK: :[[@LINE+1]]:3: error: end: insufficient values on the type stack
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
end_block
+ drop
end_function
end_block_insufficient_values_on_stack_2:
.functype end_block_insufficient_values_on_stack_2 () -> ()
block () -> (i32)
-# CHECK: :[[@LINE+1]]:3: error: end: insufficient values on the type stack
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
end_block
+ drop
end_function
end_block_type_mismatch:
.functype end_block_type_mismatch () -> ()
block i32
f32.const 1.0
-# CHECK: :[[@LINE+1]]:3: error: end got f32, expected i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got [f32]
end_block
+ drop
end_function
end_loop_insufficient_values_on_stack:
.functype end_loop_insufficient_values_on_stack () -> ()
loop i32
-# CHECK: :[[@LINE+1]]:3: error: end: insufficient values on the type stack
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
end_loop
+ drop
end_function
end_loop_type_mismatch:
.functype end_loop_type_mismatch () -> ()
loop f32
i32.const 1
-# CHECK: :[[@LINE+1]]:3: error: end got i32, expected f32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [f32] but got [i32]
end_loop
+ drop
end_function
end_if_insufficient_values_on_stack_1:
.functype end_if_insufficient_values_on_stack_1 () -> ()
i32.const 1
if i32
-# CHECK: :[[@LINE+1]]:3: error: end: insufficient values on the type stack
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
end_if
end_function
@@ -319,8 +337,9 @@ end_if_type_mismatch_1:
i32.const 1
if f32
i32.const 1
-# CHECK: :[[@LINE+1]]:3: error: end got i32, expected f32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [f32] but got [i32]
end_if
+ drop
end_function
end_if_insufficient_values_on_stack_2:
@@ -329,7 +348,7 @@ end_if_insufficient_values_on_stack_2:
if i32
i32.const 2
else
-# CHECK: :[[@LINE+1]]:3: error: end: insufficient values on the type stack
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
end_if
drop
end_function
@@ -341,7 +360,7 @@ end_if_type_mismatch_2:
i32.const 2
else
f32.const 3.0
-# CHECK: :[[@LINE+1]]:3: error: end got f32, expected i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got [f32]
end_if
drop
end_function
@@ -350,7 +369,7 @@ else_insufficient_values_on_stack:
.functype else_insufficient_values_on_stack () -> ()
i32.const 1
if i32
-# CHECK: :[[@LINE+1]]:3: error: end: insufficient values on the type stack
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
else
i32.const 0
end_if
@@ -362,7 +381,7 @@ else_type_mismatch:
i32.const 1
if i32
f32.const 0.0
-# CHECK: :[[@LINE+1]]:3: error: popped f32, expected i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got [f32]
else
i32.const 0
end_if
@@ -377,7 +396,7 @@ end_try_insufficient_values_on_stack:
try i32
i32.const 0
catch_all
-# CHECK: :[[@LINE+1]]:3: error: end: insufficient values on the type stack
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
end_try
drop
end_function
@@ -387,7 +406,7 @@ end_try_type_mismatch:
try i32
i32.const 0
catch tag_f32
-# CHECK: :[[@LINE+1]]:3: error: end got f32, expected i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got [f32]
end_try
drop
end_function
@@ -395,7 +414,7 @@ end_try_type_mismatch:
catch_insufficient_values_on_stack:
.functype catch_insufficient_values_on_stack () -> ()
try i32
-# CHECK: :[[@LINE+1]]:3: error: end: insufficient values on the type stack
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
catch tag_i32
end_try
drop
@@ -405,7 +424,7 @@ catch_type_mismatch:
.functype catch_type_mismatch () -> ()
try i32
f32.const 1.0
-# CHECK: :[[@LINE+1]]:3: error: popped f32, expected i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got [f32]
catch tag_i32
end_try
drop
@@ -414,7 +433,7 @@ catch_type_mismatch:
catch_all_insufficient_values_on_stack:
.functype catch_all_insufficient_values_on_stack () -> ()
try i32
-# CHECK: :[[@LINE+1]]:3: error: end: insufficient values on the type stack
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
catch_all
i32.const 0
end_try
@@ -425,7 +444,7 @@ catch_all_type_mismatch:
.functype catch_all_type_mismatch () -> ()
try i32
f32.const 1.0
-# CHECK: :[[@LINE+1]]:3: error: popped f32, expected i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got [f32]
catch_all
i32.const 0
end_try
@@ -435,7 +454,7 @@ catch_all_type_mismatch:
delegate_insufficient_values_on_stack:
.functype delegate_insufficient_values_on_stack () -> ()
try i32
-# CHECK: :[[@LINE+1]]:3: error: end: insufficient values on the type stack
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
delegate 0
drop
end_function
@@ -444,46 +463,46 @@ delegate_type_mismatch:
.functype delegate_type_mismatch () -> ()
try i32
f32.const 1.0
-# CHECK: :[[@LINE+1]]:3: error: end got f32, expected i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got [f32]
delegate 0
drop
end_function
end_function_empty_stack_while_popping:
.functype end_function_empty_stack_while_popping () -> (i32)
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
end_function
end_function_type_mismatch:
.functype end_function_type_mismatch () -> (f32)
i32.const 1
-# CHECK: :[[@LINE+1]]:3: error: popped i32, expected f32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [f32] but got [i32]
end_function
end_function_superfluous_end_function_values:
.functype end_function_superfluous_end_function_values () -> ()
i32.const 1
f32.const 2.0
-# CHECK: :[[@LINE+1]]:3: error: 2 superfluous return values
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [] but got [i32, f32]
end_function
return_empty_stack_while_popping:
.functype return_empty_stack_while_popping () -> (i32)
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
return
end_function
return_type_mismatch:
.functype return_type_mismatch () -> (f32)
i32.const 1
-# CHECK: :[[@LINE+1]]:3: error: popped i32, expected f32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [f32] but got [i32]
return
end_function
# Missing index for call_indirect.
call_indirect_empty_stack_while_popping_1:
.functype call_indirect_empty_stack_while_popping_1 () -> ()
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
call_indirect () -> ()
end_function
@@ -491,7 +510,7 @@ call_indirect_empty_stack_while_popping_1:
call_indirect_empty_stack_while_popping_2:
.functype call_indirect_empty_stack_while_popping_1 (f32) -> ()
i32.const 1
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping f32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [f32] but got []
call_indirect (f32) -> ()
end_function
@@ -499,7 +518,7 @@ call_indirect_type_mismatch_for_argument:
.functype call_indirect_type_mismatch_for_argument () -> ()
i32.const 1
i32.const 2
-# CHECK: :[[@LINE+1]]:3: error: popped i32, expected f32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [f32] but got [i32]
call_indirect (f32) -> ()
end_function
@@ -507,13 +526,13 @@ call_indirect_superfluous_value_at_end:
.functype call_indirect_superfluous_value_at_end () -> ()
i32.const 1
call_indirect () -> (i64)
-# CHECK: :[[@LINE+1]]:3: error: 1 superfluous return values
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [] but got [i64]
end_function
# Missing index for return_call_indirect.
return_call_indirect_empty_stack_while_popping_1:
.functype return_call_indirect_empty_stack_while_popping_1 () -> ()
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
return_call_indirect () -> ()
end_function
@@ -521,7 +540,7 @@ return_call_indirect_empty_stack_while_popping_1:
return_call_indirect_empty_stack_while_popping_2:
.functype return_call_indirect_empty_stack_while_popping_2 () -> ()
i32.const 1
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping f32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [f32] but got []
return_call_indirect (f32) -> ()
end_function
@@ -535,14 +554,14 @@ call_expected_expression_operand:
call_empty_stack_while_popping:
.functype call_empty_stack_while_popping () -> ()
-# CHECK: [[@LINE+1]]:3: error: empty stack while popping i32
+# CHECK: [[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
call fn_i32_to_void
end_function
call_type_mismatch:
.functype call_type_mismatch () -> ()
f32.const 1.0
-# CHECK: :[[@LINE+1]]:3: error: popped f32, expected i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got [f32]
call fn_i32_to_void
end_function
@@ -551,7 +570,7 @@ call_type_mismatch:
call_superfluous_value_at_end:
.functype call_superfluous_value_at_end () -> ()
call fn_void_to_i32
-# CHECK: :[[@LINE+1]]:3: error: 1 superfluous return values
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [] but got [i32]
end_function
call_missing_functype:
@@ -568,14 +587,14 @@ return_call_expected_expression_operand:
return_call_empty_stack_while_popping:
.functype return_call_empty_stack_while_popping () -> ()
-# CHECK: [[@LINE+1]]:3: error: empty stack while popping i32
+# CHECK: [[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
return_call fn_i32_to_void
end_function
return_call_type_mismatch:
.functype return_call_type_mismatch () -> ()
f32.const 1.0
-# CHECK: :[[@LINE+1]]:3: error: popped f32, expected i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got [f32]
return_call fn_i32_to_void
end_function
@@ -607,27 +626,29 @@ catch_superfluous_value_at_end:
catch tag_i32
end_try
# FIXME: Superfluous value should be caught at end_try?
-# CHECK: :[[@LINE+1]]:3: error: 1 superfluous return values
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [] but got [i32]
end_function
ref_is_null_empty_stack_while_popping:
.functype ref_is_null_empty_stack_while_popping () -> ()
-# CHECK: [[@LINE+1]]:3: error: empty stack while popping reftype
+# CHECK: [[@LINE+1]]:3: error: type mismatch, expected [ref] but got []
ref.is_null
+ drop
end_function
ref_is_null_type_mismatch:
.functype ref_is_null_type_mismatch () -> ()
i32.const 1
-# CHECK: [[@LINE+1]]:3: error: popped i32, expected reftype
+# CHECK: [[@LINE+1]]:3: error: type mismatch, expected [ref] but got [i32]
ref.is_null
+ drop
end_function
ref_is_null_pushes_i32:
.functype ref_is_null_pushes_i32 () -> (i64)
ref.null_func
ref.is_null
-# CHECK: :[[@LINE+1]]:3: error: popped i32, expected i64
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i64] but got [i32]
end_function
# For the other instructions, the type checker checks vs the operands in the
@@ -636,16 +657,18 @@ ref_is_null_pushes_i32:
other_insn_test_1:
.functype other_insn_test_1 () -> ()
-# CHECK: [[@LINE+1]]:3: error: empty stack while popping i32
+# CHECK: [[@LINE+1]]:3: error: type mismatch, expected [i32, i32] but got []
i32.add
+ drop
end_function
other_insn_test_2:
.functype other_insn_test_2 () -> ()
i32.const 1
ref.null_func
-# CHECK: :[[@LINE+1]]:3: error: popped funcref, expected i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32, i32] but got [i32, funcref]
i32.add
+ drop
end_function
other_insn_test_3:
@@ -653,7 +676,7 @@ other_insn_test_3:
f32.const 1.0
f32.const 2.0
f32.add
-# CHECK: :[[@LINE+1]]:3: error: 1 superfluous return values
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [] but got [f32]
end_function
# Unreachable code within 'block' does not affect type checking after
@@ -663,7 +686,7 @@ check_after_unreachable_within_block:
block
unreachable
end_block
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping value
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [any] but got []
drop
end_function
@@ -673,7 +696,7 @@ check_after_unreachable_within_loop:
loop
unreachable
end_loop
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping value
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [any] but got []
drop
end_function
@@ -686,7 +709,7 @@ check_after_unreachable_within_if_1:
else
unreachable
end_if
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping value
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [any] but got []
drop
end_function
@@ -697,7 +720,7 @@ check_after_unreachable_within_if_2:
if
unreachable
else
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping value
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [any] but got []
drop
end_if
end_function
@@ -710,7 +733,7 @@ check_after_unreachable_within_try_1:
catch_all
unreachable
end_try
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping value
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [any] but got []
drop
end_function
@@ -721,7 +744,7 @@ check_after_unreachable_within_try_2:
unreachable
catch tag_i32
drop
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping value
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [any] but got []
drop
end_try
end_function
@@ -732,7 +755,7 @@ check_after_unreachable_within_try_3:
try
unreachable
catch_all
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping value
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [any] but got []
drop
end_try
end_function
@@ -743,7 +766,7 @@ check_after_unreachable_within_try_4:
try
unreachable
delegate 0
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping value
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [any] but got []
drop
end_function
@@ -753,7 +776,7 @@ br_invalid_type_loop:
loop (i32) -> (f32)
drop
f32.const 1.0
-# CHECK: :[[@LINE+1]]:5: error: br got f32, expected i32
+# CHECK: :[[@LINE+1]]:5: error: type mismatch, expected [i32] but got [f32]
br 0
end_loop
drop
@@ -763,7 +786,7 @@ br_invalid_type_block:
.functype br_invalid_type_block () -> ()
i32.const 1
block (i32) -> (f32)
-# CHECK: :[[@LINE+1]]:5: error: br got i32, expected f32
+# CHECK: :[[@LINE+1]]:5: error: type mismatch, expected [f32] but got [i32]
br 0
f32.const 1.0
end_block
@@ -777,7 +800,7 @@ br_invalid_type_if:
f32.const 1.0
else
i32.const 1
-# CHECK: :[[@LINE+1]]:5: error: br got i32, expected f32
+# CHECK: :[[@LINE+1]]:5: error: type mismatch, expected [f32] but got [i32]
br 0
end_if
drop
@@ -787,7 +810,7 @@ br_invalid_type_try:
.functype br_invalid_type_try () -> ()
try f32
i32.const 1
-# CHECK: :[[@LINE+1]]:5: error: br got i32, expected f32
+# CHECK: :[[@LINE+1]]:5: error: type mismatch, expected [f32] but got [i32]
br 0
catch tag_f32
end_try
@@ -799,7 +822,7 @@ br_invalid_type_catch:
try f32
f32.const 1.0
catch tag_i32
-# CHECK: :[[@LINE+1]]:5: error: br got i32, expected f32
+# CHECK: :[[@LINE+1]]:5: error: type mismatch, expected [f32] but got [i32]
br 0
end_try
drop
@@ -811,7 +834,7 @@ br_invalid_type_catch_all:
f32.const 1.0
catch_all
i32.const 1
-# CHECK: :[[@LINE+1]]:5: error: br got i32, expected f32
+# CHECK: :[[@LINE+1]]:5: error: type mismatch, expected [f32] but got [i32]
br 0
end_try
drop
@@ -834,7 +857,7 @@ br_incorrect_signature:
block f32
block i32
i32.const 1
-# CHECK: :[[@LINE+1]]:7: error: br got i32, expected f32
+# CHECK: :[[@LINE+1]]:7: error: type mismatch, expected [f32] but got [i32]
br 1
end_block
drop
@@ -847,7 +870,7 @@ br_incorrect_func_signature:
.functype br_incorrect_func_signature () -> (i32)
block f32
f32.const 1.0
-# CHECK: :[[@LINE+1]]:5: error: br got f32, expected i32
+# CHECK: :[[@LINE+1]]:5: error: type mismatch, expected [i32] but got [f32]
br 1
end_block
drop
@@ -856,20 +879,29 @@ br_incorrect_func_signature:
multiple_errors_in_function:
.functype multiple_errors_in_function () -> ()
-# CHECK: :[[@LINE+2]]:3: error: empty stack while popping i32
+# CHECK: :[[@LINE+2]]:3: error: type mismatch, expected [i32] but got []
# CHECK: :[[@LINE+1]]:13: error: expected expression operand
table.get 1
-# CHECK: :[[@LINE+3]]:3: error: empty stack while popping i32
-# CHECK: :[[@LINE+2]]:3: error: empty stack while popping externref
-# CHECK: :[[@LINE+1]]:3: error: empty stack while popping i32
+# CHECK: :[[@LINE+3]]:3: error: type mismatch, expected [i32] but got []
+# CHECK: :[[@LINE+2]]:3: error: type mismatch, expected [externref] but got []
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got []
table.fill valid_table
f32.const 0.0
ref.null_extern
-# CHECK: :[[@LINE+2]]:3: error: popped externref, expected i32
-# CHECK: :[[@LINE+1]]:3: error: popped f32, expected i32
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32, i32] but got [f32, externref]
i32.add
drop
+ end_function
+
+.functype take_and_return_multi(i32, i64, f32, f64) -> (i32, i64, f32, f64)
+call_with_multi_param_and_return:
+ .functype call_with_multi_param_and_return () -> (i32)
+ ref.null_extern
+ f32.const 0.0
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32, i64, f32, f64] but got [externref, f32]
+ call take_and_return_multi
+# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [i32] but got [i32, i64, f32, f64]
end_function
diff --git a/llvm/test/MC/WebAssembly/type-checker-return.s b/llvm/test/MC/WebAssembly/type-checker-return.s
index 552093bc555bd9..016c0340581016 100644
--- a/llvm/test/MC/WebAssembly/type-checker-return.s
+++ b/llvm/test/MC/WebAssembly/type-checker-return.s
@@ -1,10 +1,5 @@
# RUN: llvm-mc -triple=wasm32 -mattr=+tail-call %s 2>&1
-# XFAIL: *
-
-# FIXME: These shouldn't produce an error, as return will implicitly drop any
-# superfluous values.
-
return_superfluous_return_values:
.functype return_superfluous_return_values () -> ()
i32.const 1
More information about the llvm-commits
mailing list