It is almost impossible to avoid LLVM storing operands in floating-point operations into the data segment, RODATA, of the executable.
For example, here the compiler converts 3
into Float64
, storing the bytes into the data section, which then requires loading the bytes from memory to run the code:
julia> f(x) = x + 3
f (generic function with 1 method)
julia> @code_native f(1.5)
.file "f"
.section .rodata.cst8,"aM",@progbits,8
.p2align 3, 0x0 # -- Begin function julia_f_2056
.LCPI0_0:
.quad 0x4008000000000000 # double 3
.section .ltext,"axl",@progbits
.globl julia_f_2056
.p2align 4
.type julia_f_2056,@function
julia_f_2056: # @julia_f_2056
; Function Signature: f(Float64)
; ┌ @ REPL[1]:1 within `f`
# %bb.0: # %top
#DEBUG_VALUE: f:x <- $xmm0
push rbp
mov rbp, rsp
movabs rax, offset .LCPI0_0
; │┌ @ promotion.jl:433 within `+` @ float.jl:492
vaddsd xmm0, xmm0, qword ptr [rax]
pop rbp
ret
.Lfunc_end0:
.size julia_f_2056, .Lfunc_end0-julia_f_2056
; └└
# -- End function
.type ".L+Core.Float64#2058",@object # @"+Core.Float64#2058"
.section .lrodata,"al",@progbits
.p2align 3, 0x0
".L+Core.Float64#2058":
.quad ".L+Core.Float64#2058.jit"
.size ".L+Core.Float64#2058", 8
.set ".L+Core.Float64#2058.jit", 140624736139632
.size ".L+Core.Float64#2058.jit", 8
.section ".note.GNU-stack","",@progbits
It might more sense to instead encode the integer value 3
directly into an instruction loading it into a register, and then load/convert from the integer register into the floating-point register. That way there’s no data memory access.
Am I missing something? Is this really such a low-hanging fruit in LLVM? Or perhaps it’s Julia specific?