; RUN: llc < %s -mtriple=i686-windows | FileCheck %s -check-prefix=NORMAL ; RUN: llc < %s -mtriple=i686-windows -no-x86-call-frame-opt | FileCheck %s -check-prefix=NOPUSH ; RUN: llc < %s -mtriple=x86_64-windows | FileCheck %s -check-prefix=X64 ; RUN: llc < %s -mtriple=i686-pc-linux | FileCheck %s -check-prefix=LINUX %class.Class = type { i32 } %struct.s = type { i64 } declare void @good(i32 %a, i32 %b, i32 %c, i32 %d) declare void @inreg(i32 %a, i32 inreg %b, i32 %c, i32 %d) declare x86_thiscallcc void @thiscall(ptr %class, i32 %a, i32 %b, i32 %c, i32 %d) declare void @oneparam(i32 %a) declare void @eightparams(i32 %a, i32 %b, i32 %c, i32 %d, i32 %e, i32 %f, i32 %g, i32 %h) declare void @eightparams16(i16 %a, i16 %b, i16 %c, i16 %d, i16 %e, i16 %f, i16 %g, i16 %h) declare void @eightparams64(i64 %a, i64 %b, i64 %c, i64 %d, i64 %e, i64 %f, i64 %g, i64 %h) declare void @struct(ptr byval(%struct.s) %a, i32 %b, i32 %c, i32 %d) declare void @inalloca(ptr inalloca(<{ %struct.s }>)) declare ptr @llvm.stacksave() declare void @llvm.stackrestore(ptr) ; We should get pushes for x86, even though there is a reserved call frame. ; Make sure we don't touch x86-64, and that turning it off works. ; NORMAL-LABEL: test1: ; NORMAL-NOT: subl {{.*}} %esp ; NORMAL: pushl $4 ; NORMAL-NEXT: pushl $3 ; NORMAL-NEXT: pushl $2 ; NORMAL-NEXT: pushl $1 ; NORMAL-NEXT: call ; NORMAL-NEXT: addl $16, %esp ; X64-LABEL: test1: ; X64: movl $1, %ecx ; X64-NEXT: movl $2, %edx ; X64-NEXT: movl $3, %r8d ; X64-NEXT: movl $4, %r9d ; X64-NEXT: callq good ; NOPUSH-LABEL: test1: ; NOPUSH: subl $16, %esp ; NOPUSH-NEXT: movl $4, 12(%esp) ; NOPUSH-NEXT: movl $3, 8(%esp) ; NOPUSH-NEXT: movl $2, 4(%esp) ; NOPUSH-NEXT: movl $1, (%esp) ; NOPUSH-NEXT: call ; NOPUSH-NEXT: addl $16, %esp define void @test1() { entry: call void @good(i32 1, i32 2, i32 3, i32 4) ret void } ; If we have a reserved frame, we should have pushes ; NORMAL-LABEL: test2: ; NORMAL-NOT: subl {{.*}} %esp ; NORMAL: pushl $4 ; NORMAL-NEXT: pushl $3 ; NORMAL-NEXT: pushl $2 ; NORMAL-NEXT: pushl $1 ; NORMAL-NEXT: call define void @test2(i32 %k) { entry: %a = alloca i32, i32 %k call void @good(i32 1, i32 2, i32 3, i32 4) ret void } ; Again, we expect a sequence of 4 immediate pushes ; Checks that we generate the right pushes for >8bit immediates ; NORMAL-LABEL: test2b: ; NORMAL-NOT: subl {{.*}} %esp ; NORMAL: pushl $4096 ; NORMAL-NEXT: pushl $3072 ; NORMAL-NEXT: pushl $2048 ; NORMAL-NEXT: pushl $1024 ; NORMAL-NEXT: call ; NORMAL-NEXT: addl $16, %esp define void @test2b() optsize { entry: call void @good(i32 1024, i32 2048, i32 3072, i32 4096) ret void } ; The first push should push a register ; NORMAL-LABEL: test3: ; NORMAL-NOT: subl {{.*}} %esp ; NORMAL: pushl $4 ; NORMAL-NEXT: pushl $3 ; NORMAL-NEXT: pushl $2 ; NORMAL-NEXT: pushl %e{{..}} ; NORMAL-NEXT: call ; NORMAL-NEXT: addl $16, %esp define void @test3(i32 %k) optsize { entry: %f = add i32 %k, 1 call void @good(i32 %f, i32 2, i32 3, i32 4) ret void } ; We support weird calling conventions ; NORMAL-LABEL: test4: ; NORMAL: movl $2, %eax ; NORMAL-NEXT: pushl $4 ; NORMAL-NEXT: pushl $3 ; NORMAL-NEXT: pushl $1 ; NORMAL-NEXT: call ; NORMAL-NEXT: addl $12, %esp define void @test4() optsize { entry: call void @inreg(i32 1, i32 inreg 2, i32 3, i32 4) ret void } ; NORMAL-LABEL: test4b: ; NORMAL: movl 4(%esp), %ecx ; NORMAL-NEXT: pushl $4 ; NORMAL-NEXT: pushl $3 ; NORMAL-NEXT: pushl $2 ; NORMAL-NEXT: pushl $1 ; NORMAL-NEXT: call ; NORMAL-NEXT: ret define void @test4b(ptr %f) optsize { entry: call x86_thiscallcc void @thiscall(ptr %f, i32 1, i32 2, i32 3, i32 4) ret void } ; Check that pushing the addresses of globals (Or generally, things that ; aren't exactly immediates) isn't broken. ; Fixes PR21878. ; NORMAL-LABEL: test6: ; NORMAL: pushl $_ext ; NORMAL-NEXT: call declare void @f(ptr) @ext = external dso_local constant i8 define void @test6() { call void @f(ptr @ext) br label %bb bb: alloca i32 ret void } ; Check that we fold simple cases into the push ; NORMAL-LABEL: test7: ; NORMAL-NOT: subl {{.*}} %esp ; NORMAL: movl 4(%esp), [[EAX:%e..]] ; NORMAL-NEXT: pushl $4 ; NORMAL-NEXT: pushl ([[EAX]]) ; NORMAL-NEXT: pushl $2 ; NORMAL-NEXT: pushl $1 ; NORMAL-NEXT: call ; NORMAL-NEXT: addl $16, %esp define void @test7(ptr %ptr) optsize { entry: %val = load i32, ptr %ptr call void @good(i32 1, i32 2, i32 %val, i32 4) ret void } ; Fold stack-relative loads into the push, with correct offset ; In particular, at the second push, %b was at 12(%esp) and ; %a wast at 8(%esp), but the second push bumped %esp, so %a ; is now it at 12(%esp) ; NORMAL-LABEL: test8: ; NORMAL: pushl $4 ; NORMAL-NEXT: pushl 12(%esp) ; NORMAL-NEXT: pushl 12(%esp) ; NORMAL-NEXT: pushl $1 ; NORMAL-NEXT: call ; NORMAL-NEXT: addl $16, %esp define void @test8(i32 %a, i32 %b) optsize { entry: call void @good(i32 1, i32 %a, i32 %b, i32 4) ret void } ; If one function is using push instructions, and the other isn't ; (because it has frame-index references), then we must resolve ; these references correctly. ; NORMAL-LABEL: test9: ; NORMAL-NOT: leal (%esp), ; NORMAL: pushl $4 ; NORMAL-NEXT: pushl $3 ; NORMAL-NEXT: pushl $2 ; NORMAL-NEXT: pushl $1 ; NORMAL-NEXT: call ; NORMAL-NEXT: addl $16, %esp ; NORMAL-NEXT: movl (%esp), [[E1:%e..]] ; NORMAL-NEXT: movl 4(%esp), [[E2:%e..]] ; NORMAL-NEXT: leal 16(%esp), [[E3:%e..]] ; NORMAL-NEXT: leal 12(%esp), [[E4:%e..]] ; NORMAL-NEXT: pushl [[E3]] ; NORMAL-NEXT: pushl [[E4]] ; NORMAL-NEXT: pushl $6 ; NORMAL-NEXT: pushl [[E2]] ; NORMAL-NEXT: pushl [[E1]] ; NORMAL-NEXT: call ; NORMAL-NEXT: addl $20, %esp define void @test9() optsize { entry: %p = alloca i32, align 4 %q = alloca i32, align 4 %s = alloca %struct.s, align 8 call void @good(i32 1, i32 2, i32 3, i32 4) %pv = ptrtoint ptr %p to i32 %qv = ptrtoint ptr %q to i32 call void @struct(ptr byval(%struct.s) %s, i32 6, i32 %qv, i32 %pv) ret void } ; We can end up with an indirect call which gets reloaded on the spot. ; Make sure we reference the correct stack slot - we spill into (%esp) ; and reload from 16(%esp) due to the pushes. ; NORMAL-LABEL: test10: ; NORMAL: movl $_good, [[ALLOC:.*]] ; NORMAL-NEXT: movl [[ALLOC]], [[EAX:%e..]] ; NORMAL-NEXT: movl [[EAX]], (%esp) # 4-byte Spill ; NORMAL: nop ; NORMAL: pushl $4 ; NORMAL-NEXT: pushl $3 ; NORMAL-NEXT: pushl $2 ; NORMAL-NEXT: pushl $1 ; NORMAL-NEXT: calll *16(%esp) ; NORMAL-NEXT: addl $24, %esp define void @test10() optsize { %stack_fptr = alloca ptr store ptr @good, ptr %stack_fptr %good_ptr = load volatile ptr, ptr %stack_fptr call void asm sideeffect "nop", "~{ax},~{bx},~{cx},~{dx},~{bp},~{si},~{di}"() call void (i32, i32, i32, i32) %good_ptr(i32 1, i32 2, i32 3, i32 4) ret void } ; We can't fold the load from the global into the push because of ; interference from the store ; NORMAL-LABEL: test11: ; NORMAL: movl _the_global, [[EAX:%e..]] ; NORMAL-NEXT: movl $42, _the_global ; NORMAL-NEXT: pushl $4 ; NORMAL-NEXT: pushl $3 ; NORMAL-NEXT: pushl $2 ; NORMAL-NEXT: pushl [[EAX]] ; NORMAL-NEXT: call ; NORMAL-NEXT: addl $16, %esp @the_global = external dso_local global i32 define void @test11() optsize { %myload = load i32, ptr @the_global store i32 42, ptr @the_global call void @good(i32 %myload, i32 2, i32 3, i32 4) ret void } ; Converting one mov into a push isn't worth it when ; doing so forces too much overhead for other calls. ; NORMAL-LABEL: test12: ; NORMAL: pushl $8 ; NORMAL-NEXT: pushl $7 ; NORMAL-NEXT: pushl $6 ; NORMAL-NEXT: pushl $5 ; NORMAL-NEXT: calll _good define void @test12() optsize { entry: %s = alloca %struct.s, align 4 call void @struct(ptr byval(%struct.s) %s, i32 2, i32 3, i32 4) call void @good(i32 5, i32 6, i32 7, i32 8) call void @struct(ptr byval(%struct.s) %s, i32 10, i32 11, i32 12) ret void } ; But if the gains outweigh the overhead, we should do it ; NORMAL-LABEL: test12b: ; NORMAL: pushl $4 ; NORMAL-NEXT: pushl $3 ; NORMAL-NEXT: pushl $2 ; NORMAL-NEXT: pushl $1 ; NORMAL-NEXT: calll _good ; NORMAL-NEXT: addl $16, %esp ; NORMAL=NEXT: movl (%esp), %eax ; NORMAL=NEXT: movl 4(%esp), %ecx ; NORMAL=NEXT: pushl $8 ; NORMAL=NEXT: pushl $7 ; NORMAL=NEXT: pushl $6 ; NORMAL=NEXT: pushl %ecx ; NORMAL=NEXT: pushl %eax ; NORMAL=NEXT: calll _struct ; NORMAL=NEXT: addl $20, %esp ; NORMAL=NEXT: pushl $12 ; NORMAL=NEXT: pushl $11 ; NORMAL=NEXT: pushl $10 ; NORMAL=NEXT: pushl $9 ; NORMAL=NEXT: calll _good ; NORMAL=NEXT: addl $16, %esp define void @test12b() optsize { entry: %s = alloca %struct.s, align 4 call void @good(i32 1, i32 2, i32 3, i32 4) call void @struct(ptr byval(%struct.s) %s, i32 6, i32 7, i32 8) call void @good(i32 9, i32 10, i32 11, i32 12) ret void } ; Make sure the add does not prevent folding loads into pushes. ; val1 and val2 will not be folded into pushes since they have ; an additional use, but val3 should be. ; NORMAL-LABEL: test13: ; NORMAL: movl ([[P1:%e..]]), [[V1:%e..]] ; NORMAL-NEXT: movl ([[P2:%e..]]), [[V2:%e..]] ; NORMAL-NEXT: , [[ADD:%e..]] ; NORMAL-NEXT: pushl [[ADD]] ; NORMAL-NEXT: pushl ([[P3:%e..]]) ; NORMAL-NEXT: pushl [[V2]] ; NORMAL-NEXT: pushl [[V1]] ; NORMAL-NEXT: calll _good ; NORMAL: movl [[P3]], %eax define ptr @test13(ptr inreg %ptr1, ptr inreg %ptr2, ptr inreg %ptr3) optsize { entry: %val1 = load i32, ptr %ptr1 %val2 = load i32, ptr %ptr2 %val3 = load i32, ptr %ptr3 %add = add i32 %val1, %val2 call void @good(i32 %val1, i32 %val2, i32 %val3, i32 %add) ret ptr %ptr3 } ; Make sure to fold adjacent stack adjustments. ; LINUX-LABEL: pr27140: ; LINUX: subl $12, %esp ; LINUX: .cfi_def_cfa_offset 16 ; LINUX-NOT: sub ; LINUX: pushl $4 ; LINUX: .cfi_adjust_cfa_offset 4 ; LINUX: pushl $3 ; LINUX: .cfi_adjust_cfa_offset 4 ; LINUX: pushl $2 ; LINUX: .cfi_adjust_cfa_offset 4 ; LINUX: pushl $1 ; LINUX: .cfi_adjust_cfa_offset 4 ; LINUX: calll good ; LINUX: addl $28, %esp ; LINUX: .cfi_adjust_cfa_offset -28 ; LINUX-NOT: add ; LINUX: retl define void @pr27140() optsize { entry: tail call void @good(i32 1, i32 2, i32 3, i32 4) ret void } ; Check that a stack restore (leal -4(%ebp), %esp) doesn't get merged with a ; stack adjustment (addl $12, %esp). Just because it's a lea doesn't mean it's ; simply decreasing the stack pointer. ; NORMAL-LABEL: test14: ; NORMAL: calll _B_func ; NORMAL: leal -4(%ebp), %esp ; NORMAL-NOT: %esp ; NORMAL: retl %struct.A = type { i32, i32 } %struct.B = type { i8 } declare x86_thiscallcc ptr @B_ctor(ptr returned, ptr byval(%struct.A)) declare void @B_func(ptr sret(%struct.B), ptr, i32) define void @test14(ptr %a) { entry: %ref.tmp = alloca %struct.B, align 1 %agg.tmp = alloca i64, align 8 %tmp = alloca %struct.B, align 1 %0 = load i64, ptr %a, align 4 store i64 %0, ptr %agg.tmp, align 4 %call = call x86_thiscallcc ptr @B_ctor(ptr returned %ref.tmp, ptr byval(%struct.A) %agg.tmp) call void @B_func(ptr sret(%struct.B) %tmp, ptr %ref.tmp, i32 1) ret void } ; NORMAL-LABEL: pr34863_16 ; NORMAL: movl 4(%esp), %eax ; NORMAL-NEXT: pushl $65535 ; NORMAL-NEXT: pushl $0 ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: calll _eightparams16 ; NORMAL-NEXT: addl $32, %esp ; ; NOPUSH-LABEL: pr34863_16 ; NOPUSH: subl $32, %esp ; NOPUSH-NEXT: movl 36(%esp), %eax ; NOPUSH-NEXT: movl %eax, 20(%esp) ; NOPUSH-NEXT: movl %eax, 16(%esp) ; NOPUSH-NEXT: movl %eax, 12(%esp) ; NOPUSH-NEXT: movl %eax, 8(%esp) ; NOPUSH-NEXT: movl %eax, 4(%esp) ; NOPUSH-NEXT: movl %eax, (%esp) ; NOPUSH-NEXT: movl $65535, 28(%esp) ; NOPUSH-NEXT: andl $0, 24(%esp) ; NOPUSH-NEXT: calll _eightparams16 ; NOPUSH-NEXT: addl $32, %esp define void @pr34863_16(i16 %x) minsize nounwind { entry: tail call void @eightparams16(i16 %x, i16 %x, i16 %x, i16 %x, i16 %x, i16 %x, i16 0, i16 -1) ret void } ; NORMAL-LABEL: pr34863_32 ; NORMAL: movl 4(%esp), %eax ; NORMAL-NEXT: pushl $-1 ; NORMAL-NEXT: pushl $0 ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: calll _eightparams ; NORMAL-NEXT: addl $32, %esp ; ; NOPUSH-LABEL: pr34863_32 ; NOPUSH: subl $32, %esp ; NOPUSH-NEXT: movl 36(%esp), %eax ; NOPUSH-NEXT: movl %eax, 20(%esp) ; NOPUSH-NEXT: movl %eax, 16(%esp) ; NOPUSH-NEXT: movl %eax, 12(%esp) ; NOPUSH-NEXT: movl %eax, 8(%esp) ; NOPUSH-NEXT: movl %eax, 4(%esp) ; NOPUSH-NEXT: movl %eax, (%esp) ; NOPUSH-NEXT: orl $-1, 28(%esp) ; NOPUSH-NEXT: andl $0, 24(%esp) ; NOPUSH-NEXT: calll _eightparams ; NOPUSH-NEXT: addl $32, %esp define void @pr34863_32(i32 %x) minsize nounwind { entry: tail call void @eightparams(i32 %x, i32 %x, i32 %x, i32 %x, i32 %x, i32 %x, i32 0, i32 -1) ret void } ; NORMAL-LABEL: pr34863_64 ; NORMAL: movl 4(%esp), %eax ; NORMAL-NEXT: movl 8(%esp), %ecx ; NORMAL-NEXT: pushl $-1 ; NORMAL-NEXT: pushl $-1 ; NORMAL-NEXT: pushl $0 ; NORMAL-NEXT: pushl $0 ; NORMAL-NEXT: pushl %ecx ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: pushl %ecx ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: pushl %ecx ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: pushl %ecx ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: pushl %ecx ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: pushl %ecx ; NORMAL-NEXT: pushl %eax ; NORMAL-NEXT: calll _eightparams64 ; NORMAL-NEXT: addl $64, %esp ; ; NOPUSH-LABEL: pr34863_64 ; NOPUSH: subl $64, %esp ; NOPUSH-NEXT: movl 68(%esp), %eax ; NOPUSH-NEXT: movl 72(%esp), %ecx ; NOPUSH-NEXT: movl %ecx, 44(%esp) ; NOPUSH-NEXT: movl %eax, 40(%esp) ; NOPUSH-NEXT: movl %ecx, 36(%esp) ; NOPUSH-NEXT: movl %eax, 32(%esp) ; NOPUSH-NEXT: movl %ecx, 28(%esp) ; NOPUSH-NEXT: movl %eax, 24(%esp) ; NOPUSH-NEXT: movl %ecx, 20(%esp) ; NOPUSH-NEXT: movl %eax, 16(%esp) ; NOPUSH-NEXT: movl %ecx, 12(%esp) ; NOPUSH-NEXT: movl %eax, 8(%esp) ; NOPUSH-NEXT: movl %ecx, 4(%esp) ; NOPUSH-NEXT: movl %eax, (%esp) ; NOPUSH-NEXT: orl $-1, 60(%esp) ; NOPUSH-NEXT: orl $-1, 56(%esp) ; NOPUSH-NEXT: andl $0, 52(%esp) ; NOPUSH-NEXT: andl $0, 48(%esp) ; NOPUSH-NEXT: calll _eightparams64 ; NOPUSH-NEXT: addl $64, %esp define void @pr34863_64(i64 %x) minsize nounwind { entry: tail call void @eightparams64(i64 %x, i64 %x, i64 %x, i64 %x, i64 %x, i64 %x, i64 0, i64 -1) ret void }