Skip to content

stackmaps can't express values spanning more than a single register. #55957

@vext01

Description

@vext01

A value greater than the size of a register may still be allocated to registers: the value may get split up into parts and span multiple registers.

As far as I can see, this can happen for two reasons:

  • As an optimisation (because registers are faster than memory).
  • As an ABI constraint over a call boundary (e.g. Sys V has rules for breaking struct fields into individual arguments, and for returning 128-bit values).

At present, if you pass a live variable to llvm.experimental.stackmap, and that value that gets split across registers, then the stackmap record will report only the first register in the set of "split-across" registers.

To demonstrate:

%s = type {i64, i64}

declare %s @f(%s)

define i64 @main(i32 %argc, ptr %argv) {
  %s2 = call %s @f(%s zeroinitializer)

  call void (i64, i32, ...) @llvm.experimental.stackmap(i64 99, i32 0, %s %s2)

  %f1 = extractvalue %s %s2, 0
  %f2 = extractvalue %s %s2, 1
  %sum = add i64 %f1, %f2

  ret i64 %sum
}

Compile with -O3 here's the code:

            0x08000040      55             push  rbp                                                                 
            0x08000041      4889e5         mov   rbp, rsp                                                                                                                                                                                     
            0x08000044      31ff           xor   edi, edi                                                                                                                                                                                     
            0x08000046      31f6           xor   esi, esi                                                                                                                                                                                     
            0x08000048      e8fb010000     call  reloc.target.f        ;[1]; RELOC 32 f                                                                                                                                                       
            0x0800004d      4801d0         add   rax, rdx                                                                                                                                                                                     
            0x08000050      5d             pop   rbp                                                                                                                                                                                          
            0x08000051      c3             ret

Here we see that %s zeroinitializer has been assigned to rdi:rsi as part of the sys-v calling convention. These are the first two argument registers, so it's as if the struct has been chopped in two and passed as separate args.

We also see that the return value %s2 is in rax:rdx, also due to the calling convention for 128-bit return values.

Yet the stackmap record for this code is:

LLVM StackMap Version: 3
Num Functions: 1
  Function address: 0, stack size: 8, callsite record count: 1
Num Constants: 0
Num Records: 1
  Record ID: 99, instruction offset: 13
    1 locations:
      #1: Register R#0, size: 8 <--------------- %s2
    0 live-outs: [ ]

And R#0 is rax.

This is confusing/wrong, as it looks like the 128-bit value has someone been stored in a 64-bit register, but all that has really happened is that the record fails to mention rdx.

Fixing this would require changing the stackmap format to allow it to express a list of registers, instead of just one.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions