How to Add a Feature :)
This commit is contained in:
parent
fdfc6606cd
commit
9ceb061ad4
@ -79,13 +79,30 @@ For each variable, its UD chain is a list of each usage in the AST, with the cor
|
|||||||
|
|
||||||
As always, it's not that fuckin simple. Imagine the following pseudocode:
|
As always, it's not that fuckin simple. Imagine the following pseudocode:
|
||||||
|
|
||||||
x = 0
|
x = 0;
|
||||||
loop {
|
loop {
|
||||||
do something with x
|
x = x + 1;
|
||||||
x = x + 1
|
y = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
Despite appearing after in the source, `x = x + 1` is a potential definition for everything in `do something with x`! This means loops require special logic, so that the UD-chains aren't stupid.
|
The UD-chain code knows nothing about loops. It only cares whether something comes before or after, so it'll assume y is not in conflict with x, and they'll end up in the same register. Because of this, the parser must insert a so-called "loop guard", which will turn the AST into the following:
|
||||||
|
|
||||||
|
x = 0;
|
||||||
|
loop {
|
||||||
|
x = x + 1;
|
||||||
|
y = 5;
|
||||||
|
}
|
||||||
|
x;
|
||||||
|
|
||||||
|
That's one problem, but there's another:
|
||||||
|
|
||||||
|
x = 0;
|
||||||
|
loop {
|
||||||
|
do something with x
|
||||||
|
x = x + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Despite appearing after in the source, `x = x + 1` is a potential definition for everything in `do something with x`! This means the UD-chain generator must go through loops twice -- once with the upper definitions, and once with definitions from within the loop.
|
||||||
|
|
||||||
## Coloring
|
## Coloring
|
||||||
|
|
||||||
@ -103,7 +120,7 @@ If spill2stack is used, then CG must fail once so that dumbification can be appl
|
|||||||
|
|
||||||
I skipped forward a bit. In reality, coloring assumes that all registers have equal importance, which is never true. A return value must be in `eax`, the remainder of division must be in `edx`, etc. In 64-bit, the index of an argument determines in which register it may end up.
|
I skipped forward a bit. In reality, coloring assumes that all registers have equal importance, which is never true. A return value must be in `eax`, the remainder of division must be in `edx`, etc. In 64-bit, the index of an argument determines in which register it may end up.
|
||||||
|
|
||||||
The pre-coloring visitor applies said rules to the AST, setting the colors in the VTE. It is completely plausible that a conflict can occur here, too, from two variables having overlapping live ranges and the same color, but it can also be from demanding more than one color from the same variable. In the latter case, the precoloring visitor gives up as soon as its detected. In both cases we do spill2var, not spill2stack, because spilling to the stack doesn't solve the pre-coloring problem.
|
The pre-coloring visitor applies said rules to the AST, setting the colors in the VTE. It is completely plausible that a conflict can occur here, too, from two variables having overlapping live ranges and the same color, but it can also be from demanding more than one color from the same variable. In the latter case, the pre-coloring visitor gives up as soon as its detected. In both cases we do spill2var, not spill2stack, because spilling to the stack doesn't solve the pre-coloring problem.
|
||||||
|
|
||||||
## Callee-saved pass
|
## Callee-saved pass
|
||||||
|
|
||||||
@ -148,3 +165,22 @@ Using the same Fibonacci example as above, this is the result.
|
|||||||
mov eax, ecx
|
mov eax, ecx
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
## Adding a Feature
|
||||||
|
|
||||||
|
When adding a feature, first write it out in Nectar in the ideal dumbified form. Make sure this compiles correctly. Afterward, implement dumbification rules so that code can be written in any fashion. If specific colorings are required, then the pre-coloring and spill2var passes must be updated. The following is an example with multiplication, as this is what I'm adding as of writing.
|
||||||
|
|
||||||
|
Note the way `mul` works on x86. Firstly, one of the operands is the destination, because `mul` is a 2-op instruction. Secondly, the other operand may not be an immediate, because the operand is defined as r/m (register or memory), so if the second operand is a constant, it must be spilled into a variable (`varify` in `dumberdowner.c`). Thirdly, this destination must be the A register, so one of the operands must be pre-colored to A. Fourthly, `mul` clobbers the D register with the high half of the product. In other words, we have an instruction with *two* output registers, which the Nectar AST does not support. But we can't have the register allocator assign anything to D here.
|
||||||
|
|
||||||
|
To account for this, we can have a second assignment statement right next to the multiplication. Because the main multiplication clobbers the source operand, the mulhi assignment must come before the main mul. Putting all this together, this is the canonical way to do `z = x * y` with an x86 target:
|
||||||
|
|
||||||
|
z = x;
|
||||||
|
w = z *^ y;
|
||||||
|
z = z * y;
|
||||||
|
|
||||||
|
Now we must modify the pre-coloring pass to make sure `z` is marked as A and `w` as D. In case such pre-coloring is impossible, the `spill2var` pass must also be modified to spill whatever variables prevent this coloring into another variable (NOT into the stack). If this is the last use of `y`, then it is fine for `y` to be assigned to D.
|
||||||
|
|
||||||
|
Lastly, the codegen pass must recognize the sequence `w = z *^ y; z = z * y;` and emit a single `mul` instruction.
|
||||||
|
|
||||||
|
In `cg.c` is a function called `xop`, which returns an x86 operand string, given a trivially compilable Nectar expression. Because we've guaranteed the other operand may not be a constant, we do not need to check the XOP type, but it's a good idea to insert `assert`s and `abort`s everywhere to prevent hard-to-find bugs.
|
||||||
|
|
||||||
|
Once all that is done and tested, now we can add the following dumbification rules: all binary operations with the operand `AST_BINOP_MUL` or `AST_BINOP_MULHI` must be the whole expression within an assignment statement. If not, extract into a separate assignment & new variable with `varify`. The destination of the assignment, and both operands of the binary operation must be of type `AST_EXPR_VAR`, with their corresponding variables being of type `VARTABLEENTRY_VAR`, not `VARTABLEENTRY_SYMBOL` or `VARTABLEENTRY_TYPE`. If any of those don't apply, `varify` the offenders. Each such assignment have a neighboring, symmetric assignment, so that both A and D are caught by the pre-coloring pass.
|
Loading…
Reference in New Issue
Block a user