Compare commits
36 Commits
cdcc1f6d3d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f406b2a032 | ||
|
|
7a8b14308b | ||
|
|
fee8ea5cb3 | ||
|
|
2aa64f4a37 | ||
|
|
8c164a3b09 | ||
|
|
8c4754b563 | ||
|
|
9d975eeceb | ||
|
|
ecab77f9e9 | ||
|
|
132cedff09 | ||
|
|
f978b66662 | ||
|
|
692ed4d21c | ||
|
|
c04c1a97c9 | ||
|
|
104ba53b33 | ||
|
|
2d56f124a9 | ||
|
|
fc8d65f893 | ||
|
|
2049826870 | ||
|
|
fc16c077d9 | ||
|
|
8fc5521601 | ||
|
|
dfbadfecb9 | ||
|
|
b21ce51435 | ||
|
|
2f67a6f109 | ||
|
|
1177462bda | ||
|
|
246f7a3b71 | ||
|
|
2188448b19 | ||
|
|
d46b104d5c | ||
|
|
07e3604a8c | ||
|
|
fb75434cba | ||
|
|
7569420fe0 | ||
|
|
fb6fd76d0b | ||
|
|
e80f6643dc | ||
|
|
8d8d1cf067 | ||
|
|
1708faf14d | ||
|
|
14ab1f432d | ||
|
|
f03e8517df | ||
|
|
273e6d1058 | ||
|
|
251d24fb30 |
118
DOCUMENTATION.md
118
DOCUMENTATION.md
@@ -4,55 +4,68 @@
|
|||||||
|
|
||||||
When writing a program, I usually make the most primitive and smallest code I can that does the job. If it turns out I miscalculated the complexity, or I must add some feature that isn't compatible with the codebase, I'll obviously have to refactor it. Still, I've been programming this way for probably my entire life.
|
When writing a program, I usually make the most primitive and smallest code I can that does the job. If it turns out I miscalculated the complexity, or I must add some feature that isn't compatible with the codebase, I'll obviously have to refactor it. Still, I've been programming this way for probably my entire life.
|
||||||
|
|
||||||
That being said, if you know this compiler took since 2019 to get to its current state, you will correctly guess that I DO NOT KNOW WHAT I AM DOING. Compiler literature and online discussion is abstract to the point where it is not useful for real-world archs. When it gets specific, it's often too simplistic. It's common to say instruction selection should happen before register allocation, but how can you know which instructions to emit when some of them only work with specific registers? People say spilling causes more loads and stores, but that's false; x86 has memory operands! Imagine how long it took me to realize real-world IRs are not at all generic, and are actually quite close to their target architectures. As a result, much of what you see in the source is me basically banging rocks. There's definitely better ways to do the things I show here, but I figured it's better to have at least some resource on how a "real" compiler works.
|
That being said, if you know this compiler took since 2019 to get to its current state, you will correctly guess that I DO NOT KNOW WHAT I AM DOING. Compiler literature and online discussion is abstract to the point where it is not useful for real-world archs. When it gets specific, it's often too simplistic. It's common to say instruction selection should happen before register allocation, but how can you know which instructions to emit when some of them only work with specific registers? People say spilling causes load and store instructions, but that's false -- x86 has memory operands! Imagine how long it took me to realize real-world IRs are not at all generic, and are actually quite close to their target architectures. As a result, much of what you see in the source is me basically banging rocks. There's definitely better ways to do the things I show here, but I figured it's better to have at least some resource on how a "real" compiler works.
|
||||||
|
|
||||||
The core idea behind the compiler is to progressively iterate through the AST, turning it into a more primitive form step by step. Once this primitivization ends, the code generator is given the code in a form it will understand. Doing it this way is necessary because machine code itself is primitive, and instructions typically have 0-3 operands. Thanks to both this, and Nectar itself being highly low-level, the need for an IR disappears. On the other hand, making sure the AST is in a correct state between steps is the prime source of bugs.
|
The core idea behind the compiler is to progressively iterate through the AST, turning it into a more primitive form step by step. After this process, the code generator is given the code in a form it will understand. Doing it this way is necessary because machine code itself is primitive, and instructions typically have 0-3 operands. Thanks to both this, and Nectar itself being highly low-level, the need for an IR disappears.
|
||||||
|
|
||||||
Currently the compiler is designed with only i386+ processors in mind. I intend to add support for i286- and other exotic processors, but I honestly don't see it happening ever, especially if this remains a solo project. More RISC architectures with regular register files will be easier to add support for, but they're also the kind for which the advantages of this programming language aren't worth the squeeze.
|
But make no mistake: this approach does not simplify the compiler! At best this moves the complexity to the normalization. Along each step you must make sure the AST is in a valid state. Almost every bug will be silent, and will manifest as misbehavior or malformed results.
|
||||||
|
|
||||||
|
Currently the compiler is designed with only i386+ processors in mind. I intend to add support for i286- and other exotic processors, but I honestly don't see it happening ever, especially if this remains a solo project. RISCier architectures with regular register files will be easier to add support for, but as they're likely designed with C in mind, the advantages of this programming language aren't worth the squeeze.
|
||||||
|
|
||||||
## AST structure
|
## AST structure
|
||||||
|
|
||||||
Starting with a Nectar source file, the compiler begins with the two common passes: lexing and parsing. The skimming in-between exploits Nectar's syntax quirks, and may jump back and forth multiple times to find all declared symbols and types in advance. This is necessary to avoid having to forward declare items. At the end, parsing returns what is called an AST in the source.
|
Starting with a Nectar source file, the compiler begins with the common passes: lexing, skimming and parsing. Skimming exploits quirks in Nectar's syntax, and may jump back and forth multiple times to find all symbols and types, as an automatic forward declaration. At the end, parsing returns what is called an AST in the source.
|
||||||
|
|
||||||
An AST node may not be shared by multiple parent nodes. Also, the internal Nectar AST does not have scaling for pointer arithmetic; all pointers behave as `u8*`. This is the first of many simplifications.
|
An AST node may not be shared by multiple parent nodes. Also, the internal Nectar AST does not have scaling for pointer arithmetic; all pointers behave as `u8*`. This is the first of many simplifications.
|
||||||
|
|
||||||
Each block of code is called a "chunk", likely a term I took from Lua. Chunks may contain one another; the least deep one within a function is called the top-level chunk (very important). Top-level chunks may contain other top-level chunks, because user-defined functions are within the "global scope", which is considered a function in itself. After all, nothing stops you from directly inserting instructions in the `.text` section of an executable, without attaching it to a label.
|
Each block of code between `{` and `}` is called a "chunk", a term from Lua. Chunks may contain one another; the highest one within a function is called the top-level chunk (TLC). During parsing of a chunk, definitions of vars/symbols are collected into the relevant `Scope`. After a chunk is parsed, all local variables in the chunk's `Scope` are copied to the TLC's list of local variables, for subsequent analysis.
|
||||||
|
|
||||||
During parsing, a tree of maps, `Scope`, is used to handle scopes for variables, types, symbols and constant expressions. Its entries are of type `ScopeItem` (often called VTE in the source for historical reasons). Shadowing of scope items is allowed, like in Nectar itself.
|
However, top-level chunks may contain other top-level chunks, because user-defined functions are within the "global scope", which is considered a function in itself. After all, nothing stops you from directly inserting instructions in the `.text` section of an executable, without attaching it to a label.
|
||||||
|
|
||||||
The top-level chunk keeps a list of variables within its `ASTChunk` structure. After a chunk is finished parsing, all local variables in the current `Scope` are added to its top-level chunk's variable list. Names may conflict, but at this point they're no longer important. Also worth mentioning is that this flat list contains `ScopeItem` structs, even though `Scope`s are now irrelevant. Said items are all of type `SCOPEITEM_VAR`; the rest are ignored because they're not subject to coloring.
|
There's enough passes to justify a generic way to invoke the visitor pattern on the AST. Because passes may do many different things to the AST, including modify it, the definition of a generic visitor is very broad. Most functionality is unused by each pass, but all of it is needed.
|
||||||
|
|
||||||
There's enough types of passes to push us to have a generic way to invoke the visitor pattern on the AST. Because passes may do many different things to the AST, including modify it, the definition of a generic visitor is very broad. Most functionality is unused by each pass, but all of it is needed.
|
|
||||||
|
|
||||||
void generic_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *tlc, void *ud, GenericVisitorHandler preHandler, GenericVisitorHandler postHandler);
|
void generic_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *tlc, void *ud, GenericVisitorHandler preHandler, GenericVisitorHandler postHandler);
|
||||||
|
|
||||||
`*nptr` is the actual node that is currently being visited. It is behind an additional indirection, because the node may be replaced by another.
|
`*nptr` is the actual node that is currently being visited. It is behind an additional indirection, because the node may be replaced by another.
|
||||||
|
|
||||||
If the current node is within a statement (most are), `stmt` is equal to that statement. `stmtPrev` is the previous statement. This is necessary for patching in the linked list of statements within a chunk during modification passes. If there is no previous statement, then the head pointer of the singly-linked list must be patched through the `chu` node. The `tlc` is the top-level chunk, which may be equal to `chu`.
|
If the current node is within a statement (most are), `stmt` is equal to that statement. `stmtPrev` is the previous statement. This is necessary in case a pass generates new statements, patching in the linked list of statements. If there is no previous statement, then the head pointer of the list must be patched through the `chu` node. `tlc` is the top-level chunk, which may be equal to `chu`.
|
||||||
|
|
||||||
A handler may be called before or after delving deeper into the tree (hence the pre and post handlers). Most passes use the prehandler, but type checking will be better with a posthandler, since we want type checks to happen bottom to top.
|
A handler may be called before (pre) or after (post) delving deeper into the tree. Most passes use the prehandler, but type checking will be better with a posthandler, since we want type checks to happen bottom to top.
|
||||||
|
|
||||||
## Desegmentation
|
## Desegmentation
|
||||||
|
|
||||||
Pointers in Nectar might not be regular integers. The basic ones (called near) are, but we also have far pointers to support x86 segmentation. Internally these are record types with two fields: a `u16` segment and a `T*` near pointer.
|
Pointers in Nectar might not be regular integers. The basic (near) ones are, but we also have far pointers to support x86 segmentation. Internally these are record types with two fields: a `u16` segment and a `T*` near pointer.
|
||||||
|
|
||||||
|
`ast_segmented_dereference` takes each instance of a far pointer dereference and converts it to a segment load & near pointer dereference. For example, with a `u8 @far* x;`, `y = *x` becomes:
|
||||||
|
|
||||||
|
$segtemp_0 = x.segment;
|
||||||
|
y = *x.offset;
|
||||||
|
|
||||||
|
`$segtemp_0` is colored to `ds`. Other segment registers exist in x86; they're unsupported for now.
|
||||||
|
|
||||||
|
There's also huge pointers, but they're basically far pointers + some QoL.
|
||||||
|
|
||||||
## SRoA
|
## SRoA
|
||||||
|
|
||||||
## Pre-dumbification
|
A record variable that needn't be stored in memory should be split into the fields that compose it. This can help produce faster machine code. SRoA is handled by `src/ast/sroa.c`.
|
||||||
|
|
||||||
Before dumbification we need to make sure the code at least matches the semantics of the x86 architecture.
|
The decision to split a record depends on two factors: the record size and whether the record is ever referenced with `&`. A superfluous `&` is harmful for SRoA and should be removed by prior optimization passes, but this is unimplemented.
|
||||||
|
|
||||||
|
## Pre-normalization
|
||||||
|
|
||||||
|
We make some one-time changes to the AST that normalization itself shouldn't take care of.
|
||||||
|
|
||||||
For one thing, arguments are not magically loaded with the correct values. The `pre_dumb_visitor` pass inserts assignment statements at the beginning of the function to load the values from the stack, as per the C ABI.
|
For one thing, arguments are not magically loaded with the correct values. The `pre_dumb_visitor` pass inserts assignment statements at the beginning of the function to load the values from the stack, as per the C ABI.
|
||||||
|
|
||||||
Then we have structures which, of course, x86 doesn't support. There must be no by-value use of a structure anywhere. The `decompose_symbol_record_field_access` pass decomposes all references to a structure's field to pointer-like accesses. For example, `a.b` becomes `*((&a + x) as B*)`, where `B` is the type of `a.b`, and `x` is the memory offset of the field `a.b`. The same pattern is specially recognized by the code generator for outputting an x86 address mode like `[eax + x]`.
|
Then we have structures which, of course, x86 doesn't support. There must be no by-value use of a structure anywhere. The `decompose_symbol_record_field_access` pass decomposes all references to a structure's field to pointer-like accesses. For example, `a.b` becomes `*((&a + x) as B*)`, where `B` is the type of `a.b`, and `x` is the memory offset of the field `a.b`. The same pattern is specially recognized by the code generator for outputting an x86 address mode like `[eax + x]`.
|
||||||
|
|
||||||
Afterward, local structs must be either spilled to global memory or to the stack. They may also be decomposed in some cases, but that is currently unsupported. This is done by `spill2stack_visitor`, with `ast_spill_to_stack` as the public interface. Since spilling causes the stack frame of the function to grow, all references to the stack must be updated accordingly. This is also done by `spill2stack_visitor`, and in such a way so as to not require more dumbification.
|
Afterward, local structs must be either spilled to global memory or to the stack. This is done by `ast_spill_to_stack`. Since spilling causes the stack frame of the function to grow, all references to the stack must be updated accordingly. This is also done by `spill2stack_visitor`, and in such a way so as to not require more normalization.
|
||||||
|
|
||||||
Now a deeper structure access like `a.b.c` (`*((&(*((&a + x) as B*)) + y) as C*)`) is not recognized by the code generator. We must rely on optimizations passes that are guaranteed to happen: `*&p` and `&*p` should become `p`, `p as A* as B*` should become `p as B*` and `(p + x) as A* + y` should become `(p + (x + y)) as A*`. These are done by `denoop_visitor`.
|
Now a deeper structure access like `a.b.c` (`*((&(*((&a + x) as B*)) + y) as C*)`) is not recognized by the code generator. We must rely on optimizations passes that are guaranteed to happen: `*&p` and `&*p` should become `p`, `p as A* as B*` should become `p as B*` and `(p + x) as A* + y` should become `(p + (x + y)) as A*`. These are done by `ast_denoop`.
|
||||||
|
|
||||||
## Dumbification
|
## Normalization
|
||||||
|
|
||||||
The idea of turning the AST progressively primitive is called "dumbification" in the source. The most simple example would be the following:
|
The idea of turning the AST progressively primitive is called "normalization" in the source. The most simple example would be the following:
|
||||||
|
|
||||||
a = -b
|
a = -b
|
||||||
|
|
||||||
@@ -61,13 +74,13 @@ which should become
|
|||||||
a = b
|
a = b
|
||||||
a = -a
|
a = -a
|
||||||
|
|
||||||
Because the `neg` instruction on x86 is single-operand. If targeting an arch like MIPS, this specific dumbification would not be used, because one can use the 3-operand `subu` with the zero register.
|
Because the `neg` instruction on x86 is single-operand. If targeting an arch like MIPS, this specific normalization would not be used, because one can use the 3-operand `subu` with the zero register.
|
||||||
|
|
||||||
Another rule is to extract function arguments and place them into local variables, but *only* if they do not form an x86 operand (for example `5` is ok because `push 5` exists).
|
Another rule is to extract function arguments and place them into local variables, but *only* if they do not form an x86 operand (for example `5` is ok because `push 5` exists).
|
||||||
|
|
||||||
Dumbification must be repeated until there are no more changes. The dumbification part of the source is responsible for making sure the resulting AST is "trivially compilable" to the machine code. For example, `a = a + b` is trivially compilable, because we have the `add reg, reg` instruction. What is trivially compilable depends on which registers are used in the end (a variable colored as `edi`, `esi` or `ebp` cannot be used for 8-bit stores/loads). These details are not taken into account by dumbification.
|
Normalization must be repeated until there are no more changes. The normalization part of the source is responsible for making sure the resulting AST is "trivially compilable" to the machine code. For example, `a = a + b` is trivially compilable, because we have the `add reg, reg` instruction. What is trivially compilable depends on which registers are used in the end (a variable colored as `edi`, `esi` or `ebp` cannot be used for 8-bit stores/loads). These details are not taken into account by normalization.
|
||||||
|
|
||||||
Putting all of this together, here is an example of nctref's dumbification of the following Fibonacci implementation, as of writing. Here is the main Nectar source code:
|
Putting all of this together, here is an example of nctref's normalization of the following Fibonacci implementation, as of writing. Here is the main Nectar source code:
|
||||||
|
|
||||||
fibonacci: u32(u32 n) -> {
|
fibonacci: u32(u32 n) -> {
|
||||||
if(n <= 1) {
|
if(n <= 1) {
|
||||||
@@ -95,8 +108,6 @@ And the processed AST output by the compiler:
|
|||||||
|
|
||||||
`@stack` is an internal variable that points to the beginning of the current stack frame.
|
`@stack` is an internal variable that points to the beginning of the current stack frame.
|
||||||
|
|
||||||
NOTE: Later someone called this normalization, which is a much less stupid word than dumbification, and I'm shocked I never thought of it myself. There's also canonicalization...
|
|
||||||
|
|
||||||
## Use-def chain
|
## Use-def chain
|
||||||
|
|
||||||
***WARNING: THIS ENTIRE SECTION HAS BECOME OUTDATED***
|
***WARNING: THIS ENTIRE SECTION HAS BECOME OUTDATED***
|
||||||
@@ -189,19 +200,17 @@ The actual coloring algorithm used is Welsh-Powell, which sorts the VTEs/vertice
|
|||||||
|
|
||||||
If there's more colors than there are physical registers, then we have a conflict, and must spill. `spill2stack` transforms every use of a local variable (`ASTExprVar` where its VTE is of type `SCOPEITEM_VAR`) into the form `@stack + n`.
|
If there's more colors than there are physical registers, then we have a conflict, and must spill. `spill2stack` transforms every use of a local variable (`ASTExprVar` where its VTE is of type `SCOPEITEM_VAR`) into the form `@stack + n`.
|
||||||
|
|
||||||
If spill2stack is used, then CG must fail once so that dumbification can be applied again.
|
If spill2stack is used, then CG must fail once so that normalization can be applied again.
|
||||||
|
|
||||||
## Pre-coloring
|
## Pre-coloring
|
||||||
|
|
||||||
NOTE: `spill2var` turned out to be pushing the problem a step back rather than solving it. Because it is known in advance what must be pre-colored, any such expressions are immediately placed in their own variable by another pass. If the assignment turns out to have been redundant, the register allocator should coaslesce the moves.
|
NOTE: `spill2var` turned out to be pushing the problem a step back rather than solving it. Because it is known in advance what must be pre-colored, any such expressions are immediately placed in their own variable by another pass. If the assignment turns out to have been redundant, the register allocator should coaslesce the moves.
|
||||||
|
|
||||||
TODO: preclassing.
|
Pre-coloring is a necessary evil. It is *very* common in x86 that an instruction's operand is some fixed register we cannot change. But even worse: pre-coloring cannot be handled by
|
||||||
|
|
||||||
## Callee-saved pass
|
## Callee-saved pass
|
||||||
|
|
||||||
If a function uses a callee-saved register, these must be stored and loaded at the correct times. This is done by modifying the AST in a special pass.
|
If a function uses a callee-saved register, these must be stored and loaded at the correct times. This is done by `callee_saved` in `src/x86/cg.c`. If the hardware resources used by any variable overlaps with those of a callee-saved register, said register is marked. Afterward, these registers are passed to the code generator.
|
||||||
|
|
||||||
Of the four currently used registers, only `ebx` is callee-saved. A random variable colored `ebx` is chosen, and is saved to/loaded from the stack. The rule is written such that dumbification isn't necessary, unlike spill2stack.
|
|
||||||
|
|
||||||
## Code generation
|
## Code generation
|
||||||
|
|
||||||
@@ -287,7 +296,7 @@ This splits the pointer type into three subtypes:
|
|||||||
|
|
||||||
By default, pointers in Nectar are just integers, i.e. near, limiting a Nectar program to only 64kB. The parameter `mem` sets all unspecified pointers in a Nectar source file to huge pointers.
|
By default, pointers in Nectar are just integers, i.e. near, limiting a Nectar program to only 64kB. The parameter `mem` sets all unspecified pointers in a Nectar source file to huge pointers.
|
||||||
|
|
||||||
Far and huge pointers have to be dumbified. Let us have the following:
|
Far and huge pointers have to be normalized. Let us have the following:
|
||||||
|
|
||||||
u8 @far* a;
|
u8 @far* a;
|
||||||
|
|
||||||
@@ -314,16 +323,11 @@ This example benefits from scalar replacement:
|
|||||||
|
|
||||||
a_offset = a_offset + 1;
|
a_offset = a_offset + 1;
|
||||||
|
|
||||||
If `a` were `u8 @huge*`, we must account for overflow and the last statement would instead become:
|
|
||||||
|
|
||||||
a.offset = a.offset + 1;
|
|
||||||
if(a.offset == 0) {
|
|
||||||
a.segment = a.segment + 4096;
|
|
||||||
}
|
|
||||||
|
|
||||||
## Other problems with this approach (1)
|
## Other problems with this approach (1)
|
||||||
|
|
||||||
Short-circuit evaluation is when the evaluation of an expression is guaranteed to stop once the output is already known. For example, if in `A || B` `A` is already truthy, then `B` is not evaluated. This is not an optimization, but an important semantical detail, as evaluation of the operands may have side-effects.
|
In the past, a function in the AST was essentially a tree of blocks, following the "structured programming" of Nectar source code. This was a poor choice for multiple reasons.
|
||||||
|
|
||||||
|
Short-circuit evaluation is when the evaluation of an expression is guaranteed to stop once the output is already known. For example, if in `A || B` `A` is already truthy, then `B` is not evaluated. This is not an optimization, but an important semantical detail.
|
||||||
|
|
||||||
Let us write `if(x == 1 || y == 1) { do stuff; }` in x86:
|
Let us write `if(x == 1 || y == 1) { do stuff; }` in x86:
|
||||||
|
|
||||||
@@ -335,9 +339,9 @@ Let us write `if(x == 1 || y == 1) { do stuff; }` in x86:
|
|||||||
; do stuff
|
; do stuff
|
||||||
.L2:
|
.L2:
|
||||||
|
|
||||||
Note that the two jump instructions are basically goto statements. As the Nectar IR is defined without gotos, it is practically impossible for the compiler to output the neat code shown above. You could insert special logic for this case, but in general it'll fail.
|
Note that the two jump instructions are basically goto statements. With a tree of blocks, this kind of logic isn't feasible.
|
||||||
|
|
||||||
Even worse, the dumbification pass will try to move the condition into a variable and dumbify it further, creating the following:
|
Even worse, normalization will try to move the condition into a variable and normalize it further, creating the following:
|
||||||
|
|
||||||
u1 z = x == 1;
|
u1 z = x == 1;
|
||||||
u1 w = y == 1;
|
u1 w = y == 1;
|
||||||
@@ -348,35 +352,11 @@ Even worse, the dumbification pass will try to move the condition into a variabl
|
|||||||
|
|
||||||
And now we need 2 new registers for no reason..
|
And now we need 2 new registers for no reason..
|
||||||
|
|
||||||
Lack of gotos also makes function inlining impossible, as returns also become gotos (!!).
|
What else? Did you know `return` statements are gotos when a function is inlined, too? In other words, no function inlining.
|
||||||
|
|
||||||
In conclusion, what? Should a good IR actually be 100% flat and have nothing but jumps? Can this be solved by modelling the code as a graph of basic blocks? I don't know, but for now I have given up on short-circuit evaluation, and I do not actually support neither `||` nor `&&`.
|
|
||||||
|
|
||||||
## Other problems with this approach (2)
|
|
||||||
|
|
||||||
The `denoop_visitor` pass is incredibly important in normalizing the AST to something other passes will accept. Here's one case I found when trying to implement a statically allocated list class:
|
|
||||||
|
|
||||||
T* data = &((*this).data[0]);
|
|
||||||
|
|
||||||
It seems innocent enough, but it actually becomes:
|
|
||||||
|
|
||||||
T* data = &*(&*((&*this + 4) as T[4]*) + 0);
|
|
||||||
|
|
||||||
As of writing, `denoop_visitor` had produced this:
|
|
||||||
|
|
||||||
T* data = (this + 4) as T*;
|
|
||||||
|
|
||||||
The code generator failed to accept this, because the `as T*` cast meant that it could not match any pattern. The dumbifier also failed to decompose this to `data = this; data = data + 4;` for the same reason.
|
|
||||||
|
|
||||||
What was my solution? IGNORE ALL POINTER CASTS! As I wrote above, the Nectar AST does not support pointer arithmetic like that of C. By this point, all complex types should have already been converted into integers. Therefore, it does not even matter.
|
|
||||||
|
|
||||||
By adding the rule (`x as A*` -> `x` if x is of a pointer type), we obtain the following after denooping:
|
|
||||||
|
|
||||||
T* data = this + 4;
|
|
||||||
|
|
||||||
## Adding a Feature
|
## 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.
|
When adding a feature, first write it out in Nectar in the ideal normalized form. Make sure this compiles correctly. Afterward, implement normalization 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 (yes, I'm aware `imul` exists). Firstly, one of the operands is the destination, because `mul` is a 2-op instruction. Secondly, the other operand cannot be an immediate, because it is defined as r/m (register or memory), so if the second operand is a constant, it must be split into a variable (`varify` in `dumberdowner.c`). Thirdly, the 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.
|
Note the way `mul` works on x86 (yes, I'm aware `imul` exists). Firstly, one of the operands is the destination, because `mul` is a 2-op instruction. Secondly, the other operand cannot be an immediate, because it is defined as r/m (register or memory), so if the second operand is a constant, it must be split into a variable (`varify` in `dumberdowner.c`). Thirdly, the 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.
|
||||||
|
|
||||||
@@ -386,22 +366,22 @@ To account for this, we can have a second assignment statement right next to the
|
|||||||
w = z *^ y;
|
w = z *^ y;
|
||||||
z = z * y;
|
z = z * y;
|
||||||
|
|
||||||
But this is without pre-coloring. We want precolored nodes to live as little as possible, because separately solving pre-coloring collisions whilst also keeping the code dumbified *and* not horrible turned out to be practically impossible (spill2var).
|
But this is without pre-coloring. We want precolored nodes to live as little as possible, because separately solving pre-coloring collisions whilst also keeping the code normalized *and* not horrible turned out to be practically impossible (spill2var).
|
||||||
|
|
||||||
k = x;
|
k = x;
|
||||||
w = k *^ y;
|
w = k *^ y;
|
||||||
k = k * y;
|
k = k * y;
|
||||||
z = k;
|
z = k;
|
||||||
|
|
||||||
Where `k` and `w` are the VTEs that must live and die immediately. The dumbifier checks if `w` and `k` are already precolored to not repeat forever, but it would be better to check the UD-chain.
|
Where `k` and `w` are the VTEs that must live and die immediately. The normalizer checks if `w` and `k` are already precolored to not loop forever, but it would be better to check the UD-chain.
|
||||||
|
|
||||||
Lastly, the codegen pass must recognize the above sequence as a multiplication and emit a single `mul` instruction.
|
Lastly, the codegen pass must recognize the above sequence as a multiplication 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.
|
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 `SCOPEITEM_VAR`, not `SCOPEITEM_SYMBOL`, `SCOPEITEM_TYPE` nor `SCOPEITEM_CEXPR`. 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.
|
Once all that is done and tested, now we can add the following normalization 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 `SCOPEITEM_VAR`, not `SCOPEITEM_SYMBOL`, `SCOPEITEM_TYPE` nor `SCOPEITEM_CEXPR`. 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.
|
||||||
|
|
||||||
A common bug when writing a dumbification rule is ending up with one that is always successful. If this happens, the compiler will become stuck endlessly dumbifying, which is nonsense. It would be nice if you could formally prove that won't happen. Another common bug is not realizing the order in which dumbification rules are applied matters :).
|
A common bug when writing a normalization rule is ending up with one that is always successful. If this happens, the compiler will become stuck endlessly normalizing, which is nonsense. It would be nice if you could formally prove that won't happen. Another common bug is not realizing the order in which normalization rules are applied matters :).
|
||||||
|
|
||||||
You know, I really regret writing this in C.
|
You know, I really regret writing this in C.
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* ListDLC: Dynamic, Linear growth, C-managed
|
* ListDLC: Dynamic, Linear growth, C-managed
|
||||||
*/
|
*/
|
||||||
|
|
||||||
extern u8*(u8*, u32) realloc;
|
extern u8*(u8*, ugpr) realloc;
|
||||||
|
|
||||||
record ListDLC[T, S; growth] {
|
record ListDLC[T, S; growth] {
|
||||||
S capacity;
|
S capacity;
|
||||||
@@ -31,7 +31,7 @@ ListDLC_remove: [T, S; growth]u0(ListDLC[T, S; growth]* this, S index) -> {
|
|||||||
|
|
||||||
ListDLC_add: [T, S; growth]u0(ListDLC[T, S; growth]* this, T value) -> {
|
ListDLC_add: [T, S; growth]u0(ListDLC[T, S; growth]* this, T value) -> {
|
||||||
if((*this).size == (*this).capacity) {
|
if((*this).size == (*this).capacity) {
|
||||||
u32 newcap = (*this).capacity + growth;
|
S newcap = (*this).capacity + growth;
|
||||||
(*this).capacity = newcap;
|
(*this).capacity = newcap;
|
||||||
(*this).data = realloc((*this).data, newcap * @sizeof T);
|
(*this).data = realloc((*this).data, newcap * @sizeof T);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ record ListDLU[T, S; growth] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ListDLU_remove: [T, S; growth]u0(ListDLU[T, S; growth]* this, S index) -> {
|
ListDLU_remove: [T, S; growth]u0(ListDLU[T, S; growth]* this, S index) -> {
|
||||||
T* data0 = &((*((*this).data))[index]);
|
T* data0 = &(*(*this).data)[index];
|
||||||
T* data1 = data0 + @sizeof T;
|
T* data1 = data0 + @sizeof T;
|
||||||
S sz = (*this).size;
|
S sz = (*this).size;
|
||||||
(*this).size = (*this).size - 1;
|
(*this).size = (*this).size - 1;
|
||||||
@@ -33,7 +33,7 @@ ListDLU_add: [T, S; growth]u0(ListDLU[T, S; growth]* this, Alloc *alloc, T value
|
|||||||
if((*this).size == (*this).capacity) {
|
if((*this).size == (*this).capacity) {
|
||||||
u32 newcap = (*this).capacity + growth;
|
u32 newcap = (*this).capacity + growth;
|
||||||
(*this).capacity = newcap;
|
(*this).capacity = newcap;
|
||||||
(*this).data = ((*alloc).realloc)((*alloc).userdata, (*this).data, newcap * @sizeof T);
|
(*this).data = (*alloc).realloc((*alloc).userdata, (*this).data, newcap * @sizeof T);
|
||||||
}
|
}
|
||||||
|
|
||||||
(*((*this).data))[(*this).size] = value;
|
(*((*this).data))[(*this).size] = value;
|
||||||
|
|||||||
132
examples/MapDQCaLDhS.nct
Normal file
132
examples/MapDQCaLDhS.nct
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* MapDQCOaLDhS: Dynamically-allocated, Quadratic growth, C allocator, Interleaved KV values, Open-addressing, Linear probing, Dynamic hash, Flag tombstones
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern u8*(u8*, ugpr) calloc;
|
||||||
|
|
||||||
|
record KVPair[K, V] {
|
||||||
|
K key;
|
||||||
|
V value;
|
||||||
|
}
|
||||||
|
|
||||||
|
record MapDQCOaLDhS[K, V, S] {
|
||||||
|
S(K*)* hash;
|
||||||
|
|
||||||
|
S capacity;
|
||||||
|
KVPair[K, V][?]* data;
|
||||||
|
u8[?]* occupied;
|
||||||
|
}
|
||||||
|
|
||||||
|
MapDQCOaLDhS_try_add: [K, V, S]u1(MapDQCOaLDhS[K, V, S]* this, K* key, V* value) -> {
|
||||||
|
if(this.capacity == 0) {
|
||||||
|
this.capacity = 64;
|
||||||
|
this.data = calloc(this.capacity, @sizeof KVPair[K, V]);
|
||||||
|
this.occupied = calloc(this.capacity, @sizeof((*this.occupied)[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
S capacity = this.capacity;
|
||||||
|
|
||||||
|
S start = this.hash(key);
|
||||||
|
start = start & (capacity - 1);
|
||||||
|
|
||||||
|
S index = start;
|
||||||
|
loop {
|
||||||
|
KVPair[K, V]* pair = &(*this.data)[index];
|
||||||
|
if(pair.key == *key || (*this.occupied)[index] == 0) {
|
||||||
|
pair.key = *key;
|
||||||
|
pair.value = *value;
|
||||||
|
(*this.occupied)[index] = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
index = (index + 1) & (capacity - 1);
|
||||||
|
if(index == start) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MapDQCOaLDhS_expand: [K, V, S]u1(MapDQCOaLDhS[K, V, S]* this) -> {
|
||||||
|
S capacity = this.capacity;
|
||||||
|
KVPair[K, V][?]* old_data = this.data;
|
||||||
|
u8[?]* old_occupied = this.occupied;
|
||||||
|
|
||||||
|
S new_capacity = capacity * 2;
|
||||||
|
this.capacity = new_capacity;
|
||||||
|
this.data = calloc(new_capacity, @sizeof KVPair[K, V]);
|
||||||
|
this.occupied = calloc(new_capacity, @sizeof((*this.occupied)[0]));
|
||||||
|
|
||||||
|
S i = 0;
|
||||||
|
loop {
|
||||||
|
if(i == capacity) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if((*old_occupied)[i] != 0) {
|
||||||
|
KVPair[K, V]* pair = &((*old_data)[i]);
|
||||||
|
MapDQCOaLDhS_try_add[K, V, S](this, &pair.key, &pair.value);
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
MapDQCOaLDhS_add: [K, V, S]u1(MapDQCOaLDhS[K, V, S]* this, K* key, V* value) -> {
|
||||||
|
loop {
|
||||||
|
if(MapDQCOaLDhS_try_add[K, V, S](this, key, value) != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if(MapDQCOaLDhS_expand[K, V, S](this) == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MapDQCOaLDhS_get: [K, V, S]V*(MapDQCOaLDhS[K, V, S]* this, K* key) -> {
|
||||||
|
S capacity = this.capacity;
|
||||||
|
|
||||||
|
if(capacity == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
S start = this.hash(key);
|
||||||
|
start = start & (capacity - 1);
|
||||||
|
|
||||||
|
S index = start;
|
||||||
|
loop {
|
||||||
|
KVPair[K, V]* pair = &((*this.data)[index]);
|
||||||
|
if(pair.key == *key) {
|
||||||
|
if((*this.occupied)[index] == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return &pair.value;
|
||||||
|
}
|
||||||
|
index = (index + 1) & (capacity - 1);
|
||||||
|
if(index == start) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
zero_hash: ugpr(ugpr* val) -> {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
@instantiate MapDQCOaLDhS_try_add[ugpr, ugpr, ugpr];
|
||||||
|
@instantiate MapDQCOaLDhS_expand[ugpr, ugpr, ugpr];
|
||||||
|
@instantiate MapDQCOaLDhS_add[ugpr, ugpr, ugpr];
|
||||||
|
@instantiate MapDQCOaLDhS_get[ugpr, ugpr, ugpr];
|
||||||
|
|
||||||
|
main: u0() -> {
|
||||||
|
map.hash = &zero_hash;
|
||||||
|
|
||||||
|
ugpr test_key = 5;
|
||||||
|
MapDQCOaLDhS_get[ugpr, ugpr, ugpr](&map, &test_key);
|
||||||
|
MapDQCOaLDhS_add[ugpr, ugpr, ugpr](&map, &test_key, &test_value);
|
||||||
|
MapDQCOaLDhS_get[ugpr, ugpr, ugpr](&map, &test_key);
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {}
|
||||||
|
|
||||||
|
@section(".data");
|
||||||
|
MapDQCOaLDhS[ugpr, ugpr, ugpr; 32] map:;
|
||||||
|
ugpr test_value: 10;
|
||||||
77
examples/MapSOaLDhS.nct
Normal file
77
examples/MapSOaLDhS.nct
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* SIOaLpDhFt: Statically-allocated, Interleaved KV values, Open-addressing, Linear probing, Dynamic hash, Flag tombstones
|
||||||
|
*/
|
||||||
|
|
||||||
|
record KVPair[K, V] {
|
||||||
|
K key;
|
||||||
|
V value;
|
||||||
|
}
|
||||||
|
|
||||||
|
record MapSIOaLpDhFt[K, V, S; capacity] {
|
||||||
|
S(K*)* hash;
|
||||||
|
KVPair[capacity] data;
|
||||||
|
u8[capacity] occupied;
|
||||||
|
}
|
||||||
|
|
||||||
|
MapSIOaLpDhFt_add: [K, V, S; capacity]u1(MapSIOaLpDhFt[K, V, S; capacity]* this, K* key, V* value) -> {
|
||||||
|
S start = this.hash(key);
|
||||||
|
start = start & (capacity - 1);
|
||||||
|
|
||||||
|
S index = start;
|
||||||
|
loop {
|
||||||
|
KVPair[K, V]* pair = &this.data[index];
|
||||||
|
if(pair.key == *key || this.occupied[index] == 0) {
|
||||||
|
pair.key = *key;
|
||||||
|
pair.value = *value;
|
||||||
|
this.occupied[index] = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
index = (index + 1) & (capacity - 1);
|
||||||
|
if(index == start) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MapSIOaLpDhFt_get: [K, V, S; capacity]V*(MapSIOaLpDhFt[K, V, S; capacity]* this, K* key) -> {
|
||||||
|
S start = this.hash(key);
|
||||||
|
start = start & (capacity - 1);
|
||||||
|
|
||||||
|
S index = start;
|
||||||
|
loop {
|
||||||
|
KVPair[K, V]* pair = &this.data[index];
|
||||||
|
if(pair.key == *key) {
|
||||||
|
if(this.occupied[index] == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return &pair.value;
|
||||||
|
}
|
||||||
|
index = (index + 1) & (capacity - 1);
|
||||||
|
if(index == start) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
zero_hash: ugpr(ugpr* val) -> {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
@instantiate MapSIOaLpDhFt_add[ugpr, ugpr, ugpr; 32];
|
||||||
|
@instantiate MapSIOaLpDhFt_get[ugpr, ugpr, ugpr; 32];
|
||||||
|
|
||||||
|
main: u0() -> {
|
||||||
|
map.hash = &zero_hash;
|
||||||
|
|
||||||
|
MapSIOaLpDhFt_get[ugpr, ugpr, ugpr; 32](&map, &test_key);
|
||||||
|
MapSIOaLpDhFt_add[ugpr, ugpr, ugpr; 32](&map, &test_key, &test_value);
|
||||||
|
MapSIOaLpDhFt_get[ugpr, ugpr, ugpr; 32](&map, &test_key);
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {}
|
||||||
|
|
||||||
|
@section(".data");
|
||||||
|
MapSIOaLpDhFt[ugpr, ugpr, ugpr; 32] map:;
|
||||||
|
|
||||||
|
ugpr test_value: 10;
|
||||||
|
ugpr test_key: 5;
|
||||||
@@ -2,16 +2,16 @@ use ListDLC;
|
|||||||
|
|
||||||
@section(".text");
|
@section(".text");
|
||||||
|
|
||||||
@instantiate ListDLC_remove[u32, u32; 9];
|
@instantiate ListDLC_remove[u16, u16; 9];
|
||||||
@instantiate ListDLC_add[u32, u32; 9];
|
@instantiate ListDLC_add[u16, u16; 9];
|
||||||
|
|
||||||
main: u0() -> {
|
main: u0() -> {
|
||||||
ListDLC[u32, u32; 9] list;
|
ListDLC[u16, u16; 9] list;
|
||||||
ListDLC_add[u32, u32; 9](&list, 1234);
|
ListDLC_add[u16, u16; 9](&list, 1234);
|
||||||
ListDLC_add[u32, u32; 9](&list, 4321);
|
ListDLC_add[u16, u16; 9](&list, 4321);
|
||||||
ListDLC_add[u32, u32; 9](&list, 7777);
|
ListDLC_add[u16, u16; 9](&list, 7777);
|
||||||
ListDLC_add[u32, u32; 9](&list, 6969);
|
ListDLC_add[u16, u16; 9](&list, 6969);
|
||||||
ListDLC_remove[u32, u32; 9](&list, 1);
|
ListDLC_remove[u16, u16; 9](&list, 1);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
fibonacci: u32(u32 n) -> {
|
fibonacci: u16(u16 n) -> {
|
||||||
if(n <= 1) {
|
if(n <= 1) {
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,14 +49,22 @@ void generic_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *tlc, v
|
|||||||
} else if(n->nodeKind == AST_STMT_EXPR) {
|
} else if(n->nodeKind == AST_STMT_EXPR) {
|
||||||
generic_visitor(&n->stmtExpr.expr, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
generic_visitor(&n->stmtExpr.expr, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||||
} else if(n->nodeKind == AST_STMT_EXT_ORG) {
|
} else if(n->nodeKind == AST_STMT_EXT_ORG) {
|
||||||
} else if(n->nodeKind == AST_STMT_EXT_SECTION) {
|
} else if(n->nodeKind == AST_SECTION) {
|
||||||
|
generic_visitor(&n->section.tlc, stmt, stmtPrev, n->section.tlc, n->section.tlc, ud, preHandler, postHandler);
|
||||||
|
if(n->section.next) {
|
||||||
|
generic_visitor(&n->section.next, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||||
|
}
|
||||||
} else if(n->nodeKind == AST_STMT_RETURN) {
|
} else if(n->nodeKind == AST_STMT_RETURN) {
|
||||||
if(n->stmtReturn.val) {
|
if(n->stmtReturn.val) {
|
||||||
generic_visitor(&n->stmtReturn.val, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
generic_visitor(&n->stmtReturn.val, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||||
}
|
}
|
||||||
} else if(n->nodeKind == AST_EXPR_BINARY_OP) {
|
} else if(n->nodeKind == AST_EXPR_BINARY_OP) {
|
||||||
generic_visitor(&n->exprBinOp.operands[0], stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
if(n->exprBinOp.operands[0]) {
|
||||||
generic_visitor(&n->exprBinOp.operands[1], stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
generic_visitor(&n->exprBinOp.operands[0], stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||||
|
}
|
||||||
|
if(n->exprBinOp.operands[1]) {
|
||||||
|
generic_visitor(&n->exprBinOp.operands[1], stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||||
|
}
|
||||||
} else if(n->nodeKind == AST_EXPR_CALL) {
|
} else if(n->nodeKind == AST_EXPR_CALL) {
|
||||||
generic_visitor(&n->exprCall.what, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
generic_visitor(&n->exprCall.what, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||||
|
|
||||||
@@ -92,6 +100,7 @@ void generic_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *tlc, v
|
|||||||
generic_visitor(&n->stmtJump.condition, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
generic_visitor(&n->stmtJump.condition, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||||
}
|
}
|
||||||
} else if(n->nodeKind == AST_STMT_LABEL) {
|
} else if(n->nodeKind == AST_STMT_LABEL) {
|
||||||
|
} else if(n->nodeKind == AST_EXPR_NULL) {
|
||||||
} else {
|
} else {
|
||||||
stahp_node(n, "generic_visitor: unhandled %s", AST_KIND_STR[n->nodeKind]);
|
stahp_node(n, "generic_visitor: unhandled %s", AST_KIND_STR[n->nodeKind]);
|
||||||
}
|
}
|
||||||
@@ -100,6 +109,9 @@ void generic_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *tlc, v
|
|||||||
}
|
}
|
||||||
|
|
||||||
int ast_expression_equal(AST *a, AST *b) {
|
int ast_expression_equal(AST *a, AST *b) {
|
||||||
|
if(!a && !b) return 1;
|
||||||
|
if(a == b) return 1;
|
||||||
|
|
||||||
if(a->nodeKind != b->nodeKind) return 0;
|
if(a->nodeKind != b->nodeKind) return 0;
|
||||||
|
|
||||||
if(a->nodeKind == AST_EXPR_PRIMITIVE) {
|
if(a->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||||
@@ -177,6 +189,11 @@ static void *memdup(void *a, size_t len) {
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WARNING: Just because you deep copy an AST node, does not mean
|
||||||
|
* ast_expression_equal will return true! This matters for example with
|
||||||
|
* function calls (a function call is not equal to itself).
|
||||||
|
*/
|
||||||
AST *ast_deep_copy(AST *src) {
|
AST *ast_deep_copy(AST *src) {
|
||||||
if(src->nodeKind == AST_EXPR_VAR) {
|
if(src->nodeKind == AST_EXPR_VAR) {
|
||||||
return memdup(src, sizeof(ASTExprVar));
|
return memdup(src, sizeof(ASTExprVar));
|
||||||
@@ -232,17 +249,25 @@ AST *ast_cast_expr(AST *what, Type *to) {
|
|||||||
ASTExprPrimitive *ret = calloc(1, sizeof(*ret));
|
ASTExprPrimitive *ret = calloc(1, sizeof(*ret));
|
||||||
ret->nodeKind = AST_EXPR_PRIMITIVE;
|
ret->nodeKind = AST_EXPR_PRIMITIVE;
|
||||||
ret->type = to;
|
ret->type = to;
|
||||||
memcpy(&ret->val, what->exprStrLit.data, sizeof(ret->val));
|
ret->val = 0;
|
||||||
|
memcpy(&ret->val, what->exprStrLit.data, what->exprStrLit.length);
|
||||||
return (AST*) ret;
|
return (AST*) ret;
|
||||||
} else abort();
|
} else abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool copy = false;
|
||||||
|
|
||||||
// Make sure an unparametrized generic int parameter hasn't sneaked its way in
|
// Make sure an unparametrized generic int parameter hasn't sneaked its way in
|
||||||
while(what->nodeKind == AST_EXPR_VAR && what->exprVar.thing->kind == SCOPEITEM_CEXPR && what->exprVar.thing->data.cexpr.concrete) {
|
while(what->nodeKind == AST_EXPR_VAR && what->exprVar.thing->kind == SCOPEITEM_CEXPR && what->exprVar.thing->data.cexpr.concrete) {
|
||||||
what = what->exprVar.thing->data.cexpr.concrete;
|
what = what->exprVar.thing->data.cexpr.concrete;
|
||||||
|
copy = true;
|
||||||
}
|
}
|
||||||
assert(!(what->nodeKind == AST_EXPR_VAR && what->exprVar.thing->kind == SCOPEITEM_CEXPR));
|
assert(!(what->nodeKind == AST_EXPR_VAR && what->exprVar.thing->kind == SCOPEITEM_CEXPR));
|
||||||
|
|
||||||
|
if(copy) {
|
||||||
|
what = ast_deep_copy(what);
|
||||||
|
}
|
||||||
|
|
||||||
if(type_equal(what->expression.type, to)) return what;
|
if(type_equal(what->expression.type, to)) return what;
|
||||||
|
|
||||||
if(!type_is_castable(what->expression.type, to)) {
|
if(!type_is_castable(what->expression.type, to)) {
|
||||||
@@ -273,3 +298,55 @@ AST *ast_cast_expr(AST *what, Type *to) {
|
|||||||
fail:
|
fail:
|
||||||
stahp_node(what, "Cannot cast type %s into %s", type_to_string(what->expression.type), type_to_string(to));
|
stahp_node(what, "Cannot cast type %s into %s", type_to_string(what->expression.type), type_to_string(to));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ReferencesStackState {
|
||||||
|
bool yes;
|
||||||
|
};
|
||||||
|
static void references_stack_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||||
|
AST *n = *nptr;
|
||||||
|
if(n->nodeKind == AST_EXPR_STACK_POINTER) {
|
||||||
|
((struct ReferencesStackState*) ud)->yes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool ast_references_stack(AST *a) {
|
||||||
|
struct ReferencesStackState state = {};
|
||||||
|
generic_visitor(&a, NULL, NULL, NULL, NULL, &state, references_stack_visitor, NULL);
|
||||||
|
return state.yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IsScopeItemReferenced {
|
||||||
|
bool yes;
|
||||||
|
ScopeItem *si;
|
||||||
|
};
|
||||||
|
static void is_scopeitem_referenced_visitor(AST **aptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||||
|
struct IsScopeItemReferenced *state = ud;
|
||||||
|
|
||||||
|
AST *n = *aptr;
|
||||||
|
if(n->nodeKind == AST_EXPR_UNARY_OP) {
|
||||||
|
if(n->exprUnOp.operand->nodeKind == AST_EXPR_VAR && n->exprUnOp.operator == UNOP_REF && n->exprUnOp.operand->exprVar.thing == state->si) {
|
||||||
|
state->yes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool ast_is_scopeitem_referenced(AST *tlc, ScopeItem *si) {
|
||||||
|
struct IsScopeItemReferenced state = {.si = si};
|
||||||
|
generic_visitor(&tlc, NULL, NULL, tlc, tlc, &state, NULL, is_scopeitem_referenced_visitor);
|
||||||
|
return state.yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopeItem *ast_tlc_new_var(AST *tlc, char *name, Type *itstype) {
|
||||||
|
ScopeItem *vte = calloc(1, sizeof(*vte));
|
||||||
|
vte->kind = SCOPEITEM_VAR;
|
||||||
|
vte->type = itstype;
|
||||||
|
vte->data.var.color = -1;
|
||||||
|
vte->data.var.precolored = false;
|
||||||
|
vte->data.var.degree = 0;
|
||||||
|
vte->data.var.priority = 0;
|
||||||
|
vte->data.var.name = name;
|
||||||
|
|
||||||
|
// Add to var array
|
||||||
|
tlc->chunk.vars = realloc(tlc->chunk.vars, sizeof(*tlc->chunk.vars) * (++tlc->chunk.varCount));
|
||||||
|
tlc->chunk.vars[tlc->chunk.varCount - 1] = vte;
|
||||||
|
|
||||||
|
return vte;
|
||||||
|
}
|
||||||
@@ -38,13 +38,14 @@
|
|||||||
K(AST_EXPR_ARRAY) \
|
K(AST_EXPR_ARRAY) \
|
||||||
K(AST_EXPR_FUNC) \
|
K(AST_EXPR_FUNC) \
|
||||||
K(AST_STMT_EXT_ORG) \
|
K(AST_STMT_EXT_ORG) \
|
||||||
K(AST_STMT_EXT_SECTION) \
|
K(AST_SECTION) \
|
||||||
K(AST_STMT_RETURN) \
|
K(AST_STMT_RETURN) \
|
||||||
K(AST_EXPR_EXT_SALLOC) \
|
K(AST_EXPR_EXT_SALLOC) \
|
||||||
K(AST_EXPR_DOT) \
|
K(AST_EXPR_DOT) \
|
||||||
K(AST_EXPR_EXT_SIZEOF) \
|
K(AST_EXPR_EXT_SIZEOF) \
|
||||||
K(AST_STMT_JUMP) \
|
K(AST_STMT_JUMP) \
|
||||||
K(AST_STMT_LABEL)
|
K(AST_STMT_LABEL) \
|
||||||
|
K(AST_EXPR_NULL)
|
||||||
|
|
||||||
typedef enum ENUMPAK { AST_KINDS(GEN_ENUM) } ASTKind;
|
typedef enum ENUMPAK { AST_KINDS(GEN_ENUM) } ASTKind;
|
||||||
extern const char *AST_KIND_STR[];
|
extern const char *AST_KIND_STR[];
|
||||||
@@ -76,6 +77,8 @@ typedef enum ENUMPAK {
|
|||||||
BINOP_LOGICAL_AND = 15,
|
BINOP_LOGICAL_AND = 15,
|
||||||
BINOP_LOGICAL_OR = 16,
|
BINOP_LOGICAL_OR = 16,
|
||||||
|
|
||||||
|
BINOP_DEREF = 17,
|
||||||
|
|
||||||
BINOP_WTF = 999,
|
BINOP_WTF = 999,
|
||||||
} BinaryOp;
|
} BinaryOp;
|
||||||
|
|
||||||
@@ -105,7 +108,6 @@ static inline BinaryOp binop_comp_opposite(BinaryOp op) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
typedef enum ENUMPAK {
|
typedef enum ENUMPAK {
|
||||||
UNOP_DEREF = 0,
|
|
||||||
UNOP_NEGATE = 1,
|
UNOP_NEGATE = 1,
|
||||||
UNOP_BITWISE_NOT = 2,
|
UNOP_BITWISE_NOT = 2,
|
||||||
UNOP_REF = 3,
|
UNOP_REF = 3,
|
||||||
@@ -202,7 +204,17 @@ typedef struct {
|
|||||||
union AST *expression;
|
union AST *expression;
|
||||||
} ASTStmtDecl;
|
} ASTStmtDecl;
|
||||||
|
|
||||||
typedef struct {
|
struct ASTChunk;
|
||||||
|
typedef struct ASTSection {
|
||||||
|
ASTBase;
|
||||||
|
|
||||||
|
Token name;
|
||||||
|
struct ASTChunk *tlc;
|
||||||
|
|
||||||
|
struct ASTSection *next;
|
||||||
|
} ASTSection;
|
||||||
|
|
||||||
|
typedef struct ASTChunk {
|
||||||
ASTBase;
|
ASTBase;
|
||||||
|
|
||||||
/* Flattened variable array for global register allocation */
|
/* Flattened variable array for global register allocation */
|
||||||
@@ -298,12 +310,6 @@ typedef struct {
|
|||||||
size_t val;
|
size_t val;
|
||||||
} ASTStmtExtOrg;
|
} ASTStmtExtOrg;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
ASTStmt;
|
|
||||||
|
|
||||||
Token name;
|
|
||||||
} ASTStmtExtSection;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
ASTStmt;
|
ASTStmt;
|
||||||
|
|
||||||
@@ -331,6 +337,10 @@ typedef struct {
|
|||||||
char *name;
|
char *name;
|
||||||
} ASTStmtLabel;
|
} ASTStmtLabel;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ASTExpr;
|
||||||
|
} ASTExprNull;
|
||||||
|
|
||||||
typedef union AST {
|
typedef union AST {
|
||||||
ASTBase;
|
ASTBase;
|
||||||
|
|
||||||
@@ -358,10 +368,11 @@ typedef union AST {
|
|||||||
ASTExprDot exprDot;
|
ASTExprDot exprDot;
|
||||||
ASTExprExtSalloc exprExtSalloc;
|
ASTExprExtSalloc exprExtSalloc;
|
||||||
ASTStmtExtOrg stmtExtOrg;
|
ASTStmtExtOrg stmtExtOrg;
|
||||||
ASTStmtExtSection stmtExtSection;
|
ASTSection section;
|
||||||
ASTExprExtSizeOf exprExtSizeOf;
|
ASTExprExtSizeOf exprExtSizeOf;
|
||||||
ASTStmtJump stmtJump;
|
ASTStmtJump stmtJump;
|
||||||
ASTStmtLabel stmtLabel;
|
ASTStmtLabel stmtLabel;
|
||||||
|
ASTExprNull exprNull;
|
||||||
} AST;
|
} AST;
|
||||||
|
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
@@ -381,6 +392,13 @@ void ast_typecheck(AST *tlc);
|
|||||||
|
|
||||||
AST *ast_get_label_by_name(AST *tlc, const char *name);
|
AST *ast_get_label_by_name(AST *tlc, const char *name);
|
||||||
|
|
||||||
|
bool ast_references_stack(AST *a);
|
||||||
|
|
||||||
|
bool ast_is_scopeitem_referenced(AST *tlc, ScopeItem *si);
|
||||||
|
|
||||||
|
// name becomes OWNED
|
||||||
|
ScopeItem *ast_tlc_new_var(AST *tlc, char *name, Type *itstype);
|
||||||
|
|
||||||
#include"dump.h"
|
#include"dump.h"
|
||||||
#include"stack.h"
|
#include"stack.h"
|
||||||
#include"desegment.h"
|
#include"desegment.h"
|
||||||
@@ -388,5 +406,6 @@ AST *ast_get_label_by_name(AST *tlc, const char *name);
|
|||||||
#include"linearize.h"
|
#include"linearize.h"
|
||||||
#include"usedef.h"
|
#include"usedef.h"
|
||||||
#include"commutativity.h"
|
#include"commutativity.h"
|
||||||
|
#include"scr.h"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ static AST *varify(AST *tlc, AST *chunk, AST **stmtPrev, AST *stmt, AST *e) {
|
|||||||
assign->nodeKind = AST_STMT_ASSIGN;
|
assign->nodeKind = AST_STMT_ASSIGN;
|
||||||
assign->what = (AST*) ev[0];
|
assign->what = (AST*) ev[0];
|
||||||
assign->to = e;
|
assign->to = e;
|
||||||
|
vte->data.var.declaration = (AST*) assign;
|
||||||
|
|
||||||
if(*stmtPrev) {
|
if(*stmtPrev) {
|
||||||
(*stmtPrev)->statement.next = (AST*) assign;
|
(*stmtPrev)->statement.next = (AST*) assign;
|
||||||
@@ -61,54 +62,29 @@ static void ast_segmented_dereference_visitor(AST **aptr, AST *stmt, AST *stmtPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
AST *n = *aptr;
|
AST *n = *aptr;
|
||||||
if(n->nodeKind == AST_EXPR_UNARY_OP && n->exprUnOp.operator == UNOP_DEREF && type_is_segmented_pointer(n->exprUnOp.operand->expression.type)) {
|
if(n->nodeKind == AST_EXPR_BINARY_OP && n->exprBinOp.operator == BINOP_DEREF && type_is_segmented_pointer(n->exprBinOp.operands[0]->expression.type)) {
|
||||||
static size_t idx = 0;
|
static size_t idx = 0;
|
||||||
|
|
||||||
AST *v;
|
AST *v;
|
||||||
if(n->exprUnOp.operand->nodeKind == AST_EXPR_VAR)
|
if(n->exprBinOp.operands[0]->nodeKind == AST_EXPR_VAR)
|
||||||
v = n->exprUnOp.operand;
|
v = n->exprBinOp.operands[0];
|
||||||
else
|
else
|
||||||
v = varify(tlc, chunk, &stmtPrev, stmt, n->exprUnOp.operand);
|
v = varify(tlc, chunk, &stmtPrev, stmt, n->exprBinOp.operands[0]);
|
||||||
|
|
||||||
ScopeItem *si = calloc(1, sizeof(*si));
|
|
||||||
si->kind = SCOPEITEM_VAR;
|
|
||||||
si->type = primitive_parse("u16");
|
|
||||||
si->data.var.preclassed = true;
|
|
||||||
si->data.var.registerClass = REG_CLASS_DATASEGS;
|
|
||||||
si->data.var.precolored = true;
|
|
||||||
si->data.var.color = 0;
|
|
||||||
si->data.var.name = malp("$segtemp_%lu", idx++);
|
|
||||||
ast_tlc_add_var(tlc, si);
|
|
||||||
|
|
||||||
ASTExprVar *ev = calloc(1, sizeof(*ev));
|
|
||||||
ev->nodeKind = AST_EXPR_VAR;
|
|
||||||
ev->type = si->type;
|
|
||||||
ev->thing = si;
|
|
||||||
|
|
||||||
ASTExprDot *edseg = calloc(1, sizeof(*edseg));
|
ASTExprDot *edseg = calloc(1, sizeof(*edseg));
|
||||||
edseg->type = n->exprUnOp.operand->expression.type->record.fieldTypes[0];
|
edseg->type = n->exprBinOp.operands[0]->expression.type->record.fieldTypes[0];
|
||||||
edseg->nodeKind = AST_EXPR_DOT;
|
edseg->nodeKind = AST_EXPR_DOT;
|
||||||
edseg->a = v;
|
edseg->a = v;
|
||||||
edseg->b = strdup("segment");
|
edseg->b = strdup("segment");
|
||||||
|
|
||||||
ASTStmtAssign *ass = calloc(1, sizeof(*ass));
|
|
||||||
ass->nodeKind = AST_STMT_ASSIGN;
|
|
||||||
ass->what = (AST*) ev;
|
|
||||||
ass->to = (AST*) edseg;
|
|
||||||
ass->next = (AST*) stmt;
|
|
||||||
if(stmtPrev)
|
|
||||||
stmtPrev->statement.next = (AST*) ass;
|
|
||||||
else
|
|
||||||
chunk->chunk.statementFirst = (AST*) ass;
|
|
||||||
stmtPrev = (AST*) ass;
|
|
||||||
|
|
||||||
ASTExprDot *ed = calloc(1, sizeof(*ed));
|
ASTExprDot *ed = calloc(1, sizeof(*ed));
|
||||||
ed->type = n->exprUnOp.operand->expression.type->record.fieldTypes[1];
|
ed->type = n->exprBinOp.operands[0]->expression.type->record.fieldTypes[1];
|
||||||
ed->nodeKind = AST_EXPR_DOT;
|
ed->nodeKind = AST_EXPR_DOT;
|
||||||
ed->a = ast_deep_copy(v);
|
ed->a = ast_deep_copy(v);
|
||||||
ed->b = strdup("offset");
|
ed->b = strdup("offset");
|
||||||
|
|
||||||
n->exprUnOp.operand = (AST*) ed;
|
n->exprBinOp.operands[0] = (AST*) ed;
|
||||||
|
n->exprBinOp.operands[1] = (AST*) edseg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ static char *ast_dumpe(AST *tlc, AST *e) {
|
|||||||
return strdup(vte->data.var.name);
|
return strdup(vte->data.var.name);
|
||||||
} else if(vte->kind == SCOPEITEM_SYMBOL) {
|
} else if(vte->kind == SCOPEITEM_SYMBOL) {
|
||||||
return strdup(vte->data.symbol.name);
|
return strdup(vte->data.symbol.name);
|
||||||
|
} else if(vte->kind == SCOPEITEM_CEXPR) {
|
||||||
|
return ast_dumpe(tlc, vte->data.cexpr.concrete);
|
||||||
} else abort();
|
} else abort();
|
||||||
} else if(e->nodeKind == AST_EXPR_UNARY_OP) {
|
} else if(e->nodeKind == AST_EXPR_UNARY_OP) {
|
||||||
const char *op = NULL;
|
const char *op = NULL;
|
||||||
@@ -63,9 +65,6 @@ static char *ast_dumpe(AST *tlc, AST *e) {
|
|||||||
case UNOP_REF:
|
case UNOP_REF:
|
||||||
op = "&";
|
op = "&";
|
||||||
break;
|
break;
|
||||||
case UNOP_DEREF:
|
|
||||||
op = "*";
|
|
||||||
break;
|
|
||||||
case UNOP_BITWISE_NOT:
|
case UNOP_BITWISE_NOT:
|
||||||
op = "~";
|
op = "~";
|
||||||
break;
|
break;
|
||||||
@@ -82,6 +81,18 @@ static char *ast_dumpe(AST *tlc, AST *e) {
|
|||||||
char *r = malp("%s%s", op, c);
|
char *r = malp("%s%s", op, c);
|
||||||
free(c);
|
free(c);
|
||||||
return r;
|
return r;
|
||||||
|
} else if(e->nodeKind == AST_EXPR_BINARY_OP && e->exprBinOp.operator == BINOP_DEREF) {
|
||||||
|
char *a = ast_dumpe(tlc, e->exprBinOp.operands[0]);
|
||||||
|
char *r;
|
||||||
|
if(e->exprBinOp.operands[1]) {
|
||||||
|
char *b = ast_dumpe(tlc, e->exprBinOp.operands[1]);
|
||||||
|
r = malp("*[%s]%s", b, a);
|
||||||
|
free(b);
|
||||||
|
} else {
|
||||||
|
r = malp("*%s", a);
|
||||||
|
}
|
||||||
|
free(a);
|
||||||
|
return r;
|
||||||
} else if(e->nodeKind == AST_EXPR_BINARY_OP) {
|
} else if(e->nodeKind == AST_EXPR_BINARY_OP) {
|
||||||
char *a = ast_dumpe(tlc, e->exprBinOp.operands[0]);
|
char *a = ast_dumpe(tlc, e->exprBinOp.operands[0]);
|
||||||
char *b = ast_dumpe(tlc, e->exprBinOp.operands[1]);
|
char *b = ast_dumpe(tlc, e->exprBinOp.operands[1]);
|
||||||
@@ -211,18 +222,7 @@ static char *ast_dumpe(AST *tlc, AST *e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static char *ast_dumps(AST *tlc, AST *s) {
|
static char *ast_dumps(AST *tlc, AST *s) {
|
||||||
if(s->nodeKind == AST_STMT_DECL) {
|
if(s->nodeKind == AST_STMT_ASSIGN) {
|
||||||
ScopeItem *vte = s->stmtDecl.thing;
|
|
||||||
|
|
||||||
if(vte->kind == SCOPEITEM_SYMBOL) {
|
|
||||||
char *t = type_to_string(vte->type);
|
|
||||||
char *e = s->stmtDecl.expression ? ast_dumpe(tlc, s->stmtDecl.expression) : strdup("");
|
|
||||||
char *r = malp("%s%s %s: %s;", vte->data.symbol.isExternal ? "external " : "", t, vte->data.symbol.name, e);
|
|
||||||
free(t);
|
|
||||||
free(e);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
} else if(s->nodeKind == AST_STMT_ASSIGN) {
|
|
||||||
if(s->stmtAssign.to) {
|
if(s->stmtAssign.to) {
|
||||||
char *a = ast_dumpe(tlc, s->stmtAssign.what);
|
char *a = ast_dumpe(tlc, s->stmtAssign.what);
|
||||||
char *b = ast_dumpe(tlc, s->stmtAssign.to);
|
char *b = ast_dumpe(tlc, s->stmtAssign.to);
|
||||||
@@ -274,6 +274,21 @@ static char *ast_dumps(AST *tlc, AST *s) {
|
|||||||
return malp("%s; /* loop guard */", name);
|
return malp("%s; /* loop guard */", name);
|
||||||
} else if(s->nodeKind == AST_STMT_EXPR) {
|
} else if(s->nodeKind == AST_STMT_EXPR) {
|
||||||
return ast_dumpe(tlc, s->stmtExpr.expr);
|
return ast_dumpe(tlc, s->stmtExpr.expr);
|
||||||
|
} else if(s->nodeKind == AST_STMT_DECL) {
|
||||||
|
ScopeItem *si = s->stmtDecl.thing;
|
||||||
|
const char *name = si->kind == SCOPEITEM_VAR ? si->data.var.name : si->data.symbol.name;
|
||||||
|
|
||||||
|
char *a = type_to_string(si->type);
|
||||||
|
char *c;
|
||||||
|
if(s->stmtDecl.expression) {
|
||||||
|
char *b = ast_dumpe(tlc, s->stmtDecl.expression);
|
||||||
|
c = malp("%s %s = %s;", a, name, b);
|
||||||
|
free(b);
|
||||||
|
} else {
|
||||||
|
c = malp("%s %s;", a, name);
|
||||||
|
}
|
||||||
|
free(a);
|
||||||
|
return c;
|
||||||
} else if(s->nodeKind == AST_STMT_RETURN) {
|
} else if(s->nodeKind == AST_STMT_RETURN) {
|
||||||
if(s->stmtReturn.val) {
|
if(s->stmtReturn.val) {
|
||||||
char *e = ast_dumpe(tlc, s->stmtReturn.val);
|
char *e = ast_dumpe(tlc, s->stmtReturn.val);
|
||||||
|
|||||||
61
src/ast/scr.c
Normal file
61
src/ast/scr.c
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#include"sroa.h"
|
||||||
|
|
||||||
|
#include"ast.h"
|
||||||
|
#include"ntc.h"
|
||||||
|
#include"utils.h"
|
||||||
|
#include<stdlib.h>
|
||||||
|
#include<assert.h>
|
||||||
|
|
||||||
|
static bool is_sroa_candidate(ScopeItem *si) {
|
||||||
|
return si->type->type == TYPE_TYPE_RECORD && type_size(si->type) <= ntc_get_int_default("sroa-threshold", 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SCRState {
|
||||||
|
AST *tlc;
|
||||||
|
};
|
||||||
|
static void ast_scr_visitor(AST **aptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||||
|
AST *n = *aptr;
|
||||||
|
|
||||||
|
struct SCRState *state = ud;
|
||||||
|
|
||||||
|
if(state->tlc != tlc) return;
|
||||||
|
|
||||||
|
if(n->nodeKind == AST_STMT_ASSIGN && n->stmtAssign.what->expression.type->type == TYPE_TYPE_RECORD) {
|
||||||
|
Type *rectype = n->stmtAssign.what->expression.type;
|
||||||
|
|
||||||
|
for(size_t f = 0; f < rectype->record.fieldCount; f++) {
|
||||||
|
ASTExprDot *dot1 = calloc(1, sizeof(*dot1));
|
||||||
|
dot1->nodeKind = AST_EXPR_DOT;
|
||||||
|
dot1->type = rectype->record.fieldTypes[f];
|
||||||
|
dot1->a = ast_deep_copy(n->stmtAssign.what);
|
||||||
|
dot1->b = strdup(rectype->record.fieldNames[f]);
|
||||||
|
|
||||||
|
ASTExprDot *dot2 = calloc(1, sizeof(*dot2));
|
||||||
|
dot2->nodeKind = AST_EXPR_DOT;
|
||||||
|
dot2->type = rectype->record.fieldTypes[f];
|
||||||
|
dot2->a = ast_deep_copy(n->stmtAssign.to);
|
||||||
|
dot2->b = strdup(rectype->record.fieldNames[f]);
|
||||||
|
|
||||||
|
ASTStmtAssign *assign = calloc(1, sizeof(*assign));
|
||||||
|
assign->nodeKind = AST_STMT_ASSIGN;
|
||||||
|
assign->what = (AST*) dot1;
|
||||||
|
assign->to = (AST*) dot2;
|
||||||
|
|
||||||
|
stmtPrev->statement.next = (AST*) assign;
|
||||||
|
stmtPrev = assign;
|
||||||
|
}
|
||||||
|
|
||||||
|
stmtPrev->statement.next = stmt->statement.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ast_secondclass_record(AST *tlc) {
|
||||||
|
struct SCRState state = {.tlc = tlc};
|
||||||
|
generic_visitor(&tlc, NULL, NULL, tlc, tlc, &state, ast_scr_visitor, NULL);
|
||||||
|
|
||||||
|
if(ntc_get_int("pdbg")) {
|
||||||
|
char *astdump = ast_dump(tlc);
|
||||||
|
fprintf(stderr, "### SCR ###\n%s\n", astdump);
|
||||||
|
free(astdump);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/ast/scr.h
Normal file
12
src/ast/scr.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// In machine code structs/records are second-class concepts.
|
||||||
|
// Either the record gets split into its separate fields (SRoA),
|
||||||
|
// or it must be spilled into memory.
|
||||||
|
//
|
||||||
|
// In any case, many QoL features programmers love (assigning structs to
|
||||||
|
// structs or passing structs as arguments to functions) don't exist in
|
||||||
|
// machine code and must be converted to a valid but equivalent form.
|
||||||
|
|
||||||
|
union AST;
|
||||||
|
void ast_secondclass_record(union AST *tlc);
|
||||||
@@ -10,26 +10,6 @@ static bool is_sroa_candidate(ScopeItem *si) {
|
|||||||
return si->type->type == TYPE_TYPE_RECORD && type_size(si->type) <= ntc_get_int_default("sroa-threshold", 32);
|
return si->type->type == TYPE_TYPE_RECORD && type_size(si->type) <= ntc_get_int_default("sroa-threshold", 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct IsScopeItemReferenced {
|
|
||||||
bool yes;
|
|
||||||
ScopeItem *si;
|
|
||||||
};
|
|
||||||
static void is_scopeitem_referenced_visitor(AST **aptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
|
||||||
struct IsScopeItemReferenced *state = ud;
|
|
||||||
|
|
||||||
AST *n = *aptr;
|
|
||||||
if(n->nodeKind == AST_EXPR_UNARY_OP) {
|
|
||||||
if(n->exprUnOp.operand->nodeKind == AST_EXPR_VAR && n->exprUnOp.operator == UNOP_REF && n->exprUnOp.operand->exprVar.thing == state->si) {
|
|
||||||
state->yes = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static bool is_scopeitem_referenced(AST *tlc, ScopeItem *si) {
|
|
||||||
struct IsScopeItemReferenced state = {.si = si};
|
|
||||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, &state, NULL, is_scopeitem_referenced_visitor);
|
|
||||||
return state.yes;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DecomposeAutomaticRecordState {
|
struct DecomposeAutomaticRecordState {
|
||||||
AST *tlc;
|
AST *tlc;
|
||||||
ScopeItem *target;
|
ScopeItem *target;
|
||||||
@@ -71,6 +51,7 @@ static void ast_decompose_automatic_record(AST *tlc, ScopeItem *target) {
|
|||||||
si->kind = SCOPEITEM_VAR;
|
si->kind = SCOPEITEM_VAR;
|
||||||
si->type = target->type->record.fieldTypes[f];
|
si->type = target->type->record.fieldTypes[f];
|
||||||
si->data.var.name = malp("%s_sroa_%s", target->data.var.name, target->type->record.fieldNames[f]);
|
si->data.var.name = malp("%s_sroa_%s", target->data.var.name, target->type->record.fieldNames[f]);
|
||||||
|
si->data.var.declaration = target->data.var.declaration;
|
||||||
state.replacements[f] = si;
|
state.replacements[f] = si;
|
||||||
|
|
||||||
tlc->chunk.vars[tlc->chunk.varCount++] = si;
|
tlc->chunk.vars[tlc->chunk.varCount++] = si;
|
||||||
@@ -89,7 +70,7 @@ void ast_sroa(AST *tlc) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(is_scopeitem_referenced(tlc, si)) {
|
if(ast_is_scopeitem_referenced(tlc, si)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,4 +82,10 @@ void ast_sroa(AST *tlc) {
|
|||||||
/* Restart */
|
/* Restart */
|
||||||
i = -1;
|
i = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(ntc_get_int("pdbg")) {
|
||||||
|
char *astdump = ast_dump(tlc);
|
||||||
|
fprintf(stderr, "### SROA ###\n%s\n", astdump);
|
||||||
|
free(astdump);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ static void spill2stack_visitor(AST **aptr, AST *stmt, AST *stmtPrev, AST *chunk
|
|||||||
|
|
||||||
ASTExprStackPointer *rsp = calloc(1, sizeof(*rsp));
|
ASTExprStackPointer *rsp = calloc(1, sizeof(*rsp));
|
||||||
rsp->nodeKind = AST_EXPR_STACK_POINTER;
|
rsp->nodeKind = AST_EXPR_STACK_POINTER;
|
||||||
rsp->type = type_u(arch_gpr_size());
|
rsp->type = type_u(8 * arch_gpr_size());
|
||||||
|
|
||||||
ASTExprPrimitive *offset = calloc(1, sizeof(*offset));
|
ASTExprPrimitive *offset = calloc(1, sizeof(*offset));
|
||||||
offset->nodeKind = AST_EXPR_PRIMITIVE;
|
offset->nodeKind = AST_EXPR_PRIMITIVE;
|
||||||
@@ -46,18 +46,18 @@ static void spill2stack_visitor(AST **aptr, AST *stmt, AST *stmtPrev, AST *chunk
|
|||||||
bop->operands[0] = (AST*) rsp;
|
bop->operands[0] = (AST*) rsp;
|
||||||
bop->operands[1] = (AST*) offset;
|
bop->operands[1] = (AST*) offset;
|
||||||
|
|
||||||
ASTExprUnaryOp *deref = calloc(1, sizeof(*deref));
|
ASTExprBinaryOp *deref = calloc(1, sizeof(*deref));
|
||||||
deref->nodeKind = AST_EXPR_UNARY_OP;
|
deref->nodeKind = AST_EXPR_BINARY_OP;
|
||||||
deref->type = a->expression.type;
|
deref->type = a->expression.type;
|
||||||
deref->operator = UNOP_DEREF;
|
deref->operator = BINOP_DEREF;
|
||||||
deref->operand = (AST*) bop;
|
deref->operands[0] = (AST*) bop;
|
||||||
|
|
||||||
*aptr = (AST*) deref;
|
*aptr = (AST*) deref;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if(a->nodeKind == AST_EXPR_PRIMITIVE && a->exprPrim.stackGrowth) {
|
} else if(a->nodeKind == AST_EXPR_PRIMITIVE && a->exprPrim.stackGrowth) {
|
||||||
|
|
||||||
// Guaranteed to not require more dumbification
|
// Guaranteed to not require more normalization
|
||||||
a->exprPrim.val += this->stackGrowth;
|
a->exprPrim.val += this->stackGrowth;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,8 +190,10 @@ void ast_usedef_reset(AST *chu) {
|
|||||||
|
|
||||||
assert(!!vte->data.var.usedefFirst == !!vte->data.var.usedefLast);
|
assert(!!vte->data.var.usedefFirst == !!vte->data.var.usedefLast);
|
||||||
|
|
||||||
vte->data.var.liveRangeStart = vte->data.var.usedefFirst->stmt;
|
if(vte->data.var.usedefFirst) {
|
||||||
vte->data.var.liveRangeEnd = vte->data.var.usedefLast->stmt;
|
vte->data.var.liveRangeStart = vte->data.var.usedefFirst->stmt;
|
||||||
|
vte->data.var.liveRangeEnd = vte->data.var.usedefLast->stmt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fix liveRangeStart and/or liveRangeEnd depending on goto targets
|
// fix liveRangeStart and/or liveRangeEnd depending on goto targets
|
||||||
@@ -202,6 +204,8 @@ void ast_usedef_reset(AST *chu) {
|
|||||||
for(size_t sii = 0; sii < chu->chunk.varCount; sii++) {
|
for(size_t sii = 0; sii < chu->chunk.varCount; sii++) {
|
||||||
ScopeItem *si = chu->chunk.vars[sii];
|
ScopeItem *si = chu->chunk.vars[sii];
|
||||||
|
|
||||||
|
assert(si->data.var.declaration != NULL && "All local vars must have defined declaration location");
|
||||||
|
|
||||||
if(ast_stmt_is_after(chu, si->data.var.liveRangeEnd, s) == 0 && ast_stmt_is_after(chu, target, si->data.var.liveRangeEnd) == 0 && ast_stmt_is_after(chu, si->data.var.declaration, target) == 0) {
|
if(ast_stmt_is_after(chu, si->data.var.liveRangeEnd, s) == 0 && ast_stmt_is_after(chu, target, si->data.var.liveRangeEnd) == 0 && ast_stmt_is_after(chu, si->data.var.declaration, target) == 0) {
|
||||||
|
|
||||||
si->data.var.liveRangeEnd = s;
|
si->data.var.liveRangeEnd = s;
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ char *TOKEN_NAMES[] = {
|
|||||||
"'break'",
|
"'break'",
|
||||||
"','",
|
"','",
|
||||||
"'&'",
|
"'&'",
|
||||||
|
"'&&'",
|
||||||
"'|'",
|
"'|'",
|
||||||
|
"'||'",
|
||||||
"'^'",
|
"'^'",
|
||||||
"'~'",
|
"'~'",
|
||||||
"'=='",
|
"'=='",
|
||||||
@@ -50,6 +52,7 @@ char *TOKEN_NAMES[] = {
|
|||||||
"'as'",
|
"'as'",
|
||||||
"'use'",
|
"'use'",
|
||||||
"'else'",
|
"'else'",
|
||||||
|
"'null'",
|
||||||
};
|
};
|
||||||
|
|
||||||
static int isAlpha(int c) {
|
static int isAlpha(int c) {
|
||||||
@@ -309,6 +312,10 @@ Token nct_tokenize(FILE *f) {
|
|||||||
free(content);
|
free(content);
|
||||||
tok.type = TOKEN_ELSE;
|
tok.type = TOKEN_ELSE;
|
||||||
return tok;
|
return tok;
|
||||||
|
} else if(!strcmp(content, "null")) {
|
||||||
|
free(content);
|
||||||
|
tok.type = TOKEN_NULL;
|
||||||
|
return tok;
|
||||||
}
|
}
|
||||||
|
|
||||||
tok.type = TOKEN_IDENTIFIER;
|
tok.type = TOKEN_IDENTIFIER;
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ typedef enum {
|
|||||||
TOKEN_AS,
|
TOKEN_AS,
|
||||||
TOKEN_USE,
|
TOKEN_USE,
|
||||||
TOKEN_ELSE,
|
TOKEN_ELSE,
|
||||||
|
TOKEN_NULL,
|
||||||
} TokenKind;
|
} TokenKind;
|
||||||
|
|
||||||
typedef struct Token {
|
typedef struct Token {
|
||||||
|
|||||||
21
src/ntc.c
21
src/ntc.c
@@ -86,27 +86,30 @@ int main(int argc_, char **argv_) {
|
|||||||
|
|
||||||
if(in) fclose(f);
|
if(in) fclose(f);
|
||||||
|
|
||||||
AST *chunk = nct_parse(tokens);
|
AST *sects = nct_parse(tokens);
|
||||||
|
|
||||||
free(tokens);
|
free(tokens);
|
||||||
|
|
||||||
if(ntc_get_int("pdbg")) {
|
if(ntc_get_int("pdbg")) {
|
||||||
char *astdump = ast_dump(chunk);
|
char *astdump = ast_dump(sects);
|
||||||
fprintf(stderr, "### ORIGINAL ###\n%s\n", astdump);
|
fprintf(stderr, "### ORIGINAL ###\n%s\n", astdump);
|
||||||
free(astdump);
|
free(astdump);
|
||||||
}
|
}
|
||||||
|
|
||||||
ast_segmented_dereference(chunk);
|
ast_segmented_dereference(sects);
|
||||||
ast_sroa(chunk);
|
ast_secondclass_record(sects);
|
||||||
|
ast_sroa(sects);
|
||||||
|
|
||||||
ast_linearize(chunk);
|
ast_linearize(sects);
|
||||||
|
|
||||||
dumben_pre(chunk);
|
arch_normalize_pre(sects);
|
||||||
|
|
||||||
dumben_go(chunk);
|
arch_normalize(sects);
|
||||||
while(!cg_go(chunk)) {
|
while(!cg_attempt_sections(sects)) {
|
||||||
dumben_go(chunk);
|
arch_normalize(sects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cg_go_sections(sects);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,12 @@ intmax_t ntc_get_int(const char *name);
|
|||||||
intmax_t ntc_get_int_default(const char *name, intmax_t def);
|
intmax_t ntc_get_int_default(const char *name, intmax_t def);
|
||||||
|
|
||||||
union AST;
|
union AST;
|
||||||
|
void arch_init();
|
||||||
bool arch_verify_target();
|
bool arch_verify_target();
|
||||||
int arch_ptr_size();
|
int arch_ptr_size();
|
||||||
void dumben_pre(union AST *tlc);
|
void arch_normalize_pre(union AST *tlc);
|
||||||
void dumben_go(union AST *tlc);
|
void arch_normalize(union AST *tlc);
|
||||||
int cg_go(union AST *tlc);
|
int cg_attempt_sections(union AST *tlc);
|
||||||
|
void cg_go_sections(union AST *tlc);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
459
src/parse.c
459
src/parse.c
@@ -152,9 +152,10 @@ static char *parametrize_function_name(Type *t, const char *original, Scope *sco
|
|||||||
static void binop_implicit_cast(/*Parser *P, */ASTExprBinaryOp *binop) {
|
static void binop_implicit_cast(/*Parser *P, */ASTExprBinaryOp *binop) {
|
||||||
if(type_size(binop->operands[0]->expression.type) < type_size(binop->operands[1]->expression.type)) {
|
if(type_size(binop->operands[0]->expression.type) < type_size(binop->operands[1]->expression.type)) {
|
||||||
binop->operands[0] = ast_cast_expr(binop->operands[0], binop->operands[1]->expression.type);
|
binop->operands[0] = ast_cast_expr(binop->operands[0], binop->operands[1]->expression.type);
|
||||||
}
|
} else if(type_size(binop->operands[1]->expression.type) < type_size(binop->operands[0]->expression.type)) {
|
||||||
|
binop->operands[1] = ast_cast_expr(binop->operands[1], binop->operands[0]->expression.type);
|
||||||
if(type_size(binop->operands[1]->expression.type) < type_size(binop->operands[0]->expression.type)) {
|
} else {
|
||||||
|
binop->operands[0] = ast_cast_expr(binop->operands[0], binop->operands[0]->expression.type);
|
||||||
binop->operands[1] = ast_cast_expr(binop->operands[1], binop->operands[0]->expression.type);
|
binop->operands[1] = ast_cast_expr(binop->operands[1], binop->operands[0]->expression.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +202,6 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
|||||||
P->isInFunction++;
|
P->isInFunction++;
|
||||||
|
|
||||||
e->chunk = (AST*) nct_parse_chunk(P, 1, 0, scope_new(P->scope), ft);
|
e->chunk = (AST*) nct_parse_chunk(P, 1, 0, scope_new(P->scope), ft);
|
||||||
e->chunk->chunk.functionType = ft;
|
|
||||||
|
|
||||||
P->isInFunction--;
|
P->isInFunction--;
|
||||||
|
|
||||||
@@ -228,7 +228,7 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
|||||||
|
|
||||||
ASTExprStackPointer *ret = alloc_node(P, sizeof(*ret));
|
ASTExprStackPointer *ret = alloc_node(P, sizeof(*ret));
|
||||||
ret->nodeKind = AST_EXPR_STACK_POINTER;
|
ret->nodeKind = AST_EXPR_STACK_POINTER;
|
||||||
ret->type = type_u(arch_sp_size());
|
ret->type = type_u(8 * arch_sp_size());
|
||||||
|
|
||||||
e = (AST*) ret;
|
e = (AST*) ret;
|
||||||
} else if(!strcmp(peek(P, 0).content, "@salloc")) {
|
} else if(!strcmp(peek(P, 0).content, "@salloc")) {
|
||||||
@@ -255,7 +255,7 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
|||||||
ret->ofExpr = nct_parse_expression(P, lOP - 1);
|
ret->ofExpr = nct_parse_expression(P, lOP - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret->type = type_u(arch_gpr_size());
|
ret->type = type_u(8 * arch_gpr_size());
|
||||||
|
|
||||||
e = (AST*) ret;
|
e = (AST*) ret;
|
||||||
} else {
|
} else {
|
||||||
@@ -269,6 +269,12 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
|||||||
|
|
||||||
e = (AST*) exprvar(P, vte);
|
e = (AST*) exprvar(P, vte);
|
||||||
}
|
}
|
||||||
|
} else if(maybe(P, TOKEN_NULL)) {
|
||||||
|
ASTExprNull *ret = alloc_node(P, sizeof(*ret));
|
||||||
|
ret->nodeKind = AST_EXPR_NULL;
|
||||||
|
ret->type = type_pointer_wrap(type_u(8));
|
||||||
|
|
||||||
|
e = (AST*) ret;
|
||||||
} else if(peek(P, 0).type == TOKEN_STRING) {
|
} else if(peek(P, 0).type == TOKEN_STRING) {
|
||||||
ASTExprStringLiteral *ret = alloc_node(P, sizeof(*ret));
|
ASTExprStringLiteral *ret = alloc_node(P, sizeof(*ret));
|
||||||
ret->nodeKind = AST_EXPR_STRING_LITERAL;
|
ret->nodeKind = AST_EXPR_STRING_LITERAL;
|
||||||
@@ -286,46 +292,176 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
|||||||
expect(P, TOKEN_PAREN_R);
|
expect(P, TOKEN_PAREN_R);
|
||||||
}
|
}
|
||||||
|
|
||||||
while(maybe(P, TOKEN_DOT)) {
|
while(maybe(P, TOKEN_DOT) || maybe(P, TOKEN_PAREN_L) || maybe(P, TOKEN_SQUAREN_L)) {
|
||||||
assert(e->expression.type->type == TYPE_TYPE_RECORD);
|
Token op = P->tokens[P->i - 1];
|
||||||
|
|
||||||
Token fieldTok = expect(P, TOKEN_IDENTIFIER);
|
if(op.type == TOKEN_DOT) {
|
||||||
|
while(e->expression.type->type == TYPE_TYPE_POINTER) {
|
||||||
|
AST *deref = alloc_node(P, sizeof(ASTExprBinaryOp));
|
||||||
|
deref->nodeKind = AST_EXPR_BINARY_OP;
|
||||||
|
deref->exprBinOp.operator = BINOP_DEREF;
|
||||||
|
deref->exprBinOp.operands[0] = (AST*) e;
|
||||||
|
deref->exprBinOp.type = type_dereference(e->expression.type);
|
||||||
|
|
||||||
ASTExprDot *d = alloc_node(P, sizeof(*d));
|
e = (AST*) deref;
|
||||||
d->nodeKind = AST_EXPR_DOT;
|
|
||||||
d->a = (AST*) e;
|
|
||||||
|
|
||||||
bool foundField = false;
|
|
||||||
|
|
||||||
for(size_t f = 0; f < e->expression.type->record.fieldCount; f++) {
|
|
||||||
char *fieldName = e->expression.type->record.fieldNames[f];
|
|
||||||
if(!strcmp(fieldName, fieldTok.content)) {
|
|
||||||
foundField = true;
|
|
||||||
d->type = e->expression.type->record.fieldTypes[f];
|
|
||||||
d->b = strdup(fieldName);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(!foundField) {
|
assert(e->expression.type->type == TYPE_TYPE_RECORD);
|
||||||
stahp(fieldTok.row, fieldTok.column, "Field %s does not exist.", fieldTok.content);
|
|
||||||
}
|
|
||||||
|
|
||||||
e = (AST*) d;
|
Token fieldTok = expect(P, TOKEN_IDENTIFIER);
|
||||||
|
|
||||||
|
ASTExprDot *d = alloc_node(P, sizeof(*d));
|
||||||
|
d->nodeKind = AST_EXPR_DOT;
|
||||||
|
d->a = (AST*) e;
|
||||||
|
|
||||||
|
bool foundField = false;
|
||||||
|
|
||||||
|
for(size_t f = 0; f < e->expression.type->record.fieldCount; f++) {
|
||||||
|
char *fieldName = e->expression.type->record.fieldNames[f];
|
||||||
|
if(!strcmp(fieldName, fieldTok.content)) {
|
||||||
|
foundField = true;
|
||||||
|
d->type = e->expression.type->record.fieldTypes[f];
|
||||||
|
d->b = strdup(fieldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!foundField) {
|
||||||
|
stahp(fieldTok.row, fieldTok.column, "Field %s does not exist.", fieldTok.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
e = (AST*) d;
|
||||||
|
} else if(op.type == TOKEN_PAREN_L) {
|
||||||
|
// Convert a call like foo() to (&foo)()
|
||||||
|
if(e->expression.type->type == TYPE_TYPE_FUNCTION) {
|
||||||
|
ASTExprUnaryOp *ref = alloc_node(P, sizeof(*ref));
|
||||||
|
ref->nodeKind = AST_EXPR_UNARY_OP;
|
||||||
|
ref->type = type_pointer_wrap(e->expression.type);
|
||||||
|
ref->operator = UNOP_REF;
|
||||||
|
ref->operand = e;
|
||||||
|
e = (AST*) ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(e->expression.type->type != TYPE_TYPE_POINTER || e->expression.type->pointer.of->type != TYPE_TYPE_FUNCTION) {
|
||||||
|
stahp(P->tokens[P->i].row, P->tokens[P->i].column, "Only function types or function pointers may be called.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Type *ftype = e->expression.type->pointer.of;
|
||||||
|
|
||||||
|
ASTExprCall *call = alloc_node(P, sizeof(*call));
|
||||||
|
call->nodeKind = AST_EXPR_CALL;
|
||||||
|
call->type = ftype->function.ret;
|
||||||
|
call->what = e;
|
||||||
|
call->args = NULL;
|
||||||
|
|
||||||
|
int argCount = 0;
|
||||||
|
|
||||||
|
if(!maybe(P, TOKEN_PAREN_R)) {
|
||||||
|
while(peek(P, 0).type != TOKEN_PAREN_R && peek(P, 0).type != TOKEN_COMMA) {
|
||||||
|
call->args = realloc(call->args, (argCount + 1) * sizeof(AST*));
|
||||||
|
call->args[argCount] = ast_cast_expr(nct_parse_expression(P, 0), ftype->function.args[argCount]);
|
||||||
|
|
||||||
|
argCount++;
|
||||||
|
|
||||||
|
if(maybe(P, TOKEN_PAREN_R)) {
|
||||||
|
break;
|
||||||
|
} else expect(P, TOKEN_COMMA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(argCount != ftype->function.argCount) {
|
||||||
|
stahp(P->tokens[P->i].row, P->tokens[P->i].column, "Mismatched number of arguments");
|
||||||
|
}
|
||||||
|
|
||||||
|
e = (AST*) call;
|
||||||
|
} else if(op.type == TOKEN_SQUAREN_L) {
|
||||||
|
P->scope = scope_new(P->scope);
|
||||||
|
|
||||||
|
P->i--;
|
||||||
|
|
||||||
|
if(parse_parametrization(P)) {
|
||||||
|
// Generic type parametrization
|
||||||
|
|
||||||
|
// Generic functions are not first-class
|
||||||
|
assert(e->nodeKind == AST_EXPR_VAR);
|
||||||
|
assert(e->exprVar.thing != NULL);
|
||||||
|
assert(e->exprVar.thing->kind == SCOPEITEM_SYMBOL);
|
||||||
|
|
||||||
|
char *cname = parametrize_function_name(e->expression.type, e->exprVar.thing->data.symbol.name, P->scope);
|
||||||
|
|
||||||
|
ScopeItem *cvte = scope_find(P->scope, cname);
|
||||||
|
|
||||||
|
if(!cvte) {
|
||||||
|
stahp_token(&P->tokens[P->i], "Parametrization %s not found.", cname);
|
||||||
|
}
|
||||||
|
|
||||||
|
e = exprvar(P, cvte);
|
||||||
|
} else {
|
||||||
|
// Array access
|
||||||
|
|
||||||
|
expect(P, TOKEN_SQUAREN_L);
|
||||||
|
|
||||||
|
ASTExprUnaryOp *ref = alloc_node(P, sizeof(*ref));
|
||||||
|
ref->nodeKind = AST_EXPR_UNARY_OP;
|
||||||
|
ref->operator = UNOP_REF;
|
||||||
|
ref->operand = e;
|
||||||
|
ref->type = type_pointer_wrap(e->expression.type->array.of);
|
||||||
|
|
||||||
|
ASTExprBinaryOp *child = alloc_node(P, sizeof(*child));
|
||||||
|
child->nodeKind = AST_EXPR_BINARY_OP;
|
||||||
|
child->operands[0] = (AST*) ref;
|
||||||
|
child->operands[1] = nct_parse_expression(P, 0);
|
||||||
|
child->operator = BINOP_ADD;
|
||||||
|
child->type = ref->type;
|
||||||
|
|
||||||
|
if(e->expression.type->type != TYPE_TYPE_ARRAY) {
|
||||||
|
stahp_token(&P->tokens[P->i], "Attempt to index a non-array type");
|
||||||
|
}
|
||||||
|
|
||||||
|
int typesize = type_size(e->expression.type->array.of);
|
||||||
|
if(typesize != 1) {
|
||||||
|
ASTExprPrimitive *scale = alloc_node(P, sizeof(*scale));
|
||||||
|
scale->nodeKind = AST_EXPR_PRIMITIVE;
|
||||||
|
scale->type = type_u(8 * arch_gpr_size());
|
||||||
|
scale->val = typesize;
|
||||||
|
|
||||||
|
ASTExprBinaryOp *mul = alloc_node(P, sizeof(*mul));
|
||||||
|
mul->nodeKind = AST_EXPR_BINARY_OP;
|
||||||
|
mul->type = child->operands[1]->expression.type;
|
||||||
|
mul->operator = BINOP_MUL;
|
||||||
|
mul->operands[0] = (AST*) scale;
|
||||||
|
mul->operands[1] = child->operands[1];
|
||||||
|
|
||||||
|
child->operands[1] = (AST*) mul;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASTExprBinaryOp *unop = alloc_node(P, sizeof(*unop));
|
||||||
|
unop->nodeKind = AST_EXPR_BINARY_OP;
|
||||||
|
unop->type = e->expression.type->array.of;
|
||||||
|
unop->operator = BINOP_DEREF;
|
||||||
|
unop->operands[0] = (AST*) child;
|
||||||
|
|
||||||
|
expect(P, TOKEN_SQUAREN_R);
|
||||||
|
|
||||||
|
e = (AST*) unop;
|
||||||
|
}
|
||||||
|
|
||||||
|
P->scope = P->scope->parent;
|
||||||
|
} else abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
} else if(lOP == 6) {
|
} else if(lOP == 6) {
|
||||||
if(maybe(P, TOKEN_STAR)) {
|
if(maybe(P, TOKEN_STAR)) {
|
||||||
ASTExprUnaryOp *astop = alloc_node(P, sizeof(*astop));
|
ASTExprBinaryOp *astop = alloc_node(P, sizeof(*astop));
|
||||||
astop->nodeKind = AST_EXPR_UNARY_OP;
|
astop->nodeKind = AST_EXPR_BINARY_OP;
|
||||||
astop->operator = UNOP_DEREF;
|
astop->operator = BINOP_DEREF;
|
||||||
astop->operand = nct_parse_expression(P, lOP); /* Not +1! */
|
astop->operands[0] = nct_parse_expression(P, lOP); /* Not +1! */
|
||||||
|
|
||||||
if(type_is_segmented_pointer(astop->operand->expression.type)) {
|
if(type_is_segmented_pointer(astop->operands[0]->expression.type)) {
|
||||||
astop->type = astop->operand->expression.type->record.fieldTypes[1]->pointer.of;
|
astop->type = astop->operands[0]->expression.type->record.fieldTypes[1]->pointer.of;
|
||||||
} else {
|
} else {
|
||||||
assert(astop->operand->expression.type->type == TYPE_TYPE_POINTER);
|
assert(astop->operands[0]->expression.type->type == TYPE_TYPE_POINTER);
|
||||||
astop->type = astop->operand->expression.type->pointer.of;
|
astop->type = astop->operands[0]->expression.type->pointer.of;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (AST*) astop;
|
return (AST*) astop;
|
||||||
@@ -369,128 +505,7 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
|||||||
}
|
}
|
||||||
} else return nct_parse_expression(P, lOP + 1);
|
} else return nct_parse_expression(P, lOP + 1);
|
||||||
} else if(lOP == 5) {
|
} else if(lOP == 5) {
|
||||||
AST *ret = nct_parse_expression(P, lOP + 1);
|
return nct_parse_expression(P, lOP + 1);
|
||||||
|
|
||||||
while(peek(P, 0).type == TOKEN_PAREN_L || peek(P, 0).type == TOKEN_SQUAREN_L) {
|
|
||||||
if(maybe(P, TOKEN_PAREN_L)) {
|
|
||||||
if(ret->expression.type->type == TYPE_TYPE_FUNCTION) {
|
|
||||||
ASTExprUnaryOp *ref = alloc_node(P, sizeof(*ref));
|
|
||||||
ref->nodeKind = AST_EXPR_UNARY_OP;
|
|
||||||
ref->type = type_pointer_wrap(ret->expression.type);
|
|
||||||
ref->operator = UNOP_REF;
|
|
||||||
ref->operand = ret;
|
|
||||||
ret = (AST*) ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ret->expression.type->type != TYPE_TYPE_POINTER || ret->expression.type->pointer.of->type != TYPE_TYPE_FUNCTION) {
|
|
||||||
stahp(P->tokens[P->i].row, P->tokens[P->i].column, "Only function types or function pointers may be called.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Type *ftype = ret->expression.type->pointer.of;
|
|
||||||
|
|
||||||
ASTExprCall *call = alloc_node(P, sizeof(*call));
|
|
||||||
call->nodeKind = AST_EXPR_CALL;
|
|
||||||
call->type = ftype->function.ret;
|
|
||||||
call->what = ret;
|
|
||||||
call->args = NULL;
|
|
||||||
|
|
||||||
int argCount = 0;
|
|
||||||
|
|
||||||
if(!maybe(P, TOKEN_PAREN_R)) {
|
|
||||||
while(peek(P, 0).type != TOKEN_PAREN_R && peek(P, 0).type != TOKEN_COMMA) {
|
|
||||||
call->args = realloc(call->args, (argCount + 1) * sizeof(AST*));
|
|
||||||
call->args[argCount] = ast_cast_expr(nct_parse_expression(P, 0), ftype->function.args[argCount]);
|
|
||||||
|
|
||||||
argCount++;
|
|
||||||
|
|
||||||
if(maybe(P, TOKEN_PAREN_R)) {
|
|
||||||
break;
|
|
||||||
} else expect(P, TOKEN_COMMA);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(argCount != ftype->function.argCount) {
|
|
||||||
stahp(P->tokens[P->i].row, P->tokens[P->i].column, "Mismatched number of arguments");
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = (AST*) call;
|
|
||||||
} else if(peek(P, 0).type == TOKEN_SQUAREN_L) {
|
|
||||||
|
|
||||||
P->scope = scope_new(P->scope);
|
|
||||||
|
|
||||||
if(parse_parametrization(P)) {
|
|
||||||
// Generic type parametrization
|
|
||||||
|
|
||||||
// Generic functions are not first-class
|
|
||||||
assert(ret->nodeKind == AST_EXPR_VAR);
|
|
||||||
assert(ret->exprVar.thing != NULL);
|
|
||||||
assert(ret->exprVar.thing->kind == SCOPEITEM_SYMBOL);
|
|
||||||
|
|
||||||
char *cname = parametrize_function_name(ret->expression.type, ret->exprVar.thing->data.symbol.name, P->scope);
|
|
||||||
|
|
||||||
ScopeItem *cvte = scope_find(P->scope, cname);
|
|
||||||
|
|
||||||
if(!cvte) {
|
|
||||||
stahp_token(&P->tokens[P->i], "Parametrization %s not found.", cname);
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = exprvar(P, cvte);
|
|
||||||
} else {
|
|
||||||
// Array access
|
|
||||||
|
|
||||||
expect(P, TOKEN_SQUAREN_L);
|
|
||||||
|
|
||||||
ASTExprUnaryOp *ref = alloc_node(P, sizeof(*ref));
|
|
||||||
ref->nodeKind = AST_EXPR_UNARY_OP;
|
|
||||||
ref->operator = UNOP_REF;
|
|
||||||
ref->operand = ret;
|
|
||||||
ref->type = type_pointer_wrap(ret->expression.type->array.of);
|
|
||||||
|
|
||||||
ASTExprBinaryOp *child = alloc_node(P, sizeof(*child));
|
|
||||||
child->nodeKind = AST_EXPR_BINARY_OP;
|
|
||||||
child->operands[0] = (AST*) ref;
|
|
||||||
child->operands[1] = nct_parse_expression(P, 0);
|
|
||||||
child->operator = BINOP_ADD;
|
|
||||||
child->type = ref->type;
|
|
||||||
|
|
||||||
if(ret->expression.type->type != TYPE_TYPE_ARRAY) {
|
|
||||||
stahp_token(&P->tokens[P->i], "Attempt to index a non-array type");
|
|
||||||
}
|
|
||||||
|
|
||||||
int typesize = type_size(ret->expression.type->array.of);
|
|
||||||
if(typesize != 1) {
|
|
||||||
ASTExprPrimitive *scale = alloc_node(P, sizeof(*scale));
|
|
||||||
scale->nodeKind = AST_EXPR_PRIMITIVE;
|
|
||||||
scale->type = type_u(arch_gpr_size());
|
|
||||||
scale->val = typesize;
|
|
||||||
|
|
||||||
ASTExprBinaryOp *mul = alloc_node(P, sizeof(*mul));
|
|
||||||
mul->nodeKind = AST_EXPR_BINARY_OP;
|
|
||||||
mul->type = child->operands[1]->expression.type;
|
|
||||||
mul->operator = BINOP_MUL;
|
|
||||||
mul->operands[0] = (AST*) scale;
|
|
||||||
mul->operands[1] = child->operands[1];
|
|
||||||
|
|
||||||
child->operands[1] = (AST*) mul;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASTExprUnaryOp *unop = alloc_node(P, sizeof(*unop));
|
|
||||||
unop->nodeKind = AST_EXPR_UNARY_OP;
|
|
||||||
unop->type = ret->expression.type->array.of;
|
|
||||||
unop->operator = UNOP_DEREF;
|
|
||||||
unop->operand = (AST*) child;
|
|
||||||
|
|
||||||
expect(P, TOKEN_SQUAREN_R);
|
|
||||||
|
|
||||||
ret = (AST*) unop;
|
|
||||||
}
|
|
||||||
|
|
||||||
P->scope = P->scope->parent;
|
|
||||||
|
|
||||||
} else abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
} else if(lOP == 4) {
|
} else if(lOP == 4) {
|
||||||
AST *ret = nct_parse_expression(P, lOP + 1);
|
AST *ret = nct_parse_expression(P, lOP + 1);
|
||||||
|
|
||||||
@@ -582,35 +597,6 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
|||||||
} else if(lOP == 1) {
|
} else if(lOP == 1) {
|
||||||
AST *ret = nct_parse_expression(P, lOP + 1);
|
AST *ret = nct_parse_expression(P, lOP + 1);
|
||||||
|
|
||||||
if(peek(P, 0).type == TOKEN_DOUBLE_AMPERSAND || peek(P, 0).type == TOKEN_DOUBLE_VERTICAL_BAR) {
|
|
||||||
while(1) {
|
|
||||||
BinaryOp op;
|
|
||||||
if(maybe(P, TOKEN_DOUBLE_AMPERSAND)) op = BINOP_LOGICAL_AND;
|
|
||||||
else if(maybe(P, TOKEN_DOUBLE_VERTICAL_BAR)) op = BINOP_LOGICAL_OR;
|
|
||||||
else break;
|
|
||||||
|
|
||||||
ASTExprBinaryOp *astop = alloc_node(P, sizeof(*astop));
|
|
||||||
astop->nodeKind = AST_EXPR_BINARY_OP;
|
|
||||||
astop->type = NULL;
|
|
||||||
astop->operator = op;
|
|
||||||
astop->operands[0] = ret;
|
|
||||||
|
|
||||||
ASTExpr *operand = &(astop->operands[1] = nct_parse_expression(P, lOP + 1))->expression;
|
|
||||||
|
|
||||||
if(operand->type->type != TYPE_TYPE_PRIMITIVE) {
|
|
||||||
stahp_token(&P->tokens[P->i], "Invalid combination of operator and operand types.");
|
|
||||||
}
|
|
||||||
|
|
||||||
binop_implicit_cast(astop);
|
|
||||||
|
|
||||||
ret = (AST*) astop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
} else if(lOP == 0) {
|
|
||||||
AST *ret = nct_parse_expression(P, lOP + 1);
|
|
||||||
|
|
||||||
if(peek(P, 0).type == TOKEN_DOUBLE_EQUALS || peek(P, 0).type == TOKEN_EXCLAMATION_EQUALS || peek(P, 0).type == TOKEN_LESS || peek(P, 0).type == TOKEN_GREATER || peek(P, 0).type == TOKEN_LEQUAL || peek(P, 0).type == TOKEN_GEQUAL) {
|
if(peek(P, 0).type == TOKEN_DOUBLE_EQUALS || peek(P, 0).type == TOKEN_EXCLAMATION_EQUALS || peek(P, 0).type == TOKEN_LESS || peek(P, 0).type == TOKEN_GREATER || peek(P, 0).type == TOKEN_LEQUAL || peek(P, 0).type == TOKEN_GEQUAL) {
|
||||||
while(1) {
|
while(1) {
|
||||||
BinaryOp op;
|
BinaryOp op;
|
||||||
@@ -622,6 +608,33 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
|||||||
else if(maybe(P, TOKEN_GEQUAL)) op = BINOP_GEQUAL;
|
else if(maybe(P, TOKEN_GEQUAL)) op = BINOP_GEQUAL;
|
||||||
else break;
|
else break;
|
||||||
|
|
||||||
|
ASTExprBinaryOp *astop = alloc_node(P, sizeof(*astop));
|
||||||
|
astop->nodeKind = AST_EXPR_BINARY_OP;
|
||||||
|
astop->type = type_u(1);
|
||||||
|
astop->operator = op;
|
||||||
|
astop->operands[0] = ret;
|
||||||
|
|
||||||
|
ASTExpr *operand = &(astop->operands[1] = nct_parse_expression(P, lOP + 1))->expression;
|
||||||
|
|
||||||
|
if(operand->type->type != TYPE_TYPE_PRIMITIVE) {
|
||||||
|
stahp_token(&P->tokens[P->i], "Invalid combination of operator and operand types.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = (AST*) astop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
} else if(lOP == 0) {
|
||||||
|
AST *ret = nct_parse_expression(P, lOP + 1);
|
||||||
|
|
||||||
|
if(peek(P, 0).type == TOKEN_DOUBLE_AMPERSAND || peek(P, 0).type == TOKEN_DOUBLE_VERTICAL_BAR) {
|
||||||
|
while(1) {
|
||||||
|
BinaryOp op;
|
||||||
|
if(maybe(P, TOKEN_DOUBLE_AMPERSAND)) op = BINOP_LOGICAL_AND;
|
||||||
|
else if(maybe(P, TOKEN_DOUBLE_VERTICAL_BAR)) op = BINOP_LOGICAL_OR;
|
||||||
|
else break;
|
||||||
|
|
||||||
ASTExprBinaryOp *astop = alloc_node(P, sizeof(*astop));
|
ASTExprBinaryOp *astop = alloc_node(P, sizeof(*astop));
|
||||||
astop->nodeKind = AST_EXPR_BINARY_OP;
|
astop->nodeKind = AST_EXPR_BINARY_OP;
|
||||||
astop->type = NULL;
|
astop->type = NULL;
|
||||||
@@ -1159,7 +1172,7 @@ static void nct_parse_statement(Parser *P) {
|
|||||||
ret->next = NULL;
|
ret->next = NULL;
|
||||||
|
|
||||||
if(!maybe(P, TOKEN_SEMICOLON)) {
|
if(!maybe(P, TOKEN_SEMICOLON)) {
|
||||||
ret->val = nct_parse_expression(P, 0);
|
ret->val = ast_cast_expr(nct_parse_expression(P, 0), P->topLevel->functionType->function.ret);
|
||||||
|
|
||||||
expect(P, TOKEN_SEMICOLON);
|
expect(P, TOKEN_SEMICOLON);
|
||||||
}
|
}
|
||||||
@@ -1223,21 +1236,6 @@ static void nct_parse_statement(Parser *P) {
|
|||||||
|
|
||||||
expect(P, TOKEN_SEMICOLON);
|
expect(P, TOKEN_SEMICOLON);
|
||||||
|
|
||||||
pushstat(P, ret);
|
|
||||||
return;
|
|
||||||
} else if(!strcmp(peek(P, 0).content, "@section")) {
|
|
||||||
ASTStmtExtSection *ret = alloc_node(P, sizeof(*ret));
|
|
||||||
ret->nodeKind = AST_STMT_EXT_SECTION;
|
|
||||||
ret->next = NULL;
|
|
||||||
|
|
||||||
get(P);
|
|
||||||
|
|
||||||
expect(P, TOKEN_PAREN_L);
|
|
||||||
ret->name = expect(P, TOKEN_STRING);
|
|
||||||
expect(P, TOKEN_PAREN_R);
|
|
||||||
|
|
||||||
expect(P, TOKEN_SEMICOLON);
|
|
||||||
|
|
||||||
pushstat(P, ret);
|
pushstat(P, ret);
|
||||||
return;
|
return;
|
||||||
} else if(!strcmp(peek(P, 0).content, "@instantiate")) {
|
} else if(!strcmp(peek(P, 0).content, "@instantiate")) {
|
||||||
@@ -1503,9 +1501,12 @@ Type *nct_parse_record_definition(Parser *P) {
|
|||||||
return tr;
|
return tr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void skim_chunk(Parser *P, int isTopLevel) {
|
/*
|
||||||
/* Find all symbol names and struct types ahead of time. Searches for colons as those can only mean symbol declarations */
|
* Find all symbol names and struct types ahead of time.
|
||||||
|
* Searches for colons as they can only mean symbol declarations.
|
||||||
|
* This function abuses Nectar's grammar to work, so malformed source
|
||||||
|
* code can fuck it up. */
|
||||||
|
static void skim_tokens(Parser *P, int isTopLevel) {
|
||||||
P->skimMode++;
|
P->skimMode++;
|
||||||
{
|
{
|
||||||
intmax_t oldIdx = P->i;
|
intmax_t oldIdx = P->i;
|
||||||
@@ -1593,7 +1594,7 @@ static void skim_chunk(Parser *P, int isTopLevel) {
|
|||||||
free(path);
|
free(path);
|
||||||
|
|
||||||
Parser subp = {.tokens = nct_lex(f), .scope = scope_new(NULL), .externalify = 1};
|
Parser subp = {.tokens = nct_lex(f), .scope = scope_new(NULL), .externalify = 1};
|
||||||
skim_chunk(&subp, 1);
|
skim_tokens(&subp, 1);
|
||||||
|
|
||||||
// Copy all extern symbols from the scope into our TLC's externs array
|
// Copy all extern symbols from the scope into our TLC's externs array
|
||||||
for(size_t i = 0; i < subp.scope->count; i++) {
|
for(size_t i = 0; i < subp.scope->count; i++) {
|
||||||
@@ -1637,10 +1638,10 @@ ASTChunk *nct_parse_chunk(Parser *P, int isTopLevel, int varPrioritize, Scope *t
|
|||||||
P->topLevel = &ret->chunk;
|
P->topLevel = &ret->chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
skim_chunk(P, isTopLevel);
|
|
||||||
|
|
||||||
/* Arguments */
|
/* Arguments */
|
||||||
if(ft && isTopLevel) {
|
if(ft && isTopLevel) {
|
||||||
|
P->topLevel->functionType = ft;
|
||||||
|
|
||||||
ScopeItem **vtes = alloca(sizeof(*vtes) * ft->function.argCount);
|
ScopeItem **vtes = alloca(sizeof(*vtes) * ft->function.argCount);
|
||||||
|
|
||||||
// First arguments in a function TLC is the arguments
|
// First arguments in a function TLC is the arguments
|
||||||
@@ -1661,7 +1662,7 @@ ASTChunk *nct_parse_chunk(Parser *P, int isTopLevel, int varPrioritize, Scope *t
|
|||||||
|
|
||||||
arch_add_hidden_variables(P->scope);
|
arch_add_hidden_variables(P->scope);
|
||||||
|
|
||||||
while(peek(P, 0).type != TOKEN_EOF && peek(P, 0).type != TOKEN_SQUIGGLY_R) {
|
while(peek(P, 0).type != TOKEN_EOF && peek(P, 0).type != TOKEN_SQUIGGLY_R && !(peek(P, 0).type == TOKEN_IDENTIFIER && !strcmp(peek(P, 0).content, "@section"))) {
|
||||||
nct_parse_statement(P);
|
nct_parse_statement(P);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1696,9 +1697,49 @@ ASTChunk *nct_parse_chunk(Parser *P, int isTopLevel, int varPrioritize, Scope *t
|
|||||||
return &ret->chunk;
|
return &ret->chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ASTSection *nct_parse_sections(Parser *P) {
|
||||||
|
ASTSection *start = alloc_node(P, sizeof(*start));
|
||||||
|
start->nodeKind = AST_SECTION;
|
||||||
|
|
||||||
|
ASTSection *current = start;
|
||||||
|
while(1) {
|
||||||
|
current->tlc = nct_parse_chunk(P, 1, 0, P->scope, NULL);
|
||||||
|
|
||||||
|
if(peek(P, 0).type == TOKEN_EOF) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!(peek(P, 0).type == TOKEN_IDENTIFIER && !strcmp(peek(P, 0).content, "@section"))) {
|
||||||
|
stahp_token(&P->tokens[P->i - 1], "Expected @section.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ASTSection *next = alloc_node(P, sizeof(*next));
|
||||||
|
next->nodeKind = AST_SECTION;
|
||||||
|
|
||||||
|
get(P);
|
||||||
|
expect(P, TOKEN_PAREN_L);
|
||||||
|
|
||||||
|
next->name = expect(P, TOKEN_STRING);
|
||||||
|
|
||||||
|
expect(P, TOKEN_PAREN_R);
|
||||||
|
expect(P, TOKEN_SEMICOLON);
|
||||||
|
|
||||||
|
current->next = next;
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
AST *nct_parse(Token *tokens) {
|
AST *nct_parse(Token *tokens) {
|
||||||
|
Scope *scope = scope_new(NULL);
|
||||||
|
|
||||||
Parser P;
|
Parser P;
|
||||||
memset(&P, 0, sizeof(P));
|
memset(&P, 0, sizeof(P));
|
||||||
P.tokens = tokens;
|
P.tokens = tokens;
|
||||||
return (AST*) nct_parse_chunk(&P, 1, 0, scope_new(NULL), NULL);
|
P.scope = scope;
|
||||||
|
|
||||||
|
skim_tokens(&P, 1);
|
||||||
|
|
||||||
|
return (AST*) nct_parse_sections(&P);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include<string.h>
|
#include<string.h>
|
||||||
#include<assert.h>
|
#include<assert.h>
|
||||||
#include<stdio.h>
|
#include<stdio.h>
|
||||||
|
#include"x86/arch.h"
|
||||||
|
|
||||||
Scope *scope_new(Scope *parent) {
|
Scope *scope_new(Scope *parent) {
|
||||||
Scope *ret = calloc(1, sizeof(*ret));
|
Scope *ret = calloc(1, sizeof(*ret));
|
||||||
@@ -81,7 +82,12 @@ void vte_precolor(ScopeItem *vte, int class, int color) {
|
|||||||
assert(vte->kind == SCOPEITEM_VAR && "vte must be var");
|
assert(vte->kind == SCOPEITEM_VAR && "vte must be var");
|
||||||
assert(!vte->data.var.precolored && "already precolored");
|
assert(!vte->data.var.precolored && "already precolored");
|
||||||
|
|
||||||
|
if(type_size(vte->type) > 0) {
|
||||||
|
assert(type_size(vte->type) == REG_CLASSES[class].rsS[color] && "Sizes must match in precoloring");
|
||||||
|
}
|
||||||
|
|
||||||
vte->data.var.precolored = true;
|
vte->data.var.precolored = true;
|
||||||
|
vte->data.var.preclassed = true;
|
||||||
vte->data.var.registerClass = class;
|
vte->data.var.registerClass = class;
|
||||||
vte->data.var.color = color;
|
vte->data.var.color = color;
|
||||||
}
|
}
|
||||||
|
|||||||
45
src/types.c
45
src/types.c
@@ -7,6 +7,7 @@
|
|||||||
#include"ast/ast.h"
|
#include"ast/ast.h"
|
||||||
#include"reporting.h"
|
#include"reporting.h"
|
||||||
#include<assert.h>
|
#include<assert.h>
|
||||||
|
#include"x86/arch.h"
|
||||||
|
|
||||||
#include"ntc.h"
|
#include"ntc.h"
|
||||||
|
|
||||||
@@ -23,6 +24,16 @@ Type *primitive_parse(const char *src) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!strcmp(src, "ugpr")) {
|
||||||
|
return type_u(8 * arch_gpr_size());
|
||||||
|
} else if(!strcmp(src, "sgpr")) {
|
||||||
|
return type_s(8 * arch_gpr_size());
|
||||||
|
} else if(!strcmp(src, "umem")) {
|
||||||
|
return type_u(arch_memory_width());
|
||||||
|
} else if(!strcmp(src, "smem")) {
|
||||||
|
return type_s(arch_memory_width());
|
||||||
|
}
|
||||||
|
|
||||||
TypePrimitive *ret = calloc(1, sizeof(*ret));
|
TypePrimitive *ret = calloc(1, sizeof(*ret));
|
||||||
ret->type = TYPE_TYPE_PRIMITIVE;
|
ret->type = TYPE_TYPE_PRIMITIVE;
|
||||||
ret->src = strdup(src);
|
ret->src = strdup(src);
|
||||||
@@ -200,7 +211,7 @@ int type_is_castable(Type *from, Type *to) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(from->type == TYPE_TYPE_PRIMITIVE && to->type == TYPE_TYPE_POINTER) {
|
if(from->type == TYPE_TYPE_PRIMITIVE && to->type == TYPE_TYPE_POINTER || from->type == TYPE_TYPE_POINTER && to->type == TYPE_TYPE_PRIMITIVE) {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,17 +297,19 @@ Type *type_parametrize(Type *t, Scope *scope) {
|
|||||||
|
|
||||||
AST *n = vte->data.cexpr.concrete;
|
AST *n = vte->data.cexpr.concrete;
|
||||||
|
|
||||||
if(n) {
|
while(n->nodeKind == AST_EXPR_VAR && n->exprVar.thing->kind == SCOPEITEM_CEXPR && n->exprVar.thing->data.cexpr.concrete != NULL) {
|
||||||
if(n->nodeKind == AST_EXPR_PRIMITIVE) {
|
n = n->exprVar.thing->data.cexpr.concrete;
|
||||||
t->array.length = n->exprPrim.val;
|
}
|
||||||
t->array.lengthIsGeneric = false;
|
|
||||||
} else if(n->nodeKind == AST_EXPR_VAR && n->exprVar.thing->kind == SCOPEITEM_CEXPR) {
|
if(n->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||||
t->array.lengthGenericParamIdx = n->exprVar.thing->data.cexpr.paramIdx;
|
t->array.length = n->exprPrim.val;
|
||||||
t->array.lengthGenericParamName = n->exprVar.thing->data.cexpr.paramName;
|
t->array.lengthIsGeneric = false;
|
||||||
t->array.lengthIsGeneric = true;
|
} else if(n->nodeKind == AST_EXPR_VAR && n->exprVar.thing->kind == SCOPEITEM_CEXPR) {
|
||||||
} else {
|
t->array.lengthGenericParamIdx = n->exprVar.thing->data.cexpr.paramIdx;
|
||||||
stahp_node(n, "Invalid parametrization expression.");
|
t->array.lengthGenericParamName = n->exprVar.thing->data.cexpr.paramName;
|
||||||
}
|
t->array.lengthIsGeneric = true;
|
||||||
|
} else {
|
||||||
|
stahp_node(n, "Invalid parametrization expression.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -357,3 +370,11 @@ Type *type_shallow_copy(Type *t) {
|
|||||||
}
|
}
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Type *type_prim_cast(Type *t, size_t bits) {
|
||||||
|
assert(t->type == TYPE_TYPE_PRIMITIVE);
|
||||||
|
|
||||||
|
Type *t2 = type_shallow_copy(t);
|
||||||
|
t2->primitive.width = bits;
|
||||||
|
return t2;
|
||||||
|
}
|
||||||
|
|||||||
29
src/types.h
29
src/types.h
@@ -6,6 +6,8 @@
|
|||||||
#include<stdbool.h>
|
#include<stdbool.h>
|
||||||
#include<string.h>
|
#include<string.h>
|
||||||
#include<stdio.h>
|
#include<stdio.h>
|
||||||
|
#include<assert.h>
|
||||||
|
#include<stdlib.h>
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
TYPE_TYPE_PRIMITIVE, TYPE_TYPE_RECORD, TYPE_TYPE_POINTER, TYPE_TYPE_FUNCTION, TYPE_TYPE_ARRAY, TYPE_TYPE_GENERIC, TYPE_TYPE_ERROR
|
TYPE_TYPE_PRIMITIVE, TYPE_TYPE_RECORD, TYPE_TYPE_POINTER, TYPE_TYPE_FUNCTION, TYPE_TYPE_ARRAY, TYPE_TYPE_GENERIC, TYPE_TYPE_ERROR
|
||||||
@@ -111,13 +113,36 @@ Type *type_shallow_copy(Type *t);
|
|||||||
|
|
||||||
bool type_is_generic(Type *t);
|
bool type_is_generic(Type *t);
|
||||||
|
|
||||||
|
Type *type_prim_cast(Type *t, size_t bits);
|
||||||
|
|
||||||
static inline bool type_is_segmented_pointer(Type *type) {
|
static inline bool type_is_segmented_pointer(Type *type) {
|
||||||
return type->type == TYPE_TYPE_RECORD && !!strstr(type->record.name, " @far*");
|
return type->type == TYPE_TYPE_RECORD && !!strstr(type->record.name, " @far*");
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline Type *type_u(size_t size) {
|
static inline bool type_is_any_pointer(Type *type) {
|
||||||
|
return type->type == TYPE_TYPE_POINTER || type_is_segmented_pointer(type);
|
||||||
|
}
|
||||||
|
static inline Type *type_dereference(Type *type) {
|
||||||
|
assert(type_is_any_pointer(type));
|
||||||
|
|
||||||
|
if(type->type == TYPE_TYPE_POINTER) {
|
||||||
|
return type->pointer.of;
|
||||||
|
} else if(type_is_segmented_pointer(type)) {
|
||||||
|
return type->record.fieldTypes[1]->pointer.of;
|
||||||
|
}
|
||||||
|
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline Type *type_u(size_t bits) {
|
||||||
char buf[32];
|
char buf[32];
|
||||||
snprintf(buf, sizeof(buf), "u%lu", size);
|
snprintf(buf, sizeof(buf), "u%lu", bits);
|
||||||
|
return primitive_parse(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline Type *type_s(size_t bits) {
|
||||||
|
char buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "s%lu", bits);
|
||||||
return primitive_parse(buf);
|
return primitive_parse(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,18 +133,20 @@ static inline int is_xop(AST *e) {
|
|||||||
return XOP_NOT_MEM;
|
return XOP_NOT_MEM;
|
||||||
} else if(e->nodeKind == AST_EXPR_VAR) {
|
} else if(e->nodeKind == AST_EXPR_VAR) {
|
||||||
return e->exprVar.thing->kind == SCOPEITEM_VAR ? XOP_NOT_MEM : XOP_MEM;
|
return e->exprVar.thing->kind == SCOPEITEM_VAR ? XOP_NOT_MEM : XOP_MEM;
|
||||||
} else if(e->nodeKind == AST_EXPR_UNARY_OP && e->exprUnOp.operator == UNOP_DEREF && e->exprUnOp.operand->nodeKind == AST_EXPR_BINARY_OP && e->exprUnOp.operand->exprBinOp.operator == BINOP_ADD && is_xop(e->exprUnOp.operand->exprBinOp.operands[0]) == XOP_NOT_MEM && is_xop(e->exprUnOp.operand->exprBinOp.operands[1]) == XOP_NOT_MEM) {
|
} else if(e->nodeKind == AST_EXPR_BINARY_OP && e->exprBinOp.operator == BINOP_DEREF && e->exprBinOp.operands[0]->nodeKind == AST_EXPR_BINARY_OP && e->exprBinOp.operands[0]->exprBinOp.operator == BINOP_ADD && is_xop(e->exprBinOp.operands[0]->exprBinOp.operands[0]) == XOP_NOT_MEM && is_xop(e->exprBinOp.operands[0]->exprBinOp.operands[1]) == XOP_NOT_MEM) {
|
||||||
return XOP_MEM;
|
return XOP_MEM;
|
||||||
} else if(e->nodeKind == AST_EXPR_UNARY_OP && e->exprUnOp.operator == UNOP_REF && e->exprUnOp.operand->nodeKind == AST_EXPR_VAR && e->exprUnOp.operand->exprVar.thing->kind == SCOPEITEM_SYMBOL) {
|
} else if(e->nodeKind == AST_EXPR_UNARY_OP && e->exprUnOp.operator == UNOP_REF && e->exprUnOp.operand->nodeKind == AST_EXPR_VAR && e->exprUnOp.operand->exprVar.thing->kind == SCOPEITEM_SYMBOL) {
|
||||||
return XOP_NOT_MEM;
|
return XOP_NOT_MEM;
|
||||||
} else if(e->nodeKind == AST_EXPR_UNARY_OP && e->exprUnOp.operator == UNOP_DEREF) {
|
} else if(e->nodeKind == AST_EXPR_BINARY_OP && e->exprBinOp.operator == BINOP_DEREF) {
|
||||||
AST *c = e->exprUnOp.operand;
|
AST *c = e->exprBinOp.operands[0];
|
||||||
|
|
||||||
if(c->nodeKind == AST_EXPR_CAST && c->exprCast.what->expression.type->type == TYPE_TYPE_POINTER && c->exprCast.to->type == TYPE_TYPE_POINTER) {
|
if(c->nodeKind == AST_EXPR_CAST && c->exprCast.what->expression.type->type == TYPE_TYPE_POINTER && c->exprCast.to->type == TYPE_TYPE_POINTER) {
|
||||||
c = c->exprCast.what;
|
c = c->exprCast.what;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(c->nodeKind == AST_EXPR_VAR && c->exprVar.thing->kind == SCOPEITEM_VAR) {
|
if(c->nodeKind == AST_EXPR_STACK_POINTER) {
|
||||||
|
return XOP_MEM;
|
||||||
|
} else if(c->nodeKind == AST_EXPR_VAR && c->exprVar.thing->kind == SCOPEITEM_VAR) {
|
||||||
if(x86_ia16() && !x86_is_marked_ptr(c->exprVar.thing)) {
|
if(x86_ia16() && !x86_is_marked_ptr(c->exprVar.thing)) {
|
||||||
// In IA-16, pointers MUST be preclassed to REG_CLASS_IA16_PTRS
|
// In IA-16, pointers MUST be preclassed to REG_CLASS_IA16_PTRS
|
||||||
return XOP_NOT_XOP;
|
return XOP_NOT_XOP;
|
||||||
@@ -241,9 +243,23 @@ static inline WhysItBad_Huh x86_test_mul(AST *stmtPrev, AST *stmt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(x86_imul_supported()) {
|
if(x86_imul_supported()) {
|
||||||
return GUCCI;
|
if(ast_expression_equal(stmt->stmtAssign.what, stmt->stmtAssign.to->exprBinOp.operands[0])) {
|
||||||
} else if(stmt->stmtAssign.to->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE) {
|
if(is_xop(stmt->stmtAssign.to->exprBinOp.operands[1]) == XOP_NOT_XOP) {
|
||||||
return SRC1_IS_BAD_XOP;
|
return SRC1_IS_BAD_XOP;
|
||||||
|
}
|
||||||
|
return GUCCI;
|
||||||
|
} else {
|
||||||
|
if(is_xop(stmt->stmtAssign.to->exprBinOp.operands[0]) == XOP_NOT_XOP) {
|
||||||
|
return SRC0_IS_BAD_XOP;
|
||||||
|
}
|
||||||
|
if(stmt->stmtAssign.to->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||||
|
return GUCCI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(stmt->stmtAssign.to->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||||
|
return SRC1_IS_BAD_XOP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(is_xop(stmt->stmtAssign.to->exprBinOp.operands[1]) == XOP_NOT_XOP) {
|
if(is_xop(stmt->stmtAssign.to->exprBinOp.operands[1]) == XOP_NOT_XOP) {
|
||||||
@@ -294,6 +310,18 @@ static inline int arch_gpr_size() {
|
|||||||
return x86_max_gpr_size();
|
return x86_max_gpr_size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline int arch_memory_width() {
|
||||||
|
switch(x86_target()) {
|
||||||
|
case I086:
|
||||||
|
case I186:
|
||||||
|
return 20;
|
||||||
|
case I286:
|
||||||
|
return 24;
|
||||||
|
default:
|
||||||
|
return 32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// lol
|
// lol
|
||||||
static inline bool is_reg_b(int cls, int res) {
|
static inline bool is_reg_b(int cls, int res) {
|
||||||
const char *name = REG_CLASSES[cls].rsN[res];
|
const char *name = REG_CLASSES[cls].rsN[res];
|
||||||
|
|||||||
395
src/x86/cg.c
395
src/x86/cg.c
@@ -12,6 +12,11 @@ static const char *BINOP_SIMPLE_INSTRS[] = {[BINOP_ADD] = "add", [BINOP_SUB] = "
|
|||||||
|
|
||||||
/*static size_t nextLocalLabel = 0;*/
|
/*static size_t nextLocalLabel = 0;*/
|
||||||
|
|
||||||
|
struct CalleeSavedState {
|
||||||
|
const char *reg[MAX_REGS_PER_CLASS];
|
||||||
|
size_t stackOffset[MAX_REGS_PER_CLASS];
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
/*#define LOOPSTACKSIZE 96
|
/*#define LOOPSTACKSIZE 96
|
||||||
size_t loopStackStart[LOOPSTACKSIZE];
|
size_t loopStackStart[LOOPSTACKSIZE];
|
||||||
@@ -21,6 +26,8 @@ typedef struct {
|
|||||||
int isFunction;
|
int isFunction;
|
||||||
|
|
||||||
AST *tlc;
|
AST *tlc;
|
||||||
|
|
||||||
|
struct CalleeSavedState calleeSaved;
|
||||||
} CGState;
|
} CGState;
|
||||||
|
|
||||||
static const char *direct(int size) {
|
static const char *direct(int size) {
|
||||||
@@ -108,11 +115,11 @@ static const char *xj(BinaryOp op) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static AST *is_field_access(AST *e) {
|
static AST *is_field_access(AST *e) {
|
||||||
if(e->nodeKind != AST_EXPR_UNARY_OP || e->exprUnOp.operator != UNOP_DEREF) {
|
if(e->nodeKind != AST_EXPR_BINARY_OP || e->exprUnOp.operator != BINOP_DEREF) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
e = e->exprUnOp.operand;
|
e = e->exprBinOp.operands[0];
|
||||||
|
|
||||||
if(e->nodeKind == AST_EXPR_CAST && e->exprCast.what->expression.type->type == TYPE_TYPE_POINTER && e->exprCast.to->type == TYPE_TYPE_POINTER) {
|
if(e->nodeKind == AST_EXPR_CAST && e->exprCast.what->expression.type->type == TYPE_TYPE_POINTER && e->exprCast.to->type == TYPE_TYPE_POINTER) {
|
||||||
e = e->exprCast.what;
|
e = e->exprCast.what;
|
||||||
@@ -129,9 +136,23 @@ static AST *is_field_access(AST *e) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *segment_or_empty(AST *a) {
|
||||||
|
if(!a) {
|
||||||
|
return "ds";
|
||||||
|
}
|
||||||
|
assert(a->nodeKind == AST_EXPR_VAR);
|
||||||
|
assert(a->exprVar.thing->kind == SCOPEITEM_VAR);
|
||||||
|
assert(a->exprVar.thing->data.var.registerClass == REG_CLASS_DATASEGS);
|
||||||
|
return REG_CLASSES[REG_CLASS_DATASEGS].rsN[a->exprVar.thing->data.var.color];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert a XOP-able expression into an x86 operand.
|
||||||
|
* Result MUST be determinstic and always the same, for the same given expression.
|
||||||
|
* */
|
||||||
static const char *xop_sz(AST *tlc, AST *e, int sz) {
|
static const char *xop_sz(AST *tlc, AST *e, int sz) {
|
||||||
#define XOPBUFS 16
|
#define XOPBUFS 16
|
||||||
#define XOPBUFSZ 32
|
#define XOPBUFSZ 64
|
||||||
static char bufs[XOPBUFS][XOPBUFSZ];
|
static char bufs[XOPBUFS][XOPBUFSZ];
|
||||||
static int bufidx = 0;
|
static int bufidx = 0;
|
||||||
|
|
||||||
@@ -139,21 +160,24 @@ static const char *xop_sz(AST *tlc, AST *e, int sz) {
|
|||||||
|
|
||||||
int pr = 0;
|
int pr = 0;
|
||||||
|
|
||||||
if(e->nodeKind == AST_EXPR_UNARY_OP && e->exprUnOp.operator == UNOP_DEREF) {
|
if(e->nodeKind == AST_EXPR_BINARY_OP && e->exprBinOp.operator == BINOP_DEREF) {
|
||||||
AST *p = e->exprUnOp.operand;
|
AST *seg = e->exprBinOp.operands[1];
|
||||||
|
AST *p = e->exprBinOp.operands[0];
|
||||||
|
|
||||||
if(p->nodeKind == AST_EXPR_CAST && p->exprCast.to->type == TYPE_TYPE_POINTER) {
|
if(p->nodeKind == AST_EXPR_CAST && p->exprCast.to->type == TYPE_TYPE_POINTER) {
|
||||||
p = p->exprCast.what;
|
p = p->exprCast.what;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(p->nodeKind == AST_EXPR_BINARY_OP && p->exprBinOp.operator == BINOP_ADD && p->exprBinOp.operands[0]->nodeKind == AST_EXPR_VAR && p->exprBinOp.operands[1]->nodeKind == AST_EXPR_VAR && p->exprBinOp.operands[0]->exprVar.thing->kind == SCOPEITEM_VAR && p->exprBinOp.operands[1]->exprVar.thing->kind == SCOPEITEM_VAR) {
|
if(p->nodeKind == AST_EXPR_BINARY_OP && p->exprBinOp.operator == BINOP_ADD && p->exprBinOp.operands[0]->nodeKind == AST_EXPR_VAR && p->exprBinOp.operands[1]->nodeKind == AST_EXPR_VAR && p->exprBinOp.operands[0]->exprVar.thing->kind == SCOPEITEM_VAR && p->exprBinOp.operands[1]->exprVar.thing->kind == SCOPEITEM_VAR) {
|
||||||
pr = snprintf(ret, XOPBUFSZ, "%s [%s + %s]",
|
pr = snprintf(ret, XOPBUFSZ, "%s %s:[%s + %s]",
|
||||||
spec(sz),
|
spec(sz),
|
||||||
|
segment_or_empty(seg),
|
||||||
xv_sz(p->exprBinOp.operands[0]->exprVar.thing, 0),
|
xv_sz(p->exprBinOp.operands[0]->exprVar.thing, 0),
|
||||||
xv_sz(p->exprBinOp.operands[1]->exprVar.thing, 0));
|
xv_sz(p->exprBinOp.operands[1]->exprVar.thing, 0));
|
||||||
} else if(p->nodeKind == AST_EXPR_BINARY_OP && p->exprBinOp.operator == BINOP_ADD && p->exprBinOp.operands[0]->nodeKind == AST_EXPR_UNARY_OP && p->exprBinOp.operands[1]->nodeKind == AST_EXPR_VAR && p->exprBinOp.operands[0]->exprUnOp.operator == UNOP_REF && p->exprBinOp.operands[0]->exprUnOp.operand->nodeKind == AST_EXPR_VAR && p->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->kind == SCOPEITEM_SYMBOL && p->exprBinOp.operands[1]->exprVar.thing->kind == SCOPEITEM_VAR) {
|
} else if(p->nodeKind == AST_EXPR_BINARY_OP && p->exprBinOp.operator == BINOP_ADD && p->exprBinOp.operands[0]->nodeKind == AST_EXPR_UNARY_OP && p->exprBinOp.operands[1]->nodeKind == AST_EXPR_VAR && p->exprBinOp.operands[0]->exprUnOp.operator == UNOP_REF && p->exprBinOp.operands[0]->exprUnOp.operand->nodeKind == AST_EXPR_VAR && p->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->kind == SCOPEITEM_SYMBOL && p->exprBinOp.operands[1]->exprVar.thing->kind == SCOPEITEM_VAR) {
|
||||||
pr = snprintf(ret, XOPBUFSZ, "%s [%s + %s]",
|
pr = snprintf(ret, XOPBUFSZ, "%s %s:[%s + %s]",
|
||||||
spec(sz),
|
spec(sz),
|
||||||
|
segment_or_empty(seg),
|
||||||
p->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->data.symbol.name,
|
p->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->data.symbol.name,
|
||||||
xv_sz(p->exprBinOp.operands[1]->exprVar.thing, 0));
|
xv_sz(p->exprBinOp.operands[1]->exprVar.thing, 0));
|
||||||
} else if(is_field_access(e)) {
|
} else if(is_field_access(e)) {
|
||||||
@@ -162,8 +186,9 @@ static const char *xop_sz(AST *tlc, AST *e, int sz) {
|
|||||||
if(e->exprBinOp.operands[0]->nodeKind == AST_EXPR_UNARY_OP) {
|
if(e->exprBinOp.operands[0]->nodeKind == AST_EXPR_UNARY_OP) {
|
||||||
assert(e->exprBinOp.operands[0]->exprUnOp.operator == UNOP_REF);
|
assert(e->exprBinOp.operands[0]->exprUnOp.operator == UNOP_REF);
|
||||||
|
|
||||||
pr = snprintf(ret, XOPBUFSZ, "%s [%s + %i]",
|
pr = snprintf(ret, XOPBUFSZ, "%s %s:[%s + %i]",
|
||||||
spec(sz),
|
spec(sz),
|
||||||
|
segment_or_empty(seg),
|
||||||
e->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->data.symbol.name,
|
e->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->data.symbol.name,
|
||||||
e->exprBinOp.operands[1]->exprPrim.val);
|
e->exprBinOp.operands[1]->exprPrim.val);
|
||||||
} else {
|
} else {
|
||||||
@@ -171,19 +196,27 @@ static const char *xop_sz(AST *tlc, AST *e, int sz) {
|
|||||||
|
|
||||||
ScopeItem *vte = e->exprBinOp.operands[0]->exprVar.thing;
|
ScopeItem *vte = e->exprBinOp.operands[0]->exprVar.thing;
|
||||||
|
|
||||||
pr = snprintf(ret, XOPBUFSZ, "%s [%s + %i]",
|
pr = snprintf(ret, XOPBUFSZ, "%s %s:[%s + %i]",
|
||||||
spec(sz),
|
spec(sz),
|
||||||
|
segment_or_empty(seg),
|
||||||
REG_CLASSES[vte->data.var.registerClass].rsN[vte->data.var.color],
|
REG_CLASSES[vte->data.var.registerClass].rsN[vte->data.var.color],
|
||||||
e->exprBinOp.operands[1]->exprPrim.val);
|
e->exprBinOp.operands[1]->exprPrim.val);
|
||||||
}
|
}
|
||||||
} else if(p->nodeKind == AST_EXPR_BINARY_OP && p->exprBinOp.operator == BINOP_ADD && p->exprBinOp.operands[0]->nodeKind == AST_EXPR_UNARY_OP && p->exprBinOp.operands[1]->nodeKind == AST_EXPR_BINARY_OP && p->exprBinOp.operands[0]->exprUnOp.operator == UNOP_REF && p->exprBinOp.operands[0]->exprUnOp.operand->nodeKind == AST_EXPR_VAR && p->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->kind == SCOPEITEM_SYMBOL && p->exprBinOp.operands[1]->exprBinOp.operator == BINOP_MUL && p->exprBinOp.operands[1]->exprBinOp.operands[1]->nodeKind == AST_EXPR_VAR && p->exprBinOp.operands[1]->exprBinOp.operands[0]->nodeKind == AST_EXPR_PRIMITIVE && p->exprBinOp.operands[1]->exprBinOp.operands[1]->exprVar.thing->kind == SCOPEITEM_VAR) {
|
} else if(p->nodeKind == AST_EXPR_BINARY_OP && p->exprBinOp.operator == BINOP_ADD && p->exprBinOp.operands[0]->nodeKind == AST_EXPR_UNARY_OP && p->exprBinOp.operands[1]->nodeKind == AST_EXPR_BINARY_OP && p->exprBinOp.operands[0]->exprUnOp.operator == UNOP_REF && p->exprBinOp.operands[0]->exprUnOp.operand->nodeKind == AST_EXPR_VAR && p->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->kind == SCOPEITEM_SYMBOL && p->exprBinOp.operands[1]->exprBinOp.operator == BINOP_MUL && p->exprBinOp.operands[1]->exprBinOp.operands[1]->nodeKind == AST_EXPR_VAR && p->exprBinOp.operands[1]->exprBinOp.operands[0]->nodeKind == AST_EXPR_PRIMITIVE && p->exprBinOp.operands[1]->exprBinOp.operands[1]->exprVar.thing->kind == SCOPEITEM_VAR) {
|
||||||
pr = snprintf(ret, XOPBUFSZ, "%s [%s + %i * %s]",
|
pr = snprintf(ret, XOPBUFSZ, "%s %s:[%s + %i * %s]",
|
||||||
spec(sz),
|
spec(sz),
|
||||||
|
segment_or_empty(seg),
|
||||||
p->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->data.symbol.name,
|
p->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->data.symbol.name,
|
||||||
p->exprBinOp.operands[1]->exprBinOp.operands[0]->exprPrim.val,
|
p->exprBinOp.operands[1]->exprBinOp.operands[0]->exprPrim.val,
|
||||||
xv_sz(p->exprBinOp.operands[1]->exprBinOp.operands[1]->exprVar.thing, 0));
|
xv_sz(p->exprBinOp.operands[1]->exprBinOp.operands[1]->exprVar.thing, 0));
|
||||||
} else if(p->nodeKind == AST_EXPR_VAR && p->exprVar.thing->kind == SCOPEITEM_VAR) {
|
} else if(p->nodeKind == AST_EXPR_VAR && p->exprVar.thing->kind == SCOPEITEM_VAR) {
|
||||||
pr = snprintf(ret, XOPBUFSZ, "%s [%s]", spec(sz), xv_sz(p->exprVar.thing, 0));
|
pr = snprintf(ret, XOPBUFSZ, "%s %s:[%s]", spec(sz), segment_or_empty(seg), xv_sz(p->exprVar.thing, 0));
|
||||||
|
} else if(p->nodeKind == AST_EXPR_STACK_POINTER) {
|
||||||
|
if(x86_ia16()) {
|
||||||
|
pr = snprintf(ret, XOPBUFSZ, "[bp + %li]", 0 - tlc->chunk.stackReservation);
|
||||||
|
} else {
|
||||||
|
pr = snprintf(ret, XOPBUFSZ, "[esp + %i]", 0);
|
||||||
|
}
|
||||||
} else if(p->nodeKind == AST_EXPR_BINARY_OP && p->exprBinOp.operator == BINOP_ADD && p->exprBinOp.operands[0]->nodeKind == AST_EXPR_STACK_POINTER && p->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE) {
|
} else if(p->nodeKind == AST_EXPR_BINARY_OP && p->exprBinOp.operator == BINOP_ADD && p->exprBinOp.operands[0]->nodeKind == AST_EXPR_STACK_POINTER && p->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||||
if(x86_ia16()) {
|
if(x86_ia16()) {
|
||||||
pr = snprintf(ret, XOPBUFSZ, "[bp + %li]", p->exprBinOp.operands[1]->exprPrim.val - tlc->chunk.stackReservation);
|
pr = snprintf(ret, XOPBUFSZ, "[bp + %li]", p->exprBinOp.operands[1]->exprPrim.val - tlc->chunk.stackReservation);
|
||||||
@@ -243,6 +276,27 @@ static int lr_empty(ScopeItem *a) {
|
|||||||
return !a->data.var.liveRangeStart;
|
return !a->data.var.liveRangeStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool is_resource_in_use_right_now_at_this_current_point_in_time_and_also_not_created_right_now(AST *tlc, AST *statement, size_t resource) {
|
||||||
|
for(size_t vi = 0; vi < tlc->chunk.varCount; vi++) {
|
||||||
|
ScopeItem *si = tlc->chunk.vars[vi];
|
||||||
|
if((REG_CLASSES[si->data.var.registerClass].rs[si->data.var.color] & resource) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(ast_stmt_is_after(tlc, statement, si->data.var.liveRangeEnd) == 1 || statement == si->data.var.liveRangeEnd) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(si->data.var.usedefFirst == NULL) {
|
||||||
|
// Even if the resource is in use, the variable itself isn't so we don't care
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(!lr_empty(si) && (ast_stmt_is_after(tlc, statement, si->data.var.liveRangeStart) == 1
|
||||||
|
&& ast_stmt_is_after(tlc, si->data.var.liveRangeEnd, statement) == 1)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void cg_chunk(CGState *cg, AST *a) {
|
void cg_chunk(CGState *cg, AST *a) {
|
||||||
AST *s = a->chunk.statementFirst;
|
AST *s = a->chunk.statementFirst;
|
||||||
|
|
||||||
@@ -254,6 +308,17 @@ void cg_chunk(CGState *cg, AST *a) {
|
|||||||
} else {
|
} else {
|
||||||
printf("sub esp, %lu\n", a->chunk.stackReservation);
|
printf("sub esp, %lu\n", a->chunk.stackReservation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < MAX_REGS_PER_CLASS && cg->calleeSaved.reg[i]; i++) {
|
||||||
|
if(x86_ia16()) {
|
||||||
|
printf("mov [bp + %li], %s\n", cg->calleeSaved.stackOffset[i] - a->chunk.stackReservation, cg->calleeSaved.reg[i]);
|
||||||
|
} else {
|
||||||
|
printf("mov [esp + %li], %s\n", cg->calleeSaved.stackOffset[i], cg->calleeSaved.reg[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If there's no stack reservation, there can't be callee-saved regs.
|
||||||
|
assert(cg->calleeSaved.reg[0] == NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Potentially complex pattern matching
|
// Potentially complex pattern matching
|
||||||
@@ -272,15 +337,6 @@ void cg_chunk(CGState *cg, AST *a) {
|
|||||||
printf("jmp .%s\n", s->stmtJump.label);
|
printf("jmp .%s\n", s->stmtJump.label);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if(s->nodeKind == AST_STMT_EXT_SECTION) {
|
|
||||||
|
|
||||||
Token t = s->stmtExtSection.name;
|
|
||||||
printf("section %.*s\n", (int) t.length, t.content);
|
|
||||||
|
|
||||||
} else if(s->nodeKind == AST_STMT_EXT_ORG) {
|
|
||||||
|
|
||||||
printf("org %lu\n", s->stmtExtOrg.val);
|
|
||||||
|
|
||||||
} else if(s->nodeKind == AST_STMT_EXT_ALIGN) {
|
} else if(s->nodeKind == AST_STMT_EXT_ALIGN) {
|
||||||
|
|
||||||
uint32_t val = s->stmtExtAlign.val;
|
uint32_t val = s->stmtExtAlign.val;
|
||||||
@@ -336,13 +392,13 @@ void cg_chunk(CGState *cg, AST *a) {
|
|||||||
|
|
||||||
ast_linearize(s->stmtDecl.expression->exprFunc.chunk);
|
ast_linearize(s->stmtDecl.expression->exprFunc.chunk);
|
||||||
|
|
||||||
dumben_pre(s->stmtDecl.expression->exprFunc.chunk);
|
arch_normalize(s->stmtDecl.expression->exprFunc.chunk);
|
||||||
|
while(!cg_attempt_sections(s->stmtDecl.expression->exprFunc.chunk)) {
|
||||||
dumben_go(s->stmtDecl.expression->exprFunc.chunk);
|
arch_normalize(s->stmtDecl.expression->exprFunc.chunk);
|
||||||
while(!cg_go(s->stmtDecl.expression->exprFunc.chunk)) {
|
|
||||||
dumben_go(s->stmtDecl.expression->exprFunc.chunk);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cg_go_sections(s->stmtDecl.expression->exprFunc.chunk);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else abort();
|
} else abort();
|
||||||
@@ -362,12 +418,14 @@ void cg_chunk(CGState *cg, AST *a) {
|
|||||||
|
|
||||||
AST *e = s->stmtAssign.to;
|
AST *e = s->stmtAssign.to;
|
||||||
|
|
||||||
if(x86_ia16()) {
|
if(is_resource_in_use_right_now_at_this_current_point_in_time_and_also_not_created_right_now(a, s, HWR_ECX)) {
|
||||||
puts("push cx");
|
puts(x86_ia16() ? "push cx" : "push ecx");
|
||||||
puts("push dx");
|
}
|
||||||
} else {
|
if(is_resource_in_use_right_now_at_this_current_point_in_time_and_also_not_created_right_now(a, s, HWR_EDX)) {
|
||||||
puts("push ecx");
|
puts(x86_ia16() ? "push dx" : "push edx");
|
||||||
puts("push edx");
|
}
|
||||||
|
if(is_resource_in_use_right_now_at_this_current_point_in_time_and_also_not_created_right_now(a, s, HWR_EAX)) {
|
||||||
|
puts(x86_ia16() ? "push ax" : "push eax");
|
||||||
}
|
}
|
||||||
|
|
||||||
int argCount = e->exprCall.what->expression.type->pointer.of->function.argCount;
|
int argCount = e->exprCall.what->expression.type->pointer.of->function.argCount;
|
||||||
@@ -394,12 +452,14 @@ void cg_chunk(CGState *cg, AST *a) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(x86_ia16()) {
|
if(is_resource_in_use_right_now_at_this_current_point_in_time_and_also_not_created_right_now(a, s, HWR_EAX)) {
|
||||||
puts("pop dx");
|
puts(x86_ia16() ? "pop ax" : "pop eax");
|
||||||
puts("pop cx");
|
}
|
||||||
} else {
|
if(is_resource_in_use_right_now_at_this_current_point_in_time_and_also_not_created_right_now(a, s, HWR_EDX)) {
|
||||||
puts("pop edx");
|
puts(x86_ia16() ? "pop dx" : "pop edx");
|
||||||
puts("pop ecx");
|
}
|
||||||
|
if(is_resource_in_use_right_now_at_this_current_point_in_time_and_also_not_created_right_now(a, s, HWR_ECX)) {
|
||||||
|
puts(x86_ia16() ? "pop cx" : "pop ecx");
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if(s->nodeKind == AST_STMT_ASSIGN) {
|
} else if(s->nodeKind == AST_STMT_ASSIGN) {
|
||||||
@@ -412,7 +472,7 @@ void cg_chunk(CGState *cg, AST *a) {
|
|||||||
assert(s->stmtAssign.what->exprVar.thing->kind == SCOPEITEM_VAR);
|
assert(s->stmtAssign.what->exprVar.thing->kind == SCOPEITEM_VAR);
|
||||||
|
|
||||||
if(!strcmp(xop(a, s->stmtAssign.what), xop(a, s->stmtAssign.to->exprBinOp.operands[0]))) {
|
if(!strcmp(xop(a, s->stmtAssign.what), xop(a, s->stmtAssign.to->exprBinOp.operands[0]))) {
|
||||||
printf("imul %s, %s, %s\n", xop(a, s->stmtAssign.what), xop(a, s->stmtAssign.what), xop(a, s->stmtAssign.to->exprBinOp.operands[1]));
|
printf("imul %s, %s\n", xop(a, s->stmtAssign.what), xop(a, s->stmtAssign.to->exprBinOp.operands[1]));
|
||||||
} else {
|
} else {
|
||||||
printf("imul %s, %s, %s\n", xop(a, s->stmtAssign.what), xop(a, s->stmtAssign.to->exprBinOp.operands[0]), xop(a, s->stmtAssign.to->exprBinOp.operands[1]));
|
printf("imul %s, %s, %s\n", xop(a, s->stmtAssign.what), xop(a, s->stmtAssign.to->exprBinOp.operands[0]), xop(a, s->stmtAssign.to->exprBinOp.operands[1]));
|
||||||
}
|
}
|
||||||
@@ -475,10 +535,18 @@ void cg_chunk(CGState *cg, AST *a) {
|
|||||||
|
|
||||||
} else if(is_xop(s->stmtAssign.what) && s->stmtAssign.to->nodeKind == AST_EXPR_CAST) {
|
} else if(is_xop(s->stmtAssign.what) && s->stmtAssign.to->nodeKind == AST_EXPR_CAST) {
|
||||||
|
|
||||||
if(type_size(s->stmtAssign.what->expression.type) > type_size(s->stmtAssign.to->expression.type)) {
|
if(type_size(s->stmtAssign.what->expression.type) > type_size(s->stmtAssign.to->exprCast.what->expression.type)) {
|
||||||
|
|
||||||
printf("movzx %s, %s\n", xop(cg->tlc, s->stmtAssign.what), xop(cg->tlc, s->stmtAssign.to->exprCast.what));
|
printf("movzx %s, %s\n", xop(cg->tlc, s->stmtAssign.what), xop(cg->tlc, s->stmtAssign.to->exprCast.what));
|
||||||
|
|
||||||
|
} else if(type_size(s->stmtAssign.what->expression.type) > type_size(s->stmtAssign.to->exprCast.what->expression.type)) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* LEMMA: For every register in x86, its lowest bits are accessible.
|
||||||
|
*/
|
||||||
|
|
||||||
|
assert(0 && "Not implemented.");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
const char *dest = xop_sz(cg->tlc, s->stmtAssign.what, x86_max_gpr_size());
|
const char *dest = xop_sz(cg->tlc, s->stmtAssign.what, x86_max_gpr_size());
|
||||||
@@ -542,10 +610,17 @@ void cg_chunk(CGState *cg, AST *a) {
|
|||||||
if(s->stmtReturn.val) {
|
if(s->stmtReturn.val) {
|
||||||
assert(s->stmtReturn.val->nodeKind == AST_EXPR_VAR);
|
assert(s->stmtReturn.val->nodeKind == AST_EXPR_VAR);
|
||||||
assert(s->stmtReturn.val->exprVar.thing->kind == SCOPEITEM_VAR);
|
assert(s->stmtReturn.val->exprVar.thing->kind == SCOPEITEM_VAR);
|
||||||
//assert(s->stmtReturn.val->exprVar.thing->data.var.color == COLOR_EAX);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(a->chunk.stackReservation) {
|
if(a->chunk.stackReservation) {
|
||||||
|
for(int i = 0; i < MAX_REGS_PER_CLASS && cg->calleeSaved.reg[i]; i++) {
|
||||||
|
if(x86_ia16()) {
|
||||||
|
printf("mov %s, [bp + %li]\n", cg->calleeSaved.reg[i], cg->calleeSaved.stackOffset[i] - a->chunk.stackReservation);
|
||||||
|
} else {
|
||||||
|
printf("mov %s, [esp + %li]\n", cg->calleeSaved.reg[i], cg->calleeSaved.stackOffset[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(x86_ia16()) {
|
if(x86_ia16()) {
|
||||||
printf("add sp, %lu\n", a->chunk.stackReservation);
|
printf("add sp, %lu\n", a->chunk.stackReservation);
|
||||||
} else {
|
} else {
|
||||||
@@ -564,14 +639,6 @@ void cg_chunk(CGState *cg, AST *a) {
|
|||||||
|
|
||||||
printf("ret\n");
|
printf("ret\n");
|
||||||
|
|
||||||
} else if(s->nodeKind == AST_STMT_EXPR && s->stmtExpr.expr->nodeKind == AST_EXPR_VAR) {
|
|
||||||
|
|
||||||
/* Loop guard, probably. */
|
|
||||||
|
|
||||||
} else if(s->nodeKind == AST_STMT_EXPR && s->stmtExpr.expr->nodeKind == AST_EXPR_UNARY_OP && s->stmtExpr.expr->exprUnOp.operator == UNOP_DEREF && s->stmtExpr.expr->exprUnOp.operand->nodeKind == AST_EXPR_BINARY_OP && s->stmtExpr.expr->exprUnOp.operand->exprBinOp.operator == BINOP_ADD && s->stmtExpr.expr->exprUnOp.operand->exprBinOp.operands[0]->nodeKind == AST_EXPR_STACK_POINTER && s->stmtExpr.expr->exprUnOp.operand->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE) {
|
|
||||||
|
|
||||||
/* Loop guard for a spilled variable, probably. */
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
stahp_node(s, "Unknown statement %s caught by code generator.", AST_KIND_STR[s->nodeKind]);
|
stahp_node(s, "Unknown statement %s caught by code generator.", AST_KIND_STR[s->nodeKind]);
|
||||||
@@ -599,170 +666,48 @@ static bool var_collision(AST *tlc, ScopeItem *v1, ScopeItem *v2) {
|
|||||||
return liveRangeIntersection && resourceIntersection;
|
return liveRangeIntersection && resourceIntersection;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CalleeSavedState {
|
static void callee_saved(AST *tlc, struct CalleeSavedState *state) {
|
||||||
AST *targetTLC;
|
bool ebxused = false, ediused = false, esiused = false, ebpused = false;
|
||||||
|
|
||||||
ScopeItem *calleeUsers[MAX_REGS_PER_CLASS];
|
|
||||||
size_t calleeOffsets[MAX_REGS_PER_CLASS];
|
|
||||||
|
|
||||||
// To make sure we don't process the same return statement to infinity
|
|
||||||
AST *lastProcessedReturn;
|
|
||||||
};
|
|
||||||
static void callee_saved_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
|
||||||
struct CalleeSavedState *this = ud;
|
|
||||||
|
|
||||||
AST *n = *nptr;
|
|
||||||
|
|
||||||
if(tlc != this->targetTLC) {
|
|
||||||
// Don't do anything.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(n == tlc) {
|
|
||||||
// Function entry
|
|
||||||
|
|
||||||
for(int i = 0; i < MAX_REGS_PER_CLASS; i++) {
|
|
||||||
ScopeItem *vte = this->calleeUsers[i];
|
|
||||||
|
|
||||||
if(!vte) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASTExprStackPointer *stk = calloc(1, sizeof(*stk));
|
|
||||||
stk->nodeKind = AST_EXPR_STACK_POINTER;
|
|
||||||
stk->type = primitive_parse("u32");
|
|
||||||
|
|
||||||
ASTExprPrimitive *offset = calloc(1, sizeof(*offset));
|
|
||||||
offset->nodeKind = AST_EXPR_PRIMITIVE;
|
|
||||||
offset->type = primitive_parse("u32");
|
|
||||||
offset->val = this->calleeOffsets[i];
|
|
||||||
offset->stackGrowth = true;
|
|
||||||
|
|
||||||
ASTExprBinaryOp *sum = calloc(1, sizeof(*sum));
|
|
||||||
sum->nodeKind = AST_EXPR_BINARY_OP;
|
|
||||||
sum->type = offset->type;
|
|
||||||
sum->operator = BINOP_ADD;
|
|
||||||
sum->operands[0] = (AST*) stk;
|
|
||||||
sum->operands[1] = (AST*) offset;
|
|
||||||
|
|
||||||
ASTExprUnaryOp *deref = calloc(1, sizeof(*deref));
|
|
||||||
deref->nodeKind = AST_EXPR_UNARY_OP;
|
|
||||||
deref->type = offset->type;
|
|
||||||
deref->operator = UNOP_DEREF;
|
|
||||||
deref->operand = (AST*) sum;
|
|
||||||
|
|
||||||
ASTExprVar *ev = calloc(1, sizeof(*ev));
|
|
||||||
ev->nodeKind = AST_EXPR_VAR;
|
|
||||||
ev->type = vte->type;
|
|
||||||
ev->thing = vte;
|
|
||||||
|
|
||||||
ASTStmtAssign *assign = calloc(1, sizeof(*assign));
|
|
||||||
assign->nodeKind = AST_STMT_ASSIGN;
|
|
||||||
assign->what = (AST*) deref;
|
|
||||||
assign->to = (AST*) ev;
|
|
||||||
|
|
||||||
assign->next = tlc->chunk.statementFirst;
|
|
||||||
tlc->chunk.statementFirst = (AST*) assign;
|
|
||||||
|
|
||||||
assert(tlc->chunk.statementLast != NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if(n->nodeKind == AST_STMT_RETURN && n != this->lastProcessedReturn) {
|
|
||||||
// Function exit
|
|
||||||
|
|
||||||
this->lastProcessedReturn = n;
|
|
||||||
|
|
||||||
for(int i = 0; i < MAX_REGS_PER_CLASS; i++) {
|
|
||||||
ScopeItem *vte = this->calleeUsers[i];
|
|
||||||
|
|
||||||
if(!vte) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASTExprStackPointer *stk = calloc(1, sizeof(*stk));
|
|
||||||
stk->nodeKind = AST_EXPR_STACK_POINTER;
|
|
||||||
stk->type = primitive_parse("u32");
|
|
||||||
|
|
||||||
ASTExprPrimitive *offset = calloc(1, sizeof(*offset));
|
|
||||||
offset->nodeKind = AST_EXPR_PRIMITIVE;
|
|
||||||
offset->type = primitive_parse("u32");
|
|
||||||
offset->val = this->calleeOffsets[i];
|
|
||||||
offset->stackGrowth = true;
|
|
||||||
|
|
||||||
ASTExprBinaryOp *sum = calloc(1, sizeof(*sum));
|
|
||||||
sum->nodeKind = AST_EXPR_BINARY_OP;
|
|
||||||
sum->type = offset->type;
|
|
||||||
sum->operator = BINOP_ADD;
|
|
||||||
sum->operands[0] = (AST*) stk;
|
|
||||||
sum->operands[1] = (AST*) offset;
|
|
||||||
|
|
||||||
ASTExprUnaryOp *deref = calloc(1, sizeof(*deref));
|
|
||||||
deref->nodeKind = AST_EXPR_UNARY_OP;
|
|
||||||
deref->type = offset->type;
|
|
||||||
deref->operator = UNOP_DEREF;
|
|
||||||
deref->operand = (AST*) sum;
|
|
||||||
|
|
||||||
ASTExprVar *ev = calloc(1, sizeof(*ev));
|
|
||||||
ev->nodeKind = AST_EXPR_VAR;
|
|
||||||
ev->type = vte->type;
|
|
||||||
ev->thing = vte;
|
|
||||||
|
|
||||||
ASTStmtAssign *assign = calloc(1, sizeof(*assign));
|
|
||||||
assign->nodeKind = AST_STMT_ASSIGN;
|
|
||||||
assign->what = (AST*) ev;
|
|
||||||
assign->to = (AST*) deref;
|
|
||||||
assign->next = stmt;
|
|
||||||
|
|
||||||
if(stmtPrev) {
|
|
||||||
stmtPrev->statement.next = (AST*) assign;
|
|
||||||
} else {
|
|
||||||
tlc->chunk.statementFirst = (AST*) assign;
|
|
||||||
}
|
|
||||||
stmtPrev = (AST*) assign;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void callee_saved(AST *tlc) {
|
|
||||||
ScopeItem *ebxuser = NULL, *ediuser = NULL, *esiuser = NULL;
|
|
||||||
|
|
||||||
for(size_t v = 0; v < tlc->chunk.varCount; v++) {
|
for(size_t v = 0; v < tlc->chunk.varCount; v++) {
|
||||||
if(is_reg_b(tlc->chunk.vars[v]->data.var.registerClass, tlc->chunk.vars[v]->data.var.color)) {
|
size_t resource = REG_CLASSES[tlc->chunk.vars[v]->data.var.registerClass].rs[tlc->chunk.vars[v]->data.var.color];
|
||||||
ebxuser = tlc->chunk.vars[v];
|
|
||||||
|
if(resource & HWR_EBX) {
|
||||||
|
ebxused = true;
|
||||||
}
|
}
|
||||||
if(is_reg_di(tlc->chunk.vars[v]->data.var.registerClass, tlc->chunk.vars[v]->data.var.color)) {
|
if(resource & HWR_EDI) {
|
||||||
ediuser = tlc->chunk.vars[v];
|
ediused = true;
|
||||||
}
|
}
|
||||||
if(is_reg_si(tlc->chunk.vars[v]->data.var.registerClass, tlc->chunk.vars[v]->data.var.color)) {
|
if(resource & HWR_ESI) {
|
||||||
esiuser = tlc->chunk.vars[v];
|
esiused = true;
|
||||||
|
}
|
||||||
|
if(resource & HWR_EBP) {
|
||||||
|
ebpused = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CalleeSavedState state = {};
|
|
||||||
state.targetTLC = tlc;
|
|
||||||
|
|
||||||
size_t nextUser = 0;
|
size_t nextUser = 0;
|
||||||
if(ebxuser) {
|
if(ebxused) {
|
||||||
state.calleeOffsets[nextUser] = nextUser * 4;
|
state->stackOffset[nextUser] = nextUser * x86_max_gpr_size();
|
||||||
state.calleeUsers[nextUser] = ebxuser;
|
state->reg[nextUser] = x86_ia16() ? "bx" : "ebx";
|
||||||
nextUser++;
|
nextUser++;
|
||||||
}
|
}
|
||||||
if(esiuser) {
|
if(esiused) {
|
||||||
state.calleeOffsets[nextUser] = nextUser * 4;
|
state->stackOffset[nextUser] = nextUser * x86_max_gpr_size();
|
||||||
state.calleeUsers[nextUser] = esiuser;
|
state->reg[nextUser] = x86_ia16() ? "si" : "esi";
|
||||||
nextUser++;
|
nextUser++;
|
||||||
}
|
}
|
||||||
if(ediuser) {
|
if(ediused) {
|
||||||
state.calleeOffsets[nextUser] = nextUser * 4;
|
state->stackOffset[nextUser] = nextUser * x86_max_gpr_size();
|
||||||
state.calleeUsers[nextUser] = ediuser;
|
state->reg[nextUser] = x86_ia16() ? "di" : "edi";
|
||||||
nextUser++;
|
nextUser++;
|
||||||
}
|
}
|
||||||
ast_grow_stack_frame(tlc, nextUser * 4);
|
if(ebpused) {
|
||||||
|
state->stackOffset[nextUser] = nextUser * x86_max_gpr_size();
|
||||||
if(nextUser) {
|
state->reg[nextUser] = x86_ia16() ? "bp" : "ebp";
|
||||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, &state, callee_saved_visitor, NULL);
|
nextUser++;
|
||||||
}
|
}
|
||||||
|
ast_grow_stack_frame(tlc, nextUser * x86_max_gpr_size());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void determine_register_classes(AST *tlc) {
|
static void determine_register_classes(AST *tlc) {
|
||||||
@@ -846,7 +791,7 @@ nextColor:;
|
|||||||
return mustSpillRegisterClass;
|
return mustSpillRegisterClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cg_go(AST *a) {
|
int cg_tlc(AST *a) {
|
||||||
assert(a->nodeKind == AST_CHUNK);
|
assert(a->nodeKind == AST_CHUNK);
|
||||||
|
|
||||||
for(size_t e = 0; e < a->chunk.externCount; e++) {
|
for(size_t e = 0; e < a->chunk.externCount; e++) {
|
||||||
@@ -856,6 +801,23 @@ int cg_go(AST *a) {
|
|||||||
printf("extern %s\n", a->chunk.externs[e]->data.symbol.name);
|
printf("extern %s\n", a->chunk.externs[e]->data.symbol.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CalleeSavedState calleeSaved = {};
|
||||||
|
if(a->chunk.functionType) {
|
||||||
|
callee_saved(a, &calleeSaved);
|
||||||
|
}
|
||||||
|
|
||||||
|
CGState cg;
|
||||||
|
memset(&cg, 0, sizeof(cg));
|
||||||
|
cg.tlc = a;
|
||||||
|
cg.isFunction = !!a->chunk.functionType;
|
||||||
|
cg.calleeSaved = calleeSaved;
|
||||||
|
|
||||||
|
cg_chunk(&cg, a);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cg_attempt(AST *a) {
|
||||||
ast_usedef_reset(a);
|
ast_usedef_reset(a);
|
||||||
|
|
||||||
size_t adjCount = 0;
|
size_t adjCount = 0;
|
||||||
@@ -945,17 +907,36 @@ cont:;
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(a->chunk.functionType) {
|
return 1;
|
||||||
callee_saved(a);
|
}
|
||||||
|
|
||||||
|
int cg_attempt_sections(AST *a) {
|
||||||
|
assert(a->nodeKind == AST_SECTION);
|
||||||
|
|
||||||
|
ASTSection *current = &a->section;
|
||||||
|
while(current) {
|
||||||
|
if(!cg_attempt(current->tlc)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
CGState cg;
|
|
||||||
memset(&cg, 0, sizeof(cg));
|
|
||||||
cg.tlc = a;
|
|
||||||
cg.isFunction = !!a->chunk.functionType;
|
|
||||||
|
|
||||||
cg_chunk(&cg, a);
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cg_go_sections(AST *a) {
|
||||||
|
assert(a->nodeKind == AST_SECTION);
|
||||||
|
|
||||||
|
ASTSection *current = &a->section;
|
||||||
|
while(current) {
|
||||||
|
if(current->name.content) {
|
||||||
|
printf("section %s\n", current->name.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
cg_tlc(current->tlc);
|
||||||
|
|
||||||
|
current = current->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,36 +6,20 @@
|
|||||||
#include"reporting.h"
|
#include"reporting.h"
|
||||||
#include"utils.h"
|
#include"utils.h"
|
||||||
|
|
||||||
// This is the dumbing down pass.
|
// This is the normalization pass:
|
||||||
//
|
// Complex expressions are to be "broken down" into simpler ones until
|
||||||
// Complex expressions are to be "broken down" into simpler ones until the AST
|
// the AST can be trivially translated to the target architecture.
|
||||||
// can be trivially translated to the target architecture.
|
// This file along with CG is strictly for IA-32 & IA-16 and will fail
|
||||||
//
|
// for other architectures.
|
||||||
// This file along with CG is strictly for IA-32 and will fail for other
|
|
||||||
// architectures.
|
|
||||||
|
|
||||||
static ScopeItem *create_dumbtemp(AST *tlc, Type *itstype) {
|
static ScopeItem *create_temp(AST *tlc, Type *itstype) {
|
||||||
static size_t vidx = 0;
|
static size_t vidx = 0;
|
||||||
|
return ast_tlc_new_var(tlc, malp("nrm%lu", vidx++), itstype);
|
||||||
ScopeItem *vte = calloc(1, sizeof(*vte));
|
|
||||||
vte->kind = SCOPEITEM_VAR;
|
|
||||||
vte->type = itstype;
|
|
||||||
vte->data.var.color = -1;
|
|
||||||
vte->data.var.precolored = false;
|
|
||||||
vte->data.var.degree = 0;
|
|
||||||
vte->data.var.priority = 0;
|
|
||||||
vte->data.var.name = malp("$dumb%lu", vidx++);
|
|
||||||
|
|
||||||
// Add to var array
|
|
||||||
tlc->chunk.vars = realloc(tlc->chunk.vars, sizeof(*tlc->chunk.vars) * (++tlc->chunk.varCount));
|
|
||||||
tlc->chunk.vars[tlc->chunk.varCount - 1] = vte;
|
|
||||||
|
|
||||||
return vte;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Split away complex expression into a new local variable */
|
/* Split away complex expression into a new local variable */
|
||||||
static AST *varify(AST *tlc, AST *chunk, AST *stmtPrev, AST *stmt, AST *e) {
|
static AST *varify(AST *tlc, AST *chunk, AST *stmtPrev, AST *stmt, AST *e) {
|
||||||
ScopeItem *vte = create_dumbtemp(tlc, e->expression.type);
|
ScopeItem *vte = create_temp(tlc, e->expression.type);
|
||||||
|
|
||||||
// Alter AST
|
// Alter AST
|
||||||
|
|
||||||
@@ -51,6 +35,7 @@ static AST *varify(AST *tlc, AST *chunk, AST *stmtPrev, AST *stmt, AST *e) {
|
|||||||
assign->nodeKind = AST_STMT_ASSIGN;
|
assign->nodeKind = AST_STMT_ASSIGN;
|
||||||
assign->what = (AST*) ev[0];
|
assign->what = (AST*) ev[0];
|
||||||
assign->to = e;
|
assign->to = e;
|
||||||
|
vte->data.var.declaration = (AST*) assign;
|
||||||
|
|
||||||
if(stmtPrev) {
|
if(stmtPrev) {
|
||||||
stmtPrev->statement.next = (AST*) assign;
|
stmtPrev->statement.next = (AST*) assign;
|
||||||
@@ -71,6 +56,8 @@ static AST *xopify(AST *tlc, AST *chunk, AST *stmtPrev, AST *stmt, AST *e) {
|
|||||||
return varify(tlc, chunk, stmtPrev, stmt, e);
|
return varify(tlc, chunk, stmtPrev, stmt, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is needed for architectures where not all registers can be used
|
||||||
|
// as pointers (for example IA-16 or the 68000 series)
|
||||||
static void mark_ptr(AST *a) {
|
static void mark_ptr(AST *a) {
|
||||||
assert(a->nodeKind == AST_EXPR_VAR);
|
assert(a->nodeKind == AST_EXPR_VAR);
|
||||||
assert(a->exprVar.thing->kind == SCOPEITEM_VAR);
|
assert(a->exprVar.thing->kind == SCOPEITEM_VAR);
|
||||||
@@ -83,14 +70,42 @@ static void mark_ptr(AST *a) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DumbenState {
|
static void mark_a(ScopeItem *si) {
|
||||||
|
size_t sz = type_size(si->type);
|
||||||
|
if(sz <= 1) {
|
||||||
|
vte_precolor(si, REG_CLASS_8, 0);
|
||||||
|
} else if(sz == 2) {
|
||||||
|
vte_precolor(si, REG_CLASS_16_32, 0);
|
||||||
|
} else if(sz == 4 || sz == 0) {
|
||||||
|
vte_precolor(si, REG_CLASS_16_32, 1);
|
||||||
|
} else {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mark_d(ScopeItem *si) {
|
||||||
|
size_t sz = type_size(si->type);
|
||||||
|
if(sz <= 1) {
|
||||||
|
vte_precolor(si, REG_CLASS_8, 6);
|
||||||
|
} else if(sz == 2) {
|
||||||
|
vte_precolor(si, REG_CLASS_16_32, 6);
|
||||||
|
} else if(sz == 4 || sz == 0) {
|
||||||
|
vte_precolor(si, REG_CLASS_16_32, 7);
|
||||||
|
} else {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RULE ONE OF DUMBING: NEVER xopify NOR varify MORE THAN ONCE IN A SINGLE CALL TO VISITOR!!!
|
||||||
|
* IF YOU DO THIS, stmtPrev WILL FUCK UP AND STATEMENTS WILL BE LOST
|
||||||
|
*/
|
||||||
|
struct NormState {
|
||||||
AST *targetTLC;
|
AST *targetTLC;
|
||||||
int effective;
|
int effective;
|
||||||
};
|
};
|
||||||
static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *tlc, void *ud) {
|
static void normalize_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *tlc, void *ud) {
|
||||||
struct DumbenState *this = ud;
|
struct NormState *this = ud;
|
||||||
|
|
||||||
if(this->targetTLC != tlc) return;
|
|
||||||
|
|
||||||
AST *s = *nptr;
|
AST *s = *nptr;
|
||||||
|
|
||||||
@@ -103,14 +118,10 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
|||||||
if(is_xop(e->exprBinOp.operands[0]) == XOP_NOT_XOP) {
|
if(is_xop(e->exprBinOp.operands[0]) == XOP_NOT_XOP) {
|
||||||
e->exprBinOp.operands[0] = xopify(tlc, chu, stmtPrev, s, e->exprBinOp.operands[0]);
|
e->exprBinOp.operands[0] = xopify(tlc, chu, stmtPrev, s, e->exprBinOp.operands[0]);
|
||||||
this->effective = 1;
|
this->effective = 1;
|
||||||
}
|
} else if(is_xop(e->exprBinOp.operands[1]) == XOP_NOT_XOP) {
|
||||||
|
|
||||||
if(is_xop(e->exprBinOp.operands[1]) == XOP_NOT_XOP) {
|
|
||||||
e->exprBinOp.operands[1] = xopify(tlc, chu, stmtPrev, s, e->exprBinOp.operands[1]);
|
e->exprBinOp.operands[1] = xopify(tlc, chu, stmtPrev, s, e->exprBinOp.operands[1]);
|
||||||
this->effective = 1;
|
this->effective = 1;
|
||||||
}
|
} else if(is_xop(e->exprBinOp.operands[0]) == XOP_MEM && is_xop(e->exprBinOp.operands[1]) == XOP_MEM) {
|
||||||
|
|
||||||
if(is_xop(e->exprBinOp.operands[0]) == XOP_MEM && is_xop(e->exprBinOp.operands[1]) == XOP_MEM) {
|
|
||||||
// Can't have two mems; put one in var
|
// Can't have two mems; put one in var
|
||||||
|
|
||||||
e->exprBinOp.operands[1] = varify(tlc, chu, stmtPrev, s, e->exprBinOp.operands[1]);
|
e->exprBinOp.operands[1] = varify(tlc, chu, stmtPrev, s, e->exprBinOp.operands[1]);
|
||||||
@@ -225,7 +236,7 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
|||||||
|
|
||||||
} else if(s->nodeKind == AST_STMT_RETURN) {
|
} else if(s->nodeKind == AST_STMT_RETURN) {
|
||||||
|
|
||||||
// ret specifically returns in eax always, so it needs to be in a precolored var
|
// ret returns in eax always, so it needs to be in a precolored var
|
||||||
AST *retval = s->stmtReturn.val;
|
AST *retval = s->stmtReturn.val;
|
||||||
|
|
||||||
if(retval && (!is_xop(retval) || retval->nodeKind == AST_EXPR_PRIMITIVE || (retval->nodeKind == AST_EXPR_VAR && retval->exprVar.thing->kind == SCOPEITEM_VAR && (!retval->exprVar.thing->data.var.precolored || !strchr(REG_CLASSES[retval->exprVar.thing->data.var.registerClass].rsN[retval->exprVar.thing->data.var.color], 'a'))))) {
|
if(retval && (!is_xop(retval) || retval->nodeKind == AST_EXPR_PRIMITIVE || (retval->nodeKind == AST_EXPR_VAR && retval->exprVar.thing->kind == SCOPEITEM_VAR && (!retval->exprVar.thing->data.var.precolored || !strchr(REG_CLASSES[retval->exprVar.thing->data.var.registerClass].rsN[retval->exprVar.thing->data.var.color], 'a'))))) {
|
||||||
@@ -233,7 +244,7 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
|||||||
retval = s->stmtReturn.val = varify(tlc, chu, stmtPrev, s, retval);
|
retval = s->stmtReturn.val = varify(tlc, chu, stmtPrev, s, retval);
|
||||||
this->effective = 1;
|
this->effective = 1;
|
||||||
|
|
||||||
vte_precolor(retval->exprVar.thing, REG_CLASS_16_32, 1);
|
mark_a(retval->exprVar.thing);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +257,7 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
|||||||
|
|
||||||
s->stmtExpr.expr = varify(tlc, chu, stmtPrev, s, s->stmtExpr.expr);
|
s->stmtExpr.expr = varify(tlc, chu, stmtPrev, s, s->stmtExpr.expr);
|
||||||
|
|
||||||
vte_precolor(s->stmtExpr.expr->exprVar.thing, REG_CLASS_16_32, 1);
|
mark_a(s->stmtExpr.expr->exprVar.thing);
|
||||||
|
|
||||||
// Purge this statement entirely, otherwise we'd have
|
// Purge this statement entirely, otherwise we'd have
|
||||||
// a = f(x);
|
// a = f(x);
|
||||||
@@ -272,8 +283,8 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
|||||||
|
|
||||||
if(s->stmtAssign.to->nodeKind == AST_EXPR_CALL && (s->stmtAssign.what->nodeKind != AST_EXPR_VAR || s->stmtAssign.what->exprVar.thing->kind != SCOPEITEM_VAR || !s->stmtAssign.what->exprVar.thing->data.var.precolored)) {
|
if(s->stmtAssign.to->nodeKind == AST_EXPR_CALL && (s->stmtAssign.what->nodeKind != AST_EXPR_VAR || s->stmtAssign.what->exprVar.thing->kind != SCOPEITEM_VAR || !s->stmtAssign.what->exprVar.thing->data.var.precolored)) {
|
||||||
|
|
||||||
ScopeItem *tmp = create_dumbtemp(tlc, s->stmtAssign.what->expression.type);
|
ScopeItem *tmp = create_temp(tlc, s->stmtAssign.what->expression.type);
|
||||||
vte_precolor(tmp, REG_CLASS_16_32, 1);
|
mark_a(tmp);
|
||||||
|
|
||||||
ASTExprVar *ev[2] = {calloc(1, sizeof(**ev)), calloc(1, sizeof(**ev))};
|
ASTExprVar *ev[2] = {calloc(1, sizeof(**ev)), calloc(1, sizeof(**ev))};
|
||||||
ev[0]->nodeKind = AST_EXPR_VAR;
|
ev[0]->nodeKind = AST_EXPR_VAR;
|
||||||
@@ -285,6 +296,7 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
|||||||
assign2->nodeKind = AST_STMT_ASSIGN;
|
assign2->nodeKind = AST_STMT_ASSIGN;
|
||||||
assign2->what = (AST*) s->stmtAssign.what;
|
assign2->what = (AST*) s->stmtAssign.what;
|
||||||
assign2->to = (AST*) ev[0];
|
assign2->to = (AST*) ev[0];
|
||||||
|
tmp->data.var.declaration = (AST*) s;
|
||||||
|
|
||||||
s->stmtAssign.what = (AST*) ev[1];
|
s->stmtAssign.what = (AST*) ev[1];
|
||||||
|
|
||||||
@@ -293,18 +305,24 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
|||||||
|
|
||||||
this->effective = 1;
|
this->effective = 1;
|
||||||
|
|
||||||
} else if(s->stmtAssign.what->nodeKind == AST_EXPR_UNARY_OP && s->stmtAssign.what->exprUnOp.operator == UNOP_DEREF
|
} else if(s->stmtAssign.what->nodeKind == AST_EXPR_BINARY_OP && s->stmtAssign.what->exprBinOp.operator == BINOP_DEREF
|
||||||
&& s->stmtAssign.to->nodeKind == AST_EXPR_UNARY_OP && s->stmtAssign.to->exprUnOp.operator == UNOP_DEREF) {
|
&& s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && s->stmtAssign.to->exprBinOp.operator == BINOP_DEREF) {
|
||||||
|
|
||||||
s->stmtAssign.to = varify(tlc, chu, stmtPrev, s, s->stmtAssign.to);
|
s->stmtAssign.to = varify(tlc, chu, stmtPrev, s, s->stmtAssign.to);
|
||||||
|
|
||||||
this->effective = 1;
|
this->effective = 1;
|
||||||
|
|
||||||
} else if(s->stmtAssign.what->nodeKind == AST_EXPR_UNARY_OP && s->stmtAssign.what->exprUnOp.operator == UNOP_DEREF && !is_xop(s->stmtAssign.what)) {
|
} else if(s->stmtAssign.what->nodeKind == AST_EXPR_BINARY_OP && s->stmtAssign.what->exprBinOp.operator == BINOP_DEREF && !is_xop(s->stmtAssign.what)) {
|
||||||
|
|
||||||
s->stmtAssign.what->exprUnOp.operand = varify(tlc, chu, stmtPrev, s, s->stmtAssign.what->exprUnOp.operand);
|
s->stmtAssign.what->exprBinOp.operands[0] = varify(tlc, chu, stmtPrev, s, s->stmtAssign.what->exprBinOp.operands[0]);
|
||||||
|
|
||||||
mark_ptr(s->stmtAssign.what->exprUnOp.operand);
|
mark_ptr(s->stmtAssign.what->exprBinOp.operands[0]);
|
||||||
|
|
||||||
|
this->effective = 1;
|
||||||
|
|
||||||
|
} else if(s->stmtAssign.what && s->stmtAssign.what->nodeKind == AST_EXPR_VAR && s->stmtAssign.what->exprVar.thing->kind == SCOPEITEM_VAR && s->stmtAssign.to->nodeKind == AST_EXPR_CALL && is_xop(s->stmtAssign.to->exprCall.what) == XOP_NOT_XOP) {
|
||||||
|
|
||||||
|
s->stmtAssign.to->exprCall.what = varify(tlc, chu, stmtPrev, s, s->stmtAssign.to->exprCall.what);
|
||||||
|
|
||||||
this->effective = 1;
|
this->effective = 1;
|
||||||
|
|
||||||
@@ -314,7 +332,9 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
|||||||
int argCount = call->what->expression.type->pointer.of->function.argCount;
|
int argCount = call->what->expression.type->pointer.of->function.argCount;
|
||||||
|
|
||||||
for(int i = 0; i < argCount; i++) {
|
for(int i = 0; i < argCount; i++) {
|
||||||
if(is_xop(call->args[i]) == XOP_NOT_XOP) {
|
// We also check for ast_references_stack because the stack gets
|
||||||
|
// shifted during a function call which screws up all of the offsets
|
||||||
|
if(is_xop(call->args[i]) == XOP_NOT_XOP || ast_references_stack(call->args[i])) {
|
||||||
call->args[i] = xopify(tlc, chu, stmtPrev, s, call->args[i]);
|
call->args[i] = xopify(tlc, chu, stmtPrev, s, call->args[i]);
|
||||||
this->effective = 1;
|
this->effective = 1;
|
||||||
|
|
||||||
@@ -349,10 +369,10 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
|||||||
|
|
||||||
this->effective = 1;
|
this->effective = 1;
|
||||||
} else if(is_xop(s->stmtAssign.to) == XOP_NOT_XOP) {
|
} else if(is_xop(s->stmtAssign.to) == XOP_NOT_XOP) {
|
||||||
if(s->stmtAssign.to->nodeKind == AST_EXPR_UNARY_OP && s->stmtAssign.to->exprUnOp.operator == UNOP_DEREF) {
|
if(s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && s->stmtAssign.to->exprUnOp.operator == BINOP_DEREF) {
|
||||||
s->stmtAssign.to->exprUnOp.operand = varify(tlc, chu, stmtPrev, s, s->stmtAssign.to->exprUnOp.operand);
|
s->stmtAssign.to->exprBinOp.operands[0] = varify(tlc, chu, stmtPrev, s, s->stmtAssign.to->exprBinOp.operands[0]);
|
||||||
|
|
||||||
mark_ptr(s->stmtAssign.to->exprUnOp.operand);
|
mark_ptr(s->stmtAssign.to->exprBinOp.operands[0]);
|
||||||
|
|
||||||
this->effective = 1;
|
this->effective = 1;
|
||||||
} else if(s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && s->stmtAssign.to->exprBinOp.operator == BINOP_MUL) {
|
} else if(s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && s->stmtAssign.to->exprBinOp.operator == BINOP_MUL) {
|
||||||
@@ -392,7 +412,7 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
|||||||
|
|
||||||
AST *hihalf = varify(tlc, chu, stmtPrev->statement.next, s, mulhi);
|
AST *hihalf = varify(tlc, chu, stmtPrev->statement.next, s, mulhi);
|
||||||
|
|
||||||
vte_precolor(hihalf->exprVar.thing, REG_CLASS_16_32, 7);
|
mark_d(hihalf->exprVar.thing);
|
||||||
}
|
}
|
||||||
|
|
||||||
s->stmtAssign.what = ast_deep_copy(s->stmtAssign.to->exprBinOp.operands[0]);
|
s->stmtAssign.what = ast_deep_copy(s->stmtAssign.to->exprBinOp.operands[0]);
|
||||||
@@ -405,7 +425,7 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
|||||||
|
|
||||||
s->stmtAssign.next = (AST*) redest;
|
s->stmtAssign.next = (AST*) redest;
|
||||||
|
|
||||||
vte_precolor(s->stmtAssign.to->exprBinOp.operands[0]->exprVar.thing, REG_CLASS_16_32, 1);
|
mark_a(s->stmtAssign.to->exprBinOp.operands[0]->exprVar.thing);
|
||||||
|
|
||||||
this->effective = 1;
|
this->effective = 1;
|
||||||
} else assert(because == NOT_AT_ALL_IT || because == GUCCI);
|
} else assert(because == NOT_AT_ALL_IT || because == GUCCI);
|
||||||
@@ -452,16 +472,11 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DenoopState {
|
static void pre_norm_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||||
AST *targetTLC;
|
|
||||||
bool success;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void pre_dumb_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
|
||||||
AST *n = *nptr;
|
AST *n = *nptr;
|
||||||
|
|
||||||
if(n == ud) {
|
if(n->nodeKind == AST_CHUNK) {
|
||||||
if(tlc->chunk.functionType) {
|
if(n->chunk.functionType) {
|
||||||
size_t argCount = n->chunk.functionType->function.argCount;
|
size_t argCount = n->chunk.functionType->function.argCount;
|
||||||
|
|
||||||
// First argCount vtes in list are the arguments
|
// First argCount vtes in list are the arguments
|
||||||
@@ -474,7 +489,7 @@ static void pre_dumb_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, A
|
|||||||
|
|
||||||
ASTExprPrimitive *offset = calloc(1, sizeof(*offset));
|
ASTExprPrimitive *offset = calloc(1, sizeof(*offset));
|
||||||
offset->nodeKind = AST_EXPR_PRIMITIVE;
|
offset->nodeKind = AST_EXPR_PRIMITIVE;
|
||||||
offset->type = type_u(x86_max_gpr_size());
|
offset->type = type_u(8 * x86_max_gpr_size());
|
||||||
offset->val = 4 + i * x86_max_gpr_size();
|
offset->val = 4 + i * x86_max_gpr_size();
|
||||||
offset->stackGrowth = true;
|
offset->stackGrowth = true;
|
||||||
|
|
||||||
@@ -485,11 +500,11 @@ static void pre_dumb_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, A
|
|||||||
sum->operands[1] = (AST*) offset;
|
sum->operands[1] = (AST*) offset;
|
||||||
sum->operator = BINOP_ADD;
|
sum->operator = BINOP_ADD;
|
||||||
|
|
||||||
ASTExprUnaryOp *deref = calloc(1, sizeof(*deref));
|
ASTExprBinaryOp *deref = calloc(1, sizeof(*deref));
|
||||||
deref->nodeKind = AST_EXPR_UNARY_OP;
|
deref->nodeKind = AST_EXPR_BINARY_OP;
|
||||||
deref->type = vte->type;
|
deref->type = vte->type;
|
||||||
deref->operand = (AST*) sum;
|
deref->operands[0] = (AST*) sum;
|
||||||
deref->operator = UNOP_DEREF;
|
deref->operator = BINOP_DEREF;
|
||||||
|
|
||||||
ASTExprVar *evar = calloc(1, sizeof(*evar));
|
ASTExprVar *evar = calloc(1, sizeof(*evar));
|
||||||
evar->nodeKind = AST_EXPR_VAR;
|
evar->nodeKind = AST_EXPR_VAR;
|
||||||
@@ -500,8 +515,9 @@ static void pre_dumb_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, A
|
|||||||
ass->nodeKind = AST_STMT_ASSIGN;
|
ass->nodeKind = AST_STMT_ASSIGN;
|
||||||
ass->next = NULL;
|
ass->next = NULL;
|
||||||
ass->what = (AST*) evar;
|
ass->what = (AST*) evar;
|
||||||
ass->to = (AST*) deref;
|
ass->to = ast_cast_expr((AST*) deref, vte->type); // Must cast because of "convention correctness"
|
||||||
ass->next = n->chunk.statementFirst;
|
ass->next = n->chunk.statementFirst;
|
||||||
|
vte->data.var.declaration = (AST*) ass;
|
||||||
|
|
||||||
if(n->chunk.statementFirst) {
|
if(n->chunk.statementFirst) {
|
||||||
n->chunk.statementFirst = ass;
|
n->chunk.statementFirst = ass;
|
||||||
@@ -515,8 +531,6 @@ static void pre_dumb_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, A
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void decompose_symbol_record_field_access(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
static void decompose_symbol_record_field_access(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||||
if(tlc != (AST*) ud) return;
|
|
||||||
|
|
||||||
AST *n = *nptr;
|
AST *n = *nptr;
|
||||||
|
|
||||||
if(n->nodeKind == AST_EXPR_DOT) {
|
if(n->nodeKind == AST_EXPR_DOT) {
|
||||||
@@ -549,11 +563,11 @@ static void decompose_symbol_record_field_access(AST **nptr, AST *stmt, AST *stm
|
|||||||
|
|
||||||
AST *cast = ast_cast_expr((AST*) sum, type_pointer_wrap(n->exprDot.a->expression.type->record.fieldTypes[f]));
|
AST *cast = ast_cast_expr((AST*) sum, type_pointer_wrap(n->exprDot.a->expression.type->record.fieldTypes[f]));
|
||||||
|
|
||||||
ASTExprUnaryOp *deref = calloc(1, sizeof(*deref));
|
ASTExprBinaryOp *deref = calloc(1, sizeof(*deref));
|
||||||
deref->nodeKind = AST_EXPR_UNARY_OP;
|
deref->nodeKind = AST_EXPR_BINARY_OP;
|
||||||
deref->type = cast->expression.type->pointer.of;
|
deref->type = cast->expression.type->pointer.of;
|
||||||
deref->operator = UNOP_DEREF;
|
deref->operator = BINOP_DEREF;
|
||||||
deref->operand = cast;
|
deref->operands[0] = cast;
|
||||||
|
|
||||||
*nptr = (AST*) deref;
|
*nptr = (AST*) deref;
|
||||||
}
|
}
|
||||||
@@ -567,19 +581,34 @@ static bool is_double_field_access(AST *e) {
|
|||||||
return e->nodeKind == AST_EXPR_BINARY_OP && is_pointer2pointer_cast(e->exprBinOp.operands[0]) && e->exprBinOp.operands[0]->exprCast.what->nodeKind == AST_EXPR_BINARY_OP && e->exprBinOp.operands[0]->exprCast.what->exprBinOp.operator == e->exprBinOp.operator && e->exprBinOp.operator == BINOP_ADD && e->exprBinOp.operands[0]->exprCast.what->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE && e->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE;
|
return e->nodeKind == AST_EXPR_BINARY_OP && is_pointer2pointer_cast(e->exprBinOp.operands[0]) && e->exprBinOp.operands[0]->exprCast.what->nodeKind == AST_EXPR_BINARY_OP && e->exprBinOp.operands[0]->exprCast.what->exprBinOp.operator == e->exprBinOp.operator && e->exprBinOp.operator == BINOP_ADD && e->exprBinOp.operands[0]->exprCast.what->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE && e->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct DenoopState {
|
||||||
|
AST *targetTLC;
|
||||||
|
bool success;
|
||||||
|
};
|
||||||
|
|
||||||
static void denoop_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
static void denoop_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||||
struct DenoopState *state = ud;
|
struct DenoopState *state = ud;
|
||||||
|
|
||||||
// if(state->targetTLC != tlc) return;
|
|
||||||
|
|
||||||
AST *n = *nptr;
|
AST *n = *nptr;
|
||||||
|
|
||||||
bool *success = &state->success;
|
bool *success = &state->success;
|
||||||
|
|
||||||
if(n->nodeKind == AST_EXPR_UNARY_OP && n->exprUnOp.operator == UNOP_REF && n->exprUnOp.operand->nodeKind == AST_EXPR_UNARY_OP && n->exprUnOp.operand->exprUnOp.operator == UNOP_DEREF) {
|
if(n->nodeKind == AST_EXPR_NULL) {
|
||||||
|
// Turn `null` into integer
|
||||||
|
|
||||||
|
ASTExprPrimitive *prim = calloc(1, sizeof(*prim));
|
||||||
|
prim->nodeKind = AST_EXPR_PRIMITIVE;
|
||||||
|
prim->type = n->expression.type;
|
||||||
|
prim->val = ntc_get_int_default("null", 0);
|
||||||
|
|
||||||
|
*nptr = (AST*) prim;
|
||||||
|
} else if(n->nodeKind == AST_EXPR_UNARY_OP && n->exprUnOp.operator == UNOP_REF && n->exprUnOp.operand->nodeKind == AST_EXPR_BINARY_OP && n->exprUnOp.operand->exprBinOp.operator == BINOP_DEREF) {
|
||||||
// Turn `&*a` into `a`
|
// Turn `&*a` into `a`
|
||||||
|
|
||||||
*nptr = n->exprUnOp.operand->exprUnOp.operand;
|
// Artificially change type of casted expression to keep types valid for subsequent passes
|
||||||
|
n->exprUnOp.operand->exprBinOp.operands[0]->expression.type = n->expression.type;
|
||||||
|
|
||||||
|
*nptr = n->exprUnOp.operand->exprBinOp.operands[0];
|
||||||
|
|
||||||
*success = true;
|
*success = true;
|
||||||
} else if(is_double_field_access(n)) {
|
} else if(is_double_field_access(n)) {
|
||||||
@@ -596,10 +625,20 @@ static void denoop_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST
|
|||||||
n->exprCast.what = n->exprCast.what->exprCast.what;
|
n->exprCast.what = n->exprCast.what->exprCast.what;
|
||||||
|
|
||||||
*success = true;
|
*success = true;
|
||||||
} else if(n->nodeKind == AST_EXPR_BINARY_OP && n->exprBinOp.operands[0]->nodeKind == AST_EXPR_BINARY_OP && n->exprBinOp.operator == BINOP_ADD && n->exprBinOp.operands[0]->exprBinOp.operator == BINOP_ADD && n->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE && n->exprBinOp.operands[0]->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE) {
|
} else if(n->nodeKind == AST_EXPR_BINARY_OP && n->exprBinOp.operands[0]->nodeKind == AST_EXPR_BINARY_OP && n->exprBinOp.operator == BINOP_ADD && n->exprBinOp.operands[0]->exprBinOp.operator == BINOP_ADD && n->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE && n->exprBinOp.operands[0]->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE && !(n->exprBinOp.operands[0]->exprBinOp.operands[1]->exprPrim.stackGrowth && n->exprBinOp.operands[1]->exprPrim.stackGrowth)) {
|
||||||
// Turn `(x + a) + b` into `x + (a + b)`
|
// Turn `(x + a) + b` into `x + (a + b)`
|
||||||
|
|
||||||
n->exprBinOp.operands[0]->exprBinOp.operands[1]->exprPrim.val += n->exprBinOp.operands[1]->exprPrim.val;
|
n->exprBinOp.operands[0]->exprBinOp.operands[1]->exprPrim.val += n->exprBinOp.operands[1]->exprPrim.val;
|
||||||
|
n->exprBinOp.operands[0]->exprBinOp.operands[1]->exprPrim.stackGrowth = n->exprBinOp.operands[0]->exprBinOp.operands[1]->exprPrim.stackGrowth || n->exprBinOp.operands[1]->exprPrim.stackGrowth;
|
||||||
|
|
||||||
|
*nptr = n->exprBinOp.operands[0];
|
||||||
|
|
||||||
|
*success = true;
|
||||||
|
} else if(n->nodeKind == AST_EXPR_BINARY_OP && n->exprBinOp.operator == BINOP_ADD && n->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE && n->exprBinOp.operands[1]->exprPrim.val == 0 && !n->exprBinOp.operands[1]->exprPrim.stackGrowth) {
|
||||||
|
// Turn `x + 0` into `x`
|
||||||
|
|
||||||
|
// Artificially change type of casted expression to keep types valid for subsequent passes
|
||||||
|
n->exprBinOp.operands[0]->expression.type = n->expression.type;
|
||||||
|
|
||||||
*nptr = n->exprBinOp.operands[0];
|
*nptr = n->exprBinOp.operands[0];
|
||||||
|
|
||||||
@@ -621,8 +660,33 @@ static void denoop_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST
|
|||||||
} else if(n->nodeKind == AST_EXPR_CAST && n->exprCast.what->expression.type->type == TYPE_TYPE_POINTER && n->exprCast.to->type == TYPE_TYPE_POINTER) {
|
} else if(n->nodeKind == AST_EXPR_CAST && n->exprCast.what->expression.type->type == TYPE_TYPE_POINTER && n->exprCast.to->type == TYPE_TYPE_POINTER) {
|
||||||
// Turn (x as A*) into x, since all pointer types are identical in Nectar's AST
|
// Turn (x as A*) into x, since all pointer types are identical in Nectar's AST
|
||||||
|
|
||||||
|
// Artificially change type of casted expression to keep types valid for subsequent passes
|
||||||
|
n->exprCast.what->expression.type = n->exprCast.to;
|
||||||
|
|
||||||
*nptr = n->exprCast.what;
|
*nptr = n->exprCast.what;
|
||||||
|
|
||||||
|
*success = true;
|
||||||
|
} else if(n->nodeKind == AST_EXPR_BINARY_OP && n->exprBinOp.operator == BINOP_ADD && n->exprBinOp.operands[0]->nodeKind == AST_EXPR_PRIMITIVE && n->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE && !(n->exprBinOp.operands[0]->exprPrim.stackGrowth && n->exprBinOp.operands[1]->exprPrim.stackGrowth)) {
|
||||||
|
// Constant propagation of + operator
|
||||||
|
|
||||||
|
AST *prim = n->exprBinOp.operands[0];
|
||||||
|
prim->expression.type = n->exprBinOp.type;
|
||||||
|
prim->exprPrim.val = n->exprBinOp.operands[0]->exprPrim.val + n->exprBinOp.operands[1]->exprPrim.val;
|
||||||
|
prim->exprPrim.stackGrowth = n->exprBinOp.operands[0]->exprPrim.stackGrowth || n->exprBinOp.operands[1]->exprPrim.stackGrowth;
|
||||||
|
|
||||||
|
*nptr = prim;
|
||||||
|
|
||||||
|
*success = true;
|
||||||
|
} else if(n->nodeKind == AST_EXPR_BINARY_OP && n->exprBinOp.operator == BINOP_SUB && n->exprBinOp.operands[0]->nodeKind == AST_EXPR_PRIMITIVE && n->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE && !(n->exprBinOp.operands[0]->exprPrim.stackGrowth && n->exprBinOp.operands[1]->exprPrim.stackGrowth)) {
|
||||||
|
// Constant propagation of - operator
|
||||||
|
|
||||||
|
AST *prim = n->exprBinOp.operands[0];
|
||||||
|
prim->expression.type = n->exprBinOp.type;
|
||||||
|
prim->exprPrim.val = n->exprBinOp.operands[0]->exprPrim.val - n->exprBinOp.operands[1]->exprPrim.val;
|
||||||
|
prim->exprPrim.stackGrowth = n->exprBinOp.operands[0]->exprPrim.stackGrowth || n->exprBinOp.operands[1]->exprPrim.stackGrowth;
|
||||||
|
|
||||||
|
*nptr = prim;
|
||||||
|
|
||||||
*success = true;
|
*success = true;
|
||||||
} else if(n->nodeKind == AST_EXPR_EXT_SIZEOF) {
|
} else if(n->nodeKind == AST_EXPR_EXT_SIZEOF) {
|
||||||
ASTExprPrimitive *prim = calloc(1, sizeof(*prim));
|
ASTExprPrimitive *prim = calloc(1, sizeof(*prim));
|
||||||
@@ -676,12 +740,54 @@ void ast_denoop(AST *tlc, AST **node) {
|
|||||||
} while(state.success);
|
} while(state.success);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dumben_pre(AST *tlc) {
|
/*
|
||||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, tlc, pre_dumb_visitor, NULL);
|
* The convention correctness pass converts all function calls & function sources to the form
|
||||||
|
* hat matches the architecture most closely. For example, arguments (and return values) in
|
||||||
|
* cdecl are always passed as 32-bit integers, even if they are defined as 8-bit or 16-bit in
|
||||||
|
* the source.
|
||||||
|
*
|
||||||
|
* TODO: convert records to proper form also.
|
||||||
|
*/
|
||||||
|
static void convention_correctness_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||||
|
AST *n = *nptr;
|
||||||
|
|
||||||
|
if(n->nodeKind == AST_EXPR_CALL) {
|
||||||
|
Type *type = n->exprCall.what->expression.type;
|
||||||
|
|
||||||
|
assert(type->type == TYPE_TYPE_POINTER);
|
||||||
|
|
||||||
|
type = type->pointer.of;
|
||||||
|
|
||||||
|
assert(type->type == TYPE_TYPE_FUNCTION);
|
||||||
|
|
||||||
|
for(size_t i = 0; i < type->function.argCount; i++) {
|
||||||
|
if(type->function.args[i]->type == TYPE_TYPE_PRIMITIVE) {
|
||||||
|
n->exprCall.args[i] = ast_cast_expr(n->exprCall.args[i], type_prim_cast(type->function.args[i], 8 * x86_max_gpr_size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void decompose_segmented_dereferences(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||||
|
AST *n = *nptr;
|
||||||
|
if(n->nodeKind == AST_EXPR_BINARY_OP && n->exprBinOp.operator == BINOP_DEREF && n->exprBinOp.operands[1]) {
|
||||||
|
AST *seg = n->exprBinOp.operands[1];
|
||||||
|
if(seg->nodeKind != AST_EXPR_VAR || seg->exprVar.thing->kind != SCOPEITEM_VAR || seg->exprVar.thing->data.var.registerClass != REG_CLASS_DATASEGS) {
|
||||||
|
seg = n->exprBinOp.operands[1] = varify(tlc, chunk, stmtPrev, stmt, seg);
|
||||||
|
seg->exprVar.thing->data.var.preclassed = true;
|
||||||
|
seg->exprVar.thing->data.var.registerClass = REG_CLASS_DATASEGS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void arch_normalize_pre(AST *tlc) {
|
||||||
|
generic_visitor(&tlc, NULL, NULL, tlc, tlc, tlc, convention_correctness_visitor, NULL);
|
||||||
|
generic_visitor(&tlc, NULL, NULL, tlc, tlc, tlc, pre_norm_visitor, NULL);
|
||||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, tlc, decompose_symbol_record_field_access, NULL);
|
generic_visitor(&tlc, NULL, NULL, tlc, tlc, tlc, decompose_symbol_record_field_access, NULL);
|
||||||
|
generic_visitor(&tlc, NULL, NULL, tlc, tlc, tlc, decompose_segmented_dereferences, NULL);
|
||||||
|
|
||||||
for(size_t t = 0; t < tlc->chunk.varCount;) {
|
for(size_t t = 0; t < tlc->chunk.varCount;) {
|
||||||
if(tlc->chunk.vars[t]->type->type == TYPE_TYPE_RECORD) {
|
if(ast_is_scopeitem_referenced(tlc, tlc->chunk.vars[t]) || tlc->chunk.vars[t]->type->type == TYPE_TYPE_RECORD) {
|
||||||
ast_spill_to_stack(tlc, tlc->chunk.vars[t]);
|
ast_spill_to_stack(tlc, tlc->chunk.vars[t]);
|
||||||
} else {
|
} else {
|
||||||
t++;
|
t++;
|
||||||
@@ -693,16 +799,16 @@ void dumben_pre(AST *tlc) {
|
|||||||
ast_commutativity_pass(tlc);
|
ast_commutativity_pass(tlc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dumben_go(AST* tlc) {
|
void arch_normalize(AST* tlc) {
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
while(1) {
|
while(1) {
|
||||||
if(i == 20000) {
|
if(i == 20000) {
|
||||||
stahp(0, 0, "TOO MANY DUMBS. TOO MANY DUMBS.");
|
stahp(0, 0, "TOO MANY DUMBS. TOO MANY DUMBS.");
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DumbenState state = {.targetTLC = tlc};
|
struct NormState state = {.targetTLC = tlc};
|
||||||
|
|
||||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, &state, dumben_visitor, NULL);
|
generic_visitor(&tlc, NULL, NULL, tlc, tlc, &state, normalize_visitor, NULL);
|
||||||
|
|
||||||
int successful = state.effective;
|
int successful = state.effective;
|
||||||
|
|
||||||
@@ -711,7 +817,7 @@ void dumben_go(AST* tlc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(ntc_get_int("pdbg")) {
|
if(ntc_get_int("pdbg")) {
|
||||||
fprintf(stderr, "### DUMBED DOWN %lu ###\n", i++);
|
fprintf(stderr, "### NORM %lu ###\n", i++);
|
||||||
char *astdump = ast_dump(tlc);
|
char *astdump = ast_dump(tlc);
|
||||||
fputs(astdump, stderr);
|
fputs(astdump, stderr);
|
||||||
free(astdump);
|
free(astdump);
|
||||||
Reference in New Issue
Block a user