-
Notifications
You must be signed in to change notification settings - Fork 2
/
abi.txt
118 lines (96 loc) · 2.96 KB
/
abi.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
## Runtime
```c
typedef size_t word;
typedef struct obj {
void (*entrypoint)(void);
word contents[];
} obj;
enum gc_tag { FORWARD, REF, FUN, PAP, RIGID, THUNK, BLACKHOLE };
struct gc_data {
/** Size of the whole object in words.
* If 0, the first word of contents is a `struct info_word` that contains
* the true size
*/
uint32_t size;
/* enum gc_tag */ uint32_t tag;
};
struct info_word {
/** Size of the whole object in words */
uint32_t size;
/** only in heap objects representing rigid terms */
uint32_t var;
};
```
Heap object layout:
```
.dword GC_DATA_SIZE
.dword GC_DATA_TAG
entrypoint:
x86 code...
```
entrypoint contains executable code for this object
(struct gc_data *) (entrypoint - sizeof(struct gc_data)) contains the GC info
table for this object
---
Calling convention/ABI:
rsp is call stack (obviously)
rbx is self, the closure being evaluated
r12 is data stack
r13 is heap pointer
r14 is heap limit
r15 is argc
rdi, rsi are temporary registers
All of these (except the temp registers) are callee-saved under the SysV ABI, so
we're OK to call foreign library functions.
When entering a closure:
- self points to the closure being evaluated
- The data stack contains the (blackholed) thunk to be updated, then argc many
arguments
Operationally, entering `f` pops argc many arguments, evaluates `f args...` to a
value, then returns that value in `self`
→ Notably, it's up to the caller to do the thunk update
Exposed runtime functions:
rt_too_few_args: package up self and the args into a PAP
rt_update_thunk: update the top of the data stack to `REF self`
rt_avoid_adjacent_update_frames:
like rt_update_thunk but also push self to the data stack
rt_minor_gc: do GC!
Built-in entry codes:
Partial application, rt_pap_entry
Rigid term, rt_rigid_entry
(?)
Compiled entry code actions:
0 (Closures only). Argc check
If there are too few arguments, then jump to rt_too_few_args
0 (thunks only). Update frame
If argc == 0:
call rt_avoid_adjacent_update_frames
Else:
push self to data stack
push argc to call stack
set argc := 0
call rest_of_code
pop argc
call rt_update_thunk
jump to self->entrypoint
rest_of_code:
1. Heap check (when TOTAL_ALLOC_SIZE != 0)
Heap -= TOTAL_ALLOC_SIZE
if Heap < Heap limit:
call rt_minor_gc
Heap -= TOTAL_ALLOC_SIZE
(TOTAL_ALLOC_SIZE is less than the nursery, so no need to check again)
2. Perform allocations
temp = Heap
Repeat zero or more times:
*temp = entry code
(repeated) *(temp+8N) = Nth env item for this allocation
push temp to the data stack
temp += size of that allocation
3. Shuffle the data stack and self
Increase data stack size if necessary
Perform parallel move using the temp registers
→ before writing to self, blackhole it if it's a thunk
Decrease data stack size if necessary
Set new value of argc
4. Execute the call! Jump to *self