Skip to content

List scheduler asserts when a later result of a multi-value call feeds a loop phi #178

Description

@nebasuke

The EVM backend asserts in the SelectionDAG list scheduler when a non-first element of a multi-value fastcc call result flows into a loop phi. On solx-llvm 0ed778b:

target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256"
target triple = "evm-unknown-unknown"

declare fastcc { i256, i256 } @g(i256)
declare fastcc void @h(i256)

define fastcc void @f() {
entry:
  br label %loop

loop:
  %p = phi i256 [ %r, %loop ], [ 0, %entry ]
  %s = call fastcc { i256, i256 } @g(i256 %p)
  %r = extractvalue { i256, i256 } %s, 1
  tail call fastcc void @h(i256 %p)
  br label %loop
}
$ llc -O3 repro.ll -o /dev/null
llc: llvm/include/llvm/ADT/ArrayRef.h:253: ... ArrayRef<unsigned short>::operator[](size_t) const: Assertion `Index < Length && "Invalid index!"' failed.
 #9 (anonymous namespace)::RegReductionPQBase::initNodes(...) ScheduleDAGRRList.cpp
#10 (anonymous namespace)::ScheduleDAGRRList::Schedule()
#11 llvm::SelectionDAGISel::CodeGenAndEmitDAG()

It fails for any result arity of two or more when the extracted index is 1 or higher. With extractvalue ..., 0, or at -O0, it compiles.

The Slang frontend produces this shape for internal functions with several return values that are called in a loop. This contract hits it:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Repro {
    mapping(uint256 => uint256) next;
    mapping(uint256 => uint256) weight;

    event Seen(uint256 from, uint256 to);

    function step(uint256 cursor) internal returns (uint256 seen, uint256 following) {
        following = cursor;
        for (uint256 i = 0; i < 8; i++) {
            following = next[following];
            if (following == 0) break;
            weight[following] += i;
            emit Seen(cursor, following);
            seen++;
        }
        for (uint256 i = 0; i < seen; i++) {
            weight[cursor + i] = weight[following] / (i + 1);
            emit Seen(following, weight[cursor + i]);
        }
    }

    function walk(uint256 cursor) internal {
        while (cursor != 0) {
            (, uint256 following) = step(cursor);
            step(cursor);
            cursor = following;
        }
    }

    function w1(uint256 c) external { walk(c); }
    function w2(uint256 c) external { walk(c + 1); }

    function a1(uint256 c) external returns (uint256, uint256) { return step(c); }
    function a2(uint256 c) external returns (uint256, uint256) { return step(c + 1); }
}
$ solx --bin Repro.sol
solx: llvm/include/llvm/ADT/ArrayRef.h:253: ... Assertion `Index < Length && "Invalid index!"' failed.
Error: The worker exited without replying: signal: 6 (SIGABRT) (core dumped)

step has to stay out of line, which is why it has a second loop and extra callers. The old cursor also has to be passed to a call after the one that produces following. The original is ph_distribution in GH_MLM_V3.sol (Polygon, 0x3768bd82b8f0eec396f2e3336bc2efa742eaebad). It walks a list with (,_type, next_no,) = marketplaceFeed(_type, no, 1), calls checkAndDetach(no), then sets no = next_no.

It accounts for 29 failures in the Sourcify sweep (NomicFoundation/solx#685). The solc-frontend 0.1.8 compiles all of them, including the contract above.

Activity

  1. added theissue type on Oct 5, 2026
  2. self-assigned this
    on Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions