[ir] Emit disassembly from the validator
This CL adds the needed plumbing to get information out of the
disassembler and into the validator in order to report source
information in the validation errors.
Bug: tint:1952
Change-Id: Id74117370ce2ac6eae5c6a33da7de52b0eb93a70
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/136080
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/ir/disassembler.cc b/src/tint/ir/disassembler.cc
index 31e3868..7b4a401 100644
--- a/src/tint/ir/disassembler.cc
+++ b/src/tint/ir/disassembler.cc
@@ -73,6 +73,12 @@
return out_;
}
+void Disassembler::EmitLine() {
+ out_ << std::endl;
+ current_output_line_ += 1;
+ current_output_start_pos_ = out_.tellp();
+}
+
void Disassembler::EmitBlockInstructions(const Block* b) {
for (const auto* inst : *b) {
Indent();
@@ -95,6 +101,10 @@
});
}
+Source::Location Disassembler::MakeCurrentLocation() {
+ return Source::Location{current_output_line_, out_.tellp() - current_output_start_pos_ + 1};
+}
+
std::string Disassembler::Disassemble() {
for (auto* ty : mod_.Types()) {
if (auto* str = ty->As<type::Struct>()) {
@@ -103,9 +113,10 @@
}
if (mod_.root_block) {
- Indent() << "# Root block" << std::endl;
+ Indent() << "# Root block";
+ EmitLine();
WalkInternal(mod_.root_block);
- out_ << std::endl;
+ EmitLine();
}
for (auto* func : mod_.functions) {
@@ -119,16 +130,11 @@
return;
}
visited_.Add(blk);
-
- // If this block is dead, nothing to do
- if (!blk->HasBranchTarget()) {
- return;
- }
-
WalkInternal(blk);
}
void Disassembler::WalkInternal(const Block* blk) {
+ SourceMarker sm(this);
Indent() << "%b" << IdOf(blk) << " = block";
if (!blk->Params().IsEmpty()) {
out_ << " (";
@@ -136,12 +142,16 @@
out_ << ")";
}
- out_ << " {" << std::endl;
+ out_ << " {";
+ EmitLine();
{
ScopedIndent si(indent_size_);
EmitBlockInstructions(blk);
}
- Indent() << "}" << std::endl;
+ Indent() << "}";
+ sm.Store(blk);
+
+ EmitLine();
}
void Disassembler::EmitBindingPoint(BindingPoint p) {
@@ -257,13 +267,15 @@
EmitReturnAttributes(func);
- out_ << " -> %b" << IdOf(func->StartTarget()) << " {" << std::endl;
+ out_ << " -> %b" << IdOf(func->StartTarget()) << " {";
+ EmitLine();
{
ScopedIndent si(indent_size_);
Walk(func->StartTarget());
}
- Indent() << "}" << std::endl;
+ Indent() << "}";
+ EmitLine();
}
void Disassembler::EmitValueWithType(const Value* val) {
@@ -327,6 +339,12 @@
[&](Default) { out_ << "Unknown value: " << val->TypeInfo().name; });
}
+void Disassembler::EmitInstructionName(std::string_view name, const Instruction* inst) {
+ SourceMarker sm(this);
+ out_ << name;
+ sm.Store(inst);
+}
+
void Disassembler::EmitInstruction(const Instruction* inst) {
tint::Switch(
inst, //
@@ -337,54 +355,71 @@
[&](const ir::Unary* u) { EmitUnary(u); },
[&](const ir::Bitcast* b) {
EmitValueWithType(b);
- out_ << " = bitcast ";
+ out_ << " = ";
+ EmitInstructionName("bitcast", b);
+ out_ << " ";
EmitArgs(b);
- out_ << std::endl;
+ EmitLine();
},
- [&](const ir::Discard*) { out_ << "discard" << std::endl; },
+ [&](const ir::Discard* d) {
+ EmitInstructionName("discard", d);
+ EmitLine();
+ },
[&](const ir::Builtin* b) {
EmitValueWithType(b);
- out_ << " = " << builtin::str(b->Func()) << " ";
+ out_ << " = ";
+ EmitInstructionName(builtin::str(b->Func()), b);
+ out_ << " ";
EmitArgs(b);
- out_ << std::endl;
+ EmitLine();
},
[&](const ir::Construct* c) {
EmitValueWithType(c);
- out_ << " = construct ";
+ out_ << " = ";
+ EmitInstructionName("construct", c);
+ out_ << " ";
EmitArgs(c);
- out_ << std::endl;
+ EmitLine();
},
[&](const ir::Convert* c) {
EmitValueWithType(c);
- out_ << " = convert " << c->FromType()->FriendlyName() << ", ";
+ out_ << " = ";
+ EmitInstructionName("convert", c);
+ out_ << " " << c->FromType()->FriendlyName() << ", ";
EmitArgs(c);
- out_ << std::endl;
+ EmitLine();
},
[&](const ir::Load* l) {
EmitValueWithType(l);
- out_ << " = load ";
+ out_ << " = ";
+ EmitInstructionName("load", l);
+ out_ << " ";
EmitValue(l->From());
- out_ << std::endl;
+ EmitLine();
},
[&](const ir::Store* s) {
- out_ << "store ";
+ EmitInstructionName("store", s);
+ out_ << " ";
EmitValue(s->To());
out_ << ", ";
EmitValue(s->From());
- out_ << std::endl;
+ EmitLine();
},
[&](const ir::UserCall* uc) {
EmitValueWithType(uc);
- out_ << " = call %" << IdOf(uc->Func());
+ out_ << " = ";
+ EmitInstructionName("call", uc);
+ out_ << " %" << IdOf(uc->Func());
if (!uc->Args().IsEmpty()) {
out_ << ", ";
}
EmitArgs(uc);
- out_ << std::endl;
+ EmitLine();
},
[&](const ir::Var* v) {
EmitValueWithType(v);
- out_ << " = var";
+ out_ << " = ";
+ EmitInstructionName("var", v);
if (v->Initializer()) {
out_ << ", ";
EmitValue(v->Initializer());
@@ -393,12 +428,13 @@
out_ << " ";
EmitBindingPoint(v->BindingPoint().value());
}
-
- out_ << std::endl;
+ EmitLine();
},
[&](const ir::Access* a) {
EmitValueWithType(a);
- out_ << " = access ";
+ out_ << " = ";
+ EmitInstructionName("access", a);
+ out_ << " ";
EmitValue(a->Object());
out_ << ", ";
for (size_t i = 0; i < a->Indices().Length(); ++i) {
@@ -407,11 +443,13 @@
}
EmitValue(a->Indices()[i]);
}
- out_ << std::endl;
+ EmitLine();
},
[&](const ir::Swizzle* s) {
EmitValueWithType(s);
- out_ << " = swizzle ";
+ out_ << " = ";
+ EmitInstructionName("swizzle", s);
+ out_ << " ";
EmitValue(s->Object());
out_ << ", ";
for (auto idx : s->Indices()) {
@@ -430,15 +468,22 @@
break;
}
}
- out_ << std::endl;
+ EmitLine();
},
[&](const ir::Branch* b) { EmitBranch(b); },
[&](Default) { out_ << "Unknown instruction: " << inst->TypeInfo().name; });
}
+void Disassembler::EmitOperand(const Value* val, const Instruction* inst, uint32_t index) {
+ SourceMarker condMarker(this);
+ EmitValue(val);
+ condMarker.Store(Operand{inst, index});
+}
+
void Disassembler::EmitIf(const If* i) {
+ SourceMarker sm(this);
out_ << "if ";
- EmitValue(i->Condition());
+ EmitOperand(i->Condition(), i, If::kConditionOperandIndex);
bool has_true = i->True()->HasBranchTarget();
bool has_false = i->False()->HasBranchTarget();
@@ -456,24 +501,32 @@
if (i->Merge()->HasBranchTarget()) {
out_ << ", m: %b" << IdOf(i->Merge());
}
- out_ << "]" << std::endl;
+ out_ << "]";
+ sm.Store(i);
+
+ EmitLine();
if (has_true) {
ScopedIndent si(indent_size_);
- Indent() << "# True block" << std::endl;
+ Indent() << "# True block";
+ EmitLine();
+
Walk(i->True());
- out_ << std::endl;
+ EmitLine();
}
if (has_false) {
ScopedIndent si(indent_size_);
- Indent() << "# False block" << std::endl;
+ Indent() << "# False block";
+ EmitLine();
+
Walk(i->False());
- out_ << std::endl;
+ EmitLine();
}
if (i->Merge()->HasBranchTarget()) {
- Indent() << "# Merge block" << std::endl;
+ Indent() << "# Merge block";
+ EmitLine();
Walk(i->Merge());
- out_ << std::endl;
+ EmitLine();
}
}
@@ -485,38 +538,47 @@
if (l->Body()->HasBranchTarget()) {
parts.Push("b: %b" + std::to_string(IdOf(l->Body())));
}
+
if (l->Continuing()->HasBranchTarget()) {
parts.Push("c: %b" + std::to_string(IdOf(l->Continuing())));
}
if (l->Merge()->HasBranchTarget()) {
parts.Push("m: %b" + std::to_string(IdOf(l->Merge())));
}
- out_ << "loop [" << utils::Join(parts, ", ") << "]" << std::endl;
+ SourceMarker sm(this);
+ out_ << "loop [" << utils::Join(parts, ", ") << "]";
+ sm.Store(l);
+ EmitLine();
if (l->Initializer()->HasBranchTarget()) {
ScopedIndent si(indent_size_);
- Indent() << "# Initializer block" << std::endl;
+ Indent() << "# Initializer block";
+ EmitLine();
Walk(l->Initializer());
- out_ << std::endl;
+ EmitLine();
}
- {
+ if (l->Body()->HasBranchTarget()) {
ScopedIndent si(indent_size_);
- Indent() << "# Body block" << std::endl;
+ Indent() << "# Body block";
+ EmitLine();
Walk(l->Body());
- out_ << std::endl;
+ EmitLine();
}
if (l->Continuing()->HasBranchTarget()) {
ScopedIndent si(indent_size_);
- Indent() << "# Continuing block" << std::endl;
+ Indent() << "# Continuing block";
+ EmitLine();
Walk(l->Continuing());
- out_ << std::endl;
+ EmitLine();
}
if (l->Merge()->HasBranchTarget()) {
- Indent() << "# Merge block" << std::endl;
+ Indent() << "# Merge block";
+ EmitLine();
+
Walk(l->Merge());
- out_ << std::endl;
+ EmitLine();
}
}
@@ -545,22 +607,28 @@
if (s->Merge()->HasBranchTarget()) {
out_ << ", m: %b" << IdOf(s->Merge());
}
- out_ << "]" << std::endl;
+ out_ << "]";
+ EmitLine();
for (auto& c : s->Cases()) {
ScopedIndent si(indent_size_);
- Indent() << "# Case block" << std::endl;
+ Indent() << "# Case block";
+ EmitLine();
+
Walk(c.Start());
- out_ << std::endl;
+ EmitLine();
}
if (s->Merge()->HasBranchTarget()) {
- Indent() << "# Merge block" << std::endl;
+ Indent() << "# Merge block";
+ EmitLine();
+
Walk(s->Merge());
- out_ << std::endl;
+ EmitLine();
}
}
void Disassembler::EmitBranch(const Branch* b) {
+ SourceMarker sm(this);
tint::Switch(
b, //
[&](const ir::Return*) { out_ << "ret"; },
@@ -584,7 +652,9 @@
out_ << " ";
EmitValueList(b->Args());
}
- out_ << std::endl;
+ sm.Store(b);
+
+ EmitLine();
}
void Disassembler::EmitValueList(utils::Slice<Value const* const> values) {
@@ -657,7 +727,7 @@
EmitValue(b->LHS());
out_ << ", ";
EmitValue(b->RHS());
- out_ << std::endl;
+ EmitLine();
}
void Disassembler::EmitUnary(const Unary* u) {
@@ -673,11 +743,13 @@
}
out_ << " ";
EmitValue(u->Val());
- out_ << std::endl;
+ EmitLine();
}
void Disassembler::EmitStructDecl(const type::Struct* str) {
- out_ << str->Name().Name() << " = struct @align(" << str->Align() << ") {" << std::endl;
+ out_ << str->Name().Name() << " = struct @align(" << str->Align() << ") {";
+ EmitLine();
+
for (auto* member : str->Members()) {
out_ << " " << member->Name().Name() << ":" << member->Type()->FriendlyName();
out_ << " @offset(" << member->Offset() << ")";
@@ -698,11 +770,11 @@
if (member->Attributes().builtin.has_value()) {
out_ << ", @builtin(" << member->Attributes().builtin.value() << ")";
}
-
- out_ << std::endl;
+ EmitLine();
}
- out_ << "}" << std::endl;
- out_ << std::endl;
+ out_ << "}";
+ EmitLine();
+ EmitLine();
}
} // namespace tint::ir
diff --git a/src/tint/ir/disassembler.h b/src/tint/ir/disassembler.h
index ea27470..6c58639 100644
--- a/src/tint/ir/disassembler.h
+++ b/src/tint/ir/disassembler.h
@@ -39,6 +39,30 @@
/// Helper class to disassemble the IR
class Disassembler {
public:
+ /// An operand used in an instruction
+ struct Operand {
+ /// The instruction
+ const Instruction* instruction = nullptr;
+ /// The operand index
+ uint32_t operand_index = 0u;
+
+ /// A specialization of utils::Hasher for Operand.
+ struct Hasher {
+ /// @param u the operand to hash
+ /// @returns a hash of the operand
+ inline std::size_t operator()(const Operand& u) const {
+ return utils::Hash(u.instruction, u.operand_index);
+ }
+ };
+
+ /// An equality helper for Operand.
+ /// @param other the operand to compare against
+ /// @returns true if the two operands are equal
+ bool operator==(const Operand& other) const {
+ return instruction == other.instruction && operand_index == other.operand_index;
+ }
+ };
+
/// Constructor
/// @param mod the module
explicit Disassembler(const Module& mod);
@@ -55,7 +79,61 @@
/// @returns the string representation
std::string AsString() const { return out_.str(); }
+ /// @param inst the instruction to retrieve
+ /// @returns the source for the instruction
+ Source InstructionSource(const Instruction* inst) {
+ return instruction_to_src_.Get(inst).value_or(Source{});
+ }
+
+ /// @param operand the operand to retrieve
+ /// @returns the source for the operand
+ Source OperandSource(Operand operand) {
+ return operand_to_src_.Get(operand).value_or(Source{});
+ }
+
+ /// @param blk teh block to retrieve
+ /// @returns the source for the block
+ Source BlockSource(const Block* blk) { return block_to_src_.Get(blk).value_or(Source{}); }
+
+ /// Stores the given @p src location for @p inst instruction
+ /// @param inst the instruction to store
+ /// @param src the source location
+ void SetSource(const Instruction* inst, Source src) { instruction_to_src_.Add(inst, src); }
+
+ /// Stores the given @p src location for @p blk block
+ /// @param blk the block to store
+ /// @param src the source location
+ void SetSource(const Block* blk, Source src) { block_to_src_.Add(blk, src); }
+
+ /// Stores the given @p src location for @p op operand
+ /// @param op the operand to store
+ /// @param src the source location
+ void SetSource(Operand op, Source src) { operand_to_src_.Add(op, src); }
+
+ /// @returns the source location for the current emission location
+ Source::Location MakeCurrentLocation();
+
private:
+ class SourceMarker {
+ public:
+ explicit SourceMarker(Disassembler* d) : dis_(d), begin_(dis_->MakeCurrentLocation()) {}
+ ~SourceMarker() = default;
+
+ void Store(const Instruction* inst) { dis_->SetSource(inst, MakeSource()); }
+
+ void Store(const Block* blk) { dis_->SetSource(blk, MakeSource()); }
+
+ void Store(Operand operand) { dis_->SetSource(operand, MakeSource()); }
+
+ Source MakeSource() const {
+ return Source(Source::Range(begin_, dis_->MakeCurrentLocation()));
+ }
+
+ private:
+ Disassembler* dis_ = nullptr;
+ Source::Location begin_;
+ };
+
utils::StringStream& Indent();
size_t IdOf(const Block* blk);
@@ -80,6 +158,9 @@
void EmitLoop(const Loop* l);
void EmitIf(const If* i);
void EmitStructDecl(const type::Struct* str);
+ void EmitLine();
+ void EmitOperand(const Value* val, const Instruction* inst, uint32_t index);
+ void EmitInstructionName(std::string_view name, const Instruction* inst);
const Module& mod_;
utils::StringStream out_;
@@ -88,6 +169,13 @@
utils::Hashmap<const Value*, std::string, 32> value_ids_;
uint32_t indent_size_ = 0;
bool in_function_ = false;
+
+ uint32_t current_output_line_ = 1;
+ uint32_t current_output_start_pos_ = 0;
+
+ utils::Hashmap<const Block*, Source, 8> block_to_src_;
+ utils::Hashmap<const Instruction*, Source, 8> instruction_to_src_;
+ utils::Hashmap<Operand, Source, 8, Operand::Hasher> operand_to_src_;
};
} // namespace tint::ir
diff --git a/src/tint/ir/if.h b/src/tint/ir/if.h
index b3755e1..4566ef8 100644
--- a/src/tint/ir/if.h
+++ b/src/tint/ir/if.h
@@ -40,6 +40,9 @@
/// ```
class If : public utils::Castable<If, ControlInstruction> {
public:
+ /// The index of the condition operand
+ static constexpr size_t kConditionOperandIndex = 0;
+
/// Constructor
/// @param cond the if condition
/// @param t the true block
@@ -52,9 +55,9 @@
utils::Slice<Value const* const> Args() const override { return utils::Slice<Value*>{}; }
/// @returns the if condition
- const Value* Condition() const { return operands_[0]; }
+ const Value* Condition() const { return operands_[kConditionOperandIndex]; }
/// @returns the if condition
- Value* Condition() { return operands_[0]; }
+ Value* Condition() { return operands_[kConditionOperandIndex]; }
/// @returns the true branch block
const ir::Block* True() const { return true_; }
diff --git a/src/tint/ir/module.h b/src/tint/ir/module.h
index caffbfc..d3393e6 100644
--- a/src/tint/ir/module.h
+++ b/src/tint/ir/module.h
@@ -15,6 +15,7 @@
#ifndef SRC_TINT_IR_MODULE_H_
#define SRC_TINT_IR_MODULE_H_
+#include <memory>
#include <string>
#include "src/tint/constant/manager.h"
@@ -92,6 +93,9 @@
/// The map of constant::Value to their ir::Constant.
utils::Hashmap<const constant::Value*, ir::Constant*, 16> constants;
+
+ /// If the module generated a validation error, will store the file for the disassembly text.
+ std::unique_ptr<Source::File> disassembly_file;
};
} // namespace tint::ir
diff --git a/src/tint/ir/validate.cc b/src/tint/ir/validate.cc
index 7befeb0..029ed8f 100644
--- a/src/tint/ir/validate.cc
+++ b/src/tint/ir/validate.cc
@@ -14,6 +14,7 @@
#include "src/tint/ir/validate.h"
+#include <memory>
#include <string>
#include <utility>
@@ -25,6 +26,7 @@
#include "src/tint/ir/construct.h"
#include "src/tint/ir/continue.h"
#include "src/tint/ir/convert.h"
+#include "src/tint/ir/disassembler.h"
#include "src/tint/ir/discard.h"
#include "src/tint/ir/exit_if.h"
#include "src/tint/ir/exit_loop.h"
@@ -42,14 +44,16 @@
#include "src/tint/ir/user_call.h"
#include "src/tint/ir/var.h"
#include "src/tint/switch.h"
+#include "src/tint/type/bool.h"
#include "src/tint/type/pointer.h"
+#include "src/tint/utils/scoped_assignment.h"
namespace tint::ir {
namespace {
class Validator {
public:
- explicit Validator(const Module& mod) : mod_(mod) {}
+ explicit Validator(Module& mod) : mod_(mod) {}
~Validator() {}
@@ -61,16 +65,74 @@
}
if (diagnostics_.contains_errors()) {
+ // If a diassembly file was generated then one of the diagnostics referenced the
+ // disasembly. Emit the entire disassembly file at the end of the messages.
+ if (mod_.disassembly_file) {
+ diagnostics_.add_note(tint::diag::System::IR,
+ "# Disassembly\n" + mod_.disassembly_file->content.data, {});
+ }
return std::move(diagnostics_);
}
return Success{};
}
private:
- const Module& mod_;
+ Module& mod_;
diag::List diagnostics_;
+ Disassembler dis_{mod_};
- void AddError(const std::string& err) { diagnostics_.add_error(tint::diag::System::IR, err); }
+ const Block* current_block_ = nullptr;
+
+ void DisassembleIfNeeded() {
+ if (mod_.disassembly_file) {
+ return;
+ }
+ mod_.disassembly_file = std::make_unique<Source::File>("", dis_.Disassemble());
+ }
+
+ void AddError(const Instruction* inst, const std::string& err) {
+ DisassembleIfNeeded();
+ auto src = dis_.InstructionSource(inst);
+ src.file = mod_.disassembly_file.get();
+ AddError(err, src);
+
+ if (current_block_) {
+ AddNote(current_block_, "In block");
+ }
+ }
+
+ void AddError(const Instruction* inst, uint32_t idx, const std::string& err) {
+ DisassembleIfNeeded();
+ auto src = dis_.OperandSource(Disassembler::Operand{inst, idx});
+ src.file = mod_.disassembly_file.get();
+ AddError(err, src);
+
+ if (current_block_) {
+ AddNote(current_block_, "In block");
+ }
+ }
+
+ void AddError(const Block* blk, const std::string& err) {
+ DisassembleIfNeeded();
+ auto src = dis_.BlockSource(blk);
+ src.file = mod_.disassembly_file.get();
+ AddError(err, src);
+ }
+
+ void AddNote(const Block* blk, const std::string& err) {
+ DisassembleIfNeeded();
+ auto src = dis_.BlockSource(blk);
+ src.file = mod_.disassembly_file.get();
+ AddNote(err, src);
+ }
+
+ void AddError(const std::string& err, Source src = {}) {
+ diagnostics_.add_error(tint::diag::System::IR, err, src);
+ }
+
+ void AddNote(const std::string& note, Source src = {}) {
+ diagnostics_.add_note(tint::diag::System::IR, note, src);
+ }
std::string Name(const Value* v) { return mod_.NameOf(v).Name(); }
@@ -79,15 +141,18 @@
return;
}
+ TINT_SCOPED_ASSIGNMENT(current_block_, blk);
+
for (const auto* inst : *blk) {
auto* var = inst->As<ir::Var>();
if (!var) {
- AddError(std::string("root block: invalid instruction: ") + inst->TypeInfo().name);
+ AddError(inst,
+ std::string("root block: invalid instruction: ") + inst->TypeInfo().name);
continue;
}
if (!var->Type()->Is<type::Pointer>()) {
- AddError(std::string("root block: 'var' ") + Name(var) +
- "type is not a pointer: " + var->Type()->TypeInfo().name);
+ AddError(inst, std::string("root block: 'var' ") + Name(var) +
+ "type is not a pointer: " + var->Type()->TypeInfo().name);
}
}
}
@@ -95,13 +160,15 @@
void CheckFunction(const Function* func) { CheckBlock(func->StartTarget()); }
void CheckBlock(const Block* blk) {
+ TINT_SCOPED_ASSIGNMENT(current_block_, blk);
+
if (!blk->HasBranchTarget()) {
- AddError("block: does not end in a branch");
+ AddError(blk, "block: does not end in a branch");
}
for (const auto* inst : *blk) {
if (inst->Is<ir::Branch>() && inst != blk->Branch()) {
- AddError("block: branch which isn't the final instruction");
+ AddError(inst, "block: branch which isn't the final instruction");
continue;
}
@@ -142,15 +209,15 @@
void CheckBranch(const ir::Branch* b) {
tint::Switch(
- b, //
- [&](const ir::BreakIf*) {}, //
- [&](const ir::Continue*) {}, //
- [&](const ir::ExitIf*) {}, //
- [&](const ir::ExitLoop*) {}, //
- [&](const ir::ExitSwitch*) {}, //
- [&](const ir::If*) {}, //
- [&](const ir::Loop*) {}, //
- [&](const ir::NextIteration*) {}, //
+ b, //
+ [&](const ir::BreakIf*) {}, //
+ [&](const ir::Continue*) {}, //
+ [&](const ir::ExitIf*) {}, //
+ [&](const ir::ExitLoop*) {}, //
+ [&](const ir::ExitSwitch*) {}, //
+ [&](const ir::If* if_) { CheckIf(if_); }, //
+ [&](const ir::Loop*) {}, //
+ [&](const ir::NextIteration*) {}, //
[&](const ir::Return* ret) {
if (ret->Func() == nullptr) {
AddError("return: null function");
@@ -161,11 +228,20 @@
AddError(std::string("missing validation of branch: ") + b->TypeInfo().name);
});
}
+
+ void CheckIf(const ir::If* if_) {
+ if (!if_->Condition()) {
+ AddError(if_, "if: condition is nullptr");
+ }
+ if (if_->Condition() && !if_->Condition()->Type()->Is<type::Bool>()) {
+ AddError(if_, If::kConditionOperandIndex, "if: condition must be a `bool` type");
+ }
+ }
};
} // namespace
-utils::Result<Success, diag::List> Validate(const Module& mod) {
+utils::Result<Success, diag::List> Validate(Module& mod) {
Validator v(mod);
return v.IsValid();
}
diff --git a/src/tint/ir/validate.h b/src/tint/ir/validate.h
index 9c90d64..350dd57 100644
--- a/src/tint/ir/validate.h
+++ b/src/tint/ir/validate.h
@@ -27,7 +27,7 @@
/// Validates that a given IR module is correctly formed
/// @param mod the module to validate
/// @returns true on success, an error result otherwise
-utils::Result<Success, diag::List> Validate(const Module& mod);
+utils::Result<Success, diag::List> Validate(Module& mod);
} // namespace tint::ir
diff --git a/src/tint/ir/validate_test.cc b/src/tint/ir/validate_test.cc
index 9fe9732..0fab76f 100644
--- a/src/tint/ir/validate_test.cc
+++ b/src/tint/ir/validate_test.cc
@@ -34,12 +34,48 @@
}
TEST_F(IR_ValidateTest, RootBlock_NonVar) {
+ auto* l = b.CreateLoop();
+ l->Body()->Append(b.Continue(l));
+
mod.root_block = b.CreateRootBlockIfNeeded();
- mod.root_block->Append(b.CreateLoop());
+ mod.root_block->Append(l);
auto res = ir::Validate(mod);
ASSERT_FALSE(res);
- EXPECT_EQ(res.Failure().str(), "error: root block: invalid instruction: tint::ir::Loop");
+ EXPECT_EQ(res.Failure().str(), R"(:3:3 error: root block: invalid instruction: tint::ir::Loop
+ loop [b: %b2]
+ ^^^^^^^^^^^^^
+
+:2:1 note: In block
+%b1 = block {
+^^^^^^^^^^^^^
+ loop [b: %b2]
+^^^^^^^^^^^^^^^
+ # Body block
+^^^^^^^^^^^^^^^^
+ %b2 = block {
+^^^^^^^^^^^^^^^^^
+ continue %b3
+^^^^^^^^^^^^^^^^^^
+ }
+^^^^^
+
+
+}
+^
+
+note: # Disassembly
+# Root block
+%b1 = block {
+ loop [b: %b2]
+ # Body block
+ %b2 = block {
+ continue %b3
+ }
+
+}
+
+)");
}
TEST_F(IR_ValidateTest, RootBlock_VarBadType) {
@@ -48,7 +84,25 @@
auto res = ir::Validate(mod);
ASSERT_FALSE(res);
EXPECT_EQ(res.Failure().str(),
- "error: root block: 'var' type is not a pointer: tint::type::I32");
+ R"(:3:12 error: root block: 'var' type is not a pointer: tint::type::I32
+ %1:i32 = var
+ ^^^
+
+:2:1 note: In block
+%b1 = block {
+^^^^^^^^^^^^^
+ %1:i32 = var
+^^^^^^^^^^^^^^
+}
+^
+
+note: # Disassembly
+# Root block
+%b1 = block {
+ %1:i32 = var
+}
+
+)");
}
TEST_F(IR_ValidateTest, Function) {
@@ -68,7 +122,18 @@
auto res = ir::Validate(mod);
ASSERT_FALSE(res);
- EXPECT_EQ(res.Failure().str(), "error: block: does not end in a branch");
+ EXPECT_EQ(res.Failure().str(), R"(:2:1 error: block: does not end in a branch
+ %b1 = block {
+^^^^^^^^^^^^^^^
+ }
+^^^
+
+note: # Disassembly
+%my_func = func():void -> %b1 {
+ %b1 = block {
+ }
+}
+)");
}
TEST_F(IR_ValidateTest, Block_BranchInMiddle) {
@@ -78,7 +143,91 @@
f->StartTarget()->SetInstructions(utils::Vector{b.Return(f), b.Return(f)});
auto res = ir::Validate(mod);
ASSERT_FALSE(res);
- EXPECT_EQ(res.Failure().str(), "error: block: branch which isn't the final instruction");
+ EXPECT_EQ(res.Failure().str(), R"(:3:5 error: block: branch which isn't the final instruction
+ ret
+ ^^^
+
+:2:1 note: In block
+ %b1 = block {
+^^^^^^^^^^^^^^^
+ ret
+^^^^^^^
+ ret
+^^^^^^^
+ }
+^^^
+
+note: # Disassembly
+%my_func = func():void -> %b1 {
+ %b1 = block {
+ ret
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidateTest, If_ConditionIsBool) {
+ auto* f = b.CreateFunction("my_func", mod.Types().void_());
+ mod.functions.Push(f);
+
+ auto* if_ = b.CreateIf(b.Constant(1_i));
+ if_->True()->Append(b.Return(f));
+ if_->False()->Append(b.Return(f));
+
+ f->StartTarget()->Append(if_);
+
+ auto res = ir::Validate(mod);
+ ASSERT_FALSE(res);
+ EXPECT_EQ(res.Failure().str(), R"(:3:8 error: if: condition must be a `bool` type
+ if 1i [t: %b2, f: %b3]
+ ^^
+
+:2:1 note: In block
+ %b1 = block {
+^^^^^^^^^^^^^^^
+ if 1i [t: %b2, f: %b3]
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+ # True block
+^^^^^^^^^^^^^^^^^^
+ %b2 = block {
+^^^^^^^^^^^^^^^^^^^
+ ret
+^^^^^^^^^^^
+ }
+^^^^^^^
+
+
+ # False block
+^^^^^^^^^^^^^^^^^^^
+ %b3 = block {
+^^^^^^^^^^^^^^^^^^^
+ ret
+^^^^^^^^^^^
+ }
+^^^^^^^
+
+
+ }
+^^^
+
+note: # Disassembly
+%my_func = func():void -> %b1 {
+ %b1 = block {
+ if 1i [t: %b2, f: %b3]
+ # True block
+ %b2 = block {
+ ret
+ }
+
+ # False block
+ %b3 = block {
+ ret
+ }
+
+ }
+}
+)");
}
} // namespace
diff --git a/src/tint/utils/string_stream.h b/src/tint/utils/string_stream.h
index ecb88f7..2ab62a2 100644
--- a/src/tint/utils/string_stream.h
+++ b/src/tint/utils/string_stream.h
@@ -177,6 +177,9 @@
return *this;
}
+ /// @returns the current location in the output stream
+ uint32_t tellp() { return static_cast<uint32_t>(sstream_.tellp()); }
+
/// @returns the string contents of the stream
std::string str() const { return sstream_.str(); }