Engee documentation

Location of Julia objects in memory

Object structure ('jl_value_t`)

The 'jl_value_t` structure is the name of the memory block belonging to the Julia garbage collector and representing the data associated with the Julia object in memory. In the absence of type information, it is just an opaque pointer.

typedef struct jl_value_t* jl_pvalue_t;

Each 'jl_value_t` structure is contained in the jl_typetag_t structure, which contains metadata about the Julia object, such as its type and accessibility to the garbage collector.

typedef struct {
    opaque metadata;
    jl_value_t value;
} jl_typetag_t;

The type of any Julia object is an instance of the final object jl_datatype_t'. To request it, you can use the `jl_typeof() function.

jl_value_t *jl_typeof(jl_value_t *v);

The structure of an object depends on its type. Reflection methods can be used to check it. You can access the field by calling one of the get-field methods.

jl_value_t *jl_get_nth_field_checked(jl_value_t *v, size_t i);
jl_value_t *jl_get_field(jl_value_t *o, char *fld);

If it is known a priori that all field types are pointers, the values can also be retrieved directly as an array access.

jl_value_t *v = value->fieldptr[n];

For example, the packed pointer uint16_t is stored as follows.

struct {
    opaque metadata;
    struct {
        uint16_t data;        // -- 2 bytes
    } jl_value_t;
};

This object is created using 'jl_box_uint16()'. Note that the 'jl_value_t` pointer refers to a piece of data, not the metadata at the top of the structure.

In many cases, the value may be stored unpacked (only data, without metadata, or perhaps not even stored, but simply stored in registers), so it is unsafe to assume that the container address is a unique identifier. To compare two unknown objects for equivalence, use the egal (identity) test (corresponding to the === function in Julia).

int jl_egal(jl_value_t *a, jl_value_t *b);

This optimization should be relatively transparent to the API, as the object will be packaged on demand whenever the jl_value_t pointer is needed.

Note that modification of the 'jl_value_t` pointer in memory is allowed only if the object is mutable. Otherwise, changing the value may disrupt the program and the result will be indeterminate. The mutability property of a value can be requested as shown below.

int jl_is_mutable(jl_value_t *v);

If the object being saved is a jl_value_t pointer, the Julia garbage collector must also be notified.

void jl_gc_wb(jl_value_t *parent, jl_value_t *ptr);

Section of the manual Julia Code Injection is a must-read at this stage, as it covers other packaging and unpacking details of various types, as well as understanding interactions within garbage collection.

Mirror structures for some embedded types https://github.com/JuliaLang/julia/blob/master/src/julia.h [defined in julia.h]. The corresponding global objects jl_datatype_t are created using https://github.com/JuliaLang/julia/blob/master/src/jltypes.c ['jl_init_types` in `jltypes.c'].

Bits of the garbage collector label

The garbage collector uses several bits from the metadata portion of the jl_typetag_t structure to track each object in the system. More information about this algorithm can be found in the implementation comments. https://github.com/JuliaLang/julia/blob/master/src/gc.c [garbage collector in gc.c].

Selecting objects

Most of the new objects are allocated using 'jl_new_structv()'.

jl_value_t *jl_new_struct(jl_datatype_t *type, ...);
jl_value_t *jl_new_structv(jl_datatype_t *type, jl_value_t **args, uint32_t na);

Anyway, the objects 'isbits' can also be built directly from memory.

jl_value_t *jl_new_bits(jl_value_t *bt, void *data)

And some objects have special constructors that must be used instead of the above functions.

Types:

jl_datatype_t *jl_apply_type(jl_datatype_t *tc, jl_tuple_t *params);
jl_datatype_t *jl_apply_array_type(jl_datatype_t *type, size_t dim);

Although these are the most commonly used variants, there are also lower-level constructors that can be found declared in https://github.com/JuliaLang/julia/blob/master/src/julia.h [julia.h]. They are used in jl_init_types() to create the initial types needed to bootstrap the Julia system image.

Tuples:

jl_tuple_t *jl_tuple(size_t n, ...);
jl_tuple_t *jl_tuplev(size_t n, jl_value_t **v);
jl_tuple_t *jl_alloc_tuple(size_t n);

The representation of tuples is unique in the Julia object representation ecosystem. In some cases, the object Base.tuple() can be an array of pointers to objects contained in a tuple equivalent to the following.

typedef struct {
    size_t length;
    jl_value_t *data[length];
} jl_tuple_t;

However, in other cases, the tuple can be converted to an anonymous type. isbits and stored unpacked or not stored at all (if it is not used in the general context as jl_value_t*).

Symbols:

jl_sym_t *jl_symbol(const char *str);

Functions and MethodInstance:

jl_function_t *jl_new_generic_function(jl_sym_t *name);
jl_method_instance_t *jl_new_method_instance(jl_value_t *ast, jl_tuple_t *sparams);

Arrays:

jl_array_t *jl_new_array(jl_value_t *atype, jl_tuple_t *dims);
jl_array_t *jl_alloc_array_1d(jl_value_t *atype, size_t nr);
jl_array_t *jl_alloc_array_nd(jl_value_t *atype, size_t *dims, size_t ndims);

Note that many of them have alternative highlighting functions for various special purposes. The list given here shows the most common use cases. A more complete list can be found by examining https://github.com/JuliaLang/julia/blob/master/src/julia.h [header file julia.h].

In Julia, storage is usually allocated using newstruct() (or newobj() for special types).

jl_value_t *newstruct(jl_value_t *type);
jl_value_t *newobj(jl_value_t *type, size_t nfields);

At the lowest level, memory is allocated by calling the garbage collector (in gc.c), and then marked with a type.

jl_value_t *jl_gc_allocobj(size_t nbytes);
void jl_set_typeof(jl_value_t *v, jl_datatype_t *type);

!!! note "Out of date Warning" The documentation and information about using the jl_gc_allocobj function may be outdated.

Note that all objects are allocated in multiples of 4 bytes and are consistent with the size of the platform pointer. Memory is allocated from the pool for small objects or directly using the malloc() function for large objects.

Singleton Types

Single types have only one instance and no data fields. Individual instances are 0 bytes in size and consist only of their metadata. For example, nothing::Nothing.

See the sections [Single Types](../manual/types.md#man-singleton-types) and [Empty or missing values](../manual/faq.md#Nothingness-and-missing-values).