How does one get actual constants in julia? “actual constant” meaning behavior like #define. As far as I understood, “const” means “constant type”, not “constant value”, i.e. you can dereference or modify constants. This forbids llvm from doing constant elimination. Example code:
let
const FLAGA::UInt64 = 1<<32
const FLAGB::UInt64 = 1<<33
global foo
global bar
function foo(x::Int)
return (0== (x & (FLAGA & FLAGB)))
end
function bar(x::Int)
return (0== (x & ((1<<32) & (1<<33) ) ) )
end
end
println(“Foo:”)
@code_native foo(13)
println(“\n\nBar:”)
@code_native bar(13)
gives:
Foo:
.text
Filename: In[1]
Source line: 286
testq %rdi, %rdi
js L8
Source line: 8
movb $1, %al
retq
L8:
pushq %rbp
movq %rsp, %rbp
Source line: 286
movabsq $jl_throw, %rax
movabsq $140358617188296, %rdi # imm = 0x7FA7C984C7C8
callq *%rax
nopw %cs:(%rax,%rax)
Bar:
.text
Filename: In[1]
pushq %rbp
movq %rsp, %rbp
Source line: 11
movb $1, %al
popq %rbp
retq
nopl (%rax,%rax)
Obviously, the behavior of “bar” is desired in most cases. How do I get it? Of course I could use a macro, but this has the disadvantage of extremely different syntax. In other words, changing between a macro-constant and a “const-type” non-constant requires quite some refactoring of the source code.
No, constant means constant binding e.g. if the rhs is immutable the value can be inlined:
julia> const FLAGB = 1<<33
8589934592
julia> const FLAGA = 1<<32
4294967296
julia> function foo(x::Int)
return (0== (x & (FLAGA & FLAGB)))
end
foo (generic function with 1 method)
julia> @code_native foo(13)
.text
Filename: REPL[3]
pushq %rbp
movq %rsp, %rbp
Source line: 2
movb $1, %al
popq %rbp
retq
nopl (%rax,%rax)
- Local constant declaration is ignored, they have no effect.
- It is inlined, the extra code you get are simply due to different type.
julia> let
FLAGA = 1<<32
FLAGB = 1<<33
global foo
function foo(x::Int)
return (0== (x & (FLAGA & FLAGB)))
end
end
foo (generic function with 1 method)
julia> @code_native foo(1)
.text
; Function <invalid> {
; Location: REPL[1]
pushq %rbp
movq %rsp, %rbp
;}
; Function foo {
; Location: REPL[1]:7
movb $1, %al
popq %rbp
retq
nopl (%rax,%rax)
;}
julia> @code_warntype foo(1)
Variables:
#self# <optimized out>
x::Int64
Body:
begin
return (0 === (Base.and_int)(x::Int64, (Base.and_int)($(QuoteNode(4294967296)), $(QuoteNode(8589934592)))::Int64)::Int64)::Bool
end::Bool
Also note that how much easier it is to see if the constant is inlined if you use the right tool (i.e. code_warntype
)
1 Like
Thanks!
So the problem was that julia does not understand that UInt64 and Int64 have equivalent behavior with respect to bitwise operations?
They don’t.
julia> -1 & UInt(1)
ERROR: InexactError: check_top_bit(Int64, -1)
Stacktrace:
[1] throw_inexacterror(::Symbol, ::Type{Int64}, ::Int64) at ./int.jl:34
[2] check_top_bit at ./int.jl:428 [inlined]
[3] convert at ./int.jl:487 [inlined]
[4] _promote at ./promotion.jl:203 [inlined]
[5] promote at ./promotion.jl:247 [inlined]
[6] &(::Int64, ::UInt64) at ./promotion.jl:313
julia> -1 & Int(1)
1
This behavior seems unexpected, coming from C, but I now feel stupid for making assumptions without testing and reading the docs.
Thank you again.
This is actually about to change on master: https://github.com/JuliaLang/julia/pull/23827.