LLVM генерирует субоптимальный код при вызове функции без каких-либо побочных эффектов? - PullRequest
0 голосов
/ 30 апреля 2020

Я пишу компилятор, который переводит байт-код AngelScript в LLVM IR. Я искал сгенерированный код для определения возможностей оптимизации и нашел что-то интересное.

Ниже приведен фрагмент кода, сгенерированный моим компилятором, после оптимизации IR (которую вы можете попробовать в Compiler Explorer):


%Vec3f = type { [44 x i8] }

declare noalias i8* @asllvm.private.new_script_object(i8*) local_unnamed_addr #0

define noalias %Vec3f* @"asllvm.module.build.Vec3f CrossProduct(const Vec3f&in v1, const Vec3f&in v2)"(i32* noalias nocapture readonly %params) local_unnamed_addr {
entry:
  %"local@0.castedptr.i" = bitcast i32* %params to i8**
  %"local@0.value.i" = load i8*, i8** %"local@0.castedptr.i", align 8
  %0 = getelementptr i8, i8* %"local@0.value.i", i64 32
  %1 = bitcast i8* %0 to float*
  %2 = load float, float* %1, align 4
  %3 = getelementptr i32, i32* %params, i64 2
  %"local@-2.castedptr.i" = bitcast i32* %3 to i8**
  %"local@-2.value.i" = load i8*, i8** %"local@-2.castedptr.i", align 8
  %fieldptr.i = getelementptr i8, i8* %"local@-2.value.i", i64 36
  %4 = bitcast i8* %fieldptr.i to float*
  %5 = load float, float* %4, align 4
  %6 = fmul fast float %5, %2
  %7 = getelementptr i8, i8* %"local@0.value.i", i64 36
  %8 = bitcast i8* %7 to float*
  %9 = load float, float* %8, align 4
  %fieldptr10.i = getelementptr i8, i8* %"local@-2.value.i", i64 32
  %10 = bitcast i8* %fieldptr10.i to float*
  %11 = load float, float* %10, align 4
  %12 = fmul fast float %11, %9
  %13 = fsub fast float %6, %12
  %14 = getelementptr i8, i8* %"local@0.value.i", i64 40
  %15 = bitcast i8* %14 to float*
  %16 = load float, float* %15, align 4
  %17 = fmul fast float %16, %11
  %fieldptr47.i = getelementptr i8, i8* %"local@-2.value.i", i64 40
  %18 = bitcast i8* %fieldptr47.i to float*
  %19 = load float, float* %18, align 4
  %20 = fmul fast float %19, %2
  %21 = fsub fast float %17, %20
  %22 = fmul fast float %19, %9
  %23 = fmul fast float %16, %5
  %24 = fsub fast float %22, %23
  %25 = tail call i8* @asllvm.private.new_script_object(i8* nonnull inttoptr (i64 94575065177744 to i8*))
  %26 = getelementptr i8, i8* %25, i64 32
  %27 = bitcast i8* %26 to float*
  store float %24, float* %27, align 4
  %28 = getelementptr i8, i8* %25, i64 36
  %29 = bitcast i8* %28 to float*
  store float %21, float* %29, align 4
  %30 = getelementptr i8, i8* %25, i64 40
  %31 = bitcast i8* %30 to float*
  store float %13, float* %31, align 4
  %32 = bitcast i8* %25 to %Vec3f*
  ret %Vec3f* %32
}

attributes #0 = { inaccessiblemem_or_argmemonly }

Интересная часть - сгенерированный код. На clang-10 -O3 -march=znver2 этот IR компилируется до следующего (обратите внимание на мои комментарии):

"asllvm.module.build.Vec3f CrossProduct(const Vec3f&in v1, const Vec3f&in v2)": # @"asllvm.module.build.Vec3f CrossProduct(const Vec3f&in v1, const Vec3f&in v2)"
        sub     rsp, 24
        mov     rax, qword ptr [rdi]     # <----- v1 in rax
        mov     rcx, qword ptr [rdi + 8] # <----- v2 in rcx
        movabs  rdi, 94575065177744      # <----- param for asllvm.private.new_script_object
        # the code below performs some calculations (calculating x, y, z from the cross product of v1 and v2)
        vmovss  xmm0, dword ptr [rcx + 32] # xmm0 = mem[0],zero,zero,zero
        vmovss  xmm3, dword ptr [rax + 36] # xmm3 = mem[0],zero,zero,zero
        vmovss  xmm1, dword ptr [rcx + 36] # xmm1 = mem[0],zero,zero,zero
        vmovss  xmm2, dword ptr [rax + 32] # xmm2 = mem[0],zero,zero,zero
        vmovss  xmm6, dword ptr [rax + 40] # xmm6 = mem[0],zero,zero,zero
        vmovss  xmm5, dword ptr [rcx + 40] # xmm5 = mem[0],zero,zero,zero
        vmulss  xmm4, xmm0, xmm3
        vfmsub231ss     xmm4, xmm1, xmm2 # xmm4 = (xmm1 * xmm2) - xmm4
        vmulss  xmm2, xmm5, xmm2
        vmulss  xmm1, xmm6, xmm1
        vfmsub231ss     xmm2, xmm6, xmm0 # xmm2 = (xmm6 * xmm0) - xmm2
        vfmsub231ss     xmm1, xmm5, xmm3 # xmm1 = (xmm5 * xmm3) - xmm1
        # now, the intermediate results are stocked on the stack
        vmovss  dword ptr [rsp + 20], xmm4 # 4-byte Spill
        vmovss  dword ptr [rsp + 16], xmm2 # 4-byte Spill
        vmovss  dword ptr [rsp + 12], xmm1 # 4-byte Spill
        # the resulting Vec3f is dynamically allocated and constructed by this - there is no parameter other than the magic pointer put in rdi earlier
        call    asllvm.private.new_script_object@PLT
        # the intermediate results are fetched back from the stack, because the xmm registers are callee-saved...
        vmovss  xmm0, dword ptr [rsp + 12] # 4-byte Reload
        vmovss  xmm2, dword ptr [rsp + 16] # 4-byte Reload
        vmovss  xmm1, dword ptr [rsp + 20] # 4-byte Reload
        # ... and are put into the dynamically allocated object, as expected
        vmovss  dword ptr [rax + 32], xmm0
        vmovss  dword ptr [rax + 36], xmm2
        vmovss  dword ptr [rax + 40], xmm1
        add     rsp, 24
        ret

Итак, меня беспокоит то, что сохранение всего регистра xmm было бы ненужным, если бы функция вызывалась в начало функции, потому что регистры xmm будут использоваться только позже.

Я полагаю, что предоставляю достаточно информации, помечая new_script_object с помощью inaccessiblemem_or_argmemonly, чтобы в этом случае он мог перемещаться по вызову.

Можно ли оптимизировать LLVM так, как я хочу?

...