• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Interaction of compiled code and the runtime
2
3## Introduction
4
5During execution compiled code and Panda runtime should interact with each other. This document describes the following aspects interation:
6* Runtime structures
7* Calling convention
8* The structure of compiled code stack frames and stack traversing
9* Transition from the interpeter to compiled code and vise versa
10* Calling the runtime
11* Deoptimization
12* Stack unwinding during exception handling
13
14Documentation of meta information generated by the compiler is located in compiled_method_info.md document.
15
16## Panda runtime (the runtime)
17Panda runtime as a set of functions aimed to execute managed code. The runtime consists of several modules.
18The document refers to the interpreter and the compiler modules.
19
20The interpreter is a part of the runtime aimed to execute bytecode of managed functions. The interpreter is responsible to manage
21hotness counter (see [Structure of `ark::Method`](#structure-of-pandamethod)) of managed functions.
22
23The compiler is aimed to translate managed function's bytecode to native code. The compiler has an interface function
24`ark::CompilerInterface::CompileMethodSync` which starts compilation. When the function gets compiled the compiler
25changes its entrypoint to newly generated code. Next time when the function gets called native code will be executed.
26
27## Calling convention
28Panda runtime and managed code must call functions according to the target calling convention.
29Compiled code of a managed function must accept one extra argument: the pointer to `ark::Method` which describes this function.
30This argument must be the first argument.
31
32Example:
33Consider a function int max(int a, int b).
34When the compiler generates native code for this function for ARM target it must consider that
35the function accepts 3 arguments:
36- a pointer to `ark::Method` in the register R0.
37- `a` in the register R1
38- `b` in the register R2
39
40The function must return the result in the register R0.
41
42### Dynamic calling convention
43For dynamic languages a compiled function accepts all arguments in stack slots.
44Two additional arguments (`ark::Method*` and `uint32_t num_actual_args`) are passed in first two argument registers of the target calling convention.
45
46The result of call is placed according to the target calling convention.
47
48## Structure of `ark::ManagedThread`
49`ark::ManagedThread` has the following fields that compiled code may use:
50
51| Field                | Type                  | Description |
52| ---                  | ----                  | ----------- |
53| sp_flag_             | bool*                 | Safepoint flag. See *Safepoints* in memory_management.md |
54| pending_exception_   | ark::ObjectHeader*  | A pointer to a thrown exception or 0 if there is no exception thrown. |
55| runtime_entrypoints_ | void*[]               | A table of runtime entrypoints (See [Runtime entrypoints](#runtime_entrypoints)). |
56| stack_frame_kind_    | StackFrameKind        | A kind of the current stack frame (compiled code or interpreter stack frame). |
57
58## Access to `ark::ManagedThread` from compiled code
59There is an allocated register for each target architecture to store a pointer to `ark::ManagedThread`. This register is called `thread register` and
60must contains a valid pointer to `ark::ManagedThread` on entry to each compiled function.
61
62## Runtime entrypoints
63Runtime serves compiled code via runtime entrypoints. A runtime entrypoint is a function which conforms to the target calling convention.
64A table of the entrypoints is located in `ark::ManagedThread::runtime_entrypoints_` which could be accessed via `thread register`.
65
66## Structure of `ark::Method`
67`ark::Method` describes a managed function in the runtime.
68This document refers to the following fields of `ark::Method`:
69
70| Field | Description |
71| ----- | ----------- |
72| hotness_counter_ | A hotness counter of the managed function. |
73| compiled_entry_point_ | Function entrypoint. |
74
75### Hotness counter
76The field `hotness_counter_` reflects hotness of a managed function. The interpreter increments it each time the function gets called,
77backward branch is taken and call instruction is handled.
78When the hotness counter gets saturated (reaches the threshold) the interpreter triggers compilation of the function.
79Panda runtime provides a command line option to tune the hotness counter threshold: `--compiler-hotness-threshold`.
80
81### Entrypoint
82Entrypoint is a pointer to native code which can execute the function. This code must conform to the target calling convention and must accept
83one extra argument: a pointer to `ark::Method` ( See [Calling convention](#calling_convention)).
84The managed function could have compiled code or it could be executed by the interpreter.
85In the case the function has compiled code the `compiled_entry_point_` must point to compiled code.
86In the case the function is executed by the interpreter the `compiled_entry_point_` must point to a runtime function `CompiledCodeToInterpreterBridge` which calls the interpreter.
87
88## Stack frame
89A stack frame contains data necessary to execute the function the frame belongs to.
90The runtime can create several kinds of stack frames. But all the frames of managed code must have the structure described in [Compiled code stack frame](#compiled-code-stack-frame).
91
92### Interpreter stack frame
93Interpreter stack frame is decribed by `ark::Frame` class. The class has fields to store virtual registers and a pointer to the previous stack frame.
94All the consecutive interpreter stack frames are organized into a linked list. The field `ark::Frame::prev_` contains a pointer to the previous interpreter (or compiled bridge) frame.
95
96### Compiled code stack frame
97Each compiled function is responsible to reserve stack frame for its purpose and then release it when the function doesn't need it.
98Generaly compiled function builds the stack frame in prolog and releases it in epilog. If a compiled function doesn't require
99the stack frame it can omit its creation.
100When compiled code is executing the `stack pointer` register must point to a valid stack frame (newly created stack frame of stack frame of caller) and
101`frame pointer` register must point to correct place in the frame before the following operations:
102* Managed objects access
103* Safepoint flag access
104* Call of managed functions or runtime entrypoints
105
106Release of the stack frame could be done by restoring values of `stack pointer` and `frame pointer` registers to the value they have at the moment of function entry.
107
108Compiled code stack frames of caller and callee must be continuous in the stack i.e. the callee's stack frame must immediately follow the caller's stack frame.
109
110A compiled code stack frame must have the following structure:
111
112```
113(Stack grows in increasing order: higher slot has lower address)
114-----+----------------------+ <- Stack pointer
115     | Callee parameters    |
116     +----------------------+
117     | Spills               |
118     +----------------------+
119     | Caller saved fp regs |
120  D  +----------------------+
121  A  | Caller saved regs    |
122  T  +----------------------+
123  A  | Callee saved fp regs |
124     +----------------------+
125     | Callee saved regs    |
126     +----------------------+
127     | Locals               |
128-----+----------------------+
129  H  | Properties           |
130  E  +----------------------+
131  A  | ark::Method*       |
132  D  +----------------------+ <- Frame pointer
133  E  | Frame pointer        |
134  R  +----------------------+
135     | Return address       |
136-----+----------------------+
137```
138Stack frame elements:
139- data - arbitraty data necessary for function execution. May be omited.
140- properties - define properties of the frame, f.e. whether it is OSR frame or not.
141- `ark::Method*` - a pointer to `ark::Method` which describes the called function.
142- frame pointer - pointer to the previous frame. The value of `frame pointer` register at the moment of function entry.
143- return address - address to which control will be transfered after the function gets returned.
144
145There are two special registers: `stack pointer` and `frame pointer`.
146`stack pointer` register contains a pointer to the last stack element.
147`frame pointer` register contains a pointer to the place in the stack where the return address is stored.
148
149Panda contains special class for getting cframe layout: `class CFrameLayout`. Everything related to the cframe layout
150should be processed via this class,
151
152## Calling a function from compiled code
153To call a managed function compiled code must resolve it (i.e. retreive a pointer to callee's `ark::Method`),
154prepare arguments in the registers and the stack (if necessary) and jump to callee's entrypoint.
155Resolving of a function could be done by calling the corresponding runtime entrypoint.
156
157Example:
158Calling `int max(int a, int b)` function from compiled code on ARM architecture with arguments `2` and `3`
159could be described by the following pseudocode:
160```
161// tr - thread register
162// r0 contains a pointer to the current `ark::Method`
163// 1st step: resolve `int max(int, int)`
164mov r1, MAX_INT_INT_ID // MAX_INT_INT_ID - identifier of int max(int, int) function
165ldr lr, [tr, #RESOLVE_RUNTIME_ENTRYPOINT_OFFSET]
166blx lr // call resolve(currentMethod, MAX_INT_INT_ID)
167// r0 contains a pointer to `panada::Method` which describes `int max(int, int)` function.
168// 2nd step: prepare arguments and entrypoint to call `int max(int, int)`
169mov r1, #2
170mov r2, #3
171lr = ldr [r0, #entrypoint_offset]
172// 3rd step: call the function
173blx lr // call max('max_method', 2, 3)
174// r0 contains the function result
175```
176
177## Calling a function from compiled code: Bridge function
178The Compiler have an entrypoints table. Each entrypoint contains a link to the Bridge Function.
179The Bridge Functions are auto-generated for each runtime function to be called using the macro assembly.
180The Bridge Function sets up the Boundary Frame and performs the call to the actual runtime function.
181
182To do a runtime call from compiled code the Compiler generates:
183* putting callee saved (if need) and param holding (if any) register values to the stack
184* (callee saved regisers goes to Bridge Function stack frame, caller saved registers goes to the current stack frame)
185* parameter holding registers values setup
186* Bridge Function address load and branch intruction
187* register values restore
188
189The bridge function does:
190* setup the Bridge Function stack frame
191* push the caller saved registers (except of registers holding function parameters) to the caller's stack frame
192* adjust Stack Pointer, and pass execution to the runtime function
193* restore the Stack Pointer and caller saved registers
194
195Bridge Function stack frame:
196```
197--------+------------------------------------------+
198        | Return address                           |
199        +------------------------------------------+
200 HEADER | Frame pointer                            |
201        +------------------------------------------+
202        | COMPILED_CODE_TO_INTERPRETER_BRIDGE flag |
203        +------------------------------------------+
204        | - unused -                               |
205--------+------------------------------------------+
206        |                                          |
207        | Callee saved regs                        |
208        |                                          |
209 DATA   +------------------------------------------+
210        |                                          |
211        | Callee saved fp regs                     |
212        |                                          |
213--------+------------------------------------------+
214        +  16-byte alignment pad to the next frame +
215```
216
217## Transition from the interpreter to compiled code
218When the interpreter handles a call instruction first it should resolve the callee method.
219Depending on the callee's entrypoint there may be different cases.
220If the entrypoint points to `CompiledCodeToInterpreterBridge` then the callee should be executed by the interpreter. In this case the interpreter calls itself directly.
221In other cases the interpreter calls the function `InterpreterToCompiledCodeBridge` passing to it the resolved callee function,
222the call instruction, the interpreter's frame and the pointer to `ark::ManagedThread`.
223
224`InterpreterToCompiledCodeBridge` function does the following:
225* Build a boundary stack frame.
226* Set the pointer to `ark::ManagedThread` to the thread register.
227* Change stack frame kind in `ark::ManagedThread::stack_frame_kind_` to compiled code stack frame.
228* Prepare the arguments according to the target calling convention. The function uses the bytecode instruction (which must be a variant of `call` instruction)
229    and interpreter's frame to retreive the function's arguments.
230* Jump to the callee's entrypoint.
231* After the return save the result to the interpreter stack frame.
232* Change stack frame kind in `ark::ManagedThread::stack_frame_kind_` back to interpreter stack frame.
233* Drop the boundary stack frame.
234
235`InterpreterToCompiledCodeBridge`'s boundary stack frame is necessary to link the interpreter's frame with the compiled code's frame.
236Its structure is depicted below:
237```
238---- +----------------+ <- stack pointer
239b s  | INTERPRETER_   |
240o t  | TO_COMPILED_   |
241u a  | CODE_BRIDGE    |
242n c  +----------------+ <- frame pointer
243d k  | pointer to the |
244a    | interpreter    |
245r f  | frame          |
246y r  |                |
247  a  +----------------+
248  m  | return address |
249  e  |                |
250---- +----------------+
251```
252
253The structure of boundary frame is the same as a stack frame of compiled code.
254Instead of pointer to `ark::Method` the frame contains constant `INTERPRETER_TO_COMPILED_CODE_BRIDGE`.
255Frame pointer points to the previous interpreter frame.
256
257## Transition from compiled code to the interpreter
258If a function should be executed by the interpreter it must have `CompiledCodeToInterpreterBridge` as an entrypoint.
259`CompiledCodeToInterpreterBridge` does the following:
260* Change stack frame kind in `ark::ManagedThread::stack_frame_kind_` to interpreter stack frame.
261* Creates a boundary stack frame which contains room for interpreter frame.
262* Fill in the interpreter frame by the arguments passed to `CompiledCodeToInterpreterBridge` in the registers or via the stack.
263* Call the interpreter.
264* Store the result in registers or in the stack according to the target calling convention.
265* Drop the boundary stack frame.
266* Change stack frame kind in `ark::ManagedThread::stack_frame_kind_` back to compiled code stack frame.
267
268`CompiledCodeToInterpreterBridge`'s boundary stack frame is necessary to link the compiled code's frame with the interpreter's frame.
269Its structure is depicted below:
270```
271---- +----------------+ <-+ stack pointer
272  s  | interpreter's  | -+ `ark::Frame::prev_`
273b t  | frame          |  |
274o a  +----------------+ <+ frame pointer
275u c  | frame pointer  |
276n k  +----------------+
277d    | COMPILED_CODE_ |
278a f  | TO_            |
279r r  | INTERPRETER_   |
280y a  | BRIDGE         |
281  m  +----------------+
282  e  | return address |
283---- +----------------+
284     |     ...        |
285```
286
287The structure of boundary frame is the same as a stack frame of compiled code.
288Instead of a pointer to `ark::Method` the frame contains constant `COMPILED_CODE_TO_INTERPRETER_BRIDGE`.
289Frame pointer points to the previous frame in compiled code stack frame.
290The field `ark::Frame::prev_` must point to the boundary frame pointer.
291
292## Stack traversing
293Stack traversing is performed by the runtime. When the runtime examinates a managed thread's stack the thread mustn't execute any managed code.
294Stack unwinding always starts from the top frame. Its kind could be determined from `ark::ManagedThread::stak_frame_kind_` field. A pointer
295to the top frame could be determined depends on the kind of the top stack frame:
296* The top stack frame is an interpreter stack frame. Address of the interpreter's frame could be retrieved from `ark::ManagedThread::GetCurrentFrame()`.
297* The top stack frame is a compiled code stack frame. `frame pointer` register contains the address of the top stack frame.
298
299Having a pointer to the top stack frame, its kind and structure the runtime can move to the next frame.
300Moving to the next frame is done according to the table below:
301
302| Kind of the current stack frame | How to get a pointer to the next stack frame | Kind of the previous stack frame |
303| ------------------------------- | -------------------------------------------- | -------------------------------- |
304| Interpreter stack frame         | Read `ark::Frame::prev_` field             | Interpreter stack frame or COMPILED_CODE_TO_INTERPRETER boundary frame |
305| INTERPRETER_TO_COMPILED_CODE_BRIDGE boundary stack frame | Read `pointer to the interpreter frame` from the stack | Interpreter stack frame |
306| COMPILED_CODE_TO_INTERPRETER_BRIDGE boundary stack frame | Read `frame pointer` from the stack | Compiled code stack frame |
307| Compiled code stack frame | Read `frame pointer` | Compiled code stack frame or INTERPRETER_TO_COMPILED_CODE_BRIDGE boundary frame|
308
309Thus the runtime can traverse all the managed stack frames moving from one frame to the previous frame and changing frame type
310crossing the boundary frames.
311
312Unwinding of stack frames has specifics.
313* Compiled code could be combined from several managed functions (inlined functions). If the runtime needs to get information about inlined functions
314during handling a compiled code stack frame it uses meta information generated by the compiler (See compiled_method_info.md).
315* Compiled code may save any callee-saved registers on the stack. Before moving to the next stack frame the runtime must restore values of these registers.
316To do that the runtime uses information about callee-saved registers stored on the stack. This information is generated by the compiler (See compiled_method_info.md).
317* Values of virtual registers could be changed during stack unwinding. For example, when GC moves an object, it must update all the references to the object.
318The runtime should provide an internal API for changing values of virtual registers.
319
320Example:
321Consider the following call sequence:
322```
323         calls        calls
324    foo --------> bar ------> baz
325(interpreted)  (compiled)  (interpreted)
326```
327Functions `foo` and `baz` are executed by the interpreter and the function `bar` has compiled code.
328In this situation the stack might look as follow:
329```
330---- +----------------+ <- stack pointer
331E    | native frame   |
332x u  | of             |
333e t  | interpreter    |
334c e  |                |
335---- +----------------+ <--- `ark::ManagedThread::GetCurrentFrame()`
336b    | baz's          | -+
337o s  | interperer     |  |
338u t  | stack frame    |  |
339n a  +----------------+<-+
340d c  | frame pointer  | -+
341a k  +----------------+  |
342r    | COMPILED_CODE_ |  |
343y f  | TO_            |  |
344  r  | INTERPRETER_   |  |
345  a  | BRIDGE         |  |
346  m  +----------------+  |
347  e  | return address |  |
348---- +----------------+  |
349     |      data      |  |
350     +----------------+  |
351 b   | ark::Method* |  |
352 a   +----------------+ <+
353 r   | frame pointer  | -+
354     +----------------+  |
355     | return address |  |
356---- +----------------+  |
357b s  | INTERPRETER_   |  |
358o t  | TO_COMPILED_   |  |
359u a  | CODE_BRIDGE    |  |
360n c  +----------------+ <+
361d k  | pointer to the | -+
362a    | interpreter    |  |
363r f  | frame          |  |
364y r  |                |  |
365  a  +----------------+  |
366  m  | return address |  |
367  e  |                |  |
368---- +----------------+  |
369E    | native frame   |  |
370x u  | of             |  |
371e t  | interpreter    |  |
372c e  |                |  |
373---- +----------------+  |
374     |      ...       |  |
375     +----------------+ <+
376     | foo's          |
377     | interpreter    |
378     | frame          |
379     +----------------+
380     |       ...      |
381```
382
383The runtime determines kind of the top stack frame by reading `ark::ManagedThread::stack_frame_kind_` (the top stack frame kind must be interpreter stack frame).
384`ark::ManagedThread::GetCurrentFrame()` method must return the pointer to `baz`'s interpreter stack frame.
385To go to the previous frame the runtime reads the field `ark::Frame::prev_` which must point to `COMPILED_CODE_TO_INTERPRETER_BRIDGE` boundary stack frame.
386It means that to get `bar`'s stack frame the runtime must read `frame pointer` and the kind of the next frame will be compiled code's frame.
387At this step the runtime has a pointer to `bar`'s compiled code stack frame. To go to the next frame runtime reads `frame pointer` again and gets
388`INTERPRETER_TO_COMPILED_CODE_BRIDGE` boundary stack frame. To reach `foo`'s interpreter stack frame the runtime reads `pointer to the interpreter's frame` field.
389
390## Deoptimization
391There is may be a situation when compiled code cannot continue execution for some reason.
392For such cases compiled code must call `void Deoptimize()` runtime entrypoint to continue execution of the method in the interpreter from the point where compiled code gets stopped.
393The function reconstructs the interpreter stack frame and calls the interpreter.
394When compiled code is combined from several managed functions (inlined functions) `Deoptimize` reconstructs interpreter stack frame and calls the interpreter for each inlined function too.
395
396Details in [deoptimization documentation](deoptimization.md)
397## Throwing an exception
398Throwing an exeption from compiled code is performed by calling a runtime entrypoint `void ThrowException(ark::ObjectHeader* exception)`.
399The function `ThrowException` does the following:
400* Saves all the callee-saved registers to the stack
401* Stores the pointer to the exception object to `ark::ManagedThread::pending_exception_`
402* Unwind compiled code stack frames to find the corresponding exception handler by going from one stack frame to the previous and making checks.
403
404If the corresponding catch handler is found in the current stack frame the runtime jumps to the handler.
405
406If a INTERPRETER_TO_COMPILED_CODE_BRIDGE boundary stack frame is reached the runtime returns to the interpreter letting it to handle the exception.
407Returning to the interpreter is performed as follow:
4081. Determine the return address to the boundary frame. The return address is stored in the following compiled code stack frame.
4092. Set the pointer to the boundary frame into stack pointer, assign the return address determined at the previous step to program counter.
410
411If there is no catch handler in the current frame then the runtime restores values of callee-saved registers and moves to the previous stack frame.
412
413Details of stack travesing are described in [Stack traversing](#stack_traversing)
414
415Finding a catch handler in a compiled code stack frame is performed according meta information generated by the compiler (See compiled_method_info.md).
416
417The interpreter must ignore the returned value if `ark::ManagedThread::pending_exception_` is not 0.
418
419