/
NOTES
96 lines (67 loc) · 3.54 KB
/
NOTES
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
These are descriptions of the current implementation, as best as I can
determine them.
Stack: (last modified Apr 6, 2008)
The implementation uses an emulated stack. Objects are pushed on the stack
and are popped off by primitive operations. When functions are called, the
stack is *reset* and loaded with the arguments to the function.
Function labels: (last modified May 9, 2008)
All function calls are implemented as GOTO's. Each function is thus assigned
a label. However, unfortunately standard C does not support passing GOTO-able
labels as values (it's possible in GCC by using the &&label extension). Thus,
functions are instead represented by longs, and each label is a "case" label
in a switch structure.
When jumping, the calling function assigns the destination in a C-local
variable, pc. Then the calling function GOTOs the top of the switch structure
(cref. jump: label), which dispatches based on the pc value. cref. END_JUMP()
C-macro.
Function objects: (last modified Apr 28, 2008)
Functions are represented by a "closure" type which is simply a flat array
of "obj". The [0] element is a type tag, while the [1] element specifies
the length of the closure.
The array's [2] element is a long. This long is the numeric
representation of the function's "case" label. Note that it is a real C
long, not an Arc fixnum represented as an obj (which happens to be a long).
Succeeding elements of the array are the enclosed variables.
Closure-captured variables are stored in the rest of the array as a flat
representation.
When a closure is created, variables are copied from any parent closure.
Generally, this means that the actual variables are not shared between
functions defined in the same lexical scope. However, mutated local
variables are handled specially; see the section "mutable local variables".
Function calls: (last modified May 9, 2008)
Function calls are started by the BEGIN_JUMP() and END_JUMP() C-macros.
Before entering BEGIN_JUMP(), the function's parameters are pushed on the
stack.
BEGIN_JUMP() then resets the stack; the function's parameters are then copied
from the previous stack top to the new stack. END_JUMP() then gets the label
from the 0'th parameter, sets this to the pc, and jumps to the top of the
switch.
END_JUMP() also performs some checking to determine if the 0'th parameter is
not a function type. Collection types are handled specially in the
END_JUMP() code; END_JUMP() dispatches based on the actual type of the
object.
Mutable Local Variables: (last modified Apr 28, 2008)
Closures in arc2c do not share variable space with other closures, even those
defined in the same lexical scope. This means that a mutation of one closure
does not cause a mutation in another closure, even if they are in the same
scope.
Instead, if a local variable is ever mutated, a "sharedvar" C-struct is used
to represent that variable. These are constructed by %sharedvar primitives,
and are accessed by %sharedvar-write and %sharedvar-read primitives. For
example, the following code transformation is done:
(let x nil
(set reader
(fn () x))
(set writer
(fn (v) (set x v))))
=>
(let x nil
(let shared (%shared-var x)
(set reader
(fn () (%sharedvar-read shared)))
(set writer
(fn (v) (%sharedvar-write shared v)))))
In this way, all assignments to local variables are eliminated.
The "sharedvar" objects are completely invisible to the high-level Arc; they
are simply a singleton container for the mutable local variable. Even so,
they have a type code, solely for the garbage collector.