GithubHelp home page GithubHelp logo

cassette.jl's People

Contributors

aviatesk avatar avik-pal avatar christopher-dg avatar dilumaluthge avatar hyrodium avatar jrevels avatar keno avatar kristofferc avatar maleadt avatar mikeinnes avatar mortenpi avatar mschauer avatar nhdaly avatar oscardssmith avatar oxinabox avatar rdeits avatar roger-luo avatar simeonschaub avatar tkoolen avatar vchuravy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cassette.jl's Issues

Composed broadcast example does not infer

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}

Extra Cassette code doesn't compile away like it's supposed to?

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)
;}

overdubbed method doesn't infer

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

post-JuliaLang/julia#24960 metadata wrapper refactor

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:

  • tighter type constraints for mutable fields (right now it falls back to Any)
  • support for persisted Base Array wrapping (similar to struct wrapping but with dynamic integer indices rather than statically named fields)

Tagged context breaks recursive overdub example

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.

broadcast noop not inferred

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

Function splitting

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?

Use Cases

I've talked to a lot of nice folks recently about the potential Cassette use cases. Here's are the ones I remember:

  • chain-rule + primitive based
    • disciplined convex programming (e.g. Convex.jl)
    • differentiation (e.g. ForwardDiff, ReverseDiff)
    • subdifferentiation (subgradients)
    • interval constraint programming (primitive = inverse, chain rule = set intersection) (e.g. IntervalConstraintProgramming.jl)
  • dynamic analysis/optimization
    • memoization (types/storage/values) (@juan-pablo-vielma has applications for randomized algorithms)
    • serial memory/operation scheduling
    • parallel memory/operation scheduling
    • computation graph rewriting (e.g. CSE)
    • profiling (call counts, memory usage, etc.)
    • debugging
    • fuzzing
    • stochastic arithmetic (numeric fuzzing)
  • coprocessor usage/management (e.g. GPUArrays.jl, CUDANative.jl)

Matmul noop not inferred

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

failure to infer/inline

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?

Fix tag generation

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.

cassette method overloads not respected

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...

@JeffBezanson
@vtjnash

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)

Optimize scalar locality for static graph representation

...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.

Better varargs are actually worse

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

tagging system test errors (normally hidden by compiler's try/catch)

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
⋮

UnionAll inference issues

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}

Typed IR passes

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.

Passes over IR

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.

Null pass overhead

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

tagging system interaction with concurrent load/stores

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]

Assertion failure on 1.0

$ 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

Issues with nested trace

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])

generated `CodeInfo` inlining should happen in Base rather than in Cassette

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.

invalid pcre ccall error message

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

overhead of getfield projection approach

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 structs" that can store metadata alongside normal structs.

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).

clean debug info

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 🙂

Design Decisions

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.

Stuff I've Changed/Realized Since JuliaCon

Primitive-Handling API

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.

Tracing Mechanism

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.

Graph Representations/Traversal

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.

Ideas/Decisions I'm Currently Grappling With

The Intercept Type and Granular Interception Traits

The 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...)".

Contextual Metadata Propagation Semantics

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:

  1. Boxing/unboxing always occurs w.r.t. a specific context, and does not interfere with other contexts.

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...)
  1. Normal boxing/unboxing argument contexts always match the "nesting level" of their respective caller contexts.

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))
  1. Cassette must enforce a consistent orientation for metadata storage w.r.t. context wrapping.

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)))).

  1. The type of an "original"/"uncontextualized" value should be "directly" available for dispatch from any nested context level.

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]);

Weird behavior in the AD POC code

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

bad inferrability is back on recent Julia master

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.

reinstate/redesign argument-local metadata propagation

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.

Another day, another crash

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.

API changes to make it easier to build nested traces

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 ctx, which I'm naively okay with, but have to think on it a bit more to make sure... 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.

add pass type generation

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:

  1. defines the pass type for the user
  2. uses the user-provided behavior for the pass type
  3. overloads Cassette's default overdub intercept definition w.r.t. the pass type (e.g. (f::Overdub{Intercept,...,MyPass})(args...))

cc @SimonDanisch (who helped me discover that the old pass mechanism is now broken)

world age validation for certain generated functions

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:

  1. CodeInfo objects retrieved from the method table contained their valid world age bounds
  2. Generators could return their forward edge set along with the CodeInfo

Const-prop through tuple

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.

Overdubbing kwargs methods

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)

Broken by linear IR change in Base

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

Overhead with nested overdub

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)

Error when handling ccall with tuple as first argument

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

Iteration is now broken

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?

Tag a release

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.

Example suggestion: Hook into compiler to make all math operations `@fastmath`

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.