Я пишу компилятор, который переводит байт-код 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 так, как я хочу?