This is an archive of the discontinued LLVM Phabricator instance.

[CodeGen] emit inline asm clobber list warnings for reserved (cont)
ClosedPublic

Authored by stuij on Aug 23 2018, 7:58 AM.

Details

Summary

This is a continuation of https://reviews.llvm.org/D49727
Below the original text, current changes in the comments:

Currently, in line with GCC, when specifying reserved registers like sp or pc on an inline asm() clobber list, we don't always preserve the original value across the statement. And in general, overwriting reserved registers can have surprising results.

For example:

extern int bar(int[]);

int foo(int i) {
  int a[i]; // VLA
  asm volatile(
      "mov r7, #1"
    :
    :
    : "r7"
  );

  return 1 + bar(a);
}

Compiled for thumb, this gives:

$ clang --target=arm-arm-none-eabi -march=armv7a -c test.c -o - -S -O1 -mthumb
...
foo:
        .fnstart
@ %bb.0:                                @ %entry
        .save   {r4, r5, r6, r7, lr}
        push    {r4, r5, r6, r7, lr}
        .setfp  r7, sp, #12
        add     r7, sp, #12
        .pad    #4
        sub     sp, #4
        movs    r1, #7
        add.w   r0, r1, r0, lsl #2
        bic     r0, r0, #7
        sub.w   r0, sp, r0
        mov     sp, r0
        @APP
        mov.w   r7, #1
        @NO_APP
        bl      bar
        adds    r0, #1
        sub.w   r4, r7, #12
        mov     sp, r4
        pop     {r4, r5, r6, r7, pc}
...

r7 is used as the frame pointer for thumb targets, and this function needs to restore the SP from the FP because of the variable-length stack allocation a. r7 is clobbered by the inline assembly (and r7 is included in the clobber list), but LLVM does not preserve the value of the frame pointer across the assembly block.

This type of behavior is similar to GCC's and has been discussed on the bugtracker: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=11807 . No consensus seemed to have been reached on the way forward. Clang behavior has briefly been discussed on the CFE mailing (starting here: http://lists.llvm.org/pipermail/cfe-dev/2018-July/058392.html). I've opted for following Eli Friedman's advice to print warnings when there are reserved registers on the clobber list so as not to diverge from GCC behavior for now.

The patch uses MachineRegisterInfo's target-specific knowledge of reserved registers, just before we convert the inline asm string in the AsmPrinter.

If we find a reserved register, we print a warning:

repro.c:6:7: warning: inline asm clobber list contains reserved registers: R7 [-Winline-asm]
      "mov r7, #1"
      ^

Diff Detail

Repository
rL LLVM

Event Timeline

stuij created this revision.Aug 23 2018, 7:58 AM
stuij added a comment.EditedAug 23 2018, 8:10 AM

Unfortunately the original patch was a bit too general, in that on some architectures certain reserved registers on an asm clobber list will make sure that register isn't overridden. There's no one-to-one mapping between clobber behaviour and reserved registers.

An example of that is the PowerPC ctr register:

volatile int a[50];
int main(int i) {

  while(i--) {
    asm volatile(
        "li    29, 0"
        :
        :
        : "ctr"
                 );

    a[i] = 1;
 }

  return 1; 
}

In this example, the use of the ppc ctr instruction is avoided altogether, but not when it's not on the clobber list.

To avoid these kinds of false-positives, I've added a new isAsmClobberable method to TargetRegisterInfo that architectures can implement if they want to emit these asm clobber warning messages.

This revision is now accepted and ready to land.Aug 23 2018, 12:11 PM

Hmm... actually, looking at this again, I guess we actually have some support for "clobbering" sp, so maybe we shouldn't warn on that. Granted, I don't think it's documented anywhere.

stuij added a comment.Aug 24 2018, 3:28 AM

Could you tell me how this sp clobbering support works?

FunctionLoweringInfo::set detects inline asm which clobbers "sp", and calls setHasOpaqueSPAdjustment. And targets can check it to do something special with frame lowering... I guess the intent is to allow inline asm to allocate memory on the stack. It looks like only the x86 and PPC backends actually do anything with it, though.

stuij added a comment.Aug 28 2018, 3:23 AM

Had a look at the uses for hasOpaqueSPAdjustment. It looks like the the PPC and X86 backends only use it to check for 'how trustworthy is this SP' kind of checks in things like base pointer logic. So we should be fine on this front.

K, then let's not worry about it (particularly since it doesn't do anything on ARM or AArch64 anyway).

This revision was automatically updated to reflect the committed changes.

We're seeing a failure from this on this test case. Looks like somehow the operand after an InlineAsm::Kind_Clobber has become an immediate rather than a register. So the call to getReg fails.

source_filename = "test.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

@test_mem = dso_local global [16 x i8] c"UUUUUUUUUUUUUUUU", align 16

; Function Attrs: nounwind uwtable
define dso_local void @foo() local_unnamed_addr {
entry:
  tail call void asm sideeffect inteldialect "clc\0A\09cmpxchg8b $0\0A\09cmpxchg16b $1\0A\09clc", "=*m,=*m,~{eax},~{edx},~{flags},~{rax},~{rdx},~{dirflag},~{fpsr},~{flags}"([16 x i8]* nonnull @test_mem, [16 x i8]* nonnull @test_mem) #1
  ret void
}

For the failure I posted. It looks like the Live Variable Analysis Pass is removing a register operand for a clobber without removing the immediate operand preceeding that contains the inline assembly flags that indicate it was a clobber. So later we see the clobber flag, but the register operand after it is missing.