Well, I managed to answer the first part of my question. A variable won't be stack-allocated unless the compiler can prove it is always defined before being used. Mine were, but the control flow was too complex for the compiler to deal with. I added `local finger = Finger{1}(0,0)` in a few places to make life easier for the compiler and my allocations went away.
I'm still interested in the second half of the question though - is there a better way to debug allocation problems other than guesswork? On 22 September 2016 at 14:06, Jamie Brandon <ja...@scattered-thoughts.net> wrote: > Oh, for comparison, this simper function contains no heap allocation at > all, so the compiler is definitely willing to do this under some > circumstances. > > function foo() > finger = Finger{1}(1,1) > for _ in 1:1000 > finger = Finger{1}(finger.hi + finger.lo, finger.lo) > end > finger > end > > On 22 September 2016 at 14:01, Jamie Brandon <ja...@scattered-thoughts.net > > wrote: > >> I have a query compiler which emits Julia code. The code contains lots of >> calls to generated functions, which include sections like this: >> >> hi = gallop(column, column[finger.lo], finger.lo, finger.hi, <=) >> Finger{$(C+1)}(finger.lo, hi) >> >> Finger is an immutable, isbits type: >> >> immutable Finger{C} >> lo::Int64 >> hi::Int64 >> end >> >> When I run the generated code I see many millions of allocations. Using >> code_warntype I can see that all the generated functions have been inlined, >> every variable has a concrete inferred type and there are no generic calls. >> And yet I see many sections in the llvm code like this: >> >> %235 = call %jl_value_t* @jl_gc_pool_alloc(i8* %ptls_i8, i32 1456, i32 >> 32) >> %236 = getelementptr inbounds %jl_value_t, %jl_value_t* %235, i64 -1, >> i32 0 >> store %jl_value_t* inttoptr (i64 139661604385936 to %jl_value_t*), >> %jl_value_t** %236, align 8 >> %237 = bitcast %jl_value_t* %235 to i64* >> store i64 %229, i64* %237, align 8 >> %238 = getelementptr inbounds %jl_value_t, %jl_value_t* %235, i64 1 >> %239 = bitcast %jl_value_t* %238 to i64* >> store i64 %234, i64* %239, align 8 >> store %jl_value_t* %235, %jl_value_t** %finger_2_2, align 8 >> %.pr = load %jl_value_t*, %jl_value_t** %finger_1_2, align 8 >> >> The pointer on the third line is: >> >> unsafe_pointer_to_objref(convert(Ptr{Any}, 139661604385936)) >> # => Data.Finger{1} >> >> So it appears that these fingers are still being heap-allocated. >> >> What could cause this? And more generally, how does one debug issues like >> this? Is there any way to introspect on the decision? >> > >