Skip to content

Exponential inlining when inlining through mutually recursive functions in the presence of devirtualization #186926

@aeubanks

Description

@aeubanks

$ cat /tmp/a.ll

declare void @asdf()

@a = constant [2 x ptr] [ptr @g, ptr @h]

define void @f() {
        %a = alloca [2 x ptr]
        store [2 x ptr] [ptr @g, ptr @h], ptr %a
        call void @g(ptr %a, i64 0)
        ret void
}

define void @g(ptr %p, i64 %a) minsize {
        %b = add i64 %a, 1
        %c = getelementptr ptr, ptr %p, i64 %b
        %f = load ptr, ptr %c
        call void %f(ptr %p, i64 %b)
        call void @asdf()
        ret void
}

define void @h(ptr %p, i64 %a) minsize {
        %b = add i64 %a, -1
        %c = getelementptr ptr, ptr %p, i64 %b
        %f = load ptr, ptr %c
        call void %f(ptr %p, i64 %b)
        call void @asdf()
        ret void
}

$ opt -O3 -S /tmp/a.ll -max-devirt-iterations=20

...
define void @f() local_unnamed_addr {        
  %a = alloca [2 x ptr], align 8
  store ptr @g, ptr %a, align 8    
  %.fca.1.gep = getelementptr inbounds nuw i8, ptr %a, i64 8
  store ptr @h, ptr %.fca.1.gep, align 8
  call void @h(ptr nonnull %a, i64 1)
  call void @asdf()
  call void @asdf()      
  call void @asdf()                                     
  call void @asdf()  
  call void @asdf()                                     
  call void @asdf()                                     
  call void @asdf()                                     
  call void @asdf()     
  call void @asdf()
  call void @asdf()
  call void @asdf()
  call void @asdf()        
  call void @asdf()                                     
  call void @asdf()
  call void @asdf()
  call void @asdf()
  call void @asdf()
  call void @asdf()
  call void @asdf()
  call void @asdf()
  call void @asdf()
  ret void
}
...

the example here doesn't blow up exponentially but with more layers it can, as we've seen internally

g and h are mutually recursive given the arguments passed from f. normally the inline history would detect this, but the history is only kept within a run of the inliner pass. after inlining once, the indirect call can't be inlined, and we move on in the pipeline. another pass devirtualizes the call, and the devirtualization wrapper reruns the inliner + simplification pipeline. there's no inline history and we keep repeating the devirtualization + inlining until we hit -max-devirt-iterations.

we have things we keep track of within the context of an entire run of CGSCCPassManager, e.g.

. we could keep track of inline history there, although I'm unsure of the proper way to do so, given that the inline history needs to be per-CallBase, and those CallBases can be modified/removed/cloned in the simplification pipeline. perhaps the most principled way is to mark the inline history in the IR itself, as we already do in the presence of debug information. for this bug we probably only need to do this for inlined indirect calls, but there may be some edge case with direct calls that would also benefit from this. I'm unsure of the overhead of doing so, and the type of debug info we'd want to emit in the case of no debug info

Metadata

Metadata

Assignees

No one assigned

    Labels

    ipoInterprocedural optimizations

    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