Compare commits

...

96 Commits

Author SHA1 Message Date
Mid
f406b2a032 Sections are now top-level nodes (which contain TLCs)
Previously, `@section` was a statement inside a top-level chunk, which
caused issues when the TLC allocated stack memory (the prologue was
generated before the `@section`).

This way, Nectar source code is now defined as a list of sections,
first and foremost.
2025-11-12 12:29:15 +02:00
Mid
7a8b14308b Make dereferencing operator binary, to account for segmentation 2025-10-17 18:16:58 +03:00
Mid
fee8ea5cb3 Bug fixed in string literal -> int cast 2025-10-17 14:09:40 +03:00
Mid
2aa64f4a37 More declarations 2025-10-17 14:09:25 +03:00
Mid
8c164a3b09 sgpr and smem 2025-10-17 14:09:10 +03:00
Mid
8c4754b563 Joe expansion 2025-10-15 14:11:41 +03:00
Mid
9d975eeceb Force assign declaration statement for all virtual/temp variables 2025-10-15 14:11:24 +03:00
Mid
ecab77f9e9 Holy fucking bug omg 2025-10-15 14:11:02 +03:00
Mid
132cedff09 ebp is also callee-saved 2025-10-15 14:10:54 +03:00
Mid
f978b66662 Conditionally save volatile regs 2025-10-15 14:10:42 +03:00
Mid
692ed4d21c Update DOCUMENTATION.md 2025-10-14 16:32:00 +03:00
Mid
c04c1a97c9 MapDQCaLDhS for the standard library 2025-10-14 16:31:24 +03:00
Mid
104ba53b33 Introduce null keyword 2025-10-14 16:30:58 +03:00
Mid
2d56f124a9 Rename dumbification to normalization 2025-10-10 17:18:10 +03:00
Mid
fc8d65f893 Bug fix 2025-10-10 17:16:06 +03:00
Mid
2049826870 Bug fix 2025-10-10 17:15:52 +03:00
Mid
fc16c077d9 Auto dereferencing is only for near pointers 2025-10-09 11:36:44 +03:00
Mid
8fc5521601 Multiple bug fixes 2025-10-09 11:36:21 +03:00
Mid
dfbadfecb9 More AST functions (incl new second-class record pass) 2025-10-09 11:35:35 +03:00
Mid
b21ce51435 Update examples with QoL parsing additions 2025-10-02 13:47:55 +03:00
Mid
2f67a6f109 Fix dumbing bug 2025-10-02 13:46:08 +03:00
Mid
1177462bda Increase XOPBUFSZ to 64 2025-10-02 13:45:15 +03:00
Mid
246f7a3b71 type_dereference 2025-10-02 13:44:49 +03:00
Mid
2188448b19 Implicitly cast return type 2025-10-02 13:44:35 +03:00
Mid
d46b104d5c Fix operator precedence and introduce auto-deref 2025-10-02 13:44:18 +03:00
Mid
07e3604a8c Set TLC functionType sooner 2025-10-02 13:39:11 +03:00
Mid
fb75434cba Copy cexprs to keep AST nodes unique/unshared 2025-10-02 13:38:27 +03:00
Mid
7569420fe0 MapSOaLDhS 2025-09-19 19:08:22 +03:00
Mid
fb6fd76d0b More denoops 2025-09-19 19:05:18 +03:00
Mid
e80f6643dc Dumbify callee expressions 2025-09-19 19:04:53 +03:00
Mid
8d8d1cf067 Match resource masks instead of resource names 2025-09-19 19:04:26 +03:00
Mid
1708faf14d Cast fixes 2025-09-19 19:03:50 +03:00
Mid
14ab1f432d Correct type sizes under different ABIs 2025-09-15 17:10:38 +03:00
Mid
f03e8517df document xop_sz 2025-09-14 10:44:28 +03:00
Mid
273e6d1058 Add ugpr and umem types 2025-09-14 10:44:08 +03:00
Mid
251d24fb30 in bits, not bytes 2025-09-14 10:43:37 +03:00
Mid
cdcc1f6d3d Update DOCUMENTATION.md 2025-09-13 11:21:14 +03:00
Mid
2771609049 Add logical op tests 2025-09-13 11:20:11 +03:00
Mid
d0262c586e So many bug fixes like omg why didn't u use bp for stacc idiot 2025-09-13 11:19:49 +03:00
Mid
13333c971a Add && and || 2025-09-13 11:17:11 +03:00
Mid
b4272a67d1 Add more deepcopies 2025-09-13 11:14:30 +03:00
Mid
6b7fefc5e7 Add documentation passes to DOCUMENTATION.md 2025-08-22 23:27:09 +03:00
Mid
f9fe031532 Add documentation passes svg 2025-08-22 23:25:40 +03:00
Mid
7f855f3931 Despaghettify source 2025-08-15 17:31:05 +03:00
Mid
5196026ed1 Fucking add else statement 2025-08-14 18:22:31 +03:00
Mid
5755e243a9 IR linearization 2025-08-13 18:27:52 +03:00
Mid
67fb9e5b52 Add back lea 2025-08-06 16:19:10 +03:00
Mid
012d18cdf8 Fix stack growth and parameter passing 2025-08-04 18:06:30 +03:00
Mid
81b1010453 Basic allocators with unmanaged list (broken) 2025-07-29 17:54:13 +03:00
Mid
fb4849d382 Call pointers only 2025-07-29 17:48:31 +03:00
Mid
5455e1cebb BF test 2025-07-18 13:56:56 +03:00
Mid
6b3f4ac486 mkdir build directory 2025-07-17 21:08:58 +03:00
Mid
db08fcbd27 End of an Era 2025-07-17 21:02:10 +03:00
BrainStackOverFlow
5c5187f0ff Fix actually use specified include dirs 2025-07-17 13:06:48 +03:00
BrainStackOverFlow
0a29b5e6b2 Fix make install to copy not move final ntc finary 2025-07-17 13:02:49 +03:00
BrainStackOverFlow
9adcfd2ff5 Fix (null) output of stack variables 2025-07-17 11:26:20 +03:00
BrainStackOverFlow
20197ee0bc Fix unhandled AST_EXPR_EXT_SIZEOF Error 2025-07-17 11:00:59 +03:00
BrainStackOverFlow
f3b6f81ef5 Make clean delete obj files 2025-07-17 10:58:23 +03:00
BrainStackOverFlow
109711e79f Add .d files to gitignore 2025-07-17 10:56:17 +03:00
Mid
b204860b2e Fucking add scope file oh my god how could I miss this 2025-07-16 11:00:45 +03:00
Mid
a39bf40755 Add irregular allocation and segmentation to DOCUMENTATION.md 2025-06-30 21:26:20 +03:00
Mid
6844b2d0b7 Add inc parameter to README.md 2025-06-30 21:22:48 +03:00
Mid
923ec25d79 Restructure source tree, SRoA and variable pointer size 2025-06-30 20:37:51 +03:00
Mid
438c3b3467 Additional tests 2025-06-10 22:07:38 +03:00
Mid
6ec68a766f penis sex fuck penis cock, irregular register allocation and rename VarTable to Scope 2025-06-10 22:07:22 +03:00
Mid
1c4b5a5095 updater documentater 2025-05-03 10:24:46 +03:00
Mid
13f43cadeb More tests 2025-05-03 10:03:51 +03:00
Mid
0d808de34c Updated DOCUMENTATION.md 2025-05-03 10:03:26 +03:00
Mid
56c10daaa7 parametrize your mom? 2025-05-03 10:00:20 +03:00
Mid
2c6033e501 gingus 2025-02-27 20:10:02 +02:00
Mid
f349fc1d56 New documentation update 2025-02-27 20:07:27 +02:00
Mid
9dc5bddfef Doc v5 2025-01-22 17:04:52 +02:00
Mid
87a07e29d6 Doc v4 (oh no) 2025-01-13 18:40:41 +02:00
Mid
9ceb061ad4 How to Add a Feature :) 2025-01-07 17:30:25 +02:00
Mid
fdfc6606cd documentation v2 2025-01-06 17:35:25 +02:00
Mid
ff962b6361 Add fibonacci to funcdefs.nct 2025-01-02 18:07:55 +02:00
Mid
6968777385 documentation v1 2025-01-02 18:05:37 +02:00
Mid
5b3991e81c Handle certain precoloring collisions 2025-01-02 18:04:57 +02:00
Mid
17a0c9d902 update 2024-12-14 18:13:33 +02:00
Mid
390c4c954d Add return statement 2024-11-28 21:40:03 +02:00
Mid
8caadf9af1 Function definition example 2024-11-26 18:43:34 +02:00
Mid
d6d2b228ef Implement function definitions 2024-11-26 18:42:20 +02:00
Mid
6789984bd1 Codegen casts 2024-11-25 18:35:11 +02:00
Mid
fe0baa26a0 Spilling 2024-11-25 17:36:03 +02:00
Mid
9e04938065 Partial compilation 2024-11-25 17:33:46 +02:00
Mid
ba6f9fb6c1 Safety commit 2024-11-20 16:36:17 +02:00
mid
07f6d57aba Whoopsies, I dropped this 2024-06-13 09:56:30 +03:00
mid
012320569e Support negation, parentheses. Always zero-extend registers for bugless memory operand usage 2024-06-12 11:17:09 +03:00
mid
fa40a78546 Don't specify immediate operand size 2024-06-11 17:18:45 +03:00
Mid
8e4cb71924 BF doesn't need loop guards anymore 2024-02-15 22:33:27 +02:00
Mid
77a459ffd3 Loop guards 2024-02-15 22:33:06 +02:00
Mid
5ec2349336 Update README.md 2024-02-13 22:43:28 +02:00
Mid
55bfa2289e Iterative dumbification 2024-02-13 22:43:04 +02:00
Mid
a1077f7c03 Oh who gives a fuck? 2024-02-13 21:33:49 +02:00
Mid
83e0771f2c Use wcl386 2024-02-13 21:30:42 +02:00
Mid
4d182e1685 Closer to OW-compliance + removed some leaks 2023-08-31 21:24:46 +03:00
94 changed files with 7851 additions and 1487 deletions

2
.gitignore vendored
View File

@@ -1,9 +1,9 @@
a.out
tests/*.o
tests/*.asm
ntc
ntc.exe
*.o
*.d
*.err
dos4gw.exe
massif.out.*

390
DOCUMENTATION.md Normal file
View File

@@ -0,0 +1,390 @@
# 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 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. 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.
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 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 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.
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.
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 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 (pre) or after (post) delving deeper into the tree. Most passes use the prehandler, but type checking will be better with a posthandler, since we want type checks to happen bottom to top.
## Desegmentation
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. 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 `ast_denoop`.
## Normalization
The idea of turning the AST progressively primitive is called "normalization" in the source. The most simple example would be the following:
a = -b
which should become
a = b
a = -a
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).
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 normalization of the following Fibonacci implementation, as of writing. Here is the main Nectar source code:
fibonacci: u32(u32 n) -> {
if(n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
};
And the processed AST output by the compiler:
u32(u32) fibonacci: u32(u32) {
n = *(@stack + 4);
if((n <= 1)) {
return n;
}
$dumb2 = n;
$dumb2 = ($dumb2 - 1);
$dumb0 = fibonacci($dumb2);
$dumb3 = n;
$dumb3 = ($dumb3 - 2);
$dumb1 = fibonacci($dumb3);
$dumb0 = ($dumb0 + $dumb1);
return $dumb0;
};
`@stack` is an internal variable that points to the beginning of the current stack frame.
## 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.
As simplest, the code `u8 x = 0;` has an empty UD-chain, because there are no uses. It's definition could even be classified as dead code.
Clearly, a definition of a variable overrides every definition before it, but that is only within a basic block. In the following code, a variable has a single potential definition in each branch of the if statement, but afterward it will have two:
u8 x = 0; /* Potential definitions: [x = 0]
* UD-chain of x:
* empty */
if(y) {
x = 1;
f1(x); /* Potential definitions: [x = 1]
* UD-chain of x:
* - def=[x = 1], use=[f1(x)] */
} else {
x = 2;
f2(x); /* Potential definitions: [x = 2]
* UD-chain of x:
* - def=[x = 1], use=[f1(x)]
* - def=[x = 2], use=[f2(x)] */
}
f3(x); /* Potential definitions: [x = 1], [x = 2]
* UD-chain of x:
* - def=[x = 1], use=[f1(x)]
* - def=[x = 2], use=[f2(x)]
* - def=[x = 1], use=[f3(x)]
* - def=[x = 2], use=[f3(x)] */
It gets worse. Imagine the following pseudocode:
x = 0;
loop {
x = x + 1;
y = 5;
}
The UD-chain knows nothing about loops. It only cares whether something comes before or after. As is, it'll assume y is not in conflict with x, and they'll end up in the same register. Because of this, the parser must insert a so-called "loop guard", which will turn the AST into the following:
x = 0;
loop {
x = x + 1;
y = 5;
}
x;
That's one problem, but there's another:
x = 0;
loop {
f(x);
x = x + 1;
}
Despite appearing later in the source, `x = x + 1` is a potential definition for `f(x)`! This means the UD-chain generator must go through loops twice -- once with the upper definitions, and once with definitions from within the loop. Additionally, the UD-chain is assumed to be ordered by appearence in the source, so insertion in the second pass must consider that.
Now, why did I choose UD chains? Why, simplicity, obviously...
## Coloring
At this point we have a very distorted kind of Nectar AST in our function. Sure we've got blocks and other familiar things, but all variables are in a flat list. These variables are essentially the "virtual registers" you hear a lot about. Because x86 only has six general-purpose registers, we must assign each of these variables (VTEs) to a physical machine register.
The x86 register set is highly irregular:
1. `ax` may be split into two halves (`ah` and `al`), but `eax` may not be split. Likewise with `ebx`, `ecx` and `edx`, but nothing else.
2. The low bytes of `si`, `di`, `bp` and `sp` are accessible only in 64-bit mode.
3. Registers `r8` through `r15` are accessible only in 64-bit mode.
4. If an instruction uses any 64-bit-only register, then `ah`, `bh`, `ch`, `dh` are not accessible.
5. Of the 16-bit registers, only `bx`, `bp`, `di` and `si` may be dereferenced. Any 32-bit register can be dereferenced, including in 16-bit mode.
6. Obviously, floating-point registers, control registers, debug registers form separate spaces.
It is insufficient for a compiler to assume these are in any way comparable. The trick is to imagine the CPU as a bitmap of resources.
`al` and `ah` are separate registers on their own, so we assign 1 bit to each (for clarity, let it be ints 1 and 2). `ax` is also a separate register, but as it is the concatenation of `al` and `ah`, it is assigned the OR of both bits (int 3). You could reserve a bit for the high 16 bits of `eax`, but as it is inaccessible, you may as well give the same bits of `ax` to `eax`. Repeat the same for the rest of the registers.
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 we gave up finding any patterns and assume the CPU to be a soup.
(64-bit is not considered)
Finally we begin coloring, a large area of study in itself, but a common approach is to imagine it as a graph coloring problem, where vertices are VTEs, and edges connect conflicting VTEs that cannot have the same color. "Color" in this case means the register class and index within the register class. Edges in the graph are determined using the UD-chains of both VTEs and their resource masks (registers with an empty resource mask intersection cannot interfere).
The actual coloring algorithm used is Welsh-Powell, which sorts the VTEs/vertices by degree before attempting greedy coloring.
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 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.
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 `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
FINALLY. This pass doesn't use `generic_visitor`, because it may consume multiple sibling AST nodes for emitting code. At this point there's nothing arcane or obscure; the code is pretty self-explanatory.
Using the same Fibonacci example as above, this is the result.
global fibonacci
fibonacci:
mov edx, [esp + 4]
cmp edx, 1
ja .L0
mov eax, edx
ret
.L0:
mov eax, edx
dec eax
push ecx
push edx
push eax
call fibonacci
add esp, 4
pop edx
pop ecx
mov ecx, eax
mov eax, edx
sub eax, 2
push ecx
push edx
push eax
call fibonacci
add esp, 4
pop edx
pop ecx
add ecx, eax
mov eax, ecx
ret
## Generics
**NOTE: I intend to place this section in a different Markdown file entirely. It will be simply too big.**
record Foo[T, U, V] {
T t;
U u;
V v;
}
Nectar does generics similarly to C++. Structures are simple to make generic. When parsing a generic structure definition we must introduce a new scope, so we can introduce the generic types as instances of `TypeGeneric`. If we encounter a parametrization like `Foo[u8, u16, u32]`, we walk up the tree formed by the type of `Foo`, and replace all `TypeGeneric` instances with the concrete types. This is done by `type_parametrize` which takes a `Parametrization` structure. Note that generic type names are not used, but the indices at which they appear.
bar: [T]T(T a, T b) -> {
return a + b;
};
If a function is defined with a generic type, parsing it is skipped until an explicit instantiation. This is because type checking is coupled with parsing. It needn't be this way, but it's a refactoring I'm not interested in doing at the moment. This ended up bringing other complexities. Because of the parser-type checker coupling, we must know what a generic type's name originally was, so `TypeGeneric`s must store this in addition to the index.
@instantiate bar[u32];
Upon parsing the above statement, the parser:
1. Creates a new scope
2. Finds the generic type names (using an output value of `type_parametrize` not mentioned until now)
3. Inserts the concrete types into the scope under the generic type names
4. Jumps to the generic function definition (in fact, to *right after the `[...]` block* to ignore the genericness)
5. Begins parsing the function's code block
6. Pops the scope
7. Jumps back to the end of the `@instantiate` statement
8. Insert the function code block into a new symbol, appending the concrete type names to the original function name separated by underscores (`bar_u32`)
How's that for a hack?
## x86 Segmentation
In x86, absolutely all memory access by software goes through one of six "segments", which translates the address from the segment's space to true linear space.
In 32-bit and above, segmentation has mostly been forgotten. Major OSes set all\* segments to the same setting, letting software pretend that segmentation doesn't exist. This is called a flat memory model. In 16-bit, pointers are enough only for 64kB of memory, so Intel added segmentation to expand the memory space to 20-bit, giving the 8086 one megabyte to use.
This splits the pointer type into three subtypes:
1. Near: the kind of pointer everyone knows, which uses some "default" segment
2. Far: a pair of integers, one which defines the segment, the other defines the memory offset
3. Huge: a far pointer that is slower but more well-behaved
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 normalized. Let us have the following:
u8 @far* a;
*a = 5;
a = a + 1;
A data access goes through the data segment `ds`, so prior to dereferencing, we must make sure `ds` is set to the segment part of `a`.
FarPointer a;
$sreg_ds = a.segment;
*a.offset = 5;
a.offset = a.offset + 1;
This example benefits from scalar replacement:
u16 a_segment;
u16 a_offset;
$sreg_ds = a_segment;
*a_offset = 5;
a_offset = a_offset + 1;
## Other problems with this approach (1)
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:
cmp eax, 1
je .L1
cmp ebx, 1
jne .L2
.L1:
; do stuff
.L2:
Note that the two jump instructions are basically goto statements. With a tree of blocks, this kind of logic isn't feasible.
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;
u1 k = z || w;
if(k) {
do stuff;
}
And now we need 2 new registers for no reason..
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 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.
To account for this, we can have a second assignment statement right next to the multiplication. Because the main multiplication clobbers the source operand, the mulhi assignment must come before the mul. Putting all this together, this is the canonical way to do `z = x * y` with an x86 target:
z = x;
w = z *^ y;
z = z * y;
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 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 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 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.
![Damn](https://mid.net.ua/images/nctrefomg1.png)
Oh God.. and for what? So it runs on MS-DOS?? Was it worth it? It doesn't even work there; it crashes!

View File

@@ -3,19 +3,26 @@ rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2))
SOURCES := $(call rwildcard,src/,*.c)
HEADERS := $(call rwildcard,src/,*.h)
OBJECTS := $(patsubst src/%.c,build/%.o,$(SOURCES))
DEPS := $(patsubst build/%.o,build/%.d,$(OBJECTS))
PREFIX = /usr/local
CFLAGS = $(if $(DEBUGA),-DDEBUG=1,) -Wall $(if $(ASAN),-fsanitize=address,) -fno-PIE -no-pie -std=gnu11 $(if $(DEBUG),-O0 -g,-Os -s) -fms-extensions -Isrc -Wno-array-bounds -MMD -MP -flto -Wno-format-zero-length
.PHONY: install clean
ntc: $(SOURCES) $(HEADERS)
ifdef OW
wcl $(if $(GAS),-DSYNTAX_GAS=1,) $(if $(DEBUG),-DDEBUG=1,) -fe="ntc.exe" -0 -bcl=dos -mt $(if $(DEBUG),,-d0 -os -om -ob -oi -ol -ox) -lr -za99 -i=src $(SOURCES)
else
cc $(if $(GAS),-DSYNTAX_GAS=1,) $(if $(DEBUG),-DDEBUG=1,) -Wall -o ntc -fno-PIE -no-pie -std=gnu11 $(if $(DEBUG),-O0 -g,-Os -s) -fms-extensions -Isrc $(SOURCES)
endif
build/%.o: src/%.c
mkdir -p $(@D)
$(CC) -c $(CFLAGS) -o $@ $<
ntc: $(OBJECTS)
$(CC) $(CFLAGS) -o ntc $(OBJECTS)
install: ntc
mv ./ntc $(PREFIX)/bin
cp ./ntc $(PREFIX)/bin
clean:
rm ./ntc
rm ./ntc $(OBJECTS)
-include $(DEPS)

View File

@@ -1,11 +1,19 @@
# N19 Reference Compiler
Made to compile fast and produce not great, but acceptable output. Currently only 386 output supported (protected and partially real mode).
Made to compile fast and produce acceptable output. Currently only 386 output supported (protected and partially real mode).
Composed of the following passes:
1. Lexing
2. Parsing & loop\_second\_pass
3. Dumbification
4. Codegen
UD-chains are generated during parsing. Codegen uses on primitive patterns within the AST, possible thanks to dumbification. This technique is applicable owing to Nectar's already low-level nature
# Installation
make
sudo make install
# Command-line usage
@@ -19,10 +27,23 @@ This will be ported into a man page later:
(TBA) x86_target: Target processor feature set (0 for 8086, 3 for 80386, m for generic x86_64)
(TBA) x86_mode: Target operating mode (16 for 16-bit real mode, 32 for 32-bit protected mode or long mode, 64 for 64-bit long mode)
in: Input Nectar source file
inc: Additional module include path
Unknown arguments are ignored.
# Example
# Example 1
$ ntc < tests/parsenum.nct > out.asm
$ nasm -f elf -o out.o out.asm
$ ld -dynamic-linker /lib/ld-linux.so.2 -m elf_i386 -o parsenum out.o -lc
$ printf "123" | ./parsenum
$ echo $?
123
$ printf "05" | ./parsenum
$ echo $?
5
# Example 2
$ ntc < tests/bf.nct > out.asm
$ nasm -f elf -o out.o out.asm

472
documentation_passes.svg Normal file
View 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
View File

@@ -0,0 +1,4 @@
record Alloc {
u8* userdata;
u8*(u8* userdata, u8* ptr, u32 sz)* realloc;
}

5
examples/Exporter.nct Normal file
View File

@@ -0,0 +1,5 @@
u32 SYMBOL: 1234;
record Foo {
u32 gaga;
}

8
examples/FarPointer.nct Normal file
View File

@@ -0,0 +1,8 @@
u8 @far* x;
u8 @far* y;
loop {
u8 v = *x;
*y = v;
x.offset = x.offset + 1;
}

View File

@@ -0,0 +1,68 @@
record LibCSockAddrInet6 {
u16 family;
u16 port;
u32 flowinfo;
u8[16] address;
}
@section(".text");
extern u32(u32 domain, u32 type, u32 protocol) socket;
extern u32(u32 sockfd, u8* addr, u32 socklen) bind;
extern u32(u32 sockfd, u32 backlog) listen;
extern u32(u32 sockfd, u8* addr, u32* socklen) accept;
extern u32(u32 sockfd, u8* data, u32 length) write;
extern u32(u32 sockfd, u8* data, u32 length) read;
extern u32(u32 fd) close;
main: u0() -> {
SERV_SOCKET = socket(10, 1, 0);
LibCSockAddrInet6 addr;
addr.family = 10; /* AF_INET6 */
addr.port = 43105; /* 25000 in reversed endian */
addr.flowinfo = 0;
addr.address[0] = 0;
addr.address[1] = 0;
addr.address[2] = 0;
addr.address[3] = 0;
addr.address[4] = 0;
addr.address[5] = 0;
addr.address[6] = 0;
addr.address[7] = 0;
addr.address[8] = 0;
addr.address[9] = 0;
addr.address[10] = 0;
addr.address[11] = 0;
addr.address[12] = 0;
addr.address[13] = 0;
addr.address[14] = 0;
addr.address[15] = 0;
bind(SERV_SOCKET, &addr, @sizeof(addr));
listen(SERV_SOCKET, 16);
loop {
u32 fd = accept(SERV_SOCKET, &addr, &SOCK_LEN);
if(fd == -1) {
continue;
}
read(fd, &BUFFER, @sizeof(BUFFER));
write(fd, &RESPONSE, @sizeof(RESPONSE));
close(fd);
}
return;
};
@section(".data");
u32 SOCK_LEN:;
u32 SERV_SOCKET:;
u8[?] RESPONSE: "HTTP/1.0 200 OK\r\nServer: NectarTestHTTPFileServer\r\nContent-Type: text/plain\r\n\r\nYo.";
u8[1024] BUFFER:;

4
examples/Importer.nct Normal file
View File

@@ -0,0 +1,4 @@
use Exporter;
Foo f;
f.gaga = SYMBOL;

View File

@@ -0,0 +1,14 @@
u8 a = 12;
u8 b = 19;
u8 c = a + b;
u32 d = 1;
u8 e = d + 1;
u32 f = 10;
u32 g = 55;
a;
b;
c;
d;
e;
f;
g;

43
examples/ListDLC.nct Normal file
View File

@@ -0,0 +1,43 @@
/*
* ListDLC: Dynamic, Linear growth, C-managed
*/
extern u8*(u8*, ugpr) realloc;
record ListDLC[T, S; growth] {
S capacity;
S size;
T[?]* data;
}
ListDLC_remove: [T, S; growth]u0(ListDLC[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;
};
ListDLC_add: [T, S; growth]u0(ListDLC[T, S; growth]* this, T value) -> {
if((*this).size == (*this).capacity) {
S newcap = (*this).capacity + growth;
(*this).capacity = newcap;
(*this).data = realloc((*this).data, newcap * @sizeof T);
}
(*((*this).data))[(*this).size] = value;
(*this).size = (*this).size + 1;
return;
};

43
examples/ListDLU.nct Normal file
View 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;
};

27
examples/ListS.nct Normal file
View File

@@ -0,0 +1,27 @@
record ListS[T, S; capacity] {
S size;
T[capacity] data;
}
ListS_remove: [T, S; capacity]u0(ListS[T, S; capacity]* this, S index) -> {
T* data = &((*this).data[index]);
(*this).size = (*this).size - 1;
S sz = (*this).size;
loop {
if(index == sz) {
break;
}
*data = *(data + 1);
data = data + 1;
index = index + 1;
}
return;
};
ListS_add: [T, S; capacity]u0(ListS[T, S; capacity]* this, T value) -> {
(*this).data[(*this).size] = value;
(*this).size = (*this).size + 1;
return;
};

6
examples/LogicalAnd.nct Normal file
View File

@@ -0,0 +1,6 @@
u16 x = 0;
u16 y = 5;
if(x && y) {
u16 z = 9;
}

View 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
View File

@@ -0,0 +1,6 @@
u16 x = 0;
u16 y = 5;
if(x || y) {
u16 z = 9;
}

132
examples/MapDQCaLDhS.nct Normal file
View 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
View 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;

View File

@@ -0,0 +1,9 @@
u16 x = 0;
x = 5;
if(x == 2) {
x = 3;
}
x = 4;

View File

@@ -0,0 +1,14 @@
record X {
u32 a;
u32 b;
}
main: u0() -> {
X x;
x.a = 5;
x.b = 6;
u32 c = x.a + x.b;
return;
};

43
examples/UDPEcho.nct Normal file
View File

@@ -0,0 +1,43 @@
@section(".text");
record sockaddr {
0: u16 family;
2: u16 port;
4: u32 addr;
8: u32 zero0;
12: u32 zero1;
}
extern u32(u32, u32, u32) socket;
extern u32(u32, sockaddr*, u32) bind;
extern u32(u32, u8*, u32, u32, u8*, u32*) recvfrom;
extern u32(u32, u8*, u32, u32, u8*, u32) sendto;
MY_SOCKET = socket(2, 2, 0);
sockaddr sa;
sa.family = 2; /* AF_INET */
sa.port = 43105; /* 25000 in net-endian */
sa.addr = 0; /* Bind to all */
sa.zero0 = 0;
sa.zero1 = 0;
bind(MY_SOCKET, &sa, 16);
loop {
OTHER_SIDE_LEN = 64;
u32 msglength = recvfrom(MY_SOCKET, &BUFFER, 512, 0, &OTHER_SIDE, &OTHER_SIDE_LEN);
sendto(MY_SOCKET, &BUFFER, msglength, 0, &OTHER_SIDE, OTHER_SIDE_LEN);
}
@section(".data");
u32 MY_SOCKET:;
u32 OTHER_SIDE_LEN:;
u8[64] OTHER_SIDE:;
u8[512] BUFFER:;

18
examples/UserListDLC.nct Normal file
View File

@@ -0,0 +1,18 @@
use ListDLC;
@section(".text");
@instantiate ListDLC_remove[u16, u16; 9];
@instantiate ListDLC_add[u16, u16; 9];
main: u0() -> {
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;
};
@section(".bss");

32
examples/UserListDLU.nct Normal file
View 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");

View File

@@ -30,12 +30,10 @@ loop {
data[dataPtr] = data[dataPtr] - 1;
}
if(code[codePtr] == 46) {
u32 z = &data + dataPtr;
write(1, z, 1);
write(1, &data + dataPtr, 1);
}
if(code[codePtr] == 44) {
u32 z = &data + dataPtr;
read(0, z, 1);
read(0, &data + dataPtr, 1);
}
if(code[codePtr] == 91) {
if(data[dataPtr] == 0) {
@@ -52,8 +50,7 @@ loop {
}
codePtr = codePtr + 1;
}
}
if(data[dataPtr] != 0) {
} else {
stckPtr = stckPtr + 1;
stck[stckPtr] = codePtr;
}
@@ -64,8 +61,7 @@ loop {
if(code[codePtr] == 93) {
if(data[dataPtr] == 0) {
stckPtr = stckPtr - 1;
}
if(data[dataPtr] != 0) {
} else {
codePtr = stck[stckPtr];
}
}
@@ -75,7 +71,3 @@ loop {
codePtr = codePtr + 1;
}
codePtr;
dataPtr;
stckPtr;

11
examples/bit-rounding.nct Normal file
View File

@@ -0,0 +1,11 @@
u32 x: 123;
u33 y: 5;
u3* o = 5000;
u32 z = *o;
u3 p = *(o + z);
u3 *g = o - z;
p = *g;
u32 c = 566;
u16 j = c;

48
examples/countwords.nct Normal file
View File

@@ -0,0 +1,48 @@
@section(".data");
local u8[33] finalstring: "The amount of words in stdin is: ";
local u8[4] answer: "0000";
@section(".text");
extern s32() getchar;
extern s32(s32, u8*, u32) write;
increase: u0() -> {
answer[3] = answer[3] + 1;
if(answer[3] > 57) {
answer[3] = 48;
answer[2] = answer[2] + 1;
if(answer[2] > 57) {
answer[2] = 48;
answer[1] = answer[1] + 1;
if(answer[1] > 57) {
answer[1] = 48;
answer[0] = answer[0] + 1;
if(answer[0] > 57) {
answer[0] = 48;
}
}
}
}
return;
};
main: u0() -> {
loop {
s32 c = getchar();
if(c == -1) {
break;
}
if(c == 32) {
increase();
}
}
write(1, &finalstring, 33);
write(1, &answer, 4);
return;
};

6
examples/funcdefs.nct Normal file
View File

@@ -0,0 +1,6 @@
fibonacci: u16(u16 n) -> {
if(n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
};

12
examples/functions.nct Normal file
View File

@@ -0,0 +1,12 @@
extern s32() getchar;
extern u0(s32) putchar;
loop {
s32 z = 5;
s32 a = getchar();
if(a == -1) {
break;
}
putchar(a);
putchar(z);
}

7
examples/if.nct Normal file
View File

@@ -0,0 +1,7 @@
u16 x: 5;
if(x != 0) {
x = 2;
} else {
x = 5;
}

11
examples/loopregalloc.nct Normal file
View File

@@ -0,0 +1,11 @@
u8 a;
a = 2;
loop {
a = 3;
u8 b = 1;
u8 c = 2;
}

View File

@@ -0,0 +1,16 @@
record sockaddr {
u32 family;
u16 port;
u32 addr;
u32 zero;
}
@section(".data");
@section(".text");
sockaddr sa;
sa.family = 5;
sa.port = 43105;
sa.addr = 16rC0A80001;
sa.zero = 0;

View File

@@ -0,0 +1,17 @@
record sockaddr {
u32 family;
u16 port;
u32 addr;
u32 zero;
}
@section(".data");
sockaddr sa:;
@section(".text");
sa.family = 5;
sa.port = 43105;
sa.addr = 16rC0A80001;
sa.zero = 0;

View File

@@ -0,0 +1,16 @@
record A {
u32 x;
u8 z;
}
record B {
0: u32 x;
7: A a;
}
@section(".text");
B rec;
rec.x = 5;
rec.a.x = 10;
rec.a.z = 9;

View File

@@ -0,0 +1,19 @@
record A {
u32 x;
u8 z;
}
record B {
u32 x;
A a;
}
@section(".data");
B rec:;
@section(".text");
rec.x = 5;
rec.a.x = 10;
rec.a.z = 9;

View File

@@ -19,4 +19,4 @@ loop {}
u8[19] string: "Hello from Nectar!\0";
@align(510);
u16 bootsig: 16rAA55;
u16 bootsig: 16rAA55;

View File

@@ -1,8 +1,9 @@
u16 a = 12;
u16 b = a & 6;
u16 c = b ^ a | 3;
u16 d = 11 * c;
u16 o = 5;
u16 d = o * 2;
if(a) {
if(a == 0) {
u16 e = b + c + d;
}
}

23
examples/parsenum.nct Normal file
View File

@@ -0,0 +1,23 @@
@section(".data");
u8 buf: 0;
@section(".text");
extern u0(u32) exit;
extern s32(u32, u8*, u32) read;
_start: u0() -> {
u16 value = 0;
loop {
if(read(0, &buf, 1) != 1) {
break;
}
buf = buf - 48;
value = value * 10;
value = value + buf;
}
exit(value);
};

View File

@@ -1,5 +1,5 @@
u8 a = 5;
if(a) {
if(a != 0) {
u8 a = 10; /* Should not cause scoping errors. */
}
u8 b = 15; /* `a` in the if statement scope should be free'd. */
u8 b = 15; /* `a` in the if statement scope should be free'd. */

6
examples/spill.nct Normal file
View File

@@ -0,0 +1,6 @@
u8 a = 4;
u8 b = 1;
u8 c = 9;
u8 d = 5;
u8 e = 22;
u8 f = a + b + c + d + e;

1
examples/stack.nct Normal file
View File

@@ -0,0 +1 @@
@stack = @stack - 123;

View File

@@ -1,63 +0,0 @@
#include"ast.h"
#include<stdint.h>
#include<string.h>
#include<stdlib.h>
int BINOP_COMMUTATIVE[] = {
[BINOP_ADD] = 1,
[BINOP_SUB] = 0,
[BINOP_MUL] = 1,
[BINOP_DIV] = 0
};
AST *ast_expression_optimize(AST *ast) {
return ast;
}
int ast_expression_equal(AST *a, AST *b) {
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]);
}
return 0;
}
// ... Ew
int ast_stmt_is_after(const AST *chunk, const AST *s1, const AST *s2) {
const AST *s = chunk->chunk.statementFirst;
while(s) {
if(s == s1) {
return 0;
}
if(s == s2) {
return 1;
}
if(s->nodeKind == AST_STMT_IF) {
int i = ast_stmt_is_after(s->stmtIf.then, s1, s2);
if(i != -1) {
return i;
}
} else if(s->nodeKind == AST_STMT_LOOP) {
int i = ast_stmt_is_after(s->stmtLoop.body, s1, s2);
if(i != -1) {
return i;
}
}
s = s->statement.next;
}
return -1;
}

259
src/ast.h
View File

@@ -1,259 +0,0 @@
#ifndef NCTREF_AST_H
#define NCTREF_AST_H
#include"types.h"
#include"lexer.h"
#include"vartable.h"
typedef enum {
AST_CHUNK,
AST_STMT_DECL,
AST_TYPE_IDENTIFIER,
AST_EXPR_PRIMITIVE,
AST_STMT_IF,
AST_EXPR_BINARY_OP,
AST_EXPR_VAR,
AST_TYPE_POINTER,
AST_EXPR_UNARY_OP,
AST_STMT_LOOP,
AST_STMT_BREAK,
AST_STMT_CONTINUE,
AST_EXPR_CALL,
AST_STMT_EXPR,
AST_STMT_ASSIGN,
AST_STMT_EXT_ALIGN,
AST_EXPR_STRING_LITERAL,
AST_EXPR_CAST,
AST_EXPR_ARRAY,
AST_STMT_EXT_ORG,
AST_STMT_EXT_SECTION,
} ASTKind;
typedef enum {
BINOP_ADD = 0,
BINOP_SUB = 1,
BINOP_BITWISE_AND = 2,
BINOP_BITWISE_OR = 3,
BINOP_BITWISE_XOR = 4,
BINOP_SIMPLES = 5,
BINOP_MUL = 5,
BINOP_DIV = 6,
BINOP_EQUAL = 7,
BINOP_NEQUAL = 8,
BINOP_WTF = 999,
} BinaryOp;
extern int BINOP_COMMUTATIVE[];
static inline int binop_is_comparison(BinaryOp op) {
return op == BINOP_EQUAL || op == BINOP_NEQUAL;
}
static inline BinaryOp binop_comp_opposite(BinaryOp op) {
if(op == BINOP_EQUAL) {
return BINOP_NEQUAL;
} else if(op == BINOP_NEQUAL) {
return BINOP_EQUAL;
}
return BINOP_WTF;
}
typedef enum {
UNOP_DEREF = 0,
UNOP_NEGATE = 1,
UNOP_BITWISE_NOT = 2,
UNOP_REF = 3,
} UnaryOp;
union AST;
typedef struct {
ASTKind nodeKind;
Type *type;
} ASTExpr;
typedef struct {
ASTExpr;
int val;
} ASTExprPrimitive;
typedef struct {
ASTExpr;
union AST *operands[2];
BinaryOp operator;
} ASTExprBinaryOp;
typedef struct {
ASTExpr;
UnaryOp operator;
union AST *operand;
} ASTExprUnaryOp;
typedef struct {
ASTExpr;
VarTableEntry *thing;
} ASTExprVar;
typedef struct {
ASTExpr;
union AST *what;
union AST **args;
} ASTExprCall;
typedef struct {
ASTExpr;
size_t length;
char *data;
} ASTExprStringLiteral;
typedef struct {
ASTKind nodeKind;
size_t size;
} ASTType;
typedef struct {
ASTType;
Token identifier;
} ASTTypeIdentifier;
typedef struct {
ASTType;
union AST *child;
int levels;
} ASTTypePointer;
typedef struct {
ASTKind nodeKind;
union AST *next;
} ASTStmt;
typedef struct {
ASTStmt;
VarTableEntry *thing;
union AST *expression;
} ASTStmtDecl;
typedef struct {
ASTKind nodeKind;
/* Flattened variable array for global register allocation */
size_t varCount;
VarTableEntry **vars;
union AST *statementFirst;
union AST *statementLast;
} ASTChunk;
typedef struct {
ASTStmt;
union AST *expression;
union AST *then;
} ASTStmtIf;
typedef struct {
ASTStmt;
union AST *body;
} ASTStmtLoop;
typedef struct {
ASTStmt;
} ASTStmtBreak;
typedef struct {
ASTStmt;
} ASTStmtContinue;
typedef struct {
ASTStmt;
union AST *expr;
} ASTStmtExpr;
typedef struct {
ASTStmt;
union AST *what;
union AST *to;
} ASTStmtAssign;
typedef struct {
ASTStmt;
int val;
} ASTStmtExtAlign;
typedef struct {
ASTExpr;
union AST *what;
Type *to;
char reinterpretation; /* 1 = as, 0 = to */
} ASTExprCast;
typedef struct {
ASTExpr;
union AST **items;
} ASTExprArray;
typedef struct {
ASTStmt;
size_t val;
} ASTStmtExtOrg;
typedef struct {
ASTStmt;
Token name;
} ASTStmtExtSection;
typedef union AST {
ASTKind nodeKind;
ASTChunk chunk;
ASTStmt statement;
ASTStmtDecl stmtDecl;
ASTStmtIf stmtIf;
ASTStmtLoop stmtLoop;
ASTStmtBreak stmtBreak;
ASTStmtContinue stmtContinue;
ASTStmtExpr stmtExpr;
ASTStmtAssign stmtAssign;
ASTExpr expression;
ASTExprPrimitive exprPrim;
ASTExprBinaryOp exprBinOp;
ASTExprUnaryOp exprUnOp;
ASTExprVar exprVar;
ASTExprCall exprCall;
ASTStmtExtAlign stmtExtAlign;
ASTExprStringLiteral exprStrLit;
ASTExprCast exprCast;
ASTExprArray exprArray;
ASTStmtExtOrg stmtExtOrg;
ASTStmtExtSection stmtExtSection;
} AST;
AST *ast_expression_optimize(AST*);
int ast_expression_equal(AST*, AST*);
int ast_stmt_is_after(const AST *chunk, const AST *s1, const AST *s2);
#endif

352
src/ast/ast.c Normal file
View 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;
}

411
src/ast/ast.h Normal file
View File

@@ -0,0 +1,411 @@
#ifndef NCTREF_AST_H
#define NCTREF_AST_H
#include"types.h"
#include"lexer.h"
#include"scope.h"
#pragma pack(push, 1)
#ifdef __GNUC__
#define ENUMPAK __attribute__((packed))
#else
#define ENUMPAK
#endif
#define GEN_ENUM(x) x,
#define GEN_STRI(x) #x,
#define AST_KINDS(K) \
K(AST_MISSING) \
K(AST_CHUNK) \
K(AST_STMT_DECL) \
K(AST_EXPR_PRIMITIVE) \
K(AST_STMT_IF) \
K(AST_EXPR_BINARY_OP) \
K(AST_EXPR_VAR) \
K(AST_EXPR_STACK_POINTER) \
K(AST_EXPR_UNARY_OP) \
K(AST_STMT_LOOP) \
K(AST_STMT_BREAK) \
K(AST_STMT_CONTINUE) \
K(AST_EXPR_CALL) \
K(AST_STMT_EXPR) \
K(AST_STMT_ASSIGN) \
K(AST_STMT_EXT_ALIGN) \
K(AST_EXPR_STRING_LITERAL) \
K(AST_EXPR_CAST) \
K(AST_EXPR_ARRAY) \
K(AST_EXPR_FUNC) \
K(AST_STMT_EXT_ORG) \
K(AST_SECTION) \
K(AST_STMT_RETURN) \
K(AST_EXPR_EXT_SALLOC) \
K(AST_EXPR_DOT) \
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[];
typedef enum ENUMPAK {
BINOP_ADD = 0,
BINOP_SUB = 1,
BINOP_BITWISE_AND = 2,
BINOP_BITWISE_OR = 3,
BINOP_BITWISE_XOR = 4,
BINOP_SIMPLES = 5,
BINOP_MUL = 5,
BINOP_DIV = 6,
BINOP_2OPS = 7,
BINOP_MULHI = 7,
BINOP_MOD = 8,
BINOP_EQUAL = 9,
BINOP_NEQUAL = 10,
BINOP_LESS = 11,
BINOP_GREATER = 12,
BINOP_LEQUAL = 13,
BINOP_GEQUAL = 14,
BINOP_LOGICAL_AND = 15,
BINOP_LOGICAL_OR = 16,
BINOP_DEREF = 17,
BINOP_WTF = 999,
} BinaryOp;
static inline int binop_is_comparison(BinaryOp op) {
return op == BINOP_EQUAL || op == BINOP_NEQUAL || op == BINOP_LESS || op == BINOP_GREATER || op == BINOP_LEQUAL || op == BINOP_GEQUAL;
}
static inline int binop_is_commutative(BinaryOp op) {
return op == BINOP_ADD || op == BINOP_MUL || op == BINOP_MULHI || op == BINOP_EQUAL || op == BINOP_NEQUAL || op == BINOP_BITWISE_AND || op == BINOP_BITWISE_OR || op == BINOP_BITWISE_XOR;
}
static inline BinaryOp binop_comp_opposite(BinaryOp op) {
if(op == BINOP_EQUAL) {
return BINOP_NEQUAL;
} else if(op == BINOP_NEQUAL) {
return BINOP_EQUAL;
} else if(op == BINOP_LESS) {
return BINOP_GEQUAL;
} else if(op == BINOP_GREATER) {
return BINOP_LEQUAL;
} else if(op == BINOP_LEQUAL) {
return BINOP_GREATER;
} else if(op == BINOP_GEQUAL) {
return BINOP_LESS;
}
return BINOP_WTF;
}
typedef enum ENUMPAK {
UNOP_NEGATE = 1,
UNOP_BITWISE_NOT = 2,
UNOP_REF = 3,
UNOP_NOT = 4,
} UnaryOp;
union AST;
typedef struct {
ASTKind nodeKind;
uint16_t row;
uint16_t col;
} ASTBase;
typedef struct {
ASTBase;
Type *type;
} ASTExpr;
typedef struct {
ASTExpr;
int val;
// If true, increase this literal during stack growths
bool stackGrowth;
} ASTExprPrimitive;
typedef struct {
ASTExpr;
union AST *operands[2];
BinaryOp operator;
} ASTExprBinaryOp;
typedef struct {
ASTExpr;
UnaryOp operator;
union AST *operand;
} ASTExprUnaryOp;
typedef struct {
ASTExpr;
ScopeItem *thing;
} ASTExprVar;
typedef struct {
ASTExpr;
union AST *what;
union AST **args;
} ASTExprCall;
typedef struct {
ASTExpr;
size_t length;
char *data;
} ASTExprStringLiteral;
typedef struct {
ASTExpr;
} ASTExprStackPointer;
typedef struct {
ASTExpr;
union AST *chunk;
// Necessary for when the parser jumps to a generic function
Scope *scope;
Token *rangeTokens;
size_t startTokI;
size_t endTokI;
} ASTExprFunc;
typedef struct {
ASTBase;
union AST *next;
// Used for reaching definition analysis
bool dirty;
ReachingDefs rd;
} ASTStmt;
typedef struct {
ASTStmt;
ScopeItem *thing;
union AST *expression;
} ASTStmtDecl;
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 */
size_t varCount;
ScopeItem **vars;
/* extern symbol array */
size_t externCount;
ScopeItem **externs;
union AST *statementFirst;
union AST *statementLast;
size_t stackReservation;
/* NULL unless this is a top-level chunk belonging to a function */
Type *functionType;
} ASTChunk;
typedef struct {
ASTStmt;
union AST *expression;
union AST *then;
union AST *elss;
} ASTStmtIf;
typedef struct {
ASTStmt;
union AST *body;
} ASTStmtLoop;
typedef struct {
ASTStmt;
} ASTStmtBreak;
typedef struct {
ASTStmt;
} ASTStmtContinue;
typedef struct {
ASTStmt;
union AST *expr;
} ASTStmtExpr;
typedef struct {
ASTStmt;
union AST *what;
union AST *to;
} ASTStmtAssign;
typedef struct {
ASTStmt;
int val;
} ASTStmtExtAlign;
typedef struct {
ASTExpr;
union AST *what;
Type *to;
char reinterpretation; /* 1 = as, 0 = to */
} ASTExprCast;
typedef struct {
ASTExpr;
union AST **items;
} ASTExprArray;
typedef struct {
ASTExpr;
Type *size;
} ASTExprExtSalloc;
typedef struct {
ASTExpr;
union AST *a;
const char *b;
} ASTExprDot;
typedef struct {
ASTStmt;
size_t val;
} ASTStmtExtOrg;
typedef struct {
ASTStmt;
union AST *val;
} ASTStmtReturn;
typedef struct {
ASTExpr;
// One of these will be NULL
union AST *ofExpr;
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;
ASTChunk chunk;
ASTStmt statement;
ASTStmtDecl stmtDecl;
ASTStmtIf stmtIf;
ASTStmtLoop stmtLoop;
ASTStmtBreak stmtBreak;
ASTStmtContinue stmtContinue;
ASTStmtExpr stmtExpr;
ASTStmtAssign stmtAssign;
ASTStmtReturn stmtReturn;
ASTExpr expression;
ASTExprPrimitive exprPrim;
ASTExprBinaryOp exprBinOp;
ASTExprUnaryOp exprUnOp;
ASTExprVar exprVar;
ASTExprCall exprCall;
ASTStmtExtAlign stmtExtAlign;
ASTExprStringLiteral exprStrLit;
ASTExprCast exprCast;
ASTExprArray exprArray;
ASTExprFunc exprFunc;
ASTExprDot exprDot;
ASTExprExtSalloc exprExtSalloc;
ASTStmtExtOrg stmtExtOrg;
ASTSection section;
ASTExprExtSizeOf exprExtSizeOf;
ASTStmtJump stmtJump;
ASTStmtLabel stmtLabel;
ASTExprNull exprNull;
} AST;
#pragma pack(pop)
typedef void(*GenericVisitorHandler)(AST**, AST*, AST*, AST*, AST*, void*);
void generic_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *tlc, void *ud, GenericVisitorHandler preHandler, GenericVisitorHandler postHandler);
int ast_expression_equal(AST*, AST*);
int ast_stmt_is_after(const AST *chunk, const AST *s1, const AST *s2);
AST *ast_deep_copy(AST*);
AST *ast_cast_expr(AST *what, Type *to);
void ast_typecheck(AST *tlc);
AST *ast_get_label_by_name(AST *tlc, const char *name);
bool ast_references_stack(AST *a);
bool ast_is_scopeitem_referenced(AST *tlc, ScopeItem *si);
// name becomes OWNED
ScopeItem *ast_tlc_new_var(AST *tlc, char *name, Type *itstype);
#include"dump.h"
#include"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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,4 @@
#pragma once
union AST;
void ast_linearize(union AST *tlc);

61
src/ast/scr.c Normal file
View 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
View 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
View 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
View File

@@ -0,0 +1,4 @@
#pragma once
union AST;
void ast_sroa(union AST *tlc);

102
src/ast/stack.c Normal file
View 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
View 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
View 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
View File

@@ -0,0 +1,4 @@
#pragma once
union AST;
void ast_usedef_reset(union AST *chu);

327
src/cg.c
View File

@@ -1,327 +0,0 @@
#include"cg.h"
#include<stdlib.h>
#include<signal.h>
#include<string.h>
#include<assert.h>
#define REGS 4
static const char *regs[REGS][3] = {{"al", "ax", "eax"}, {"bl", "bx", "ebx"}, {"cl", "cx", "ecx"}, {"dl", "dx", "edx"}, {"sil", "si", "esi"}, {"dil", "di", "edi"}};
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;
#define LOOPSTACKSIZE 64
static size_t loopStackStart[LOOPSTACKSIZE];
static size_t loopStackEnd[LOOPSTACKSIZE];
static size_t loopStackIdx;
static const char *direct(int size) {
switch(size) {
case 1: return "db";
case 2: return "dw";
case 4: return "dd";
case 8: return "dq";
}
abort();
}
static const char *spec(int size) {
switch(size) {
case 1: return "byte";
case 2: return "word";
case 4: return "dword";
case 8: return "qword";
}
abort();
}
static const char *specexpr(AST *e) {
return spec(type_size(e->expression.type));
}
static const char *xv(VarTableEntry *v) {
assert(v->kind == VARTABLEENTRY_VAR);
#define XVBUFS 8
#define XVBUFSZ 8
static char bufs[XVBUFS][XVBUFSZ];
static int bufidx = 0;
char *ret = bufs[bufidx];
#ifdef DEBUG
snprintf(ret, XVBUFSZ, "@%i", v->data.var.color);
#else
snprintf(ret, XVBUFSZ, "%s", regs[v->data.var.color][2]);
#endif
bufidx = (bufidx + 1) % XVBUFS;
return ret;
}
static const char *xj(BinaryOp op) {
switch(op) {
case BINOP_EQUAL: return "e";
case BINOP_NEQUAL: return "ne";
default: return "wtf";
}
}
static const char *xop(AST *e) {
#define XOPBUFS 16
#define XOPBUFSZ 24
static char bufs[XOPBUFS][XOPBUFSZ];
static int bufidx = 0;
char *ret = bufs[bufidx];
if(e->nodeKind == AST_EXPR_VAR) {
VarTableEntry *v = e->exprVar.thing;
if(v->kind == VARTABLEENTRY_VAR) {
return xv(v);
} else if(v->kind == VARTABLEENTRY_SYMBOL) {
snprintf(ret, XOPBUFSZ, "[%s]", v->data.symbol.name);
} else abort();
} else if(e->nodeKind == AST_EXPR_PRIMITIVE) {
snprintf(ret, XOPBUFSZ, "%i", e->exprPrim.val);
} 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 && e->exprUnOp.operand->exprBinOp.operands[0]->nodeKind == AST_EXPR_UNARY_OP && e->exprUnOp.operand->exprBinOp.operands[1]->nodeKind == AST_EXPR_VAR && e->exprUnOp.operand->exprBinOp.operands[0]->exprUnOp.operator == UNOP_REF && e->exprUnOp.operand->exprBinOp.operands[0]->exprUnOp.operand->nodeKind == AST_EXPR_VAR && e->exprUnOp.operand->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->kind == VARTABLEENTRY_SYMBOL && e->exprUnOp.operand->exprBinOp.operands[1]->exprVar.thing->kind == VARTABLEENTRY_VAR) {
snprintf(ret, XOPBUFSZ, "[%s + %s]",
e->exprUnOp.operand->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->data.symbol.name,
xv(e->exprUnOp.operand->exprBinOp.operands[1]->exprVar.thing));
} 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 && e->exprUnOp.operand->exprBinOp.operands[0]->nodeKind == AST_EXPR_UNARY_OP && e->exprUnOp.operand->exprBinOp.operands[1]->nodeKind == AST_EXPR_BINARY_OP && e->exprUnOp.operand->exprBinOp.operands[0]->exprUnOp.operator == UNOP_REF && e->exprUnOp.operand->exprBinOp.operands[0]->exprUnOp.operand->nodeKind == AST_EXPR_VAR && e->exprUnOp.operand->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->kind == VARTABLEENTRY_SYMBOL && e->exprUnOp.operand->exprBinOp.operands[1]->exprBinOp.operator == BINOP_MUL && e->exprUnOp.operand->exprBinOp.operands[1]->exprBinOp.operands[1]->nodeKind == AST_EXPR_VAR && e->exprUnOp.operand->exprBinOp.operands[1]->exprBinOp.operands[0]->nodeKind == AST_EXPR_PRIMITIVE && e->exprUnOp.operand->exprBinOp.operands[1]->exprBinOp.operands[1]->exprVar.thing->kind == VARTABLEENTRY_VAR) {
snprintf(ret, XOPBUFSZ, "[%s + %i * %s]",
e->exprUnOp.operand->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->data.symbol.name,
e->exprUnOp.operand->exprBinOp.operands[1]->exprBinOp.operands[0]->exprPrim.val,
xv(e->exprUnOp.operand->exprBinOp.operands[1]->exprBinOp.operands[1]->exprVar.thing));
} 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 == VARTABLEENTRY_SYMBOL) {
snprintf(ret, XOPBUFSZ, "%s", e->exprUnOp.operand->exprVar.thing->data.symbol.name);
} else if(e->nodeKind == AST_EXPR_UNARY_OP && e->exprUnOp.operator == UNOP_DEREF && e->exprUnOp.operand->nodeKind == AST_EXPR_VAR && e->exprUnOp.operand->exprVar.thing->kind == VARTABLEENTRY_VAR) {
snprintf(ret, XOPBUFSZ, "[%s]", xv(e->exprUnOp.operand->exprVar.thing));
} else {
return NULL;
}
bufidx = (bufidx + 1) % XOPBUFS;
return ret;
}
void cg_chunk(AST *a) {
AST *s = a->chunk.statementFirst;
// 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);
} else if(s->nodeKind == AST_STMT_EXT_ORG) {
printf("org %lu\n", s->stmtExtOrg.val);
} else if(s->nodeKind == AST_STMT_DECL && s->stmtDecl.thing->kind == VARTABLEENTRY_SYMBOL) {
VarTableEntry *v = s->stmtDecl.thing;
if(v->data.symbol.isExternal) {
printf("extern %s\n", v->data.symbol.name);
} else {
if(!v->data.symbol.isLocal) {
printf("global %s\n", v->data.symbol.name);
}
if(s->stmtDecl.expression) {
puts("A");
} else {
printf("%s resb %lu\n", v->data.symbol.name, type_size(s->stmtDecl.thing->type));
}
}
} else if(s->nodeKind == AST_STMT_ASSIGN) {
if(s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && ast_expression_equal(s->stmtAssign.what, s->stmtAssign.to->exprBinOp.operands[0]) && (s->stmtAssign.to->exprBinOp.operator == BINOP_ADD || s->stmtAssign.to->exprBinOp.operator == BINOP_SUB) && s->stmtAssign.to->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE && s->stmtAssign.to->exprBinOp.operands[1]->exprPrim.val == 1) {
// inc or dec
static const char *instrs[] = {"inc", "dec"};
printf("%s %s %s\n", instrs[s->stmtAssign.to->exprBinOp.operator == BINOP_SUB], specexpr(s->stmtAssign.what), xop(s->stmtAssign.what));
} 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 == VARTABLEENTRY_VAR && s->stmtAssign.to->exprBinOp.operands[1]->exprVar.thing->kind == VARTABLEENTRY_VAR) {
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));
} 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 == VARTABLEENTRY_SYMBOL && s->stmtAssign.to->exprBinOp.operands[1]->exprVar.thing->kind == VARTABLEENTRY_VAR) {
printf("lea %s, [%s + %s]\n",
xv(s->stmtAssign.what->exprVar.thing),
s->stmtAssign.to->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->data.symbol.name,
xv(s->stmtAssign.to->exprBinOp.operands[1]->exprVar.thing));
} else {
printf("mov %s, %s\n", xop(s->stmtAssign.what), xop(s->stmtAssign.to));
}
} else if(s->nodeKind == AST_STMT_LOOP) {
size_t lbl0 = nextLocalLabel++;
size_t lbl1 = nextLocalLabel++;
loopStackStart[loopStackIdx] = lbl0;
loopStackEnd[loopStackIdx] = lbl1;
loopStackIdx++;
printf(".L%lu:\n", lbl0);
cg_chunk(s->stmtLoop.body);
printf("jmp .L%lu\n", lbl0);
printf(".L%lu:\n", lbl1);
loopStackIdx--;
} else if(s->nodeKind == AST_STMT_BREAK) {
printf("jmp .L%lu\n", loopStackEnd[loopStackIdx - 1]);
} else if(s->nodeKind == AST_STMT_CONTINUE) {
printf("jmp .L%lu\n", loopStackStart[loopStackIdx - 1]);
} else if(s->nodeKind == AST_STMT_IF) {
assert(s->stmtIf.expression->nodeKind == AST_EXPR_BINARY_OP && binop_is_comparison(s->stmtIf.expression->exprBinOp.operator));
size_t lbl = nextLocalLabel++;
printf("cmp %s %s, %s\n", specexpr(s->stmtIf.expression->exprBinOp.operands[0]), xop(s->stmtIf.expression->exprBinOp.operands[0]), xop(s->stmtIf.expression->exprBinOp.operands[1]));
printf("j%s .L%lu\n", xj(binop_comp_opposite(s->stmtIf.expression->exprBinOp.operator)), lbl);
cg_chunk(s->stmtIf.then);
printf(".L%lu:\n", lbl);
} else if(s->nodeKind == AST_STMT_EXPR) {
AST *e = s->stmtExpr.expr;
if(e->nodeKind == AST_EXPR_CALL) {
puts("push eax");
puts("push ecx");
puts("push edx");
int argCount = e->exprCall.what->expression.type->function.argCount;
size_t argSize = 0;
for(int i = argCount - 1; i >= 0; i--) {
printf("push %s\n", xop(e->exprCall.args[i]));
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 == VARTABLEENTRY_SYMBOL);
printf("call %s\n", e->exprCall.what->exprVar.thing->data.symbol.name);
printf("add esp, %lu\n", argSize);
puts("pop edx");
puts("pop ecx");
puts("pop eax");
}
}
s = s->statement.next;
}
}
/* Welsh-Powell graph coloring */
static int comparator(const void *A, const void *B) {
VarTableEntry *const *a = A;
VarTableEntry *const *b = B;
return ((*a)->data.var.degree * (*a)->data.var.priority) - ((*b)->data.var.degree * (*b)->data.var.priority);
}
void cg_go(AST *a) {
typedef VarTableEntry *Adjacency[2];
size_t adjCount = 0;
Adjacency *adjs = malloc(sizeof(*adjs) * adjCount);
VarTableEntry **vars = a->chunk.vars;
for(size_t v1i = 0; v1i < a->chunk.varCount; v1i++) {
for(size_t v2i = 0; v2i < a->chunk.varCount; v2i++) {
if(v1i == v2i) continue;
VarTableEntry *v1 = vars[v1i];
VarTableEntry *v2 = vars[v2i];
/* 1D intersection test */
// if((v1->data.var.start >= v2->data.var.start && v1->data.var.start <= v2->data.var.end)
// || (v1->data.var.end >= v2->data.var.start && v1->data.var.end <= v2->data.var.end)) {
if(
(ast_stmt_is_after(a, v1->data.var.usedefFirst->stmt, v2->data.var.usedefFirst->stmt) == 1
&& ast_stmt_is_after(a, v2->data.var.usedefLast->stmt, v1->data.var.usedefFirst->stmt) == 1)
||
(ast_stmt_is_after(a, v1->data.var.usedefLast->stmt, v2->data.var.usedefFirst->stmt) == 1
&& ast_stmt_is_after(a, v2->data.var.usedefLast->stmt, v1->data.var.usedefLast->stmt) == 1)
) {
VarTableEntry *min = v1 < v2 ? v1 : v2;
VarTableEntry *max = v1 < v2 ? v2 : v1;
for(size_t a = 0; a < adjCount; a++) {
if(adjs[a][0] == min && adjs[a][1] == max) {
goto cont;
}
}
adjs = realloc(adjs, sizeof(*adjs) * ++adjCount);
adjs[adjCount - 1][0] = min;
adjs[adjCount - 1][1] = max;
cont:;
}
}
}
for(size_t a = 0; a < adjCount; a++) {
adjs[a][0]->data.var.degree++;
adjs[a][1]->data.var.degree++;
}
qsort(vars, a->chunk.varCount, sizeof(*vars), comparator);
/* Welsh plow my ass */
for(int v = 0; v < a->chunk.varCount; v++) {
for(int c = 0;; c++) {
for(int a = 0; a < adjCount; a++) {
if(adjs[a][0] == vars[v] && adjs[a][1]->data.var.color == c) {
goto nextColor;
} else if(adjs[a][1] == vars[v] && adjs[a][0]->data.var.color == c) {
goto nextColor;
}
}
vars[v]->data.var.color = c;
break;
nextColor:;
}
}
free(adjs);
cg_chunk(a);
free(vars);
}

View File

@@ -1,8 +0,0 @@
#ifndef H_CG
#define H_CG
#include"ast.h"
void cg_go(union AST*);
#endif

View File

@@ -1,141 +0,0 @@
#include"dstr.h"
#include<stdarg.h>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
static int ilen(int i) {
if(i == 0) return 1;
int ret = 0;
if(i < 0) {
ret = 1;
i = -i;
}
while(i > 0) {
ret++;
i = i / 10;
}
return ret;
}
static char *myitoa(int src) {
static char ret[12];
snprintf(ret, 12, "%i", src);
return ret;
}
typedef struct {
size_t length;
char data[];
} dstrInternal;
dstr dstrempty() {
dstrInternal *i = malloc(sizeof(dstrInternal) + 1);
i->length = 0;
i->data[0] = '\0';
return (dstr) i + sizeof(dstrInternal);
}
dstr dstrz(const char *src) {
size_t len = strlen(src);
dstrInternal *i = malloc(sizeof(dstrInternal) + len + 1);
i->length = len;
memcpy(i->data, src, len + 1);
return (dstr) i + sizeof(dstrInternal);
}
dstr dstrfmt(dstr original, const char *fmt, ...) {
dstrInternal *originalInternal = (dstrInternal*) (original - sizeof(dstrInternal));
const char *start = fmt;
va_list list;
va_start(list, fmt);
size_t totalLength = 0;
while(*fmt) {
if(*fmt == '%') {
switch(*++fmt) {
case 's':
totalLength += strlen(va_arg(list, char*));
break;
case 'c':
if(va_arg(list, int)) totalLength++;
break;
case 'S': {
dstrInternal *i = (dstrInternal*) (va_arg(list, dstr) - sizeof(dstrInternal));
totalLength += i->length;
break;
}
case 'i':
totalLength += ilen(va_arg(list, int));
break;
default: {
totalLength++;
}
}
} else totalLength++;
fmt++;
}
va_end(list);
fmt = start;
originalInternal = realloc(originalInternal, sizeof(dstrInternal) + originalInternal->length + totalLength + 1);
va_start(list, fmt);
char *dst = originalInternal->data + originalInternal->length;
originalInternal->length += totalLength;
originalInternal->data[originalInternal->length] = 0;
while(*fmt) {
if(*fmt == '%') {
switch(*++fmt) {
case 's': {
char *asdfasdf = va_arg(list, char*);
strcpy(dst, asdfasdf);
dst += strlen(asdfasdf);
break;
}
case 'c': {
int c = va_arg(list, int);
if(c) {
*(dst++) = c;
}
break;
}
case 'S': {
dstrInternal *i = (dstrInternal*) (va_arg(list, dstr) - sizeof(dstrInternal));
memcpy(dst, i->data, i->length);
dst += i->length;
break;
}
case 'i': {
const char *asdf = myitoa(va_arg(list, int));
strcpy(dst, asdf);
dst += strlen(asdf);
break;
}
default: {
*(dst++) = *fmt;
}
}
} else {
*(dst++) = *fmt;
}
fmt++;
}
va_end(list);
return (dstr) originalInternal + sizeof(dstrInternal);
}
void dstrfree(dstr s) {
free(s - sizeof(dstrInternal));
}

View File

@@ -1,15 +0,0 @@
#ifndef _DSTR_H
#define _DSTR_H
#include<stddef.h>
/* Originally used sds, but it didn't support OpenWatcom. This isn't as optimized, but it's good enough. */
typedef char *dstr;
dstr dstrempty();
dstr dstrraw(const char*);
dstr dstrfmt(dstr, const char*, ...);
void dstrfree(dstr);
#endif

View File

@@ -27,16 +27,32 @@ char *TOKEN_NAMES[] = {
"'break'",
"','",
"'&'",
"'&&'",
"'|'",
"'||'",
"'^'",
"'~'",
"'=='",
"'['",
"']'",
"'?'",
"string"
"string",
"'!='",
"'!'"
"'!'",
"'continue'",
"'return'",
"'->'",
"'<='",
"'>='",
"'<'",
"'>'",
"'*^'",
"'record'",
"'.'",
"'as'",
"'use'",
"'else'",
"'null'",
};
static int isAlpha(int c) {
@@ -55,7 +71,7 @@ static int isWS(int c) {
return c == ' ' || c == '\n' || c == '\r' || c == '\b' || c == '\t';
}
static size_t currentRow = 0;
static size_t currentRow = 1;
static size_t currentColumn = 0;
static int ungetted = EOF;
@@ -116,15 +132,31 @@ Token nct_tokenize(FILE *f) {
return tok;
} else if(c == '-') {
tok.type = TOKEN_MINUS;
int c = nextc(f);
if(c == '>') {
tok.type = TOKEN_ARROW;
} else ungetc(c, f);
return tok;
} else if(c == '*') {
tok.type = TOKEN_STAR;
int c = nextc(f);
if(c == '^') {
tok.type = TOKEN_STAR_CARET;
} else ungetc(c, 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;
@@ -148,6 +180,20 @@ Token nct_tokenize(FILE *f) {
tok.type = TOKEN_EXCLAMATION_EQUALS;
} else ungetc(c, f);
return tok;
} else if(c == '<') {
tok.type = TOKEN_LESS;
int c = nextc(f);
if(c == '=') {
tok.type = TOKEN_LEQUAL;
} else ungetc(c, f);
return tok;
} else if(c == '>') {
tok.type = TOKEN_GREATER;
int c = nextc(f);
if(c == '=') {
tok.type = TOKEN_GEQUAL;
} else ungetc(c, f);
return tok;
} else if(c == '/') {
int c = nextc(f);
if(c == '*') { /* This is a comment; skip. */
@@ -172,6 +218,9 @@ Token nct_tokenize(FILE *f) {
} else if(c == ',') {
tok.type = TOKEN_COMMA;
return tok;
} else if(c == '.') {
tok.type = TOKEN_DOT;
return tok;
} else if(c == '"') {
int capacity = 5;
char *content = malloc(capacity);
@@ -188,6 +237,7 @@ Token nct_tokenize(FILE *f) {
if(c == '0') c = 0;
else if(c == 'n') c = '\n';
else if(c == 'r') c = '\r';
else if(c == 't') c = '\t';
}
@@ -200,14 +250,14 @@ Token nct_tokenize(FILE *f) {
tok.content = content;
tok.length = i;
return tok;
} else if(isAlpha(c) || c == '@') {
} else if(isAlpha(c) || c == '@' || c == '_') {
int capacity = 5;
char *content = malloc(capacity);
size_t i = 0;
content[i++] = c;
while(c = nextc(f), (isAlphanum(c) || c == '@')) {
while(c = nextc(f), (isAlphanum(c) || c == '@' || c == '_')) {
if(i == capacity - 1) {
content = realloc(content, capacity += 4);
}
@@ -242,6 +292,30 @@ Token nct_tokenize(FILE *f) {
free(content);
tok.type = TOKEN_CONTINUE;
return tok;
} else if(!strcmp(content, "return")) {
free(content);
tok.type = TOKEN_RETURN;
return tok;
} else if(!strcmp(content, "record")) {
free(content);
tok.type = TOKEN_RECORD;
return tok;
} else if(!strcmp(content, "as")) {
free(content);
tok.type = TOKEN_AS;
return tok;
} else if(!strcmp(content, "use")) {
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;
@@ -271,7 +345,7 @@ Token nct_tokenize(FILE *f) {
while(c = nextc(f), (isNum(c) || (base > 10 && c >= 'A' && c < ('A' + base - 10)))) {
if(i == 31) {
stahp(1, 6180, "Numbers have a maximum size of 31.");
stahp_token(&tok, "Numbers have a maximum size of 31.");
}
content[i++] = c;
@@ -301,6 +375,9 @@ Token *nct_lex(FILE *f) {
size_t length = 8, index = 0;
Token *list = malloc(sizeof(*list) * length);
currentRow = 1;
currentColumn = 0;
while(1) {
list[index] = nct_tokenize(f);
@@ -318,3 +395,10 @@ Token *nct_lex(FILE *f) {
return NULL; /* Doesn't reach here. */
}
void nct_lex_free(Token *tokens) {
for(Token *t = tokens; t->type != TOKEN_EOF; t++) {
if(t->content) free(t->content);
}
free(tokens);
}

View File

@@ -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,
@@ -38,9 +40,22 @@ typedef enum {
TOKEN_EXCLAMATION_EQUALS,
TOKEN_EXCLAMATION,
TOKEN_CONTINUE,
TOKEN_RETURN,
TOKEN_ARROW,
TOKEN_LEQUAL,
TOKEN_GEQUAL,
TOKEN_LESS,
TOKEN_GREATER,
TOKEN_STAR_CARET,
TOKEN_RECORD,
TOKEN_DOT,
TOKEN_AS,
TOKEN_USE,
TOKEN_ELSE,
TOKEN_NULL,
} TokenKind;
typedef struct {
typedef struct Token {
TokenKind type;
int row, column;
@@ -50,5 +65,6 @@ typedef struct {
Token nct_tokenize(FILE*);
Token *nct_lex(FILE*);
void nct_lex_free(Token *);
#endif

View File

@@ -1,42 +1,115 @@
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<libgen.h>
#include"lexer.h"
#include"parse.h"
#include"ntc.h"
#include"reporting.h"
#include"cg.h"
#include"utils.h"
static int argc;
static char **argv;
const char* ntc_get_arg(const char *name) {
for(int i = 1; i < argc; i++) {
if(strstr(argv[i], name) == argv[i]) {
return argv[i] + strlen(name) + 1;
static const char **includePaths;
const char **ntc_get_import_paths() {
return (const char**) includePaths;
}
static const char *ntc_get_arg_from(size_t *i, const char *name) {
for(; *i < argc; (*i)++) {
if(strstr(argv[*i], name) == argv[*i]) {
return argv[*i] + strlen(name) + 1;
}
}
return NULL;
}
const char* ntc_get_arg(const char *name) {
size_t i = 1;
return ntc_get_arg_from(&i, name);
}
intmax_t ntc_get_int(const char *name) {
return ntc_get_int_default(name, 0);
}
intmax_t ntc_get_int_default(const char *name, intmax_t def) {
const char *val = ntc_get_arg(name);
if(!val) {
return def;
}
long result = 0;
if(!unstupid_strtol(val, (char**) &val, 0, &result)) {
return def;
}
return result;
}
int main(int argc_, char **argv_) {
argc = argc_;
argv = argv_;
if(!arch_verify_target()) {
stahp(0, 0, "Unknown architecture %s", ntc_get_arg("target"));
}
const char *in = ntc_get_arg("in");
size_t includePathsCount = 1;
includePaths = malloc(sizeof(*includePaths));
includePaths[0] = strdup(in ? dirname(strdup(in)) : ".");
for(size_t i = 1; i < argc; i++) {
const char *path = ntc_get_arg_from(&i, "inc");
if(!path) {
break;
}
includePaths = realloc(includePaths, sizeof(*includePaths) * (++includePathsCount));
includePaths[includePathsCount - 1] = path;
}
FILE *f = in ? fopen(in, "rb") : stdin;
if(!f) {
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);
cg_go(chunk);
if(ntc_get_int("pdbg")) {
char *astdump = ast_dump(sects);
fprintf(stderr, "### ORIGINAL ###\n%s\n", astdump);
free(astdump);
}
ast_segmented_dereference(sects);
ast_secondclass_record(sects);
ast_sroa(sects);
ast_linearize(sects);
arch_normalize_pre(sects);
arch_normalize(sects);
while(!cg_attempt_sections(sects)) {
arch_normalize(sects);
}
cg_go_sections(sects);
return 0;
}

View File

@@ -1,6 +1,24 @@
#ifndef NTC_H
#define NTC_H
#include<stddef.h>
#include<stdint.h>
#include<stdbool.h>
const char **ntc_get_import_paths();
const char* ntc_get_arg(const char *name);
#endif
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 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

View File

@@ -1,75 +0,0 @@
#include"optims.h"
#include<assert.h>
// Currently performs only copy propagation.
// But CP is NECESSARY, otherwise it creates too many variables
// that are unable to be coalesced by the regallocator
void optim_chunk(ASTChunk *chu) {
/*AST *s = chu->statementFirst, *sPrev = NULL;
while(s) {
if(s->nodeKind == AST_STMT_ASSIGN && s->stmtAssign.what->nodeKind == AST_EXPR_VAR && s->stmtAssign.to->nodeKind == AST_EXPR_VAR) {
VarTableEntry *dst = ((AST*) s->stmtAssign.what)->exprVar.thing;
VarTableEntry *src = ((AST*) s->stmtAssign.to)->exprVar.thing;
if(dst->kind == VARTABLEENTRY_VAR && src->kind == VARTABLEENTRY_VAR) {
// Find reaching source definition
UseDef *srcUD = src->data.var.usedefFirst;
while(srcUD && srcUD->use != s->stmtAssign.to) {
srcUD = srcUD->next;
}
if(!srcUD) {
goto copypropfail;
}
// Find first use of this def
UseDef *dstUDPrev = NULL;
UseDef *dstUD = dst->data.var.usedefFirst;
while(dstUD->def != s) {
dstUDPrev = dstUD;
dstUD = dstUD->next;
}
// Update all definitions
while(dstUD && dstUD->def == s) {
((AST*) dstUD->use)->exprVar.thing = src;
UseDef *next = dstUD->next;
dstUD->def = srcUD->def;
dstUD->next = srcUD->next;
srcUD->next = dstUD;
dstUD = next;
if(dstUDPrev) {
dstUDPrev->next = dstUD;
} else {
dst->data.var.usedefFirst = dstUD;
}
}
if(!dstUD) {
// dst was never used again -> DELETE ASSIGNMENT COMPLETELY
if(sPrev) {
sPrev->statement.next = s->statement.next;
// TODO: free
}
}
recalc_lifespan(dst);
recalc_lifespan(src);
}
}
copypropfail:
sPrev = s;
s = s->statement.next;
}*/
}

View File

@@ -1,5 +0,0 @@
#pragma once
#include"ast.h"
void optim_chunk(ASTChunk*);

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
#ifndef NCTREF_PARSE_H
#define NCTREF_PARSE_H
#include"ast.h"
#include"ast/ast.h"
AST *nct_parse(Token*);

View File

@@ -3,17 +3,44 @@
#include<stdarg.h>
#include<stdio.h>
#include<stdlib.h>
#include"ast/ast.h"
#include"lexer.h"
static void stahp_va(int row, int column, const char *error, va_list l) {
fprintf(stderr, "error %i:%i: ", row, column);
vfprintf(stderr, error, l);
fputc('\n', stderr);
}
/* Abort immediately on first error (for now) */
void stahp(int row, int column, const char *error, ...) {
va_list l;
va_start(l, error);
fprintf(stderr, "error %i:%i: ", row, column);
vfprintf(stderr, error, l);
fputc('\n', stderr);
stahp_va(row, column, error, l);
va_end(l);
exit(1);
}
}
void stahp_node(union AST *node, const char *error, ...) {
va_list l;
va_start(l, error);
stahp_va(node->row, node->col, error, l);
va_end(l);
exit(1);
}
void stahp_token(struct Token *tok, const char *error, ...) {
va_list l;
va_start(l, error);
stahp_va(tok->row, tok->column, error, l);
va_end(l);
exit(1);
}

View File

@@ -1,10 +1,15 @@
#ifndef NCTREF_REPORTING_H
#define NCTREF_REPORTING_H
#ifndef _GNUC
#ifndef __GNUC__
#define __attribute__(x)
#endif
union AST;
struct Token;
void __attribute__((noreturn)) stahp(int, int, const char*, ...);
void __attribute__((noreturn)) stahp_node(union AST*, const char*, ...);
void __attribute__((noreturn)) stahp_token(struct Token*, const char*, ...);
#endif

93
src/scope.c Normal file
View File

@@ -0,0 +1,93 @@
#include"scope.h"
#include"utils.h"
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<stdio.h>
#include"x86/arch.h"
Scope *scope_new(Scope *parent) {
Scope *ret = calloc(1, sizeof(*ret));
ret->parent = parent;
ret->count = 0;
ret->names = NULL;
ret->data = NULL;
return ret;
}
ScopeItem *scope_get(Scope *this, const char *name) {
for(size_t v = 0; v < this->count; v++) {
if(!strcmp(name, this->names[v])) return this->data[v];
}
return NULL;
}
ScopeItem *scope_find(Scope *this, const char *name) {
Scope *tbl = this;
while(tbl) {
ScopeItem *entry = scope_get(tbl, name);
if(entry) {
return entry;
}
tbl = tbl->parent;
}
return NULL;
}
ScopeItem *scope_set(Scope *this, const char *name, ScopeItem *e) {
name = strdup(name);
this->names = realloc(this->names, sizeof(*this->names) * (this->count + 1));
this->data = realloc(this->data, sizeof(*this->data) * (this->count + 1));
this->names[this->count] = name;
this->data[this->count] = e;
this->count++;
if(e->kind == SCOPEITEM_VAR) e->data.var.name = name;
e->owner = this;
return e;
}
ScopeItem *scope_find_int(Scope *scope, intmax_t val, const char *suffix) {
char buf[64];
snprintf(buf, sizeof(buf), "%li%s", val, suffix ? suffix : "");
return scope_find(scope, buf);
}
Scope *scope_merge(Scope *child) {
Scope *parent = child->parent;
parent->names = realloc(parent->names, sizeof(*parent->names) * (parent->count + child->count));
parent->data = realloc(parent->data, sizeof(*parent->data) * (parent->count + child->count));
for(size_t i = 0; i < child->count; i++) {
child->data[i]->owner = parent;
parent->names[parent->count] = child->names[i];
parent->data[parent->count] = child->data[i];
parent->count++;
}
//free(child->names);
//free(child->data);
//free(child);
return parent;
}
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;
}

105
src/scope.h Normal file
View File

@@ -0,0 +1,105 @@
#ifndef NCTREF_VARTABLE_H
#define NCTREF_VARTABLE_H
#include"types.h"
#include<stdbool.h>
struct Token;
union AST;
typedef enum {
SCOPEITEM_SYMBOL, SCOPEITEM_VAR, SCOPEITEM_TYPE, SCOPEITEM_CEXPR
} ScopeItemKind;
union AST;
typedef struct UseDef {
union AST *def; // assign stmt
union AST *use; // corresponding AST_EXPR_VAR
union AST *stmt; // whole using stmt
struct UseDef *next;
} UseDef;
typedef struct ReachingDefs {
size_t defCount;
union AST **defs;
} ReachingDefs;
struct Scope;
typedef struct ScopeItem {
Type *type;
struct Scope *owner;
ScopeItemKind kind;
union {
struct {
char isLocal;
char isExternal;
const char *name;
struct {
struct Scope *scope;
struct Token *rangeTokens;
size_t startTokI;
size_t endTokI;
} genfunc;
} symbol;
struct {
// For debugging
const char *name;
// Register allocation
// vars in loops have higher priority
// a more advanced approach would be to use weights for different colors (e.g. multipliers "should" be in eax)
uint8_t priority;
int16_t color, degree;
bool precolored, preclassed;
int registerClass;
// Used by ast_usedef_pass
//ReachingDefs *reachingDefs;
union AST *declaration;
UseDef *usedefFirst;
UseDef *usedefLast;
union AST *liveRangeStart;
union AST *liveRangeEnd;
} var;
struct {
Type *ptr;
} type;
struct {
// cexpr is used for expression parametization as opposed to type parametrization
// I don't like the idea of having a special ScopeItem kind for these, but all other places were worse
const char *paramName;
size_t paramIdx;
// If the cexpr has been parametrized (as opposed to just being a symbol), this field will be non-NULL
union AST *concrete;
} cexpr;
} data;
} ScopeItem;
typedef struct Scope {
struct Scope *parent;
size_t count;
const char **names;
ScopeItem **data;
} Scope;
Scope *scope_new(Scope*);
ScopeItem *scope_get(Scope*, const char*);
ScopeItem *scope_find(Scope*, const char*);
ScopeItem *scope_set(Scope*, const char*, ScopeItem*);
ScopeItem *scope_find_int(Scope*, intmax_t, const char*);
Scope *scope_merge(Scope *child);
void vte_precolor(ScopeItem *vte, int resclass, int color);
#endif

View File

@@ -4,6 +4,10 @@
#include<stdlib.h>
#include<string.h>
#include<stdint.h>
#include"ast/ast.h"
#include"reporting.h"
#include<assert.h>
#include"x86/arch.h"
#include"ntc.h"
@@ -20,9 +24,19 @@ Type *primitive_parse(const char *src) {
}
}
TypePrimitive *ret = malloc(sizeof(*ret));
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 = src;
ret->src = strdup(src);
if(*src == 'n') {
src++;
@@ -72,6 +86,11 @@ Type *primitive_parse(const char *src) {
ret->vector = 1;
}
if(*src) {
free(ret);
return NULL;
}
ret->next = primitiveDatabase[hash];
primitiveDatabase[hash] = ret;
@@ -91,9 +110,20 @@ size_t type_size(Type *t) {
return w;
} else if(t->type == TYPE_TYPE_POINTER || t->type == TYPE_TYPE_FUNCTION) {
return 4;
return arch_ptr_size();
} else if(t->type == TYPE_TYPE_ARRAY) {
return type_size(t->array.of) * t->array.length;
} else if(t->type == TYPE_TYPE_RECORD) {
size_t max = 0;
for(size_t f = 0; f < t->record.fieldCount; f++) {
size_t end = t->record.fieldOffsets[f] + type_size(t->record.fieldTypes[f]);
if(max < end) {
max = end;
}
}
return max;
}
abort();
@@ -116,7 +146,37 @@ int type_equal(Type *O, Type *T) {
} else if(O->type == TYPE_TYPE_POINTER) {
return type_equal(O->pointer.of, T->pointer.of);
} else if(O->type == TYPE_TYPE_ARRAY) {
return type_equal(O->array.of, T->array.of) && O->array.length == T->array.length;
if(!type_equal(O->array.of, T->array.of)) {
return 0;
}
if(O->array.lengthIsGeneric != T->array.lengthIsGeneric) {
return 0;
}
if(O->array.lengthIsGeneric) {
return O->array.lengthGenericParamIdx == T->array.lengthGenericParamIdx && !strcmp(O->array.lengthGenericParamName, T->array.lengthGenericParamName);
} else {
return O->array.length == T->array.length;
}
} else if(O->type == TYPE_TYPE_FUNCTION) {
if(!type_equal(O->function.ret, T->function.ret)) {
return 0;
}
if(O->function.argCount != T->function.argCount) {
return 0;
}
for(int i = 0; i < O->function.argCount; i++) {
if(!type_equal(O->function.args[i], T->function.args[i])) {
return 0;
}
// Don't compare argument names
}
return 1;
}
/* Consider nominal typing. */
@@ -126,12 +186,16 @@ int type_equal(Type *O, Type *T) {
/* TODO: cache */
Type *type_pointer_wrap(Type *t) {
TypePointer *ret = malloc(sizeof(*ret));
TypePointer *ret = calloc(1, sizeof(*ret));
ret->type = TYPE_TYPE_POINTER;
ret->of = t;
return (Type*) ret;
}
int type_is_number(Type *t) {
return t->type == TYPE_TYPE_POINTER || t->type == TYPE_TYPE_PRIMITIVE;
}
int type_is_castable(Type *from, Type *to) {
if(type_equal(from, to)) return 2;
@@ -147,9 +211,170 @@ int type_is_castable(Type *from, Type *to) {
}
}
if(from->type == TYPE_TYPE_PRIMITIVE && to->type == TYPE_TYPE_POINTER) {
if(from->type == TYPE_TYPE_PRIMITIVE && to->type == TYPE_TYPE_POINTER || from->type == TYPE_TYPE_POINTER && to->type == TYPE_TYPE_PRIMITIVE) {
return 2;
}
return 0;
}
}
bool type_is_generic(Type *t) {
if(t->type == TYPE_TYPE_GENERIC) {
return true;
} else if(t->type == TYPE_TYPE_FUNCTION) {
if(type_is_generic(t->function.ret)) {
return true;
}
for(int i = 0; i < t->function.argCount; i++) {
if(type_is_generic(t->function.args[i])) {
return true;
}
}
} else if(t->type == TYPE_TYPE_RECORD) {
for(int i = 0; i < t->record.fieldCount; i++) {
if(type_is_generic(t->record.fieldTypes[i])) {
return true;
}
}
} else if(t->type == TYPE_TYPE_POINTER) {
return type_is_generic(t->pointer.of);
} else if(t->type == TYPE_TYPE_ARRAY) {
return type_is_generic(t->array.of) || t->array.lengthIsGeneric;
}
return false;
}
Type *type_parametrize(Type *t, Scope *scope) {
if(t->type == TYPE_TYPE_RECORD) {
t = type_shallow_copy(t);
for(size_t f = 0; f < t->record.fieldCount; f++) {
t->record.fieldTypes[f] = type_parametrize(t->record.fieldTypes[f], scope);
}
if(!type_is_generic(t)) {
// Now that everything is concrete we may set the field offsets
size_t nextOffset = 0;
for(size_t f = 0; f < t->record.fieldCount; f++) {
t->record.fieldOffsets[f] = nextOffset;
nextOffset += type_size(t->record.fieldTypes[f]);
}
}
} else if(t->type == TYPE_TYPE_FUNCTION) {
t = type_shallow_copy(t);
t->function.ret = type_parametrize(t->function.ret, scope);
for(size_t i = 0; i < t->function.argCount; i++) {
t->function.args[i] = type_parametrize(t->function.args[i], scope);
}
} else if(t->type == TYPE_TYPE_GENERIC) {
ScopeItem *vte = scope_find_int(scope, t->generic.paramIdx, "t");
if(vte) {
assert(vte->kind == SCOPEITEM_TYPE);
return vte->data.type.ptr;
}
} else if(t->type == TYPE_TYPE_POINTER) {
t = type_shallow_copy(t);
t->pointer.of = type_parametrize(t->pointer.of, scope);
} else if(t->type == TYPE_TYPE_ARRAY) {
t = type_shallow_copy(t);
t->array.of = type_parametrize(t->array.of, scope);
if(t->array.lengthIsGeneric) {
ScopeItem *vte = scope_find_int(scope, t->array.lengthGenericParamIdx, "i");
if(vte) {
assert(vte->kind == SCOPEITEM_CEXPR);
AST *n = vte->data.cexpr.concrete;
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.");
}
}
}
}
return t;
}
Type *type_shallow_copy(Type *t) {
if(t->type == TYPE_TYPE_PRIMITIVE) {
Type *n = calloc(1, sizeof(TypePrimitive));
memcpy(n, t, sizeof(TypePrimitive));
return n;
} else if(t->type == TYPE_TYPE_POINTER) {
Type *n = calloc(1, sizeof(TypePointer));
memcpy(n, t, sizeof(TypePointer));
return n;
} else if(t->type == TYPE_TYPE_ARRAY) {
Type *n = calloc(1, sizeof(TypeArray));
memcpy(n, t, sizeof(TypeArray));
return n;
} else if(t->type == TYPE_TYPE_GENERIC) {
Type *n = calloc(1, sizeof(TypeGeneric));
memcpy(n, t, sizeof(TypeGeneric));
return n;
} else if(t->type == TYPE_TYPE_FUNCTION) {
Type *n = calloc(1, sizeof(TypeFunction));
n->type = TYPE_TYPE_FUNCTION;
n->function.ret = t->function.ret;
n->function.argCount = t->function.argCount;
n->function.argNames = calloc(n->function.argCount, sizeof(*n->function.argNames));
memcpy(n->function.argNames, t->function.argNames, n->function.argCount * sizeof(*n->function.argNames));
n->function.args = calloc(n->function.argCount, sizeof(*n->function.args));
memcpy(n->function.args, t->function.args, n->function.argCount * sizeof(*n->function.args));
return n;
} else if(t->type == TYPE_TYPE_RECORD) {
Type *n = calloc(1, sizeof(TypeRecord));
n->type = TYPE_TYPE_RECORD;
n->record.name = strdup(t->record.name);
n->record.fieldCount = t->record.fieldCount;
n->record.fieldNames = calloc(n->record.fieldCount, sizeof(*n->record.fieldNames));
memcpy(n->record.fieldNames, t->record.fieldNames, n->record.fieldCount * sizeof(*n->record.fieldNames));
n->record.fieldTypes = calloc(n->record.fieldCount, sizeof(*n->record.fieldTypes));
memcpy(n->record.fieldTypes, t->record.fieldTypes, n->record.fieldCount * sizeof(*n->record.fieldTypes));
n->record.fieldOffsets = calloc(n->record.fieldCount, sizeof(*n->record.fieldOffsets));
memcpy(n->record.fieldOffsets, t->record.fieldOffsets, n->record.fieldCount * sizeof(*n->record.fieldOffsets));
return n;
}
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;
}

View File

@@ -3,9 +3,14 @@
#include<stddef.h>
#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_COMPOUND, TYPE_TYPE_POINTER, TYPE_TYPE_FUNCTION, TYPE_TYPE_ARRAY, TYPE_TYPE_ERROR
TYPE_TYPE_PRIMITIVE, TYPE_TYPE_RECORD, TYPE_TYPE_POINTER, TYPE_TYPE_FUNCTION, TYPE_TYPE_ARRAY, TYPE_TYPE_GENERIC, TYPE_TYPE_ERROR
} TypeType;
union Type;
@@ -39,6 +44,7 @@ typedef struct TypeFunction {
union Type *ret;
char **argNames;
union Type **args;
size_t argCount;
} TypeFunction;
@@ -47,9 +53,32 @@ typedef struct TypeArray {
TypeType type;
union Type *of;
size_t length; /* 0 means unknown */
intmax_t length;
bool lengthIsGeneric;
char *lengthGenericParamName;
size_t lengthGenericParamIdx;
} TypeArray;
typedef struct TypeRecord {
TypeType type;
char *name;
union Type **fieldTypes;
size_t *fieldOffsets;
char **fieldNames;
size_t fieldCount;
} TypeRecord;
typedef struct TypeGeneric {
TypeType type;
char *paramName;
size_t paramIdx;
} TypeGeneric;
typedef union Type {
TypeType type;
@@ -57,11 +86,14 @@ typedef union Type {
TypePointer pointer;
TypeFunction function;
TypeArray array;
TypeRecord record;
TypeGeneric generic;
} Type;
extern Type TYPE_ERROR;
Type *primitive_parse(const char*);
Type *primitive_make(uint16_t width);
size_t type_size(Type*);
int type_equal(Type*, Type*);
@@ -70,4 +102,48 @@ Type *type_pointer_wrap(Type*);
/* 0 = not castable, 1 = explicitly castable, 2 = implicitly castable */
int type_is_castable(Type *from, Type *to);
int type_is_number(Type *t);
char *type_to_string(Type*);
struct Scope;
Type *type_parametrize(Type *target, struct Scope *scope);
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

View File

@@ -2,6 +2,12 @@
#define NCTREF_UTILS_H
#include<stddef.h>
#include<stdbool.h>
#include<errno.h>
#include<stdlib.h>
#include<stdarg.h>
#include<stdio.h>
#include <string.h>
inline static size_t djb2(const char *str) {
size_t hash = 5381;
@@ -14,4 +20,46 @@ inline static size_t djb2(const char *str) {
return hash;
}
inline static bool unstupid_strtol(const char *str, char **endptr, int base, long *result) {
errno = 0;
char *endptr2 = NULL;
*result = strtol(str, &endptr2, base);
if(endptr2 == str) {
return false;
}
if(errno == ERANGE) {
return false;
}
if(endptr) {
*endptr = endptr2;
}
return true;
}
__attribute__((format(printf, 1, 2))) static inline char *malp(const char *fmt, ...) {
va_list v1, v2;
va_start(v1, fmt);
va_copy(v2, v1);
size_t len = vsnprintf(NULL, 0, fmt, v1);
va_end(v1);
va_start(v2, fmt);
char *str = malloc(len + 1);
vsnprintf(str, len + 1, fmt, v2);
str[len] = 0;
va_end(v2);
return str;
}
inline static int is_str_equal_check_null(const char *s1, const char *s2) {
if (!s1 || !s2) {
return 0;
}
return !strcmp(s1, s2);
}
#endif

View File

@@ -1,92 +0,0 @@
#include"vartable.h"
#include"utils.h"
#include<stdlib.h>
#include<string.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;
}
VarTable *vartable_new(VarTable *parent) {
VarTable *ret = malloc(sizeof(*ret));
ret->parent = parent;
ret->count = 0;
ret->names = NULL;
ret->data = NULL;
return ret;
}
VarTableEntry *vartable_get(VarTable *this, const char *name) {
for(size_t v = 0; v < this->count; v++) {
if(!strcmp(name, this->names[v])) return this->data[v];
}
return NULL;
}
VarTableEntry *vartable_find(VarTable *this, const char *name) {
VarTable *tbl = this;
while(tbl) {
VarTableEntry *entry = vartable_get(tbl, name);
if(entry) {
return entry;
}
tbl = tbl->parent;
}
return NULL;
}
VarTableEntry *vartable_set(VarTable *this, const char *name, VarTableEntry *e) {
this->names = realloc(this->names, sizeof(*this->names) * (this->count + 1));
this->data = realloc(this->data, sizeof(*this->data) * (this->count + 1));
this->names[this->count] = name;
this->data[this->count] = e;
this->count++;
if(e->kind == VARTABLEENTRY_VAR) e->data.var.name = name;
return e;
}
void vartable_new_reachingdefs_for_all_vars(VarTable *this) {
for(size_t i = 0; i < this->count; i++) {
if(this->data[i]->kind == VARTABLEENTRY_VAR) {
this->data[i]->data.var.reachingDefs = reachingdefs_push(this->data[i]->data.var.reachingDefs);
}
}
if(this->parent) {
vartable_new_reachingdefs_for_all_vars(this->parent);
}
}
void vartable_coalesce_reachingdefs_for_all_vars(VarTable *this) {
for(size_t i = 0; i < this->count; i++) {
if(this->data[i]->kind == VARTABLEENTRY_VAR) {
this->data[i]->data.var.reachingDefs = reachingdefs_coalesce(this->data[i]->data.var.reachingDefs);
}
}
if(this->parent) {
vartable_coalesce_reachingdefs_for_all_vars(this->parent);
}
}

View File

@@ -1,81 +0,0 @@
#ifndef NCTREF_VARTABLE_H
#define NCTREF_VARTABLE_H
#include"types.h"
typedef enum {
VARTABLEENTRY_SYMBOL, VARTABLEENTRY_TYPE, VARTABLEENTRY_VAR
} VarTableEntryKind;
union AST;
typedef struct UseDef {
union AST *def; // assign stmt
union AST *use; // corresponding AST_EXPR_VAR
union AST *stmt; // whole using stmt
struct UseDef *next;
} UseDef;
// Stack, necessary for "possible reaching defs" such as from if statements
typedef struct ReachingDefs {
size_t defCount;
union AST **defs;
struct ReachingDefs *parent;
} ReachingDefs;
struct ReachingDefs *reachingdefs_push(struct ReachingDefs*);
struct ReachingDefs *reachingdefs_coalesce(struct ReachingDefs*);
void reachingdefs_set(struct ReachingDefs*, union AST*);
typedef struct VarTableEntry {
Type *type;
VarTableEntryKind kind;
struct {
union {
struct {
char isLocal;
char isExternal;
const char *name;
} symbol;
struct {
// For debugging
const char *name;
// Register allocation
// vars in loops have higher priority
// a more advanced approach would be to use weights for different colors (e.g. vars for mul "should" be in eax)
uint8_t priority;
uint16_t color, degree;
// Used during parsing
ReachingDefs *reachingDefs;
// Optimizations
UseDef *usedefFirst;
UseDef *usedefLast;
} var;
};
} data;
} VarTableEntry;
typedef struct VarTable {
struct VarTable *parent;
size_t count;
const char **names;
VarTableEntry **data;
} VarTable;
VarTable *vartable_new(VarTable*);
VarTableEntry *vartable_get(VarTable*, const char*);
VarTableEntry *vartable_find(VarTable*, const char*);
VarTableEntry *vartable_set(VarTable*, const char*, VarTableEntry*);
void vartable_new_reachingdefs_for_all_vars(VarTable*);
void vartable_coalesce_reachingdefs_for_all_vars(VarTable*);
#endif

62
src/x86/arch.c Normal file
View File

@@ -0,0 +1,62 @@
#include"arch.h"
RegisterClass REG_CLASSES[] = {
[REG_CLASS_8] = {
.rMask = HWR_GPR, .w = 1, .p = 8,
.rs = {HWR_AL, HWR_AH, HWR_BL, HWR_BH, HWR_CL, HWR_CH, HWR_DL, HWR_DH},
.rsN = {"al", "ah", "bl", "bh", "cl", "ch", "dl", "dh"},
.rsS = {1, 1, 1, 1, 1, 1, 1, 1},
},
[REG_CLASS_NOT_8] = {
.rMask = HWR_GPR, .w = 2, .p = 4,
.rs = {HWR_AX, HWR_EAX, HWR_BX, HWR_EBX, HWR_CX, HWR_ECX, HWR_DX, HWR_EDX, HWR_DI, HWR_EDI, HWR_SI, HWR_ESI, HWR_BP, HWR_EBP},
.rsN = {"ax", "eax", "bx", "ebx", "cx", "ecx", "dx", "edx", "di", "edi", "si", "esi", "bp", "ebp"},
.rsS = {2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4},
},
[REG_CLASS_16_32] = {
.rMask = HWR_GPR, .w = 2, .p = 4,
.rs = {HWR_AX, HWR_EAX, HWR_BX, HWR_EBX, HWR_CX, HWR_ECX, HWR_DX, HWR_EDX, HWR_BP, HWR_EBP},
.rsN = {"ax", "eax", "bx", "ebx", "cx", "ecx", "dx", "edx", "bp", "ebp"},
.rsS = {2, 4, 2, 4, 2, 4, 2, 4, 2, 4},
},
[REG_CLASS_IND] = {
.rMask = HWR_IND, .w = 2, .p = 2,
.rs = {HWR_DI, HWR_EDI, HWR_SI, HWR_ESI},
.rsN = {"di", "edi", "si", "esi"},
.rsS = {2, 4, 2, 4},
},
[REG_CLASS_IA16_PTRS] = {
.rMask = HWR_IND | HWR_BX,
.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,
.rs = {HWR_DS, HWR_ES, HWR_FS, HWR_GS},
.rsN = {"ds", "es", "fs", "gs"},
.rsS = {2, 2, 2, 2},
},
};
bool arch_verify_target() {
if(x86_target() == IUNKNOWN86) {
return false;
}
return true;
}
int arch_ptr_size() {
return x86_ia16() ? 2 : 4;
}
void arch_init() {
}

364
src/x86/arch.h Normal file
View File

@@ -0,0 +1,364 @@
#pragma once
#include<string.h>
#include"ntc.h"
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>
#include"ast/ast.h"
#define HWR_AL 1
#define HWR_AH 2
#define HWR_BL 4
#define HWR_BH 8
#define HWR_CL 16
#define HWR_CH 32
#define HWR_DL 64
#define HWR_DH 128
#define HWR_DI 256
#define HWR_SI 512
#define HWR_BP 1024
#define HWR_DS 2048
#define HWR_ES 4096
#define HWR_FS 8192
#define HWR_GS 16384
#define HWR_AX (HWR_AL | HWR_AH)
#define HWR_BX (HWR_BL | HWR_BH)
#define HWR_CX (HWR_CL | HWR_CH)
#define HWR_DX (HWR_DL | HWR_DH)
#define HWR_EAX HWR_AX
#define HWR_EBX HWR_BX
#define HWR_ECX HWR_CX
#define HWR_EDX HWR_DX
#define HWR_EDI HWR_DI
#define HWR_ESI HWR_SI
#define HWR_EBP HWR_BP
#define HWR_GPR (HWR_EAX | HWR_EBX | HWR_ECX | HWR_EDX | HWR_EDI | HWR_ESI | HWR_EBP)
#define HWR_IND (HWR_EDI | HWR_ESI)
#define HWR_ALL (HWR_GPR | HWR_IND)
#define HWR_SEGREGS (HWR_DS | HWR_ES | HWR_FS | HWR_GS)
#define MAX_REGS_PER_CLASS 16
typedef struct RegisterClass {
uint32_t rMask;
uint16_t w;
uint16_t p;
uint32_t rs[MAX_REGS_PER_CLASS];
const char *rsN[MAX_REGS_PER_CLASS];
uint8_t rsS[MAX_REGS_PER_CLASS];
} RegisterClass;
#define REG_CLASS_8 0
#define REG_CLASS_NOT_8 1
#define REG_CLASS_16_32 2
#define REG_CLASS_IND 3
#define REG_CLASS_IA16_PTRS 4
#define REG_CLASS_IA16_USEABLE 5
#define REG_CLASS_DATASEGS 6
extern RegisterClass REG_CLASSES[];
typedef enum {
I086 = 0,
I186 = 1,
I286 = 2,
I386 = 3,
IUNKNOWN86 = 255,
} X86Target;
static inline X86Target x86_target() {
const char *str = ntc_get_arg("target");
if(!str) return I386;
if(!strcmp(str, "086")) {
return I086;
}
if(!strcmp(str, "186")) {
return I186;
}
if(!strcmp(str, "286")) {
return I286;
}
if(!strcmp(str, "386")) {
return I386;
}
return IUNKNOWN86;
}
static inline bool x86_ia16() {
return x86_target() < I386;
}
static inline size_t x86_max_gpr_size() {
switch(x86_target()) {
case I086:
case I186:
case I286:
return 2;
case I386:
return 4;
default: abort();
}
}
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
#define XOP_MEM 2
static inline int is_xop(AST *e) {
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;
}
if(e->nodeKind == AST_EXPR_PRIMITIVE) {
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_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_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_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;
} else if(
(c->nodeKind == AST_EXPR_BINARY_OP && c->exprBinOp.operator == BINOP_ADD && c->exprBinOp.operands[0]->nodeKind == AST_EXPR_UNARY_OP && c->exprBinOp.operands[0]->exprUnOp.operator == UNOP_REF && c->exprBinOp.operands[0]->exprUnOp.operand->nodeKind == AST_EXPR_VAR) ||
(c->nodeKind == AST_EXPR_BINARY_OP && c->exprBinOp.operator == BINOP_ADD && c->exprBinOp.operands[0]->nodeKind == AST_EXPR_VAR)) {
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) {
return XOP_MEM;
}
}
}
} else if(e->nodeKind == AST_EXPR_STACK_POINTER) {
return XOP_NOT_MEM;
}
return XOP_NOT_XOP;
}
typedef enum {
NOT_AT_ALL_IT,
DEST_IS_BAD_XOP,
SRC0_IS_BAD_XOP,
SRC1_IS_BAD_XOP,
DEST_NOT_PRECOLORED,
SRC0_NOT_PRECOLORED,
SRC1_NOT_PRECOLORED,
DEST_NOT_SRC0,
MISSING_MULHI_FOR_MUL,
GUCCI,
} WhysItBad_Huh;
static inline WhysItBad_Huh x86_test_mul_matching_mulhi(AST *mulhi, AST *mul) {
assert(mulhi->statement.next == mul);
if(mulhi->stmtAssign.to->nodeKind != AST_EXPR_BINARY_OP) {
return NOT_AT_ALL_IT;
}
if(mulhi->stmtAssign.to->exprBinOp.operator != BINOP_MULHI) {
return NOT_AT_ALL_IT;
}
if(!ast_expression_equal(mulhi->stmtAssign.to->exprBinOp.operands[0], mul->stmtAssign.to->exprBinOp.operands[0])) {
return NOT_AT_ALL_IT;
}
if(!ast_expression_equal(mulhi->stmtAssign.to->exprBinOp.operands[1], mul->stmtAssign.to->exprBinOp.operands[1])) {
return NOT_AT_ALL_IT;
}
if(is_xop(mulhi->stmtAssign.what) != XOP_NOT_MEM) {
return DEST_IS_BAD_XOP;
}
return GUCCI;
}
static inline WhysItBad_Huh x86_test_mul(AST *stmtPrev, AST *stmt) {
if(stmt->nodeKind != AST_STMT_ASSIGN) {
return NOT_AT_ALL_IT;
}
if(stmt->stmtAssign.to->nodeKind != AST_EXPR_BINARY_OP) {
return NOT_AT_ALL_IT;
}
if(stmt->stmtAssign.to->exprBinOp.operator != BINOP_MUL) {
return NOT_AT_ALL_IT;
}
if(stmt->stmtAssign.what->nodeKind != AST_EXPR_VAR || stmt->stmtAssign.what->exprVar.thing->kind != SCOPEITEM_VAR) {
return DEST_IS_BAD_XOP;
}
if(x86_imul_supported()) {
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) {
return SRC1_IS_BAD_XOP;
}
/*if(stmt->stmtAssign.to->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE && x86_get_target() < I186) {
return SRC1_IS_BAD_XOP;
}*/
if(!ast_expression_equal(stmt->stmtAssign.what, stmt->stmtAssign.to->exprBinOp.operands[0])) {
return DEST_NOT_SRC0;
}
if(!stmt->stmtAssign.what->exprVar.thing->data.var.precolored) {
return DEST_NOT_PRECOLORED;
}
if(x86_test_mul_matching_mulhi(stmtPrev, stmt) != GUCCI) {
return MISSING_MULHI_FOR_MUL;
}
return GUCCI;
}
static inline void arch_add_hidden_variables(Scope *scope) {
/*if(!scope_find(scope, "@seg_ds")) {
ScopeItem *si = calloc(1, sizeof(*si));
si->type = primitive_parse("u16");
si->kind = SCOPEITEM_VAR;
si->data.var.preclassed = true;
si->data.var.registerClass = REG_CLASS_DATASEGS;
si->data.var.precolored = true;
si->data.var.color = 0;
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();

942
src/x86/cg.c Normal file
View File

@@ -0,0 +1,942 @@
#include<stdlib.h>
#include<signal.h>
#include<string.h>
#include<assert.h>
#include"ntc.h"
#include"reporting.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;*/
struct CalleeSavedState {
const char *reg[MAX_REGS_PER_CLASS];
size_t stackOffset[MAX_REGS_PER_CLASS];
};
typedef struct {
/*#define LOOPSTACKSIZE 96
size_t loopStackStart[LOOPSTACKSIZE];
size_t loopStackEnd[LOOPSTACKSIZE];
size_t loopStackIdx;*/
int isFunction;
AST *tlc;
struct CalleeSavedState calleeSaved;
} CGState;
static const char *direct(int size) {
switch(size) {
case 0:
case 1: return "db";
case 2: return "dw";
case 4: return "dd";
case 8: return "dq";
}
abort();
}
static const char *spec(int size) {
switch(size) {
case 0:
case 1: return "byte";
case 2: return "word";
case 4: return "dword";
case 8: return "qword";
}
abort();
}
/*static int log_size(int size) {
switch(size) {
case 0:
case 1: return 0;
case 2: return 1;
case 4: return 2;
case 8: return 3;
}
abort();
}
static const char *specexpr(AST *e) {
return spec(type_size(e->expression.type));
}*/
static const char *xv_sz(ScopeItem *v, int sz) {
assert(v->kind == SCOPEITEM_VAR);
if(sz == 0) sz = arch_ptr_size();
#define XVBUFS 8
#define XVBUFSZ 16
static char bufs[XVBUFS][XVBUFSZ];
static int bufidx = 0;
char *ret = bufs[bufidx];
if(ntc_get_int("rcd")) {
snprintf(ret, XVBUFSZ, "%i@%i", v->data.var.registerClass, v->data.var.color);
} else {
int cls = v->data.var.registerClass, reg = v->data.var.color;
if(type_size(v->type) != sz) {
if(sz == x86_max_gpr_size()) {
reg_cast_to_gpr(&cls, &reg);
} else abort();
}
snprintf(ret, XVBUFSZ, "%s", REG_CLASSES[cls].rsN[reg]);
}
bufidx = (bufidx + 1) % XVBUFS;
return ret;
}
static const char *xv(ScopeItem *v) {
return xv_sz(v, type_size(v->type));
}
static const char *xj(BinaryOp op) {
switch(op) {
case BINOP_EQUAL: return "e";
case BINOP_NEQUAL: return "ne";
case BINOP_LESS: return "b";
case BINOP_GREATER: return "a";
case BINOP_LEQUAL: return "be";
case BINOP_GEQUAL: return "ae";
default: abort(); return NULL;
}
}
static AST *is_field_access(AST *e) {
if(e->nodeKind != AST_EXPR_BINARY_OP || e->exprUnOp.operator != BINOP_DEREF) {
return NULL;
}
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;
}
if(e->nodeKind == AST_EXPR_BINARY_OP && e->exprBinOp.operator == BINOP_ADD && e->exprBinOp.operands[0]->nodeKind == AST_EXPR_UNARY_OP && e->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE && e->exprBinOp.operands[0]->exprUnOp.operator == UNOP_REF && e->exprBinOp.operands[0]->exprUnOp.operand->nodeKind == AST_EXPR_VAR && e->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->kind == SCOPEITEM_SYMBOL) {
return e;
}
if(e->nodeKind == AST_EXPR_BINARY_OP && e->exprBinOp.operator == BINOP_ADD && e->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE && e->exprBinOp.operands[0]->nodeKind == AST_EXPR_VAR && e->exprBinOp.operands[0]->exprVar.thing->kind == SCOPEITEM_VAR) {
return 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 64
static char bufs[XOPBUFS][XOPBUFSZ];
static int bufidx = 0;
char *ret = bufs[bufidx];
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) {
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) {
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)) {
e = is_field_access(e);
if(e->exprBinOp.operands[0]->nodeKind == AST_EXPR_UNARY_OP) {
assert(e->exprBinOp.operands[0]->exprUnOp.operator == UNOP_REF);
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 {
assert(e->exprBinOp.operands[0]->nodeKind == AST_EXPR_VAR);
ScopeItem *vte = e->exprBinOp.operands[0]->exprVar.thing;
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) {
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) {
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) {
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) {
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) {
pr = snprintf(ret, XOPBUFSZ, "%s [%s]", spec(sz), v->data.symbol.name);
} else abort();
} else if(e->nodeKind == AST_EXPR_PRIMITIVE) {
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) {
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;
}
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);
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) {
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_LABEL) {
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) {
uint32_t val = s->stmtExtAlign.val;
if((val & (val - 1))) {
// nasm does not support non-PoT alignments, so pad manually
printf("times ($ - $$ + %u) / %u * %u - ($ - $$) db 0\n", val - 1, val, val);
} else {
printf("align %u\n", val);
}
} else if(s->nodeKind == AST_STMT_DECL) {
ScopeItem *v = s->stmtDecl.thing;
if(v->kind == SCOPEITEM_SYMBOL) {
if(v->data.symbol.isExternal) {
// Do nothing.
// All external symbols are handled at once in the top-level chunk.
//printf("extern %s\n", v->data.symbol.name);
} else {
if(!v->data.symbol.isLocal) {
printf("global %s\n", v->data.symbol.name);
}
if(s->stmtDecl.expression) {
printf("%s:", v->data.symbol.name);
if(v->type->type == TYPE_TYPE_PRIMITIVE) {
assert(s->stmtDecl.expression->nodeKind == AST_EXPR_PRIMITIVE);
printf("%s %i", direct(type_size(v->type)), s->stmtDecl.expression->exprPrim.val);
} else if(v->type->type == TYPE_TYPE_ARRAY && v->type->array.of->type == TYPE_TYPE_PRIMITIVE) {
printf("%s ", direct(type_size(v->type->array.of)));
for(size_t i = 0; i < v->type->array.length; i++) {
printf("%i,", s->stmtDecl.expression->exprArray.items[i]->exprPrim.val);
}
} else if(v->type->type == TYPE_TYPE_FUNCTION) {
putchar('\n');
assert(s->stmtDecl.expression->nodeKind == AST_EXPR_FUNC);
// Generic functions have non-NULL code blocks
if(!type_is_generic(s->stmtDecl.expression->expression.type)) {
ast_segmented_dereference(s->stmtDecl.expression->exprFunc.chunk);
ast_sroa(s->stmtDecl.expression->exprFunc.chunk);
ast_linearize(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();
putchar('\n');
} else {
printf("%s resb %lu\n", v->data.symbol.name, type_size(s->stmtDecl.thing->type));
}
}
} else if(v->kind == SCOPEITEM_VAR) {
assert(s->stmtDecl.expression == NULL);
}
} else if(s->nodeKind == AST_STMT_ASSIGN && s->stmtAssign.to && s->stmtAssign.what->nodeKind == AST_EXPR_VAR && s->stmtAssign.what->exprVar.thing->kind == SCOPEITEM_VAR && s->stmtAssign.to->nodeKind == AST_EXPR_CALL) {
AST *e = s->stmtAssign.to;
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->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], arch_gpr_size()));
argSize += (type_size(e->exprCall.args[i]->expression.type) + 3) & ~3;
}
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));
}
if(argSize) {
if(x86_ia16()) {
printf("add sp, %lu\n", argSize);
} else {
printf("add esp, %lu\n", argSize);
}
}
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) {
if(s->stmtAssign.to && is_xop(s->stmtAssign.what) == XOP_NOT_MEM && is_xop(s->stmtAssign.to) == XOP_NOT_MEM && is_str_equal_check_null(xop(cg->tlc, s->stmtAssign.what), xop(cg->tlc, s->stmtAssign.to))) {
// It's a noop
} else if(s->stmtAssign.to) {
if(x86_imul_supported() && s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && s->stmtAssign.to->exprBinOp.operator == BINOP_MUL) {
assert(s->stmtAssign.what->nodeKind == AST_EXPR_VAR);
assert(s->stmtAssign.what->exprVar.thing->kind == SCOPEITEM_VAR);
if(!strcmp(xop(a, s->stmtAssign.what), xop(a, s->stmtAssign.to->exprBinOp.operands[0]))) {
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]));
}
} else if(s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && s->stmtAssign.to->exprBinOp.operator == BINOP_MULHI) {
assert(s->stmtAssign.what->nodeKind == AST_EXPR_VAR);
assert(s->stmtAssign.what->exprVar.thing->kind == SCOPEITEM_VAR);
//assert(s->stmtAssign.what->exprVar.thing->data.var.color == COLOR_EDX);
assert(s->statement.next->nodeKind == AST_STMT_ASSIGN);
assert(s->statement.next->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP);
assert(s->statement.next->stmtAssign.to->exprBinOp.operator == BINOP_MUL);
AST *otherop = NULL;
if(ast_expression_equal(s->statement.next->stmtAssign.to->exprBinOp.operands[0], s->statement.next->stmtAssign.what)) {
otherop = s->statement.next->stmtAssign.to->exprBinOp.operands[1];
} else if(ast_expression_equal(s->statement.next->stmtAssign.to->exprBinOp.operands[1], s->statement.next->stmtAssign.what)) {
otherop = s->statement.next->stmtAssign.to->exprBinOp.operands[0];
} else abort();
printf("mul %s\n", xop(a, otherop));
// Skip next statement, because they come in a pair
s = s->statement.next;
} else if(s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && ast_expression_equal(s->stmtAssign.what, s->stmtAssign.to->exprBinOp.operands[0]) && (s->stmtAssign.to->exprBinOp.operator == BINOP_ADD || s->stmtAssign.to->exprBinOp.operator == BINOP_SUB) && s->stmtAssign.to->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE && s->stmtAssign.to->exprBinOp.operands[1]->exprPrim.val == 1) {
// inc or dec
static const char *instrs[] = {"inc", "dec"};
printf("%s %s\n", instrs[s->stmtAssign.to->exprBinOp.operator == BINOP_SUB], xop(cg->tlc, s->stmtAssign.what));
} else if(s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && ast_expression_equal(s->stmtAssign.what, s->stmtAssign.to->exprBinOp.operands[0]) && s->stmtAssign.to->exprBinOp.operator < BINOP_SIMPLES) {
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(x86_is_lea(s)) {
printf("lea %s, [%s + %s]\n",
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) {
printf("lea %s, [%s + %s]\n",
xv(s->stmtAssign.what->exprVar.thing),
s->stmtAssign.to->exprBinOp.operands[0]->exprUnOp.operand->exprVar.thing->data.symbol.name,
xv(s->stmtAssign.to->exprBinOp.operands[1]->exprVar.thing));
} 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_PRIMITIVE && s->stmtAssign.to->exprBinOp.operands[0]->exprVar.thing->kind == SCOPEITEM_VAR) {
printf("lea %s, [%s + %i]\n",
xv_sz(s->stmtAssign.what->exprVar.thing, 0),
xv_sz(s->stmtAssign.to->exprBinOp.operands[0]->exprVar.thing, 0),
s->stmtAssign.to->exprBinOp.operands[1]->exprPrim.val);
} else if(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)) {
printf("neg %s\n", xop(cg->tlc, s->stmtAssign.what));
} 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->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, 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);
}
}
} else {
printf("mov %s, %s\n", xop(cg->tlc, s->stmtAssign.what), xop(cg->tlc, s->stmtAssign.to));
}
}
} /*else if(s->nodeKind == AST_STMT_LOOP) {
size_t lbl0 = nextLocalLabel++;
size_t lbl1 = nextLocalLabel++;
cg->loopStackStart[cg->loopStackIdx] = lbl0;
cg->loopStackEnd[cg->loopStackIdx] = lbl1;
cg->loopStackIdx++;
printf(".L%lu:\n", lbl0);
cg_chunk(cg, s->stmtLoop.body);
printf("jmp .L%lu\n", lbl0);
printf(".L%lu:\n", lbl1);
cg->loopStackIdx--;
} else if(s->nodeKind == AST_STMT_BREAK) {
printf("jmp .L%lu\n", cg->loopStackEnd[cg->loopStackIdx - 1]);
} else if(s->nodeKind == AST_STMT_CONTINUE) {
printf("jmp .L%lu\n", cg->loopStackStart[cg->loopStackIdx - 1]);
} else if(s->nodeKind == AST_STMT_IF) {
assert(s->stmtIf.expression->nodeKind == AST_EXPR_BINARY_OP && binop_is_comparison(s->stmtIf.expression->exprBinOp.operator));
size_t lbl = nextLocalLabel++;
printf("cmp %s, %s\n", xop(cg->tlc, s->stmtIf.expression->exprBinOp.operands[0]), xop(cg->tlc, s->stmtIf.expression->exprBinOp.operands[1]));
printf("j%s .L%lu\n", xj(binop_comp_opposite(s->stmtIf.expression->exprBinOp.operator)), lbl);
cg_chunk(cg, s->stmtIf.then);
printf(".L%lu:\n", lbl);
}*/ 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);
}
if(a->chunk.stackReservation) {
for(int i = 0; i < MAX_REGS_PER_CLASS && cg->calleeSaved.reg[i]; i++) {
if(x86_ia16()) {
printf("mov %s, [bp + %li]\n", cg->calleeSaved.reg[i], cg->calleeSaved.stackOffset[i] - a->chunk.stackReservation);
} else {
printf("mov %s, [esp + %li]\n", cg->calleeSaved.reg[i], cg->calleeSaved.stackOffset[i]);
}
}
if(x86_ia16()) {
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 {
stahp_node(s, "Unknown statement %s caught by code generator.", AST_KIND_STR[s->nodeKind]);
}
s = s->statement.next;
}
}
typedef ScopeItem *Adjacency[2];
static bool var_collision(AST *tlc, ScopeItem *v1, ScopeItem *v2) {
/* 1D intersection test */
bool liveRangeIntersection = !lr_empty(v1) && !lr_empty(v2) && (v1->data.var.liveRangeStart == v2->data.var.liveRangeStart || (
(ast_stmt_is_after(tlc, v1->data.var.liveRangeStart, v2->data.var.liveRangeStart) == 1
&& ast_stmt_is_after(tlc, v2->data.var.liveRangeEnd, v1->data.var.liveRangeStart) == 1)
||
(ast_stmt_is_after(tlc, v1->data.var.liveRangeEnd, v2->data.var.liveRangeStart) == 1
&& ast_stmt_is_after(tlc, v2->data.var.liveRangeEnd, v1->data.var.liveRangeEnd) == 1)
));
bool resourceIntersection = (REG_CLASSES[v1->data.var.registerClass].rMask & REG_CLASSES[v2->data.var.registerClass].rMask) != 0;
return liveRangeIntersection && resourceIntersection;
}
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++) {
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(resource & HWR_EDI) {
ediused = true;
}
if(resource & HWR_ESI) {
esiused = true;
}
if(resource & HWR_EBP) {
ebpused = true;
}
}
size_t nextUser = 0;
if(ebxused) {
state->stackOffset[nextUser] = nextUser * x86_max_gpr_size();
state->reg[nextUser] = x86_ia16() ? "bx" : "ebx";
nextUser++;
}
if(esiused) {
state->stackOffset[nextUser] = nextUser * x86_max_gpr_size();
state->reg[nextUser] = x86_ia16() ? "si" : "esi";
nextUser++;
}
if(ediused) {
state->stackOffset[nextUser] = nextUser * x86_max_gpr_size();
state->reg[nextUser] = x86_ia16() ? "di" : "edi";
nextUser++;
}
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) {
for(size_t v = 0; v < tlc->chunk.varCount; v++) {
if(tlc->chunk.vars[v]->data.var.preclassed) {
continue;
}
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 = x86_ia16() ? REG_CLASS_IA16_USEABLE : REG_CLASS_NOT_8;
}
}
}
/* Welsh-Powell graph coloring */
static int comparator(const void *A, const void *B) {
ScopeItem *const *a = A;
ScopeItem *const *b = B;
return ((*a)->data.var.degree * (*a)->data.var.priority) - ((*b)->data.var.degree * (*b)->data.var.priority);
}
static int assign_coloring(AST *a, ScopeItem **vars, size_t adjCount, Adjacency *adjs, bool preclassed) {
int mustSpillRegisterClass = -1;
/* Welsh plow my ass */
for(int v = 0; v < a->chunk.varCount; v++) {
if(vars[v]->data.var.preclassed != preclassed) {
continue;
}
if(vars[v]->data.var.color != -1) {
// Already assigned.
continue;
}
for(int c = 0;; c++) {
if(c >= MAX_REGS_PER_CLASS || REG_CLASSES[vars[v]->data.var.registerClass].rs[c] == 0) {
// Ran out of resources
mustSpillRegisterClass = vars[v]->data.var.registerClass;
break;
}
int resource = REG_CLASSES[vars[v]->data.var.registerClass].rs[c];
if(REG_CLASSES[vars[v]->data.var.registerClass].rsS[c] != type_size(vars[v]->type)) {
continue;
}
for(int a = 0; a < adjCount; a++) {
ScopeItem *me = NULL, *they = NULL;
if(adjs[a][0] == vars[v]) {
me = adjs[a][0];
they = adjs[a][1];
} else if(adjs[a][1] == vars[v]) {
me = adjs[a][1];
they = adjs[a][0];
} else continue;
if(they->data.var.color == -1) {
continue;
}
int theyRes = REG_CLASSES[they->data.var.registerClass].rs[they->data.var.color];
if((resource & theyRes) != 0) {
goto nextColor;
}
}
vars[v]->data.var.color = c;
break;
nextColor:;
}
}
return mustSpillRegisterClass;
}
int cg_tlc(AST *a) {
assert(a->nodeKind == AST_CHUNK);
for(size_t e = 0; e < a->chunk.externCount; e++) {
assert(a->chunk.externs[e]->kind == SCOPEITEM_SYMBOL);
assert(a->chunk.externs[e]->data.symbol.isExternal);
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;
Adjacency *adjs = calloc(adjCount, sizeof(*adjs));
ScopeItem **vars = a->chunk.vars;
for(size_t vi = 0; vi < a->chunk.varCount; vi++) {
vars[vi]->data.var.priority = 1;
vars[vi]->data.var.degree = 0;
if(!vars[vi]->data.var.preclassed) {
vars[vi]->data.var.registerClass = -1;
}
if(!vars[vi]->data.var.precolored) {
vars[vi]->data.var.color = -1;
}
}
determine_register_classes(a);
for(size_t v1i = 0; v1i < a->chunk.varCount; v1i++) {
for(size_t v2i = 0; v2i < a->chunk.varCount; v2i++) {
if(v1i == v2i) continue;
ScopeItem *v1 = vars[v1i];
ScopeItem *v2 = vars[v2i];
if(var_collision(a, v1, v2)) {
ScopeItem *min = v1 < v2 ? v1 : v2;
ScopeItem *max = v1 < v2 ? v2 : v1;
for(size_t a = 0; a < adjCount; a++) {
if(adjs[a][0] == min && adjs[a][1] == max) {
goto cont;
}
}
adjs = realloc(adjs, sizeof(*adjs) * ++adjCount);
adjs[adjCount - 1][0] = min;
adjs[adjCount - 1][1] = max;
cont:;
}
}
}
for(size_t a = 0; a < adjCount; a++) {
adjs[a][0]->data.var.degree++;
adjs[a][1]->data.var.degree++;
}
qsort(vars, a->chunk.varCount, sizeof(*vars), comparator);
int mustSpillRegisterClass;
// Important to prioritize preclassed variables
mustSpillRegisterClass = assign_coloring(a, vars, adjCount, adjs, true);
if(mustSpillRegisterClass == -1) {
mustSpillRegisterClass = assign_coloring(a, vars, adjCount, adjs, false);
}
free(adjs);
if(mustSpillRegisterClass != -1) {
// Spill node with highest degree
ScopeItem *chosen = NULL;
for(ssize_t i = a->chunk.varCount - 1; i >= 0; i--) {
if(vars[i]->data.var.registerClass == mustSpillRegisterClass && !vars[i]->data.var.precolored) {
chosen = vars[i];
break;
}
}
assert(chosen != NULL);
if(ntc_get_int("pdbg")) {
char *astdump = ast_dump(a);
fprintf(stderr, "### CG FAILURE ###\n%s\n", astdump);
free(astdump);
}
ast_spill_to_stack(a, chosen);
return 0;
}
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;
}
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;
}
}

826
src/x86/normalize.c Normal file
View File

@@ -0,0 +1,826 @@
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include"arch.h"
#include"reporting.h"
#include"utils.h"
// 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_temp(AST *tlc, Type *itstype) {
static size_t vidx = 0;
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_temp(tlc, e->expression.type);
// 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;
// Reset ud-chain
vte->data.var.usedefFirst = NULL;
vte->data.var.usedefLast = NULL;
return (AST*) ev[1];
}
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);
if(x86_ia16()) {
assert(!a->exprVar.thing->data.var.preclassed || a->exprVar.thing->data.var.registerClass == REG_CLASS_IA16_PTRS);
a->exprVar.thing->data.var.preclassed = true;
a->exprVar.thing->data.var.registerClass = REG_CLASS_IA16_PTRS;
}
}
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 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_JUMP && s->stmtJump.condition) {
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;
} 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;
} 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 {
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;
}
} else if(s->nodeKind == AST_STMT_LOOP) {
} else if(s->nodeKind == AST_STMT_RETURN) {
// 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 && (!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;
mark_a(retval->exprVar.thing);
}
} else if(s->nodeKind == AST_STMT_EXPR && s->stmtExpr.expr->nodeKind == AST_EXPR_CALL) {
// Turn this:
// f(x);
// into:
// a = f(x);
s->stmtExpr.expr = varify(tlc, chu, stmtPrev, s, s->stmtExpr.expr);
mark_a(s->stmtExpr.expr->exprVar.thing);
// Purge this statement entirely, otherwise we'd have
// a = f(x);
// a;
if(stmtPrev) {
assert(stmtPrev->statement.next->statement.next == s);
stmtPrev->statement.next->statement.next = s->statement.next;
} else {
assert(chu->chunk.statementFirst->statement.next == s);
chu->chunk.statementFirst->statement.next = s->statement.next;
}
this->effective = 1;
} else if(s->nodeKind == AST_STMT_ASSIGN && s->stmtAssign.to) {
if(ast_expression_equal(s->stmtAssign.what, s->stmtAssign.to) && stmtPrev) {
// This is a NOP, remove it from the AST
stmtPrev->statement.next = s->statement.next;
this->effective = 1;
} else {
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_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;
ev[0]->type = tmp->type;
ev[0]->thing = tmp;
memcpy(ev[1], ev[0], sizeof(**ev));
ASTStmtAssign *assign2 = calloc(1, sizeof(*assign2));
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];
assign2->next = s->stmtAssign.next;
s->stmtAssign.next = (AST*) assign2;
this->effective = 1;
} 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_BINARY_OP && s->stmtAssign.what->exprBinOp.operator == BINOP_DEREF && !is_xop(s->stmtAssign.what)) {
s->stmtAssign.what->exprBinOp.operands[0] = varify(tlc, chu, stmtPrev, s, s->stmtAssign.what->exprBinOp.operands[0]);
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->pointer.of->function.argCount;
for(int i = 0; i < argCount; i++) {
// 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)) {
// Turn this:
// a = -b
// into
// a = b
// a = -a
AST *ev[2] = {
ast_deep_copy(s->stmtAssign.what),
ast_deep_copy(s->stmtAssign.what)
};
AST *negation = s->stmtAssign.to;
s->stmtAssign.to = negation->exprUnOp.operand;
negation->exprUnOp.operand = ev[0];
AST *assign2 = calloc(1, sizeof(ASTStmtAssign));
assign2->nodeKind = AST_STMT_ASSIGN;
assign2->stmtAssign.what = ev[1];
assign2->stmtAssign.to = negation;
assign2->statement.next = s->statement.next;
s->statement.next = assign2;
this->effective = 1;
} else if(is_xop(s->stmtAssign.to) == XOP_NOT_XOP) {
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->exprBinOp.operands[0]);
this->effective = 1;
} else if(s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && s->stmtAssign.to->exprBinOp.operator == BINOP_MUL) {
WhysItBad_Huh because = x86_test_mul(stmtPrev, s);
if(because == SRC0_IS_BAD_XOP) {
s->stmtAssign.to->exprBinOp.operands[0] = varify(tlc, chu, stmtPrev, s, s->stmtAssign.to->exprBinOp.operands[0]);
this->effective = 1;
} else if(because == SRC1_IS_BAD_XOP) {
s->stmtAssign.to->exprBinOp.operands[1] = varify(tlc, chu, stmtPrev, s, s->stmtAssign.to->exprBinOp.operands[1]);
this->effective = 1;
} else if(because == MISSING_MULHI_FOR_MUL || because == DEST_NOT_SRC0) {// !stmtPrev || stmtPrev->nodeKind != AST_STMT_ASSIGN || stmtPrev->stmtAssign.to->nodeKind != AST_EXPR_BINARY_OP || stmtPrev->stmtAssign.to->exprBinOp.operator != BINOP_MULHI) {
// Turn this:
// a = b * c
// Into this:
// d = b
// e = d *^ c
// d = d * c
// a = d
ASTExprVar *originalDestination = s->stmtAssign.what;
s->stmtAssign.to->exprBinOp.operands[0] = varify(tlc, chu, stmtPrev, s, s->stmtAssign.to->exprBinOp.operands[0]);
assert(stmtPrev->statement.next->nodeKind == AST_STMT_ASSIGN && stmtPrev->statement.next->stmtAssign.what->nodeKind == AST_EXPR_VAR && ast_expression_equal(stmtPrev->statement.next->stmtAssign.what, s->stmtAssign.to->exprBinOp.operands[0]));
if(!x86_imul_supported()) {
ASTExprBinaryOp *mulhi = calloc(1, sizeof(*mulhi));
mulhi->nodeKind = AST_EXPR_BINARY_OP;
mulhi->type = s->stmtAssign.to->expression.type;
mulhi->operator = BINOP_MULHI;
mulhi->operands[0] = ast_deep_copy(s->stmtAssign.to->exprBinOp.operands[0]);
mulhi->operands[1] = ast_deep_copy(s->stmtAssign.to->exprBinOp.operands[1]);
AST *hihalf = varify(tlc, chu, stmtPrev->statement.next, s, mulhi);
mark_d(hihalf->exprVar.thing);
}
s->stmtAssign.what = ast_deep_copy(s->stmtAssign.to->exprBinOp.operands[0]);
ASTStmtAssign *redest = calloc(1, sizeof(*redest));
redest->nodeKind = AST_STMT_ASSIGN;
redest->what = originalDestination;
redest->to = ast_deep_copy(s->stmtAssign.to->exprBinOp.operands[0]);
redest->next = s->stmtAssign.next;
s->stmtAssign.next = (AST*) redest;
mark_a(s->stmtAssign.to->exprBinOp.operands[0]->exprVar.thing);
this->effective = 1;
} else assert(because == NOT_AT_ALL_IT || because == GUCCI);
} else if(s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && s->stmtAssign.to->exprBinOp.operator < BINOP_SIMPLES && !ast_expression_equal(s->stmtAssign.what, s->stmtAssign.to->exprBinOp.operands[0])) {
// Turn this:
// a = b op c
// into
// a = b
// a = a op c
if(x86_is_lea(s)) {
// We do the lea trick in cg.c
} else {
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;
}
} 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;
} else if(s->stmtAssign.to->nodeKind == AST_EXPR_BINARY_OP && !is_xop(s->stmtAssign.to->exprBinOp.operands[1])) {
s->stmtAssign.to->exprBinOp.operands[1] = xopify(tlc, chu, stmtPrev, s, s->stmtAssign.to->exprBinOp.operands[1]);
this->effective = 1;
} else if(s->stmtAssign.to->nodeKind == AST_EXPR_CAST && s->stmtAssign.to->exprCast.what->nodeKind != AST_EXPR_VAR) {
s->stmtAssign.to->exprCast.what = varify(tlc, chu, stmtPrev, s, s->stmtAssign.to->exprCast.what);
this->effective = 1;
}
}
}
}
}
static void pre_norm_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
AST *n = *nptr;
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
for(size_t i = 0; i < argCount; i++) {
ScopeItem *vte = n->chunk.vars[i];
ASTExprStackPointer *stck = calloc(1, sizeof(*stck));
stck->nodeKind = AST_EXPR_STACK_POINTER;
stck->type = type_pointer_wrap(vte->type);
ASTExprPrimitive *offset = calloc(1, sizeof(*offset));
offset->nodeKind = AST_EXPR_PRIMITIVE;
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;
sum->type = stck->type;
sum->operands[0] = (AST*) stck;
sum->operands[1] = (AST*) offset;
sum->operator = BINOP_ADD;
ASTExprBinaryOp *deref = calloc(1, sizeof(*deref));
deref->nodeKind = AST_EXPR_BINARY_OP;
deref->type = vte->type;
deref->operands[0] = (AST*) sum;
deref->operator = BINOP_DEREF;
ASTExprVar *evar = calloc(1, sizeof(*evar));
evar->nodeKind = AST_EXPR_VAR;
evar->type = vte->type;
evar->thing = vte;
ASTStmtAssign *ass = calloc(1, sizeof(*ass));
ass->nodeKind = AST_STMT_ASSIGN;
ass->next = NULL;
ass->what = (AST*) evar;
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;
} else {
assert(!n->chunk.statementLast);
n->chunk.statementFirst = n->chunk.statementLast = ass;
}
}
}
}
}
static void decompose_symbol_record_field_access(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
AST *n = *nptr;
if(n->nodeKind == AST_EXPR_DOT) {
ASTExprUnaryOp *ref = calloc(1, sizeof(*ref));
ref->nodeKind = AST_EXPR_UNARY_OP;
ref->type = type_pointer_wrap(n->exprDot.a->expression.type);
ref->operator = UNOP_REF;
ref->operand = n->exprDot.a;
ASTExprPrimitive *offset = calloc(1, sizeof(*offset));
offset->nodeKind = AST_EXPR_PRIMITIVE;
offset->type = primitive_parse("u32");
size_t f;
for(f = 0; f < n->exprDot.a->expression.type->record.fieldCount; f++) {
if(!strcmp(n->exprDot.a->expression.type->record.fieldNames[f], n->exprDot.b)) {
break;
}
}
assert(f < n->exprDot.a->expression.type->record.fieldCount);
offset->val = n->exprDot.a->expression.type->record.fieldOffsets[f];
ASTExprBinaryOp *sum = calloc(1, sizeof(*sum));
sum->nodeKind = AST_EXPR_BINARY_OP;
sum->type = ref->type;
sum->operator = BINOP_ADD;
sum->operands[0] = (AST*) ref;
sum->operands[1] = (AST*) offset;
AST *cast = ast_cast_expr((AST*) sum, type_pointer_wrap(n->exprDot.a->expression.type->record.fieldTypes[f]));
ASTExprBinaryOp *deref = calloc(1, sizeof(*deref));
deref->nodeKind = AST_EXPR_BINARY_OP;
deref->type = cast->expression.type->pointer.of;
deref->operator = BINOP_DEREF;
deref->operands[0] = cast;
*nptr = (AST*) deref;
}
}
static bool is_pointer2pointer_cast(AST *e) {
return e->nodeKind == AST_EXPR_CAST && e->exprCast.what->expression.type->type == TYPE_TYPE_POINTER && e->exprCast.to->type == TYPE_TYPE_POINTER;
}
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;
AST *n = *nptr;
bool *success = &state->success;
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`
// 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)) {
// Turn `(p + x) as C + y` into `(p + (x + y)) as C`
n->exprBinOp.operands[0]->exprCast.what->exprBinOp.operands[1]->exprPrim.val += n->exprBinOp.operands[1]->exprPrim.val;
*nptr = n->exprBinOp.operands[0];
*success = true;
} else if(n->nodeKind == AST_EXPR_CAST && n->exprCast.what->nodeKind == AST_EXPR_CAST) {
// Turn `(p as A) as B` to `p as B`
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 && !(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));
prim->nodeKind = AST_EXPR_PRIMITIVE;
prim->type = n->expression.type;
assert(!!n->exprExtSizeOf.ofExpr != !!n->exprExtSizeOf.ofType);
if(n->exprExtSizeOf.ofType) {
prim->val = type_size(n->exprExtSizeOf.ofType);
} else {
prim->val = type_size(n->exprExtSizeOf.ofExpr->expression.type);
}
*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 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(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++;
}
}
ast_denoop(tlc, NULL);
ast_commutativity_pass(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 NormState state = {.targetTLC = tlc};
generic_visitor(&tlc, NULL, NULL, tlc, tlc, &state, normalize_visitor, NULL);
int successful = state.effective;
if(!successful) {
break;
}
if(ntc_get_int("pdbg")) {
fprintf(stderr, "### NORM %lu ###\n", i++);
char *astdump = ast_dump(tlc);
fputs(astdump, stderr);
free(astdump);
}
}
}

View File

@@ -1,2 +0,0 @@
u32 x: 123;
u33 y: 5;

View File

@@ -1,9 +0,0 @@
extern u32() getchar;
extern void(u32) putchar;
loop {
u8 a = getchar();
if(a - 48) {
putchar(a);
}
}

View File

@@ -1,11 +0,0 @@
u16 x: 5;
loop {
u16* y = 257;
u9 w = -4;
u4 z = 3 + *y;
u2 o = -w;
if(x != 0) {
break;
}
}

View File

@@ -0,0 +1,48 @@
AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDEGFFEEEEDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAAAAABBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDEEEFGIIGFFEEEDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAAABBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEFFFI KHGGGHGEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAABBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEFFGHIMTKLZOGFEEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAABBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEEFGGHHIKPPKIHGFFEEEDDDDDDDDDCCCCCCCCCCBBBBBBBBBBBBBBBBBB
AAAAAAAAAABBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEEFFGHIJKS X KHHGFEEEEEDDDDDDDDDCCCCCCCCCCBBBBBBBBBBBBBBBB
AAAAAAAAABBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEEFFGQPUVOTY ZQL[MHFEEEEEEEDDDDDDDCCCCCCCCCCCBBBBBBBBBBBBBB
AAAAAAAABBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEFFFFFGGHJLZ UKHGFFEEEEEEEEDDDDDCCCCCCCCCCCCBBBBBBBBBBBB
AAAAAAABBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEFFFFFFGGGGHIKP KHHGGFFFFEEEEEEDDDDDCCCCCCCCCCCBBBBBBBBBBB
AAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEEFGGHIIHHHHHIIIJKMR VMKJIHHHGFFFFFFGSGEDDDDCCCCCCCCCCCCBBBBBBBBB
AAAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDEEEEEEFFGHK MKJIJO N R X YUSR PLV LHHHGGHIOJGFEDDDCCCCCCCCCCCCBBBBBBBB
AAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDEEEEEEEEEFFFFGH O TN S NKJKR LLQMNHEEDDDCCCCCCCCCCCCBBBBBBB
AAAAABBCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDEEEEEEEEEEEEFFFFFGHHIN Q UMWGEEEDDDCCCCCCCCCCCCBBBBBB
AAAABBCCCCCCCCCCCCCCCCCCCCCCCCCDDDDEEEEEEEEEEEEEEEFFFFFFGHIJKLOT [JGFFEEEDDCCCCCCCCCCCCCBBBBB
AAAABCCCCCCCCCCCCCCCCCCCCCCDDDDEEEEEEEEEEEEEEEEFFFFFFGGHYV RQU QMJHGGFEEEDDDCCCCCCCCCCCCCBBBB
AAABCCCCCCCCCCCCCCCCCDDDDDDDEEFJIHFFFFFFFFFFFFFFGGGGGGHIJN JHHGFEEDDDDCCCCCCCCCCCCCBBB
AAABCCCCCCCCCCCDDDDDDDDDDEEEEFFHLKHHGGGGHHMJHGGGGGGHHHIKRR UQ L HFEDDDDCCCCCCCCCCCCCCBB
AABCCCCCCCCDDDDDDDDDDDEEEEEEFFFHKQMRKNJIJLVS JJKIIIIIIJLR YNHFEDDDDDCCCCCCCCCCCCCBB
AABCCCCCDDDDDDDDDDDDEEEEEEEFFGGHIJKOU O O PR LLJJJKL OIHFFEDDDDDCCCCCCCCCCCCCCB
AACCCDDDDDDDDDDDDDEEEEEEEEEFGGGHIJMR RMLMN NTFEEDDDDDDCCCCCCCCCCCCCB
AACCDDDDDDDDDDDDEEEEEEEEEFGGGHHKONSZ QPR NJGFEEDDDDDDCCCCCCCCCCCCCC
ABCDDDDDDDDDDDEEEEEFFFFFGIPJIIJKMQ VX HFFEEDDDDDDCCCCCCCCCCCCCC
ACDDDDDDDDDDEFFFFFFFGGGGHIKZOOPPS HGFEEEDDDDDDCCCCCCCCCCCCCC
ADEEEEFFFGHIGGGGGGHHHHIJJLNY TJHGFFEEEDDDDDDDCCCCCCCCCCCCC
A PLJHGGFFEEEDDDDDDDCCCCCCCCCCCCC
ADEEEEFFFGHIGGGGGGHHHHIJJLNY TJHGFFEEEDDDDDDDCCCCCCCCCCCCC
ACDDDDDDDDDDEFFFFFFFGGGGHIKZOOPPS HGFEEEDDDDDDCCCCCCCCCCCCCC
ABCDDDDDDDDDDDEEEEEFFFFFGIPJIIJKMQ VX HFFEEDDDDDDCCCCCCCCCCCCCC
AACCDDDDDDDDDDDDEEEEEEEEEFGGGHHKONSZ QPR NJGFEEDDDDDDCCCCCCCCCCCCCC
AACCCDDDDDDDDDDDDDEEEEEEEEEFGGGHIJMR RMLMN NTFEEDDDDDDCCCCCCCCCCCCCB
AABCCCCCDDDDDDDDDDDDEEEEEEEFFGGHIJKOU O O PR LLJJJKL OIHFFEDDDDDCCCCCCCCCCCCCCB
AABCCCCCCCCDDDDDDDDDDDEEEEEEFFFHKQMRKNJIJLVS JJKIIIIIIJLR YNHFEDDDDDCCCCCCCCCCCCCBB
AAABCCCCCCCCCCCDDDDDDDDDDEEEEFFHLKHHGGGGHHMJHGGGGGGHHHIKRR UQ L HFEDDDDCCCCCCCCCCCCCCBB
AAABCCCCCCCCCCCCCCCCCDDDDDDDEEFJIHFFFFFFFFFFFFFFGGGGGGHIJN JHHGFEEDDDDCCCCCCCCCCCCCBBB
AAAABCCCCCCCCCCCCCCCCCCCCCCDDDDEEEEEEEEEEEEEEEEFFFFFFGGHYV RQU QMJHGGFEEEDDDCCCCCCCCCCCCCBBBB
AAAABBCCCCCCCCCCCCCCCCCCCCCCCCCDDDDEEEEEEEEEEEEEEEFFFFFFGHIJKLOT [JGFFEEEDDCCCCCCCCCCCCCBBBBB
AAAAABBCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDEEEEEEEEEEEEFFFFFGHHIN Q UMWGEEEDDDCCCCCCCCCCCCBBBBBB
AAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDEEEEEEEEEFFFFGH O TN S NKJKR LLQMNHEEDDDCCCCCCCCCCCCBBBBBBB
AAAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDEEEEEEFFGHK MKJIJO N R X YUSR PLV LHHHGGHIOJGFEDDDCCCCCCCCCCCCBBBBBBBB
AAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEEFGGHIIHHHHHIIIJKMR VMKJIHHHGFFFFFFGSGEDDDDCCCCCCCCCCCCBBBBBBBBB
AAAAAAABBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEFFFFFFGGGGHIKP KHHGGFFFFEEEEEEDDDDDCCCCCCCCCCCBBBBBBBBBBB
AAAAAAAABBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEFFFFFGGHJLZ UKHGFFEEEEEEEEDDDDDCCCCCCCCCCCCBBBBBBBBBBBB
AAAAAAAAABBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEEFFGQPUVOTY ZQL[MHFEEEEEEEDDDDDDDCCCCCCCCCCCBBBBBBBBBBBBBB
AAAAAAAAAABBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEEFFGHIJKS X KHHGFEEEEEDDDDDDDDDCCCCCCCCCCBBBBBBBBBBBBBBBB
AAAAAAAAAAABBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEEFGGHHIKPPKIHGFFEEEDDDDDDDDDCCCCCCCCCCBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAABBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEFFGHIMTKLZOGFEEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAAABBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEFFFI KHGGGHGEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAAAAABBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDEEEFGIIGFFEEEDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBB

12
tests/testbf.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
../ntc in=../examples/bf.nct target=386 pdbg=0 > bf.asm
nasm -felf -o bf.o bf.asm
ld -dynamic-linker /lib/ld-linux.so.2 -melf_i386 -o bf bf.o -lc
diff -u mandelbrot_expected.txt <(./bf < ../examples/mandelbrot.b | head -c6239)
if [ $? == 0 ] ; then
echo -e "Mandelbrot test \e[1;32msuccessful\e[0m."
else
echo -e "Mandelbrot test \e[1;31mfailed\e[0m."
fi