Function entry points¶
Each function has multiple entry points. Which entry point is used depends on the context in which the function is called.
As a running example, consider the entry points generated for
.export e_StdList_s++ .export e_StdList_ea++ .descexp e_StdList_d++ e_StdList_n++ e_StdList_l++ 2 0 "++" .a 2 e_StdList_ea++ .o 2 0 e_StdList_l++ push_args 0 1 1 update_a 2 1 create update_a 0 3 pop_a 1 .d 3 0 jmp e_StdList_ea++ .n 2 e_StdList_d++ e_StdList_ea++ .o 1 0 e_StdList_n++ push_node _cycle_in_spine 2 .o 3 0 e_StdList_ea++ jsr_eval 0 .o 3 0 e_StdList_s++ | actual implementation ...
The strict entry point¶
The strict entry point starts has the prefix
other entry points eventually end up here. It contains the actual
implementation of the function, with the assumption that all strict arguments
are in head normal form on the A stack or unboxed on the B stack. Below the
arguments on the A stack is a thunk that can be filled with the result.
When the result of a function call is guaranteed to be needed, the strict entry
point is used directly. The calling function makes sure to evaluate the strict
arguments. This can be seen in the code generated for
Start =  ++ :
buildh _Nil 0 buildh _Nil 0 .d 3 0 jmp e_StdList_s++
Here it is the
Start thunk that is below the arguments on the stack and is
overwritten with the result. In some contexts, an empty
is created to hold the result:
create buildh _Nil 0 buildh _Nil 0 .d 3 0 jsr e_StdList_s++
The evaluate arguments entry point¶
In contexts where it is uncertain that the strict arguments are in head normal
form or unboxed, the evaluate arguments entry point is used. The prefix for
this entry point is
e_StdList_ea++). It uses
evaluate the necessary arguments and may use instructions like
unbox values from the A stack to the B stack. It then moves on to the strict
The node entry point¶
The node entry point has the prefix
e_StdList_n++). It is used
when evaluating a thunk. The descriptor of a thunk points directly to the node
entry point. To evaluate a thunk, all that is needed is to jump into this entry
point with the node itself on the stack.
The thunk arguments are pushed to the stack, and the thunk itself is
_cycle_in_spine. This allows the run time system to
recognize the situation that the evaluation of a thunk depends on its own
result, as in
Start = let xs = [hd xs] in xs.
This brings the stack in the state required for the evaluate arguments entry point.
The lazy entry point¶
The lazy entry point, with prefix
e_StdList_l++) is used for the
implementation of currying. It covers the case that all arguments but one are
in a closure and the final argument is provided. The closure is on top of the
stack; the argument is right below. The lazy entry point creates a new node for
the result and brings the stack in the state required for the evaluate
arguments entry point.
The first word of every node in the heap points to a descriptor. This descriptor is a section in the data segment containing information about the node type which is used in various places:
- In the garbage collector, to determine the arity and node layout.
- In the driver in
_system.abc, to print values correctly.
To be able to check efficiently whether a node is a head normal form or a
thunk, the descriptor of head normal forms is bitwise OR’ed with
leads to the following Assembly code for a
ea4: # evaluate arguments entry point testb $2,(%rcx) # check for head normal form jne e_310 # if so, omit evaluation call *(%rcx) # if not, evaluate with the node entry point e_310: # proceed
Because there are different kinds of nodes, different types of descriptors are generated:
- ADT constructors
- Node entry points for functions
The exact contents of the descriptor varies, but includes such things as:
The arity. This is needed for the garbage collector.
For records with unboxed values, the arity is stored as
(a_size + b_size + 256) | (a_size << 16). For thunks with unboxed values, the arity is stored as
(a_size + b_size) | (b_size << 8).
The name of the constructor, record, or function (in case of a closure). This is needed to print the node.
- A type string, for records. This contains the type of each unboxed value, so that the run time system knows how to print them.
For nodes that can be curried (ADT constructors and function closures) a
curry table is generated. It is best to look at an example, the descriptor
.quad e__StdList__d_A_A+2 .globl e__StdList__d_A_A e__StdList__d_A_A: .word 0 .word 16 .long yet_args_needed_0 .word 1 .word 8 .long e__StdList__l_A_A .word 2 .word 0 .word 0 .word 2 .long m__StdList
e_StdList__d_A_A is the
e_StdList_d++ descriptor (see symbol
names), which is generated with the following
.descexp e_StdList_d++ e_StdList_n++ e_StdList_l++ 2 0 "++"
2 here is the A stack arity and
0 is the B stack arity.
++ is the name,
for printing, and the labels refer to the relevant entry
When a node
(++) is generated, it uses the
d++ descriptor. This descriptor
thus points to
0, the arity.
When currying one argument into the closure (i.e.,
(++) xs), the label at
offset 4 (for this target platform) is used, i.e.,
label is defined in the run time system and will create a closure with one
The closure with one argument has the descriptor
1, which is the arity. Again, to curry one additional argument in
((++) xs) ys), we use the label, here
e__StdList__l_A_A. This is the
lazy entry point, because we are precisely in that
situation: there is a closure that expects one argument, and the argument is
8 are the number of needed arguments, shifted to the
right by 3: 2 and 1, respectively. These are used when currying more than one
argument at the time (e.g.,
(++) xs ys). The run time system checks whether
the number of provided arguments matches the number of needed arguments. If
this is the case, it finds the lazy entry and finds the evaluate arguments
entry point from right above it (this is
.a 2 e_StdList_ea++ directive above the lazy entry point is for). It
moves all arguments from the closure to the stack (in addition to the curried
arguments which are already on the stack), and calls the evaluate arguments
entry point. However, if still more arguments are needed, a new closure is
The last five lines of the descriptor are not part of the curry table.