Skip to content

[ARC] arcilator FlattenModulesPass self-RAUW assertion crash with self-referential port connection #10184

@m2kar

Description

@m2kar

[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, the PrefixingInliner::handleTerminator attempts to replace a value with itself (self-RAUW), triggering the assertion in mlir::IRObjectWithUseList::replaceAllUsesWith.

Minimal Reproducible Example

module top();
  m u(a, a);
  module m(input p, output q);
    assign q = p;
  endmodule
endmodule

Reproduction Steps

circt-verilog --ir-hw minimal_testcase.sv -o t.mlir && arcilator t.mlir --state-file state.json -o arc.ll

Actual Output

 $ 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

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

Tool Result Exit Code Notes
CIRCT arcilator Crash (SIGABRT) 134 Assertion failure: self-RAUW in FlattenModulesPass
Verilator Compilation error 1 "module decls within module decls" - nested modules unsupported
Icarus Verilog Compilation error 2 "Unknown module type: test_module" - nested modules unsupported

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-verilog lowers the SV source to HW MLIR, generating a self-referential instance result for uut(x,x):

%uut.port_b = hw.instance "uut" @test_module(port_a: %uut.port_b: i1) -> (port_b: i1)

When FlattenModulesPass inlines test_module into top, PrefixingInliner::handleTerminator (FlattenModules.cpp:89-94) processes the hw.output %port_a terminator by calling from.replaceAllUsesWith(to) where from is the inlined block argument %port_a and to is %uut.port_b (the instance result that depends on %port_a as its own operand). This creates a cyclic dependency where a value would replace itself, triggering the MLIR assertion.

Standard Compliance Assessment

  • Classification: tool_bug
  • IEEE References:
    • IEEE 1800-2017 Section 23.4 (page 720) - "Nested modules": Explicitly permits module definitions to be nested inside other modules, which is the pattern used in the testcase (test_module defined inside top).
    • IEEE 1800-2017 Section 23.2.2 - "Port connection semantics": The pattern of connecting the same signal to both input and output ports of an instance is valid SystemVerilog; the output port directly driven by the input creates a self-connecting wire.
  • Justification: The testcase uses valid IEEE 1800 SystemVerilog constructs. CIRCT's FlattenModulesPass crashes when processing the self-referential port connection 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.
  • Confidence: High

Related Issues

Suggested Fix Direction

  1. In FlattenModules.cpp PrefixingInliner::handleTerminator: Add a check before from.replaceAllUsesWith(to) to detect when from and to are the same value or when to depends on from (cycle detection). If a cycle is detected, skip the replacement or emit a meaningful error instead of crashing.

  2. 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.

  3. Defensive check: The inliner could check from != to before calling RAUW, or verify whether to is an operand of the instance being inlined, which would indicate a cyclic dependency.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions