julialabs / cassette.jl Goto Github PK
View Code? Open in Web Editor NEWOverdub Your Julia Code
License: Other
Overdub Your Julia Code
License: Other
relu(x) = max(zero(x), x)
a(W, x, b) = relu.(x)
b(W, x, b) = W*x .+ b
c(W, x, b) = relu.(W*x .+ b)
julia> (@code_typed Cassette.overdub(NoOp(), a, rand(Float64, 10, 10), rand(Float32, 10), rand(Float32, 10)))[2]
Array{Float32,1}
julia> (@code_typed Cassette.overdub(NoOp(), b, rand(Float64, 10, 10), rand(Float32, 10), rand(Float32, 10)))[2]
Array{Float64,1}
julia> (@code_typed Cassette.overdub(NoOp(), c, rand(Float64, 10, 10), rand(Float32, 10), rand(Float32, 10)))[2]
Any
vs
julia> (@code_typed a(rand(Float64, 10, 10), rand(Float32, 10), rand(Float32, 10)))[2]
Array{Float32,1}
julia> (@code_typed b(rand(Float64, 10, 10), rand(Float32, 10), rand(Float32, 10)))[2]
Array{Float64,1}
julia> (@code_typed c(rand(Float64, 10, 10), rand(Float32, 10), rand(Float32, 10)))[2]
Array{Float64,1}
I seem to recall you stating somewhere that you want more people "using Cassette and posting bugs", so here i go! :P
I've created and pushed up Cassertte.jl, and the more general Toggles.jl I used to create it. Thanks for your help with it!
It works, but I'm surprised and sad to see all this native code bloat! Am i doing something wrong there, or is this a legitimate issue with Cassette that still remains?
:D So much fun!
julia> @code_native @withCassertte 5+5
.section __TEXT,__text,regular,pure_instructions
; Function @withCassertte {
; Location: Cassertte.jl:35
pushl %ebp
decl %eax
movl %esp, %ebp
incl %ecx
pushl %edi
incl %ecx
pushl %esi
incl %ecx
pushl %ebp
incl %ecx
pushl %esp
pushl %ebx
decl %eax
andl $-32, %esp
decl %eax
subl $160, %esp
decl %eax
movl %esi, %ebx
vxorps %xmm0, %xmm0, %xmm0
vmovaps %ymm0, 64(%esp)
decl %eax
movl $0, 96(%esp)
decl %eax
movl %ebx, 128(%esp)
decl %eax
movl $61493632, %eax ## imm = 0x3AA5180
addl %eax, (%eax)
addb %al, (%eax)
vzeroupper
calll *%eax
decl %ecx
movl %eax, %esi
decl %eax
movl $6, 64(%esp)
decl %ecx
movl (%esi), %eax
decl %eax
movl %eax, 72(%esp)
decl %eax
leal 64(%esp), %eax
decl %ecx
movl %eax, (%esi)
decl %esp
movl 16(%ebx), %esp
decl %eax
movl $61271328, %ebx ## imm = 0x3A6ED20
addl %eax, (%eax)
addb %al, (%eax)
decl %eax
movl $139887888, %edi ## imm = 0x8568510
addl %eax, (%eax)
addb %al, (%eax)
calll *%ebx
decl %ecx
movl %eax, %edi
decl %esp
movl %edi, 96(%esp)
decl %eax
movl $139887952, %edi ## imm = 0x8568550
addl %eax, (%eax)
addb %al, (%eax)
calll *%ebx
decl %eax
movl %eax, %ebx
decl %eax
movl %ebx, 88(%esp)
; Function esc; {
; Location: essentials.jl:462
; Function Type; {
; Location: boot.jl:221
decl %eax
movl $187084000, %eax ## imm = 0xB26ACE0
addl %eax, (%eax)
addb %al, (%eax)
decl %eax
movl %eax, 24(%esp)
decl %esp
movl %esp, 32(%esp)
decl %ecx
movl $61292176, %ebp ## imm = 0x3A73E90
addl %eax, (%eax)
addb %al, (%eax)
decl %esp
leal 24(%esp), %esp
xorl %edi, %edi
movl $2, %edx
decl %esp
movl %esp, %esi
incl %ecx
calll *%ebp
decl %eax
movl %eax, 80(%esp)
;}}
decl %eax
movl $187083952, %ecx ## imm = 0xB26ACB0
addl %eax, (%eax)
addb %al, (%eax)
decl %eax
movl %ecx, 24(%esp)
decl %esp
movl %edi, 32(%esp)
decl %eax
movl $139888080, %ecx ## imm = 0x85685D0
addl %eax, (%eax)
addb %al, (%eax)
decl %eax
movl %ecx, 40(%esp)
decl %eax
movl %ebx, 48(%esp)
decl %eax
movl %eax, 56(%esp)
xorl %edi, %edi
movl $5, %edx
decl %esp
movl %esp, %esi
incl %ecx
calll *%ebp
decl %eax
movl %eax, 80(%esp)
decl %eax
movl $187097240, %ecx ## imm = 0xB26E098
addl %eax, (%eax)
addb %al, (%eax)
decl %eax
movl %ecx, 24(%esp)
decl %eax
movl $139888144, %ecx ## imm = 0x8568610
addl %eax, (%eax)
addb %al, (%eax)
decl %eax
movl %ecx, 32(%esp)
decl %eax
movl %eax, 40(%esp)
xorl %edi, %edi
movl $3, %edx
decl %esp
movl %esp, %esi
incl %ecx
calll *%ebp
decl %eax
movl 72(%esp), %ecx
decl %ecx
movl %ecx, (%esi)
decl %eax
leal -40(%ebp), %esp
popl %ebx
incl %ecx
popl %esp
incl %ecx
popl %ebp
incl %ecx
popl %esi
incl %ecx
popl %edi
popl %ebp
retl
;}
julia> @code_native 5+5
.section __TEXT,__text,regular,pure_instructions
; Function + {
; Location: int.jl:53
decl %eax
leal (%edi,%esi), %eax
retl
;}
; Function <invalid> {
; Location: int.jl:53
nopw %cs:(%eax,%eax)
;}
...because if you're gonna break the compiler you should at least know how to break it properly
For lack of better title (I haven't looked into the issue, only reduced it).
Repro:
foo(ptr::Ptr{T}, i) where {T} = Base.unsafe_load(ptr, i)::T
# removing ::T or i arg fixes this one
code_warntype(foo, Tuple{Ptr{Float32}, Int})
using Cassette
Cassette.@context Ctx
const ctx = Ctx(foo)
code_warntype(Cassette.Overdub(Cassette.Execute(), foo, Cassette.Settings(ctx)),
Tuple{Ptr{Float32}, Int})
Original IR:
Variables:
ptr::Ptr{Float32}
i::Int64
Body:
begin
return (Base.pointerref)(ptr::Ptr{Float32}, i::Int64, 1)::Float32
end::Float32
Overdubbed IR:
Variables:
o<optimized out>
x1::Ptr{Float32}
x2::Int64
#temp#@_6::Int64
#temp#@_10::Any
Body:
begin
#temp#@_6::Int64 = (typeassert)(x2::Int64, Int64)::Int64
goto 39
39:
goto 43
43:
#temp#@_10::Any = (Base.pointerref)(x1::Ptr{Float32}, #temp#@_6::Int64, 1)::Any
goto 66
66:
goto 70
70:
return ($(QuoteNode(Cassette.Overdub{Cassette.Intercept,typeof(typeassert),Cassette.Settings{Ctx{0xb88cf540947f2ecf},Void,0x0000000000005b28,false}}(Cassette.Intercept(), typeassert, Cassette.Settings{Ctx{0xb88cf540947f2ecf},Void,0x0000000000005b28,false}(Ctx{13298273457436307151}(), nothing, Cassette.World{0x0000000000005b28}(), Val{false}())))))(#temp#@_10::Any, Float32)::Any
end::Any
Cassette's Wrapper
type employs anonymous types to provide metadata storage that "shadows" the field structure of underlying types, and specially intercepts getfield
/setfield!
in order to implement structural metadata propagation semantics.
Once JuliaLang/julia#24960 lands, we might be able to clean up this implementation quite a bit.
Furthermore, there are a few missing features that didn't make it into the first Wrapper
prototype that definitely need to be included/tested in the next design iteration:
Any
)Array
wrapping (similar to struct wrapping but with dynamic integer indices rather than statically named fields)I am probably to greedy and might not quite understand how withtagfor
is supposed to work, but I was hoping that the recursive overdub works even with a tagged context.
using Test, Cassette
using Cassette: @context, @pass, @overdub, overdub, hasmetadata, metadata, hasmetameta,
metameta, untag, tag, withtagfor, untagtype, istagged, istaggedtype,
Tagged, fallback, canoverdub, similarcontext
const Typ = Core.Typeof
@context TraceCtx
function Cassette.execute(ctx::TraceCtx, args...)
subtrace = Any[]
push!(ctx.metadata, args => subtrace)
if canoverdub(ctx, args...)
newctx = similarcontext(ctx, metadata = subtrace)
return overdub(newctx, args...)
else
return fallback(ctx, args...)
end
end
trace = Any[]
x, y, z = rand(3)
trtest(x, y, z) = x*y + y*z
@test @overdub(TraceCtx(metadata = trace), trtest(x, y, z)) == trtest(x, y, z)
@test trace == Any[
(trtest,x,y,z) => Any[
(*,x,y) => Any[(Base.mul_float,x,y)=>Any[]]
(*,y,z) => Any[(Base.mul_float,y,z)=>Any[]]
(+,x*y,y*z) => Any[(Base.add_float,x*y,y*z)=>Any[]]
]
]
trace = Any[]
@test @overdub(Cassette.withtagfor(TraceCtx(metadata = trace), trtest), trtest(x, y, z)) == trtest(x, y, z)
@test trace == Any[
(trtest,x,y,z) => Any[
(*,x,y) => Any[(Base.mul_float,x,y)=>Any[]]
(*,y,z) => Any[(Base.mul_float,y,z)=>Any[]]
(+,x*y,y*z) => Any[(Base.add_float,x*y,y*z)=>Any[]]
]
]
Sidenote: I got tripped up by the difference between @overdub
and overdub
. The former means "enter" context and the latter means "execute" in context.
julia> Cassette.@context NoOp
julia> f(a, b) = a .+ b
julia> (@code_typed f(rand(Float32, 10), rand(Float32, 10)))[2]
Array{Float32,1}
julia> (@code_typed Cassette.overdub(NoOp(), f, rand(Float32, 10), rand(Float32, 10)))[2]
Any
Related to #65, say I have a SPMD compiler, with code like
function foo(x)
y = x + x
sync()
return y
end
In order to run this I have a Cassette pass to vectorise it, and then call it within some kind of scheduler. The function does not actually run to completion, but instead gets to sync()
and returns a continuation; a closure which holds the value y
and executes return y
when called. (The continuation could of course return another continuation instead, and so on.)
This kind of issue comes up for many more invasive control flow transforms (happy to give other examples if this one is confusing). It's easy enough to implement something like this in a low-level way using the usual generated function tricks, but the question is, how do we achieve it via Cassette's interface?
I've talked to a lot of nice folks recently about the potential Cassette use cases. Here's are the ones I remember:
julia> using Cassette
julia> Cassette.@context NoOp
julia> @code_typed Cassette.overdub(NoOp(), *, rand(Float32, 10, 10), rand(Float32, 10))
CodeInfo(
19 1 ─ getfield(%%args, 1) │
│ getfield(%%args, 2) │
│ getfield(%%args, 3) │
20 │ getfield(%%args, 1) │
│ getfield(%%args, 2) │
│ getfield(%%args, 3) │
└── goto 3 if not false │
2 ─ nothing │
23 3 ┄ %9 = getfield(%%args, 1)::Core.Compiler.Const(*, false) │
│ %10 = getfield(%%args, 2)::Array{Float32,2} │
│ %11 = getfield(%%args, 3)::Array{Float32,1} │
│ %12 = invoke Cassette.recurse(:($(QuoteNode(Cassette.Context{nametype(NoOp),Nothing,Cassette.NoPass,Nothing,Nothing}(nametype(NoOp)(), nothing, Cassette.NoPass(), nothing, nothing))))::Cassette.Context{nametype(NoOp),Nothing,Cassette.NoPass,Nothing,Nothing}, %9::typeof(*), %10::Array{Float32,2}, %11::Array{Float32,1})::Any
25 │ getfield(%%args, 1) │
│ getfield(%%args, 2) │
│ getfield(%%args, 3) │
26 └── return %12 │
) => Any
Reduced from getindex(Number,Int)
, the following code demonstrates a failure to infer a call to Cassette.execute
and subsequent failure to inline the overdubbed code:
using Cassette
Cassette.@context Ctx
foobar(i::Int, whatever) = i == 1
code_warntype(foobar, Tuple{Int, Int})
let ctx = Ctx(foobar)
@code_warntype (Cassette.Overdub(Cassette.Execute(), foobar, Cassette.Settings(ctx)))(1,1)
end
Original IR:
Variables:
i::Int64
whatever<optimized out>
Body:
begin
return (i::Int64 === 1)::Bool
end::Bool
Overdubbed:
Variables:
o<optimized out>
args::Tuple{Int64,Int64}
Body:
begin
return $(Expr(:invoke, MethodInstance for execute(::Cassette.Overdub{Cassette.Execute,typeof(==),Cassette.Settings{Ctx{0xc9f9ac09b400a019},Void,0x00000000000059fe,false}}, ::Int64, ::Int64, ::Vararg{Int64,N} where N), :(Cassette.execute), :($(QuoteNode(Cassette.Overdub{Cassette.Execute,typeof(==),Cassette.Settings{Ctx{0xc9f9ac09b400a019},Void,0x00000000000059fe,false}}(Cassette.Execute(), ==, Cassette.Settings{Ctx{0xc9f9ac09b400a019},Void,0x00000000000059fe,false}(Ctx{14553852828499091481}(), nothing, Cassette.World{0x00000000000059fe}(), Val{false}()))))), :((Core.getfield)(args, 1)::Int64), 1))::Any
end::Any
Tested both on master and the IPO branch (JuliaLang/julia#24362).
Dropping the unused whatever
argument resolved the issue. Is this a case of #5?
ref JuliaDiff/ForwardDiff.jl#313
Luckily, we can avoid this problem by using Cassette...on itself! We can make the tag generation itself a contextual primitive by default, and have it hash the executing context with the input type. This gets around the problem because we can exploit non-local type information.
Originally thought this was a straight up world age issue a la #6, but getting rid of the type parameter in the prehook
definition "fixes" it...
julia> using Cassette
julia> Cassette.@context Ctx
julia> ctx = Ctx()
Ctx{Cassette.Unused,Cassette.Unused,Nothing}(Cassette.Unused(), Cassette.Unused(), nothing)
julia> Cassette.overdub_recurse(ctx, sin, 1)
0.8414709848078965
# Doing the following instead makes this work instead of break:
#
# Cassette.prehook(::Ctx, f::Any, args...) = println("1")
#
julia> Cassette.prehook(::C, f::Any, args...) where {C<:Ctx} = println("1")
julia> Cassette.overdub_recurse(ctx, sin, 1)
0.8414709848078965
julia> methods(Cassette.prehook)
# 2 methods for generic function "prehook":
[1] prehook(::C, f, args...) where C<:Ctx in Main at REPL[6]:1
[2] prehook(::Cassette.AbstractContext, ...) in Cassette at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub.jl:5
julia> @which Cassette.prehook(ctx, sin, 1)
prehook(::C, f, args...) where C<:Ctx in Main at REPL[6]:1
# reflection gives us the right method, but it's still not getting called
julia> Cassette.prehook(ctx, sin, 1)
...a la ReverseDiffSparse.
Scalar variables can be stored in "live variable" vectors that are topologically sorted (i.e. are sorted in traversal order). Instructions can then store indices into these vectors rather than have a pointer per scalar.
After ecbe4d9 the following example fails to inline calls to overdub
:
using Cassette
Cassette.@context Ctx
code_warntype(Cassette.overdub(Ctx, unsafe_load), Tuple{Ptr{Float32}})
Before:
Body:
begin
...
Core.SSAValue(491) = (pointerref)(x1::Ptr{Float32}, Core.SSAValue(316), 1)::Any
...
end::Any
After:
Body:
begin
...
Core.SSAValue(32) = $(Expr(:invoke, MethodInstance for (::Cassette.Overdub{Cassette.Execute,typeof(unsafe_load),Cassette.Settings{Ctx{0xf0c703cd62596463},Cassette.Unused,0x00000000000063ef,false,Cassette.Unused}})(::Ptr{Float32}, ::Int64), :($(QuoteNode(Overdub{Execute}(Ctx, unsafe_load)))), :(x1), 1))::Any
...
end::Any
For reference, the non-overdubbed IR:
Body:
begin
# meta: location pointer.jl unsafe_load 105
Core.SSAValue(2) = (Base.pointerref)(p::Ptr{Float32}, 1, 1)::Float32
# meta: pop location
return Core.SSAValue(2)
end::Float32
cc @cfoket
Don't have time to look into these now, but apply this diff to the compiler to catch
generated function errors that would otherwise get swallowed:
diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl
index 89f5c0b05f..7b5af5dbde 100644
--- a/base/compiler/utilities.jl
+++ b/base/compiler/utilities.jl
@@ -89,7 +89,10 @@ function get_staged(li::MethodInstance)
try
# user code might throw errors – ignore them
return ccall(:jl_code_for_staged, Any, (Any,), li)::CodeInfo
- catch
+ catch ex
+ println(stderr, "WARNING: An error occurred during generated function execution.")
+ println(stderr, ex)
+ ccall(:jlbacktrace, Cvoid, ())
return nothing
end
end
...and then run the tagging tests produce a couple of different kinds of errors:
WARNING: An error occurred during generated function execution.
BoundsError(a=(), i=1)
rec_backtrace at /Users/jarrettrevels/data/repos/juliadev/src/stackwalk.c:94
record_backtrace at /Users/jarrettrevels/data/repos/juliadev/src/task.c:249 [inlined]
jl_throw at /Users/jarrettrevels/data/repos/juliadev/src/task.c:580
jl_bounds_error_unboxed_int at /Users/jarrettrevels/data/repos/juliadev/src/rtutils.c:166
getindex at ./tuple.jl:24
jfptr_getindex_2594 at /Users/jarrettrevels/data/repos/juliadev/usr/lib/julia/sys.dylib (unknown line)
#s23#31 at /Users/jarrettrevels/.julia/dev/Cassette/src/tagging.jl:521 [inlined]
#s23#31 at ./none:0
jl_apply_2va at /Users/jarrettrevels/data/repos/juliadev/src/rtutils.c:296
GeneratedFunctionStub at ./boot.jl:506
jl_apply at /Users/jarrettrevels/data/repos/juliadev/src/./julia.h:1559 [inlined]
jl_call_staged at /Users/jarrettrevels/data/repos/juliadev/src/method.c:376
jl_code_for_staged at /Users/jarrettrevels/data/repos/juliadev/src/method.c:409
get_staged at ./compiler/utilities.jl:91
⋮
WARNING: An error occurred during generated function execution.
ArgumentError(msg="Union{} does not have elements")
rec_backtrace at /Users/jarrettrevels/data/repos/juliadev/src/stackwalk.c:94
record_backtrace at /Users/jarrettrevels/data/repos/juliadev/src/task.c:249 [inlined]
jl_throw at /Users/jarrettrevels/data/repos/juliadev/src/task.c:580
eltype at ./array.jl:124
#s16#10 at /Users/jarrettrevels/.julia/dev/Cassette/src/tagging.jl:259
jl_apply_2va at /Users/jarrettrevels/data/repos/juliadev/src/rtutils.c:296
GeneratedFunctionStub at ./boot.jl:506
jl_apply at /Users/jarrettrevels/data/repos/juliadev/src/./julia.h:1559 [inlined]
jl_call_staged at /Users/jarrettrevels/data/repos/juliadev/src/method.c:376
jl_code_for_staged at /Users/jarrettrevels/data/repos/juliadev/src/method.c:409
get_staged at ./compiler/utilities.jl:91
⋮
Found in #41:
julia> using Cassette: @context, @prehook, @overdub
julia> @context Ctx
# This isn't prehook needed to reproduce, just gives us a little info about what's going on
julia> @prehook (f::Any)(args...) where {__CONTEXT__<:Ctx} = println(f, args)
julia> bar(T) = isa(T, UnionAll) ? bar(T.body) : T
bar (generic function with 1 method)
julia> @overdub(Ctx(), bar(Vector{T} where T))
TypeVar(:T,)
Core.apply_type(Union,)
Core.apply_type(Array{T,1} where T, T)
UnionAll(T, Array{T,1})
bar(Array{T,1} where T,)
isa(Array{T,1} where T, UnionAll)
Base.getproperty(Array{T,1} where T, :body)
getfield(Array{T,1} where T, :body)
bar(Array{T,1},)
isa(Array{T,1}, UnionAll)
Array{T,1}
julia> bar(Vector{T} where T)
Array{T,1}
julia> @overdub(Ctx(), bar(Vector{Vector{T}} where T))
TypeVar(:T,)
Core.apply_type(Union,)
Core.apply_type(Array{T,1} where T, T)
Core.apply_type(Array{T,1} where T, Array{T,1})
UnionAll(T, Array{Array{T,1},1})
bar(Array{Array{T,1},1} where T,)
isa(Array{Array{T,1},1} where T, UnionAll)
Base.getproperty(Array{Array{T,1},1} where T, :body)
getfield(Array{Array{T,1},1} where T, :body)
bar(Array{Array{T,1},1},)
isa(Array{Array{T,1},1}, UnionAll)
Base.getproperty(Array{Array{T,1},1}, :body)
getfield(Array{Array{T,1},1}, :body)
ERROR: type DataType has no field body
Stacktrace:
[1] overdub_execute at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub.jl:9 [inlined]
[2] overdub_recurse at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub.jl:155 [inlined]
[3] overdub_execute at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub.jl:34 [inlined]
[4] overdub_recurse(::Ctx{Cassette.Unused,0x0000000000006ba7,Cassette.Unused,getfield(Main, Symbol("##CtxTag#1559")){Nothing,0x5e60b7525025d2c1}}, ::typeof(bar), ::Type{Array{Array{T,1},1} where T}) at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub.jl:155
[5] overdub_execute at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub.jl:34 [inlined]
[6] overdub_recurse(::Ctx{Cassette.Unused,0x0000000000006ba7,Cassette.Unused,getfield(Main, Symbol("##CtxTag#1559")){Nothing,0x5e60b7525025d2c1}}, ::typeof(bar), ::Type{Array{Array{T,1},1} where T}) at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub.jl:155
[7] overdub_execute at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub.jl:34 [inlined]
[8] overdub_recurse(::Ctx{Cassette.Unused,0x0000000000006ba7,Cassette.Unused,getfield(Main, Symbol("##CtxTag#1559")){Nothing,0x5e60b7525025d2c1}}, ::getfield(Main, Symbol("##16#17")), ::Ctx{Cassette.Unused,0x0000000000006ba7,Cassette.Unused,getfield(Main, Symbol("##CtxTag#1559")){Nothing,0x5e60b7525025d2c1}}) at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub.jl:155
[9] top-level scope at /Users/jarrettrevels/.julia/v0.7/Cassette/src/macros.jl:68
julia> bar(Vector{Vector{T}} where T)
Array{Array{T,1},1}
Related to #65, there are various reasons to want to implement Cassette-like passes over typed IR, and more generally to have control over Julia's optimisation stack in a given context. Semantic code transformations should always be possible on untyped IR, but we might want to, for example, implement custom optimisation passes – like exploiting model parallelism in ML.
This is a fairly vague discussion issue; it will take some back-and-forth with the core Julia compiler folks to work out a good approach.
Cassette has gotten quite a bit more ambitious since it started out, but there is some way to go to really get past "contextual dispatch and metadata propagation" and become a general tool for creating new compilers on top of Julia.
One immediate step is that Cassette passes should be able to work on Julia's SSA IR. Aside from the fact that the IR has much more tooling around it (beyond find-and-replace), it's a natural format for many of the analyses and semantic transformations we might want to plug into the compiler.
I think that IR should eventually be the default with lowered code hidden from view, but this isn't possible right now as phi nodes are only accepted by the compiler post-type-inference. One workaround is to work on code_typed, preserving type information as much as possible, and then allow generated functions to return typed code without trying to re-infer it. It's hacky but it does get the job done.
This is a known issue, but just so it's recorded – Cassette's null pass still has a significant overhead compared to running a function directly.
julia> using Cassette, BenchmarkTools
julia> function loop(x, n)
r = x/x
while n > 0
r *= sin(x)
n -= 1
end
return r
end
loop (generic function with 1 method)
julia> @btime loop(2, 50)
474.760 ns (0 allocations: 0 bytes)
0.008615849517446223
julia> Cassette.@context NoOp
Cassette.Context{nametype(NoOp),M,P,T,B} where B<:Union{cNothing, IdDict{Module,Dict{Symbol,BindingMeta}}} where P<:Cassette.AbstractPass where T<:Union{Nothing, Tag} where M
julia> const ctx = NoOp()
Cassette.Context{nametype(NoOp),Nothing,Cassette.NoPass,Nothing,Nothing}(nametype(NoOp)(), nothing, Cassette.NoPass(), nothing, nothing)
julia> @btime Cassette.@overdub ctx loop(2, 50)
48.256 μs (350 allocations: 7.03 KiB)
0.008615849517446223ple: 1
cc @yurivish
using Cassette
Cassette.@context Ctx
f() = println("hello")
ctx = Cassette.enabletagging(Ctx(), f)
Cassette.overdub(ctx, f)
ERROR: BoundsError: attempt to access 0-element Array{Cassette.Meta{Any,NamedTuple{(:parent, :storage, :state, :donenotify, :result, :exception, :backtrace, :logstate, :code),Tuple{Cassette.Mutable{Cassette.Meta},Cassette.Mutable{Cassette.Meta},Cassette.Mutable{Cassette.Meta{Any,Cassette.NoMetaMeta}},Cassette.Mutable{Cassette.Meta},Cassette.Mutable{Cassette.Meta},Cassette.Mutable{Cassette.Meta},Cassette.Mutable{Cassette.Meta},Cassette.Mutable{Cassette.Meta},Cassette.Mutable{Cassette.Meta}}}},1} at index [1]
$ git clone https://github.com/JuliaLang/julia julia
$ cd julia
$ make -j10 LLVM_ASSERTIONS=1 FORCE_ASSERTIONS=1
$ JULIA_DEPOT_PATH=$(mktemp -d) ./julia
_
_ _ _(_)_ | A fresh approach to technical computing
(_) | (_) (_) | Documentation: https://docs.julialang.org
_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.
| | | | | | |/ _` | |
| | |_| | | | (_| | | Version 1.0.0-rc1.5 (2018-08-07 20:49 UTC)
_/ |\__'_|_|_|\__'_| | Commit d038f2f499 (0 days old master)
|__/ | x86_64-linux-gnu
(v1.0) pkg> add https://github.com/jrevels/Cassette.jl
Cloning git-repo `https://github.com/jrevels/Cassette.jl`
Updating git-repo `https://github.com/jrevels/Cassette.jl`
[ Info: Assigning UUID 59fb9612-49be-509a-8654-f972bbc0bdb2 to Cassette
Resolving package versions...
Updating `/tmp/tmp.WrMAkxtd5A/environments/v1.0/Project.toml`
[59fb9612] + Cassette v0.0.0 #master (https://github.com/jrevels/Cassette.jl)
Updating `/tmp/tmp.WrMAkxtd5A/environments/v1.0/Manifest.toml`
[59fb9612] + Cassette v0.0.0 #master (https://github.com/jrevels/Cassette.jl)
[2a0f44e3] + Base64
[8ba89e20] + Distributed
[b77e0a4c] + InteractiveUtils
[8f399da3] + Libdl
[37e2e46d] + LinearAlgebra
[56ddb016] + Logging
[d6f4376e] + Markdown
[9a3f8284] + Random
[9e88b42a] + Serialization
[6462fe0b] + Sockets
[8dfed614] + Test
julia> using Cassette
[ Info: Precompiling Cassette [59fb9612-49be-509a-8654-f972bbc0bdb2]
julia> Cassette.@context Ctx
Cassette.Context{nametype(Ctx),M,P,T,B} where B<:Union{Nothing, IdDict{Module,Dict{Symbol,BindingMeta}}} where P<:Cassette.AbstractPass where T<:Union{Nothing, Tag} where M
julia> Cassette.overdub(Ctx(), /, 1, 2)
julia: julia/src/gf.c:1431: jl_method_instance_add_backedge: Assertion `callee->def.method->min_world <= caller->min_world && callee->max_world >= caller->max_world' failed.
signal (6): Aborted
in expression starting at no file:0
gsignal at /lib/x86_64-linux-gnu/libc.so.6 (unknown line)
abort at /lib/x86_64-linux-gnu/libc.so.6 (unknown line)
unknown function (ip: 0x7fd2a25d2e66)
__assert_fail at /lib/x86_64-linux-gnu/libc.so.6 (unknown line)
jl_method_instance_add_backedge at julia/src/gf.c:1431
store_backedges at ./compiler/typeinfer.jl:197
typeinf at ./compiler/typeinfer.jl:72
typeinf_edge at ./compiler/typeinfer.jl:492
abstract_call_method at ./compiler/abstractinterpretation.jl:315
abstract_call_gf_by_type at ./compiler/abstractinterpretation.jl:79
abstract_call at ./compiler/abstractinterpretation.jl:763
abstract_eval_call at ./compiler/abstractinterpretation.jl:792
abstract_eval at ./compiler/abstractinterpretation.jl:877
typeinf_local at ./compiler/abstractinterpretation.jl:1101
typeinf_nocycle at ./compiler/abstractinterpretation.jl:1157
typeinf at ./compiler/typeinfer.jl:15
typeinf_edge at ./compiler/typeinfer.jl:492
abstract_call_method at ./compiler/abstractinterpretation.jl:315
abstract_call_gf_by_type at ./compiler/abstractinterpretation.jl:79
abstract_call at ./compiler/abstractinterpretation.jl:763
abstract_eval_call at ./compiler/abstractinterpretation.jl:792
abstract_eval at ./compiler/abstractinterpretation.jl:877
typeinf_local at ./compiler/abstractinterpretation.jl:1101
typeinf_nocycle at ./compiler/abstractinterpretation.jl:1157
typeinf at ./compiler/typeinfer.jl:15
typeinf_edge at ./compiler/typeinfer.jl:492
abstract_call_method at ./compiler/abstractinterpretation.jl:315
abstract_call_gf_by_type at ./compiler/abstractinterpretation.jl:79
abstract_call at ./compiler/abstractinterpretation.jl:763
abstract_eval_call at ./compiler/abstractinterpretation.jl:792
abstract_eval at ./compiler/abstractinterpretation.jl:877
typeinf_local at ./compiler/abstractinterpretation.jl:1101
typeinf_nocycle at ./compiler/abstractinterpretation.jl:1157
typeinf at ./compiler/typeinfer.jl:15
typeinf_edge at ./compiler/typeinfer.jl:492
abstract_call_method at ./compiler/abstractinterpretation.jl:315
abstract_call_gf_by_type at ./compiler/abstractinterpretation.jl:79
abstract_call at ./compiler/abstractinterpretation.jl:763
abstract_eval_call at ./compiler/abstractinterpretation.jl:792
abstract_eval at ./compiler/abstractinterpretation.jl:877
typeinf_local at ./compiler/abstractinterpretation.jl:1101
typeinf_nocycle at ./compiler/abstractinterpretation.jl:1157
typeinf at ./compiler/typeinfer.jl:15
typeinf_ext at ./compiler/typeinfer.jl:567
typeinf_ext at ./compiler/typeinfer.jl:604
jfptr_typeinf_ext_1 at julia/usr/lib/julia/sys.so (unknown line)
jl_apply_generic at julia/src/gf.c:2182
jl_apply at julia/src/julia.h:1536 [inlined]
jl_apply_with_saved_exception_state at julia/src/rtutils.c:257
jl_type_infer at julia/src/gf.c:275
jl_fptr_trampoline at julia/src/gf.c:1784
jl_apply_generic at julia/src/gf.c:2182
jl_apply_2va at julia/src/rtutils.c:296
GeneratedFunctionStub at ./boot.jl:506
jl_apply_generic at julia/src/gf.c:2182
jl_apply at julia/src/julia.h:1536 [inlined]
jl_call_staged at julia/src/method.c:376
jl_code_for_staged at julia/src/method.c:409
get_staged at ./compiler/utilities.jl:91
retrieve_code_info at ./compiler/utilities.jl:112
Type at ./compiler/inferencestate.jl:117 [inlined]
typeinf_ext at ./compiler/typeinfer.jl:565
typeinf_ext at ./compiler/typeinfer.jl:604
jfptr_typeinf_ext_1 at julia/usr/lib/julia/sys.so (unknown line)
jl_apply_generic at julia/src/gf.c:2182
jl_apply at julia/src/julia.h:1536 [inlined]
jl_apply_with_saved_exception_state at julia/src/rtutils.c:257
jl_type_infer at julia/src/gf.c:275
jl_fptr_trampoline at julia/src/gf.c:1784
jl_apply_generic at julia/src/gf.c:2182
do_call at julia/src/interpreter.c:324
eval_value at julia/src/interpreter.c:428
eval_stmt_value at julia/src/interpreter.c:363 [inlined]
eval_body at julia/src/interpreter.c:686
jl_interpret_toplevel_thunk_callback at julia/src/interpreter.c:799
unknown function (ip: 0xfffffffffffffffe)
unknown function (ip: 0x7fd292f0d09f)
unknown function (ip: 0x1)
jl_interpret_toplevel_thunk at julia/src/interpreter.c:808
jl_toplevel_eval_flex at julia/src/toplevel.c:785
jl_toplevel_eval_in at julia/src/builtins.c:622
eval at ./boot.jl:319
jl_apply_generic at julia/src/gf.c:2182
eval_user_input at julia/usr/share/julia/stdlib/v1.0/REPL/src/REPL.jl:85
macro expansion at julia/usr/share/julia/stdlib/v1.0/REPL/src/REPL.jl:117 [inlined]
#28 at ./task.jl:259
jl_apply_generic at julia/src/gf.c:2182
jl_apply at julia/src/julia.h:1536 [inlined]
start_task at julia/src/task.c:268
unknown function (ip: 0xffffffffffffffff)
Allocations: 9302501 (Pool: 9300888; Big: 1613); GC: 18
Okay, this was a tough one to get to a minimal working example, but i've got a sample script and manifest here:
https://gist.github.com/simonbyrne/6e7ff189401019bbf153efdee65418c1
It works as expected on Mac (throws a test failure), but on Windows I get some sort of parsing error: https://gist.github.com/simonbyrne/6e7ff189401019bbf153efdee65418c1#file-winerr-txt
Any suggestions?
using Cassette
using Cassette: @context, @primitive, overdub
@context Trace
@context BTrace
@primitive Trace (::typeof(broadcast))(f, args...) = overdub(BTrace, f)(args...)
@primitive BTrace (::typeof(+))(args...) = nothing
# Works
overdub(Trace, (a,b) -> a .+ b)([1,2,3],[4,5,6])
# Segfaults
overdub(Trace, x -> x .+ x)([1,2,3])
# #temp# not defined
overdub(Trace, (a,b) -> a .+ b .+ b)([1,2,3],[4,5,6])
To properly leverage JuliaLang/julia#22440, one needs to reimplement a bit of work that would normally be done as part of inlining during type inference.
Cassette currently does this as part of CodeInfo
lookup, but this should really be in Base. Otherwise, the code will easily become stale w.r.t. inference changes. For example, JuliaLang/julia#22826 has already broken the current implementation in Cassette.
This pass could be either be exposed from reflection.jl
(maybe as an inline
keyword arg to lowered_code
), or automagically performed by the compiler after generator expansion in the case where expansion yields a CodeInfo
object instead of an Expr
.
Might be from the depwarn? Just overdubbing the depwarn doesn't trigger it though...
julia> using Cassette
julia> Cassette.@context Ctx
julia> Cassette.recurse(Ctx(), Vector{Any}, 1)
Exception handling log message: TypeError(:recurse, "ccall: first argument not a pointer or valid constant expression", Ptr, (:pcre2_match_8, "libpcre2-8"))
module=Cassette file=/Users/jarrettrevels/.julia/dev/Cassette/src/overdub.jl line=0
Second exception: TypeError(:recurse, "ccall: first argument not a pointer or valid constant expression", Ptr, (:pcre2_match_8, "libpcre2-8"))
1-element Array{Any,1}:
#undef
Cassette relies heavily on varargs functions to pass through arguments with "no" overhead; until JuliaLang/julia#5402 is fixed, the performance of contextual code execution will be trash.
For the sake of metadata propagation, Cassette will intercept getfield(x, :f)
(before type inference) and convert it into something like Cassette._getfield(x, Val(:f))
. Bringing :f
into the type domain is necessary to enable the type-stable construction of "shadow struct
s" that can store metadata alongside normal struct
s.
According to a comment by @vtjnash in the base getfield
overloading issue, there is some concern that such an approach may thwart necessary optimizations (such as optimizing namespace references).
For future use in "production", the debug info added|overwritten by Cassette is going to be severely downgrading the debugging experience.
Exhibit 1
foo(ptr, i) = Base.unsafe_load(ptr, i)
Original IR:
; Function foo
; Location: test.jl:1
define float @julia_foo_62524(i64, i64) {
top:
%2 = add i64 %1, -1
%3 = inttoptr i64 %0 to float*
%4 = getelementptr float, float* %3, i64 %2
%5 = load float, float* %4, align 1
ret float %5
}
Overdubbed IR:
; Function Overdub
; Location: Cassette/src/workarounds.jl:21
define nonnull %jl_value_t addrspace(10)* @julia_Overdub_62640(i64, i64) {
top:
; Function execute; {
; Location: Cassette/src/workarounds.jl:16
; Function execute; {
; Location: Cassette/src/overdub/execution.jl:75
; Function execute; {
; Location: Cassette/src/workarounds.jl:18
; Function execute; {
; Location: Cassette/src/overdub/execution.jl:77
; Function Overdub; {
; Location: Cassette/src/overdub/execution.jl:129
; Function Overdub; {
; Location: Cassette/src/workarounds.jl:21
; Function execute; {
; Location: Cassette/src/workarounds.jl:16
; Function execute; {
; Location: Cassette/src/overdub/execution.jl:75
; Function execute; {
; Location: Cassette/src/workarounds.jl:18
; Function execute; {
; Location: Cassette/src/overdub/execution.jl:77
; Function Overdub; {
; Location: Cassette/src/overdub/execution.jl:129
; Function Overdub; {
; Location: Cassette/src/workarounds.jl:21
; Function execute; {
; Location: Cassette/src/workarounds.jl:16
; Function execute; {
; Location: Cassette/src/overdub/execution.jl:75
; Function execute; {
; Location: Cassette/src/workarounds.jl:18
; Function execute; {
; Location: Cassette/src/overdub/execution.jl:77
; Function Overdub; {
; Location: Cassette/src/overdub/execution.jl:129
; Function @generated body; {
; Location: Cassette/src/overdub/execution.jl:138
; Function execute; {
; Location: Cassette/src/workarounds.jl:17
; Function execute; {
; Location: Cassette/src/overdub/execution.jl:76
; Function execution; {
; Location: Cassette/src/workarounds.jl:12
; Function execution; {
; Location: Cassette/src/overdub/execution.jl:46
; Function _execution; {
; Location: Cassette/src/workarounds.jl:9
; Function _execution; {
; Location: Cassette/src/overdub/execution.jl:45
; Function unwrapcall; {
; Location: Cassette/src/workarounds.jl:24
; Function unwrapcall; {
; Location: Cassette/src/contextual/metadata.jl:45
; Function @generated body; {
; Location: Cassette/src/contextual/metadata.jl:48
%2 = add i64 %1, -1
%3 = inttoptr i64 %0 to float*
%4 = getelementptr float, float* %3, i64 %2
%5 = load float, float* %4, align 1
%6 = call %jl_value_t addrspace(10)* @jl_box_float32(float %5)
;}}}}}}}}}}}}}}}}}}}}}}}}}}}
ret %jl_value_t addrspace(10)* %6
}
Another problem is method names getting lost. For example:
foobar(i) = foo(bar(i))
@noinline foo(i) = i
@noinline bar(i) = i
Original IR:
define i64 @julia_foobar_62524(i64) {
top:
%1 = call i64 @julia_bar_62525(i64 %0)
%2 = call i64 @julia_foo_62526(i64 %1)
ret i64 %2
}
Overdubbed IR:
define i64 @julia_Overdub_62611(i64) {
top:
%1 = call i64 @julia_Overdub_62612(i64 %0)
%2 = call i64 @julia_Overdub_62613(i64 %1)
ret i64 %2
}
ie. everything is overdub now.
Of course, these issues aren't critical, just putting it out here 🙂
Cassette is at a pivotal moment in development. The purpose of this post is to provide a public semi-recap of Cassette's post-JuliaCon development, seek feedback from any interested developers, and - most importantly to me - to clarify my thinking regarding current/future decisions.
Warning: this issue is going to be a monolithic brain dump. Normally, if Cassette was farther in development, I'd split this into several issues. This early in the process, however, I'm not too worried about separating design discussion.
Anybody who saw Cassette at JuliaCon might recall code like:
@defgenre ValueGenre
Play{ValueGenre,f}(args...) = # `Play` definition for the `ValueGenre` primitive `f`
Record{ValueGenre,f}(args...) = # `Record` definition for the `ValueGenre` primitive `f`
Replay{ValueGenre,f}(args...) = # `Replay` definition for the `ValueGenre` primitive `f`
Rewind{ValueGenre,f}(args...) = # `Rewind` definition for the `ValueGenre` primitive `f`
I've since realized that the free-floating G<:AbstractGenre
type parameters were overly complicated, and - it pains me to admit this - more punny than practical.
Now, instead of users dispatching on free type parameters, users will dispatch on context functors. I think an example describes this best:
julia> using Cassette: Trace, InterceptAction, @defcontext, unbox
julia> @defcontext MyCtx
# This `InterceptAction` trait says "any function call encountered
# during a `MyCtx` trace should be processed as a `MyCtx` primitive".
# Note that the form (and even the existence) of these traits is
# very much up for debate.
julia> (::InterceptAction{<:MyCtx})(args...) = Val{:process}()
julia> (f::MyCtx)(args...) = (println("called $f"); unbox(f)(args...))
julia> f(x) = x[1] + x[2] + x[3] * prod(x)
f (generic function with 1 method)
julia> Trace(MyCtx(f))(rand(3))
called MyCtx{Base.#getindex}(getindex)
called MyCtx{Base.#getindex}(getindex)
called MyCtx{Base.#getindex}(getindex)
called MyCtx{Base.#prod}(prod)
called MyCtx{Base.#*}(*)
called MyCtx{Base.#+}(+)
1.8495487943289208
Obviously, the replay and rewind mechanisms are still going to exist, but I'm not too worried about nailing down their re-implementation for the time being.
The most drastic change to the tracing mechanism since JuliaCon is that I've removed the world age parameter from trace-related functors. It was annoying to maintain during prototyping, and was only a stopgap anyway. The real solution to 265-safety for Cassette's tracer is to implement a base Julia mechanism that enables 265-safety for general @generated
functions. The current plan for this is to automatically pass the caller's world age as a hidden argument to the generator, and then require the generator to return the world bounds in which the method definition is safe. Placing the burden of 265-safety on the @generated
function author may not be as robust as having it enforced by the compiler, but it's better than nothing at all.
Other than 265-related stuff, I've realized that Julia's reflection facilities returns the "wrong" CodeInfo for @generated
functions (okay, not actually wrong, just wrong for my use case). Given a type signature, the lowering machinery returns the CodeInfo for the generator rather than the actual method body corresponding to the type signature. You can get the lowered method body by allowing type inference to expand the generator, but then the method body has inferred type information in it.
Here's an example:
julia> @generated function bob(x)
if isa(x, Int)
return :(x + x)
else
return :(x * x)
end
end
bob (generic function with 1 method)
# the lowered form of the generator expression
julia> @code_lowered bob(1)
CodeInfo(:(begin
nothing
unless x isa Main.Int goto 5
#= line 3 =#
return $(Expr(:copyast, :($(QuoteNode(:(x + x))))))
5:
#= line 5 =#
return $(Expr(:copyast, :($(QuoteNode(:(x * x))))))
end))
# the generator gets expanded as part of type inference
julia> @code_typed bob(1)
CodeInfo(:(begin
#= REPL[1]:2 =#
# meta: location REPL[1] @generated body
return (Base.mul_int)(x, x)::Int64
# meta: pop location
end))=>Int64
Following from the above example, Cassette can only trace bob
if it can grab the bob
's lowered code after generator expansion but before the expanded method body is inferred.
This problem will need to be resolved before Cassette can trace through @generated
functions. In other words, this is a blocking issue for nested tracing support.
There's probably a way to tell Julia to expand the generator, but I feel like there should be an exposed reflection tool for this (maybe there is and I missed it)? For example, something like @method_lowered f(1)
(bad name, perhaps) could return the CodeInfo for the method specified by the signature Tuple{typeof(f),Int}
. If f
is a normal function, this CodeInfo will be the same CodeInfo returned by @code_lowered f(1)
. If f
is a @generated
function, the returned CodeInfo will be the lowered form of the method body yielded by f
's generator expansion given an Int
argument.
I basically know exactly what I want this to look like, and it's not too different from what I had going at JuliaCon, so there's not really anything new to say here.
I should note that, for development purposes, I've ripped out the graph representation code from master
. This way, I can focus on nailing down tracing/primitive-handling semantics without worrying about updating any coupled graph code. Once the tracing implementation is more complete, I'll rebuild the graph stuff on top of it.
Intercept
Type and Granular Interception TraitsThe idea here is to define Trace(MyCtx(f))
to mean "rewrite every subcall g(args...)
into Intercept(MyCtx(g))(args...)
", as opposed to it meaning "rewrite every subcall g(args...)
into MyCtx(g)(args...)
."
This approach enables a system of "granular interception traits", where context creators can overload an InterceptAction
trait for any function call that tells Cassette whether to trace into the call, skip the call (i.e. call the original function with original arguments) or process the call as a primitive (i.e. call the contextualized function with the original arguments).
This implementation looks something like the following:
struct Intercept{C<:AbstractContext}
callable::C
end
unbox(i::Intercept) = i.callable
(i::Intercept)(input...) = perform(action(unbox(i), input...), unbox(i), input...)
perform(action::Val{:process}, c::AbstractContext, input...) = c(input...)
perform(action::Val{:trace}, c::AbstractContext, input...) = Trace(c)(input...)
perform(action::Val{:skip}, c::AbstractContext, input...) = unbox(c)(input...)
struct InterceptAction{C<:AbstractContext}
callable::C
end
unbox(i::InterceptAction) = i.callable
# skip by default
(::InterceptAction)(input...) = Val{:skip}()
@generated function action(i::AbstractContext{F}, input...) where {F}
if F.name.module === Core
return :(Val{:skip}())
else
return :(InterceptAction(c)(input...))
end
end
This approach is nice because it relieves the context creator of the inevitable burden of implementing a similar system. Additionally, the action
function can enforce skipping "fundamental" primitives, like Core
functions.
However, it's more complex and less transparent than simply defining Trace(MyCtx(f))
to mean "rewrite every subcall g(args...)
into MyCtx(g)(args...)
".
One of the primary use cases for Cassette are data flow regimes where forward- and back-propagation of metadata can be fully specified by some kind of primitive action coupled with a chain rule. This includes, for example, automatic differentiation (primitive action = derivative calculation, chain rule = the usual differentiation chain rule) and interval arithmetic (primitive action = inverse calculation, chain rule = set intersection). For these kinds of contexts, Cassette needs to provide a common interface for attaching/propagating contextual metadata via function arguments.
Technically, we could leave metadata propagation semantics up to the context creators, but this is a horrible idea in practice. It's quite tricky to define metadata semantics that work in the regime of arbitrarily granular/nested traces, and this regime will be encountered whenever two Cassette contexts need to compose. In other words, if Cassette doesn't impose some consistent set of semantics for nested metadata propagation, then it will be almost impossible to confidently compose two independently defined Cassette contexts.
Consider the following example, where we have an interval arithmetic context IntervalCtx
with an associated interval constructor Interval
, and a differentiation context DiffCtx
with an associated dual number constructor Dual
:
xinterval(f, x, y) = Trace(IntervalCtx(f))(Interval(x, 2*abs(x)), y)
yinterval(f, x, y) = Trace(IntervalCtx(f))(x, Interval(y, 2*abs(y)))
xdiff(f, x, y) = Trace(DiffCtx(f))(Dual(x, one(x)), y)
ydiff(f, x, y) = Trace(DiffCtx(f))(x, Dual(y, one(y)))
Assuming IntervalCtx
was developed independently from DiffCtx
, let's see what happens when we compose these functions (for the sake of this example, let's forget about interception traits, or just assume they work "correctly" in the nested tracing regime):
f(x, y) = sin(x) * tan(y)
a, b = rand(), rand()
# these should all be well-defined/well-behaved
xdiff((x, y) -> xdiff(f, x, y), a, b)
ydiff((x, y) -> ydiff(f, x, y), a, b)
xinterval((x, y) -> xinterval(f, x, y), a, b)
yinterval((x, y) -> yinterval(f, x, y), a, b)
xinterval((x, y) -> yinterval(f, x, y), a, b)
yinterval((x, y) -> xinterval(f, x, y), a, b)
xdiff((x, y) -> xinterval(f, x, y), a, b)
ydiff((x, y) -> yinterval(f, x, y), a, b)
xinterval((x, y) -> xdiff(f, x, y), a, b)
yinterval((x, y) -> ydiff(f, x, y), a, b)
# these will cause perturbation confusion; see
# https://github.com/JuliaDiff/ForwardDiff.jl/issues/83
xdiff((x, y) -> ydiff(f, x, y), a, b)
ydiff((x, y) -> xdiff(f, x, y), a, b)
# this isn't well-defined; may or may not
# do the "correct" thing depending on how
# clever both context authors were about
# metadata boxing/unboxing
xdiff((x, y) -> yinterval(f, x, y), a, b)
ydiff((x, y) -> xinterval(f, x, y), a, b)
From playing around with examples like these, I've come to the belief that Cassette must provide a means of contextualizing arguments such that argument metadata is tied in some way to its originating context. Such a mechanism would look similar to ForwardDiff's tagging system, where dual numbers and their associated differential operators share unique type tags (in order to prevent perturbation confusion).
As far as an implementation goes, my thinking at this point is that Cassette could reuse the existing caller context wrapping machinery for arguments, and then metadata functors could reside over/under the context wrapper. An alternative would be to add a freely typed meta
field to context wrappers, which can be used for whatever the context creator wants. The meta
field approach seems superficially simpler, but may also be an unnecessary coupling of concerns...I'm not sure yet which approach is best.
Here are the guarantees I think need to be provided by this system:
The current Cassette prototype already defines some box
/unbox
methods to handle this, which could be exposed to downstream context creators:
# if `x` has the same context as `c`, return `x`,
# else return `x` wrapped in the same context as `c`
box(c::AbstractContext, x) = # boring implementation
# if `x` has the exact same context as `c`,
# then return `unbox(x)`, else return `x`
unbox(c::AbstractContext, x) = # boring implementation
# efficiently perform `f(map(g, args)...)`
mapcall(g, f, args...) = # boring implementation
# an example of composing `mapcall` and `unbox`
unboxcall(c, f, args...) = mapcall(x -> unbox(c, x), f, args...)
These examples conform to this guarantee:
Ctx1(Ctx2(f))(Ctx1(Ctx2(x)), y)
Ctx1(Ctx2(f))(Ctx1(Ctx2(x)), Ctx2(y))
Ctx1(Ctx2(f))(Ctx1(x), Ctx2(y))
Ctx1(Ctx2(f))(Ctx1(x), Ctx1(y))
These examples do not conform to this guarantee:
Ctx1(Ctx2(f))(Ctx1(Ctx1(x)), y)
Ctx1(Ctx2(f))(Ctx2(Ctx1(x)), Ctx2(y))
Ctx1(Ctx2(f))(Ctx1(x), Ctx2(y))
In other words (taking the metadata functor approach as an example) we can choose either Meta(Ctx(value))
or Ctx(Meta(value))
as canonical orientations, but we should not allow both orientations. The point here is to force a contextual barrier in the case of nested tracing, so that we disallow weird situations like Ctx(Meta(Meta(Ctx(value))))
.
For example, I should be able to write code like isa(Ctx1(Ctx2(Ctx3(x::Real)))), Ctx1{<:Real})
, which should return true
.
An implementation of this guarantee would look something like the following example. Note that the example doesn't show the tag system required for a robust implementation of guarantee 1, and it uses the meta
field approach mentioned in guarantee 3.
# The purpose of `CountPlusCtx` will be to allow arguments to keep track of
# how many times they appear as input to the `+` function.
julia> @macroexpand Cassette.@defcontext CountPlusCtx
quote
struct CountPlusCtx{T,M,V} <: AbstractContext{T}
value::V
meta::M
CountPlusCtx(value::V, meta::M = nothing) where {V,M} = new{V,M,V}(value, meta)
CountPlusCtx(value::AbstractContext{T}, meta::M = nothing) where {T,M} = new{T,M,typeof(x)}(value, meta)
end
Cassette.box(::CountPlusCtx, value, meta = nothing) = CountPlusCtx(value, meta)
end
This way, context creators can always dispatch on T
, no matter how nested the structure becomes at runtime:
(f::CountPlusCtx)(args...) = unboxcall(f, unbox(f), args...)
# Note how we dispatch on the underlying `+`, even though `+` could actually be deeply
# nested in other contexts, e.g. `CountPlusCtx(SomeOtherCtx(YetAnotherCtx(+)))`
function (f::CountPlusCtx{typeof(+)})(args...)
result = mapcall(unbox(f), args...) do x
if hascontext(f, x)
x.meta[] += 1
end
return unbox(f, x)
end
return box(f, result, [0])
end
Intended behavior of this context in practice (I just mocked up this result, didn't actually run it):
julia> f(x, y) = (x + y) * sin(y + y + y) * cos(x + x);
julia> x = CountPlusCtx(1, [0]);
julia> y = CountPlusCtx(2, [0]);
julia> Trace(CountPlusCtx(f))(x, y)
1.6298819286267558
julia> x
CountPlusCtx(1, [3]);
julia> y
CountPlusCtx(2, [4]);
I'm running the AD POC code
@context DiffCtx
const DiffCtxWithTag{T} = DiffCtx{Nothing,T}
Cassette.metadatatype(::Type{<:DiffCtx}, ::Type{T}) where {T<:Real} = T
tangent(x, context) = hasmetadata(x, context) ? metadata(x, context) : zero(untag(x, context))
function D(f, x)
ctx = enabletagging(DiffCtx(), f)
result = overdub(ctx, f, tag(x, ctx, oftype(x, 1.0)))
return tangent(result, ctx)
end
function Cassette.execute(ctx::DiffCtxWithTag{T}, ::Typ(sin), x::Tagged{T,<:Real}) where {T}
vx, dx = untag(x, ctx), tangent(x, ctx)
return tag(sin(vx), ctx, cos(vx) * dx)
end
function Cassette.execute(ctx::DiffCtxWithTag{T}, ::Typ(cos), x::Tagged{T,<:Real}) where {T}
vx, dx = untag(x, ctx), tangent(x, ctx)
return tag(cos(vx), ctx, -sin(vx) * dx)
end
function Cassette.execute(ctx::DiffCtxWithTag{T}, ::Typ(*), x::Tagged{T,<:Real}, y::Tagged{T,<:Real}) where {T}
vx, dx = untag(x, ctx), tangent(x, ctx)
vy, dy = untag(y, ctx), tangent(y, ctx)
return tag(vx * vy, ctx, vy * dx + vx * dy)
end
function Cassette.execute(ctx::DiffCtxWithTag{T}, ::Typ(*), x::Tagged{T,<:Real}, y::Real) where {T}
vx, dx = untag(x, ctx), tangent(x, ctx)
return tag(vx * y, ctx, y * dx)
end
function Cassette.execute(ctx::DiffCtxWithTag{T}, ::Typ(*), x::Real, y::Tagged{T,<:Real}) where {T}
vy, dy = untag(y, ctx), tangent(y, ctx)
return tag(x * vy, ctx, x * dy)
end
function Cassette.execute(ctx::DiffCtxWithTag{T}, ::Typ(+), x::Tagged{T,<:Real}, y::Tagged{T,<:Real}) where {T}
vx, dx = untag(x, ctx), tangent(x, ctx)
vy, dy = untag(y, ctx), tangent(y, ctx)
return tag(vx + vy, ctx, dx + dy)
end
function Cassette.execute(ctx::DiffCtxWithTag{T}, ::Typ(+), x::Tagged{T,<:Real}, y::Real) where {T}
vx, dx = untag(x, ctx), tangent(x, ctx)
return tag(vx + y, ctx, dx)
end
function Cassette.execute(ctx::DiffCtxWithTag{T}, ::Typ(+), x::Real, y::Tagged{T,<:Real}) where {T}
vy, dy = untag(y, ctx), tangent(y, ctx)
return tag(x + vy, ctx, dy)
end
and getting some weird things
First I try to add a prehook
for DiffCtxWithTag
, like this:
Cassette.prehook(ctx::DiffCtxWithTag{T}, f, args...) where {T} = println(f, args)
and running
julia> D(x -> x * D(y -> x * y, 1), 2)
and get
typeof(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), 2, Meta(1, _)),)
Core.apply_type(getfield(Main, Symbol("##5#7")), Int64)
D(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), getfield(Main, Symbol("##5#7")){Int64}(2), Meta(_, (x = Meta(1, _)))), 1)
Cassette.Context{nametype(DiffCtx),M,P,T,B} where B<:Union{Nothing, IdDict{Module,Dict{Symbol,BindingMeta}}} where P<:Cassette.AbstractPass where T<:Union{Nothing, Tag} where M()
NamedTuple()
Core.apply_type(Tuple,)
Core.apply_type(NamedTuple, (), Tuple{})
NamedTuple{(),Tuple{}}((),)
Core.apply_type(NamedTuple, (), Tuple{})
pairs(NamedTuple(),)
keys(NamedTuple(),)
Base.Iterators.Pairs(NamedTuple(), ())
eltype(Tuple{},)
eltype(NamedTuple{(),Tuple{}},)
Core.apply_type(Base.Iterators.Pairs, Union{}, Union{}, Tuple{}, NamedTuple{(),Tuple{}})
fieldtype(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, :data)
convert(NamedTuple{(),Tuple{}}, NamedTuple())
fieldtype(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, :itr)
convert(Tuple{}, ())
#DiffCtx#3(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}(), Cassette.Context{nametype(DiffCtx),M,P,T,B} where B<:Union{Nothing, IdDict{Module,Dict{Symbol,BindingMeta}}} where P<:Cassette.AbstractPass where T<:Union{Nothing, Tag} where M)
getproperty(Cassette, :Context)
getfield(Cassette, :Context)
nametype(DiffCtx)()
NamedTuple()
Core.apply_type(Tuple,)
Core.apply_type(NamedTuple, (), Tuple{})
NamedTuple{(),Tuple{}}((),)
Core.apply_type(NamedTuple, (), Tuple{})
merge(NamedTuple(), Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}())
getproperty(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}(), :data)
getfield(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}(), :data)
merge(NamedTuple(), NamedTuple())
isempty(NamedTuple(),)
Cassette.Context(nametype(DiffCtx)(),)
Cassette.NoPass()
Cassette.#Context#4(nothing, Cassette.NoPass(), Cassette.Context, nametype(DiffCtx)())
Cassette.Context(nametype(DiffCtx)(), nothing, Cassette.NoPass(), nothing, nothing)
Core.apply_type(Cassette.Context, nametype(DiffCtx), Nothing, Cassette.NoPass, Nothing, Nothing)
fieldtype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Nothing,Nothing}, :name)
convert(nametype(DiffCtx), nametype(DiffCtx)())
fieldtype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Nothing,Nothing}, :metadata)
convert(Nothing, nothing)
fieldtype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Nothing,Nothing}, :pass)
convert(Cassette.NoPass, Cassette.NoPass())
fieldtype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Nothing,Nothing}, :tag)
convert(Nothing, nothing)
fieldtype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Nothing,Nothing}, :bindingscache)
convert(Nothing, nothing)
Cassette.enabletagging(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Nothing,Nothing}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), nothing, nothing), Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), getfield(Main, Symbol("##5#7")){Int64}(2), Meta(_, (x = Meta(1, _)))))
Core.apply_type(NamedTuple, (:tag, :bindingscache))
getproperty(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Nothing,Nothing}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), nothing, nothing), :name)
getfield(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Nothing,Nothing}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), nothing, nothing), :name)
typeof(nametype(DiffCtx)(),)
typeof(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), getfield(Main, Symbol("##5#7")){Int64}(2), Meta(_, (x = Meta(1, _)))),)
Cassette.Tag(nametype(DiffCtx), getfield(Main, Symbol("##5#7")){Int64})
IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()
Core.apply_type(IdDict, Module, Dict{Symbol,Cassette.BindingMeta})
Core.apply_type(Array{T,1} where T, Any)
Core.apply_type(Array{T,1} where T, Any)
Array{Any,1}(array initializer with undefined values, 32)
convert(Array{Any,1}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Any[#undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef], Meta(_, Cassette.Meta[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _])))
isa(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Any[#undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef], Meta(_, Cassette.Meta[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _])), Array{Any,1})
convert(Int64, 0)
convert(Int64, 0)
tuple(Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}(), Meta(_, (ht = Meta(_, Cassette.Meta[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), count = _, ndel = _))))
NamedTuple{(:tag, :bindingscache),T} where T<:Tuple(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), (Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (_, Meta(_, (ht = Meta(_, Cassette.Meta[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), count = _, ndel = _))))),)
typeof(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), (Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (_, Meta(_, (ht = Meta(_, Cassette.Meta[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), count = _, ndel = _))))),)
Core.apply_type(NamedTuple, (:tag, :bindingscache), Tuple{Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}})
NamedTuple{(:tag, :bindingscache),Tuple{Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}}(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), (Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (_, Meta(_, (ht = Meta(_, Cassette.Meta[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), count = _, ndel = _))))),)
Core.apply_type(NamedTuple, (:tag, :bindingscache), Tuple{Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}})
getfield(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), (Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (_, Meta(_, (ht = Meta(_, Cassette.Meta[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), count = _, ndel = _))))), 1)
getfield(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), (Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (_, Meta(_, (ht = Meta(_, Cassette.Meta[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), count = _, ndel = _))))), 2)
Core.kwfunc(Cassette.similarcontext,)
getfield(Cassette, Symbol("#kw##similarcontext"))()(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), (tag = Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), bindingscache = IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (tag = _, bindingscache = Meta(_, (ht = Meta(_, Cassette.Meta[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), count = _, ndel = _))))), Cassette.similarcontext, Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Nothing,Nothing}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), nothing, nothing))
getproperty(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Nothing,Nothing}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), nothing, nothing), :name)
getfield(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Nothing,Nothing}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), nothing, nothing), :name)
Cassette.Context(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}())
typeof(Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(),)
Core.apply_type(Cassette.Context, nametype(DiffCtx), Nothing, Cassette.NoPass, Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}, IdDict{Module,Dict{Symbol,Cassette.BindingMeta}})
fieldtype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}, :name)
convert(nametype(DiffCtx), nametype(DiffCtx)())
fieldtype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}, :metadata)
convert(Nothing, nothing)
fieldtype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}, :pass)
convert(Cassette.NoPass, Cassette.NoPass())
fieldtype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}, :tag)
convert(Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}, Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}())
fieldtype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}, :bindingscache)
convert(IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}, IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}())
oftype(1, 1.0)
typeof(1,)
convert(Int64, 1.0)
Int64(1.0,)
<=(-9.223372036854776e18, 1.0)
le_float(-9.223372036854776e18, 1.0)
<(1.0, 9.223372036854776e18)
lt_float(1.0, 9.223372036854776e18)
round(1.0, RoundingMode{:ToZero}())
trunc_llvm(1.0,)
==(1.0, 1.0)
eq_float(1.0, 1.0)
unsafe_trunc(Int64, 1.0)
fptosi(Int64, 1.0)
Cassette.tag(1, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), 1)
Cassette.initmeta(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), 1, 1)
Cassette.metadatatype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}, Int64)
Cassette.metametatype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}, Int64)
Core.apply_type(Cassette.Meta, Int64, Cassette.NoMetaMeta)
Cassette.initmetameta(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), 1)
Cassette.NoMetaMeta()
Cassette.Meta{Int64,Cassette.NoMetaMeta}(1, _)
Core.apply_type(Cassette.Meta, Int64, Cassette.NoMetaMeta)
Core.apply_type(Union, Int64, Cassette.NoMetaData)
convert(Union{NoMetaData, Int64}, 1)
Core.apply_type(Union, Cassette.NoMetaMeta, Cassette.NoMetaMeta)
convert(Cassette.NoMetaMeta, _)
Tagged(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), 1, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Meta(1, _), Meta(_, (data = _, meta = _))))
Cassette.metadatatype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}, Int64)
Cassette.metametatype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}, Int64)
Core.apply_type(Tagged, Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}, Int64, Int64, Cassette.NoMetaMeta)
getproperty(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), :tag)
getfield(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), :tag)
convert(Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), _))
convert(Int64, 1)
Core.apply_type(Cassette.Meta, Int64, Cassette.NoMetaMeta)
Core.apply_type(Cassette.Meta, Int64, Cassette.NoMetaMeta)
convert(Cassette.Meta{Int64,Cassette.NoMetaMeta}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Meta(1, _), Meta(_, (data = _, meta = _))))
convert(Cassette.Meta{Int64,Cassette.NoMetaMeta}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Meta(1, _), Meta(_, (data = _, meta = _))))
Cassette.overdub(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), getfield(Main, Symbol("##5#7")){Int64}(2), Meta(_, (x = Meta(1, _)))), Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Tagged(Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), 1, Meta(1, _)), Meta(_, (tag = _, value = _, meta = Meta(_, (data = _, meta = _))))))
getfield(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), (getfield(Main, Symbol("##5#7")){Int64}(2), Tagged(Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), 1, Meta(1, _))), Meta(_, (Meta(_, (x = Meta(1, _))), Meta(_, (tag = _, value = _, meta = Meta(_, (data = _, meta = _))))))), 1)
getfield(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), (getfield(Main, Symbol("##5#7")){Int64}(2), Tagged(Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), 1, Meta(1, _))), Meta(_, (Meta(_, (x = Meta(1, _))), Meta(_, (tag = _, value = _, meta = Meta(_, (data = _, meta = _))))))), 2)
Cassette.fetch_tagged_module(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Core, Meta(_, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}(_, Dict{Symbol,Cassette.BindingMeta}()))))
Cassette.NoMetaData()
Cassette.fetch_modulemeta(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Core, Meta(_, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}(_, Dict{Symbol,Cassette.BindingMeta}()))))
getproperty(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), :bindingscache)
getfield(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), :bindingscache)
haskey(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}(), _), Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Core, Meta(_, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}(_, Dict{Symbol,Cassette.BindingMeta}()))))
keys(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}(), _),)
Base.KeySet(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}(), _),)
Core.apply_type(Base.KeySet, Module, IdDict{Module,Dict{Symbol,Cassette.BindingMeta}})
Base.KeySet{Module,IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}(), _),)
Core.apply_type(Base.KeySet, Module, IdDict{Module,Dict{Symbol,Cassette.BindingMeta}})
convert(IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}(), _))
in(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Core, Meta(_, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}(_, Dict{Symbol,Cassette.BindingMeta}()))), Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Module[], Meta(_, (dict = _))))
getproperty(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Module[], Meta(_, (dict = _))), :dict)
getfield(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Module[], Meta(_, (dict = _))), :dict)
get(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}(), _), Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Core, Meta(_, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}(_, Dict{Symbol,Cassette.BindingMeta}()))), :__c782dbf1cf4d6a2e5e3865d7e95634f2e09b5902__)
getproperty(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}(), _), :ht)
getfield(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}(), _), :ht)
===(:__c782dbf1cf4d6a2e5e3865d7e95634f2e09b5902__, :__c782dbf1cf4d6a2e5e3865d7e95634f2e09b5902__)
!==(:__c782dbf1cf4d6a2e5e3865d7e95634f2e09b5902__, :__c782dbf1cf4d6a2e5e3865d7e95634f2e09b5902__)
===(:__c782dbf1cf4d6a2e5e3865d7e95634f2e09b5902__, :__c782dbf1cf4d6a2e5e3865d7e95634f2e09b5902__)
!(true,)
not_int(true,)
Dict{Symbol,Cassette.BindingMeta}()
Core.apply_type(Dict, Symbol, Cassette.BindingMeta)
Core.apply_type(Array, UInt8, 1)
zeros(UInt8, 16)
zeros(UInt8, (16,))
Core.apply_type(Array, UInt8, 1)
map(Base.to_dim, (16,))
getindex((16,), 1)
getfield((16,), 1, true)
Base.to_dim(16,)
tuple(16,)
Array{UInt8,1}(array initializer with undefined values, (16,))
zero(UInt8,)
convert(UInt8, 0)
UInt8(0,)
Core.toUInt8(0,)
Core.checked_trunc_uint(UInt8, 0)
trunc_int(UInt8, 0)
zext_int(Int64, 0x00)
eq_int(0, 0)
typeassert(0x00, UInt8)
fill!(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), UInt8[0x70, 0xc1, 0xb2, 0x1c, 0x01, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x7e, 0x1a, 0x01, 0x00, 0x00, 0x00], Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _])), 0x00)
Core.apply_type(Ptr, Nothing)
Base.cconvert(Ptr{Nothing}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), UInt8[0x70, 0xc1, 0xb2, 0x1c, 0x01, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x7e, 0x1a, 0x01, 0x00, 0x00, 0x00], Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _])))
Base.cconvert(Int32, 0x00)
convert(Int32, 0x00)
Int32(0x00,)
Core.toInt32(0x00,)
zext_int(Int32, 0x00)
typeassert(0, Int32)
length(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), UInt8[0x70, 0xc1, 0xb2, 0x1c, 0x01, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x7e, 0x1a, 0x01, 0x00, 0x00, 0x00], Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _])),)
arraylen(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), UInt8[0x70, 0xc1, 0xb2, 0x1c, 0x01, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x7e, 0x1a, 0x01, 0x00, 0x00, 0x00], Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _])),)
Base.cconvert(UInt64, 16)
convert(UInt64, 16)
UInt64(16,)
Core.toUInt64(16,)
Core.check_top_bit(16,)
Core.is_top_bit_set(16,)
Core.sizeof(16,)
shl_int(8, 3)
sub_int(64, 1)
lshr_int(16, 63)
trunc_int(UInt8, 0)
trunc_int(UInt8, 1)
eq_int(0x00, 0x01)
bitcast(UInt64, 16)
typeassert(0x0000000000000010, UInt64)
Core.apply_type(Ptr, Nothing)
Base.unsafe_convert(Ptr{Nothing}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), UInt8[0x70, 0xc1, 0xb2, 0x1c, 0x01, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x7e, 0x1a, 0x01, 0x00, 0x00, 0x00], Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _])))
Core.apply_type(Ptr, Nothing)
Core.apply_type(Ptr, UInt8)
Base.unsafe_convert(Ptr{UInt8}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), UInt8[0x70, 0xc1, 0xb2, 0x1c, 0x01, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x7e, 0x1a, 0x01, 0x00, 0x00, 0x00], Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _])))
convert(Ptr{Nothing}, Ptr{UInt8} @0x000000011cef5160)
Core.apply_type(Ptr, Nothing)
bitcast(Ptr{Nothing}, Ptr{UInt8} @0x000000011cef5160)
Base.unsafe_convert(Int32, 0)
Base.unsafe_convert(UInt64, 0x0000000000000010)
convert(Array{UInt8,1}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), UInt8[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _])))
isa(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), UInt8[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _])), Array{UInt8,1})
Core.apply_type(Array, Symbol, 1)
Core.apply_type(Array{T,1} where T, Symbol)
Array{Symbol,1}(array initializer with undefined values, 16)
convert(Array{Symbol,1}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Symbol[#undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef], _))
isa(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Symbol[#undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef], _), Array{Symbol,1})
Core.apply_type(Array, Cassette.BindingMeta, 1)
Core.apply_type(Array{T,1} where T, Cassette.BindingMeta)
Array{Cassette.BindingMeta,1}(array initializer with undefined values, 16)
convert(Array{Cassette.BindingMeta,1}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.BindingMeta[#undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef], Meta(_, Cassette.Meta{Cassette.NoMetaData,NamedTuple{(:data,),Tuple{Cassette.Mutable{Cassette.Meta}}}}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _])))
isa(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.BindingMeta[#undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef], Meta(_, Cassette.Meta{Cassette.NoMetaData,NamedTuple{(:data,),Tuple{Cassette.Mutable{Cassette.Meta}}}}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _])), Array{Cassette.BindingMeta,1})
convert(Int64, 0)
convert(Int64, 0)
convert(UInt64, 0)
UInt64(0,)
Core.toUInt64(0,)
Core.check_top_bit(0,)
Core.is_top_bit_set(0,)
Core.sizeof(0,)
shl_int(8, 3)
sub_int(64, 1)
lshr_int(0, 63)
trunc_int(UInt8, 0)
trunc_int(UInt8, 1)
eq_int(0x00, 0x01)
bitcast(UInt64, 0)
typeassert(0x0000000000000000, UInt64)
convert(Int64, 1)
convert(Int64, 0)
getproperty(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), :bindingscache)
getfield(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}()), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), :bindingscache)
setindex!(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}(), _), Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Dict{Symbol,Cassette.BindingMeta}(), Meta(_, (slots = Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), keys = _, vals = Meta(_, Cassette.Meta{Cassette.NoMetaData,NamedTuple{(:data,),Tuple{Cassette.Mutable{Cassette.Meta}}}}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), ndel = _, count = _, age = _, idxfloor = _, maxprobe = _))), Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Core, Meta(_, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}(_, Dict{Symbol,Cassette.BindingMeta}()))))
isa(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Core, Meta(_, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}(_, Dict{Symbol,Cassette.BindingMeta}()))), Module)
!(true,)
not_int(true,)
convert(Dict{Symbol,Cassette.BindingMeta}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Dict{Symbol,Cassette.BindingMeta}(), Meta(_, (slots = Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), keys = _, vals = Meta(_, Cassette.Meta{Cassette.NoMetaData,NamedTuple{(:data,),Tuple{Cassette.Mutable{Cassette.Meta}}}}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), ndel = _, count = _, age = _, idxfloor = _, maxprobe = _))))
getproperty(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}(), _), :ndel)
getfield(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}(), _), :ndel)
getproperty(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}(), _), :ht)
getfield(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}(), _), :ht)
length(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Any[#undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef], _),)
arraylen(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Any[#undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef], _),)
*(3, 32)
mul_int(3, 32)
>>(96, 2)
<=(0, 2)
sle_int(0, 2)
unsigned(2,)
reinterpret(UInt64, 2)
bitcast(UInt64, 2)
>>(96, 0x0000000000000002)
ashr_int(96, 0x0000000000000002)
-(2,)
neg_int(2,)
unsigned(-2,)
reinterpret(UInt64, -2)
bitcast(UInt64, -2)
<<(96, 0xfffffffffffffffe)
shl_int(96, 0xfffffffffffffffe)
ifelse(true, 24, 0)
>=(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), 0, _), 24)
<=(24, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), 0, _))
sle_int(24, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), 0, _))
Core.apply_type(Base.RefValue, Int32)
Base.RefValue{Int32}(0,)
Core.apply_type(Base.RefValue, Int32)
convert(Int32, 0)
Int32(0,)
Core.toInt32(0,)
Core.checked_trunc_sint(Int32, 0)
trunc_int(Int32, 0)
sext_int(Int64, 0)
eq_int(0, 0)
typeassert(0, Int32)
Core.apply_type(Ptr, Int32)
Base.cconvert(Ptr{Int32}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Base.RefValue{Int32}(0), Meta(_, (x = _))))
getproperty(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}(), _), :ht)
getfield(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}(), _), :ht)
Core.apply_type(Ptr, Int32)
Base.unsafe_convert(Ptr{Int32}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Base.RefValue{Int32}(0), Meta(_, (x = _))))
Core.apply_type(Base.RefValue, Int32)
Base.datatype_pointerfree(Base.RefValue{Int32},)
getproperty(Base.RefValue{Int32}, :layout)
getfield(Base.RefValue{Int32}, :layout)
==(Ptr{Nothing} @0x0000000116698380, Ptr{Nothing} @0x0000000000000000)
UInt64(Ptr{Nothing} @0x0000000116698380,)
bitcast(UInt64, Ptr{Nothing} @0x0000000116698380)
UInt64(Ptr{Nothing} @0x0000000000000000,)
bitcast(UInt64, Ptr{Nothing} @0x0000000000000000)
==(0x0000000116698380, 0x0000000000000000)
===(0x0000000116698380, 0x0000000000000000)
Core.apply_type(Ptr, Base.DataTypeLayout)
getproperty(Base.RefValue{Int32}, :layout)
getfield(Base.RefValue{Int32}, :layout)
convert(Ptr{Base.DataTypeLayout}, Ptr{Nothing} @0x0000000116698380)
Core.apply_type(Ptr, Base.DataTypeLayout)
bitcast(Ptr{Base.DataTypeLayout}, Ptr{Nothing} @0x0000000116698380)
unsafe_load(Ptr{Base.DataTypeLayout} @0x0000000116698380,)
unsafe_load(Ptr{Base.DataTypeLayout} @0x0000000116698380, 1)
Int64(1,)
Core.toInt64(1,)
typeassert(1, Int64)
pointerref(Ptr{Base.DataTypeLayout} @0x0000000116698380, 1, 1)
getproperty(Base.DataTypeLayout(0x00000001, 0x00000004), :alignment)
getfield(Base.DataTypeLayout(0x00000001, 0x00000004), :alignment)
>>(0x00000004, 10)
<=(0, 10)
sle_int(0, 10)
unsigned(10,)
reinterpret(UInt64, 10)
bitcast(UInt64, 10)
>>(0x00000004, 0x000000000000000a)
lshr_int(0x00000004, 0x000000000000000a)
-(10,)
neg_int(10,)
unsigned(-10,)
reinterpret(UInt64, -10)
bitcast(UInt64, -10)
<<(0x00000004, 0xfffffffffffffff6)
shl_int(0x00000004, 0xfffffffffffffff6)
ifelse(true, 0x00000000, 0x00000000)
&(0x00000000, 0x000fffff)
and_int(0x00000000, 0x000fffff)
==(0x00000000, 0)
>=(0, 0)
<=(0, 0)
sle_int(0, 0)
unsigned(0,)
reinterpret(UInt64, 0)
bitcast(UInt64, 0)
==(0x00000000, 0x0000000000000000)
promote(0x00000000, 0x0000000000000000)
Base._promote(0x00000000, 0x0000000000000000)
promote_type(UInt32, UInt64)
promote_rule(UInt32, UInt64)
promote_rule(UInt64, UInt32)
Base.promote_result(UInt32, UInt64, Union{}, UInt64)
promote_type(Union{}, UInt64)
convert(UInt64, 0x00000000)
UInt64(0x00000000,)
Core.toUInt64(0x00000000,)
zext_int(UInt64, 0x00000000)
typeassert(0x0000000000000000, UInt64)
convert(UInt64, 0x0000000000000000)
tuple(0x0000000000000000, 0x0000000000000000)
Base.indexed_iterate((0x0000000000000000, 0x0000000000000000), 1)
Base.indexed_iterate((0x0000000000000000, 0x0000000000000000), 1, 1)
getindex((0x0000000000000000, 0x0000000000000000), 1)
getfield((0x0000000000000000, 0x0000000000000000), 1, true)
+(1, 1)
add_int(1, 1)
tuple(0x0000000000000000, 2)
getfield((0x0000000000000000, 2), 1)
getfield((0x0000000000000000, 2), 2)
Base.indexed_iterate((0x0000000000000000, 0x0000000000000000), 2, 2)
getindex((0x0000000000000000, 0x0000000000000000), 2)
getfield((0x0000000000000000, 0x0000000000000000), 2, true)
+(2, 1)
add_int(2, 1)
tuple(0x0000000000000000, 3)
getfield((0x0000000000000000, 3), 1)
getfield((0x0000000000000000, 3), 2)
tuple(0x00000000, 0x0000000000000000)
tuple(0x0000000000000000, 0x0000000000000000)
Base.not_sametype((0x00000000, 0x0000000000000000), (0x0000000000000000, 0x0000000000000000))
tuple(0x0000000000000000, 0x0000000000000000)
Core._apply(==, (0x0000000000000000, 0x0000000000000000))
==(0x0000000000000000, 0x0000000000000000)
===(0x0000000000000000, 0x0000000000000000)
&(true, true)
and_int(true, true)
pointer_from_objref(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Base.RefValue{Int32}(0), Meta(_, (x = _))),)
typeof(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Base.RefValue{Int32}(0), Meta(_, (x = _))),)
getproperty(Base.RefValue{Int32}, :mutable)
getfield(Base.RefValue{Int32}, :mutable)
convert(Ptr{Int32}, Ptr{Nothing} @0x000000011b3ba730)
Core.apply_type(Ptr, Int32)
bitcast(Ptr{Int32}, Ptr{Nothing} @0x000000011b3ba730)
===(setproperty!, setfield!)
setproperty!(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict(Core=>Dict()), _), :ht, Any[#undef, #undef, Core, Dict{Symbol,Cassette.BindingMeta}(), #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef])
typeof(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict(Core=>Dict()), _),)
fieldtype(IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}, :ht)
convert(Array{Any,1}, Any[#undef, #undef, Core, Dict{Symbol,Cassette.BindingMeta}(), #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef])
isa(Any[#undef, #undef, Core, Dict{Symbol,Cassette.BindingMeta}(), #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef], Array{Any,1})
setfield!(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict(Core=>Dict()), _), :ht, Any[#undef, #undef, Core, Dict{Symbol,Cassette.BindingMeta}(), #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef])
getproperty(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict(Core=>Dict()), _), :count)
getfield(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict(Core=>Dict()), _), :count)
getindex(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Base.RefValue{Int32}(1), Meta(_, (x = _))),)
getproperty(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Base.RefValue{Int32}(1), Meta(_, (x = _))), :x)
getfield(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Base.RefValue{Int32}(1), Meta(_, (x = _))), :x)
+(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), 0, _), Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), 1, _))
===(setproperty!, setfield!)
setproperty!(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict(Core=>Dict()), _), :count, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), 1, Meta(0, _)))
typeof(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict(Core=>Dict()), _),)
fieldtype(IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}, :count)
convert(Int64, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), 1, Meta(0, _)))
setfield!(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), IdDict(Core=>Dict()), _), :count, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), 1, Meta(0, _)))
typeassert(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Dict{Symbol,Cassette.BindingMeta}(), Meta(_, (slots = Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), keys = _, vals = Meta(_, Cassette.Meta{Cassette.NoMetaData,NamedTuple{(:data,),Tuple{Cassette.Mutable{Cassette.Meta}}}}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), ndel = _, count = _, age = _, idxfloor = _, maxprobe = _))), Dict{Symbol,Cassette.BindingMeta})
Cassette.ModuleMeta(_, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Dict{Symbol,Cassette.BindingMeta}(), Meta(_, (slots = Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), keys = _, vals = Meta(_, Cassette.Meta{Cassette.NoMetaData,NamedTuple{(:data,),Tuple{Cassette.Mutable{Cassette.Meta}}}}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), ndel = _, count = _, age = _, idxfloor = _, maxprobe = _))))
Core.apply_type(Cassette.ModuleMeta, Cassette.NoMetaData, Cassette.NoMetaMeta)
Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}(_, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Dict{Symbol,Cassette.BindingMeta}(), Meta(_, (slots = Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), keys = _, vals = Meta(_, Cassette.Meta{Cassette.NoMetaData,NamedTuple{(:data,),Tuple{Cassette.Mutable{Cassette.Meta}}}}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), ndel = _, count = _, age = _, idxfloor = _, maxprobe = _))))
Core.apply_type(Cassette.ModuleMeta, Cassette.NoMetaData, Cassette.NoMetaMeta)
Core.apply_type(Cassette.Meta, Cassette.NoMetaData, Cassette.NoMetaMeta)
convert(Cassette.Meta{Cassette.NoMetaData,Cassette.NoMetaMeta}, _)
convert(Dict{Symbol,Cassette.BindingMeta}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Dict{Symbol,Cassette.BindingMeta}(), Meta(_, (slots = Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), keys = _, vals = Meta(_, Cassette.Meta{Cassette.NoMetaData,NamedTuple{(:data,),Tuple{Cassette.Mutable{Cassette.Meta}}}}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), ndel = _, count = _, age = _, idxfloor = _, maxprobe = _))))
Cassette.Meta(_, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}(_, Dict{Symbol,Cassette.BindingMeta}()), Meta(_, (name = _, bindings = Meta(_, (slots = Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), keys = _, vals = Meta(_, Cassette.Meta{Cassette.NoMetaData,NamedTuple{(:data,),Tuple{Cassette.Mutable{Cassette.Meta}}}}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), ndel = _, count = _, age = _, idxfloor = _, maxprobe = _))))))
Core.apply_type(Cassette.Meta, Cassette.NoMetaData, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta})
Cassette.Meta{Cassette.NoMetaData,Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}}(_, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}(_, Dict{Symbol,Cassette.BindingMeta}()), Meta(_, (name = _, bindings = Meta(_, (slots = Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), keys = _, vals = Meta(_, Cassette.Meta{Cassette.NoMetaData,NamedTuple{(:data,),Tuple{Cassette.Mutable{Cassette.Meta}}}}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), ndel = _, count = _, age = _, idxfloor = _, maxprobe = _))))))
Core.apply_type(Cassette.Meta, Cassette.NoMetaData, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta})
Core.apply_type(Union, Cassette.NoMetaData, Cassette.NoMetaData)
convert(Cassette.NoMetaData, _)
Core.apply_type(Union, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}, Cassette.NoMetaMeta)
convert^[[D(Union{NoMetaMeta, ModuleMeta{NoMetaData,NoMetaMeta}}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}(_, Dict{Symbol,Cassette.BindingMeta}()), Meta(_, (name = _, bindings = Meta(_, (slots = Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), keys = _, vals = Meta(_, Cassette.Meta{Cassette.NoMetaData,NamedTuple{(:data,),Tuple{Cassette.Mutable{Cassette.Meta}}}}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), ndel = _, count = _, age = _, idxfloor = _, maxprobe = _))))))
Tagged(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict(Core=>Dict())), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Core, Meta(_, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}(_, Dict{Symbol,Cassette.BindingMeta}()))), Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Meta(_, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}(_, Dict{Symbol,Cassette.BindingMeta}())), Meta(_, (data = _, meta = Meta(_, (name = _, bindings = Meta(_, (slots = Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), keys = _, vals = Meta(_, Cassette.Meta{Cassette.NoMetaData,NamedTuple{(:data,),Tuple{Cassette.Mutable{Cassette.Meta}}}}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), ndel = _, count = _, age = _, idxfloor = _, maxprobe = _))))))))
Cassette.metadatatype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}, Module)
Cassette.metametatype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}, Module)
Cassette.metadatatype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}, Symbol)
Cassette.metametatype(Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}, Symbol)
Core.apply_type(Cassette.ModuleMeta, Cassette.NoMetaData, Cassette.NoMetaMeta)
Core.apply_type(Tagged, Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}, Module, Cassette.NoMetaData, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta})
getproperty(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict(Core=>Dict())), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), :tag)
getfield(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict(Core=>Dict())), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), :tag)
convert(Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), _))
convert(Module, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Core, Meta(_, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}(_, Dict{Symbol,Cassette.BindingMeta}()))))
Core.apply_type(Cassette.Meta, Cassette.NoMetaData, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta})
Core.apply_type(Cassette.Meta, Cassette.NoMetaData, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta})
convert(Cassette.Meta{Cassette.NoMetaData,Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Meta(_, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}(_, Dict{Symbol,Cassette.BindingMeta}())), Meta(_, (data = _, meta = Meta(_, (name = _, bindings = Meta(_, (slots = Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), keys = _, vals = Meta(_, Cassette.Meta{Cassette.NoMetaData,NamedTuple{(:data,),Tuple{Cassette.Mutable{Cassette.Meta}}}}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), ndel = _, count = _, age = _, idxfloor = _, maxprobe = _))))))))
convert(Cassette.Meta{Cassette.NoMetaData,Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}}, Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Meta(_, Cassette.ModuleMeta{Cassette.NoMetaData,Cassette.NoMetaMeta}(_, Dict{Symbol,Cassette.BindingMeta}())), Meta(_, (data = _, meta = Meta(_, (name = _, bindings = Meta(_, (slots = Meta(_, Cassette.Meta{UInt8,Cassette.NoMetaMeta}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), keys = _, vals = Meta(_, Cassette.Meta{Cassette.NoMetaData,NamedTuple{(:data,),Tuple{Cassette.Mutable{Cassette.Meta}}}}[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]), ndel = _, count = _, age = _, idxfloor = _, maxprobe = _))))))))
Cassette.fetch_tagged_module(Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Cassette.Context{nametype(DiffCtx),Nothing,Cassette.NoPass,Cassette.Tag{nametype(DiffCtx),0x05a29318ccf630f6,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}},IdDict{Module,Dict{Symbol,Cassette.BindingMeta}}}(nametype(DiffCtx)(), nothing, Cassette.NoPass(), Tag{nametype(DiffCtx),406048651143950582,Cassette.Tag{nametype(DiffCtx),0x4d096580f381ca1a,Nothing}}(), IdDict(Core=>Dict())), Meta(_, (name = _, metadata = _, pass = _, tag = _, bindingscache = _))), Tagged(Tag{nametype(DiffCtx),5551079620226435610,Nothing}(), Main, Segmentation fault: 11
end with a Segmentation fault: 11
and also,
if I run
x = 2
D(x -> x * D(y -> 5*x*y, 3), x)
In my mind, it should be
x = 2
D(x -> x * D(y -> 5*x*y, 3), x) == 10x #however here is false
but actually
julia> D(x -> x * D(y -> 5*x*y, 3), x)
10
I'm not sure why the above happened or whether they should happen, so I just open an issue here.
I'll close the issue if everything is just fine
julia> versioninfo()
Julia Version 0.7.0-beta.206
Commit b6f9244d6b (2018-07-08 19:42 UTC)
Platform Info:
OS: macOS (x86_64-apple-darwin17.5.0)
CPU: Intel(R) Core(TM) i7-7920HQ CPU @ 3.10GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-6.0.0 (ORCJIT, skylake)
julia> using Cassette
julia> Cassette.@context Ctx
julia> @code_typed Cassette.recurse(Ctx(), unsafe_load, pointer(Float32[]))
CodeInfo(
105 1 ── Core.getfield(%%##recurse_arguments#377, 1) │
│ %2 = Core.getfield(%%##recurse_arguments#377, 2)::Ptr{Float32} │
└─── goto 2 if not false │╻ overdub
2 ┄─ goto 3 if not false ││╻╷ unsafe_load
3 ┄─ goto 4 if not false │││╻╷ overdub
4 ┄─ goto 5 ││││┃│ Type
5 ── %7 = :(Core.typeassert)::typeof(typeassert) │││││
│ %8 = :(Core.Int64)::Type{Int64} │││││
└─── goto 6 if not false │││││╻ overdub
6 ┄─ Base.getfield(Cassette, :execute) ││││││╻╷╷ recurse
│ %7(1, %8) │││││││╻ macro expansion
│ %12 = π (1, Int64) ││││││││┃│ execute
└─── goto 7 │││││╻ overdub
7 ── goto 8 │││││
8 ── goto 9 │││╻ overdub
9 ── %16 = :(Base.pointerref)::Core.IntrinsicFunction │││
└─── goto 10 if not false │││╻ overdub
10 ┄ Base.getfield(Cassette, :execute) ││││╻╷╷ recurse
│ %19 = %16(%2, %12, 1)::Any │││││╻ macro expansion
└─── goto 11 │││╻ overdub
11 ─ goto 12 │││
12 ─ goto 13 │╻ overdub
13 ─ return %19 │
) => Float32
Here %19
should be inferred as a Float32, but isn't. It doesn't necessarily matter in this case since we inferred the output anyway, but in other cases this seems like an indicator that something broke.
Specifically to avoid the crazy dual supertype/subtype calculations that the old design required, and to lean more on the idea of "contextual memory space" instead of storing metadata directly beside primal values (though that might end up being done anyway as an optimization, of course).
Note that I already ripped out the old system in #41, since I didn't want to have to update the old code along with the other refactoring that was going on.
I just fixed the :new
interception/replacement to work against Julia master, but now I'm getting crashes like these:
julia> using Cassette
julia> struct Foo{T}
x::T
end
julia> Cassette.@context Ctx
julia> Cassette.overdub(Ctx, Foo)(1)
Unreachable reached at 0x1107c92c0
signal (4): Illegal instruction: 4
in expression starting at no file:0
unknown function (ip: 0x1107c92bf)
unknown function (ip: 0x1107c92e2)
jl_call_fptr_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:380 [inlined]
jl_call_method_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:399 [inlined]
jl_apply_generic at /Users/jarrettrevels/data/repos/julia7/src/gf.c:2081
Overdub at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub/execution.jl:163 [inlined]
execute at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub/execution.jl:78 [inlined]
execute at /Users/jarrettrevels/.julia/v0.7/Cassette/src/workarounds.jl:18 [inlined]
execute at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub/execution.jl:76 [inlined]
execute at /Users/jarrettrevels/.julia/v0.7/Cassette/src/workarounds.jl:16 [inlined]
Overdub at /Users/jarrettrevels/.julia/v0.7/Cassette/src/workarounds.jl:21
unknown function (ip: 0x1107c4a72)
jl_call_fptr_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:380 [inlined]
jl_call_method_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:399 [inlined]
jl_apply_generic at /Users/jarrettrevels/data/repos/julia7/src/gf.c:2081
do_call at /Users/jarrettrevels/data/repos/julia7/src/interpreter.c:323
eval_body at /Users/jarrettrevels/data/repos/julia7/src/interpreter.c:509
jl_interpret_toplevel_thunk_callback at /Users/jarrettrevels/data/repos/julia7/src/interpreter.c:720
unknown function (ip: 0xfffffffffffffffe)
unknown function (ip: 0x10b92a64f)
unknown function (ip: 0x1)
jl_interpret_toplevel_thunk at /Users/jarrettrevels/data/repos/julia7/src/interpreter.c:729
jl_toplevel_eval_flex at /Users/jarrettrevels/data/repos/julia7/src/toplevel.c:721
jl_toplevel_eval_in at /Users/jarrettrevels/data/repos/julia7/src/builtins.c:626
eval at ./boot.jl:298 [inlined]
eval at ./repl/REPL.jl:3
jl_call_fptr_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:380 [inlined]
jl_call_method_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:399 [inlined]
jl_apply_generic at /Users/jarrettrevels/data/repos/julia7/src/gf.c:2081
eval_user_input at ./repl/REPL.jl:70
jl_call_fptr_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:380 [inlined]
jl_call_method_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:399 [inlined]
jl_apply_generic at /Users/jarrettrevels/data/repos/julia7/src/gf.c:2081
macro expansion at ./repl/REPL.jl:101 [inlined]
#1 at ./event.jl:92
jl_call_fptr_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:380 [inlined]
jl_call_method_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:399 [inlined]
jl_apply_generic at /Users/jarrettrevels/data/repos/julia7/src/gf.c:2081
jl_apply at /Users/jarrettrevels/data/repos/julia7/src/./julia.h:1474 [inlined]
start_task at /Users/jarrettrevels/data/repos/julia7/src/task.c:268
Allocations: 7205991 (Pool: 7204666; Big: 1325); GC: 16
[1] 34137 illegal hardware instruction julia7
Note that the crash doesn't happen if Foo
doesn't have a type parameter.
I wanted to implement some of these soon, but I've been super busy lately, so I'm making an issue to preserve the ideas.
First, @hook
should be renamed to @prehook
, and we should add a @posthook
macro that does what you'd expect. This should be pretty easy to implement and is a non-controversial improvement. EDIT: this is now implemented in #32
Second, we need a way for express a base case when calling overdub recursively. A Cassette "primitive" is any method call that Cassette chooses to "execute" rather than trace; user-defined primitives are only a subset of all Cassette primitives. We need to expose some way for people to test whether a call is a primitive or not that captures this whole definition, for example:
using Cassette
Cassette.@context TraceCtx
Cassette.@primitive TraceCtx function f(args...)
if Cassette.isprimitive(__config__, f, args...; user = false) # this function doesn't exist in an exposable way yet
return f(args...)
else
subtrace = Any[]
push!(trace, f => subtrace)
return Cassette.overdub(ctx, f; phase = Cassette.Intercept(), metadata = subtrace)(args...)
end
end
To achieve this, we'd have to add a world age field to EDIT: After speaking with some folks, I think just exposing the internal trace-local configuration object via a non-hygienic binding is the best syntax for this.ctx
, which I'm naively okay with, but have to think on it a bit more to make sure...
#18 only fixes overdubbing for calls that are RHS of assignments. Cassette still needs a follow-up PR to overdub calls in non-assignment statements
In order to prevent world-age issues and make things more convenient, user-defined passes should have a special API.
For example, here's the current behavior, which is broken because of world-age problems akin to JuliaLang/julia#23223:
julia> using Cassette
# define our pass type
julia> struct MyPass end
# just a no-op pass
julia> (::Type{MyPass})(sig, body) = body
julia> Cassette.@context Ctx
julia> f = Cassette.overdub(Ctx, -, pass = MyPass())
Overdub{Execute}(Ctx, -)
julia> f(1)
ERROR DURING OVERDUBBED EXECUTION: MethodError: no method matching MyPass(::Type{Tuple{typeof(-),Int64}}, ::CodeInfo)
The applicable method may be too new: running in world age 25205, while current world is 25564.
Closest candidates are:
MyPass(::Any, ::Any) at REPL[3]:1 (method too new to be called from this world context.)
To fix this, Cassette can provide a @pass
macro which:
(f::Overdub{Intercept,...,MyPass})(args...)
)cc @SimonDanisch (who helped me discover that the old pass mechanism is now broken)
There's not an actual upstream issue for this yet.
Cassette relies very heavily on @generated
functions, which currently have lots of issues w.r.t. world age validation and precompilation. For Cassette's specific use case, many of these issues can be fixed if:
CodeInfo
objects retrieved from the method table contained their valid world age boundsCodeInfo
The following fails to generate efficient code:
inner(::Type{Bool}, i) = nothing
outer(I...) = inner(Bool, I...)
using Cassette
Cassette.@context Ctx
f = (args...) -> Cassette.overdub(Ctx(), outer, args...)
code_warntype(f, Tuple{Int})
Body::ANY
10 1 ─ %1 = Cassette.overdub::Core.Compiler.Const(Cassette.overdub, false)
│ %2 = (getfield)(args, 1)::Int64
│ %3 = invoke %1($(QuoteNode(Cassette.Context{nametype(Ctx),Nothing,Cassette.NoPass,Nothing,Nothing}(nametype(Ctx)(), nothing, Cassette.NoPass(), nothing, nothing)))::Cassette.Context{nametype(Ctx),Nothing,Cassette.NoPass,Nothing,Nothing}, outer::typeof(outer), %2::Int64)::ANY
└── return %3
... whereas without overdubbing, we simply get:
Body::Nothing
2 1 ─ return
Checking the code for overdub
reveals why we get the invoke:
code_warntype(Cassette.overdub, Tuple{typeof(Ctx()), typeof(outer), Int})
Body::ANY
2 1 ─ %1 = (Core.getfield)(##overdub_arguments#369, 2)::Int64
│ %2 = (Core.tuple)(%1)::Tuple{Int64}
└── goto #3 if not true
2 ─ %4 = Core.tuple::Core.Compiler.Const(tuple, false)
│ %5 = Main.Bool::Core.Compiler.Const(Bool, false)
└── %6 = (%4)(%5)::Tuple{DataType}
3 ─ %7 = φ (#2 => %6, #1 => $(QuoteNode(Cassette.OverdubInstead())))::UNION{OVERDUBINSTEAD, TUPLE{DATATYPE}}
│ %8 = π (%7, Core.Compiler.Const((Bool,), false))
└── goto #5 if not true
4 ─ %10 = Core._apply::typeof(Core._apply)
│ %11 = (%10)(tuple, %8, %2)::Tuple{DataType,Int64}
│ %12 = (getfield)(%11, 1)::DATATYPE
│ %13 = (getfield)(%11, 2)::Int64
└── %14 = (Cassette.overdub)($(QuoteNode(Cassette.Context{nametype(Ctx),Nothing,Cassette.NoPass,Nothing,Nothing}(nametype(Ctx)(), nothing, Cassette.NoPass(), nothing, nothing))), inner, %12, %13)::ANY
5 ─ %15 = φ (#4 => %14, #3 => $(QuoteNode(Cassette.OverdubInstead())))::ANY
└── return %15
Looks like a failure to const-prop the Bool
argument through the argument tuple. But then again, the second element of the tuple isn't const, and I'm not sure whether that can be expressed with Compiler.Const
.
This specific pattern arises with checkbounds(::Array, ::Integer)
which calls to checkbounds(Bool, ...)
(ie. passing a Type{Bool}
), so this probably penalizes quite a lot of code as soon as it indexes arrays.
This is similar to what Box
already supports, but allowing for dynamic integer indices in addition to statically named fields.
It's currently possible to do this manually:
@isprimitive (::getfield(Base, Symbol("#kw##mapreduce")))(::Any...) where {__CONTEXT__ <: TraceCtx}
But it would be much nicer if it were built in:
@isprimitive mapreduce(::Any...; kwargs...) where {__CONTEXT__ <: TraceCtx}
because the generated #kw#
methods and the namedtuple-munging code with which they start are a Julia implementation detail that isn't visible during ordinary method overloading.
Basically, I want to see this call tower:
sum([0.52695 0.88639; 1.0 1.0]; dims = 1)
Base._sum([0.52695 0.88639; 1.0 1.0], 1)
and not this one:
getfield(Base, Symbol("#kw##sum"))()((dims = 1,), sum, [0.52695 0.88639; 1.0 1.0])
haskey((dims = 1,), :dims)
isdefined((dims = 1,), :dims)
getindex((dims = 1,), :dims)
getfield((dims = 1,), :dims)
Core.apply_type(NamedTuple, (:dims,))
Base.structdiff((dims = 1,), NamedTuple{(:dims,),T} where T<:Tuple)
Core.apply_type(NamedTuple, (), Tuple{})
NamedTuple{(),Tuple{}}((),)
Core.apply_type(NamedTuple, (), Tuple{})
pairs(NamedTuple(),)
keys(NamedTuple(),)
Base.Iterators.Pairs(NamedTuple(), ())
eltype(Tuple{},)
eltype(NamedTuple{(),Tuple{}},)
eltype(Tuple{},)
Core.apply_type(Base.Iterators.Pairs, Union{}, Union{}, Tuple{}, NamedTuple{(),Tuple{}})
fieldtype(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, :data)
convert(NamedTuple{(),Tuple{}}, NamedTuple())
fieldtype(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, :itr)
convert(Tuple{}, ())
isempty(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}(),)
Base.isdone(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}(),)
getproperty(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}(), :itr)
getfield(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}(), :itr)
Core._apply(Base.isdone, ((),), ())
Base.isdone((),)
!==(missing, missing)
===(missing, missing)
!(true,)
not_int(true,)
iterate(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}(),)
getproperty(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}(), :itr)
getfield(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}(), :itr)
Core._apply(iterate, ((),), ())
iterate((),)
iterate((), 1)
length((),)
nfields((),)
<(0, 1)
slt_int(0, 1)
===(nothing, nothing)
===(nothing, nothing)
Base.#sum#542(1, sum, [0.52695 0.88639; 1.0 1.0])
Base._sum([0.52695 0.88639; 1.0 1.0], 1)
Before JuliaLang/julia#24113, Cassette tests pass. After JuliaLang/julia#24113, running the tests gives us this lovely error:
Unreachable reached at 0x11bb7fdcc
signal (4): Illegal instruction: 4
in expression starting at /Users/jarrettrevels/.julia/v0.7/Cassette/test/ExecuteTests.jl:24
_hook at /Users/jarrettrevels/.julia/v0.7/Cassette/test/ExecuteTests.jl:23 [inlined]
hook at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub/execution.jl:43 [inlined]
hook at /Users/jarrettrevels/.julia/v0.7/Cassette/src/workarounds.jl:11 [inlined]
hook at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub/execution.jl:72 [inlined]
hook at /Users/jarrettrevels/.julia/v0.7/Cassette/src/workarounds.jl:14 [inlined]
Overdub at /Users/jarrettrevels/.julia/v0.7/Cassette/src/workarounds.jl:21 [inlined]
Overdub at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub/execution.jl:130
unknown function (ip: 0x11bb7fde9)
jl_call_fptr_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:380 [inlined]
jl_call_method_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:399 [inlined]
jl_apply_generic at /Users/jarrettrevels/data/repos/julia7/src/gf.c:2011
execute at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub/execution.jl:78 [inlined]
execute at /Users/jarrettrevels/.julia/v0.7/Cassette/src/workarounds.jl:18 [inlined]
execute at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub/execution.jl:76 [inlined]
execute at /Users/jarrettrevels/.julia/v0.7/Cassette/src/workarounds.jl:16 [inlined]
Overdub at /Users/jarrettrevels/.julia/v0.7/Cassette/src/workarounds.jl:21 [inlined]
Overdub at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub/execution.jl:11 [inlined]
execute at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub/execution.jl:78 [inlined]
execute at /Users/jarrettrevels/.julia/v0.7/Cassette/src/workarounds.jl:18 [inlined]
execute at /Users/jarrettrevels/.julia/v0.7/Cassette/src/overdub/execution.jl:76 [inlined]
execute at /Users/jarrettrevels/.julia/v0.7/Cassette/src/workarounds.jl:16 [inlined]
Overdub at /Users/jarrettrevels/.julia/v0.7/Cassette/src/workarounds.jl:21
unknown function (ip: 0x11bb7664f)
jl_call_fptr_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:380 [inlined]
jl_call_method_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:399 [inlined]
jl_apply_generic at /Users/jarrettrevels/data/repos/julia7/src/gf.c:2011
do_call at /Users/jarrettrevels/data/repos/julia7/src/interpreter.c:323
eval_body at /Users/jarrettrevels/data/repos/julia7/src/interpreter.c:509
jl_interpret_toplevel_thunk_callback at /Users/jarrettrevels/data/repos/julia7/src/interpreter.c:720
unknown function (ip: 0xfffffffffffffffe)
unknown function (ip: 0x11770ab8f)
unknown function (ip: 0x5)
jl_interpret_toplevel_thunk at /Users/jarrettrevels/data/repos/julia7/src/interpreter.c:729
jl_toplevel_eval_flex at /Users/jarrettrevels/data/repos/julia7/src/toplevel.c:721
jl_eval_module_expr at /Users/jarrettrevels/data/repos/julia7/src/toplevel.c:228
jl_toplevel_eval_flex at /Users/jarrettrevels/data/repos/julia7/src/toplevel.c:566
jl_parse_eval_all at /Users/jarrettrevels/data/repos/julia7/src/ast.c:793
jl_load at /Users/jarrettrevels/data/repos/julia7/src/toplevel.c:759 [inlined]
jl_load_ at /Users/jarrettrevels/data/repos/julia7/src/toplevel.c:766
include at ./boot.jl:279 [inlined]
include_relative at ./loading.jl:509
jl_call_fptr_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:380 [inlined]
jl_call_method_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:399 [inlined]
jl_apply_generic at /Users/jarrettrevels/data/repos/julia7/src/gf.c:2011
include at ./sysimg.jl:15
jl_call_fptr_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:380 [inlined]
jl_call_method_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:399 [inlined]
jl_apply_generic at /Users/jarrettrevels/data/repos/julia7/src/gf.c:2011
include at ./sysimg.jl:54
jl_call_fptr_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:380 [inlined]
jl_call_method_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:399 [inlined]
jl_apply_generic at /Users/jarrettrevels/data/repos/julia7/src/gf.c:2011
do_call at /Users/jarrettrevels/data/repos/julia7/src/interpreter.c:323
eval_body at /Users/jarrettrevels/data/repos/julia7/src/interpreter.c:509
jl_interpret_toplevel_thunk_callback at /Users/jarrettrevels/data/repos/julia7/src/interpreter.c:720
unknown function (ip: 0xfffffffffffffffe)
unknown function (ip: 0x1175fc78f)
unknown function (ip: 0x0)
jl_interpret_toplevel_thunk at /Users/jarrettrevels/data/repos/julia7/src/interpreter.c:729
jl_toplevel_eval_flex at /Users/jarrettrevels/data/repos/julia7/src/toplevel.c:721
jl_parse_eval_all at /Users/jarrettrevels/data/repos/julia7/src/ast.c:793
jl_load at /Users/jarrettrevels/data/repos/julia7/src/toplevel.c:759 [inlined]
jl_load_ at /Users/jarrettrevels/data/repos/julia7/src/toplevel.c:766
include at ./boot.jl:279 [inlined]
include_relative at ./loading.jl:509
jl_call_fptr_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:380 [inlined]
jl_call_method_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:399 [inlined]
jl_apply_generic at /Users/jarrettrevels/data/repos/julia7/src/gf.c:2011
include at ./sysimg.jl:15
jl_call_fptr_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:380 [inlined]
jl_call_method_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:399 [inlined]
jl_apply_generic at /Users/jarrettrevels/data/repos/julia7/src/gf.c:2011
include at ./sysimg.jl:54
jl_call_fptr_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:380 [inlined]
jl_call_method_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:399 [inlined]
jl_apply_generic at /Users/jarrettrevels/data/repos/julia7/src/gf.c:2011
do_call at /Users/jarrettrevels/data/repos/julia7/src/interpreter.c:323
eval_body at /Users/jarrettrevels/data/repos/julia7/src/interpreter.c:509
jl_interpret_toplevel_thunk_callback at /Users/jarrettrevels/data/repos/julia7/src/interpreter.c:720
unknown function (ip: 0xfffffffffffffffe)
unknown function (ip: 0x117adefcf)
unknown function (ip: 0xffffffffffffffff)
jl_interpret_toplevel_thunk at /Users/jarrettrevels/data/repos/julia7/src/interpreter.c:729
jl_toplevel_eval_flex at /Users/jarrettrevels/data/repos/julia7/src/toplevel.c:721
jl_toplevel_eval_in at /Users/jarrettrevels/data/repos/julia7/src/builtins.c:626
eval at ./boot.jl:282 [inlined]
eval at ./repl/REPL.jl:3
jl_call_fptr_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:380 [inlined]
jl_call_method_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:399 [inlined]
jl_apply_generic at /Users/jarrettrevels/data/repos/julia7/src/gf.c:2011
eval_user_input at ./repl/REPL.jl:70
jl_call_fptr_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:380 [inlined]
jl_call_method_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:399 [inlined]
jl_apply_generic at /Users/jarrettrevels/data/repos/julia7/src/gf.c:2011
macro expansion at ./repl/REPL.jl:101 [inlined]
#1 at ./event.jl:95
jl_call_fptr_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:380 [inlined]
jl_call_method_internal at /Users/jarrettrevels/data/repos/julia7/src/./julia_internal.h:399 [inlined]
jl_apply_generic at /Users/jarrettrevels/data/repos/julia7/src/gf.c:2011
jl_apply at /Users/jarrettrevels/data/repos/julia7/src/./julia.h:1474 [inlined]
start_task at /Users/jarrettrevels/data/repos/julia7/src/task.c:268
Allocations: 8347437 (Pool: 8345618; Big: 1819); GC: 18
[1] 30774 illegal hardware instruction julia
x-ref #73
julia> versioninfo()
Julia Version 1.1.0-DEV.695
Commit 9f43871e54* (2018-11-20 05:28 UTC)
Platform Info:
OS: macOS (x86_64-apple-darwin18.2.0)
CPU: Intel(R) Core(TM) i5-8259U CPU @ 2.30GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-6.0.1 (ORCJIT, skylake)
julia> using Cassette, BenchmarkTools
julia> Cassette.@context NoOp;
julia> function loop(x, n)
r = x/x
while n > 0
r *= sin(x)
n -= 1
end
return r
end
julia> loop2(x, n) = Cassette.overdub(NoOp(), loop, x, n);
julia> loop3(x, n) = Cassette.overdub(NoOp(), loop2, x, n);
julia> @btime loop(x, n) setup=(x=2; n=50);
323.500 ns (0 allocations: 0 bytes)
julia> @btime loop2(x, n) setup=(x=2; n=50);
324.163 ns (0 allocations: 0 bytes)
julia> @btime loop3(x, n) setup=(x=2; n=50);
1.325 ms (7757 allocations: 147.05 KiB)
julia> Cassette.@context NoOp
julia> Cassette.overdub(NoOp(), LinearAlgebra.BLAS.gemv!, 'T', 1.0, rand(Float64, 10, 10), rand(Float64, 10), 2.0, rand(Float64, 10))
ERROR: TypeError: in recurse, in ccall: first argument not a pointer or valid constant expression, expected Ptr, got Tuple{Symbol,String}
Stacktrace:
[1] recurse(::Cassette.Context{nametype(NoOp),Nothing,Cassette.NoPass,Nothing,Nothing}, ::typeof(LinearAlgebra.BLAS.gemv!), ::Char, ::Float64, ::Array{Float64,2}, ::Array{Float64,1}, ::Float64, ::Array{Float64,1}) at /Users/kfischer/Projects/julia-doublecheck/usr/share/julia/stdlib/v0.7/LinearAlgebra/src/blas.jl:571
[2] overdub(::Cassette.Context{nametype(NoOp),Nothing,Cassette.NoPass,Nothing,Nothing}, ::Function, ::Vararg{Any,N} where N) at /Users/kfischer/.julia/packages/Cassette/NyRt/src/overdub.jl:23
[3] top-level scope at none:0
I'm assuming this new bug is due to a change in Base (might be a holdover from #16):
julia> using Cassette
julia> Cassette.@context Ctx
julia> function g(x)
iter = 1:length(x)
i = start(iter)
while !(done(iter, i))
println("at iteration: ", i)
println("next value: ", next(iter, i))
println("done?: ", done(iter, i))
_, i = next(iter, i)
end
end
g (generic function with 1 method)
julia> g(rand(3))
at iteration: 1
next value: (1, 2)
done?: false
at iteration: 2
next value: (2, 3)
done?: false
at iteration: 3
next value: (3, 4)
done?: false
julia> Cassette.@execute Ctx g(rand(3))
at iteration: 1
next value: (1, 2)
done?: false
at iteration: 2
next value: (2, 3)
done?: false
at iteration: 3
next value: (3, 4)
done?: false
at iteration: 4
next value: (4, 5)
done?: true
at iteration: 5
next value: (5, 6)
done?: false
at iteration: 6
next value: (6, 7)
done?: false
at iteration: 7
⋮ # goes on for as long as your heart desires
Note that the same thing happens using for
instead of while
; did something change about the way unless/gotos/labels are being handled?
Cassette is getting pretty functional; it'd be nice to have it in metadata, just to make it easier to start building tooling that depends on it.
A neat example usage of Cassette would be to set up an evaluation context that makes all enclosed floating point operations @fastmath
. This could be useful for one that doesn't want to restart their Julia sessions with --math-mode=fast
and don't want to go into all their low-level function definitions and tag them with @fastmath
.
I would try my hand at this but I really don't know what I'm doing.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.