Skip to content

[WebAssembly] Support type checker for new EH #111069

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 82 additions & 9 deletions llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ void WebAssemblyAsmTypeCheck::localDecl(
}

void WebAssemblyAsmTypeCheck::dumpTypeStack(Twine Msg) {
LLVM_DEBUG({ dbgs() << Msg << getTypesString(Stack, 0) << "\n"; });
LLVM_DEBUG({ dbgs() << Msg << getTypesString(Stack) << "\n"; });
}

bool WebAssemblyAsmTypeCheck::typeError(SMLoc ErrorLoc, const Twine &Msg) {
Expand Down Expand Up @@ -116,8 +116,15 @@ std::string WebAssemblyAsmTypeCheck::getTypesString(ArrayRef<StackType> Types,
return SS.str();
}

std::string
WebAssemblyAsmTypeCheck::getTypesString(ArrayRef<wasm::ValType> Types,
size_t StartPos) {
return getTypesString(valTypesToStackTypes(Types), StartPos);
}

SmallVector<WebAssemblyAsmTypeCheck::StackType, 4>
WebAssemblyAsmTypeCheck::valTypeToStackType(ArrayRef<wasm::ValType> ValTypes) {
WebAssemblyAsmTypeCheck::valTypesToStackTypes(
ArrayRef<wasm::ValType> ValTypes) {
SmallVector<StackType, 4> Types(ValTypes.size());
std::transform(ValTypes.begin(), ValTypes.end(), Types.begin(),
[](wasm::ValType Val) -> StackType { return Val; });
Expand All @@ -127,7 +134,7 @@ WebAssemblyAsmTypeCheck::valTypeToStackType(ArrayRef<wasm::ValType> ValTypes) {
bool WebAssemblyAsmTypeCheck::checkTypes(SMLoc ErrorLoc,
ArrayRef<wasm::ValType> ValTypes,
bool ExactMatch) {
return checkTypes(ErrorLoc, valTypeToStackType(ValTypes), ExactMatch);
return checkTypes(ErrorLoc, valTypesToStackTypes(ValTypes), ExactMatch);
}

bool WebAssemblyAsmTypeCheck::checkTypes(SMLoc ErrorLoc,
Expand Down Expand Up @@ -178,14 +185,14 @@ bool WebAssemblyAsmTypeCheck::checkTypes(SMLoc ErrorLoc,
: std::max((int)BlockStackStartPos,
(int)Stack.size() - (int)Types.size());
return typeError(ErrorLoc, "type mismatch, expected " +
getTypesString(Types, 0) + " but got " +
getTypesString(Types) + " but got " +
getTypesString(Stack, StackStartPos));
}

bool WebAssemblyAsmTypeCheck::popTypes(SMLoc ErrorLoc,
ArrayRef<wasm::ValType> ValTypes,
bool ExactMatch) {
return popTypes(ErrorLoc, valTypeToStackType(ValTypes), ExactMatch);
return popTypes(ErrorLoc, valTypesToStackTypes(ValTypes), ExactMatch);
}

bool WebAssemblyAsmTypeCheck::popTypes(SMLoc ErrorLoc,
Expand Down Expand Up @@ -215,7 +222,7 @@ bool WebAssemblyAsmTypeCheck::popAnyType(SMLoc ErrorLoc) {
}

void WebAssemblyAsmTypeCheck::pushTypes(ArrayRef<wasm::ValType> ValTypes) {
Stack.append(valTypeToStackType(ValTypes));
Stack.append(valTypesToStackTypes(ValTypes));
}

bool WebAssemblyAsmTypeCheck::getLocal(SMLoc ErrorLoc, const MCOperand &LocalOp,
Expand Down Expand Up @@ -322,6 +329,63 @@ bool WebAssemblyAsmTypeCheck::endOfFunction(SMLoc ErrorLoc, bool ExactMatch) {
return checkTypes(ErrorLoc, FuncInfo.Sig.Returns, ExactMatch);
}

// Unlike checkTypes() family, this just compare the equivalence of the two
// ValType vectors
static bool compareTypes(ArrayRef<wasm::ValType> TypesA,
ArrayRef<wasm::ValType> TypesB) {
if (TypesA.size() != TypesB.size())
return true;
for (size_t I = 0, E = TypesA.size(); I < E; I++)
if (TypesA[I] != TypesB[I])
return true;
return false;
}

bool WebAssemblyAsmTypeCheck::checkTryTable(SMLoc ErrorLoc,
const MCInst &Inst) {
bool Error = false;
unsigned OpIdx = 1; // OpIdx 0 is the block type
int64_t NumCatches = Inst.getOperand(OpIdx++).getImm();
for (int64_t I = 0; I < NumCatches; I++) {
int64_t Opcode = Inst.getOperand(OpIdx++).getImm();
std::string ErrorMsgBase =
"try_table: catch index " + std::to_string(I) + ": ";

const wasm::WasmSignature *Sig = nullptr;
SmallVector<wasm::ValType> SentTypes;
if (Opcode == wasm::WASM_OPCODE_CATCH ||
Opcode == wasm::WASM_OPCODE_CATCH_REF) {
if (!getSignature(ErrorLoc, Inst.getOperand(OpIdx++),
wasm::WASM_SYMBOL_TYPE_TAG, Sig))
SentTypes.insert(SentTypes.end(), Sig->Params.begin(),
Sig->Params.end());
else
Error = true;
}
if (Opcode == wasm::WASM_OPCODE_CATCH_REF ||
Opcode == wasm::WASM_OPCODE_CATCH_ALL_REF) {
SentTypes.push_back(wasm::ValType::EXNREF);
}

unsigned Level = Inst.getOperand(OpIdx++).getImm();
if (Level < BlockInfoStack.size()) {
const auto &DestBlockInfo =
BlockInfoStack[BlockInfoStack.size() - Level - 1];
if (compareTypes(SentTypes, DestBlockInfo.Sig.Returns)) {
std::string ErrorMsg =
ErrorMsgBase + "type mismatch, catch tag type is " +
getTypesString(SentTypes) + ", but destination's type is " +
getTypesString(DestBlockInfo.Sig.Returns);
Error |= typeError(ErrorLoc, ErrorMsg);
}
} else {
Error = typeError(ErrorLoc, ErrorMsgBase + "invalid depth " +
std::to_string(Level));
}
}
return Error;
}

bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
OperandVector &Operands) {
auto Opc = Inst.getOpcode();
Expand Down Expand Up @@ -460,10 +524,13 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
return popType(ErrorLoc, Any{});
}

if (Name == "block" || Name == "loop" || Name == "if" || Name == "try") {
if (Name == "block" || Name == "loop" || Name == "if" || Name == "try" ||
Name == "try_table") {
bool Error = Name == "if" && popType(ErrorLoc, wasm::ValType::I32);
// Pop block input parameters and check their types are correct
Error |= popTypes(ErrorLoc, LastSig.Params);
if (Name == "try_table")
Error |= checkTryTable(ErrorLoc, Inst);
// Push a new block info
BlockInfoStack.push_back({LastSig, Stack.size(), Name == "loop"});
// Push back block input parameters
Expand All @@ -472,8 +539,8 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
}

if (Name == "end_block" || Name == "end_loop" || Name == "end_if" ||
Name == "end_try" || Name == "delegate" || Name == "else" ||
Name == "catch" || Name == "catch_all") {
Name == "end_try" || Name == "delegate" || Name == "end_try_table" ||
Name == "else" || Name == "catch" || Name == "catch_all") {
assert(!BlockInfoStack.empty());
// Check if the types on the stack match with the block return type
const auto &LastBlockInfo = BlockInfoStack.back();
Expand Down Expand Up @@ -586,6 +653,12 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
return Error;
}

if (Name == "throw_ref") {
bool Error = popType(ErrorLoc, wasm::ValType::EXNREF);
pushType(Polymorphic{});
return Error;
}

// The current instruction is a stack instruction which doesn't have
// explicit operands that indicate push/pop types, so we get those from
// the register version of the same instruction.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,11 @@ class WebAssemblyAsmTypeCheck final {
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);
std::string getTypesString(ArrayRef<wasm::ValType> Types,
size_t StartPos = 0);
std::string getTypesString(ArrayRef<StackType> Types, size_t StartPos = 0);
SmallVector<StackType, 4>
valTypeToStackType(ArrayRef<wasm::ValType> ValTypes);
valTypesToStackTypes(ArrayRef<wasm::ValType> ValTypes);

void dumpTypeStack(Twine Msg);
bool typeError(SMLoc ErrorLoc, const Twine &Msg);
Expand All @@ -80,6 +82,7 @@ class WebAssemblyAsmTypeCheck final {
bool getTable(SMLoc ErrorLoc, const MCOperand &TableOp, wasm::ValType &Type);
bool getSignature(SMLoc ErrorLoc, const MCOperand &SigOp,
wasm::WasmSymbolType Type, const wasm::WasmSignature *&Sig);
bool checkTryTable(SMLoc ErrorLoc, const MCInst &Inst);

public:
WebAssemblyAsmTypeCheck(MCAsmParser &Parser, const MCInstrInfo &MII,
Expand Down
1 change: 1 addition & 0 deletions llvm/test/MC/WebAssembly/annotations.s
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
test_annotation:
.functype test_annotation () -> ()
.tagtype __cpp_exception i32
.tagtype __c_longjmp i32
try
br 0
catch __cpp_exception
Expand Down
6 changes: 2 additions & 4 deletions llvm/test/MC/WebAssembly/eh-assembly.s
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# RUN: llvm-mc -triple=wasm32-unknown-unknown -mattr=+exception-handling --no-type-check < %s | FileCheck %s
# 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 -filetype=obj -mattr=+exception-handling --no-type-check -o %t.o < %s
# RUN: llvm-mc -triple=wasm32-unknown-unknown -filetype=obj -mattr=+exception-handling -o %t.o < %s

.tagtype __cpp_exception i32
.tagtype __c_longjmp i32
Expand All @@ -24,7 +24,6 @@ eh_test:
return
end_block
throw_ref
drop
end_block
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After throw_ref, the stack is polymorphic, so you don't need to drop the i32

return
end_block
Expand Down Expand Up @@ -101,7 +100,6 @@ eh_test:
# 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
Expand Down
23 changes: 23 additions & 0 deletions llvm/test/MC/WebAssembly/type-checker-errors.s
Original file line number Diff line number Diff line change
Expand Up @@ -944,3 +944,26 @@ block_param_and_return:

# CHECK: :[[@LINE+1]]:3: error: type mismatch, expected [] but got [f32]
end_function

.tagtype __cpp_exception i32

eh_test:
.functype eh_test () -> ()
block i32
block i32
block i32
block
# CHECK: :[[@LINE+4]]:11: error: try_table: catch index 0: type mismatch, catch tag type is [i32], but destination's type is []
# CHECK: :[[@LINE+3]]:11: error: try_table: catch index 1: type mismatch, catch tag type is [i32, exnref], but destination's type is [i32]
# CHECK: :[[@LINE+2]]:11: error: try_table: catch index 2: type mismatch, catch tag type is [], but destination's type is [i32]
# CHECK: :[[@LINE+1]]:11: error: try_table: catch index 3: type mismatch, catch tag type is [exnref], but destination's type is [i32]
try_table i32 (catch __cpp_exception 0) (catch_ref __cpp_exception 1) (catch_all 2) (catch_all_ref 3)
# CHECK: :[[@LINE+1]]:11: error: type mismatch, expected [i32] but got []
end_try_table
drop
end_block
end_block
end_block
end_block
drop
end_function
Loading