Skip to content

[DEMO] Compiler Optimization#3169

Draft
DBooots wants to merge 84 commits into
KSP-KOS:developfrom
DBooots:compiler_optimizations
Draft

[DEMO] Compiler Optimization#3169
DBooots wants to merge 84 commits into
KSP-KOS:developfrom
DBooots:compiler_optimizations

Conversation

@DBooots
Copy link
Copy Markdown
Contributor

@DBooots DBooots commented Mar 14, 2026

Demo only at this time

This PR adds an optimization stage to the compiler. This stage converts the Opcodes into a three address code interim representation, upon which various optimizing passes are performed. The interim representation is then converted back into opcodes and emitted to the program context as normal.
The optimizing passes currently implemented trim opcodes without changing any control flow, except to remove branches that are shown to be inaccessible.

Currently Implemented Passes

Suffix Replacement
Replaces CONSTANT: fields with the constant value, and replaces aliased SHIP: fields with the direct alias. This saves 1 opcode per ship alias replaced, and allows constant folding of the CONSTANT values.
Constant Propagation
Where a variable is assigned a constant, it replaces accesses of that variable with the constant (cognizant of global variables and triggers messing with variable values). This allows constant folding of the assigned values.
Constant Folding
Performs arithmetic, including scalar math functions, to reduce formulas to their simplest form. This includes the CONSTANT: fields from Suffix Replacement and the propagated constant variables from Constant Propagation. This step saves between 1 and 3 opcodes for every folding optimization that is performed.
Dead Code Elimination
Eliminates any branches where the condition is known to be false after propagating and folding constants.
Peephole Optimizations
This pass does a number of small optimizations:

  • Replaces string indexing against a lexicon with suffix accessing, when the string is a valid identifier. This saves 1 opcode for every index set or get.
  • Replaces parameterless suffix calls with a direct suffix access (since the suffix get opcode checks if it's a method and invokes it for free). This saves 2 opcodes for each suffix method call, but feels like cheating.
  • Replaces calls to VectorDotProduct() with a simple multiplication opcode. This saves 2 opcodes for every VectorDotProduct() that is performed.
  • Replaces double negation or double logical not operations with the original value. This probably doesn't come up often, but it saves 2 opcodes every time.
  • Replaces not->branch[true|false] with branch[false|true]. This saves 1 opcode every branch simplified.
  • Performs algebraic simplification (not necessarily of constants) to reduce the number of opcodes. E.g. A*B+A*C=A*(B+C), X*X*X = X^3, or -A+B = B-A . This saves multiple opcodes wherever possible.

Scope Simplification
Any scope that does not define any variables is eliminated. This saves 2 opcodes for each scope that is collapsed.

Next Steps

  • More testing! While I've added some unit tests, which run correctly, and I've reviewed the opcode output from those tests to see that the optimization passes are doing what they are supposed to, unit testing only covers simple cases. I need to compile larger and more complicated programs and see what breaks.
  • Further optimization passes. The OptimizationLevel.cs file includes the roadmap for future passes that could be implemented. So far only the O1 (Minimal) level is finished.
  • Adding optimization level specification to the compile function. Unit testing has direct access to the Compile Options fed to the compiler. We'll need to add support for in-game compilation level selection. I think I've got it hardcoded to use no optimizations in the interpreter (prioritizing responsivity over performance) and Minimal for general compilation (running or compiling programs), but this is only a development hack.

DBooots added 30 commits March 1, 2026 22:53
…OptimizationLevel enum to select level of optimizaiton; BaseIntegrationTest uses no optimization. Default the interpreter to no optimization since it won't be worth it there.
…IRBuilder generates basic blocks, which IREmitter can then convert back to kRISC opcodes. This initial implementation does not accomplish any optimization yet (the opposite sometimes) but all current tests pass on the outputted opcodes.
…nalities with IRVariable but not be a subclass.
…or logical operation. This will be used for constant folding during compile. Hopefully the C# compiler will inline these back into the original Opcode methods, but the impact to execution should be minimal either way.
…heir most reduced form. Invalid operations found during folding throw a compilation exception, which should report the problematic line and column in the source.

Also add tests to confirm correct functionality (and improve current test suite for equivalent non-optimizing tests).
…d adding a virtual 'interim CPU' to accomplish the function call through the FunctionManager in order to avoid rewriting the function call logic.
…ng the AssemblyWalk attribute to discover all classes implementing IOptimizationPass.
…n be aliased with the alias (saves one opcode per access). This also replaces all constant() or constant: suffix accesses with the constant value, and does so in time for the constant folding pass.
…ally) every emitted opcode will have a location that makes sense.
…entation for whole-program optimizations (e.g. function inlining).
…d function and lock labels. Fix fallthrough jumps happening at the wrong time.
…optimizing of function code fragments. Add inspection methods to UserFunction and UserFunctionCollection to intercept the code fragments during the optimization pipeline.
…re something would be popped from the stack, but a BasicBlock's stack is empty at that point.
…of pass sort indices to give more space for expansion.
…. Note that scopes are not fully implemented yet.
…that 0/0=1 or 0/X=0 for all X (including 0) instead of throwing an exception. But that's already undefined behaviour so this should be acceptable.
…ter passes to do targeted constant folding after making a change.
DBooots added 5 commits March 16, 2026 21:54
…ues. Also converts the previously-unused IRInstruction.SideEffects property to IsInvariant, which indicates that the effect of the instruction, or the value of the IRValue, can be fully known at compile time.
… ConstantPropagation pass, which needs rewriting. Variables that are written to inside of triggers and global variables are excluded from SSA conversion, and are represented instead with the base IRVariable instead of the derived SSAVariable. Two areas remain problematic for type inferencing: determining the type of a phi value that when a block precedes itself (requires solving a circular reference), and determining the type of a local variable that is written to in a function (requires resolving blocks in call order, which could be a problem in the case of circular calls).
@DBooots DBooots marked this pull request as draft March 21, 2026 22:38
@DBooots
Copy link
Copy Markdown
Contributor Author

DBooots commented Mar 21, 2026

Type inferencing is mostly implemented now. The SSA form currently loses inferred type through phi joins and when modifying external variables in functions. I have to check the literature to see if there are clever solutions for those places where circular references are possible.

DBooots added 23 commits April 6, 2026 19:55
… two abstract subclasses of IRInstruction that make mutating or forEach'ing across operands easy.
…iable if one of the same name existed at a higher scope.
…t propagation pass. This pass also propagates type inferences across Phi variables.
…dard commutativity rules. Use this to ensure that arithmetic operations are optimized appropriately.
… an IEvaluatableToConstant since that's the only time it makes sense.
… possible values in the case of potentially unset variables. Fix variables unset in triggers being forgotten at the next assignment.
…the actual opcode-level transformations that are applied. Adds DEBUG-level ability to restrict which passes are performed, for when some passes might obfuscate the actions of the pass under test.
… scopes. Unresolved variable references of the same name are now only equal if they originate from the same line of source (assumes the user does not expect triggers to interrupt and redefine a variable within the opcodes emitted for a single line of code).

Improve ToString() representation of resolved variable references.
Make the SSAIndexIssuer publicly accessible.
…reference.

Limit search for 'exist' to not include the global scope because that could always be unset.
Switch to using 'Equals' instead of '==' for comparing operands.
Make equal-priority operations interchangeable for folding across algebraic branches.
Fix logic for when operands are folded to a constant, and when they are just simplified.
Disable some optimizations that could go awry if function names have changed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants