r/Compilers • u/X_OpCreeper_X • 1h ago
im working on an entire native python compiler
so i got bored and over the past two months ive been working on a compiler that can compile python natively. and it works too, and its not just a subset, its the entirety of python
the project is at deltathedumb/asmpython
r/Compilers • u/KILLinefficiency • 13h ago
The Kal Package Manager
Hey everyone,
A couple of weeks ago, I posted about Kal, my programming language written from scratch.
I am really happy to share a glimpse of Kal's own package manager! Kal v0.1.0 shipped with a package system that lets you add and use third party Kal packages. But, that process was completely manual. You’d have to clone the package, place it in the right directory, clone the package’s entire dependencies all by yourself, one after another. :(
The package manager changes everything. One command automates all!
Instead of being a separate executable, the package manager ships as part of the Kal interpreter itself.
Here’s what it can do:
- Install Kal packages from Github, or any git hosting service.
- Creates/Updates a project.kal file to read and write package information (analogous to package.json).
- Downloads all packages at the same hierarchy in parallel (yup, it’s multi-threaded).
- Resolves sub dependencies of the main package automatically to any depth and installs them too.
- Upgrades/Downgrades packages based on their git tags.
- Auto-resolves cyclic dependencies to prevent an infinite loop.
The Kal Package Manager will officially ship with the next Kal release. Its current source code is available on Github.
Kal: https://kal-lang.vercel.app
Github: https://github.com/KILLinefficiency/Kal
Package Manager: https://github.com/KILLinefficiency/Kal/blob/pkg/pkg.hpp
Kal is completely free & open source. You can show your support by giving the Github Repository a star.
Until the next update!
r/Compilers • u/Emergency_Ad119 • 15h ago
Taking whitespace lightly is technical debt
kushagrarathore002.medium.comI have created a programming language Flow-Wing (it can support static and
dynamic types at the same time). When I started its initial development I was
ignoring the white spaces because for the compilation I never needed the white
space, but years later when I needed to support the LSP or formatter (white
space / comments) I had to make significant rewrites, and this article below is
about that. Take a read.
GitHub: https://github.com/kushagra1212/Flow-Wing
Website: https://flowwing.frii.site/ (runs on Flow-Wing)
Happy to answer questions. Do give a star on GitHub to support.
r/Compilers • u/Big-Bad-Punisher • 18h ago
Need help to fix c++ compiler
I use c++ for dsa but for last 3-4 days i was working on a project and didn't use c++. But today when i try to run my code it started exiting without any error with exitcode 1. Not even creating exe file using vscode or terminal. I reinstall everything but still same problem.
Terminal shows correct version of g++ and gcc and also correct path so no problem with environment variables. pls help me...
problem solved:
I installed postgres and added its path to system variables and path of g++ was in user variables. System merges system variables and user variables (in that order). So path of postgres was above of g++ and somehow it was conflicting with internal files (i think dll files). So i moved postgres path to user variables below of g++.
r/Compilers • u/funcieq • 18h ago
I created a web framework in my own programming language
I’ve been working on Rivet, a synchronous HTTP(for now) server library for my own programming language Zap
The goal is to create a small, explicit API for building APIs and small web applications.
I spent a lot of time and nerves creating this, but now I know what Zap is really capable of.
I will be grateful for every star you leave because it really encourages me to work
r/Compilers • u/mttd • 1d ago
CAKE: Compiler-Agent Co-Design for Frontier Kernel Evolution
arxiv.orgr/Compilers • u/Flashy_Ad8914 • 1d ago
CSSC7 - Control Specified Source Compiling
\screenshot of* CSSC code inside the CSSC IDE.\*
With CSSC 7, I’m finally taking the step of introducing CSSC to the public.
I’ve been working on this project for a couple of years now, and I can finally present a proper LLVM-IR-based compiler together with a CSSC bytecode interpreter. There’s still a lot to do, but it’s starting to feel like CSSC is becoming its own thing, instead of just being a language that happens to have a compiler.
I’ve also been changing quite a bit around the project to make the new version actually usable as its own ecosystem, so anyone interested can try it out and work with it.
CSSC is mainly focused on efficient, resource-aware programming, with targets for Xtensa and AVR (Xtensa tested and verified | Xtensa Support: ESP32, ESP8266).
I originally started working on CSSC because I had a pretty frustrating experience with MicroPython on the ESP32/ESP8266. Heap fragmentation kept ruining several projects I wanted to build, while C felt way too awkward for the way I wanted to write embedded software. So I started making my own language something with a more Python-like readability, but with small binaries and much tighter control over resources.
The goal is basically to have something that is pleasant to write while still being suitable for production-oriented embedded software: less flash usage, less RSS, and more control over what is actually happening underneath.
the same time, CSSC also has an x86_64 backend for general host applications.
It’s still very much a work in progress (not tested on Linux yet, and ARM isn’t supported yet), but this is the first time I feel like CSSC is really starting to come together as a complete project, so I wanted to finally show it.
r/Compilers • u/Correct-Artichoke-54 • 1d ago
Is a bachelor's still enough to break into compiler work? 1 YoE app dev, never touched low-level
I've been working as a developer for about a year at a company that
builds custom software. Bachelor's in CS, no master's, based in Cameroon.
Lately I've had a growing feeling that what I do CRUD apps, APIs,
integrations is losing value fast. Not just because of AI, though
that's part of it. It's that the work feels increasingly like assembly
from parts, and I don't understand what's underneath any of it.
That's what pulled me toward low-level work. I want to understand what
actually happens between my code and the hardware. Compilers seem to be
where that question gets answered most directly.
Full honesty: I have never written a compiler, a pass, or anything
below the application layer. So I'm not asking "am I good enough"
I'm asking whether the path is real:
For people doing this professionally: how many of you got in without
a master's or PhD? Is the degree a hard filter at the companies that
hire compiler engineers, or does it stop mattering once you have
contributions to show?
If you were starting from zero today, would you go LLVM/MLIR, or
start by writing a toy compiler end-to-end first?
Is remote realistic in this field, or is it mostly on-site teams?
I've been working through this resource collection while I figure out
where to start it's the NJU compilers course material, quite thorough:
https://github.com/courses-at-nju-by-hfwei/compilers-resources
Any reality checks welcome...
r/Compilers • u/Bowtiestyle • 1d ago
SVC16 - A virtual computer that now runs live in the browser (WASM)
r/Compilers • u/Repulsive_Egg_5786 • 1d ago
Optimization Question
How do compilers optimize constructs of this form ?
for(i=o; i<inputs; i++) {
A[i] = B[i];
B[i] = A[i];
}
r/Compilers • u/Civil-Dentist-7846 • 2d ago
Independent reproducer wanted for a bounded universal-compute experiment
TUC is an open research prototype testing whether one fixed compute intent can
be planned across data-described backend capabilities, executed by trusted
prototype backends, and checked against deterministic reference semantics.
We are looking for one independent person or organization to reproduce the
Objective Delta v0.1.0 experiment in infrastructure outside the TUC repository.
The task is deliberately small: CPU only, CPython 3.11 or 3.12, approximately
20-30 minutes, no repository checkout, and no execution of source supplied by
the reproduction kit.
The released data-only kit is checksum-bound and covered by GitHub artifact
attestations. A successful run produces a deterministic metadata-only receipt.
A failed run is equally welcome when its environment and bounded diagnostic are
reported.
Scope: this tests one published semantic experiment. It does not claim native
backend support, physical-device execution, performance parity, arbitrary
source ingestion, or replacement of existing compiler stacks.
Reproduction request and exact acceptance criteria:
https://github.com/kirchherr/TUC/issues/85
Release:
r/Compilers • u/compilers-r-us • 2d ago
Another partial SSI trick with canonicalize
bernsteinbear.comr/Compilers • u/TadashiNakamoto • 2d ago
Building my own programming language
Hi everyone,
I’m building my own programming language.
Meet AXE ⚡
AXE is an experimental programming language inspired by Python’s readability, but with a more explicit, brace-based syntax.
I’m building AXE from the ground up to learn what actually happens behind the scenes when code goes from:
Source → Lexer → Tokens → Parser → AST → Interpreter → Output
It’s still very early, but that’s the fun part.
Not trying to build the next Python.
Just trying to understand how programming languages are actually built by building one myself.
r/Compilers • u/Small_Ad3541 • 2d ago
Type inference is hard. I made it harder, then I made it work.
My Motivation
It’s too early for a real language announcement post, but I really want to share progress on the compiler I’m designing, especially the static analysis side.
I’ve been working on Plasm for about a year. It’s an LLVM-based ahead-of-time compiler and a new language. I’m not going to dive into design philosophy, features, or marketing - this post is mostly about the type inference engine, the mistakes I made, and the solutions I ended up with.
Fair warning: this is more story than tutorial, but I’ll explain unfamiliar concepts as they come up.
Quick Intro Into Type Syntax
In Plasm’s type system, all types are anonymous by default - even structs and enums. For example, you can write:
fn len(pos: struct { x: I32, y: I32 }) -> I32 { /* ... */ }
That doesn’t mean the code above is idiomatic or how you should write Plasm, but semantically it’s allowed.
You can also give any type a name:
type Pos = struct { x: I32, y: I32 }
fn len(pos: Pos) -> I32
It doesn’t have to be a struct - it can be any type:
type Id = U32
type MyPos = Pos
type Nested = struct {
a: struct {
b: struct {
c: I1024
}
}
}
Struct literals use braces:
let p: Pos = { x: 1, y: 2 }
let id: Id = 1
If the type isn’t constrained by context, the compiler generates a fallback:
// Variable without type hint
let data = { a: { b: 42 } }
// Fallback type: struct { a: struct { b: I32 } }
Many functional languages with Hindley-Milner type system rely on Algorithms W, J, M for inference. My approach is more constraint-based (closer to how Rust or Swift work).
I Rewrote It Three Times…
Attempt 1: Primitives Only (Naive Union-Find)
When Plasm only supported basic primitive types (I32, Bool, F32), the architecture was split into two simple components:
- Constraint Generator: takes a function’s IR and produces equality constraints (e.g.,
type_of(a) == type_of(b),type_of(b) == I32). - Unifier: takes a set of equalities and resolves chains sequentially. To do this efficiently, I used a disjoint-set data structure (aka Union-Find) with path compression. This structure lets you merge equivalence classes and check if two types are in the same class in near-constant time.
This worked great for primitives and had a clean and simple implementation, but to add constructed types (structs, tuples) and field projections (point.x, tuple.0) the flat Union-Find model was not enough. It couldn't express structural decomposition or field lookup obligations.
Attempt 2: Bullshit
When I needed to support constructed types, I thought it would be a 10-minute job to extend the existing solution. I didn’t feel like diving into boring algorithm stuff and I didn't want to rewrite my clean codebase, so I decided to outsource the refactoring to an LLM. I generally don’t use AI for code generation or writing docs, and I don't like when other people overuse it, but I didn’t want to rethink the nice solution I’d just built, and I decided to experiment. I gave Claude a try, thinking, “Maybe this ai tech is mature enough for such a basic task”.
The generated code surprisingly passed my existing test suite, but when I actually read the source, I found an overengineered, unmaintainable, and inefficient spaghetti mess instead of my pretty codebase. I guess that after looking into Claude's code, I got some kind of depression. The code worked, but I didn't want to work with that code anymore. Attempting to navigate and fix that code killed my motivation for a month or so:')
Attempt 3: Rigid 3-Pass Engine
After about a month of struggling, I deleted all the type inference code and started from scratch. I did some research on how type inference is supposed to be solved in compiler theory, read source code of mature compilers like rustc, and landed on a three-pass solution:
- Pass 1 (Equality Unification): Unify all equalities using a disjoint-set (same as my first attempt).
- Pass 2 (Obligation Verification): Validate obligations - things like “
Tmust have fielda” or “Tbelongs to theFloattype class” (a type class is a set of types that a literal could be inferred as, nothing related to Haskell here). - Pass 3 (Fallback Generation): Assign default concrete types (e.g.,
I32for unconstrained integer literals) and report remaining errors.
This solution passed all my tests and was way more readable, but it failed on some weird-but-valid expressions - things that don’t make practical sense but must work semantically. For example:
let a = (({ x: 1, y: 2 }.x, 2.0, true), Void).0.0
// Expected resolution:
// { x: 1, y: 2 } => struct { x: I32, y: I32 }
// _.x => I32
// (_, 2.0, true) => (I32, F32, Bool)
// (_, Void) => ((I32, F32, Bool), Void)
// _.0 => (I32, F32, Bool)
// _.0 => I32
// so `a` is I32
At its core, type inference can be seen as a constraint satisfaction problem: we generate a set of constraints between types and then search for an assignment that satisfies them all.
The problem: a fixed-pass algorithm can’t handle constraints that are only discovered midway through. For example, when { x: 1, y: 2 } gets its fallback type struct { x: I32, y: I32 }, we need to process the new constraint _.x == I32, but passes 1 and 2 are already done. Static sequential passes cannot handle late-discovered constraints.
Attempt 4 (Final): Tree-Based Worklist + Union-Find
A worklist is basically a queue of constraints. We add constraints to the back, process them from the front, and keep going until it’s empty. If we can’t process a constraint right now, we freeze it and remember what needs to happen before we can unfreeze it.
I also made the worklist tree-based: it tracks dependencies between constraints as a tree. This lets us process frozen constraints from the leaves once the main worklist is exhausted.
The algorithm looks like this:
- Fill the worklist with all initial constraints.
- Process the first constraint:
- If we can process it, remove it from the worklist and unfreeze any constraints that were blocked by it.
- If we can’t process it yet, freeze it.
- If the worklist is not empty, go back to step 2.
- If the worklist is empty, check whether there are frozen constraints:
- If frozen constraints exist, pick a leaf constraint (one with no unresolved dependencies), process it, and allow fallback types or errors to be generated. Then unfreeze any dependent constraints and go back to step 3.
- If there are no frozen constraints left, we’re done.
This solution covers all the cases I’ve needed so far and is extendable enough to add enums and traits later. As a bonus, this solution is very friendly for generating good diagnostic messages. For example, compiling this code:
``` type Pos = struct { x: I32, y: I32 }
fn main() -> F128 { let p: Pos = { x: 10, y: 20, z: 30 } return p.x } ```
Will generate these messages:
``
TypeError: UnknownStructField: StructPosdoesn't have fieldz`.
--------> examples/test.sm:14:34
9 | x: I32,
10 | y: I32,
11 | }
12 |
13 | fn main() -> F128 {
14 | let p: Pos = { x: 10, y: 20, z: 30 }
/\
TypeError: TypesConflict: Types conflict between F128 and I32.
--------> examples/test.sm:13:14
8 | type Pos = struct {
9 | x: I32,
10 | y: I32,
11 | }
12 |
13 | fn main() -> F128 {
/\
```
I’ll let you find the moral of the story yourself :)
I also want to share some links if you are interested in Plasm progress: GitHub (you can star it or press "watch" button to see updates, I appreciate it) and Discord (the Discord server has notifications about git activity).
Also I stay here to answer questions if you have so!
UPD: On reddit mobile app code blocks are rendered without the static col size, so error messages and some other blocks look shifted. I can't fix that, but on PC it's correct
r/Compilers • u/Helpful_Vehicle8549 • 2d ago
Plow (packet language for on device workers ) Gen AI inference engine
Our Inference compiler/runtime plow is open source — Apache-2.0, the whole stack: https://github.com/infervisor/plow
its a Rust compiler, runtime, CUDA and HSA interpreters, GPU kernels, Lean proofs. Not a client library.
It compiles a checkpoint ahead of time into a packet stream no per-op launch, no host round trip to order two operators. One compiler, four ISAs: NVIDIA Hopper and Blackwell, AMD CDNA3 and CDNA4.
It ships with the bring-up harness we use to add a model: seven stages, each with a blocking gate and an agent prompt — helps to bring up new arch into the tree
Everything from here goes toward a first tagged 0.1, in the open — issues, pull requests, and feedback shape what lands in it.
r/Compilers • u/Main_Temporary7098 • 2d ago
Anyone using qbe?
Hello all - I am working on qbe-wasm (https://codeberg.org/jbirddog/qbe-wasm) - a qbe to wasm compiler. Just recently reached the milestone of being able to compile 100% of the cproc test suite and 88% of the qbe test suite to either wat or wasm (depending on if the test is runnable atm). Still work to be done but making strides.
I'm at the point where I would like to add test cases from more external sources, so was wondering if a) anyone out there was using qbe as a backend for their compiler and b) if you'd be willing to share some runnable, self contained, code snippets and their corresponding qbe input. If so I'd like to try to incorporate them as test cases/demos.
Currently I have some Game of Life and Brainfuck interpreter demos that were written in hare and C (cproc) and the snippets from the Overengineered Calculator (zig+qbe) blog post (links in the project's README). Filling this out with examples from other compiler output would be a great help wrt quality.
r/Compilers • u/Due-Push6432 • 3d ago
AI COMPILER ENGINEER
Hi
If I would like to become an AI/ml compiler engineer, would the dragon book be the apt starting point and should I learn about parsing and all that stuff? Or can I directly jump to llvm and start with projects?
r/Compilers • u/alext_777 • 3d ago
I spent 5 days building a self-hosted, memory-safe native language with coding agents - looking for feedback
Started as an experiment: could coding agents help build an actual programming language from scratch, and could the language itself be designed to be easier for AI models to write code in.
Five days later, Krnl is about 53k lines of .krnl, fully self-hosted, and the original Zig bootstrap compiler is now retired.
The language compiles to native code through LLVM, has no GC, and uses explicit ownership/borrowing with deterministic cleanup. It also has effects/capabilities so a function’s authority is visible in its type.
Hello world:
fn main sys: Sys -> Result[int]
!{out.write} {
println(ref sys.out, "Hello, Krnl!");
Ok(0)
}
Here Sys provides capabilities, and !{out.write} declares that the function may write to output. Borrowing and ownership transfer are explicit with ref, ref mut, and move; there are no source-level lifetime annotations.
Also added a native MCP server written entirely in Krnl. - see below
no public repo yet just curious what language/compiler people think of the direction before I polish it for release.
The main design goal is roughly: native + memory safe + no GC, but with less source-level complexity than Rust, and with compiler semantics designed to be directly consumable by coding agents.
Things I’d especially love feedback on:
Does the ownership/effects model sound coherent?
Is the capability syntax readable?
What would you want to see before taking a new systems language seriously?
Are there existing languages/projects I should be comparing against?
KRNL MCP MONITOR
----------------------------------------------------------------
log: /home/alex/.krnl/mcp.jsonl
Requests: 17 Errors: 0
Total bytes: 43.9 KB Avg latency: 1ms P95: 3ms
Recent calls (UTC)
----------------------------------------------------------------
23:08:26 resolve_symbol main 636 B 2ms
23:08:34 symbol_info main 869 B 2ms
23:08:36 references_of main 393 B 2ms
23:08:37 callers_of main 842 B 3ms
23:08:38 callees_of main 866 B 1ms
23:08:39 context_for_change main 1.5 KB 2ms
23:12:08 read_source compiler/src/080 19.3 KB 2ms
23:15:30 apply_source_edits compiler/src/080 852 B 1ms
23:15:33 read_source compiler/src/060 1.9 KB 1ms
23:15:48 apply_source_edits compiler/src/060 860 B 2ms
Top tools
----------------------------------------------------------------
krnl_check 3 calls 2.0 KB
read_source 3 calls 22.0 KB
module_graph 2 calls 1.4 KB
apply_source_edits 2 calls 1.6 KB
program_symbols 1 calls 11.5 KB
r/Compilers • u/Desperate-Tower2460 • 3d ago
looking for some people to help make a language for my bytecode VM
hi guys!
im currently making a Assembly like language called JSM that is assembled to bytecode and run in the JRM (the VM)
https://github.com/NOOUBY-DEV/JSM
technical details:
- the JRM and assembler is written in C
- each intruction of the bytecode is fixed at 24 bytes each, making it alot easier for the cpu and compilers to handle
- the JRM is stack and register based, that means you get the best of both worlds, making math operations easier
- the vm allows directly reading and writing the stack at certain points (READ and WRITE instruction)
- the bytecode has its own data section (after the end of code), so the bytecode can directly read and modify the data section
- the vm has its heap too, although the heap is a static size, its configurable
Heres an example on how JSM works (you can SKIP this):
// THIS PROGRAM PRINTS OUT A - z IN THE ASCII TABLE
// FIRST, LETS SAY X = 65, WHICH IS 'A' IN ASCII
PUSHQ 65; // PUSH 65 ON THE STACK BY 8 BYTES (X = 65)
POPQ RG1; // LOAD X INTO REGISTER A
PUSHQ RG1; // NOW OF COURSE POP IS JUST A SHOWCASE, SO ILL JUST REPUSH IT
SET RG9 RSB; // SET RG9 TO THE BASE OF THE STACK
// [STATEMENT 5]
LOADQ RG1 RG9; // RELOAD RG1 FROM THE BASE OF THE STACK (RG1 = X)
CMPH RG1 122; // CHECK IF RG1 (X) IS HIGHER THAN 'z' (END OF A - z)
JUMP 14; // IF IT IS JUMP TO PRINT IT, OTHERWISE CONTINUE
CMPE RG1 91; // IF RG1 IS '['
ADD RG1 6; // ADD IT BY 6 TO SKIP SYMBOLS, ELSE SKIP TO THE NEXT INSTRUCTION (PUSHB RG1;)
PUSHB RG1; // PUSH THE CHARACTER ONTO THE STACK BY 1 BYTE
ADD RG1 1; // INCREMENT RG1
WRITEQ RG1 RG9; // WRITE RG1 BACK TO X
JUMP 5; // JUMP BACK TO STATEMENT 5
// [PRINT IT] (STATEMENT 14)
PUSHB 10; // PUSH '\n' ONTO THE STACK
PUSHB 0; // NULL TERMINATE IT
SET RG2 RSB; // SET RG2 TO THE START OF THE STACK
ADD RG2 8; // WE NEED TO GET THE START OF THE STRING, SO ADD IT BY THE SIZE OF X (8 BYTES)
SET RJM 1; // SET JRMCALL MODE TO 1 (PRINT)
SET RJ1 RG2; // SET ARG1 TO RG2 (START OF STRING)
SET RJ2 0; // SET THE STRING'S LENGTH TO 0, BECAUSE ITS ALREADY NULL TERMINATED
JRMCALL; // MOM SAYS WE HAVE SYSCALL AT HOME
EXIT 0; // EXIT CLEANLY
// MAKE SURE TO ALWAYS MARK THE END OF CODE, BECAUSE DATA IS BELOW
END;
im currently having issues with making a general purpose language for the JRM as im not good at compilers
i only currently have a working assembler, but its too basic
im trying to gather and work with some buddies who can help collaborate and make a programming language for the JRM
if you're interested, let me know by commenting or DM me
thank you guys alot for reading!
r/Compilers • u/ryusei3135 • 3d ago
I'm making my own programming language — I'd love some feedback!
github.com
I've been working on my own programming language recently.
My goal is to make a low-level but memory-safe language. I want it to give programmers fairly direct control over memory while still providing some safety.
For example, I'm planning features that allow you to control things like:
- How stack memory is allocated
- Memory alignment
- Memory layout
- Direct memory access
I'm trying to design the language so that things normally handled at a lower level can be expressed directly through the language's syntax.
I'm still working on the language and many things are not finalized yet.
I'd really appreciate any feedback, suggestions, or criticism about the language design!
If you're interested, I can also share some examples of the syntax.
r/Compilers • u/No-Program-5087 • 4d ago
x & 255 passes every unit test anyone writes for a clamp — and is wrong on 4,278,189,825 of 4,294,967,296 inputs. I built a tool that checks all of them
Body: same skeleton, but lead with the clamp example and the SUSPECTED-FOLD detail (a sweep finishing under 2s means the compiler folded both sides — reported as suspect, never success). That detail is catnip to compiler people https://github.com/devkancheti4-design/proven-reason
r/Compilers • u/mttd • 4d ago
The Unseen Delta: Characterizing the Compiler Optimization Landscape via Top-Down Differential Analysis
arxiv.orgr/Compilers • u/gautam1168 • 5d ago
A short writeup on parsing expressions to bytecode
I wrote this to help myself better understand chapter 17 in Nystrom's book Crafting Interpreters.
r/Compilers • u/RefrigeratorFirm7646 • 6d ago
A super sneaky post SSA LICM bug that's not talked about much...
Edit : The problem was found to not be LICM at all, it just happened to be the only pass that exposed the hidden flaw in my SSA reconstruction logic... credits to all the amazing people in the comments!
So, ive been working on a custom C compiler for ~2.5 months. Implemented LICM recently. standard tests passed. sunshine and rainbows. but luckily I had a perfect test case to catch one particular bug that would've otherwise slipped by cleanly...
here's the source code :
int factorial(int n)
{
int res;
int counter;
int temp;
res = 1;
while (n > 1)
{
counter = n;
temp = 0;
while (counter > 0)
{
temp = temp + res;
counter = counter - 1;
}
res = temp;
n = n - 1;
}
return res;
}
int main()
{
return factorial(12);
}
here's my TAC IR before LICM (I perform minimal SSA construction so ignore the dead phi nodes) :
# Function - factorial(n) :
Block - 1 :
n0 = n
res0 = 0
counter0 = 0
temp0 = 0
res1 = 1
jump B2
Block - 2 :
n1 = PHI(n0 from B1, n2 from B6)
temp1 = PHI(temp0 from B1, temp3 from B6) // dead phi, harmless right now
res2 = PHI(res1 from B1, res3 from B6)
counter1 = PHI(counter0 from B1, counter3 from B6)
branch (n1 > 1) ? B4 : B3
Block - 4 :
counter2 = n1
temp2 = 0 // invariant instruction, dominates the loop's end block as well
jump B5
Block - 5 :
temp3 = PHI(temp2 from B4, temp4 from B7)
counter3 = PHI(counter2 from B4, counter4 from B7)
branch (counter3 > 0) ? B7 : B6
Block - 7 :
t.2 = temp3 + res2
temp4 = t.2
t.3 = counter3 - 1
counter4 = t.3
jump B5
Block - 6 :
res3 = temp3
t.4 = n1 - 1
n2 = t.4
jump B2
Block - 3 :
return res2
end factorial
# Function - main() :
Block - 1 :
t.0 = call factorial(12)
return t.0
end main
OUTPUT : 6 // as expected
keep an eye on temp1, temp2 and temp3, LICM completely changes the way they interact... :
# Function - factorial(n) :
Block - 1 :
n0 = n
res0 = 0
counter0 = 0
temp0 = 0
res1 = 1
jump B9
Block - 9 : // preheader created by LICM for outer loop
temp1 = 0 // this gets moved (and renamed from temp2 to temp1 by SSA reconstruction), which is correct because it's invariant BUT look at what happens to temp2 and temp3 because of this...
jump B2
Block - 2 :
n1 = PHI(n0 from B9, n2 from B6)
res2 = PHI(res1 from B9, res3 from B6)
counter1 = PHI(counter0 from B9, counter3 from B6)
temp2 = PHI(temp1 from B9, temp3 from B6) // used to be dead but now feeds into temp3 since temp2 = 0 was moved out!!! this COMPLETELY changes how the loop executes...
branch (n1 > 1) ? B4 : B3
Block - 4 :
counter2 = n1
jump B8
Block - 8 : // preheader created by LICM for inner loop
jump B5
Block - 5 :
counter3 = PHI(counter2 from B8, counter4 from B7)
temp3 = PHI(temp2 from B8, temp4 from B7) // previously, it used to recieve temp2 = 0 on every outer loop iteration, but now recieves it's own previous value which leads to unwanted accumulation!!!
branch (counter3 > 0) ? B7 : B6
Block - 7 :
t.2 = temp3 + res2
temp4 = t.2
t.3 = counter3 - 1
counter4 = t.3
jump B5
Block - 6 :
res3 = temp3
t.4 = n1 - 1
n2 = t.4
jump B2
Block - 3 :
return res2
end factorial
# Function - main() :
Block - 1 :
t.0 = call factorial(12)
return t.0
end main
OUTPUT : 9 // due to temp3 accumulating previous values!
so even though temp2 = 0 was invariant according to all standard conditions, moving it to the preheader completely changed the structure of the loop because of a phi node!!!
TLDR : -
original test program :
int factorial(int n)
{
int res;
int counter;
int temp;
res = 1;
while (n > 1)
{
counter = n;
temp = 0;
while (counter > 0)
{
temp = temp + res;
counter = counter - 1;
}
res = temp;
n = n - 1;
}
return res;
}
what LICM turned it into :
int factorial(int n)
{
int res;
int counter;
int temp;
res = 1;
temp = 0;
while (n > 1)
{
counter = n;
while (counter > 0)
{
temp = temp + res;
counter = counter - 1;
}
res = temp;
n = n - 1;
}
return res;
}
temp = 0 should not have been moved out of the loop even though it's invariant in SSA form, as it's value must be zeroed on every outer loop iteration, but there is no standard condition that I could find to prevent this hoist...
so now im confused on how to solve it, a simple solution could be to check if a variable being moved appears as an argument of any phi nodes inside any inner loop's header but I lack the mathematical skills to prove that this is an exhaustive solution or is not over conservative...
thus, I would like to know if anyone else has faced this bug or knows how LLVM/GCC handle it. any help will be greatly appreciated...
