-
Notifications
You must be signed in to change notification settings - Fork 0
/
NOTES
15 lines (12 loc) · 3.73 KB
/
NOTES
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
### calling convention and variadics
We are currently using a (somewhat undefined) calling convention. When this is more fleshed out it will look like the AMD64 calling convention with the first few arguments placed in registers and the rest pushed to the stack. In order to facilitate this we will make one register a stack pointer and the other a frame pointer. Stack pointer should be obvious but frame pointer will be the value of the stack pointer prior to the call.
This will allow us to support variadics. When a variadic procedure is compiled, it will first have code to build up the argument list via cons-ing, similar to how the interpreter used to work.
X29 is the frame pointer, X30 is the stack pointer, X31 is always set to the integer value of zero.
### Garbage collection
There is currently no garbage collection. Because we use NaNboxing, I do not think that using Arc/Rc would work. To start with a mark-and-sweep garbage collector might work. The main problem is liveness detection. Of course we can easily mark anything in a register, on the stack, or in the environment. The problem is with LoadConst and MakeClosure. These instructions load values into registers but they currently do this by shoving the value into the list of instructions. Without scanning through all program code (a rather difficult and costly problem) there isn't a good way to find these values. One option is to return to the previous method of separating code and constants a bit like how ELF does. Then we can easily just scan the lists of constants. I don't think there would be much overhead to this.
Another problem is determining what type an object is when we perform the sweep phase. One option is to have a separate list for each type of object. I think that a better option is to use the same trick used for NaNboxing to store type information in the upper byte of the pointer. Because Scheme uses lists so heavily it might be worth having a separate list just for lists which we could then scan more often. I believe this would also allow the sweep phase to utilize two threads.
For marking we just need 1 bit for true/false so I am planning to use bit 0 of the pointer for this. Bits [0, 2] should always be 0 as I believe that all of our types are 64-bit aligned.
### User types
My plan for Other types which require a lookup to determine their type is to use a tagged-union. I want to allow user-defined types so I am thinking that this type will also have an Other variant with a generic member. This will probably be very slow to access, so to alleviate this I will also make it possible to make your own Other type.
### NaN-boxing
We use NaN-boxing to represent values. Briefly, this allows us to have fast floating-point arithmetic while being able to store other values. We use the Signally-NaN for other values, meaning that as long as one of the lower 51 bits is set we can do what we want. Of course this relies on 64-bit pointers currently only using 48-bits with sign-extension. This leaves us with 3 bits for a tag or 8 types. There are several techniques for making more types available. The most obvious one is having an `Other` type which requires a lookup to determine it's actual type. We might have several immediate types with require fewer than 48-bits (booleans, 32-bit integers, etc.). These can be given an `Immediate` type and then use some extra bits to disambiguate. Another optimization relies on alignment. It is likely that the pointer types require 8-byte or greater alignment meaning that the lower 3-bits of the pointer will always be 0. Thus we really only need 45-bits for pointers giving us 6-bits for a tag or 64 possible types. I believe that we can also use the sign bit so if an extra bit is needed this can double the amount of types representable through other methods.