Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Use Cassette.jl instead of Vinyl #11

Merged
merged 15 commits into from Sep 6, 2018
Merged

WIP: Use Cassette.jl instead of Vinyl #11

merged 15 commits into from Sep 6, 2018

Conversation

pfitzseb
Copy link
Member

No description provided.

@maleadt
Copy link
Contributor

maleadt commented Aug 13, 2018

Some additional fixes, feel free to merge in this PR: sp/cassette...maleadt:pr/11

@pfitzseb pfitzseb mentioned this pull request Aug 29, 2018
@pfitzseb
Copy link
Member Author

Ok, so apart from the dispatch pass everything in the dynamic mode seems to be working. Couple of points:

  1. The return type pass checks for "small unions" and doesn't warn if it encounters them. Not sure if that is a good thing to do.
  2. I have no idea how (and why) the dynamic dispatch pass worked, so would appreciate some help in reviving that.
  3. @MikeInnes Can you enable Travis for this repo?

@MikeInnes
Copy link
Member

  1. Maybe just check for Union{T,Missing/Nothing}? If people complain about other cases we can add something more general.
  2. Dynamic dispatches are signalled by normal call expressions (as opposed to invoke) in code_typed. So it should be as simple as looking for those, unless I'm missing something
  3. on it

src/analysis.jl Outdated
end
end

exprtype(code, x) = typeof(x)
exprtype(code, x::Expr) = x.typ
exprtype(code, x::Expr) = Union{}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be called any more, so perhaps we can just remove it. The same information should be in the SSAValue type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, I meant to remove that. There's a bit of cruft around anyways.

src/analysis.jl Outdated

rebuild(code, x) = x
rebuild(code, x::QuoteNode) = x.value
rebuild(code, x::Expr) = Expr(x.head, rebuild.(code, x.args)...)
rebuild(code, x::SlotNumber) = code.slotnames[x.id]
rebuild(code, x::Expr) = Expr(x.head, rebuild.(Ref(code), x.args))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this still need to be splatted?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, seems that way. I thought there was a Expr(head, args) constructor too.

(isexpr(ex, :(=)) && isexpr(ex.args[2], GlobalRef)) || return
ref = ex.args[2]
isconst(ref.mod, ref.name) ||
ex isa Expr || return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GlobalRefs can be top level as well, so this might be best expressed with prewalk.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in, this will catch statements of the form %5 = foo(Base.pi) but not of the form %4 = Base.pi; %5 = foo(%4)

Copy link
Member Author

@pfitzseb pfitzseb Sep 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, seems to work fine (-ish, no clue why it says x + Main.y instead of x + foo):

julia> function f(x)
           foo = y
           x+foo
       end
f (generic function with 1 method)

julia> @code_typed f(2)
CodeInfo(
2 1%1 = Main.y::Any3%2 = (x + %1)::Any                                                                                                                                                                            │
  └──      return %2                                                                                                                                                                                │
) => Any

julia> @trace f(2)
┌ Warning: uses global variable Main.y
│   method = f(x) in Main at none:2
└ @ Traceur none:2
┌ Warning: foo is assigned as Any
│   method = f(x) in Main at none:2
└ @ Traceur none:2
┌ Warning: dynamic dispatch to x + Main.y
│   method = f(x) in Main at none:2
└ @ Traceur none:3
┌ Warning: returns Any
│   method = f(x) in Main at none:2
└ @ Traceur none:-1
4

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that's because this pass actually runs on the unoptimised code_typed, not the SSA IR of @code_typed.

@pfitzseb
Copy link
Member Author

pfitzseb commented Sep 3, 2018

Okay, so except for the prewalk thing which I'm not entirely sure how to handle I should have taken care of most of your points.

dispatch printing is pretty bad though:

julia> @trace naive_sum([1.0])
┌ Warning: s is assigned as Int64
│   method = naive_sum(xs) in Main at untitled-3e45b0bd2d71ea51bb6331b5aaf260a2:390
└ @ Traceur untitled-3e45b0bd2d71ea51bb6331b5aaf260a2:390
┌ Warning: s is assigned as Float64
│   method = naive_sum(xs) in Main at untitled-3e45b0bd2d71ea51bb6331b5aaf260a2:390
└ @ Traceur untitled-3e45b0bd2d71ea51bb6331b5aaf260a2:392
┌ Warning: dynamic dispatch to (isa)(φ (%15 => 0, %51 => %32), Float64)
│   method = naive_sum(xs) in Main at untitled-3e45b0bd2d71ea51bb6331b5aaf260a2:390
└ @ Traceur untitled-3e45b0bd2d71ea51bb6331b5aaf260a2:392
┌ Warning: dynamic dispatch to (isa)(φ (%15 => 0, %51 => %32), Int64)
│   method = naive_sum(xs) in Main at untitled-3e45b0bd2d71ea51bb6331b5aaf260a2:390
└ @ Traceur untitled-3e45b0bd2d71ea51bb6331b5aaf260a2:392
┌ Warning: returns Union{Float64, Int64}
│   method = naive_sum(xs) in Main at untitled-3e45b0bd2d71ea51bb6331b5aaf260a2:390
└ @ Traceur untitled-3e45b0bd2d71ea51bb6331b5aaf260a2:-1
1.0

because it operates on the optimized code_typed. Not sure how to get back to something sane from there.

@MikeInnes
Copy link
Member

MikeInnes commented Sep 3, 2018 via email

@pfitzseb
Copy link
Member Author

pfitzseb commented Sep 3, 2018

Hm, yeah, but we don't even have the right function there:

julia> function naive_sum(xs)
         s = 0
         for x in xs
           s += x
         end
         return s
       end
naive_sum (generic function with 1 method)

julia> @code_typed naive_sum([1.0])
CodeInfo(
...   
  6 ┄─ %16 = φ (#5 => 0, #15 => %32)::Union{Float64, Int64}                                             │
  │    %17 = φ (#5 => %11, #15 => %46)::Float64                                                         │
  │    %18 = φ (#5 => %12, #15 => %47)::Int64                                                           │
4 │    %19 = (isa)(%16, Float64)::Bool                                                                  │
  └───       goto #8 if not %19                                                                         │
  7 ── %21 = π (%16, Float64)                                                                           │
  │    %22 = (Base.add_float)(%21, %17)::Float64                                                        │╻     +
  └───       goto #11                                                                                   │
  8 ── %24 = (isa)(%16, Int64)::Bool                                                                    │
  └───       goto #10 if not %24                                                                        │
  9 ── %26 = π (%16, Int64)                                                                             │
  │    %27 = (Base.sitofp)(Float64, %26)::Float64                                                       ││╻╷╷╷  promote
  │    %28 = (Base.add_float)(%27, %17)::Float64                                                        ││╻     +
  └───       goto #11                                                                                   │
...
) => Union{Float64, Int64}

julia> @trace naive_sum([1.0])
┌ Warning: s is assigned as Int64
│   method = naive_sum(xs) in Main at none:2
└ @ Traceur none:2
┌ Warning: s is assigned as Float64
│   method = naive_sum(xs) in Main at none:2
└ @ Traceur none:4
┌ Warning: dynamic dispatch to (isa)(φ (%15 => 0, %51 => %32), Float64)
│   method = naive_sum(xs) in Main at none:2
└ @ Traceur none:4
┌ Warning: dynamic dispatch to (isa)(φ (%15 => 0, %51 => %32), Int64)
│   method = naive_sum(xs) in Main at none:2
└ @ Traceur none:4
┌ Warning: returns Union{Float64, Int64}
│   method = naive_sum(xs) in Main at none:2
└ @ Traceur none:-1
1.0

The dynamic dispatch is actually happening in +, at least on the level of the user code.

@MikeInnes
Copy link
Member

Well, there isn't any dynamic dispatch happening in that function, thanks to the new union splitting. I should have mentioned that Expr(:call, ... is used both for dynamic dispatches and built-ins/intrinsics like Base.isa and Base.add_int, which we can probably just ignore in general.

To actually get a dynamic dispatch for testing we probably need to use a global or Any container type.

@pfitzseb
Copy link
Member Author

pfitzseb commented Sep 5, 2018

Ok, the code works fine then:

julia> using Traceur

julia> y = 2; f(x) = x+y
f (generic function with 1 method)

julia> @trace f(2)
┌ Warning: uses global variable Main.y
│   method = f(x) in Main at none:1
└ @ Traceur none:1
┌ Warning: dynamic dispatch to x + Main.y
│   method = f(x) in Main at none:1
└ @ Traceur none:1
┌ Warning: returns Any
│   method = f(x) in Main at none:1
└ @ Traceur none:-1
4

How can I figure out what intrinsics/built-ins I should ignore? Nvm, I think I got something.

@MikeInnes
Copy link
Member

f isa Union{Core.Builtin,Core.IntrinsicFunction} should do the trick.

@pfitzseb
Copy link
Member Author

pfitzseb commented Sep 5, 2018

Yeah, I was confused because we had that exact check in there already (via isprimitive), but gated behind a f isa GlobalRef, which wasn't true for isa.

AFAICT everything works fine now.

src/analysis.jl Outdated
end
for (i, l) in enumerate(code.code)
ind = code.codelocs[i]
ind = clamp(ind, 1, length(code.linetable))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should just set the line to nothing in this case, otherwise it could be misleading

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or -1, which would be in line with how Base handles that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that would be fine, as long as we do a reasonable printing.

src/analysis.jl Outdated
idx += 1
(isexpr(ex, :(=)) && isexpr(ex.args[1], Core.SlotNumber)) || return
typ = code.ssavaluetypes[idx]
if typ isa Core.Compiler.Const
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Core.Compiler.widenconst would acheive the same thing here.

src/analysis.jl Outdated
end
end

# return type

function issmallunion(t)
ts = Base.uniontypes(t)
length(ts) == 1 && isconcretetype(ts) && return true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isconcretetype(ts::Vector)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isconcretetype(first(ts))... :)

src/trace.jl Outdated
@@ -1,3 +1,44 @@
# struct Trace
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we planning to recover this? Seems best to leave a comment explaining why code is there, or delete.

src/trace.jl Outdated
result = overdub(ctx, f, args...)
analyse((a...) -> ctx.warn(Warning(a...)), C)
return result
function Cassette.prehook(ctx::TraceurCtx, f, args...)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously this was (effectively) a posthook; best to keep it that way unless there's a specific reason to change it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reasoning for that? I mean it's (probably) trivial to change, but why is posthook more appropriate here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reasoning is that it tends to give warnings in the "right order", in the sense that type inference issues tend to propagate up the stack rather than down. e.g. in f(x) = ...; g(x), f using a global variable won't affect the inferability of g, but g doing so will likely break f as well; so printing in post order gives you the root cause first followed by knock-on effects.

(The order to analysis passes is also designed to fit that rule, though it's obviously heuristic).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, makes sense.

@pfitzseb
Copy link
Member Author

pfitzseb commented Sep 5, 2018

Ok, I think I have resolved all of the above points.

@MikeInnes
Copy link
Member

Ok this looks good. Awesome work @pfitzseb.

@MikeInnes MikeInnes merged commit bd94628 into master Sep 6, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants