Compare commits
50 Commits
5455e1cebb
...
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 | ||
|
|
cdcc1f6d3d | ||
|
|
2771609049 | ||
|
|
d0262c586e | ||
|
|
13333c971a | ||
|
|
b4272a67d1 | ||
|
|
6b7fefc5e7 | ||
|
|
f9fe031532 | ||
|
|
7f855f3931 | ||
|
|
5196026ed1 | ||
|
|
5755e243a9 | ||
|
|
67fb9e5b52 | ||
|
|
012d18cdf8 | ||
|
|
81b1010453 | ||
|
|
fb4849d382 |
128
DOCUMENTATION.md
128
DOCUMENTATION.md
@@ -1,50 +1,71 @@
|
||||
# Nectar Reference Compiler Source Documentation
|
||||
|
||||
<img src="https://mid.net.ua/git/mid/nctref/raw/branch/master/documentation_passes.svg" width="256" align="right" />
|
||||
|
||||
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
|
||||
|
||||
Starting with a Nectar source file, the compiler begins with the two common passes: lexing and parsing. Parsing exploits Nectar's syntax quirks, and may jump back and forth multiple times to fully parse a source file. This is necessary to avoid having to forward declare items. At the end, parsing returns what is called an AST in the source, although formally speaking the term is incorrectly used.
|
||||
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.
|
||||
|
||||
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 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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Pre-dumbification
|
||||
## Desegmentation
|
||||
|
||||
Before dumbification we need to make sure the code at least matches the semantics of the x86 architecture.
|
||||
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
|
||||
|
||||
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`.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
@@ -53,13 +74,13 @@ which should become
|
||||
a = b
|
||||
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).
|
||||
|
||||
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) -> {
|
||||
if(n <= 1) {
|
||||
@@ -87,10 +108,10 @@ And the processed AST output by the compiler:
|
||||
|
||||
`@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
|
||||
|
||||
***WARNING: THIS ENTIRE SECTION HAS BECOME OUTDATED***
|
||||
|
||||
I hate these things. Another is def-use chains, but both are horribly underdocumented. Their only use in most literature is so the author can immediately move to SSA form.
|
||||
|
||||
For each variable, its UD chain is a list of each usage in the AST, with the corresponding potential definition of the variable at that use. For each potential definition that exists at that point, there is one UD element in the chain. If there's only one potential definition at a point, then it's definitely the true one. Users of UD chains include optimizers and codegen. UD chains are always regenerated for use between passes by using the UD visitor on the top-level chunk.
|
||||
@@ -169,7 +190,7 @@ It is insufficient for a compiler to assume these are in any way comparable. The
|
||||
|
||||
From these we form sets of registers called "register classes", which can be thought of as "ways in which a register can be used". The resource mask of a register class is the union (bitwise OR) of all bits used by all of its registers.
|
||||
|
||||
This compiler currently considers 3 register classes: `REG_CLASS_8`, for `al`, `ah`, `bl`, `bh`, `cl`, `ch`, `dl`, `dh`; `REG_CLASS_NOT_8` for `ax`, `eax`, `bx`, `ebx`, `cx`, `ecx`, `dx`, `edx`, `di`, `edi`, `si`, `esi`; `REG_CLASS_IA16_PTRS` for `di`, `si`, `bx`. It can be seen registers are not unique under this abstraction, but this is necessary as this abstraction assumes the CPU to be a soup.
|
||||
This compiler currently considers 3 register classes: `REG_CLASS_8`, for `al`, `ah`, `bl`, `bh`, `cl`, `ch`, `dl`, `dh`; `REG_CLASS_NOT_8` for `ax`, `eax`, `bx`, `ebx`, `cx`, `ecx`, `dx`, `edx`, `di`, `edi`, `si`, `esi`; `REG_CLASS_IA16_PTRS` for `di`, `si`, `bx`. It can be seen registers are not unique under this abstraction, but this is necessary as we gave up finding any patterns and assume the CPU to be a soup.
|
||||
|
||||
(64-bit is not considered)
|
||||
|
||||
@@ -179,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 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
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Code generation
|
||||
|
||||
@@ -277,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.
|
||||
|
||||
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;
|
||||
|
||||
@@ -304,16 +323,11 @@ This example benefits from scalar replacement:
|
||||
|
||||
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)
|
||||
|
||||
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:
|
||||
|
||||
@@ -325,9 +339,9 @@ Let us write `if(x == 1 || y == 1) { do stuff; }` in x86:
|
||||
; do stuff
|
||||
.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 w = y == 1;
|
||||
@@ -338,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..
|
||||
|
||||
Lack of gotos also makes function inlining impossible, as returns also become gotos (!!).
|
||||
|
||||
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` *only* if x's type is a pointer), we obtain the following after denooping:
|
||||
|
||||
T* data = this + 4;
|
||||
What else? Did you know `return` statements are gotos when a function is inlined, too? In other words, no function inlining.
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -376,22 +366,22 @@ To account for this, we can have a second assignment statement right next to the
|
||||
w = 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;
|
||||
w = k *^ y;
|
||||
k = k * y;
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
472
documentation_passes.svg
Normal file
472
documentation_passes.svg
Normal file
@@ -0,0 +1,472 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64.350662mm"
|
||||
height="191.12521mm"
|
||||
viewBox="0 0 64.350662 191.12521"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
|
||||
sodipodi:docname="documentation_passes.svg">
|
||||
<metadata
|
||||
id="metadata57">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.3968848"
|
||||
inkscape:cx="132.64522"
|
||||
inkscape:cy="370.56552"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2109"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-rotation="0"
|
||||
fit-margin-top="1"
|
||||
fit-margin-bottom="1"
|
||||
fit-margin-left="1"
|
||||
fit-margin-right="1" />
|
||||
<defs
|
||||
id="defs2">
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="Arrow1Lend"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
transform="matrix(-0.8,0,0,-0.8,-10,0)"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||
id="path940" />
|
||||
</marker>
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(28.296833,1.0320015)">
|
||||
<rect
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:0.329003;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect31"
|
||||
width="54.232807"
|
||||
height="8.7961817"
|
||||
x="-27.132332"
|
||||
y="0.13249999" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.99722px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:pre;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="-0.018652661"
|
||||
y="6.828989"
|
||||
id="text5232"><tspan
|
||||
id="tspan104126"><tspan
|
||||
style="text-align:center;text-anchor:middle"
|
||||
id="tspan104124">Lexing</tspan></tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:0.329003;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect31-3"
|
||||
width="54.232807"
|
||||
height="8.7961817"
|
||||
x="-27.132332"
|
||||
y="15.1325" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.99722px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:pre;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="0.0091233542"
|
||||
y="21.80534"
|
||||
id="text5232-1"><tspan
|
||||
id="tspan104130"><tspan
|
||||
style="text-align:center;text-anchor:middle"
|
||||
id="tspan104128">Skimming</tspan></tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:0.329003;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect31-3-7"
|
||||
width="54.232807"
|
||||
height="8.7961817"
|
||||
x="-27.132332"
|
||||
y="30.1325" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.99722px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:pre;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="-0.13216139"
|
||||
y="36.702583"
|
||||
id="text5232-1-5"><tspan
|
||||
id="tspan104134"><tspan
|
||||
style="text-align:center;text-anchor:middle"
|
||||
id="tspan104132">Parsing</tspan></tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:0.329003;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect31-3-7-9"
|
||||
width="54.232807"
|
||||
height="8.7961817"
|
||||
x="-27.132332"
|
||||
y="45.1325" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.99722px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:pre;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="0.23284389"
|
||||
y="51.497505"
|
||||
id="text5232-1-5-6"><tspan
|
||||
id="tspan104138"><tspan
|
||||
style="text-align:center;text-anchor:middle"
|
||||
id="tspan104136">Desegmentation</tspan></tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:0.329003;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect31-3-7-9-7"
|
||||
width="54.232807"
|
||||
height="8.7961817"
|
||||
x="-27.132332"
|
||||
y="60.1325" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.99722px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:pre;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="0.14818139"
|
||||
y="66.713989"
|
||||
id="text5232-1-5-6-5"><tspan
|
||||
id="tspan104142"><tspan
|
||||
style="text-align:center;text-anchor:middle"
|
||||
id="tspan104140">SRoA</tspan></tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:0.329003;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect31-3-7-9-7-6"
|
||||
width="54.232807"
|
||||
height="8.7961817"
|
||||
x="-27.132332"
|
||||
y="135.13251" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.99722px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:pre;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="0.083988138"
|
||||
y="141.51147"
|
||||
id="text5232-1-5-6-5-4"><tspan
|
||||
id="tspan104146"><tspan
|
||||
style="text-align:center;text-anchor:middle"
|
||||
id="tspan104144">Denooping</tspan></tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:0.329003;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect31-3-7-9-7-6-38"
|
||||
width="54.232807"
|
||||
height="8.7961817"
|
||||
x="-27.132332"
|
||||
y="165.13252" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.99722px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:pre;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="0.033953242"
|
||||
y="171.85045"
|
||||
id="text5232-1-5-6-5-4-6"><tspan
|
||||
id="tspan104150"><tspan
|
||||
style="text-align:center;text-anchor:middle"
|
||||
id="tspan104148">Dumbification</tspan></tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:0.329003;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect31-3-7-9-7-6-38-0"
|
||||
width="54.232807"
|
||||
height="8.7961817"
|
||||
x="-27.132332"
|
||||
y="180.13252" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.99722px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:pre;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="0.25064546"
|
||||
y="186.79578"
|
||||
id="text5232-1-5-6-5-4-6-4"><tspan
|
||||
id="tspan104154"><tspan
|
||||
style="text-align:center;text-anchor:middle"
|
||||
id="tspan104152">Try Codegen</tspan></tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:0.329003;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect31-3-7-9-7-6-3"
|
||||
width="54.232807"
|
||||
height="8.7961817"
|
||||
x="-27.132332"
|
||||
y="150.13252" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.99722px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:pre;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="-0.19107197"
|
||||
y="156.62112"
|
||||
id="text5232-1-5-6-5-4-3"><tspan
|
||||
id="tspan104158"><tspan
|
||||
style="text-align:center;text-anchor:middle"
|
||||
id="tspan104156">Commutativity</tspan></tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:0.329003;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect31-3-7-9-7-1"
|
||||
width="54.232807"
|
||||
height="8.7961817"
|
||||
x="-27.132332"
|
||||
y="120.1325" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.99722px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:pre;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="0.017620174"
|
||||
y="126.4618"
|
||||
id="text5232-1-5-6-5-89"><tspan
|
||||
id="tspan104162"><tspan
|
||||
style="text-align:center;text-anchor:middle"
|
||||
id="tspan104160">Record Spilling</tspan></tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:0.329003;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect31-3-7-9-7-3"
|
||||
width="54.232807"
|
||||
height="8.7961817"
|
||||
x="-27.132332"
|
||||
y="90.1325" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.99722px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:pre;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="-0.016195312"
|
||||
y="96.573265"
|
||||
id="text5232-1-5-6-5-8"><tspan
|
||||
id="tspan104166"><tspan
|
||||
style="text-align:center;text-anchor:middle"
|
||||
id="tspan104164">Predumbing</tspan></tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:0.329003;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect31-3-7-9-7-3-8"
|
||||
width="54.232807"
|
||||
height="8.7961817"
|
||||
x="-27.132332"
|
||||
y="105.1325" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:0.25em;font-family:sans-serif;white-space:pre;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="-0.052187458"
|
||||
y="109.01695"
|
||||
id="text101633"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan2359"
|
||||
x="-0.052186634"
|
||||
y="109.01695"><tspan
|
||||
id="tspan104172"><tspan
|
||||
style="font-size:4.23333px;-inkscape-font-specification:'sans-serif, Normal';text-align:center;text-anchor:middle"
|
||||
id="tspan104168">Decompose Record</tspan><tspan
|
||||
dx="2.6871719"
|
||||
style="font-size:4.23333px;-inkscape-font-specification:'sans-serif, Normal';text-align:center;text-anchor:middle"
|
||||
id="tspan104170" /></tspan><tspan
|
||||
id="tspan104176"><tspan
|
||||
style="font-size:4.23333px;-inkscape-font-specification:'sans-serif, Normal';text-align:center;text-anchor:middle"
|
||||
id="tspan104174"></tspan></tspan></tspan><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan2361"
|
||||
x="-0.052187435"
|
||||
y="113.31228"><tspan
|
||||
id="tspan2363"><tspan
|
||||
style="font-size:4.23333px;-inkscape-font-specification:'sans-serif, Normal';text-align:center;text-anchor:middle"
|
||||
id="tspan2365">Field Access</tspan></tspan></tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:0.329003;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect31-3-7-9-7-65"
|
||||
width="54.232807"
|
||||
height="8.7961817"
|
||||
x="-27.132332"
|
||||
y="75.1325" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.99722px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="-19.417631"
|
||||
y="81.833992"
|
||||
id="text933"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan931"
|
||||
x="-19.417631"
|
||||
y="81.833992"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.99722px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583">Linearization</tspan></text>
|
||||
<g
|
||||
id="g1321">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 0.0021762,8.9798566 V 15.048811"
|
||||
id="path1315" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -0.73956258,14.035325 H 0.74417303 L 0.0021762,15.048811 Z"
|
||||
id="path1317" />
|
||||
</g>
|
||||
<g
|
||||
id="g1321-4"
|
||||
transform="translate(0,15.000143)">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 0.0021762,8.9798566 V 15.048811"
|
||||
id="path1315-8" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -0.73956258,14.035325 H 0.74417303 L 0.0021762,15.048811 Z"
|
||||
id="path1317-1" />
|
||||
</g>
|
||||
<g
|
||||
id="g1321-4-0"
|
||||
transform="matrix(0.99982636,0,0,1.0000031,-1.5064804e-4,30.000115)">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 0.0021762,8.9798566 V 15.048811"
|
||||
id="path1315-8-3" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -0.73956258,14.035325 H 0.74417303 L 0.0021762,15.048811 Z"
|
||||
id="path1317-1-0" />
|
||||
</g>
|
||||
<g
|
||||
id="g1321-4-0-4"
|
||||
transform="matrix(0.99982636,0,0,1.0000031,-1.5064804e-4,45.000116)">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 0.0021762,8.9798566 V 15.048811"
|
||||
id="path1315-8-3-4" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -0.73956258,14.035325 H 0.74417303 L 0.0021762,15.048811 Z"
|
||||
id="path1317-1-0-4" />
|
||||
</g>
|
||||
<g
|
||||
id="g1321-4-0-4-4"
|
||||
transform="matrix(0.99982636,0,0,1.0000031,-1.5064804e-4,60.000116)">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 0.0021762,8.9798566 V 15.048811"
|
||||
id="path1315-8-3-4-7" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -0.73956258,14.035325 H 0.74417303 L 0.0021762,15.048811 Z"
|
||||
id="path1317-1-0-4-6" />
|
||||
</g>
|
||||
<g
|
||||
id="g1321-4-0-4-4-3"
|
||||
transform="matrix(0.99982636,0,0,1.0000031,-1.5064804e-4,75.000116)">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 0.0021762,8.9798566 V 15.048811"
|
||||
id="path1315-8-3-4-7-1" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -0.73956258,14.035325 H 0.74417303 L 0.0021762,15.048811 Z"
|
||||
id="path1317-1-0-4-6-7" />
|
||||
</g>
|
||||
<g
|
||||
id="g1321-4-0-4-4-3-5"
|
||||
transform="matrix(0.99982636,0,0,1.0000031,-1.5064804e-4,90.000116)">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 0.0021762,8.9798566 V 15.048811"
|
||||
id="path1315-8-3-4-7-1-9" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -0.73956258,14.035325 H 0.74417303 L 0.0021762,15.048811 Z"
|
||||
id="path1317-1-0-4-6-7-6" />
|
||||
</g>
|
||||
<g
|
||||
id="g1321-4-0-4-4-3-5-2"
|
||||
transform="matrix(0.99982636,0,0,1.0000031,-1.5064804e-4,105.00012)">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 0.0021762,8.9798566 V 15.048811"
|
||||
id="path1315-8-3-4-7-1-9-1" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -0.73956258,14.035325 H 0.74417303 L 0.0021762,15.048811 Z"
|
||||
id="path1317-1-0-4-6-7-6-7" />
|
||||
</g>
|
||||
<g
|
||||
id="g1321-4-0-4-4-3-5-2-8"
|
||||
transform="matrix(0.99982636,0,0,1.0000031,-1.5064804e-4,120.00012)">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 0.0021762,8.9798566 V 15.048811"
|
||||
id="path1315-8-3-4-7-1-9-1-5" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -0.73956258,14.035325 H 0.74417303 L 0.0021762,15.048811 Z"
|
||||
id="path1317-1-0-4-6-7-6-7-7" />
|
||||
</g>
|
||||
<g
|
||||
id="g1321-4-0-4-4-3-5-2-8-4"
|
||||
transform="matrix(0.99982636,0,0,1.0000031,-1.5064804e-4,135.00012)">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 0.0021762,8.9798566 V 15.048811"
|
||||
id="path1315-8-3-4-7-1-9-1-5-1" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -0.73956258,14.035325 H 0.74417303 L 0.0021762,15.048811 Z"
|
||||
id="path1317-1-0-4-6-7-6-7-7-8" />
|
||||
</g>
|
||||
<g
|
||||
id="g1321-4-0-4-4-3-5-2-8-4-5"
|
||||
transform="matrix(0.99982636,0,0,1.0000031,-1.5064804e-4,150.00012)">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 0.0021762,8.9798566 V 15.048811"
|
||||
id="path1315-8-3-4-7-1-9-1-5-1-9" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -0.73956258,14.035325 H 0.74417303 L 0.0021762,15.048811 Z"
|
||||
id="path1317-1-0-4-6-7-6-7-7-8-7" />
|
||||
</g>
|
||||
<g
|
||||
id="g1321-4-0-4-4-3-5-2-8-4-5-5"
|
||||
transform="matrix(0.99982636,0,0,1.0000031,-1.5064804e-4,165.00012)">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 0.0021762,8.9798566 V 15.048811"
|
||||
id="path1315-8-3-4-7-1-9-1-5-1-9-3" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -0.73956258,14.035325 H 0.74417303 L 0.0021762,15.048811 Z"
|
||||
id="path1317-1-0-4-6-7-6-7-7-8-7-8" />
|
||||
</g>
|
||||
<g
|
||||
id="g1689"
|
||||
transform="matrix(0.99982636,0,0,1.0000031,34.755416,165.00012)">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m -6.5075767,4.3822683 4.0709535,2.3501724 2.58903688,5.3435213 -2.33068508,5.160166 -5.3537243,2.596475"
|
||||
id="path1315-8-3-4-7-1-9-1-5-1-9-3-9"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -6.5833718,3.6560712 V 5.1395446 L -7.5970369,4.3976789 Z"
|
||||
id="path1317-1-0-4-6-7-6-7-7-8-7-8-6" />
|
||||
</g>
|
||||
<g
|
||||
id="g1689-3"
|
||||
transform="matrix(0.99982636,0,0,1.0000031,34.755416,15.084195)">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m -6.5075767,4.3822683 4.0709535,2.3501724 2.58903688,5.3435213 -2.33068508,5.160166 -5.3537243,2.596475"
|
||||
id="path1315-8-3-4-7-1-9-1-5-1-9-3-9-8"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -6.5833718,3.6560712 V 5.1395446 L -7.5970369,4.3976789 Z"
|
||||
id="path1317-1-0-4-6-7-6-7-7-8-7-8-6-6" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 24 KiB |
4
examples/Alloc.nct
Normal file
4
examples/Alloc.nct
Normal file
@@ -0,0 +1,4 @@
|
||||
record Alloc {
|
||||
u8* userdata;
|
||||
u8*(u8* userdata, u8* ptr, u32 sz)* realloc;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
record Allocator {
|
||||
u8* userdata;
|
||||
u8*(u8* userdata, u8* original, u32 size) realloc;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* ListDLC: Dynamic, Linear growth, C-managed
|
||||
*/
|
||||
|
||||
extern u8*(u8*, u32) realloc;
|
||||
extern u8*(u8*, ugpr) realloc;
|
||||
|
||||
record ListDLC[T, S; growth] {
|
||||
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) -> {
|
||||
if((*this).size == (*this).capacity) {
|
||||
u32 newcap = (*this).capacity + growth;
|
||||
S newcap = (*this).capacity + growth;
|
||||
(*this).capacity = newcap;
|
||||
(*this).data = realloc((*this).data, newcap * @sizeof T);
|
||||
}
|
||||
|
||||
43
examples/ListDLU.nct
Normal file
43
examples/ListDLU.nct
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* ListDLU: Dynamic, Linear growth, unmanaged
|
||||
*/
|
||||
|
||||
use Alloc;
|
||||
|
||||
record ListDLU[T, S; growth] {
|
||||
S capacity;
|
||||
S size;
|
||||
T[?]* data;
|
||||
}
|
||||
|
||||
ListDLU_remove: [T, S; growth]u0(ListDLU[T, S; growth]* this, S index) -> {
|
||||
T* data0 = &(*(*this).data)[index];
|
||||
T* data1 = data0 + @sizeof T;
|
||||
S sz = (*this).size;
|
||||
(*this).size = (*this).size - 1;
|
||||
loop {
|
||||
if(index == sz) {
|
||||
break;
|
||||
}
|
||||
|
||||
*data0 = *data1;
|
||||
|
||||
data0 = data0 + @sizeof T;
|
||||
data1 = data1 + @sizeof T;
|
||||
index = index + 1;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
ListDLU_add: [T, S; growth]u0(ListDLU[T, S; growth]* this, Alloc *alloc, T value) -> {
|
||||
if((*this).size == (*this).capacity) {
|
||||
u32 newcap = (*this).capacity + growth;
|
||||
(*this).capacity = newcap;
|
||||
(*this).data = (*alloc).realloc((*alloc).userdata, (*this).data, newcap * @sizeof T);
|
||||
}
|
||||
|
||||
(*((*this).data))[(*this).size] = value;
|
||||
(*this).size = (*this).size + 1;
|
||||
|
||||
return;
|
||||
};
|
||||
6
examples/LogicalAnd.nct
Normal file
6
examples/LogicalAnd.nct
Normal file
@@ -0,0 +1,6 @@
|
||||
u16 x = 0;
|
||||
u16 y = 5;
|
||||
|
||||
if(x && y) {
|
||||
u16 z = 9;
|
||||
}
|
||||
7
examples/LogicalAndOr.nct
Normal file
7
examples/LogicalAndOr.nct
Normal file
@@ -0,0 +1,7 @@
|
||||
u16 x = 0;
|
||||
u16 y = 5;
|
||||
u16 z = 9;
|
||||
|
||||
if(x && y || z) {
|
||||
u16 w = 15;
|
||||
}
|
||||
6
examples/LogicalOr.nct
Normal file
6
examples/LogicalOr.nct
Normal file
@@ -0,0 +1,6 @@
|
||||
u16 x = 0;
|
||||
u16 y = 5;
|
||||
|
||||
if(x || y) {
|
||||
u16 z = 9;
|
||||
}
|
||||
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;
|
||||
9
examples/ReachingDefinitions.nct
Normal file
9
examples/ReachingDefinitions.nct
Normal file
@@ -0,0 +1,9 @@
|
||||
u16 x = 0;
|
||||
|
||||
x = 5;
|
||||
|
||||
if(x == 2) {
|
||||
x = 3;
|
||||
}
|
||||
|
||||
x = 4;
|
||||
@@ -2,16 +2,16 @@ use ListDLC;
|
||||
|
||||
@section(".text");
|
||||
|
||||
@instantiate ListDLC_remove[u32, u32; 9];
|
||||
@instantiate ListDLC_add[u32, u32; 9];
|
||||
@instantiate ListDLC_remove[u16, u16; 9];
|
||||
@instantiate ListDLC_add[u16, u16; 9];
|
||||
|
||||
main: u0() -> {
|
||||
ListDLC[u32, u32; 9] list;
|
||||
ListDLC_add[u32, u32; 9](&list, 1234);
|
||||
ListDLC_add[u32, u32; 9](&list, 4321);
|
||||
ListDLC_add[u32, u32; 9](&list, 7777);
|
||||
ListDLC_add[u32, u32; 9](&list, 6969);
|
||||
ListDLC_remove[u32, u32; 9](&list, 1);
|
||||
ListDLC[u16, u16; 9] list;
|
||||
ListDLC_add[u16, u16; 9](&list, 1234);
|
||||
ListDLC_add[u16, u16; 9](&list, 4321);
|
||||
ListDLC_add[u16, u16; 9](&list, 7777);
|
||||
ListDLC_add[u16, u16; 9](&list, 6969);
|
||||
ListDLC_remove[u16, u16; 9](&list, 1);
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
32
examples/UserListDLU.nct
Normal file
32
examples/UserListDLU.nct
Normal file
@@ -0,0 +1,32 @@
|
||||
use ListDLU;
|
||||
|
||||
@section(".text");
|
||||
|
||||
@instantiate ListDLU_remove[u32, u32; 9];
|
||||
@instantiate ListDLU_add[u32, u32; 9];
|
||||
|
||||
extern u8*(u8* ptr, u32 sz) realloc;
|
||||
libc_realloc: u8*(u8* userdata, u8* ptr, u32 sz) -> {
|
||||
return realloc(ptr, sz);
|
||||
};
|
||||
|
||||
main: u0() -> {
|
||||
Alloc libc;
|
||||
libc.realloc = &libc_realloc;
|
||||
|
||||
ListDLU[u32, u32; 9] list;
|
||||
list.capacity = 0;
|
||||
list.size = 0;
|
||||
list.data = 0;
|
||||
|
||||
ListDLU_add[u32, u32; 9](&list, &libc, 1234);
|
||||
ListDLU_add[u32, u32; 9](&list, &libc, 4321);
|
||||
ListDLU_add[u32, u32; 9](&list, &libc, 7777);
|
||||
ListDLU_add[u32, u32; 9](&list, &libc, 6969);
|
||||
|
||||
ListDLU_remove[u32, u32; 9](&list, 1);
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
@section(".bss");
|
||||
@@ -50,8 +50,7 @@ loop {
|
||||
}
|
||||
codePtr = codePtr + 1;
|
||||
}
|
||||
}
|
||||
if(data[dataPtr] != 0) {
|
||||
} else {
|
||||
stckPtr = stckPtr + 1;
|
||||
stck[stckPtr] = codePtr;
|
||||
}
|
||||
@@ -62,8 +61,7 @@ loop {
|
||||
if(code[codePtr] == 93) {
|
||||
if(data[dataPtr] == 0) {
|
||||
stckPtr = stckPtr - 1;
|
||||
}
|
||||
if(data[dataPtr] != 0) {
|
||||
} else {
|
||||
codePtr = stck[stckPtr];
|
||||
}
|
||||
}
|
||||
@@ -72,4 +70,4 @@ loop {
|
||||
}
|
||||
|
||||
codePtr = codePtr + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
fibonacci: u32(u32 n) -> {
|
||||
fibonacci: u16(u16 n) -> {
|
||||
if(n <= 1) {
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
u16 x: 5;
|
||||
loop {
|
||||
u16* y = 257;
|
||||
u9 w = -4;
|
||||
u16 p = *y;
|
||||
u4 z = p + 3;
|
||||
u2 o = -w;
|
||||
|
||||
if(x != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(x != 0) {
|
||||
x = 2;
|
||||
} else {
|
||||
x = 5;
|
||||
}
|
||||
|
||||
352
src/ast/ast.c
Normal file
352
src/ast/ast.c
Normal file
@@ -0,0 +1,352 @@
|
||||
#include"ast.h"
|
||||
|
||||
#include<stdint.h>
|
||||
#include<string.h>
|
||||
#include<stdlib.h>
|
||||
#include<assert.h>
|
||||
#include<stdarg.h>
|
||||
#include"ntc.h"
|
||||
#include"reporting.h"
|
||||
#include"utils.h"
|
||||
#include"x86/arch.h"
|
||||
|
||||
const char *AST_KIND_STR[] = { AST_KINDS(GEN_STRI) };
|
||||
|
||||
void generic_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *tlc, void *ud, GenericVisitorHandler preHandler, GenericVisitorHandler postHandler) {
|
||||
if(preHandler) preHandler(nptr, stmt, stmtPrev, chu, tlc, ud);
|
||||
|
||||
AST *n = *nptr;
|
||||
|
||||
if(n->nodeKind == AST_CHUNK) {
|
||||
AST *sPrev = NULL;
|
||||
AST **s = &n->chunk.statementFirst;
|
||||
while(*s) {
|
||||
generic_visitor(s, *s, sPrev, n, tlc, ud, preHandler, postHandler);
|
||||
|
||||
sPrev = *s;
|
||||
s = &sPrev->statement.next;
|
||||
}
|
||||
} else if(n->nodeKind == AST_STMT_ASSIGN) {
|
||||
generic_visitor(&n->stmtAssign.what, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||
if(n->stmtAssign.to) {
|
||||
generic_visitor(&n->stmtAssign.to, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||
}
|
||||
} else if(n->nodeKind == AST_STMT_IF) {
|
||||
generic_visitor(&n->stmtIf.expression, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||
generic_visitor(&n->stmtIf.then, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||
if(n->stmtIf.elss) {
|
||||
generic_visitor(&n->stmtIf.elss, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||
}
|
||||
} else if(n->nodeKind == AST_STMT_LOOP) {
|
||||
generic_visitor(&n->stmtLoop.body, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||
} else if(n->nodeKind == AST_STMT_BREAK) {
|
||||
} else if(n->nodeKind == AST_STMT_CONTINUE) {
|
||||
} else if(n->nodeKind == AST_STMT_EXT_ALIGN) {
|
||||
} else if(n->nodeKind == AST_STMT_DECL) {
|
||||
if(n->stmtDecl.expression) {
|
||||
generic_visitor(&n->stmtDecl.expression, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||
}
|
||||
} else if(n->nodeKind == AST_STMT_EXPR) {
|
||||
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_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) {
|
||||
if(n->stmtReturn.val) {
|
||||
generic_visitor(&n->stmtReturn.val, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||
}
|
||||
} else if(n->nodeKind == AST_EXPR_BINARY_OP) {
|
||||
if(n->exprBinOp.operands[0]) {
|
||||
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) {
|
||||
generic_visitor(&n->exprCall.what, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||
|
||||
for(size_t i = 0; i < n->exprCall.what->expression.type->pointer.of->function.argCount; i++) {
|
||||
generic_visitor(&n->exprCall.args[i], stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||
}
|
||||
} else if(n->nodeKind == AST_EXPR_CAST) {
|
||||
generic_visitor(&n->exprCast.what, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||
} else if(n->nodeKind == AST_EXPR_FUNC) {
|
||||
generic_visitor(&n->exprFunc.chunk, NULL, NULL, n->exprFunc.chunk, n->exprFunc.chunk, ud, preHandler, postHandler);
|
||||
} else if(n->nodeKind == AST_EXPR_UNARY_OP) {
|
||||
generic_visitor(&n->exprUnOp.operand, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||
} else if(n->nodeKind == AST_EXPR_VAR) {
|
||||
} else if(n->nodeKind == AST_EXPR_STACK_POINTER) {
|
||||
} else if(n->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||
} else if(n->nodeKind == AST_EXPR_STRING_LITERAL) {
|
||||
} else if(n->nodeKind == AST_EXPR_ARRAY) {
|
||||
assert(n->expression.type->type == TYPE_TYPE_ARRAY);
|
||||
assert(n->expression.type->array.length != 0);
|
||||
|
||||
for(size_t i = 0; i < n->expression.type->array.length; i++) {
|
||||
generic_visitor(&n->exprArray.items[i], stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||
}
|
||||
} else if(n->nodeKind == AST_EXPR_EXT_SALLOC) {
|
||||
} else if(n->nodeKind == AST_EXPR_DOT) {
|
||||
generic_visitor(&n->exprDot.a, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||
} else if(n->nodeKind == AST_EXPR_EXT_SIZEOF) {
|
||||
if(n->exprExtSizeOf.ofExpr) {
|
||||
generic_visitor(&n->exprExtSizeOf.ofExpr, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||
}
|
||||
} else if(n->nodeKind == AST_STMT_JUMP) {
|
||||
if(n->stmtJump.condition) {
|
||||
generic_visitor(&n->stmtJump.condition, stmt, stmtPrev, chu, tlc, ud, preHandler, postHandler);
|
||||
}
|
||||
} else if(n->nodeKind == AST_STMT_LABEL) {
|
||||
} else if(n->nodeKind == AST_EXPR_NULL) {
|
||||
} else {
|
||||
stahp_node(n, "generic_visitor: unhandled %s", AST_KIND_STR[n->nodeKind]);
|
||||
}
|
||||
|
||||
if(postHandler) postHandler(nptr, stmt, stmtPrev, chu, tlc, ud);
|
||||
}
|
||||
|
||||
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 == AST_EXPR_PRIMITIVE) {
|
||||
return a->exprPrim.val == b->exprPrim.val;
|
||||
} else if(a->nodeKind == AST_EXPR_VAR) {
|
||||
return a->exprVar.thing == b->exprVar.thing;
|
||||
} else if(a->nodeKind == AST_EXPR_UNARY_OP) {
|
||||
return a->exprUnOp.operator == b->exprUnOp.operator && ast_expression_equal(a->exprUnOp.operand, b->exprUnOp.operand);
|
||||
} else if(a->nodeKind == AST_EXPR_BINARY_OP) {
|
||||
return a->exprBinOp.operator == b->exprBinOp.operator && ast_expression_equal(a->exprBinOp.operands[0], b->exprBinOp.operands[0]) && ast_expression_equal(a->exprBinOp.operands[1], b->exprBinOp.operands[1]);
|
||||
} else if(a->nodeKind == AST_EXPR_STACK_POINTER) {
|
||||
return 1;
|
||||
} else if(a->nodeKind == AST_EXPR_CAST) {
|
||||
return ast_expression_equal(a->exprCast.what, b->exprCast.what) && type_equal(a->exprCast.to, b->exprCast.to) && a->exprCast.reinterpretation == b->exprCast.reinterpretation;
|
||||
} else {
|
||||
stahp_node(a, "ast_expression_equal: unhandled %s", AST_KIND_STR[a->nodeKind]);
|
||||
}
|
||||
}
|
||||
|
||||
// This function may return three values: YES (1), NO (0) or UNKNOWN (-1).
|
||||
// ... Ew
|
||||
int ast_stmt_is_after(const AST *chunk, const AST *s1, const AST *s2) {
|
||||
const AST *s = chunk->chunk.statementFirst;
|
||||
|
||||
while(1) {
|
||||
if(s && s->nodeKind == AST_STMT_LOOP) {
|
||||
int i = ast_stmt_is_after(s->stmtLoop.body, s1, s2);
|
||||
if(i == 1 || (i == 0 && s1 != NULL)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
if(s == s1) {
|
||||
return 0;
|
||||
}
|
||||
if(s == s2) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(!s) break;
|
||||
|
||||
if(s->nodeKind == AST_STMT_IF) {
|
||||
int i = ast_stmt_is_after(s->stmtIf.then, s1, s2);
|
||||
if(i == 1 || (i == 0 && s1 != NULL)) {
|
||||
return i;
|
||||
}
|
||||
|
||||
if(s->stmtIf.elss) {
|
||||
i = ast_stmt_is_after(s->stmtIf.elss, s1, s2);
|
||||
if(i == 1 || (i == 0 && s1 != NULL)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s = s->statement.next;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
AST *ast_get_label_by_name(AST *tlc, const char *name) {
|
||||
for(AST *s = tlc->chunk.statementFirst; s; s = s->statement.next) {
|
||||
if(s->nodeKind == AST_STMT_LABEL && !strcmp(s->stmtLabel.name, name)) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
stahp(0, 0, "Label %s not found", name);
|
||||
}
|
||||
|
||||
static void *memdup(void *a, size_t len) {
|
||||
void *r = malloc(len);
|
||||
memcpy(r, a, len);
|
||||
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) {
|
||||
if(src->nodeKind == AST_EXPR_VAR) {
|
||||
return memdup(src, sizeof(ASTExprVar));
|
||||
} else if(src->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||
return memdup(src, sizeof(ASTExprPrimitive));
|
||||
} else if(src->nodeKind == AST_EXPR_VAR) {
|
||||
return memdup(src, sizeof(ASTExprVar));
|
||||
} else if(src->nodeKind == AST_EXPR_UNARY_OP) {
|
||||
ASTExprUnaryOp *n = memdup(src, sizeof(ASTExprUnaryOp));
|
||||
n->operand = ast_deep_copy(n->operand);
|
||||
return n;
|
||||
}
|
||||
|
||||
abort();
|
||||
}
|
||||
|
||||
AST *ast_cast_expr(AST *what, Type *to) {
|
||||
if(what == NULL) {
|
||||
stahp(0, 0, "NULL what passed to ast_cast_expr");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Only exists at parse-time, hence not part of type system and is handled separately */
|
||||
if(what->nodeKind == AST_EXPR_STRING_LITERAL) {
|
||||
if(to->type == TYPE_TYPE_ARRAY && type_equal(primitive_parse("u8"), to->array.of) && (to->array.length == what->exprStrLit.length || to->array.length == -1)) {
|
||||
ASTExprArray *ret = calloc(1, sizeof(*ret));
|
||||
ret->nodeKind = AST_EXPR_ARRAY;
|
||||
ret->items = calloc(what->exprStrLit.length, sizeof(*ret->items));
|
||||
ret->type = to;
|
||||
|
||||
// If this declaration is of an unknown-length array type (eg u8[?]) then update the length
|
||||
if(to->array.length == -1) {
|
||||
to->array.length = what->exprStrLit.length;
|
||||
}
|
||||
|
||||
for(int i = 0; i < what->exprStrLit.length; i++) {
|
||||
uint8_t bajt = what->exprStrLit.data[i];
|
||||
|
||||
ASTExprPrimitive *item = calloc(1, sizeof(*item));
|
||||
item->nodeKind = AST_EXPR_PRIMITIVE;
|
||||
item->type = to->array.of;
|
||||
item->val = bajt;
|
||||
|
||||
ret->items[i] = (AST*) item;
|
||||
}
|
||||
|
||||
return (AST*) ret;
|
||||
} else if(to->type == TYPE_TYPE_PRIMITIVE) {
|
||||
if(to->primitive.width != what->exprStrLit.length * 8) {
|
||||
stahp_node(what, "Size mismatch between string literal and target type");
|
||||
}
|
||||
|
||||
ASTExprPrimitive *ret = calloc(1, sizeof(*ret));
|
||||
ret->nodeKind = AST_EXPR_PRIMITIVE;
|
||||
ret->type = to;
|
||||
ret->val = 0;
|
||||
memcpy(&ret->val, what->exprStrLit.data, what->exprStrLit.length);
|
||||
return (AST*) ret;
|
||||
} else abort();
|
||||
}
|
||||
|
||||
bool copy = false;
|
||||
|
||||
// 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) {
|
||||
what = what->exprVar.thing->data.cexpr.concrete;
|
||||
copy = true;
|
||||
}
|
||||
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_is_castable(what->expression.type, to)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if(what->nodeKind == AST_EXPR_PRIMITIVE && (to->type == TYPE_TYPE_PRIMITIVE || to->type == TYPE_TYPE_POINTER)) {
|
||||
ASTExprPrimitive *ret = calloc(1, sizeof(*ret));
|
||||
ret->nodeKind = AST_EXPR_PRIMITIVE;
|
||||
ret->type = to;
|
||||
|
||||
if(to->type == TYPE_TYPE_PRIMITIVE) {
|
||||
ret->val = what->exprPrim.val & ((1UL << to->primitive.width) - 1);
|
||||
} else {
|
||||
ret->val = what->exprPrim.val & ((1UL << (8 * type_size(to))) - 1);
|
||||
}
|
||||
|
||||
return (AST*) ret;
|
||||
} else {
|
||||
ASTExprCast *ret = calloc(1, sizeof(*ret));
|
||||
ret->nodeKind = AST_EXPR_CAST;
|
||||
ret->type = to;
|
||||
ret->what = what;
|
||||
ret->to = to;
|
||||
return (AST*) ret;
|
||||
}
|
||||
|
||||
fail:
|
||||
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,11 +38,14 @@
|
||||
K(AST_EXPR_ARRAY) \
|
||||
K(AST_EXPR_FUNC) \
|
||||
K(AST_STMT_EXT_ORG) \
|
||||
K(AST_STMT_EXT_SECTION) \
|
||||
K(AST_SECTION) \
|
||||
K(AST_STMT_RETURN) \
|
||||
K(AST_EXPR_EXT_SALLOC) \
|
||||
K(AST_EXPR_DOT) \
|
||||
K(AST_EXPR_EXT_SIZEOF)
|
||||
K(AST_EXPR_EXT_SIZEOF) \
|
||||
K(AST_STMT_JUMP) \
|
||||
K(AST_STMT_LABEL) \
|
||||
K(AST_EXPR_NULL)
|
||||
|
||||
typedef enum ENUMPAK { AST_KINDS(GEN_ENUM) } ASTKind;
|
||||
extern const char *AST_KIND_STR[];
|
||||
@@ -71,6 +74,11 @@ typedef enum ENUMPAK {
|
||||
BINOP_LEQUAL = 13,
|
||||
BINOP_GEQUAL = 14,
|
||||
|
||||
BINOP_LOGICAL_AND = 15,
|
||||
BINOP_LOGICAL_OR = 16,
|
||||
|
||||
BINOP_DEREF = 17,
|
||||
|
||||
BINOP_WTF = 999,
|
||||
} BinaryOp;
|
||||
|
||||
@@ -100,10 +108,10 @@ static inline BinaryOp binop_comp_opposite(BinaryOp op) {
|
||||
}
|
||||
|
||||
typedef enum ENUMPAK {
|
||||
UNOP_DEREF = 0,
|
||||
UNOP_NEGATE = 1,
|
||||
UNOP_BITWISE_NOT = 2,
|
||||
UNOP_REF = 3,
|
||||
UNOP_NOT = 4,
|
||||
} UnaryOp;
|
||||
|
||||
union AST;
|
||||
@@ -123,6 +131,9 @@ typedef struct {
|
||||
ASTExpr;
|
||||
|
||||
int val;
|
||||
|
||||
// If true, increase this literal during stack growths
|
||||
bool stackGrowth;
|
||||
} ASTExprPrimitive;
|
||||
|
||||
typedef struct {
|
||||
@@ -179,6 +190,10 @@ typedef struct {
|
||||
typedef struct {
|
||||
ASTBase;
|
||||
union AST *next;
|
||||
|
||||
// Used for reaching definition analysis
|
||||
bool dirty;
|
||||
ReachingDefs rd;
|
||||
} ASTStmt;
|
||||
|
||||
typedef struct {
|
||||
@@ -189,7 +204,17 @@ typedef struct {
|
||||
union AST *expression;
|
||||
} ASTStmtDecl;
|
||||
|
||||
typedef struct {
|
||||
struct ASTChunk;
|
||||
typedef struct ASTSection {
|
||||
ASTBase;
|
||||
|
||||
Token name;
|
||||
struct ASTChunk *tlc;
|
||||
|
||||
struct ASTSection *next;
|
||||
} ASTSection;
|
||||
|
||||
typedef struct ASTChunk {
|
||||
ASTBase;
|
||||
|
||||
/* Flattened variable array for global register allocation */
|
||||
@@ -215,6 +240,7 @@ typedef struct {
|
||||
union AST *expression;
|
||||
|
||||
union AST *then;
|
||||
union AST *elss;
|
||||
} ASTStmtIf;
|
||||
|
||||
typedef struct {
|
||||
@@ -284,12 +310,6 @@ typedef struct {
|
||||
size_t val;
|
||||
} ASTStmtExtOrg;
|
||||
|
||||
typedef struct {
|
||||
ASTStmt;
|
||||
|
||||
Token name;
|
||||
} ASTStmtExtSection;
|
||||
|
||||
typedef struct {
|
||||
ASTStmt;
|
||||
|
||||
@@ -304,6 +324,23 @@ typedef struct {
|
||||
Type *ofType;
|
||||
} ASTExprExtSizeOf;
|
||||
|
||||
typedef struct {
|
||||
ASTStmt;
|
||||
|
||||
union AST *condition;
|
||||
char *label;
|
||||
} ASTStmtJump;
|
||||
|
||||
typedef struct {
|
||||
ASTStmt;
|
||||
|
||||
char *name;
|
||||
} ASTStmtLabel;
|
||||
|
||||
typedef struct {
|
||||
ASTExpr;
|
||||
} ASTExprNull;
|
||||
|
||||
typedef union AST {
|
||||
ASTBase;
|
||||
|
||||
@@ -331,8 +368,11 @@ typedef union AST {
|
||||
ASTExprDot exprDot;
|
||||
ASTExprExtSalloc exprExtSalloc;
|
||||
ASTStmtExtOrg stmtExtOrg;
|
||||
ASTStmtExtSection stmtExtSection;
|
||||
ASTSection section;
|
||||
ASTExprExtSizeOf exprExtSizeOf;
|
||||
ASTStmtJump stmtJump;
|
||||
ASTStmtLabel stmtLabel;
|
||||
ASTExprNull exprNull;
|
||||
} AST;
|
||||
|
||||
#pragma pack(pop)
|
||||
@@ -344,26 +384,28 @@ int ast_expression_equal(AST*, AST*);
|
||||
|
||||
int ast_stmt_is_after(const AST *chunk, const AST *s1, const AST *s2);
|
||||
|
||||
void ast_usedef_reset(AST *chu);
|
||||
|
||||
char *ast_dump(AST *tlc);
|
||||
|
||||
AST *ast_deep_copy(AST*);
|
||||
|
||||
AST *ast_cast_expr(AST *what, Type *to);
|
||||
|
||||
void ast_spill_to_stack(AST *tlc, ScopeItem *vte);
|
||||
|
||||
void ast_typecheck(AST *tlc);
|
||||
|
||||
void ast_grow_stack_frame(AST *tlc, size_t bytes);
|
||||
AST *ast_get_label_by_name(AST *tlc, const char *name);
|
||||
|
||||
void ast_commutativity_pass(AST *tlc);
|
||||
bool ast_references_stack(AST *a);
|
||||
|
||||
void ast_sroa(AST *tlc);
|
||||
bool ast_is_scopeitem_referenced(AST *tlc, ScopeItem *si);
|
||||
|
||||
// Convert segmented derefences like *x into segment move and *x.offset.
|
||||
// Must be done before ast_sroa.
|
||||
void ast_segmented_dereference(AST *tlc);
|
||||
// name becomes OWNED
|
||||
ScopeItem *ast_tlc_new_var(AST *tlc, char *name, Type *itstype);
|
||||
|
||||
#include"dump.h"
|
||||
#include"stack.h"
|
||||
#include"desegment.h"
|
||||
#include"sroa.h"
|
||||
#include"linearize.h"
|
||||
#include"usedef.h"
|
||||
#include"commutativity.h"
|
||||
#include"scr.h"
|
||||
|
||||
#endif
|
||||
21
src/ast/commutativity.c
Normal file
21
src/ast/commutativity.c
Normal file
@@ -0,0 +1,21 @@
|
||||
#include"commutativity.h"
|
||||
|
||||
#include"ast.h"
|
||||
|
||||
static void commutativity_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||
AST *n = *nptr;
|
||||
|
||||
if(n->nodeKind == AST_EXPR_BINARY_OP) {
|
||||
if(binop_is_commutative(n->exprBinOp.operator)) {
|
||||
if(n->exprBinOp.operands[0]->nodeKind == AST_EXPR_PRIMITIVE && n->exprBinOp.operands[1]->nodeKind != AST_EXPR_PRIMITIVE) {
|
||||
AST *tmp = n->exprBinOp.operands[0];
|
||||
n->exprBinOp.operands[0] = n->exprBinOp.operands[1];
|
||||
n->exprBinOp.operands[1] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void ast_commutativity_pass(AST *tlc) {
|
||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, NULL, NULL, commutativity_visitor);
|
||||
}
|
||||
|
||||
8
src/ast/commutativity.h
Normal file
8
src/ast/commutativity.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
union AST;
|
||||
|
||||
// This pass makes sure that integer literals are always operand 1
|
||||
// (the right operand) in a commutative binary operation,
|
||||
// simplifying passes further down.
|
||||
void ast_commutativity_pass(union AST *chu);
|
||||
93
src/ast/desegment.c
Normal file
93
src/ast/desegment.c
Normal file
@@ -0,0 +1,93 @@
|
||||
#include"desegment.h"
|
||||
|
||||
#include"ast.h"
|
||||
#include"ntc.h"
|
||||
|
||||
#include"x86/arch.h"
|
||||
|
||||
#include"utils.h"
|
||||
#include<stdlib.h>
|
||||
|
||||
static void ast_tlc_add_var(AST *tlc, ScopeItem *si) {
|
||||
tlc->chunk.vars = realloc(tlc->chunk.vars, sizeof(*tlc->chunk.vars) * ++tlc->chunk.varCount);
|
||||
tlc->chunk.vars[tlc->chunk.varCount - 1] = si;
|
||||
}
|
||||
|
||||
/* Split away complex expression into a new local variable */
|
||||
static AST *varify(AST *tlc, AST *chunk, AST **stmtPrev, AST *stmt, AST *e) {
|
||||
static size_t idx = 0;
|
||||
|
||||
ScopeItem *vte = calloc(1, sizeof(*vte));
|
||||
vte->kind = SCOPEITEM_VAR;
|
||||
vte->type = e->expression.type;
|
||||
vte->data.var.name = malp("$varify_%lu", idx++);
|
||||
|
||||
ast_tlc_add_var(tlc, vte);
|
||||
|
||||
// Alter AST
|
||||
|
||||
ASTExprVar *ev[2];
|
||||
for(int i = 0; i < 2; i++) {
|
||||
ev[i] = calloc(1, sizeof(ASTExprVar));
|
||||
ev[i]->nodeKind = AST_EXPR_VAR;
|
||||
ev[i]->type = e->expression.type;
|
||||
ev[i]->thing = vte;
|
||||
}
|
||||
|
||||
ASTStmtAssign *assign = calloc(1, sizeof(*assign));
|
||||
assign->nodeKind = AST_STMT_ASSIGN;
|
||||
assign->what = (AST*) ev[0];
|
||||
assign->to = e;
|
||||
vte->data.var.declaration = (AST*) assign;
|
||||
|
||||
if(*stmtPrev) {
|
||||
(*stmtPrev)->statement.next = (AST*) assign;
|
||||
} else {
|
||||
chunk->chunk.statementFirst = (AST*) assign;
|
||||
}
|
||||
assign->next = stmt;
|
||||
*stmtPrev = (AST*) assign;
|
||||
|
||||
// Reset ud-chain
|
||||
|
||||
vte->data.var.usedefFirst = NULL;
|
||||
vte->data.var.usedefLast = NULL;
|
||||
|
||||
return (AST*) ev[1];
|
||||
}
|
||||
|
||||
static void ast_segmented_dereference_visitor(AST **aptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||
if(tlc != ud) {
|
||||
return;
|
||||
}
|
||||
|
||||
AST *n = *aptr;
|
||||
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;
|
||||
|
||||
AST *v;
|
||||
if(n->exprBinOp.operands[0]->nodeKind == AST_EXPR_VAR)
|
||||
v = n->exprBinOp.operands[0];
|
||||
else
|
||||
v = varify(tlc, chunk, &stmtPrev, stmt, n->exprBinOp.operands[0]);
|
||||
|
||||
ASTExprDot *edseg = calloc(1, sizeof(*edseg));
|
||||
edseg->type = n->exprBinOp.operands[0]->expression.type->record.fieldTypes[0];
|
||||
edseg->nodeKind = AST_EXPR_DOT;
|
||||
edseg->a = v;
|
||||
edseg->b = strdup("segment");
|
||||
|
||||
ASTExprDot *ed = calloc(1, sizeof(*ed));
|
||||
ed->type = n->exprBinOp.operands[0]->expression.type->record.fieldTypes[1];
|
||||
ed->nodeKind = AST_EXPR_DOT;
|
||||
ed->a = ast_deep_copy(v);
|
||||
ed->b = strdup("offset");
|
||||
|
||||
n->exprBinOp.operands[0] = (AST*) ed;
|
||||
n->exprBinOp.operands[1] = (AST*) edseg;
|
||||
}
|
||||
}
|
||||
|
||||
void ast_segmented_dereference(AST *tlc) {
|
||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, tlc, ast_segmented_dereference_visitor, NULL);
|
||||
}
|
||||
7
src/ast/desegment.h
Normal file
7
src/ast/desegment.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
union AST;
|
||||
|
||||
// Convert segmented derefences like *x into segment move and *x.offset.
|
||||
// Must be done before ast_sroa.
|
||||
void ast_segmented_dereference(union AST *tlc);
|
||||
348
src/ast/dump.c
Normal file
348
src/ast/dump.c
Normal file
@@ -0,0 +1,348 @@
|
||||
#include"dump.h"
|
||||
|
||||
#include"ast.h"
|
||||
#include"types.h"
|
||||
#include<stdlib.h>
|
||||
#include<stdio.h>
|
||||
#include<string.h>
|
||||
#include"utils.h"
|
||||
|
||||
char *type_to_string(Type *t) {
|
||||
if(t->type == TYPE_TYPE_PRIMITIVE) {
|
||||
char ret[16] = {};
|
||||
int i = 0;
|
||||
|
||||
ret[i++] = t->primitive.isFloat ? 'f' : (t->primitive.isUnsigned ? 'u' : 'i');
|
||||
snprintf(ret + i, sizeof(ret) - i, "%i", t->primitive.width);
|
||||
|
||||
return strdup(ret);
|
||||
} else if(t->type == TYPE_TYPE_POINTER) {
|
||||
char *c = type_to_string(t->pointer.of);
|
||||
char *r = malp("%s*", c);
|
||||
free(c);
|
||||
return r;
|
||||
} else if(t->type == TYPE_TYPE_RECORD) {
|
||||
return malp("%s", t->record.name);
|
||||
} else if(t->type == TYPE_TYPE_GENERIC) {
|
||||
return malp("%s", t->generic.paramName);
|
||||
} else if(t->type == TYPE_TYPE_ARRAY) {
|
||||
char *of = type_to_string(t->array.of);
|
||||
char *len = NULL;
|
||||
if(t->array.lengthIsGeneric) {
|
||||
len = malp("");
|
||||
} else {
|
||||
len = malp("%li", t->array.length);
|
||||
}
|
||||
char *r = malp("%s[%s]", of, len);
|
||||
free(of);
|
||||
free(len);
|
||||
return r;
|
||||
}
|
||||
|
||||
return strdup("@unimp");
|
||||
}
|
||||
|
||||
static char *ast_dumpe(AST *tlc, AST *e) {
|
||||
if(!e) {
|
||||
return malp("(null)");
|
||||
}
|
||||
|
||||
if(e->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||
return malp("%i", e->exprPrim.val);
|
||||
} else if(e->nodeKind == AST_EXPR_VAR) {
|
||||
ScopeItem *vte = e->exprVar.thing;
|
||||
|
||||
if(vte->kind == SCOPEITEM_VAR) {
|
||||
return strdup(vte->data.var.name);
|
||||
} else if(vte->kind == SCOPEITEM_SYMBOL) {
|
||||
return strdup(vte->data.symbol.name);
|
||||
} else if(vte->kind == SCOPEITEM_CEXPR) {
|
||||
return ast_dumpe(tlc, vte->data.cexpr.concrete);
|
||||
} else abort();
|
||||
} else if(e->nodeKind == AST_EXPR_UNARY_OP) {
|
||||
const char *op = NULL;
|
||||
switch(e->exprUnOp.operator) {
|
||||
case UNOP_REF:
|
||||
op = "&";
|
||||
break;
|
||||
case UNOP_BITWISE_NOT:
|
||||
op = "~";
|
||||
break;
|
||||
case UNOP_NEGATE:
|
||||
op = "-";
|
||||
break;
|
||||
case UNOP_NOT:
|
||||
op = "!";
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
char *c = ast_dumpe(tlc, e->exprUnOp.operand);
|
||||
char *r = malp("%s%s", op, c);
|
||||
free(c);
|
||||
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) {
|
||||
char *a = ast_dumpe(tlc, e->exprBinOp.operands[0]);
|
||||
char *b = ast_dumpe(tlc, e->exprBinOp.operands[1]);
|
||||
const char *op;
|
||||
switch(e->exprBinOp.operator) {
|
||||
case BINOP_ADD:
|
||||
op = "+";
|
||||
break;
|
||||
case BINOP_SUB:
|
||||
op = "-";
|
||||
break;
|
||||
case BINOP_MUL:
|
||||
op = "*";
|
||||
break;
|
||||
case BINOP_DIV:
|
||||
op = "/";
|
||||
break;
|
||||
case BINOP_BITWISE_AND:
|
||||
op = "&";
|
||||
break;
|
||||
case BINOP_BITWISE_OR:
|
||||
op = "|";
|
||||
break;
|
||||
case BINOP_BITWISE_XOR:
|
||||
op = "^";
|
||||
break;
|
||||
case BINOP_EQUAL:
|
||||
op = "==";
|
||||
break;
|
||||
case BINOP_NEQUAL:
|
||||
op = "!=";
|
||||
break;
|
||||
case BINOP_LESS:
|
||||
op = "<";
|
||||
break;
|
||||
case BINOP_GREATER:
|
||||
op = ">";
|
||||
break;
|
||||
case BINOP_LEQUAL:
|
||||
op = "<=";
|
||||
break;
|
||||
case BINOP_GEQUAL:
|
||||
op = ">=";
|
||||
break;
|
||||
case BINOP_MULHI:
|
||||
op = "*^";
|
||||
break;
|
||||
case BINOP_LOGICAL_AND:
|
||||
op = "&&";
|
||||
break;
|
||||
case BINOP_LOGICAL_OR:
|
||||
op = "||";
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
char *r = malp("(%s %s %s)", a, op, b);
|
||||
free(a);
|
||||
free(b);
|
||||
return r;
|
||||
} else if(e->nodeKind == AST_EXPR_STACK_POINTER) {
|
||||
return malp("@stack");
|
||||
} else if(e->nodeKind == AST_EXPR_FUNC) {
|
||||
char *out = NULL;
|
||||
|
||||
if(type_is_generic(e->expression.type)) {
|
||||
out = malp("(generic)");
|
||||
return out;
|
||||
}
|
||||
|
||||
{
|
||||
char *rettype = type_to_string(e->expression.type->function.ret);
|
||||
out = malp("%s(", rettype);
|
||||
free(rettype);
|
||||
}
|
||||
|
||||
for(int i = 0; i < e->expression.type->function.argCount; i++) {
|
||||
char *argtype = type_to_string(e->expression.type->function.args[i]);
|
||||
char *out2 = malp(i == e->expression.type->function.argCount - 1 ? "%s%s" : "%s%s, ", out, argtype);
|
||||
free(out);
|
||||
free(argtype);
|
||||
out = out2;
|
||||
}
|
||||
|
||||
{
|
||||
char *choonk = ast_dumpc(tlc, e->exprFunc.chunk);
|
||||
char *out2 = malp("%s) {\n%s}", out, choonk);
|
||||
free(out);
|
||||
free(choonk);
|
||||
out = out2;
|
||||
}
|
||||
|
||||
return out;
|
||||
} else if(e->nodeKind == AST_EXPR_CALL) {
|
||||
char *w = ast_dumpe(tlc, e->exprCall.what);
|
||||
char *out = malp("%s(", w);
|
||||
free(w);
|
||||
size_t argCount = e->exprCall.what->expression.type->pointer.of->function.argCount;
|
||||
for(size_t i = 0; i < argCount; i++) {
|
||||
char *a = ast_dumpe(tlc, e->exprCall.args[i]);
|
||||
char *out2 = malp(i == argCount - 1 ? "%s%s)" : "%s%s, ", out, a);
|
||||
free(a);
|
||||
free(out);
|
||||
out = out2;
|
||||
}
|
||||
return out;
|
||||
} else if(e->nodeKind == AST_EXPR_EXT_SALLOC) {
|
||||
char *w = type_to_string(e->exprExtSalloc.size);
|
||||
char *out = malp("@salloc(%s)", w);
|
||||
free(w);
|
||||
return out;
|
||||
} else if(e->nodeKind == AST_EXPR_CAST) {
|
||||
char *a = ast_dumpe(tlc, e->exprCast.what);
|
||||
char *b = type_to_string(e->exprCast.to);
|
||||
char *out = malp("(%s as %s)", a, b);
|
||||
free(a);
|
||||
free(b);
|
||||
return out;
|
||||
} else if(e->nodeKind == AST_EXPR_DOT) {
|
||||
char *a = ast_dumpe(tlc, e->exprDot.a);
|
||||
char *out = malp(e->nodeKind == AST_EXPR_BINARY_OP ? "(%s).%s" : "%s.%s", a, e->exprDot.b);
|
||||
free(a);
|
||||
return out;
|
||||
}
|
||||
|
||||
return malp("@unimp:%s", AST_KIND_STR[e->nodeKind]);
|
||||
}
|
||||
|
||||
static char *ast_dumps(AST *tlc, AST *s) {
|
||||
if(s->nodeKind == AST_STMT_ASSIGN) {
|
||||
if(s->stmtAssign.to) {
|
||||
char *a = ast_dumpe(tlc, s->stmtAssign.what);
|
||||
char *b = ast_dumpe(tlc, s->stmtAssign.to);
|
||||
char *r = malp("%s = %s;", a, b);
|
||||
free(a);
|
||||
free(b);
|
||||
return r;
|
||||
} else {
|
||||
char *a = ast_dumpe(tlc, s->stmtAssign.what);
|
||||
char *r = malp("%s = ; /* fake def */", a);
|
||||
free(a);
|
||||
return r;
|
||||
}
|
||||
} else if(s->nodeKind == AST_STMT_JUMP) {
|
||||
char *a = ast_dumpe(tlc, s->stmtJump.condition);
|
||||
char *r = malp("jump %s if %s;", s->stmtJump.label, a);
|
||||
free(a);
|
||||
return r;
|
||||
} else if(s->nodeKind == AST_STMT_LABEL) {
|
||||
return malp("@label %s;", s->stmtLabel.name);
|
||||
} else if(s->nodeKind == AST_STMT_LOOP) {
|
||||
char *inner = ast_dumpc(tlc, s->stmtLoop.body);
|
||||
char *c = malp("loop {\n%s}", inner);
|
||||
free(inner);
|
||||
return c;
|
||||
} else if(s->nodeKind == AST_STMT_IF) {
|
||||
char *cond = ast_dumpe(tlc, s->stmtIf.expression);
|
||||
char *inner = ast_dumpc(tlc, s->stmtIf.then);
|
||||
char *elss = s->stmtIf.elss ? ast_dumpc(tlc, s->stmtIf.elss) : NULL;
|
||||
char *c;
|
||||
if(elss) {
|
||||
c = malp("if(%s) {\n%s} else {\n%s}", cond, inner, elss);
|
||||
free(elss);
|
||||
} else {
|
||||
c = malp("if(%s) {\n%s}", cond, inner);
|
||||
}
|
||||
free(cond);
|
||||
free(inner);
|
||||
return c;
|
||||
} else if(s->nodeKind == AST_STMT_EXPR && s->stmtExpr.expr->nodeKind == AST_EXPR_VAR) {
|
||||
const char *name;
|
||||
|
||||
if(s->stmtExpr.expr->exprVar.thing->kind == SCOPEITEM_VAR) {
|
||||
name = s->stmtExpr.expr->exprVar.thing->data.var.name;
|
||||
} else {
|
||||
name = s->stmtExpr.expr->exprVar.thing->data.symbol.name;
|
||||
}
|
||||
|
||||
return malp("%s; /* loop guard */", name);
|
||||
} else if(s->nodeKind == AST_STMT_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) {
|
||||
if(s->stmtReturn.val) {
|
||||
char *e = ast_dumpe(tlc, s->stmtReturn.val);
|
||||
char *c = malp("return %s;", e);
|
||||
free(e);
|
||||
return c;
|
||||
} else {
|
||||
return malp("return;");
|
||||
}
|
||||
}
|
||||
|
||||
return malp("@unimp:%s;", AST_KIND_STR[s->nodeKind]);
|
||||
}
|
||||
|
||||
static char *stmt_live_range_dbgs(AST *tlc, AST *stmt) {
|
||||
char *ret = malp("");
|
||||
for(size_t v = 0; v < tlc->chunk.varCount; v++) {
|
||||
ScopeItem *si = tlc->chunk.vars[v];
|
||||
|
||||
if(si->data.var.liveRangeStart == stmt) {
|
||||
char *ret2 = malp("%s (%s start)", ret, si->data.var.name);
|
||||
free(ret);
|
||||
ret = ret2;
|
||||
}
|
||||
|
||||
if(si->data.var.liveRangeEnd == stmt) {
|
||||
char *ret2 = malp("%s (%s end)", ret, si->data.var.name);
|
||||
free(ret);
|
||||
ret = ret2;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *ast_dumpc(AST *tlc, AST *chu) {
|
||||
AST *stmt = chu->chunk.statementFirst;
|
||||
|
||||
char *ret = malp("");
|
||||
|
||||
while(stmt) {
|
||||
char *new = ast_dumps(tlc, stmt);
|
||||
char *users = stmt_live_range_dbgs(tlc, stmt);
|
||||
char *ret2 = malp("%s%s%s\n", ret, new, users);
|
||||
free(ret);
|
||||
free(new);
|
||||
free(users);
|
||||
ret = ret2;
|
||||
|
||||
stmt = stmt->statement.next;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *ast_dump(AST *tlc) {
|
||||
return ast_dumpc(tlc, tlc);
|
||||
}
|
||||
5
src/ast/dump.h
Normal file
5
src/ast/dump.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
union AST;
|
||||
char *ast_dump(union AST *chu);
|
||||
char *ast_dumpc(union AST *tlc, union AST *chu);
|
||||
161
src/ast/linearize.c
Normal file
161
src/ast/linearize.c
Normal file
@@ -0,0 +1,161 @@
|
||||
#include"linearize.h"
|
||||
|
||||
#include"ast.h"
|
||||
#include"ntc.h"
|
||||
#include"utils.h"
|
||||
#include<stdlib.h>
|
||||
|
||||
static void ast_patch_in_chunk(AST *chunkOuter, AST *stmtBefore, AST *chunkInner, AST *stmtAfter) {
|
||||
if(chunkInner->chunk.statementFirst) {
|
||||
stmtBefore->statement.next = chunkInner->chunk.statementFirst;
|
||||
|
||||
for(AST *z = chunkInner->chunk.statementFirst; z; z = z->statement.next) {
|
||||
if(!z->statement.next) {
|
||||
z->statement.next = stmtAfter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stmtBefore->statement.next = stmtAfter;
|
||||
}
|
||||
}
|
||||
|
||||
#define LOOPSTACKSIZE 64
|
||||
struct LinearizeState {
|
||||
size_t currentDepth;
|
||||
size_t loopStackStart[LOOPSTACKSIZE];
|
||||
size_t loopStackEnd[LOOPSTACKSIZE];
|
||||
AST *loopAfters[LOOPSTACKSIZE];
|
||||
};
|
||||
static void ast_linearize_visitor_pre(AST **aptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||
static size_t nextLabelIdx = 0;
|
||||
|
||||
struct LinearizeState *state = ud;
|
||||
|
||||
AST *a = *aptr;
|
||||
|
||||
if(a->nodeKind == AST_STMT_IF) {
|
||||
if(a->stmtIf.elss == NULL) {
|
||||
ASTExprUnaryOp *notcond = calloc(1, sizeof(*notcond));
|
||||
notcond->nodeKind = AST_EXPR_UNARY_OP;
|
||||
notcond->operator = UNOP_NOT;
|
||||
notcond->operand = a->stmtIf.expression;
|
||||
notcond->type = a->stmtIf.expression->expression.type;
|
||||
|
||||
ASTStmtJump *jump = calloc(1, sizeof(ASTStmtJump));
|
||||
jump->nodeKind = AST_STMT_JUMP;
|
||||
jump->condition = (AST*) notcond;
|
||||
jump->label = malp("$Lin%lu", nextLabelIdx++);
|
||||
|
||||
ASTStmtLabel *label = calloc(1, sizeof(ASTStmtLabel));
|
||||
label->nodeKind = AST_STMT_LABEL;
|
||||
label->name = strdup(jump->label);
|
||||
|
||||
if(stmtPrev) {
|
||||
stmtPrev->statement.next = (AST*) jump;
|
||||
} else {
|
||||
chunk->chunk.statementFirst = (AST*) jump;
|
||||
}
|
||||
|
||||
ast_patch_in_chunk(chunk, (AST*) jump, a->stmtIf.then, (AST*) label);
|
||||
|
||||
label->next = a->statement.next;
|
||||
} else {
|
||||
ASTExprUnaryOp *notcond = calloc(1, sizeof(*notcond));
|
||||
notcond->nodeKind = AST_EXPR_UNARY_OP;
|
||||
notcond->operator = UNOP_NOT;
|
||||
notcond->operand = a->stmtIf.expression;
|
||||
notcond->type = a->stmtIf.expression->expression.type;
|
||||
|
||||
ASTStmtJump *jump2Else = calloc(1, sizeof(ASTStmtJump));
|
||||
jump2Else->nodeKind = AST_STMT_JUMP;
|
||||
jump2Else->condition = (AST*) notcond;
|
||||
jump2Else->label = malp("$Lin%lu", nextLabelIdx++);
|
||||
|
||||
ASTStmtJump *jump2End = calloc(1, sizeof(ASTStmtJump));
|
||||
jump2End->nodeKind = AST_STMT_JUMP;
|
||||
jump2End->label = malp("$Lin%lu", nextLabelIdx++);
|
||||
|
||||
ASTStmtLabel *labelElse = calloc(1, sizeof(ASTStmtLabel));
|
||||
labelElse->nodeKind = AST_STMT_LABEL;
|
||||
labelElse->name = strdup(jump2Else->label);
|
||||
|
||||
ASTStmtLabel *labelEnd = calloc(1, sizeof(ASTStmtLabel));
|
||||
labelEnd->nodeKind = AST_STMT_LABEL;
|
||||
labelEnd->name = strdup(jump2End->label);
|
||||
|
||||
if(stmtPrev) {
|
||||
stmtPrev->statement.next = (AST*) jump2Else;
|
||||
} else {
|
||||
chunk->chunk.statementFirst = (AST*) jump2Else;
|
||||
}
|
||||
|
||||
ast_patch_in_chunk(chunk, (AST*) jump2Else, a->stmtIf.then, (AST*) jump2End);
|
||||
|
||||
jump2End->next = (AST*) labelElse;
|
||||
|
||||
ast_patch_in_chunk(chunk, (AST*) labelElse, a->stmtIf.elss, (AST*) labelEnd);
|
||||
|
||||
labelEnd->next = a->statement.next;
|
||||
}
|
||||
} else if(a->nodeKind == AST_STMT_LOOP) {
|
||||
size_t startIdx = nextLabelIdx++;
|
||||
size_t endIdx = nextLabelIdx++;
|
||||
|
||||
ASTStmtJump *jump = calloc(1, sizeof(ASTStmtJump));
|
||||
jump->nodeKind = AST_STMT_JUMP;
|
||||
jump->condition = NULL;
|
||||
jump->label = malp("$Lin%lu", startIdx);
|
||||
|
||||
ASTStmtLabel *startLabel = calloc(1, sizeof(ASTStmtLabel));
|
||||
startLabel->nodeKind = AST_STMT_LABEL;
|
||||
startLabel->name = strdup(jump->label);
|
||||
|
||||
ASTStmtLabel *endLabel = calloc(1, sizeof(ASTStmtLabel));
|
||||
endLabel->nodeKind = AST_STMT_LABEL;
|
||||
endLabel->name = malp("$Lin%lu", endIdx);
|
||||
|
||||
if(stmtPrev) {
|
||||
stmtPrev->statement.next = (AST*) startLabel;
|
||||
} else {
|
||||
chunk->chunk.statementFirst = (AST*) startLabel;
|
||||
}
|
||||
|
||||
ast_patch_in_chunk(chunk, (AST*) startLabel, a->stmtLoop.body, (AST*) jump);
|
||||
|
||||
jump->next = (AST*) endLabel;
|
||||
endLabel->next = a->statement.next;
|
||||
|
||||
state->currentDepth++;
|
||||
state->loopStackStart[state->currentDepth - 1] = startIdx;
|
||||
state->loopStackEnd[state->currentDepth - 1] = endIdx;
|
||||
state->loopAfters[state->currentDepth - 1] = (AST*) endLabel;
|
||||
} else if(a->nodeKind == AST_STMT_BREAK) {
|
||||
ASTStmtJump *jump = calloc(1, sizeof(ASTStmtJump));
|
||||
jump->nodeKind = AST_STMT_JUMP;
|
||||
jump->condition = NULL;
|
||||
jump->label = malp("$Lin%lu", state->loopStackEnd[state->currentDepth - 1]);
|
||||
jump->next = a->statement.next;
|
||||
*aptr = (AST*) jump;
|
||||
} else if(a->nodeKind == AST_STMT_CONTINUE) {
|
||||
ASTStmtJump *jump = calloc(1, sizeof(ASTStmtJump));
|
||||
jump->nodeKind = AST_STMT_JUMP;
|
||||
jump->condition = NULL;
|
||||
jump->label = malp("$Lin%lu", state->loopStackStart[state->currentDepth - 1]);
|
||||
jump->next = a->statement.next;
|
||||
*aptr = (AST*) jump;
|
||||
}
|
||||
}
|
||||
static void ast_linearize_visitor_post(AST **aptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||
struct LinearizeState *state = ud;
|
||||
|
||||
AST *a = *aptr;
|
||||
|
||||
if(state->currentDepth && state->loopAfters[state->currentDepth - 1] == a) {
|
||||
state->currentDepth--;
|
||||
}
|
||||
}
|
||||
void ast_linearize(AST *tlc) {
|
||||
struct LinearizeState state = {};
|
||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, &state, ast_linearize_visitor_pre, ast_linearize_visitor_post);
|
||||
}
|
||||
4
src/ast/linearize.h
Normal file
4
src/ast/linearize.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
union AST;
|
||||
void ast_linearize(union AST *tlc);
|
||||
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);
|
||||
91
src/ast/sroa.c
Normal file
91
src/ast/sroa.c
Normal file
@@ -0,0 +1,91 @@
|
||||
#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 DecomposeAutomaticRecordState {
|
||||
AST *tlc;
|
||||
ScopeItem *target;
|
||||
ScopeItem **replacements;
|
||||
};
|
||||
static void ast_decompose_automatic_record_visitor(AST **aptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||
AST *n = *aptr;
|
||||
|
||||
struct DecomposeAutomaticRecordState *state = ud;
|
||||
|
||||
if(state->tlc != tlc) return;
|
||||
|
||||
if(n->nodeKind == AST_EXPR_DOT && n->exprDot.a->nodeKind == AST_EXPR_VAR && n->exprDot.a->exprVar.thing == state->target) {
|
||||
size_t idx;
|
||||
for(idx = 0; strcmp(state->target->type->record.fieldNames[idx], n->exprDot.b); idx++);
|
||||
|
||||
AST *ev = calloc(1, sizeof(ASTExprVar));
|
||||
ev->nodeKind = AST_EXPR_VAR;
|
||||
ev->expression.type = state->target->type->record.fieldTypes[idx];
|
||||
ev->exprVar.thing = state->replacements[idx];
|
||||
|
||||
*aptr = (AST*) ev;
|
||||
}
|
||||
}
|
||||
static void ast_decompose_automatic_record(AST *tlc, ScopeItem *target) {
|
||||
assert(target->kind == SCOPEITEM_VAR);
|
||||
assert(target->type->type == TYPE_TYPE_RECORD);
|
||||
|
||||
struct DecomposeAutomaticRecordState state = {
|
||||
.tlc = tlc,
|
||||
.target = target,
|
||||
.replacements = calloc(target->type->record.fieldCount, sizeof(ScopeItem)),
|
||||
};
|
||||
|
||||
tlc->chunk.vars = realloc(tlc->chunk.vars, sizeof(*tlc->chunk.vars) * (tlc->chunk.varCount + target->type->record.fieldCount));
|
||||
|
||||
for(size_t f = 0; f < target->type->record.fieldCount; f++) {
|
||||
ScopeItem *si = calloc(1, sizeof(*si));
|
||||
si->kind = SCOPEITEM_VAR;
|
||||
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.declaration = target->data.var.declaration;
|
||||
state.replacements[f] = si;
|
||||
|
||||
tlc->chunk.vars[tlc->chunk.varCount++] = si;
|
||||
}
|
||||
|
||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, &state, ast_decompose_automatic_record_visitor, NULL);
|
||||
|
||||
free(state.replacements);
|
||||
}
|
||||
|
||||
void ast_sroa(AST *tlc) {
|
||||
for(int i = 0; i < tlc->chunk.varCount; i++) {
|
||||
ScopeItem *si = tlc->chunk.vars[i];
|
||||
|
||||
if(!is_sroa_candidate(si)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(ast_is_scopeitem_referenced(tlc, si)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ast_decompose_automatic_record(tlc, si);
|
||||
|
||||
memmove(tlc->chunk.vars + i, tlc->chunk.vars + i + 1, sizeof(*tlc->chunk.vars) * (tlc->chunk.varCount - i - 1));
|
||||
tlc->chunk.varCount--;
|
||||
|
||||
/* Restart */
|
||||
i = -1;
|
||||
}
|
||||
|
||||
if(ntc_get_int("pdbg")) {
|
||||
char *astdump = ast_dump(tlc);
|
||||
fprintf(stderr, "### SROA ###\n%s\n", astdump);
|
||||
free(astdump);
|
||||
}
|
||||
}
|
||||
4
src/ast/sroa.h
Normal file
4
src/ast/sroa.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
union AST;
|
||||
void ast_sroa(union AST *tlc);
|
||||
102
src/ast/stack.c
Normal file
102
src/ast/stack.c
Normal file
@@ -0,0 +1,102 @@
|
||||
#include"stack.h"
|
||||
|
||||
#include"ast.h"
|
||||
#include"ntc.h"
|
||||
#include"scope.h"
|
||||
#include<assert.h>
|
||||
#include<stdlib.h>
|
||||
#include"x86/arch.h"
|
||||
|
||||
struct Spill2StackState {
|
||||
AST *targetTLC;
|
||||
ScopeItem *target; // can be NULL
|
||||
size_t stackGrowth;
|
||||
};
|
||||
static void spill2stack_visitor(AST **aptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||
struct Spill2StackState *this = ud;
|
||||
|
||||
if(tlc != this->targetTLC) {
|
||||
// Don't do anything.
|
||||
return;
|
||||
}
|
||||
|
||||
AST *a = *aptr;
|
||||
|
||||
if(a == tlc) {
|
||||
a->chunk.stackReservation += this->stackGrowth;
|
||||
} else if(a->nodeKind == AST_EXPR_VAR) {
|
||||
|
||||
if(a->exprVar.thing == this->target) {
|
||||
// DO THE SPILL
|
||||
|
||||
ASTExprStackPointer *rsp = calloc(1, sizeof(*rsp));
|
||||
rsp->nodeKind = AST_EXPR_STACK_POINTER;
|
||||
rsp->type = type_u(8 * arch_gpr_size());
|
||||
|
||||
ASTExprPrimitive *offset = calloc(1, sizeof(*offset));
|
||||
offset->nodeKind = AST_EXPR_PRIMITIVE;
|
||||
offset->type = rsp->type;
|
||||
offset->val = -this->stackGrowth; // This will be affected by the other part of this pass, so we must reverse
|
||||
offset->stackGrowth = true;
|
||||
|
||||
ASTExprBinaryOp *bop = calloc(1, sizeof(*bop));
|
||||
bop->nodeKind = AST_EXPR_BINARY_OP;
|
||||
bop->type = rsp->type;
|
||||
bop->operator = BINOP_ADD;
|
||||
bop->operands[0] = (AST*) rsp;
|
||||
bop->operands[1] = (AST*) offset;
|
||||
|
||||
ASTExprBinaryOp *deref = calloc(1, sizeof(*deref));
|
||||
deref->nodeKind = AST_EXPR_BINARY_OP;
|
||||
deref->type = a->expression.type;
|
||||
deref->operator = BINOP_DEREF;
|
||||
deref->operands[0] = (AST*) bop;
|
||||
|
||||
*aptr = (AST*) deref;
|
||||
}
|
||||
|
||||
} else if(a->nodeKind == AST_EXPR_PRIMITIVE && a->exprPrim.stackGrowth) {
|
||||
|
||||
// Guaranteed to not require more normalization
|
||||
a->exprPrim.val += this->stackGrowth;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void ast_spill_to_stack(AST *tlc, ScopeItem *vte) {
|
||||
assert(tlc->nodeKind == AST_CHUNK);
|
||||
assert(vte != NULL);
|
||||
assert(vte->kind == SCOPEITEM_VAR);
|
||||
|
||||
if(ntc_get_int("pdbg")) {
|
||||
fprintf(stderr, "### SPILLING %s ###\n", vte->data.var.name);
|
||||
}
|
||||
|
||||
struct Spill2StackState state;
|
||||
memset(&state, 0, sizeof(state));
|
||||
state.target = vte;
|
||||
state.targetTLC = tlc;
|
||||
state.stackGrowth = (type_size(vte->type) + 7) & ~7;
|
||||
|
||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, &state, spill2stack_visitor, NULL);
|
||||
|
||||
size_t vteIndex = 0;
|
||||
while(tlc->chunk.vars[vteIndex] != vte) vteIndex++;
|
||||
|
||||
memmove(&tlc->chunk.vars[vteIndex], &tlc->chunk.vars[vteIndex + 1], sizeof(*tlc->chunk.vars) * (tlc->chunk.varCount - vteIndex - 1));
|
||||
tlc->chunk.varCount--;
|
||||
}
|
||||
|
||||
void ast_grow_stack_frame(AST *tlc, size_t bytes) {
|
||||
if(bytes == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct Spill2StackState state;
|
||||
memset(&state, 0, sizeof(state));
|
||||
state.target = NULL; // spill nothing
|
||||
state.targetTLC = tlc;
|
||||
state.stackGrowth = bytes;
|
||||
|
||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, &state, spill2stack_visitor, NULL);
|
||||
}
|
||||
9
src/ast/stack.h
Normal file
9
src/ast/stack.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include<stddef.h>
|
||||
|
||||
union AST;
|
||||
struct ScopeItem;
|
||||
|
||||
void ast_spill_to_stack(union AST *tlc, struct ScopeItem *vte);
|
||||
void ast_grow_stack_frame(union AST *tlc, size_t bytes);
|
||||
223
src/ast/usedef.c
Normal file
223
src/ast/usedef.c
Normal file
@@ -0,0 +1,223 @@
|
||||
#include"usedef.h"
|
||||
|
||||
#include"ast.h"
|
||||
#include"ntc.h"
|
||||
#include<stdlib.h>
|
||||
#include<assert.h>
|
||||
#include"reporting.h"
|
||||
#include"scope.h"
|
||||
|
||||
static void rd_kill(ReachingDefs *a, ScopeItem *var) {
|
||||
for(size_t i = a->defCount; i --> 0;) {
|
||||
AST *def = a->defs[i];
|
||||
|
||||
assert(def->nodeKind == AST_STMT_ASSIGN);
|
||||
assert(def->stmtAssign.what->nodeKind == AST_EXPR_VAR);
|
||||
assert(def->stmtAssign.what->exprVar.thing->kind == SCOPEITEM_VAR);
|
||||
|
||||
if(def->stmtAssign.what->exprVar.thing == var) {
|
||||
memmove(&a->defs[i], &a->defs[i + 1], sizeof(*a->defs) * (a->defCount - i - 1));
|
||||
a->defCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool rd_equal(ReachingDefs *a, ReachingDefs *b) {
|
||||
if(a->defCount != b->defCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < a->defCount; i++) {
|
||||
if(a->defs[i] != b->defs[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int compar_ptr(const void *a, const void *b) {
|
||||
return *(uintptr_t*) a - *(uintptr_t*) b;
|
||||
}
|
||||
|
||||
static bool rd_find(ReachingDefs *dest, union AST *ast) {
|
||||
return !!bsearch(&ast, dest->defs, dest->defCount, sizeof(*dest->defs), compar_ptr);
|
||||
}
|
||||
|
||||
static void rd_add(ReachingDefs *dest, union AST *ast) {
|
||||
if(rd_find(dest, ast)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dest->defs = realloc(dest->defs, sizeof(*dest->defs) * (++dest->defCount));
|
||||
dest->defs[dest->defCount - 1] = ast;
|
||||
qsort(dest->defs, dest->defCount, sizeof(*dest->defs), compar_ptr);
|
||||
}
|
||||
|
||||
static void rd_union(ReachingDefs *dest, ReachingDefs *src) {
|
||||
for(size_t i = 0; i < src->defCount; i++) {
|
||||
rd_add(dest, src->defs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static ReachingDefs rd_compute_in(AST *tlc, AST *stmt, AST *stmtPrev) {
|
||||
ReachingDefs rd = {};
|
||||
|
||||
// The previous statement is a predecessor unless it's an unconditional jump statement
|
||||
if(stmtPrev && (stmtPrev->nodeKind != AST_STMT_JUMP || stmtPrev->stmtJump.condition)) {
|
||||
rd_union(&rd, &stmtPrev->statement.rd);
|
||||
}
|
||||
|
||||
// If this is a label statement, then all jumps to this statement are predecessors
|
||||
if(stmt->nodeKind == AST_STMT_LABEL) {
|
||||
for(AST *s = tlc->chunk.statementFirst; s; s = s->statement.next) {
|
||||
if(s->nodeKind == AST_STMT_JUMP && !strcmp(s->stmtJump.label, stmt->stmtLabel.name)) {
|
||||
rd_union(&rd, &s->statement.rd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rd;
|
||||
}
|
||||
|
||||
static void rd_step(AST *tlc, AST *stmt, AST *stmtPrev) {
|
||||
stmt->statement.dirty = false;
|
||||
|
||||
ReachingDefs rd = rd_compute_in(tlc, stmt, stmtPrev);
|
||||
|
||||
if(stmt->nodeKind == AST_STMT_ASSIGN && stmt->stmtAssign.what->nodeKind == AST_EXPR_VAR && stmt->stmtAssign.what->exprVar.thing->kind == SCOPEITEM_VAR) {
|
||||
rd_kill(&rd, stmt->stmtAssign.what->exprVar.thing);
|
||||
rd_add(&rd, stmt);
|
||||
}
|
||||
|
||||
if(!rd_equal(&rd, &stmt->statement.rd)) {
|
||||
// Set dirty flag on all successors
|
||||
|
||||
// The next statement is a successor unless it's an unconditional jump statement
|
||||
if(stmt->statement.next && (stmt->nodeKind != AST_STMT_JUMP || stmt->stmtJump.condition)) {
|
||||
stmt->statement.next->statement.dirty = true;
|
||||
}
|
||||
|
||||
// If this is a jump statement, the target label is a successor
|
||||
if(stmt->nodeKind == AST_STMT_JUMP) {
|
||||
AST *label = ast_get_label_by_name(tlc, stmt->stmtJump.label);
|
||||
label->statement.dirty = true;
|
||||
}
|
||||
|
||||
stmt->statement.rd = rd;
|
||||
}
|
||||
}
|
||||
|
||||
static void usedef_generation_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||
AST *n = *nptr;
|
||||
|
||||
if(n->nodeKind == AST_EXPR_VAR) {
|
||||
ReachingDefs *rd = &stmt->statement.rd;
|
||||
|
||||
ScopeItem *si = n->exprVar.thing;
|
||||
|
||||
for(size_t rdi = 0; rdi < rd->defCount; rdi++) {
|
||||
AST *def = rd->defs[rdi];
|
||||
|
||||
if(def->stmtAssign.what->exprVar.thing == si) {
|
||||
UseDef *ud = calloc(1, sizeof(*ud));
|
||||
ud->def = def;
|
||||
ud->use = n;
|
||||
ud->stmt = stmt;
|
||||
|
||||
if(!si->data.var.usedefFirst) {
|
||||
si->data.var.usedefFirst = si->data.var.usedefLast = ud;
|
||||
} else {
|
||||
si->data.var.usedefLast->next = ud;
|
||||
si->data.var.usedefLast = ud;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void ast_usedef_reset(AST *chu) {
|
||||
for(size_t i = 0; i < chu->chunk.varCount; i++) {
|
||||
ScopeItem *vte = chu->chunk.vars[i];
|
||||
|
||||
assert(vte->kind == SCOPEITEM_VAR);
|
||||
|
||||
vte->data.var.usedefFirst = NULL;
|
||||
vte->data.var.usedefLast = NULL;
|
||||
vte->data.var.liveRangeStart = NULL;
|
||||
vte->data.var.liveRangeEnd = NULL;
|
||||
}
|
||||
|
||||
for(AST *s = chu->chunk.statementFirst; s; s = s->statement.next) {
|
||||
if(s->nodeKind == AST_STMT_IF || s->nodeKind == AST_STMT_LOOP) {
|
||||
stahp(0, 0, "UD-chain generation requires a completely linear IR");
|
||||
}
|
||||
|
||||
s->statement.dirty = true;
|
||||
|
||||
s->statement.rd.defCount = 0;
|
||||
free(s->statement.rd.defs);
|
||||
s->statement.rd.defs = NULL;
|
||||
}
|
||||
|
||||
for(size_t rdsteps = 0;; rdsteps++) {
|
||||
//fprintf(stderr, "RD step %lu\n", rdsteps);
|
||||
|
||||
AST *prev = NULL;
|
||||
AST *dirty = NULL;
|
||||
|
||||
// Find at least one dirty statement
|
||||
for(AST *s = chu->chunk.statementFirst; s; prev = s, s = s->statement.next) {
|
||||
if(s->statement.dirty) {
|
||||
dirty = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!dirty) {
|
||||
// Completed reaching definition computation
|
||||
break;
|
||||
}
|
||||
|
||||
rd_step(chu, dirty, prev);
|
||||
}
|
||||
|
||||
generic_visitor(&chu, NULL, NULL, chu, chu, NULL, usedef_generation_visitor, NULL);
|
||||
|
||||
for(size_t i = 0; i < chu->chunk.varCount; i++) {
|
||||
ScopeItem *vte = chu->chunk.vars[i];
|
||||
|
||||
assert(vte->kind == SCOPEITEM_VAR);
|
||||
|
||||
assert(!!vte->data.var.usedefFirst == !!vte->data.var.usedefLast);
|
||||
|
||||
if(vte->data.var.usedefFirst) {
|
||||
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
|
||||
for(AST *s = chu->chunk.statementFirst; s; s = s->statement.next) {
|
||||
if(s->nodeKind == AST_STMT_JUMP) {
|
||||
AST *target = ast_get_label_by_name(chu, s->stmtJump.label);
|
||||
|
||||
for(size_t sii = 0; sii < chu->chunk.varCount; 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) {
|
||||
|
||||
si->data.var.liveRangeEnd = s;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(ntc_get_int("pdbg")) {
|
||||
char *astdump = ast_dump(chu);
|
||||
fprintf(stderr, "### USEDEF GENERATED ###\n%s\n", astdump);
|
||||
free(astdump);
|
||||
}
|
||||
}
|
||||
4
src/ast/usedef.h
Normal file
4
src/ast/usedef.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
union AST;
|
||||
void ast_usedef_reset(union AST *chu);
|
||||
20
src/lexer.c
20
src/lexer.c
@@ -27,7 +27,9 @@ char *TOKEN_NAMES[] = {
|
||||
"'break'",
|
||||
"','",
|
||||
"'&'",
|
||||
"'&&'",
|
||||
"'|'",
|
||||
"'||'",
|
||||
"'^'",
|
||||
"'~'",
|
||||
"'=='",
|
||||
@@ -49,6 +51,8 @@ char *TOKEN_NAMES[] = {
|
||||
"'.'",
|
||||
"'as'",
|
||||
"'use'",
|
||||
"'else'",
|
||||
"'null'",
|
||||
};
|
||||
|
||||
static int isAlpha(int c) {
|
||||
@@ -142,9 +146,17 @@ Token nct_tokenize(FILE *f) {
|
||||
return tok;
|
||||
} else if(c == '&') {
|
||||
tok.type = TOKEN_AMPERSAND;
|
||||
int c = nextc(f);
|
||||
if(c == '&') {
|
||||
tok.type = TOKEN_DOUBLE_AMPERSAND;
|
||||
} else ungetc(c, f);
|
||||
return tok;
|
||||
} else if(c == '|') {
|
||||
tok.type = TOKEN_VERTICAL_BAR;
|
||||
int c = nextc(f);
|
||||
if(c == '|') {
|
||||
tok.type = TOKEN_DOUBLE_VERTICAL_BAR;
|
||||
} else ungetc(c, f);
|
||||
return tok;
|
||||
} else if(c == '^') {
|
||||
tok.type = TOKEN_CARET;
|
||||
@@ -296,6 +308,14 @@ Token nct_tokenize(FILE *f) {
|
||||
free(content);
|
||||
tok.type = TOKEN_USE;
|
||||
return tok;
|
||||
} else if(!strcmp(content, "else")) {
|
||||
free(content);
|
||||
tok.type = TOKEN_ELSE;
|
||||
return tok;
|
||||
} else if(!strcmp(content, "null")) {
|
||||
free(content);
|
||||
tok.type = TOKEN_NULL;
|
||||
return tok;
|
||||
}
|
||||
|
||||
tok.type = TOKEN_IDENTIFIER;
|
||||
|
||||
@@ -27,7 +27,9 @@ typedef enum {
|
||||
TOKEN_BREAK,
|
||||
TOKEN_COMMA,
|
||||
TOKEN_AMPERSAND,
|
||||
TOKEN_DOUBLE_AMPERSAND,
|
||||
TOKEN_VERTICAL_BAR,
|
||||
TOKEN_DOUBLE_VERTICAL_BAR,
|
||||
TOKEN_CARET,
|
||||
TOKEN_TILDE,
|
||||
TOKEN_DOUBLE_EQUALS,
|
||||
@@ -49,6 +51,8 @@ typedef enum {
|
||||
TOKEN_DOT,
|
||||
TOKEN_AS,
|
||||
TOKEN_USE,
|
||||
TOKEN_ELSE,
|
||||
TOKEN_NULL,
|
||||
} TokenKind;
|
||||
|
||||
typedef struct Token {
|
||||
|
||||
24
src/ntc.c
24
src/ntc.c
@@ -80,28 +80,36 @@ int main(int argc_, char **argv_) {
|
||||
stahp(0, 0, "Failed to read input (%s)", strerror(errno));
|
||||
}
|
||||
|
||||
arch_init();
|
||||
|
||||
Token *tokens = nct_lex(f);
|
||||
|
||||
if(in) fclose(f);
|
||||
|
||||
AST *chunk = nct_parse(tokens);
|
||||
AST *sects = nct_parse(tokens);
|
||||
|
||||
free(tokens);
|
||||
|
||||
if(ntc_get_int("pdbg")) {
|
||||
char *astdump = ast_dump(chunk);
|
||||
char *astdump = ast_dump(sects);
|
||||
fprintf(stderr, "### ORIGINAL ###\n%s\n", astdump);
|
||||
free(astdump);
|
||||
}
|
||||
|
||||
ast_segmented_dereference(chunk);
|
||||
ast_sroa(chunk);
|
||||
dumben_pre(chunk);
|
||||
dumben_go(chunk);
|
||||
ast_segmented_dereference(sects);
|
||||
ast_secondclass_record(sects);
|
||||
ast_sroa(sects);
|
||||
|
||||
while(!cg_go(chunk)) {
|
||||
dumben_go(chunk);
|
||||
ast_linearize(sects);
|
||||
|
||||
arch_normalize_pre(sects);
|
||||
|
||||
arch_normalize(sects);
|
||||
while(!cg_attempt_sections(sects)) {
|
||||
arch_normalize(sects);
|
||||
}
|
||||
|
||||
cg_go_sections(sects);
|
||||
|
||||
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);
|
||||
|
||||
union AST;
|
||||
void arch_init();
|
||||
bool arch_verify_target();
|
||||
int arch_ptr_size();
|
||||
void dumben_pre(union AST *tlc);
|
||||
void dumben_go(union AST *tlc);
|
||||
int cg_go(union AST *tlc);
|
||||
void arch_normalize_pre(union AST *tlc);
|
||||
void arch_normalize(union AST *tlc);
|
||||
int cg_attempt_sections(union AST *tlc);
|
||||
void cg_go_sections(union AST *tlc);
|
||||
|
||||
#endif
|
||||
|
||||
497
src/parse.c
497
src/parse.c
@@ -38,9 +38,6 @@ typedef struct {
|
||||
// Used by pushstat to add statements
|
||||
ASTChunk *currentChunk;
|
||||
|
||||
// Used to place guard variable uses after loops to stop reg allocation from fucking up
|
||||
Scope *loopScope;
|
||||
|
||||
// Used for generating statements that load & store arguments
|
||||
int isInFunction;
|
||||
|
||||
@@ -143,36 +140,6 @@ static AST *exprvar(Parser *P, ScopeItem *v) {
|
||||
a->exprVar.type = v->type;
|
||||
a->exprVar.thing = v;
|
||||
|
||||
/*if(P->loopScope) {
|
||||
// XXX: O(n)!!!!!!!!!
|
||||
|
||||
int inloop = 0;
|
||||
for(Scope *vt = v->owner; vt; vt = vt->parent) {
|
||||
if(vt->parent == P->loopScope) {
|
||||
inloop = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!inloop) {
|
||||
int alreadyAdded = 0;
|
||||
for(size_t i = 0; i < P->guardedVarCount; i++) {
|
||||
if(P->guardedVars[i]->thing == v) {
|
||||
alreadyAdded = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!alreadyAdded) {
|
||||
ASTExprVar *ev = alloc_node(P, sizeof(*ev));
|
||||
memcpy(ev, a, sizeof(*ev));
|
||||
|
||||
P->guardedVars = realloc(P->guardedVars, sizeof(*P->guardedVars) * (P->guardedVarCount + 1));
|
||||
P->guardedVars[P->guardedVarCount++] = ev;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
@@ -185,9 +152,10 @@ static char *parametrize_function_name(Type *t, const char *original, Scope *sco
|
||||
static void binop_implicit_cast(/*Parser *P, */ASTExprBinaryOp *binop) {
|
||||
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);
|
||||
}
|
||||
|
||||
if(type_size(binop->operands[1]->expression.type) < type_size(binop->operands[0]->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);
|
||||
} 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);
|
||||
}
|
||||
|
||||
@@ -234,7 +202,6 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
||||
P->isInFunction++;
|
||||
|
||||
e->chunk = (AST*) nct_parse_chunk(P, 1, 0, scope_new(P->scope), ft);
|
||||
e->chunk->chunk.functionType = ft;
|
||||
|
||||
P->isInFunction--;
|
||||
|
||||
@@ -250,7 +217,7 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
||||
}
|
||||
}
|
||||
|
||||
if(lOP == 6) {
|
||||
if(lOP == 7) {
|
||||
AST *e = NULL;
|
||||
|
||||
if(peek(P, 0).type == TOKEN_NUMBER) {
|
||||
@@ -261,7 +228,7 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
||||
|
||||
ASTExprStackPointer *ret = alloc_node(P, sizeof(*ret));
|
||||
ret->nodeKind = AST_EXPR_STACK_POINTER;
|
||||
ret->type = primitive_parse("u32");
|
||||
ret->type = type_u(8 * arch_sp_size());
|
||||
|
||||
e = (AST*) ret;
|
||||
} else if(!strcmp(peek(P, 0).content, "@salloc")) {
|
||||
@@ -288,7 +255,7 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
||||
ret->ofExpr = nct_parse_expression(P, lOP - 1);
|
||||
}
|
||||
|
||||
ret->type = primitive_parse("u32");
|
||||
ret->type = type_u(8 * arch_gpr_size());
|
||||
|
||||
e = (AST*) ret;
|
||||
} else {
|
||||
@@ -302,6 +269,12 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
||||
|
||||
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) {
|
||||
ASTExprStringLiteral *ret = alloc_node(P, sizeof(*ret));
|
||||
ret->nodeKind = AST_EXPR_STRING_LITERAL;
|
||||
@@ -318,47 +291,177 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
||||
|
||||
expect(P, TOKEN_PAREN_R);
|
||||
}
|
||||
|
||||
while(maybe(P, TOKEN_DOT)) {
|
||||
assert(e->expression.type->type == TYPE_TYPE_RECORD);
|
||||
|
||||
while(maybe(P, TOKEN_DOT) || maybe(P, TOKEN_PAREN_L) || maybe(P, TOKEN_SQUAREN_L)) {
|
||||
Token op = P->tokens[P->i - 1];
|
||||
|
||||
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(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);
|
||||
|
||||
e = (AST*) deref;
|
||||
}
|
||||
}
|
||||
|
||||
if(!foundField) {
|
||||
stahp(fieldTok.row, fieldTok.column, "Field %s does not exist.", fieldTok.content);
|
||||
}
|
||||
|
||||
e = (AST*) d;
|
||||
|
||||
assert(e->expression.type->type == TYPE_TYPE_RECORD);
|
||||
|
||||
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;
|
||||
} else if(lOP == 5) {
|
||||
} else if(lOP == 6) {
|
||||
if(maybe(P, TOKEN_STAR)) {
|
||||
ASTExprUnaryOp *astop = alloc_node(P, sizeof(*astop));
|
||||
astop->nodeKind = AST_EXPR_UNARY_OP;
|
||||
astop->operator = UNOP_DEREF;
|
||||
astop->operand = nct_parse_expression(P, lOP); /* Not +1! */
|
||||
ASTExprBinaryOp *astop = alloc_node(P, sizeof(*astop));
|
||||
astop->nodeKind = AST_EXPR_BINARY_OP;
|
||||
astop->operator = BINOP_DEREF;
|
||||
astop->operands[0] = nct_parse_expression(P, lOP); /* Not +1! */
|
||||
|
||||
if(type_is_segmented_pointer(astop->operand->expression.type)) {
|
||||
astop->type = astop->operand->expression.type->record.fieldTypes[1]->pointer.of;
|
||||
if(type_is_segmented_pointer(astop->operands[0]->expression.type)) {
|
||||
astop->type = astop->operands[0]->expression.type->record.fieldTypes[1]->pointer.of;
|
||||
} else {
|
||||
assert(astop->operand->expression.type->type == TYPE_TYPE_POINTER);
|
||||
astop->type = astop->operand->expression.type->pointer.of;
|
||||
assert(astop->operands[0]->expression.type->type == TYPE_TYPE_POINTER);
|
||||
astop->type = astop->operands[0]->expression.type->pointer.of;
|
||||
}
|
||||
|
||||
return (AST*) astop;
|
||||
@@ -401,120 +504,10 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
||||
return (AST *) astop;
|
||||
}
|
||||
} else return nct_parse_expression(P, lOP + 1);
|
||||
} else if(lOP == 5) {
|
||||
return nct_parse_expression(P, lOP + 1);
|
||||
} else if(lOP == 4) {
|
||||
AST *ret = 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) {
|
||||
stahp(P->tokens[P->i].row, P->tokens[P->i].column, "Only function types may be called.");
|
||||
}
|
||||
|
||||
ASTExprCall *call = alloc_node(P, sizeof(*call));
|
||||
call->nodeKind = AST_EXPR_CALL;
|
||||
call->type = ret->expression.type->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), ret->expression.type->function.args[argCount]);
|
||||
|
||||
argCount++;
|
||||
|
||||
if(maybe(P, TOKEN_PAREN_R)) {
|
||||
break;
|
||||
} else expect(P, TOKEN_COMMA);
|
||||
}
|
||||
}
|
||||
|
||||
if(argCount != call->what->expression.type->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 = primitive_parse("u32");
|
||||
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 == 3) {
|
||||
AST *ret = nct_parse_expression(P, lOP + 1);
|
||||
|
||||
if(peek(P, 0).type == TOKEN_STAR || peek(P, 0).type == TOKEN_SLASH || peek(P, 0).type == TOKEN_STAR_CARET) {
|
||||
while(1) {
|
||||
@@ -553,7 +546,7 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
||||
}
|
||||
|
||||
return ret;
|
||||
} else if(lOP == 2) {
|
||||
} else if(lOP == 3) {
|
||||
AST *ret = nct_parse_expression(P, lOP + 1);
|
||||
|
||||
if(
|
||||
@@ -592,7 +585,7 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
||||
}
|
||||
|
||||
return ret;
|
||||
} else if(lOP == 1) {
|
||||
} else if(lOP == 2) {
|
||||
AST *ret = nct_parse_expression(P, lOP + 1);
|
||||
|
||||
while(maybe(P, TOKEN_AS)) {
|
||||
@@ -601,7 +594,7 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
||||
}
|
||||
|
||||
return ret;
|
||||
} else if(lOP == 0) {
|
||||
} else if(lOP == 1) {
|
||||
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) {
|
||||
@@ -615,6 +608,33 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
||||
else if(maybe(P, TOKEN_GEQUAL)) op = BINOP_GEQUAL;
|
||||
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));
|
||||
astop->nodeKind = AST_EXPR_BINARY_OP;
|
||||
astop->type = NULL;
|
||||
@@ -633,8 +653,6 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
||||
}
|
||||
}
|
||||
|
||||
//ret = ast_expression_optimize(ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
@@ -946,6 +964,8 @@ static AST *parse_declaration(Parser *P) {
|
||||
decl->next = NULL;
|
||||
decl->expression = expr;
|
||||
|
||||
entry->data.var.declaration = (AST*) decl;
|
||||
|
||||
ret = (AST*) decl;
|
||||
|
||||
} else {
|
||||
@@ -1082,17 +1102,24 @@ static void nct_parse_statement(Parser *P) {
|
||||
expect(P, TOKEN_SQUIGGLY_L);
|
||||
ret->then = (AST*) nct_parse_chunk(P, 0, 0, NULL, NULL);
|
||||
expect(P, TOKEN_SQUIGGLY_R);
|
||||
|
||||
if(maybe(P, TOKEN_ELSE)) {
|
||||
expect(P, TOKEN_SQUIGGLY_L);
|
||||
ret->elss = (AST*) nct_parse_chunk(P, 0, 0, NULL, NULL);
|
||||
expect(P, TOKEN_SQUIGGLY_R);
|
||||
}
|
||||
|
||||
return;
|
||||
} else if(maybe(P, TOKEN_LOOP)) {
|
||||
ASTStmtLoop *ret = alloc_node(P, sizeof(*ret));
|
||||
ret->nodeKind = AST_STMT_LOOP;
|
||||
ret->next = NULL;
|
||||
|
||||
int isFirstLoop = P->loopScope == NULL;
|
||||
//int isFirstLoop = P->loopScope == NULL;
|
||||
|
||||
if(isFirstLoop) {
|
||||
P->loopScope = P->scope;
|
||||
}
|
||||
//if(isFirstLoop) {
|
||||
//P->loopScope = P->scope;
|
||||
//}
|
||||
|
||||
expect(P, TOKEN_SQUIGGLY_L);
|
||||
ret->body = (AST*) nct_parse_chunk(P, 0, 1, NULL, NULL);
|
||||
@@ -1100,8 +1127,8 @@ static void nct_parse_statement(Parser *P) {
|
||||
|
||||
pushstat(P, ret);
|
||||
|
||||
if(isFirstLoop) {
|
||||
P->loopScope = NULL;
|
||||
//if(isFirstLoop) {
|
||||
//P->loopScope = NULL;
|
||||
|
||||
/*for(size_t i = 0; i < P->guardedVarCount; i++) {
|
||||
ASTExprVar *ev = P->guardedVars[i];
|
||||
@@ -1116,7 +1143,7 @@ static void nct_parse_statement(Parser *P) {
|
||||
P->guardedVarCount = 0;
|
||||
free(P->guardedVars);
|
||||
P->guardedVars = NULL;*/
|
||||
}
|
||||
//}
|
||||
|
||||
return;
|
||||
} else if(maybe(P, TOKEN_BREAK)) {
|
||||
@@ -1145,7 +1172,7 @@ static void nct_parse_statement(Parser *P) {
|
||||
ret->next = NULL;
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1209,21 +1236,6 @@ static void nct_parse_statement(Parser *P) {
|
||||
|
||||
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);
|
||||
return;
|
||||
} else if(!strcmp(peek(P, 0).content, "@instantiate")) {
|
||||
@@ -1489,9 +1501,12 @@ Type *nct_parse_record_definition(Parser *P) {
|
||||
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++;
|
||||
{
|
||||
intmax_t oldIdx = P->i;
|
||||
@@ -1579,7 +1594,7 @@ static void skim_chunk(Parser *P, int isTopLevel) {
|
||||
free(path);
|
||||
|
||||
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
|
||||
for(size_t i = 0; i < subp.scope->count; i++) {
|
||||
@@ -1623,10 +1638,10 @@ ASTChunk *nct_parse_chunk(Parser *P, int isTopLevel, int varPrioritize, Scope *t
|
||||
P->topLevel = &ret->chunk;
|
||||
}
|
||||
|
||||
skim_chunk(P, isTopLevel);
|
||||
|
||||
/* Arguments */
|
||||
if(ft && isTopLevel) {
|
||||
P->topLevel->functionType = ft;
|
||||
|
||||
ScopeItem **vtes = alloca(sizeof(*vtes) * ft->function.argCount);
|
||||
|
||||
// First arguments in a function TLC is the arguments
|
||||
@@ -1647,7 +1662,7 @@ ASTChunk *nct_parse_chunk(Parser *P, int isTopLevel, int varPrioritize, Scope *t
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1682,9 +1697,49 @@ ASTChunk *nct_parse_chunk(Parser *P, int isTopLevel, int varPrioritize, Scope *t
|
||||
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) {
|
||||
Scope *scope = scope_new(NULL);
|
||||
|
||||
Parser P;
|
||||
memset(&P, 0, sizeof(P));
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef NCTREF_PARSE_H
|
||||
#define NCTREF_PARSE_H
|
||||
|
||||
#include"ast.h"
|
||||
#include"ast/ast.h"
|
||||
|
||||
AST *nct_parse(Token*);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include<stdarg.h>
|
||||
#include<stdio.h>
|
||||
#include<stdlib.h>
|
||||
#include"ast.h"
|
||||
#include"ast/ast.h"
|
||||
#include"lexer.h"
|
||||
|
||||
static void stahp_va(int row, int column, const char *error, va_list l) {
|
||||
|
||||
31
src/scope.c
31
src/scope.c
@@ -5,31 +5,7 @@
|
||||
#include<string.h>
|
||||
#include<assert.h>
|
||||
#include<stdio.h>
|
||||
|
||||
struct ReachingDefs *reachingdefs_push(struct ReachingDefs *this) {
|
||||
struct ReachingDefs *ret = calloc(1, sizeof(*ret));
|
||||
ret->parent = this;
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct ReachingDefs *reachingdefs_coalesce(struct ReachingDefs *this) {
|
||||
struct ReachingDefs *parent = this->parent;
|
||||
if(parent) {
|
||||
parent->defs = realloc(parent->defs, sizeof(*parent->defs) * (parent->defCount + this->defCount));
|
||||
memcpy(&parent->defs[parent->defCount], this->defs, sizeof(*this->defs) * this->defCount);
|
||||
parent->defCount += this->defCount;
|
||||
}
|
||||
free(this->defs);
|
||||
free(this);
|
||||
return parent;
|
||||
}
|
||||
|
||||
void reachingdefs_set(struct ReachingDefs *this, union AST *def) {
|
||||
this->defCount = 1;
|
||||
this->defs = realloc(this->defs, sizeof(*this->defs) * this->defCount);
|
||||
this->defs[0] = def;
|
||||
this->excludeParent = 1;
|
||||
}
|
||||
#include"x86/arch.h"
|
||||
|
||||
Scope *scope_new(Scope *parent) {
|
||||
Scope *ret = calloc(1, sizeof(*ret));
|
||||
@@ -106,7 +82,12 @@ void vte_precolor(ScopeItem *vte, int class, int color) {
|
||||
assert(vte->kind == SCOPEITEM_VAR && "vte must be var");
|
||||
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.preclassed = true;
|
||||
vte->data.var.registerClass = class;
|
||||
vte->data.var.color = color;
|
||||
}
|
||||
|
||||
10
src/scope.h
10
src/scope.h
@@ -19,17 +19,10 @@ typedef struct UseDef {
|
||||
struct UseDef *next;
|
||||
} UseDef;
|
||||
|
||||
// Stack, necessary for "possible reaching defs" such as from if statements
|
||||
typedef struct ReachingDefs {
|
||||
size_t defCount;
|
||||
union AST **defs;
|
||||
|
||||
int excludeParent;
|
||||
struct ReachingDefs *parent;
|
||||
} ReachingDefs;
|
||||
struct ReachingDefs *reachingdefs_push(struct ReachingDefs*);
|
||||
struct ReachingDefs *reachingdefs_coalesce(struct ReachingDefs*);
|
||||
void reachingdefs_set(struct ReachingDefs*, union AST*);
|
||||
|
||||
struct Scope;
|
||||
typedef struct ScopeItem {
|
||||
@@ -66,9 +59,8 @@ typedef struct ScopeItem {
|
||||
int registerClass;
|
||||
|
||||
// Used by ast_usedef_pass
|
||||
//ReachingDefs *reachingDefs;
|
||||
union AST *declaration;
|
||||
ReachingDefs *reachingDefs;
|
||||
bool usedInLoop;
|
||||
|
||||
UseDef *usedefFirst;
|
||||
UseDef *usedefLast;
|
||||
|
||||
47
src/types.c
47
src/types.c
@@ -4,9 +4,10 @@
|
||||
#include<stdlib.h>
|
||||
#include<string.h>
|
||||
#include<stdint.h>
|
||||
#include"ast.h"
|
||||
#include"ast/ast.h"
|
||||
#include"reporting.h"
|
||||
#include<assert.h>
|
||||
#include"x86/arch.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));
|
||||
ret->type = TYPE_TYPE_PRIMITIVE;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -286,17 +297,19 @@ Type *type_parametrize(Type *t, Scope *scope) {
|
||||
|
||||
AST *n = vte->data.cexpr.concrete;
|
||||
|
||||
if(n) {
|
||||
if(n->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||
t->array.length = n->exprPrim.val;
|
||||
t->array.lengthIsGeneric = false;
|
||||
} else if(n->nodeKind == AST_EXPR_VAR && n->exprVar.thing->kind == SCOPEITEM_CEXPR) {
|
||||
t->array.lengthGenericParamIdx = n->exprVar.thing->data.cexpr.paramIdx;
|
||||
t->array.lengthGenericParamName = n->exprVar.thing->data.cexpr.paramName;
|
||||
t->array.lengthIsGeneric = true;
|
||||
} else {
|
||||
stahp_node(n, "Invalid parametrization expression.");
|
||||
}
|
||||
while(n->nodeKind == AST_EXPR_VAR && n->exprVar.thing->kind == SCOPEITEM_CEXPR && n->exprVar.thing->data.cexpr.concrete != NULL) {
|
||||
n = n->exprVar.thing->data.cexpr.concrete;
|
||||
}
|
||||
|
||||
if(n->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||
t->array.length = n->exprPrim.val;
|
||||
t->array.lengthIsGeneric = false;
|
||||
} else if(n->nodeKind == AST_EXPR_VAR && n->exprVar.thing->kind == SCOPEITEM_CEXPR) {
|
||||
t->array.lengthGenericParamIdx = n->exprVar.thing->data.cexpr.paramIdx;
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
32
src/types.h
32
src/types.h
@@ -5,6 +5,9 @@
|
||||
#include<stdint.h>
|
||||
#include<stdbool.h>
|
||||
#include<string.h>
|
||||
#include<stdio.h>
|
||||
#include<assert.h>
|
||||
#include<stdlib.h>
|
||||
|
||||
typedef enum {
|
||||
TYPE_TYPE_PRIMITIVE, TYPE_TYPE_RECORD, TYPE_TYPE_POINTER, TYPE_TYPE_FUNCTION, TYPE_TYPE_ARRAY, TYPE_TYPE_GENERIC, TYPE_TYPE_ERROR
|
||||
@@ -110,8 +113,37 @@ Type *type_shallow_copy(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) {
|
||||
return type->type == TYPE_TYPE_RECORD && !!strstr(type->record.name, " @far*");
|
||||
}
|
||||
|
||||
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];
|
||||
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);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -27,9 +27,15 @@ RegisterClass REG_CLASSES[] = {
|
||||
},
|
||||
[REG_CLASS_IA16_PTRS] = {
|
||||
.rMask = HWR_IND | HWR_BX,
|
||||
.rs = {HWR_DI, HWR_SI, HWR_BX, HWR_BP},
|
||||
.rsN = {"di", "si", "bx", "bp"},
|
||||
.rsS = {2, 2, 2, 2},
|
||||
.rs = {HWR_DI, HWR_SI, HWR_BX},
|
||||
.rsN = {"di", "si", "bx"},
|
||||
.rsS = {2, 2, 2},
|
||||
},
|
||||
[REG_CLASS_IA16_USEABLE]= {
|
||||
.rMask = HWR_IND | HWR_GPR,
|
||||
.rs = {HWR_AX, HWR_CX, HWR_DX, HWR_DI, HWR_SI, HWR_BX},
|
||||
.rsN = {"ax", "cx", "dx", "di", "si", "bx"},
|
||||
.rsS = {2, 2, 2, 2, 2, 2},
|
||||
},
|
||||
[REG_CLASS_DATASEGS] = {
|
||||
.rMask = HWR_SEGREGS,
|
||||
@@ -50,3 +56,7 @@ bool arch_verify_target() {
|
||||
int arch_ptr_size() {
|
||||
return x86_ia16() ? 2 : 4;
|
||||
}
|
||||
|
||||
void arch_init() {
|
||||
|
||||
}
|
||||
|
||||
157
src/x86/arch.h
157
src/x86/arch.h
@@ -5,7 +5,7 @@
|
||||
#include<assert.h>
|
||||
#include<stdbool.h>
|
||||
#include<stdlib.h>
|
||||
#include"ast.h"
|
||||
#include"ast/ast.h"
|
||||
|
||||
#define HWR_AL 1
|
||||
#define HWR_AH 2
|
||||
@@ -60,45 +60,10 @@ typedef struct RegisterClass {
|
||||
#define REG_CLASS_16_32 2
|
||||
#define REG_CLASS_IND 3
|
||||
#define REG_CLASS_IA16_PTRS 4
|
||||
#define REG_CLASS_DATASEGS 5
|
||||
#define REG_CLASS_IA16_USEABLE 5
|
||||
#define REG_CLASS_DATASEGS 6
|
||||
extern RegisterClass REG_CLASSES[];
|
||||
|
||||
// lol
|
||||
static inline bool is_reg_b(int cls, int res) {
|
||||
const char *name = REG_CLASSES[cls].rsN[res];
|
||||
return !strcmp(name, "bl") || !strcmp(name, "bh") || !!strstr(name, "bx");
|
||||
}
|
||||
static inline bool is_reg_di(int cls, int res) {
|
||||
const char *name = REG_CLASSES[cls].rsN[res];
|
||||
return !!strstr(name, "di");
|
||||
}
|
||||
static inline bool is_reg_si(int cls, int res) {
|
||||
const char *name = REG_CLASSES[cls].rsN[res];
|
||||
return !!strstr(name, "si");
|
||||
}
|
||||
static inline void reg_cast_up(int *cls, int *res) {
|
||||
const char *name = REG_CLASSES[*cls].rsN[*res];
|
||||
if(strstr(name, "a")) {
|
||||
*cls = REG_CLASS_16_32;
|
||||
*res = 1;
|
||||
} else if(strstr(name, "b")) {
|
||||
*cls = REG_CLASS_16_32;
|
||||
*res = 3;
|
||||
} else if(strstr(name, "c")) {
|
||||
*cls = REG_CLASS_16_32;
|
||||
*res = 5;
|
||||
} else if(strstr(name, "dl") || strstr(name, "dh") || strstr(name, "dx")) {
|
||||
*cls = REG_CLASS_16_32;
|
||||
*res = 7;
|
||||
} else if(strstr(name, "di")) {
|
||||
*cls = REG_CLASS_16_32;
|
||||
*res = 9;
|
||||
} else if(strstr(name, "si")) {
|
||||
*cls = REG_CLASS_16_32;
|
||||
*res = 11;
|
||||
}
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
I086 = 0,
|
||||
I186 = 1,
|
||||
@@ -151,6 +116,10 @@ static inline bool x86_imul_supported() {
|
||||
return x86_target() >= I186;
|
||||
}
|
||||
|
||||
static inline bool x86_is_marked_ptr(ScopeItem *si) {
|
||||
return si->data.var.preclassed && si->data.var.registerClass == REG_CLASS_IA16_PTRS;
|
||||
}
|
||||
|
||||
// Can expression be expressed as a single x86 operand?
|
||||
#define XOP_NOT_XOP 0
|
||||
#define XOP_NOT_MEM 1
|
||||
@@ -164,22 +133,23 @@ static inline int is_xop(AST *e) {
|
||||
return XOP_NOT_MEM;
|
||||
} else if(e->nodeKind == AST_EXPR_VAR) {
|
||||
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;
|
||||
} 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;
|
||||
} else if(e->nodeKind == AST_EXPR_UNARY_OP && e->exprUnOp.operator == UNOP_DEREF) {
|
||||
AST *c = e->exprUnOp.operand;
|
||||
} else if(e->nodeKind == AST_EXPR_BINARY_OP && e->exprBinOp.operator == BINOP_DEREF) {
|
||||
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) {
|
||||
c = c->exprCast.what;
|
||||
}
|
||||
|
||||
if(c->nodeKind == AST_EXPR_VAR && c->exprVar.thing->kind == SCOPEITEM_VAR) {
|
||||
if(x86_ia16()) { // In IA-16, pointers MUST be preclassed to REG_CLASS_IA16_PTRS
|
||||
if(!c->exprVar.thing->data.var.preclassed || c->exprVar.thing->data.var.registerClass != REG_CLASS_IA16_PTRS) {
|
||||
return XOP_NOT_XOP;
|
||||
}
|
||||
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)) {
|
||||
// In IA-16, pointers MUST be preclassed to REG_CLASS_IA16_PTRS
|
||||
return XOP_NOT_XOP;
|
||||
}
|
||||
|
||||
return XOP_MEM;
|
||||
@@ -190,8 +160,18 @@ static inline int is_xop(AST *e) {
|
||||
if(c->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||
return XOP_MEM;
|
||||
} else if(c->exprBinOp.operands[1]->nodeKind == AST_EXPR_VAR) {
|
||||
if(x86_ia16() && !x86_is_marked_ptr(c->exprBinOp.operands[1]->exprVar.thing)) {
|
||||
// In IA-16, pointers MUST be preclassed to REG_CLASS_IA16_PTRS
|
||||
return XOP_NOT_XOP;
|
||||
}
|
||||
|
||||
return XOP_MEM;
|
||||
} else if(c->exprBinOp.operands[1]->nodeKind == AST_EXPR_BINARY_OP && c->exprBinOp.operands[1]->exprBinOp.operator == BINOP_MUL && c->exprBinOp.operands[1]->exprBinOp.operands[0]->nodeKind == AST_EXPR_PRIMITIVE && c->exprBinOp.operands[1]->exprBinOp.operands[1]->nodeKind == AST_EXPR_VAR) {
|
||||
if(x86_ia16() && !x86_is_marked_ptr(c->exprBinOp.operands[1]->exprBinOp.operands[1]->exprVar.thing)) {
|
||||
// In IA-16, pointers MUST be preclassed to REG_CLASS_IA16_PTRS
|
||||
return XOP_NOT_XOP;
|
||||
}
|
||||
|
||||
int scale = c->exprBinOp.operands[1]->exprBinOp.operands[0]->exprPrim.val;
|
||||
|
||||
if(scale == 1 || scale == 2 || scale == 4 || scale == 8) {
|
||||
@@ -263,9 +243,23 @@ static inline WhysItBad_Huh x86_test_mul(AST *stmtPrev, AST *stmt) {
|
||||
}
|
||||
|
||||
if(x86_imul_supported()) {
|
||||
return GUCCI;
|
||||
} else if(stmt->stmtAssign.to->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||
return SRC1_IS_BAD_XOP;
|
||||
if(ast_expression_equal(stmt->stmtAssign.what, stmt->stmtAssign.to->exprBinOp.operands[0])) {
|
||||
if(is_xop(stmt->stmtAssign.to->exprBinOp.operands[1]) == XOP_NOT_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) {
|
||||
@@ -303,3 +297,68 @@ static inline void arch_add_hidden_variables(Scope *scope) {
|
||||
scope_set(scope, "@seg_ds", si);
|
||||
}*/
|
||||
}
|
||||
|
||||
static inline bool x86_is_lea(AST *s) {
|
||||
return !x86_ia16() && s->nodeKind == AST_STMT_ASSIGN && s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && s->stmtAssign.to->exprBinOp.operator == BINOP_ADD && is_xop(s->stmtAssign.what) == XOP_NOT_MEM && is_xop(s->stmtAssign.to->exprBinOp.operands[0]) == XOP_NOT_MEM && is_xop(s->stmtAssign.to->exprBinOp.operands[1]) == XOP_NOT_MEM;
|
||||
}
|
||||
|
||||
static inline int arch_sp_size() {
|
||||
return x86_max_gpr_size();
|
||||
}
|
||||
|
||||
static inline int arch_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
|
||||
static inline bool is_reg_b(int cls, int res) {
|
||||
const char *name = REG_CLASSES[cls].rsN[res];
|
||||
return !strcmp(name, "bl") || !strcmp(name, "bh") || !!strstr(name, "bx");
|
||||
}
|
||||
static inline bool is_reg_di(int cls, int res) {
|
||||
const char *name = REG_CLASSES[cls].rsN[res];
|
||||
return !!strstr(name, "di");
|
||||
}
|
||||
static inline bool is_reg_si(int cls, int res) {
|
||||
const char *name = REG_CLASSES[cls].rsN[res];
|
||||
return !!strstr(name, "si");
|
||||
}
|
||||
static inline void reg_cast_to_gpr(int *cls, int *res) {
|
||||
const char *name = REG_CLASSES[*cls].rsN[*res];
|
||||
if(strstr(name, "a")) {
|
||||
*cls = REG_CLASS_16_32;
|
||||
*res = 1;
|
||||
} else if(strstr(name, "b")) {
|
||||
*cls = REG_CLASS_16_32;
|
||||
*res = 3;
|
||||
} else if(strstr(name, "c")) {
|
||||
*cls = REG_CLASS_16_32;
|
||||
*res = 5;
|
||||
} else if(strstr(name, "dl") || strstr(name, "dh") || strstr(name, "dx")) {
|
||||
*cls = REG_CLASS_16_32;
|
||||
*res = 7;
|
||||
} else if(strstr(name, "di")) {
|
||||
*cls = REG_CLASS_16_32;
|
||||
*res = 9;
|
||||
} else if(strstr(name, "si")) {
|
||||
*cls = REG_CLASS_16_32;
|
||||
*res = 11;
|
||||
}
|
||||
if(x86_ia16()) {
|
||||
(*res)--;
|
||||
}
|
||||
}
|
||||
|
||||
void arch_init();
|
||||
|
||||
503
src/x86/cg.c
503
src/x86/cg.c
@@ -4,23 +4,30 @@
|
||||
#include<assert.h>
|
||||
#include"ntc.h"
|
||||
#include"reporting.h"
|
||||
#include"ast.h"
|
||||
#include"ast/ast.h"
|
||||
#include"arch.h"
|
||||
#include"utils.h"
|
||||
|
||||
static const char *BINOP_SIMPLE_INSTRS[] = {[BINOP_ADD] = "add", [BINOP_SUB] = "sub", [BINOP_BITWISE_AND] = "and", [BINOP_BITWISE_OR] = "or", [BINOP_BITWISE_XOR] = "xor"};
|
||||
|
||||
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 {
|
||||
#define LOOPSTACKSIZE 96
|
||||
/*#define LOOPSTACKSIZE 96
|
||||
size_t loopStackStart[LOOPSTACKSIZE];
|
||||
size_t loopStackEnd[LOOPSTACKSIZE];
|
||||
size_t loopStackIdx;
|
||||
size_t loopStackIdx;*/
|
||||
|
||||
int isFunction;
|
||||
|
||||
AST *tlc;
|
||||
|
||||
struct CalleeSavedState calleeSaved;
|
||||
} CGState;
|
||||
|
||||
static const char *direct(int size) {
|
||||
@@ -78,8 +85,8 @@ static const char *xv_sz(ScopeItem *v, int sz) {
|
||||
int cls = v->data.var.registerClass, reg = v->data.var.color;
|
||||
|
||||
if(type_size(v->type) != sz) {
|
||||
if(sz == 4) {
|
||||
reg_cast_up(&cls, ®);
|
||||
if(sz == x86_max_gpr_size()) {
|
||||
reg_cast_to_gpr(&cls, ®);
|
||||
} else abort();
|
||||
}
|
||||
|
||||
@@ -108,11 +115,11 @@ static const char *xj(BinaryOp op) {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
e = e->exprCast.what;
|
||||
@@ -129,29 +136,48 @@ static AST *is_field_access(AST *e) {
|
||||
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) {
|
||||
#define XOPBUFS 16
|
||||
#define XOPBUFSZ 24
|
||||
#define XOPBUFSZ 64
|
||||
static char bufs[XOPBUFS][XOPBUFSZ];
|
||||
static int bufidx = 0;
|
||||
|
||||
char *ret = bufs[bufidx];
|
||||
|
||||
if(e->nodeKind == AST_EXPR_UNARY_OP && e->exprUnOp.operator == UNOP_DEREF) {
|
||||
AST *p = e->exprUnOp.operand;
|
||||
int pr = 0;
|
||||
|
||||
if(e->nodeKind == AST_EXPR_BINARY_OP && e->exprBinOp.operator == BINOP_DEREF) {
|
||||
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) {
|
||||
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) {
|
||||
snprintf(ret, XOPBUFSZ, "%s [%s + %s]",
|
||||
pr = snprintf(ret, XOPBUFSZ, "%s %s:[%s + %s]",
|
||||
spec(sz),
|
||||
segment_or_empty(seg),
|
||||
xv_sz(p->exprBinOp.operands[0]->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) {
|
||||
snprintf(ret, XOPBUFSZ, "%s [%s + %s]",
|
||||
pr = snprintf(ret, XOPBUFSZ, "%s %s:[%s + %s]",
|
||||
spec(sz),
|
||||
segment_or_empty(seg),
|
||||
p->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->data.symbol.name,
|
||||
xv_sz(p->exprBinOp.operands[1]->exprVar.thing, 0));
|
||||
} else if(is_field_access(e)) {
|
||||
@@ -160,8 +186,9 @@ static const char *xop_sz(AST *tlc, AST *e, int sz) {
|
||||
if(e->exprBinOp.operands[0]->nodeKind == AST_EXPR_UNARY_OP) {
|
||||
assert(e->exprBinOp.operands[0]->exprUnOp.operator == UNOP_REF);
|
||||
|
||||
snprintf(ret, XOPBUFSZ, "%s [%s + %i]",
|
||||
pr = snprintf(ret, XOPBUFSZ, "%s %s:[%s + %i]",
|
||||
spec(sz),
|
||||
segment_or_empty(seg),
|
||||
e->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->data.symbol.name,
|
||||
e->exprBinOp.operands[1]->exprPrim.val);
|
||||
} else {
|
||||
@@ -169,43 +196,57 @@ static const char *xop_sz(AST *tlc, AST *e, int sz) {
|
||||
|
||||
ScopeItem *vte = e->exprBinOp.operands[0]->exprVar.thing;
|
||||
|
||||
snprintf(ret, XOPBUFSZ, "%s [%s + %i]",
|
||||
pr = snprintf(ret, XOPBUFSZ, "%s %s:[%s + %i]",
|
||||
spec(sz),
|
||||
segment_or_empty(seg),
|
||||
REG_CLASSES[vte->data.var.registerClass].rsN[vte->data.var.color],
|
||||
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) {
|
||||
snprintf(ret, XOPBUFSZ, "%s [%s + %i * %s]",
|
||||
pr = snprintf(ret, XOPBUFSZ, "%s %s:[%s + %i * %s]",
|
||||
spec(sz),
|
||||
segment_or_empty(seg),
|
||||
p->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->data.symbol.name,
|
||||
p->exprBinOp.operands[1]->exprBinOp.operands[0]->exprPrim.val,
|
||||
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) {
|
||||
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) {
|
||||
snprintf(ret, XOPBUFSZ, "[esp + %i]", p->exprBinOp.operands[1]->exprPrim.val);
|
||||
if(x86_ia16()) {
|
||||
pr = snprintf(ret, XOPBUFSZ, "[bp + %li]", p->exprBinOp.operands[1]->exprPrim.val - tlc->chunk.stackReservation);
|
||||
} else {
|
||||
pr = snprintf(ret, XOPBUFSZ, "[esp + %i]", p->exprBinOp.operands[1]->exprPrim.val);
|
||||
}
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
} else if(e->nodeKind == AST_EXPR_STACK_POINTER) {
|
||||
snprintf(ret, XOPBUFSZ, "esp");
|
||||
pr = snprintf(ret, XOPBUFSZ, x86_ia16() ? "sp" : "esp");
|
||||
} else if(e->nodeKind == AST_EXPR_VAR) {
|
||||
ScopeItem *v = e->exprVar.thing;
|
||||
|
||||
if(v->kind == SCOPEITEM_VAR) {
|
||||
return xv_sz(v, sz);
|
||||
} else if(v->kind == SCOPEITEM_SYMBOL) {
|
||||
snprintf(ret, XOPBUFSZ, "%s [%s]", spec(sz), v->data.symbol.name);
|
||||
pr = snprintf(ret, XOPBUFSZ, "%s [%s]", spec(sz), v->data.symbol.name);
|
||||
} else abort();
|
||||
} else if(e->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||
snprintf(ret, XOPBUFSZ, "%s %i", spec(type_size(e->exprPrim.type)), e->exprPrim.val);
|
||||
pr = snprintf(ret, XOPBUFSZ, "%s %i", spec(type_size(e->exprPrim.type)), e->exprPrim.val);
|
||||
} 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) {
|
||||
snprintf(ret, XOPBUFSZ, "%s", e->exprUnOp.operand->exprVar.thing->data.symbol.name);
|
||||
pr = snprintf(ret, XOPBUFSZ, "%s %s", spec(type_size(e->exprUnOp.type)), e->exprUnOp.operand->exprVar.thing->data.symbol.name);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(pr < XOPBUFSZ && "XOPBUF OVERFLOW");
|
||||
|
||||
bufidx = (bufidx + 1) % XOPBUFS;
|
||||
|
||||
return ret;
|
||||
@@ -215,6 +256,19 @@ static const char *xop(AST *tlc, AST *e) {
|
||||
return xop_sz(tlc, e, type_size(e->expression.type));
|
||||
}
|
||||
|
||||
static const char *xop_strip_size(const char *str) {
|
||||
if(!strncmp(str, "byte ", 5)) {
|
||||
return str + 5;
|
||||
} else if(!strncmp(str, "word ", 5)) {
|
||||
return str + 5;
|
||||
} else if(!strncmp(str, "dword ", 6)) {
|
||||
return str + 6;
|
||||
} else if(!strncmp(str, "qword ", 6)) {
|
||||
return str + 6;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
static int lr_empty(ScopeItem *a) {
|
||||
assert(a->kind == SCOPEITEM_VAR);
|
||||
//assert(!a->data.var.liveRangeStart == !a->data.var.liveRangeEnd);
|
||||
@@ -222,23 +276,66 @@ static int lr_empty(ScopeItem *a) {
|
||||
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) {
|
||||
AST *s = a->chunk.statementFirst;
|
||||
|
||||
if(a->chunk.stackReservation) {
|
||||
printf("sub esp, %lu\n", a->chunk.stackReservation);
|
||||
if(x86_ia16()) {
|
||||
printf("push bp\n");
|
||||
printf("mov bp, sp\n");
|
||||
printf("sub sp, %lu\n", a->chunk.stackReservation);
|
||||
} else {
|
||||
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
|
||||
while(s) {
|
||||
if(s->nodeKind == AST_STMT_EXT_SECTION) {
|
||||
|
||||
Token t = s->stmtExtSection.name;
|
||||
printf("section %.*s\n", (int) t.length, t.content);
|
||||
if(s->nodeKind == AST_STMT_LABEL) {
|
||||
|
||||
} else if(s->nodeKind == AST_STMT_EXT_ORG) {
|
||||
|
||||
printf("org %lu\n", s->stmtExtOrg.val);
|
||||
printf(".%s:\n", s->stmtLabel.name);
|
||||
|
||||
} else if(s->nodeKind == AST_STMT_JUMP) {
|
||||
|
||||
if(s->stmtJump.condition) {
|
||||
assert(s->stmtJump.condition->nodeKind == AST_EXPR_BINARY_OP && binop_is_comparison(s->stmtJump.condition->exprBinOp.operator));
|
||||
printf("cmp %s, %s\n", xop(cg->tlc, s->stmtJump.condition->exprBinOp.operands[0]), xop(cg->tlc, s->stmtJump.condition->exprBinOp.operands[1]));
|
||||
printf("j%s .%s\n", xj(s->stmtJump.condition->exprBinOp.operator), s->stmtJump.label);
|
||||
} else {
|
||||
printf("jmp .%s\n", s->stmtJump.label);
|
||||
}
|
||||
|
||||
} else if(s->nodeKind == AST_STMT_EXT_ALIGN) {
|
||||
|
||||
@@ -293,13 +390,15 @@ void cg_chunk(CGState *cg, AST *a) {
|
||||
|
||||
ast_sroa(s->stmtDecl.expression->exprFunc.chunk);
|
||||
|
||||
dumben_pre(s->stmtDecl.expression->exprFunc.chunk);
|
||||
ast_linearize(s->stmtDecl.expression->exprFunc.chunk);
|
||||
|
||||
dumben_go(s->stmtDecl.expression->exprFunc.chunk);
|
||||
while(!cg_go(s->stmtDecl.expression->exprFunc.chunk)) {
|
||||
dumben_go(s->stmtDecl.expression->exprFunc.chunk);
|
||||
arch_normalize(s->stmtDecl.expression->exprFunc.chunk);
|
||||
while(!cg_attempt_sections(s->stmtDecl.expression->exprFunc.chunk)) {
|
||||
arch_normalize(s->stmtDecl.expression->exprFunc.chunk);
|
||||
}
|
||||
|
||||
cg_go_sections(s->stmtDecl.expression->exprFunc.chunk);
|
||||
|
||||
}
|
||||
|
||||
} else abort();
|
||||
@@ -319,27 +418,49 @@ void cg_chunk(CGState *cg, AST *a) {
|
||||
|
||||
AST *e = s->stmtAssign.to;
|
||||
|
||||
puts("push ecx");
|
||||
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_ECX)) {
|
||||
puts(x86_ia16() ? "push cx" : "push ecx");
|
||||
}
|
||||
if(is_resource_in_use_right_now_at_this_current_point_in_time_and_also_not_created_right_now(a, s, HWR_EDX)) {
|
||||
puts(x86_ia16() ? "push dx" : "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->function.argCount;
|
||||
int argCount = e->exprCall.what->expression.type->pointer.of->function.argCount;
|
||||
|
||||
size_t argSize = 0;
|
||||
|
||||
for(int i = argCount - 1; i >= 0; i--) {
|
||||
printf("push %s\n", xop_sz(cg->tlc, e->exprCall.args[i], 4));
|
||||
printf("push %s\n", xop_sz(cg->tlc, e->exprCall.args[i], arch_gpr_size()));
|
||||
|
||||
argSize += (type_size(e->exprCall.args[i]->expression.type) + 3) & ~3;
|
||||
}
|
||||
|
||||
assert(e->exprCall.what->nodeKind == AST_EXPR_VAR && e->exprCall.what->exprVar.thing->kind == SCOPEITEM_SYMBOL);
|
||||
if(e->exprCall.what->nodeKind == AST_EXPR_VAR && e->exprCall.what->exprVar.thing->kind == SCOPEITEM_SYMBOL) {
|
||||
printf("call %s\n", e->exprCall.what->exprVar.thing->data.symbol.name);
|
||||
} else {
|
||||
printf("call %s\n", xop(cg->tlc, e->exprCall.what));
|
||||
}
|
||||
|
||||
printf("call %s\n", e->exprCall.what->exprVar.thing->data.symbol.name);
|
||||
if(argSize) {
|
||||
if(x86_ia16()) {
|
||||
printf("add sp, %lu\n", argSize);
|
||||
} else {
|
||||
printf("add esp, %lu\n", argSize);
|
||||
}
|
||||
}
|
||||
|
||||
if(argSize) printf("add esp, %lu\n", argSize);
|
||||
|
||||
puts("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_EAX)) {
|
||||
puts(x86_ia16() ? "pop ax" : "pop eax");
|
||||
}
|
||||
if(is_resource_in_use_right_now_at_this_current_point_in_time_and_also_not_created_right_now(a, s, HWR_EDX)) {
|
||||
puts(x86_ia16() ? "pop dx" : "pop edx");
|
||||
}
|
||||
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) {
|
||||
|
||||
@@ -351,7 +472,7 @@ void cg_chunk(CGState *cg, AST *a) {
|
||||
assert(s->stmtAssign.what->exprVar.thing->kind == SCOPEITEM_VAR);
|
||||
|
||||
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 {
|
||||
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]));
|
||||
}
|
||||
@@ -387,12 +508,12 @@ void cg_chunk(CGState *cg, AST *a) {
|
||||
|
||||
printf("%s %s, %s\n", BINOP_SIMPLE_INSTRS[s->stmtAssign.to->exprBinOp.operator], xop(cg->tlc, s->stmtAssign.what), xop(cg->tlc, s->stmtAssign.to->exprBinOp.operands[1]));
|
||||
|
||||
} else if(s->stmtAssign.what->nodeKind == AST_EXPR_VAR && s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && s->stmtAssign.to->exprBinOp.operator == BINOP_ADD && s->stmtAssign.to->exprBinOp.operands[0]->nodeKind == AST_EXPR_VAR && s->stmtAssign.to->exprBinOp.operands[1]->nodeKind == AST_EXPR_VAR && s->stmtAssign.to->exprBinOp.operands[0]->exprVar.thing->kind == SCOPEITEM_VAR && s->stmtAssign.to->exprBinOp.operands[1]->exprVar.thing->kind == SCOPEITEM_VAR) {
|
||||
} else if(x86_is_lea(s)) {
|
||||
|
||||
printf("lea %s, [%s + %s]\n",
|
||||
xv(s->stmtAssign.what->exprVar.thing),
|
||||
xv(s->stmtAssign.to->exprBinOp.operands[0]->exprVar.thing),
|
||||
xv(s->stmtAssign.to->exprBinOp.operands[1]->exprVar.thing));
|
||||
xop(cg->tlc, s->stmtAssign.what),
|
||||
xop_strip_size(xop(cg->tlc, s->stmtAssign.to->exprBinOp.operands[0])),
|
||||
xop_strip_size(xop(cg->tlc, s->stmtAssign.to->exprBinOp.operands[1])));
|
||||
|
||||
} else if(s->stmtAssign.what->nodeKind == AST_EXPR_VAR && s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && s->stmtAssign.to->exprBinOp.operator == BINOP_ADD && s->stmtAssign.to->exprBinOp.operands[0]->nodeKind == AST_EXPR_UNARY_OP && s->stmtAssign.to->exprBinOp.operands[0]->exprUnOp.operator == UNOP_REF && s->stmtAssign.to->exprBinOp.operands[0]->exprUnOp.operand->nodeKind == AST_EXPR_VAR && s->stmtAssign.to->exprBinOp.operands[1]->nodeKind == AST_EXPR_VAR && s->stmtAssign.to->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->kind == SCOPEITEM_SYMBOL && s->stmtAssign.to->exprBinOp.operands[1]->exprVar.thing->kind == SCOPEITEM_VAR) {
|
||||
|
||||
@@ -414,14 +535,22 @@ void cg_chunk(CGState *cg, AST *a) {
|
||||
|
||||
} 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));
|
||||
|
||||
} 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 {
|
||||
|
||||
const char *dest = xop_sz(cg->tlc, s->stmtAssign.what, 4);
|
||||
const char *src = xop_sz(cg->tlc, s->stmtAssign.to->exprCast.what, 4);
|
||||
const char *dest = xop_sz(cg->tlc, s->stmtAssign.what, x86_max_gpr_size());
|
||||
const char *src = xop_sz(cg->tlc, s->stmtAssign.to->exprCast.what, x86_max_gpr_size());
|
||||
|
||||
if(strcmp(dest, src)) {
|
||||
printf("mov %s, %s\n", dest, src);
|
||||
@@ -436,7 +565,7 @@ void cg_chunk(CGState *cg, AST *a) {
|
||||
}
|
||||
}
|
||||
|
||||
} else if(s->nodeKind == AST_STMT_LOOP) {
|
||||
} /*else if(s->nodeKind == AST_STMT_LOOP) {
|
||||
|
||||
size_t lbl0 = nextLocalLabel++;
|
||||
size_t lbl1 = nextLocalLabel++;
|
||||
@@ -476,31 +605,43 @@ void cg_chunk(CGState *cg, AST *a) {
|
||||
|
||||
printf(".L%lu:\n", lbl);
|
||||
|
||||
} else if(s->nodeKind == AST_STMT_RETURN) {
|
||||
}*/ else if(s->nodeKind == AST_STMT_RETURN) {
|
||||
|
||||
if(s->stmtReturn.val) {
|
||||
assert(s->stmtReturn.val->nodeKind == AST_EXPR_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) {
|
||||
printf("add 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 %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()) {
|
||||
printf("add sp, %lu\n", a->chunk.stackReservation);
|
||||
} else {
|
||||
printf("add esp, %lu\n", a->chunk.stackReservation);
|
||||
}
|
||||
}
|
||||
|
||||
if(x86_ia16()) {
|
||||
if(x86_target() >= I186) {
|
||||
printf("leave\n");
|
||||
} else {
|
||||
printf("mov sp, bp\n");
|
||||
printf("pop bp\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 {
|
||||
|
||||
stahp_node(s, "Unknown statement caught by code generator.");
|
||||
stahp_node(s, "Unknown statement %s caught by code generator.", AST_KIND_STR[s->nodeKind]);
|
||||
|
||||
}
|
||||
|
||||
@@ -525,168 +666,48 @@ static bool var_collision(AST *tlc, ScopeItem *v1, ScopeItem *v2) {
|
||||
return liveRangeIntersection && resourceIntersection;
|
||||
}
|
||||
|
||||
struct CalleeSavedState {
|
||||
AST *targetTLC;
|
||||
|
||||
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];
|
||||
|
||||
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];
|
||||
|
||||
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;
|
||||
static void callee_saved(AST *tlc, struct CalleeSavedState *state) {
|
||||
bool ebxused = false, ediused = false, esiused = false, ebpused = false;
|
||||
|
||||
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)) {
|
||||
ebxuser = tlc->chunk.vars[v];
|
||||
size_t resource = REG_CLASSES[tlc->chunk.vars[v]->data.var.registerClass].rs[tlc->chunk.vars[v]->data.var.color];
|
||||
|
||||
if(resource & HWR_EBX) {
|
||||
ebxused = true;
|
||||
}
|
||||
if(is_reg_di(tlc->chunk.vars[v]->data.var.registerClass, tlc->chunk.vars[v]->data.var.color)) {
|
||||
ediuser = tlc->chunk.vars[v];
|
||||
if(resource & HWR_EDI) {
|
||||
ediused = true;
|
||||
}
|
||||
if(is_reg_si(tlc->chunk.vars[v]->data.var.registerClass, tlc->chunk.vars[v]->data.var.color)) {
|
||||
esiuser = tlc->chunk.vars[v];
|
||||
if(resource & HWR_ESI) {
|
||||
esiused = true;
|
||||
}
|
||||
if(resource & HWR_EBP) {
|
||||
ebpused = true;
|
||||
}
|
||||
}
|
||||
|
||||
struct CalleeSavedState state = {};
|
||||
state.targetTLC = tlc;
|
||||
|
||||
size_t nextUser = 0;
|
||||
if(ebxuser) {
|
||||
state.calleeOffsets[nextUser] = nextUser * 4;
|
||||
state.calleeUsers[nextUser] = ebxuser;
|
||||
if(ebxused) {
|
||||
state->stackOffset[nextUser] = nextUser * x86_max_gpr_size();
|
||||
state->reg[nextUser] = x86_ia16() ? "bx" : "ebx";
|
||||
nextUser++;
|
||||
}
|
||||
if(esiuser) {
|
||||
state.calleeOffsets[nextUser] = nextUser * 4;
|
||||
state.calleeUsers[nextUser] = esiuser;
|
||||
if(esiused) {
|
||||
state->stackOffset[nextUser] = nextUser * x86_max_gpr_size();
|
||||
state->reg[nextUser] = x86_ia16() ? "si" : "esi";
|
||||
nextUser++;
|
||||
}
|
||||
if(ediuser) {
|
||||
state.calleeOffsets[nextUser] = nextUser * 4;
|
||||
state.calleeUsers[nextUser] = ediuser;
|
||||
if(ediused) {
|
||||
state->stackOffset[nextUser] = nextUser * x86_max_gpr_size();
|
||||
state->reg[nextUser] = x86_ia16() ? "di" : "edi";
|
||||
nextUser++;
|
||||
}
|
||||
ast_grow_stack_frame(tlc, nextUser * 4);
|
||||
|
||||
if(nextUser) {
|
||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, &state, callee_saved_visitor, NULL);
|
||||
if(ebpused) {
|
||||
state->stackOffset[nextUser] = nextUser * x86_max_gpr_size();
|
||||
state->reg[nextUser] = x86_ia16() ? "bp" : "ebp";
|
||||
nextUser++;
|
||||
}
|
||||
ast_grow_stack_frame(tlc, nextUser * x86_max_gpr_size());
|
||||
}
|
||||
|
||||
static void determine_register_classes(AST *tlc) {
|
||||
@@ -698,7 +719,7 @@ static void determine_register_classes(AST *tlc) {
|
||||
if(type_size(tlc->chunk.vars[v]->type) == 1) {
|
||||
tlc->chunk.vars[v]->data.var.registerClass = REG_CLASS_8;
|
||||
} else {
|
||||
tlc->chunk.vars[v]->data.var.registerClass = REG_CLASS_NOT_8;
|
||||
tlc->chunk.vars[v]->data.var.registerClass = x86_ia16() ? REG_CLASS_IA16_USEABLE : REG_CLASS_NOT_8;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -770,7 +791,7 @@ nextColor:;
|
||||
return mustSpillRegisterClass;
|
||||
}
|
||||
|
||||
int cg_go(AST *a) {
|
||||
int cg_tlc(AST *a) {
|
||||
assert(a->nodeKind == AST_CHUNK);
|
||||
|
||||
for(size_t e = 0; e < a->chunk.externCount; e++) {
|
||||
@@ -780,6 +801,23 @@ int cg_go(AST *a) {
|
||||
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);
|
||||
|
||||
size_t adjCount = 0;
|
||||
@@ -869,17 +907,36 @@ cont:;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(a->chunk.functionType) {
|
||||
callee_saved(a);
|
||||
return 1;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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,37 +6,20 @@
|
||||
#include"reporting.h"
|
||||
#include"utils.h"
|
||||
|
||||
// This is the dumbing down pass.
|
||||
//
|
||||
// Complex expressions are to be "broken down" into simpler ones until the AST
|
||||
// can be trivially translated to the target architecture.
|
||||
//
|
||||
// This file along with CG is strictly for IA-32 and will fail for other
|
||||
// architectures.
|
||||
// This is the normalization pass:
|
||||
// Complex expressions are to be "broken down" into simpler ones until
|
||||
// the AST 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.
|
||||
|
||||
static ScopeItem *create_dumbtemp(AST *tlc, Type *itstype) {
|
||||
static ScopeItem *create_temp(AST *tlc, Type *itstype) {
|
||||
static size_t vidx = 0;
|
||||
|
||||
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.reachingDefs = NULL;
|
||||
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;
|
||||
return ast_tlc_new_var(tlc, malp("nrm%lu", vidx++), itstype);
|
||||
}
|
||||
|
||||
/* Split away complex expression into a new local variable */
|
||||
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
|
||||
|
||||
@@ -52,6 +35,7 @@ static AST *varify(AST *tlc, AST *chunk, AST *stmtPrev, AST *stmt, AST *e) {
|
||||
assign->nodeKind = AST_STMT_ASSIGN;
|
||||
assign->what = (AST*) ev[0];
|
||||
assign->to = e;
|
||||
vte->data.var.declaration = (AST*) assign;
|
||||
|
||||
if(stmtPrev) {
|
||||
stmtPrev->statement.next = (AST*) assign;
|
||||
@@ -72,6 +56,8 @@ static AST *xopify(AST *tlc, AST *chunk, AST *stmtPrev, AST *stmt, AST *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) {
|
||||
assert(a->nodeKind == AST_EXPR_VAR);
|
||||
assert(a->exprVar.thing->kind == SCOPEITEM_VAR);
|
||||
@@ -84,43 +70,164 @@ 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;
|
||||
int effective;
|
||||
};
|
||||
static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *tlc, void *ud) {
|
||||
struct DumbenState *this = ud;
|
||||
|
||||
if(this->targetTLC != tlc) return;
|
||||
static void normalize_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *tlc, void *ud) {
|
||||
struct NormState *this = ud;
|
||||
|
||||
AST *s = *nptr;
|
||||
|
||||
if(s->nodeKind == AST_STMT_IF) {
|
||||
if(s->nodeKind == AST_STMT_JUMP && s->stmtJump.condition) {
|
||||
|
||||
AST *e = s->stmtIf.expression;
|
||||
AST *e = s->stmtJump.condition;
|
||||
|
||||
if(e->nodeKind == AST_EXPR_BINARY_OP && binop_is_comparison(e->exprBinOp.operator)) {
|
||||
|
||||
if(is_xop(e->exprBinOp.operands[0]) == XOP_NOT_XOP) {
|
||||
e->exprBinOp.operands[0] = xopify(tlc, chu, stmtPrev, s, e->exprBinOp.operands[0]);
|
||||
this->effective = 1;
|
||||
}
|
||||
|
||||
if(is_xop(e->exprBinOp.operands[1]) == XOP_NOT_XOP) {
|
||||
} else if(is_xop(e->exprBinOp.operands[1]) == XOP_NOT_XOP) {
|
||||
e->exprBinOp.operands[1] = xopify(tlc, chu, stmtPrev, s, e->exprBinOp.operands[1]);
|
||||
this->effective = 1;
|
||||
}
|
||||
|
||||
if(is_xop(e->exprBinOp.operands[0]) == XOP_MEM && is_xop(e->exprBinOp.operands[1]) == XOP_MEM) {
|
||||
} else 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
|
||||
|
||||
e->exprBinOp.operands[1] = varify(tlc, chu, stmtPrev, s, e->exprBinOp.operands[1]);
|
||||
this->effective = 1;
|
||||
}
|
||||
|
||||
} else if(e->nodeKind == AST_EXPR_UNARY_OP && e->exprUnOp.operator == UNOP_NOT && e->exprUnOp.operand->nodeKind == AST_EXPR_BINARY_OP && (e->exprUnOp.operand->exprBinOp.operator == BINOP_LOGICAL_AND || e->exprUnOp.operand->exprBinOp.operator == BINOP_LOGICAL_OR)) {
|
||||
|
||||
AST *binop = e->exprUnOp.operand;
|
||||
|
||||
e->exprUnOp.operand = binop->exprBinOp.operands[0];
|
||||
binop->exprBinOp.operands[0] = e;
|
||||
|
||||
AST *unop2 = calloc(1, sizeof(ASTExprUnaryOp));
|
||||
unop2->nodeKind = AST_EXPR_UNARY_OP;
|
||||
unop2->expression.type = binop->exprBinOp.operands[1]->expression.type;
|
||||
unop2->exprUnOp.operator = UNOP_NOT;
|
||||
unop2->exprUnOp.operand = binop->exprBinOp.operands[1];
|
||||
binop->exprBinOp.operands[1] = unop2;
|
||||
|
||||
binop->exprBinOp.operator = binop->exprBinOp.operator == BINOP_LOGICAL_AND ? BINOP_LOGICAL_OR : BINOP_LOGICAL_AND;
|
||||
|
||||
s->stmtJump.condition = binop;
|
||||
|
||||
ast_denoop(tlc, &s->stmtJump.condition);
|
||||
|
||||
this->effective = 1;
|
||||
|
||||
} else if(e->nodeKind == AST_EXPR_BINARY_OP && e->exprBinOp.operator == BINOP_LOGICAL_OR) {
|
||||
|
||||
AST *cond0 = e->exprBinOp.operands[0];
|
||||
AST *cond1 = e->exprBinOp.operands[1];
|
||||
|
||||
s->stmtJump.condition = cond0;
|
||||
|
||||
AST *jump2 = calloc(1, sizeof(ASTStmtJump));
|
||||
jump2->nodeKind = AST_STMT_JUMP;
|
||||
jump2->stmtJump.condition = cond1;
|
||||
jump2->stmtJump.label = strdup(s->stmtJump.label);
|
||||
jump2->statement.next = s->statement.next;
|
||||
s->statement.next = jump2;
|
||||
|
||||
this->effective = 1;
|
||||
|
||||
} else if(e->nodeKind == AST_EXPR_BINARY_OP && e->exprBinOp.operator == BINOP_LOGICAL_AND) {
|
||||
|
||||
static size_t idx = 0;
|
||||
|
||||
AST *cond0 = e->exprBinOp.operands[0];
|
||||
AST *cond1 = e->exprBinOp.operands[1];
|
||||
|
||||
AST *lbl = calloc(1, sizeof(ASTStmtLabel));
|
||||
lbl->nodeKind = AST_STMT_LABEL;
|
||||
lbl->stmtLabel.name = malp("$dla%lu", idx++);
|
||||
lbl->statement.next = s->statement.next;
|
||||
|
||||
AST *unop = calloc(1, sizeof(ASTExprUnaryOp));
|
||||
unop->nodeKind = AST_EXPR_UNARY_OP;
|
||||
unop->expression.type = cond0->expression.type;
|
||||
unop->exprUnOp.operator = UNOP_NOT;
|
||||
unop->exprUnOp.operand = cond0;
|
||||
s->stmtJump.condition = unop;
|
||||
|
||||
AST *jump2 = calloc(1, sizeof(ASTStmtJump));
|
||||
jump2->nodeKind = AST_STMT_JUMP;
|
||||
jump2->stmtJump.condition = cond1;
|
||||
jump2->stmtJump.label = strdup(s->stmtJump.label);
|
||||
jump2->statement.next = lbl;
|
||||
|
||||
s->stmtJump.label = strdup(lbl->stmtLabel.name);
|
||||
|
||||
s->statement.next = jump2;
|
||||
|
||||
ast_denoop(tlc, &s->stmtJump.condition);
|
||||
|
||||
this->effective = 1;
|
||||
|
||||
} else {
|
||||
|
||||
s->stmtIf.expression = varify(tlc, chu, stmtPrev, s, e);
|
||||
AST *v = e;
|
||||
|
||||
bool not = v->nodeKind == AST_EXPR_UNARY_OP && v->exprUnOp.operator == UNOP_NOT;
|
||||
if(not) {
|
||||
v = v->exprUnOp.operand;
|
||||
}
|
||||
|
||||
if(v->nodeKind != AST_EXPR_VAR) {
|
||||
v = varify(tlc, chu, stmtPrev, s, e);
|
||||
}
|
||||
|
||||
AST *zero = calloc(1, sizeof(ASTExprPrimitive));
|
||||
zero->nodeKind = AST_EXPR_PRIMITIVE;
|
||||
zero->expression.type = v->expression.type;
|
||||
zero->exprPrim.val = 0;
|
||||
|
||||
AST *binop = calloc(1, sizeof(ASTExprBinaryOp));
|
||||
binop->nodeKind = AST_EXPR_BINARY_OP;
|
||||
binop->expression.type = v->expression.type;
|
||||
binop->exprBinOp.operator = not ? BINOP_EQUAL : BINOP_NEQUAL;
|
||||
binop->exprBinOp.operands[0] = v;
|
||||
binop->exprBinOp.operands[1] = zero;
|
||||
|
||||
s->stmtJump.condition = binop;
|
||||
|
||||
ast_denoop(tlc, &s->stmtJump.condition);
|
||||
|
||||
this->effective = 1;
|
||||
|
||||
}
|
||||
@@ -129,15 +236,15 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
||||
|
||||
} 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;
|
||||
|
||||
if(retval && (!is_xop(retval) || retval->nodeKind == AST_EXPR_PRIMITIVE || (retval->nodeKind == AST_EXPR_VAR && retval->exprVar.thing->kind == SCOPEITEM_VAR && !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'))))) {
|
||||
|
||||
retval = s->stmtReturn.val = varify(tlc, chu, stmtPrev, s, retval);
|
||||
this->effective = 1;
|
||||
|
||||
vte_precolor(retval->exprVar.thing, REG_CLASS_16_32, 1);
|
||||
mark_a(retval->exprVar.thing);
|
||||
|
||||
}
|
||||
|
||||
@@ -150,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);
|
||||
|
||||
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
|
||||
// a = f(x);
|
||||
@@ -176,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)) {
|
||||
|
||||
ScopeItem *tmp = create_dumbtemp(tlc, s->stmtAssign.what->expression.type);
|
||||
vte_precolor(tmp, REG_CLASS_16_32, 1);
|
||||
ScopeItem *tmp = create_temp(tlc, s->stmtAssign.what->expression.type);
|
||||
mark_a(tmp);
|
||||
|
||||
ASTExprVar *ev[2] = {calloc(1, sizeof(**ev)), calloc(1, sizeof(**ev))};
|
||||
ev[0]->nodeKind = AST_EXPR_VAR;
|
||||
@@ -189,6 +296,7 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
||||
assign2->nodeKind = AST_STMT_ASSIGN;
|
||||
assign2->what = (AST*) s->stmtAssign.what;
|
||||
assign2->to = (AST*) ev[0];
|
||||
tmp->data.var.declaration = (AST*) s;
|
||||
|
||||
s->stmtAssign.what = (AST*) ev[1];
|
||||
|
||||
@@ -197,30 +305,41 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
||||
|
||||
this->effective = 1;
|
||||
|
||||
} else if(s->stmtAssign.what->nodeKind == AST_EXPR_UNARY_OP && s->stmtAssign.what->exprUnOp.operator == UNOP_DEREF
|
||||
&& s->stmtAssign.to->nodeKind == AST_EXPR_UNARY_OP && s->stmtAssign.to->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_BINARY_OP && s->stmtAssign.to->exprBinOp.operator == BINOP_DEREF) {
|
||||
|
||||
s->stmtAssign.to = varify(tlc, chu, stmtPrev, s, s->stmtAssign.to);
|
||||
|
||||
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;
|
||||
|
||||
} 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) {
|
||||
ASTExprCall *call = &s->stmtAssign.to->exprCall;
|
||||
|
||||
int argCount = call->what->expression.type->function.argCount;
|
||||
int argCount = call->what->expression.type->pointer.of->function.argCount;
|
||||
|
||||
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]);
|
||||
this->effective = 1;
|
||||
|
||||
// Xopify one argument at a time otherwise stmtPrev becomes incorrect
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(s->stmtAssign.to && s->stmtAssign.to->nodeKind == AST_EXPR_UNARY_OP && s->stmtAssign.to->exprUnOp.operator == UNOP_NEGATE && !ast_expression_equal(s->stmtAssign.what, s->stmtAssign.to->exprUnOp.operand)) {
|
||||
@@ -250,10 +369,10 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
||||
|
||||
this->effective = 1;
|
||||
} 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) {
|
||||
s->stmtAssign.to->exprUnOp.operand = varify(tlc, chu, stmtPrev, s, s->stmtAssign.to->exprUnOp.operand);
|
||||
if(s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && s->stmtAssign.to->exprUnOp.operator == BINOP_DEREF) {
|
||||
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;
|
||||
} else if(s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && s->stmtAssign.to->exprBinOp.operator == BINOP_MUL) {
|
||||
@@ -293,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);
|
||||
|
||||
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]);
|
||||
@@ -306,7 +425,7 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
||||
|
||||
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;
|
||||
} else assert(because == NOT_AT_ALL_IT || because == GUCCI);
|
||||
@@ -318,21 +437,25 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
||||
// a = b
|
||||
// a = a op c
|
||||
|
||||
AST *assign2 = calloc(1, sizeof(ASTStmtAssign));
|
||||
assign2->nodeKind = AST_STMT_ASSIGN;
|
||||
assign2->stmtAssign.what = ast_deep_copy(s->stmtAssign.what);
|
||||
assign2->stmtAssign.to = s->stmtAssign.to->exprBinOp.operands[0];
|
||||
|
||||
if(stmtPrev) {
|
||||
stmtPrev->statement.next = assign2;
|
||||
if(x86_is_lea(s)) {
|
||||
// We do the lea trick in cg.c
|
||||
} else {
|
||||
chu->chunk.statementFirst = assign2;
|
||||
AST *assign2 = calloc(1, sizeof(ASTStmtAssign));
|
||||
assign2->nodeKind = AST_STMT_ASSIGN;
|
||||
assign2->stmtAssign.what = ast_deep_copy(s->stmtAssign.what);
|
||||
assign2->stmtAssign.to = s->stmtAssign.to->exprBinOp.operands[0];
|
||||
|
||||
if(stmtPrev) {
|
||||
stmtPrev->statement.next = assign2;
|
||||
} else {
|
||||
chu->chunk.statementFirst = assign2;
|
||||
}
|
||||
assign2->statement.next = s;
|
||||
|
||||
s->stmtAssign.to->exprBinOp.operands[0] = ast_deep_copy(s->stmtAssign.what);
|
||||
|
||||
this->effective = 1;
|
||||
}
|
||||
assign2->statement.next = s;
|
||||
|
||||
s->stmtAssign.to->exprBinOp.operands[0] = ast_deep_copy(s->stmtAssign.what);
|
||||
|
||||
this->effective = 1;
|
||||
} else if(s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && !is_xop(s->stmtAssign.to->exprBinOp.operands[0])) {
|
||||
s->stmtAssign.to->exprBinOp.operands[0] = xopify(tlc, chu, stmtPrev, s, s->stmtAssign.to->exprBinOp.operands[0]);
|
||||
this->effective = 1;
|
||||
@@ -349,16 +472,11 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
||||
}
|
||||
}
|
||||
|
||||
struct DenoopState {
|
||||
AST *targetTLC;
|
||||
bool success;
|
||||
};
|
||||
|
||||
static void pre_dumb_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||
static void pre_norm_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||
AST *n = *nptr;
|
||||
|
||||
if(n == ud) {
|
||||
if(tlc->chunk.functionType) {
|
||||
if(n->nodeKind == AST_CHUNK) {
|
||||
if(n->chunk.functionType) {
|
||||
size_t argCount = n->chunk.functionType->function.argCount;
|
||||
|
||||
// First argCount vtes in list are the arguments
|
||||
@@ -371,8 +489,9 @@ static void pre_dumb_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, A
|
||||
|
||||
ASTExprPrimitive *offset = calloc(1, sizeof(*offset));
|
||||
offset->nodeKind = AST_EXPR_PRIMITIVE;
|
||||
offset->type = primitive_parse("u32");
|
||||
offset->val = 4 + i * 4;
|
||||
offset->type = type_u(8 * x86_max_gpr_size());
|
||||
offset->val = 4 + i * x86_max_gpr_size();
|
||||
offset->stackGrowth = true;
|
||||
|
||||
ASTExprBinaryOp *sum = calloc(1, sizeof(*sum));
|
||||
sum->nodeKind = AST_EXPR_BINARY_OP;
|
||||
@@ -381,11 +500,11 @@ static void pre_dumb_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, A
|
||||
sum->operands[1] = (AST*) offset;
|
||||
sum->operator = BINOP_ADD;
|
||||
|
||||
ASTExprUnaryOp *deref = calloc(1, sizeof(*deref));
|
||||
deref->nodeKind = AST_EXPR_UNARY_OP;
|
||||
ASTExprBinaryOp *deref = calloc(1, sizeof(*deref));
|
||||
deref->nodeKind = AST_EXPR_BINARY_OP;
|
||||
deref->type = vte->type;
|
||||
deref->operand = (AST*) sum;
|
||||
deref->operator = UNOP_DEREF;
|
||||
deref->operands[0] = (AST*) sum;
|
||||
deref->operator = BINOP_DEREF;
|
||||
|
||||
ASTExprVar *evar = calloc(1, sizeof(*evar));
|
||||
evar->nodeKind = AST_EXPR_VAR;
|
||||
@@ -396,8 +515,9 @@ static void pre_dumb_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, A
|
||||
ass->nodeKind = AST_STMT_ASSIGN;
|
||||
ass->next = NULL;
|
||||
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;
|
||||
vte->data.var.declaration = (AST*) ass;
|
||||
|
||||
if(n->chunk.statementFirst) {
|
||||
n->chunk.statementFirst = ass;
|
||||
@@ -411,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) {
|
||||
if(tlc != (AST*) ud) return;
|
||||
|
||||
AST *n = *nptr;
|
||||
|
||||
if(n->nodeKind == AST_EXPR_DOT) {
|
||||
@@ -445,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]));
|
||||
|
||||
ASTExprUnaryOp *deref = calloc(1, sizeof(*deref));
|
||||
deref->nodeKind = AST_EXPR_UNARY_OP;
|
||||
ASTExprBinaryOp *deref = calloc(1, sizeof(*deref));
|
||||
deref->nodeKind = AST_EXPR_BINARY_OP;
|
||||
deref->type = cast->expression.type->pointer.of;
|
||||
deref->operator = UNOP_DEREF;
|
||||
deref->operand = cast;
|
||||
deref->operator = BINOP_DEREF;
|
||||
deref->operands[0] = cast;
|
||||
|
||||
*nptr = (AST*) deref;
|
||||
}
|
||||
@@ -463,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;
|
||||
}
|
||||
|
||||
struct DenoopState {
|
||||
AST *targetTLC;
|
||||
bool success;
|
||||
};
|
||||
|
||||
static void denoop_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||
struct DenoopState *state = ud;
|
||||
|
||||
// if(state->targetTLC != tlc) return;
|
||||
|
||||
AST *n = *nptr;
|
||||
|
||||
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`
|
||||
|
||||
*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;
|
||||
} else if(is_double_field_access(n)) {
|
||||
@@ -492,19 +625,68 @@ static void denoop_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST
|
||||
n->exprCast.what = n->exprCast.what->exprCast.what;
|
||||
|
||||
*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)`
|
||||
|
||||
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];
|
||||
|
||||
*success = true;
|
||||
} else if(n->nodeKind == AST_EXPR_UNARY_OP && n->exprUnOp.operator == UNOP_NOT && n->exprUnOp.operand->nodeKind == AST_EXPR_BINARY_OP && binop_comp_opposite(n->exprUnOp.operand->exprBinOp.operator) != BINOP_WTF) {
|
||||
// Turn `!(a op b)` to `(a !op b)`
|
||||
|
||||
n->exprUnOp.operand->exprBinOp.operator = binop_comp_opposite(n->exprUnOp.operand->exprBinOp.operator);
|
||||
|
||||
*nptr = n->exprUnOp.operand;
|
||||
|
||||
*success = true;
|
||||
} else if(n->nodeKind == AST_EXPR_UNARY_OP && n->exprUnOp.operator == UNOP_NOT && n->exprUnOp.operand->nodeKind == AST_EXPR_UNARY_OP && n->exprUnOp.operand->exprUnOp.operator == UNOP_NOT) {
|
||||
// Turn `!!x` to `x`
|
||||
|
||||
*nptr = n->exprUnOp.operand->exprUnOp.operand;
|
||||
|
||||
*success = true;
|
||||
} 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
|
||||
|
||||
// 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;
|
||||
|
||||
*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;
|
||||
} else if(n->nodeKind == AST_EXPR_EXT_SIZEOF) {
|
||||
ASTExprPrimitive *prim = calloc(1, sizeof(*prim));
|
||||
@@ -522,52 +704,111 @@ static void denoop_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST
|
||||
*nptr = (AST*) prim;
|
||||
|
||||
*success = true;
|
||||
} else if(n->nodeKind == AST_EXPR_CAST) {
|
||||
// TODO: Move all compile-time constant casting from ast_cast_expr to here.
|
||||
|
||||
AST *what = n->exprCast.what;
|
||||
Type *to = n->exprCast.to;
|
||||
|
||||
if(what->nodeKind == AST_EXPR_PRIMITIVE && (to->type == TYPE_TYPE_PRIMITIVE || to->type == TYPE_TYPE_POINTER)) {
|
||||
ASTExprPrimitive *ret = calloc(1, sizeof(*ret));
|
||||
ret->nodeKind = AST_EXPR_PRIMITIVE;
|
||||
ret->type = to;
|
||||
|
||||
if(to->type == TYPE_TYPE_PRIMITIVE) {
|
||||
ret->val = what->exprPrim.val & ((1UL << to->primitive.width) - 1);
|
||||
} else {
|
||||
ret->val = what->exprPrim.val & ((1UL << (8 * type_size(to))) - 1);
|
||||
}
|
||||
|
||||
*nptr = ret;
|
||||
|
||||
*success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dumben_pre(AST *tlc) {
|
||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, tlc, pre_dumb_visitor, NULL);
|
||||
void ast_denoop(AST *tlc, AST **node) {
|
||||
if(!node) {
|
||||
node = &tlc;
|
||||
}
|
||||
|
||||
struct DenoopState state = {.targetTLC = tlc};
|
||||
do {
|
||||
state.success = false;
|
||||
generic_visitor(node, NULL, NULL, tlc, tlc, &state, denoop_visitor, NULL);
|
||||
} while(state.success);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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_segmented_dereferences, NULL);
|
||||
|
||||
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]);
|
||||
} else {
|
||||
t++;
|
||||
}
|
||||
}
|
||||
|
||||
if(ntc_get_int("pdbg")) {
|
||||
char *astdump = ast_dump(tlc);
|
||||
fprintf(stderr, "### BEFORE DENOOP ###\n%s\n", astdump);
|
||||
free(astdump);
|
||||
}
|
||||
|
||||
struct DenoopState state = {.targetTLC = tlc};
|
||||
do {
|
||||
state.success = false;
|
||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, &state, denoop_visitor, NULL);
|
||||
} while(state.success);
|
||||
ast_denoop(tlc, NULL);
|
||||
|
||||
ast_commutativity_pass(tlc);
|
||||
|
||||
if(ntc_get_int("pdbg")) {
|
||||
char *astdump = ast_dump(tlc);
|
||||
fprintf(stderr, "### AFTER DENOOP ###\n%s\n", astdump);
|
||||
free(astdump);
|
||||
}
|
||||
}
|
||||
|
||||
void dumben_go(AST* tlc) {
|
||||
void arch_normalize(AST* tlc) {
|
||||
size_t i = 0;
|
||||
while(1) {
|
||||
if(i == 20000) {
|
||||
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;
|
||||
|
||||
@@ -576,7 +817,7 @@ void dumben_go(AST* tlc) {
|
||||
}
|
||||
|
||||
if(ntc_get_int("pdbg")) {
|
||||
fprintf(stderr, "### DUMBED DOWN %lu ###\n", i++);
|
||||
fprintf(stderr, "### NORM %lu ###\n", i++);
|
||||
char *astdump = ast_dump(tlc);
|
||||
fputs(astdump, stderr);
|
||||
free(astdump);
|
||||
Reference in New Issue
Block a user