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

codegen support for efficient union representations #20593

Merged
merged 9 commits into from
Feb 22, 2017
5 changes: 3 additions & 2 deletions doc/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,13 @@ const PAGES = [
"devdocs/reflection.md",
"Documentation of Julia's Internals" => [
"devdocs/init.md",
"devdocs/eval.md",
"devdocs/ast.md",
"devdocs/types.md",
"devdocs/object.md",
"devdocs/functions.md",
"devdocs/eval.md",
"devdocs/callconv.md",
"devdocs/compiler.md",
"devdocs/functions.md",
"devdocs/cartesian.md",
"devdocs/meta.md",
"devdocs/subarrays.md",
Expand Down
119 changes: 119 additions & 0 deletions doc/src/devdocs/compiler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# High-level Overview of the Native-Code Generation Process


<placeholder>
Copy link
Member

Choose a reason for hiding this comment

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

Are you planning to include something here in this PR or in a subsequent one?

Copy link
Member

Choose a reason for hiding this comment

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

Subsequent one it is 😄

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, I put it there so that nobody could complain that the document was immensely incomplete at describing codegen, and just jumps to explain a few small areas. It almost worked :)



## Representation of Pointers

When emitting code to an object file, pointers will be emitted as relocations.
The deserialization code will ensure any object that pointed to one of these constants
gets recreated and contains the right runtime pointer.

Otherwise, they will be emitted as literal constants.

To emit one of these objects, call `literal_pointer_val`.
It'll handle tracking the Julia value and the LLVM global,
ensuring they are valid both for the current runtime and after deserialization.

When emitted into the object file, these globals are stored as references
in a large `gvals` table. This allows the deserializer to reference them by index,
and implement a custom manual GOT-like mechanism to restore them.

Function pointers are handled similarly.
They are stored as values in a large `fvals` table.
Like globals, this allows the deserializer to reference them by index.

Note that extern functions are handled separately,
with names, via the usual symbol resolution mechanism in the linker.

Note too that ccall functions are also handled separately,
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps code format ccall, i.e. ccall?

via a manual GOT + PLT.
Copy link
Contributor

@tkelman tkelman Feb 21, 2017

Choose a reason for hiding this comment

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

spell out acronyms

Copy link
Member Author

Choose a reason for hiding this comment

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

standard jargon exists for a reason

Copy link
Contributor

Choose a reason for hiding this comment

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

what, to confuse people? this is documentation, very welcome to have it at all, just asking for it to be made slightly clearer

Copy link
Member Author

Choose a reason for hiding this comment

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

because Global Offset Table is not actually clearer, just longer

Copy link
Contributor

@tkelman tkelman Feb 21, 2017

Choose a reason for hiding this comment

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

yes it is clearer - I can look that up and go find out more about that if it's clear that's what's being referred to, unlike GOT on its own without any explanation, where first the reader needs to figure out what the acronym stands for

Copy link
Contributor

Choose a reason for hiding this comment

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

it's completely standard practice to spell acronyms out the first time you use them, then use the acronym from then on if you must http://blog.apastyle.org/apastyle/abbreviations/#Q1

otherwise the term serves no purpose to anyone who doesn't know what you're referring to

Copy link
Member

@ararslan ararslan Feb 22, 2017

Choose a reason for hiding this comment

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

There are also folks like me who don't have a solid background with this stuff; I didn't know that GOT was Global Offset Table, and I still don't know what PLT is. Big +1 for spelling out acronyms, at least the first time they're used. I think that would help a lot.

Copy link
Member

@StefanKarpinski StefanKarpinski Feb 22, 2017

Choose a reason for hiding this comment

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

@vtjnash: In any writing, you should spell out the first usage, and put the abbreviation in parens. From then on in the same document you may refer to it by abbreviation. This is a quite universal standard in writing, and claiming otherwise is simply wrong. If you're feeling extra helpful to your reader, you can also link to the wikipedia article on the topic from the first (spelled out) usage of the term.

Copy link
Member

Choose a reason for hiding this comment

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

That said, it's better to have this documentation and others can fix up the formatting and writing as long as we know what it means. I would not have known what GOT stood for without asking you, however. PLT I happen to know.

Copy link
Member

Choose a reason for hiding this comment

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

GOT is a pretty standard acronym though... Game of Thrones of course.



## Representation of Intermediate Values

Values are passed around in a `jl_cgval_t` struct.
This represents an R-value, and includes enough information to
determine how to assign or pass it somewhere.

They are created via one of the helper constructors, usually:
`mark_julia_type` (for immediate values) and `mark_julia_slot` (for pointers to values).

The function `convert_julia_type` can transform between any two types.
It returns an R-value with `cgval.typ` set to `typ`.
It'll cast the object to the requested representation,
making heap boxes, allocating stack copies, and computing tagged unions as
needed to change the representation.

By contrast `update_julia_type` will change `cgval.typ` to `typ`,
only if it can be done at zero-cost (i.e. without emitting any code).


## Union representation

Inferred union types may be stack allocated via a tagged type representation.

The primitive routines that need to be able to handle tagged unions are:
- mark-type
- load-local
- store-local
- isa
- is
- emit_typeof
- emit_sizeof
- boxed
- unbox
- specialized cc-ret
Copy link
Member

Choose a reason for hiding this comment

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

Would be nice if you could code format these

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm generally reluctant to code-format every word that also appears in code. In part because it is not really standard grammar, and in part because I try to avoid having every other word be bracketed :P

Copy link
Member

Choose a reason for hiding this comment

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

I just mean the things that only refer to functions or other code-specific thingamabobs that aren't part of normal prose. But if you're happy with it as it is, that's fine with me.


Everything else should be possible to handle in inference by using these
primitives to implement union-splitting.

The representation of the tagged-union is as a pair
of `< void* union, byte selector >`.
The selector is fixed-size as `byte & 0x7f`,
and will union-tag the first 126 isbits.
It records the one-based depth-first count into the type-union of the
isbits objects inside. An index of zero indicates that the `union*` is
actually a tagged heap-allocated `jl_value_t*`,
and needs to be treated as normal for a boxed object rather than as a
tagged union.

The high bit of the selector (`byte & 0x80`) can be tested to determine if the
`void*` is actually a heap-allocated (`jl_value_t*`) box,
thus avoiding the cost of re-allocating a box,
while maintaining the ability to efficiently handle union-splitting based on the low bits.

It is guaranteed that `byte & 0x7f` is an exact test for the type,
if the value can be represented by a tag – it will never be marked `byte = 0x80`.
It is not necessary to also test the type-tag when testing `isa`.

The `union*` memory region may be allocated at *any* size.
The only constraint is that it is big enough to contain the data
currently specified by `selector`.
It might not be big enough to contain the union of all types that
could be stored there according to the associated Union type field.
Use appropriate care when copying.


## Specialized Calling Convention Signature Representation

A `jl_returninfo_t` object describes the calling convention details of any callable.

If any of the arguments or return type of a method can be represented unboxed,
and the method is not varargs, it'll be given an optimized calling convention
signature based on its `specTypes` and `rettype` fields.

The general principles are that:

- Primitive types get passed in int/float registers.
- Tuples of VecElement types get passed in vector registers.
- Structs get passed on the stack.
- Return values are handle similarly to arguments,
with a size-cutoff at which they will instead be returned via a hidden sret argument.

The total logic for this is implemented by `get_specsig_function` and `deserves_sret`.

Additionally, if the return type is a union, it may be returned as a pair of values (a pointer and a tag).
If the union values can be stack-allocated, then sufficient space to store them will also be passed as a hidden first argument.
It is up to the callee whether the returned pointer will point to this space, a boxed object, or even other constant memory.
5 changes: 3 additions & 2 deletions doc/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,13 @@
* [Reflection and introspection](@ref)
* Documentation of Julia's Internals
* [Initialization of the Julia runtime](@ref)
* [Eval of Julia code](@ref)
* [Julia ASTs](@ref)
* [More about types](@ref)
* [Memory layout of Julia Objects](@ref)
* [Julia Functions](@ref)
* [Eval of Julia code](@ref)
* [Calling Conventions](@ref)
* [High-level Overview of the Native-Code Generation Process](@ref)
* [Julia Functions](@ref)
* [Base.Cartesian](@ref)
* [Talking to the compiler (the `:meta` mechanism)](@ref)
* [SubArrays](@ref)
Expand Down
2 changes: 1 addition & 1 deletion src/ccall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ static void interpret_symbol_arg(native_sym_arg_t &out, jl_value_t *arg, jl_code
"cglobal: first argument not a pointer or valid constant expression",
ctx);
}
arg1 = remark_julia_type(arg1, (jl_value_t*)jl_voidpointer_type, ctx);
arg1 = update_julia_type(arg1, (jl_value_t*)jl_voidpointer_type, ctx);
jl_ptr = emit_unbox(T_size, arg1, (jl_value_t*)jl_voidpointer_type);
}
else {
Expand Down
Loading