mirror of https://github.com/llvm/circt.git
[Debug] Add scope op (#6454)
Add the `dbg.scope` operation to the debug dialect. The op creates an additional level of hierarchy in the DI, a "scope", which can be used to group variables and other scopes. Operations such as `hw.module` introduce an implicit scope. All debug operations within a module are added to that implicit scope, unless they have an explicit `scope` operand. Providing an explicit scope can be used to represent inlined modules. Scopes in DI do not necessarily have to correspond to levels of a module hierarchy. They can also be used to model things like control flow scopes, call stacks, and other source-language concepts. This commit also introduces an optional `scope` operand on `dbg.variable`. The `DebugInfo` analysis, which traverses the IR and builds up a canonical representation of the DI, honors this operand and adds the variables to the corresponding scope.
This commit is contained in:
parent
fd9392d31e
commit
594b8f65df
|
@ -140,6 +140,45 @@ still expose the constant values under the name `Depth` and `Width` in the debug
|
|||
info.
|
||||
|
||||
|
||||
## Tracking Inlined Modules
|
||||
|
||||
The `dbg.scope` op can be used to track debug information about inlined modules.
|
||||
By default, operations such as `hw.module` in conjunction with `hw.instance`
|
||||
introduce an implicit module scope. All debug operations within a module are
|
||||
added to that implicit scope, unless they have an explicit `scope` operand. This
|
||||
explicit scope operand can be used to group the DI of an inlined module.
|
||||
Consider the following modules:
|
||||
|
||||
```
|
||||
hw.module @Foo(in %a: i42) {
|
||||
dbg.variable "a", %a : i42
|
||||
hw.instance "bar" @Bar(x: %a: i42)
|
||||
}
|
||||
hw.module @Bar(in %x: i42) {
|
||||
dbg.variable "x", %x : i42
|
||||
%0 = comb.mul %x, %x : i42
|
||||
dbg.variable "squared", %0 : i42
|
||||
}
|
||||
```
|
||||
|
||||
If we inline module `Bar`, we can introduce a `dbg.scope` operation to represent
|
||||
the original instance, and group all debug variables in `Bar` under this
|
||||
explicit scope:
|
||||
|
||||
```
|
||||
hw.module @Foo(in %a: i42) {
|
||||
dbg.variable "a", %a : i42
|
||||
%0 = dbg.scope "bar", "Bar"
|
||||
dbg.variable "x", %a scope %0 : i42
|
||||
%1 = comb.mul %a, %a : i42
|
||||
dbg.variable "squared", %1 scope %0 : i42
|
||||
}
|
||||
```
|
||||
|
||||
Despite the fact that the instance op no longer exists, the explicit `dbg.scope`
|
||||
op models the additional levle of hierarchy that used to exist in the input.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@ struct DIModule {
|
|||
SmallVector<DIVariable *, 0> variables;
|
||||
/// If this is an extern declaration.
|
||||
bool isExtern = false;
|
||||
/// If this is an inline scope created by a `dbg.scope` operation.
|
||||
bool isInline = false;
|
||||
};
|
||||
|
||||
struct DIInstance {
|
||||
|
|
|
@ -19,6 +19,37 @@ class DebugOp<string mnemonic, list<Trait> traits = []> :
|
|||
Op<DebugDialect, mnemonic, traits>;
|
||||
|
||||
|
||||
def ScopeOp : DebugOp<"scope"> {
|
||||
let summary = "Define a scope for debug values";
|
||||
let description = [{
|
||||
Creates an additional level of hierarchy in the DI, a "scope", which can be
|
||||
used to group variables and other scopes.
|
||||
|
||||
Operations such as `hw.module` introduce an implicit scope. All debug
|
||||
operations within a module are added to that implicit scope, unless they
|
||||
have an explicit `scope` operand. Providing an explicit scope can be used to
|
||||
represent inlined modules.
|
||||
|
||||
Scopes in DI do not necessarily have to correspond to levels of a module
|
||||
hierarchy. They can also be used to model things like control flow scopes,
|
||||
call stacks, and other source-language concepts.
|
||||
|
||||
The `scope` operand of any debug dialect operation must be defined locally
|
||||
by a `dbg.scope` operation. It cannot be a block argument. (This is intended
|
||||
as a temporary restriction, to be lifted in the future.)
|
||||
}];
|
||||
let arguments = (ins
|
||||
StrAttr:$instanceName,
|
||||
StrAttr:$moduleName,
|
||||
Optional<ScopeType>:$scope
|
||||
);
|
||||
let results = (outs ScopeType:$result);
|
||||
let assemblyFormat = [{
|
||||
$instanceName `,` $moduleName (`scope` $scope^)? attr-dict
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
def VariableOp : DebugOp<"variable"> {
|
||||
let summary = "A named value to be captured in debug info";
|
||||
let description = [{
|
||||
|
@ -34,10 +65,17 @@ def VariableOp : DebugOp<"variable"> {
|
|||
these source language values can be reconstituted from the actual IR values
|
||||
present at the end of compilation.
|
||||
|
||||
See the rationale for examples and details.
|
||||
See the rationale for examples and details. See the `dbg.scope` operation
|
||||
for additional details on how to use the `scope` operand.
|
||||
}];
|
||||
let arguments = (ins
|
||||
StrAttr:$name,
|
||||
AnyType:$value,
|
||||
Optional<ScopeType>:$scope
|
||||
);
|
||||
let assemblyFormat = [{
|
||||
$name `,` $value (`scope` $scope^)? attr-dict `:` type($value)
|
||||
}];
|
||||
let arguments = (ins StrAttr:$name, AnyType:$value);
|
||||
let assemblyFormat = [{ $name `,` $value attr-dict `:` type($value) }];
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -14,6 +14,12 @@ include "mlir/IR/AttrTypeBase.td"
|
|||
|
||||
class DebugTypeDef<string name> : TypeDef<DebugDialect, name> { }
|
||||
|
||||
def ScopeType : DebugTypeDef<"Scope"> {
|
||||
let mnemonic = "scope";
|
||||
let summary = "debug scope";
|
||||
let description = "The result of a `dbg.scope` operation.";
|
||||
}
|
||||
|
||||
def StructType : DebugTypeDef<"Struct"> {
|
||||
let mnemonic = "struct";
|
||||
let summary = "debug struct aggregate";
|
||||
|
|
|
@ -88,14 +88,34 @@ void DebugInfoBuilder::visitModule(hw::HWModuleOp moduleOp, DIModule &module) {
|
|||
// return. Otherwise collect ports, instances, and variables as a
|
||||
// fallback.
|
||||
|
||||
// Check what kind of DI is present in the module.
|
||||
// Check what kind of DI is present in the module. Also create additional
|
||||
// `DIModule` hierarchy levels for each explicit scope op in the module.
|
||||
SmallDenseMap<debug::ScopeOp, DIModule *> scopes;
|
||||
bool hasVariables = false;
|
||||
bool hasInstances = false;
|
||||
moduleOp.walk([&](Operation *op) {
|
||||
if (isa<debug::VariableOp>(op))
|
||||
hasVariables = true;
|
||||
if (auto scopeOp = dyn_cast<debug::ScopeOp>(op)) {
|
||||
auto *node = createModule();
|
||||
node->isInline = true;
|
||||
node->name = scopeOp.getModuleNameAttr();
|
||||
node->op = scopeOp;
|
||||
scopes.insert({scopeOp, node});
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to resolve a `scope` operand on a variable to the
|
||||
// `DIModule` into which the variable should be collected. If the `scope` is
|
||||
// not set, or it isn't a valid `dbg.scope` op, returns the `module` argument
|
||||
// of this function.
|
||||
auto getScope = [&](Value scopeValue) -> DIModule & {
|
||||
if (scopeValue)
|
||||
if (auto scopeOp = scopeValue.getDefiningOp<debug::ScopeOp>())
|
||||
return *scopes.lookup(scopeOp);
|
||||
return module;
|
||||
};
|
||||
|
||||
// If the module has no DI for variables, add variables for each of the ports
|
||||
// as a fallback.
|
||||
if (!hasVariables) {
|
||||
|
@ -119,10 +139,18 @@ void DebugInfoBuilder::visitModule(hw::HWModuleOp moduleOp, DIModule &module) {
|
|||
var->name = varOp.getNameAttr();
|
||||
var->loc = varOp.getLoc();
|
||||
var->value = varOp.getValue();
|
||||
module.variables.push_back(var);
|
||||
getScope(varOp.getScope()).variables.push_back(var);
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto scopeOp = dyn_cast<debug::ScopeOp>(op)) {
|
||||
auto *instance = createInstance();
|
||||
instance->name = scopeOp.getInstanceNameAttr();
|
||||
instance->op = scopeOp;
|
||||
instance->module = scopes.lookup(scopeOp);
|
||||
getScope(scopeOp.getScope()).instances.push_back(instance);
|
||||
}
|
||||
|
||||
// Fallback if the module has no DI for its instances.
|
||||
if (!hasInstances) {
|
||||
if (auto instOp = dyn_cast<hw::InstanceOp>(op)) {
|
||||
|
|
|
@ -57,7 +57,8 @@ void MaterializeDebugInfoPass::materializeVariable(OpBuilder &builder,
|
|||
if (name.getValue().starts_with("_"))
|
||||
return;
|
||||
if (auto dbgValue = convertToDebugAggregates(builder, value))
|
||||
builder.create<debug::VariableOp>(value.getLoc(), name, dbgValue);
|
||||
builder.create<debug::VariableOp>(value.getLoc(), name, dbgValue,
|
||||
/*scope=*/Value{});
|
||||
}
|
||||
|
||||
/// Unpack all aggregates in a FIRRTL value and repack them as debug aggregates.
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
using namespace mlir;
|
||||
using namespace circt;
|
||||
|
||||
static void dump(DIModule &module, raw_indented_ostream &os);
|
||||
|
||||
static void dump(DIVariable &variable, raw_indented_ostream &os) {
|
||||
os << "Variable " << variable.name;
|
||||
if (variable.loc)
|
||||
|
@ -38,6 +40,11 @@ static void dump(DIInstance &instance, raw_indented_ostream &os) {
|
|||
if (instance.op)
|
||||
os << " for " << instance.op->getName() << " at " << instance.op->getLoc();
|
||||
os << "\n";
|
||||
if (instance.module->isInline) {
|
||||
os.indent();
|
||||
dump(*instance.module, os);
|
||||
os.unindent();
|
||||
}
|
||||
}
|
||||
|
||||
static void dump(DIModule &module, raw_indented_ostream &os) {
|
||||
|
|
|
@ -48,3 +48,29 @@ hw.module @Aggregates(in %data_a: i32, in %data_b: index, in %data_c_0: i17, in
|
|||
%1 = dbg.struct {"a": %data_a, "b": %data_b, "c": %0} : i32, index, !dbg.array<i17>
|
||||
dbg.variable "data", %1 : !dbg.struct<i32, index, !dbg.array<i17>>
|
||||
}
|
||||
|
||||
// CHECK-LABEL: Module "InlineScopes" for hw.module
|
||||
// CHECK: Variable "a"
|
||||
// CHECK-NEXT: Arg 0 of hw.module of type i42
|
||||
// CHECK: Instance "inner" of "InnerModule" for dbg.scope
|
||||
// CHECK: Module "InnerModule" for dbg.scope
|
||||
// CHECK: Variable "b"
|
||||
// CHECK-NEXT: Result 0 of comb.mul of type i42
|
||||
hw.module @InlineScopes(in %a: i42) {
|
||||
dbg.variable "a", %a : i42
|
||||
%0 = comb.mul %a, %a : i42
|
||||
%1 = dbg.scope "inner", "InnerModule"
|
||||
dbg.variable "b", %0 scope %1 : i42
|
||||
}
|
||||
|
||||
// CHECK-LABEL: Module "NestedScopes" for hw.module
|
||||
// CHECK: Instance "foo" of "Foo" for dbg.scope
|
||||
// CHECK: Module "Foo" for dbg.scope
|
||||
// CHECK: Instance "bar" of "Bar" for dbg.scope
|
||||
// CHECK: Module "Bar" for dbg.scope
|
||||
// CHECK: Variable "a"
|
||||
hw.module @NestedScopes(in %a: i42) {
|
||||
%0 = dbg.scope "foo", "Foo"
|
||||
%1 = dbg.scope "bar", "Bar" scope %0
|
||||
dbg.variable "a", %a scope %1 : i42
|
||||
}
|
||||
|
|
|
@ -19,6 +19,13 @@ func.func @Foo(%arg0: i32, %arg1: index, %arg2: f64) {
|
|||
%1 = dbg.array [%arg1, %arg1] : index
|
||||
dbg.variable "megabar", %1 : !dbg.array
|
||||
|
||||
// CHECK-NEXT: [[TMP:%.+]] = dbg.scope "inlined", "Bar"
|
||||
// CHECK-NEXT: dbg.variable {{.+}} scope [[TMP]]
|
||||
// CHECK-NEXT: dbg.scope {{.+}} scope [[TMP]]
|
||||
%2 = dbg.scope "inlined", "Bar"
|
||||
dbg.variable "x", %arg0 scope %2 : i32
|
||||
dbg.scope "y", "Baz" scope %2
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue