Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions base/methodshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ end

show(io::IO, ms::MethodList) = show_method_table(io, ms)
show(io::IO, ::MIME"text/plain", ms::MethodList) = show_method_table(io, ms)
show(io::IO, mt::Core.MethodTable) = show_method_table(io, MethodList(mt))
show(io::IO, mt::Core.MethodTable) = print(io, mt.module, ".", mt.name, " is a Core.MethodTable with ", length(mt), " methods.")

function inbase(m::Module)
if m == Base
Expand Down Expand Up @@ -470,8 +470,6 @@ function show(io::IO, mime::MIME"text/html", ms::MethodList)
print(io, "</ul>")
end

show(io::IO, mime::MIME"text/html", mt::Core.MethodTable) = show(io, mime, MethodList(mt))

# pretty-printing of AbstractVector{Method}
function show(io::IO, mime::MIME"text/plain", mt::AbstractVector{Method})
last_shown_line_infos = get(io, :last_shown_line_infos, nothing)
Expand Down
62 changes: 19 additions & 43 deletions doc/src/devdocs/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,17 @@ This document will explain how functions, method definitions, and method tables
## Method Tables

Every function in Julia is a generic function. A generic function is conceptually a single function,
but consists of many definitions, or methods. The methods of a generic function are stored in
a method table. Method tables (type `MethodTable`) are associated with `TypeName`s. A `TypeName`
describes a family of parameterized types. For example `Complex{Float32}` and `Complex{Float64}`
share the same `Complex` type name object.

All objects in Julia are potentially callable, because every object has a type, which in turn
has a `TypeName`.
but consists of many definitions, or methods. The methods of a generic function are stored in a
method table. There is one global method table (type `MethodTable`) named `Core.GlobalMethods`. Any
default operation on methods (such as calls) uses that table.

## [Function calls](@id Function-calls)

Given the call `f(x, y)`, the following steps are performed: first, the method cache to use is
accessed as `typeof(f).name.mt`. Second, an argument tuple type is formed, `Tuple{typeof(f), typeof(x), typeof(y)}`.
Note that the type of the function itself is the first element. This is because the type might
have parameters, and so needs to take part in dispatch. This tuple type is looked up in the method
table.
Given the call `f(x, y)`, the following steps are performed: First, a tuple type is formed,
`Tuple{typeof(f), typeof(x), typeof(y)}`. Note that the type of the function itself is the first
element. This is because the function itself participates symmetrically in method lookup with the
other arguments. This tuple type is looked up in the global method table. However, the system can
then cache the results, so these steps can be skipped later for similar lookups.

This dispatch process is performed by `jl_apply_generic`, which takes two arguments: a pointer
to an array of the values `f`, `x`, and `y`, and the number of values (in this case 3).
Expand Down Expand Up @@ -49,15 +45,6 @@ jl_value_t *jl_call(jl_function_t *f, jl_value_t **args, int32_t nargs);

Given the above dispatch process, conceptually all that is needed to add a new method is (1) a
tuple type, and (2) code for the body of the method. `jl_method_def` implements this operation.
`jl_method_table_for` is called to extract the relevant method table from what would be
the type of the first argument. This is much more complicated than the corresponding procedure
during dispatch, since the argument tuple type might be abstract. For example, we can define:

```julia
(::Union{Foo{Int},Foo{Int8}})(x) = 0
```

which works since all possible matching methods would belong to the same method table.

## Creating generic functions

Expand Down Expand Up @@ -94,9 +81,7 @@ end

## Constructors

A constructor call is just a call to a type. The method table for `Type` contains all
constructor definitions. All subtypes of `Type` (`Type`, `UnionAll`, `Union`, and `DataType`)
currently share a method table via special arrangement.
A constructor call is just a call to a type, to a method defined on `Type{T}`.

## Builtins

Expand Down Expand Up @@ -128,18 +113,14 @@ import Markdown
Markdown.parse
```

These are all singleton objects whose types are subtypes of `Builtin`, which is a subtype of
`Function`. Their purpose is to expose entry points in the run time that use the "jlcall" calling
convention:
These are mostly singleton objects all of whose types are subtypes of `Builtin`, which is a
subtype of `Function`. Their purpose is to expose entry points in the run time that use the
"jlcall" calling convention:

```c
jl_value_t *(jl_value_t*, jl_value_t**, uint32_t)
```

The method tables of builtins are empty. Instead, they have a single catch-all method cache entry
(`Tuple{Vararg{Any}}`) whose jlcall fptr points to the correct function. This is kind of a hack
but works reasonably well.

## Keyword arguments

Keyword arguments work by adding methods to the kwcall function. This function
Expand Down Expand Up @@ -228,18 +209,13 @@ sees an argument in the `Function` type hierarchy passed to a slot declared as `
it behaves as if the `@nospecialize` annotation were applied. This heuristic seems to be extremely
effective in practice.

The next issue concerns the structure of method cache hash tables. Empirical studies show that
the vast majority of dynamically-dispatched calls involve one or two arguments. In turn, many
of these cases can be resolved by considering only the first argument. (Aside: proponents of single
dispatch would not be surprised by this at all. However, this argument means "multiple dispatch
is easy to optimize in practice", and that we should therefore use it, *not* "we should use single
dispatch"!) So the method cache uses the type of the first argument as its primary key. Note,
however, that this corresponds to the *second* element of the tuple type for a function call (the
first element being the type of the function itself). Typically, type variation in head position
is extremely low -- indeed, the majority of functions belong to singleton types with no parameters.
However, this is not the case for constructors, where a single method table holds constructors
for every type. Therefore the `Type` method table is special-cased to use the *first* tuple type
element instead of the second.
The next issue concerns the structure of method tables. Empirical studies show that the vast
majority of dynamically-dispatched calls involve one or two arguments. In turn, many of these cases
can be resolved by considering only the first argument. (Aside: proponents of single dispatch would
not be surprised by this at all. However, this argument means "multiple dispatch is easy to optimize
in practice", and that we should therefore use it, *not* "we should use single dispatch"!). So the
method table and cache splits up on the structure based on a left-to-right decision tree so allow
efficient nearest-neighbor searches.

The front end generates type declarations for all closures. Initially, this was implemented by
generating normal type declarations. However, this produced an extremely large number of constructors,
Expand Down
27 changes: 16 additions & 11 deletions doc/src/devdocs/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,12 @@ julia> dump(Array{Int,1}.name)
TypeName
name: Symbol Array
module: Module Core
names: empty SimpleVector
singletonname: Symbol Array
names: SimpleVector
1: Symbol ref
2: Symbol size
atomicfields: Ptr{Nothing}(0x0000000000000000)
constfields: Ptr{Nothing}(0x0000000000000000)
wrapper: UnionAll
var: TypeVar
name: Symbol T
Expand All @@ -188,20 +193,20 @@ TypeName
lb: Union{}
ub: abstract type Any
body: mutable struct Array{T, N} <: DenseArray{T, N}
Typeofwrapper: abstract type Type{Array} <: Any
cache: SimpleVector
...

linearcache: SimpleVector
...

hash: Int64 -7900426068641098781
mt: MethodTable
name: Symbol Array
defs: Nothing nothing
cache: Nothing nothing
module: Module Core
: Int64 0
: Int64 0
hash: Int64 2594190783455944385
backedges: #undef
partial: #undef
max_args: Int32 0
n_uninitialized: Int32 0
flags: UInt8 0x02
cache_entry_count: UInt8 0x00
max_methods: UInt8 0x00
constprop_heuristic: UInt8 0x00
```

In this case, the relevant field is `wrapper`, which holds a reference to the top-level type used
Expand Down
17 changes: 4 additions & 13 deletions stdlib/Serialization/src/Serialization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -469,15 +469,13 @@ end

function serialize(s::AbstractSerializer, mt::Core.MethodTable)
serialize_type(s, typeof(mt))
serialize(s, mt.cache)
serialize(s, mt.name)
serialize(s, mt.module)
nothing
end

function serialize(s::AbstractSerializer, mc::Core.MethodCache)
serialize_type(s, typeof(mc))
serialize(s, mc.name)
serialize(s, mc.module)
nothing
error("cannot serialize MethodCache objects")
end


Expand Down Expand Up @@ -1134,16 +1132,9 @@ function deserialize(s::AbstractSerializer, ::Type{Method})
end

function deserialize(s::AbstractSerializer, ::Type{Core.MethodTable})
mc = deserialize(s)::Core.MethodCache
mc === Core.GlobalMethods.cache && return Core.GlobalMethods
return getglobal(mc.mod, mc.name)::Core.MethodTable
end

function deserialize(s::AbstractSerializer, ::Type{Core.MethodCache})
name = deserialize(s)::Symbol
mod = deserialize(s)::Module
f = Base.unwrap_unionall(getglobal(mod, name))
return (f::Core.MethodTable).cache
return getglobal(mod, name)::Core.MethodTable
end

function deserialize(s::AbstractSerializer, ::Type{Core.MethodInstance})
Expand Down