Compile-time constants


#1

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.


#2

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)

#3
  1. Local constant declaration is ignored, they have no effect.
  2. 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)


#4

Thanks!

So the problem was that julia does not understand that UInt64 and Int64 have equivalent behavior with respect to bitwise operations?


#5

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

#6

This behavior seems unexpected, coming from C, but I now feel stupid for making assumptions without testing and reading the docs.

Thank you again.


#7

This is actually about to change on master: https://github.com/JuliaLang/julia/pull/23827.