I am using physical constants in a function in a package. The access to these constants should be fast, best the compiler could replace the values in the lower-level-code.
I can actually achieve this by using global constants in a module. However, I want to allow the user to experiment with those “constants” when called in a setting that I did not envison yet. E.g using a modified gravitational constant when applying functions on the moon instead of the earth.
I came up with a solution using Modules, calleble Singletons and Value Types so that by passing a different singleton, the constant can be drawn from a different module:
module mymod
const g = 9.81
end
module mymod2
const g = 9.81/6
end
struct Constants1 end
(c::Constants1)(::Val{s}) where s = getproperty(mymod, s)
cst1 = Constants1()
f1(::Val{cst}) where cst = 2*cst(Val(:g))
f1(Val(cst1))
@code_llvm f1(Val(cst1))
struct Constants2 end
# this callable queries a different module
(c::Constants2)(::Val{s}) where s = getproperty(mymod2, s)
cst3 = Constants2()
f1(Val(cst3))
@code_llvm f1(Val(cst3))
define double @julia_f1_2475() #0 {
top:
ret double 3.270000e+00
}
I think value types are ok here, because there will be only very few different configurations of constants that need to be passed around, and I want the compiler to use a different thing, i.e. replacement, for different constants.
Passing around module names directly did not work, because I cannot combine them with Value Types.
However, with the solution above, when I put the definitions of the constants into a submodule BigleafConstantsDef
of my package module (package named Bigleaf.jl) then the generated llvm code is not any more compact:
struct ConstantsB end
(c::ConstantsB)(::Val{s}) where s = getproperty(Bigleaf.BigleafConstantsDef, s)
cstB = ConstantsB()
cstB(Val(:g))
f1(Val(cstB))
@code_llvm f1(Val(cstB))
; @ /User/homes/twutz/julia/dev/Bigleaf/inst/learn_constants.jl:16 within `f1`
define nonnull {}* @julia_f1_3340() #0 {
top:
%0 = alloca [2 x {}*], align 8
%gcframe2 = alloca [3 x {}*], align 16
%gcframe2.sub = getelementptr inbounds [3 x {}*], [3 x {}*]* %gcframe2, i64 0, i64 0
%.sub = getelementptr inbounds [2 x {}*], [2 x {}*]* %0, i64 0, i64 0
%1 = bitcast [3 x {}*]* %gcframe2 to i8*
call void @llvm.memset.p0i8.i32(i8* nonnull align 16 dereferenceable(24) %1, i8 0, i32 24, i1 false)
%thread_ptr = call i8* asm "movq %fs:0, $0", "=r"() #3
%ppgcstack_i8 = getelementptr i8, i8* %thread_ptr, i64 -8
%ppgcstack = bitcast i8* %ppgcstack_i8 to {}****
%pgcstack = load {}***, {}**** %ppgcstack, align 8
; ┌ @ /User/homes/twutz/julia/dev/Bigleaf/inst/learn_constants.jl:27 within `ConstantsB`
; │┌ @ Base.jl:35 within `getproperty`
%2 = bitcast [3 x {}*]* %gcframe2 to i64*
store i64 4, i64* %2, align 16
%3 = getelementptr inbounds [3 x {}*], [3 x {}*]* %gcframe2, i64 0, i64 1
%4 = bitcast {}** %3 to {}***
%5 = load {}**, {}*** %pgcstack, align 8
store {}** %5, {}*** %4, align 8
%6 = bitcast {}*** %pgcstack to {}***
store {}** %gcframe2.sub, {}*** %6, align 8
%7 = load atomic {}*, {}** inttoptr (i64 140076403027704 to {}**) unordered, align 8
%8 = getelementptr inbounds [3 x {}*], [3 x {}*]* %gcframe2, i64 0, i64 2
store {}* %7, {}** %8, align 16
; └└
store {}* inttoptr (i64 140078443561120 to {}*), {}** %.sub, align 8
%9 = getelementptr inbounds [2 x {}*], [2 x {}*]* %0, i64 0, i64 1
store {}* %7, {}** %9, align 8
%10 = call nonnull {}* @jl_apply_generic({}* inttoptr (i64 140078264376576 to {}*), {}** nonnull %.sub, i32 2)
%11 = load {}*, {}** %3, align 8
%12 = bitcast {}*** %pgcstack to {}**
store {}* %11, {}** %12, align 8
ret {}* %10
}
Why does this work with modules defined in the same script, but not with modules defined in the package?
Are there better ways of approaching the “configurable” constants problem?