Help debugging CIRCT - LLDB doesn't find half the names

Hello there!

I’ve recently been exposed to Circt at my student assistant job and have been playing around with it, seeing what kind of stuff the IR supports. There I have run into a very curios bug regarding Arrays and Parameters. I’ll write a more detailed Issue on Github later, but I wanted to try and debug the issue myself a bit before that.

Short summary of the bug for the curious

If you instantiate a module that outputs a parametrized array the verifier will fail if the left side is parametrized. A short example:

hw.module.extern @submodule<A: i32 = 3, B: i32 = 8>(
) -> (out : !hw.array<#hw.param.decl.ref<"A">x!hw.int<#hw.param.decl.ref<"B">>>)

hw.module @test<A: i32 = 3, B: i32 = 8>(
) -> (out: !hw.array<#hw.param.decl.ref<"A">x!hw.int<#hw.param.decl.ref<"B">>>) {

  %out = hw.instance "submodule" @submodule<
  A: i32 = #hw.param.decl.ref<"A">, 
  B: i32 = #hw.param.decl.ref<"B">>() -> (out : !hw.array<#hw.param.decl.ref<"A">x!hw.int<#hw.param.decl.ref<"B">>>)

  hw.output %out : !hw.array<#hw.param.decl.ref<"A">x!hw.int<#hw.param.decl.ref<"B">>>
}

This causes a very perplexing error where it says two equal things are not equal:

error: 'hw.instance' op result type #0 must be '!hw.array<#hw.param.decl.ref<"A">xint<#hw.param.decl.ref<"B">>>', but got '!hw.array<#hw.param.decl.ref<"A">xint<#hw.param.decl.ref<"B">>>'
  %out = hw.instance "submodule" @submodule<

Parametrizing only the right side of the array works however and produces correct Verilog.

Now I’ve, got a look at the stack trace and inspected the relevant code (instance_like_impl::verifyOutputs and hw::evaluateParametricType in particular) and I still can’t quite figure out where the problem comes from, so I’ve decided to try compiling CIRCT with debug information (-DCMAKE_BUILD_TYPE=DEBUG) and using a debugger.

Now the problem however is, that I’m having issues getting any useful information. While I can evaluate some expressions like expectedType.getTypeID() or expectedType.dump()(which both show equality with resultType btw.), the following kind of stuff fails:

p resultType == expectedType
error: Couldn't lookup symbols:
  mlir::Type::Type(mlir::Type const&)

p resultType.cast<hw::ArrayType>()
error: <user expression 1>:1:12: no member named 'cast' in 'mlir::Type'
    1 | resultType.cast<hw::ArrayType>()
      | ~~~~~~~~~~ ^
error: <user expression 1>:1:30: expected '(' for function-style cast or type construction
    1 | resultType.cast<hw::ArrayType>()
      |                 ~~~~~~~~~~~~~^
error: <user expression 1>:1:32: expected expression
    1 | resultType.cast<hw::ArrayType>()
      |  

p mlir::cast<hw:ArrayType>(expectedType)
error: <user expression 3>:1:7: no member named 'cast' in namespace 'mlir'
    1 | mlir::cast<hw:ArrayType>(expectedType)
      | ~~~~~~^
error: <user expression 3>:1:14: unexpected ':' in nested name specifier; did you mean '::'?
    1 | mlir::cast<hw:ArrayType>(expectedType)
      |              ^
      |              ::
error: <user expression 3>:1:24: expected '(' for function-style cast or type construction
    1 | mlir::cast<hw:ArrayType>(expectedType)
      |            ~~~~~~~~~~~~^      

So no casting to ArrayType to see what Attr is actually stored in size of the array, which is blocking me off from investigating further.

Does anyone have some tips on Debugging CIRCT or an idea what I’m doing wrong here? I’m pretty new to C++ debugging, so maybe I missed a flag somewhere or made another error. I’ve tried it both with the CodeLLDB extension in VSCode, LLDB on the command line and quickly with the gdb debugger from the C/C++ extension and always got similar errors.

launch.json in VSCode
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "(gdb) Launch",
      "type": "cppdbg",
      "request": "launch",
      "program": "/local/llvm-project/build/bin/circt-opt",
      "args": [
        "-pass-pipeline='builtin.module(convert-fsm-to-sv,lower-seq-to-sv,lower-hwarith-to-hw,export-split-verilog{dir-name=/local/projects/circt_cnn/src/hdl/gen})'",
        "/local/projects/circt_cnn/src/mlir/array.mlir"
      ],
      "cwd": "${workspaceFolder}",
      "stopAtEntry": false,
      "environment": [],
      "externalConsole": false,
      "MIMode": "gdb",
      "setupCommands": [
          {
              "description": "Enable pretty-printing for gdb",
              "text": "-enable-pretty-printing",
              "ignoreFailures": true
          },
          {
              "description": "Set Disassembly Flavor to Intel",
              "text": "-gdb-set disassembly-flavor intel",
              "ignoreFailures": true
          }
      ]
    },
    {
      "type": "lldb",
      "request": "launch",
      "name": "Debug LLDB",
      "program": "/local/llvm-project/build/bin/circt-opt",
      "args": [
        "-pass-pipeline='builtin.module(convert-fsm-to-sv,lower-seq-to-sv,lower-hwarith-to-hw,export-split-verilog{dir-name=/local/projects/circt_cnn/src/hdl/gen})'",
        "--mlir-print-ir-after-failure",
        "/local/projects/circt_cnn/src/mlir/example_notworking.mlir"
      ],
      "cwd": "/local/llvm-project",
      "relativePathBase": "/local/llvm-project"
    }
  ]
}
Sequence of commands in lldb console
lldb /local/llvm-project/build/bin/circt-opt

(lldb) target create "/local/llvm-project/build/bin/circt-opt"
Current executable set to '/local/llvm-project/build/bin/circt-opt' (x86_64).

(lldb) breakpoint set --file circt/lib/Dialect/HW/InstanceImplementation.cpp --line 148
Breakpoint 1: 2 locations

(lldb) process launch -- -pass-pipeline='builtin.module(convert-fsm-to-sv,lower-seq-to-sv,lower-hwarith-to-hw,export-split-verilog{dir-name=/local/circt_cnn/src/hdl/gen})' /local/circt_cnn/src/mlir/array.mlir
Process 1554738 launched: '/local/llvm-project/build/bin/circt-opt' (x86_64)
Process 1554738 stopped
* thread #1, name = 'circt-opt', stop reason = breakpoint 1.1
    frame #0: 0x00000000018be4bb circt-opt`circt::hw::instance_like_impl::verifyOutputs(resultNames=ArrayAttr @ 0x00007fffffffafd0, moduleResultNames=ArrayAttr @ 0x00007fffffffafc8, resultTypes=TypeRange @ 0x00007fffffffafb8, moduleResultTypes=ArrayRef<mlir::Type> @ 0x00007fffffffafa8, emitError=0x00007fffffffb248)>)> const&) at InstanceImplementation.cpp:149:7
   146
   147      if (resultType != expectedType) {
   148      // if (resultType.getTypeID() != expectedType.getTypeID()) {
-> 149        emitError([&](auto &diag) {
   150          diag << "result type #" << i << " must be " << expectedType
   151               << ", but got " << resultType;
   152          return true;
   
(lldb) p expectedType == resultType
warning: (x86_64) /local/llvm-project/build/bin/circt-opt 0x0776c478: DW_AT_specification(0x07764bdb) has no decl

warning: (x86_64) /local/llvm-project/build/bin/circt-opt 0x09b70a61: DW_AT_specification(0x09b6da60) has no decl

error: expression failed to parse:
error: Couldn't lookup symbols:
  mlir::Type::Type(mlir::Type const&)

Hm, my actual bug might already be resolved. Haven’t had a chance to recompile yet, takes ages on my machine so I do it sparingly, but last week someone made a commit that changed some of the offending code, even added a test case containing a uarray with a parametrized left side ([HW] Support parametric UnpackedArrayType (#6119) · llvm/circt@3e1105e · GitHub). I’ll test it out later

The problem with the partially missing Debug information still holds though, so if anyone has any advice there I’d appreciate it, in case I find any more issues to debug in the future.

I don’t have any tips, but I too have a very difficult time getting decent debug output from MLIR in general. You might try https://github.com/llvm/llvm-project/tree/main/mlir/utils/lldb-scripts. There are also gdb scripts for general LLVM data structures https://github.com/llvm/llvm-project/blob/main/llvm/utils/gdb-scripts/prettyprinters.py. I’ve not had much luck getting either to work, though I haven’t tried very hard.

Sorry for the long time getting back to you, those lldb-scripts have been quite useful. Thank you for your help!

Sadly, they don’t quite seem to work for all of CIRCT (e.g. expanding dialect or context attributes causes an infinite loop if you don’t set a timeout), so I still had to work around it a bit. Evaluating expressions also still doesn’t work, so I’ve had to write my debug expressions directly into the code and recompile every time (thank god for ccache, otherwise I’d probably have lost my mind). I do wonder why the debugger can’t find stuff like cast and dyn_cast.

However, with those two combined I have managed to debug the problem and will post an issue soon.

Short Summary

Seems the expected ArrayType and result ArrayType are constructed in two different ways with different size attributes, one having the type of the parameter and the other always being a 64 bit integer (even if the parameter is e.g. a 32 bit integer).

Link to Issue

These are template functions, so this doesn’t surprise me in the least. It’s also a problem for inline functions. In the general case, lldb would need a C++ interpreter with the same memory semantics as the original compiler. C++ debugging is difficult both for the user and debugger.