A SystemVerilog module with a nested module definition and a self-referential port connection uut(x, x) causes CIRCT arcilator to crash with an MLIR assertion "cannot RAUW a value with itself". During FlattenModulesPass, when inlining a nested module whose output is connected to its own input via the same instance port, the PrefixingInliner::handleTerminator attempts to replace a value with itself (self-RAUW), triggering the assertion in mlir::IRObjectWithUseList::replaceAllUsesWith.
$ arcilator t.mlir --state-file state.json -o arc.ll
arcilator: circt-1.144.0-src/llvm/llvm/../mlir/include/mlir/IR/UseDefLists.h:213: void mlir::IRObjectWithUseList<mlir::OpOperand>::replaceAllUsesWith(ValueT &&) [OperandType = mlir::OpOperand, ValueT = mlir::Value &]: Assertion `(!newValue || this != OperandType::getUseList(newValue)) && "cannot RAUW a value with itself"' failed.
PLEASE submit a bug report to https://github.com/llvm/circt and include the crash backtrace.
Stack dump:
0. Program arguments: arcilator t.mlir --state-file state.json -o arc.ll
#0 0x00005c9c41058ce8 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/edazz/target/circt-1.144.0-bin/bin/arcilator+0x1839ce8)
#1 0x00005c9c41055bb1 llvm::sys::RunSignalHandlers() (/edazz/target/circt-1.144.0-bin/bin/arcilator+0x1836bb1)
#2 0x00005c9c41059b01 SignalHandler(int, siginfo_t*, void*) Signals.cpp:0:0
#3 0x00007528107c0330 (/lib/x86_64-linux-gnu/libc.so.6+0x45330)
#4 0x0000752810819b2c pthread_kill (/lib/x86_64-linux-gnu/libc.so.6+0x9eb2c)
#5 0x00007528107c027e raise (/lib/x86_64-linux-gnu/libc.so.6+0x4527e)
#6 0x00007528107a38ff abort (/lib/x86_64-linux-gnu/libc.so.6+0x288ff)
#7 0x00007528107a381b (/lib/x86_64-linux-gnu/libc.so.6+0x2881b)
#8 0x00007528107b6517 (/lib/x86_64-linux-gnu/libc.so.6+0x3b517)
#9 0x00005c9c415d60b6 (anonymous namespace)::PrefixingInliner::handleTerminator(mlir::Operation*, mlir::ValueRange) const FlattenModules.cpp:0:0
#10 0x00005c9c43b597de inlineRegionImpl(mlir::InlinerInterface&, llvm::function_ref<void (mlir::OpBuilder&, mlir::Region*, mlir::Block*, mlir::Block*, mlir::IRMapping&, bool)>, mlir::Region*, mlir::Block*, llvm::ilist_iterator<llvm::ilist_detail::node_options<mlir::Operation, true, false, void, false, void>, false, false>, mlir::IRMapping&, mlir::ValueRange, mlir::TypeRange, std::optional<mlir::Location>, bool, mlir::CallOpInterface) InliningUtils.cpp:0:0
#11 0x00005c9c43b59d9a mlir::inlineRegion(mlir::InlinerInterface&, llvm::function_ref<void (mlir::OpBuilder&, mlir::Region*, mlir::Block*, mlir::Block*, mlir::IRMapping&, bool)>, mlir::Region*, mlir::Block*, llvm::ilist_iterator<llvm::ilist_detail::node_options<mlir::Operation, true, false, void, false, void>, false, false>, mlir::ValueRange, mlir::ValueRange, std::optional<mlir::Location>, bool) (/edazz/target/circt-1.144.0-bin/bin/arcilator+0x433ad9a)
#12 0x00005c9c43b59b06 mlir::inlineRegion(mlir::InlinerInterface&, llvm::function_ref<void (mlir::OpBuilder&, mlir::Region*, mlir::Block*, mlir::Block*, mlir::IRMapping&, bool)>, mlir::Region*, mlir::Operation*, mlir::ValueRange, mlir::ValueRange, std::optional<mlir::Location>, bool) (/edazz/target/circt-1.144.0-bin/bin/arcilator+0x433ab06)
#13 0x00005c9c415d1acf (anonymous namespace)::FlattenModulesPass::runOnOperation() FlattenModules.cpp:0:0
#14 0x00005c9c43bd80f2 mlir::detail::OpToOpPassAdaptor::run(mlir::Pass*, mlir::Operation*, mlir::AnalysisManager, bool, unsigned int) (/edazz/target/circt-1.144.0-bin/bin/arcilator+0x43b90f2)
#15 0x00005c9c43bd8d66 mlir::detail::OpToOpPassAdaptor::runPipeline(mlir::OpPassManager&, mlir::Operation*, mlir::AnalysisManager, bool, unsigned int, mlir::PassInstrumentor*, mlir::PassInstrumentation::PipelineParentInfo const*) (/edazz/target/circt-1.144.0-bin/bin/arcilator+0x43b9d66)
#16 0x00005c9c43be0102 mlir::PassManager::runPasses(mlir::Operation*, mlir::AnalysisManager) (/edazz/target/circt-1.144.0-bin/bin/arcilator+0x43c1102)
#17 0x00005c9c43bdf74d mlir::PassManager::run(mlir::Operation*) (/edazz/target/circt-1.144.0-bin/bin/arcilator+0x43c074d)
#18 0x00005c9c40fc328d processBuffer(mlir::MLIRContext&, mlir::TimingScope&, llvm::SourceMgr&, std::optional<std::unique_ptr<llvm::ToolOutputFile, std::default_delete<llvm::ToolOutputFile>>>&) arcilator.cpp:0:0
#19 0x00005c9c40fc240f processInputSplit(mlir::MLIRContext&, mlir::TimingScope&, std::unique_ptr<llvm::MemoryBuffer, std::default_delete<llvm::MemoryBuffer>>, std::optional<std::unique_ptr<llvm::ToolOutputFile, std::default_delete<llvm::ToolOutputFile>>>&) arcilator.cpp:0:0
#20 0x00005c9c40fbf80a executeArcilator(mlir::MLIRContext&) arcilator.cpp:0:0
#21 0x00005c9c40fbeeac main (/edazz/target/circt-1.144.0-bin/bin/arcilator+0x179feac)
#22 0x00007528107a51ca (/lib/x86_64-linux-gnu/libc.so.6+0x2a1ca)
#23 0x00007528107a528b __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28b)
#24 0x00005c9c40fbba25 _start (/edazz/target/circt-1.144.0-bin/bin/arcilator+0x179ca25)
[1] 707763 IOT instruction (core dumped) arcilator t.mlir --state-file state.json -o arc.ll
arcilator should either compile successfully or emit a meaningful error message about the self-referential port connection, but it must not crash with an assertion violation.
CIRCT is the only tool that crashes; Verilator and Iverilog report expected errors about unsupported nested module definitions.
%uut.port_b = hw.instance "uut" @test_module(port_a: %uut.port_b: i1) -> (port_b: i1)
[ARC] CIRCT arcilator FlattenModulesPass self-RAUW assertion crash with self-referential port connection
Tool: CIRCT arcilator (circt-verilog + arcilator pipeline)
Severity: Crash
Description
A SystemVerilog module with a nested module definition and a self-referential port connection
uut(x, x)causes CIRCT arcilator to crash with an MLIR assertion "cannot RAUW a value with itself". During FlattenModulesPass, when inlining a nested module whose output is connected to its own input via the same instance port, thePrefixingInliner::handleTerminatorattempts to replace a value with itself (self-RAUW), triggering the assertion inmlir::IRObjectWithUseList::replaceAllUsesWith.Minimal Reproducible Example
Reproduction Steps
circt-verilog --ir-hw minimal_testcase.sv -o t.mlir && arcilator t.mlir --state-file state.json -o arc.llActual Output
Exit code: 134 (SIGABRT)
Expected Output
arcilator should either compile successfully or emit a meaningful error message about the self-referential port connection, but it must not crash with an assertion violation.
Cross-Tool Comparison
CIRCT is the only tool that crashes; Verilator and Iverilog report expected errors about unsupported nested module definitions.
Root Cause (Preliminary)
The crash chain begins when
circt-veriloglowers the SV source to HW MLIR, generating a self-referential instance result foruut(x,x):When
FlattenModulesPassinlinestest_moduleintotop,PrefixingInliner::handleTerminator(FlattenModules.cpp:89-94) processes thehw.output %port_aterminator by callingfrom.replaceAllUsesWith(to)wherefromis the inlined block argument%port_aandtois%uut.port_b(the instance result that depends on%port_aas its own operand). This creates a cyclic dependency where a value would replace itself, triggering the MLIR assertion.Standard Compliance Assessment
tool_bugtest_moduledefined insidetop).uut(x,x)during module inlining. The MLIR RAUW assertion fires because the pass generates a cyclic value mapping. A robust compiler must handle valid input without crashing; emitting a proper error message is the correct response to unusual-but-legal constructs.Related Issues
x.a <= x.a. Fixed in LowerToHW (FIRRTL dialect) in April 2023. The current bug has an identical crash pattern but occurs inFlattenModulesPass::PrefixingInliner(HW dialect), suggesting the prior fix did not cover this code path.sv.wirecanonicalizer on a self-connecting wirerwDataOut <= rwDataOut. Fixed May 2024.Suggested Fix Direction
In
FlattenModules.cppPrefixingInliner::handleTerminator: Add a check beforefrom.replaceAllUsesWith(to)to detect whenfromandtoare the same value or whentodepends onfrom(cycle detection). If a cycle is detected, skip the replacement or emit a meaningful error instead of crashing.Upstream fix in
circt-verilog: Detect self-referential instance connections during SV-to-HW lowering (where an instance's operand is derived from the same instance's result) and emit a proper error rather than generating MLIR that causes a downstream assertion failure.Defensive check: The inliner could check
from != tobefore calling RAUW, or verify whethertois an operand of the instance being inlined, which would indicate a cyclic dependency.