Skip to content

Commit 36b2d9d

Browse files
committed
feat: Implement multi-platform GitHub Actions for build and release, and add initial LLVM compiler backend.
1 parent b27bff4 commit 36b2d9d

3 files changed

Lines changed: 52 additions & 71 deletions

File tree

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ jobs:
4646
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
4747
-DBUILD_TESTS=OFF \
4848
-DBUILD_BENCH=OFF \
49+
-DLLVM_DIR="C:/Program Files/LLVM/lib/cmake/llvm" \
4950
-DCMAKE_PREFIX_PATH="C:/Program Files/LLVM"
5051
5152
- name: Build (verbose)

.github/workflows/release.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ jobs:
5353
- name: Configure CMake
5454
shell: bash
5555
run: |
56-
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="C:/Program Files/LLVM"
56+
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
57+
-DLLVM_DIR="C:/Program Files/LLVM/lib/cmake/llvm" \
58+
-DCMAKE_PREFIX_PATH="C:/Program Files/LLVM"
5759
5860
- name: Build
5961
shell: bash

src/compiler/backend_llvm.cpp

Lines changed: 48 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,64 @@
1-
#include "../../include/backend_llvm.h"
2-
#include <llvm/IR/IRBuilder.h>
3-
#include <llvm/IR/LLVMContext.h>
4-
#include <llvm/IR/Module.h>
5-
#include <llvm/IR/Verifier.h>
1+
#include "../../include/object.h"
62
#include <vector>
73
#include <map>
84
#include <iostream>
95

10-
using namespace llvm;
6+
// Do NOT use 'using namespace llvm;' due to clash with our 'Value' type.
7+
118

129
class LLVMEmitter {
13-
std::unique_ptr<LLVMContext> Context;
14-
std::unique_ptr<Module> ModuleOb;
15-
std::unique_ptr<IRBuilder<>> Builder;
16-
std::map<IRBasicBlock*, BasicBlock*> blockMap;
17-
std::vector<Value*> ssaValues;
10+
std::unique_ptr<llvm::LLVMContext> Context;
11+
std::unique_ptr<llvm::Module> ModuleOb;
12+
std::unique_ptr<llvm::IRBuilder<>> Builder;
13+
std::map<IRBasicBlock*, llvm::BasicBlock*> blockMap;
14+
std::vector<llvm::Value*> ssaValues;
1815

1916
public:
2017
LLVMEmitter() {
21-
Context = std::make_unique<LLVMContext>();
22-
ModuleOb = std::make_unique<Module>("ProXPL Module", *Context);
23-
Builder = std::make_unique<IRBuilder<>>(*Context);
18+
Context = std::make_unique<llvm::LLVMContext>();
19+
ModuleOb = std::make_unique<llvm::Module>("ProXPL Module", *Context);
20+
Builder = std::make_unique<llvm::IRBuilder<>>(*Context);
2421

2522
setupRuntimeTypes();
2623
}
2724

2825
void setupRuntimeTypes() {
29-
// Declare extern "C" functions from llvm_runtime.c
30-
3126
// Value prox_rt_add(Value a, Value b);
32-
FunctionType *BinOpType = FunctionType::get(
27+
llvm::FunctionType *BinOpType = llvm::FunctionType::get(
3328
Builder->getInt64Ty(),
3429
{Builder->getInt64Ty(), Builder->getInt64Ty()},
3530
false
3631
);
37-
Function::Create(BinOpType, Function::ExternalLinkage, "prox_rt_add", ModuleOb.get());
32+
llvm::Function::Create(BinOpType, llvm::Function::ExternalLinkage, "prox_rt_add", ModuleOb.get());
3833

3934
// void prox_rt_print(Value v);
40-
FunctionType *PrintType = FunctionType::get(
35+
llvm::FunctionType *PrintType = llvm::FunctionType::get(
4136
Builder->getVoidTy(),
4237
{Builder->getInt64Ty()},
4338
false
4439
);
45-
Function::Create(PrintType, Function::ExternalLinkage, "prox_rt_print", ModuleOb.get());
40+
llvm::Function::Create(PrintType, llvm::Function::ExternalLinkage, "prox_rt_print", ModuleOb.get());
4641

4742
// Value prox_rt_const_string(char* chars, int length);
48-
FunctionType *ConstStrType = FunctionType::get(
43+
llvm::FunctionType *ConstStrType = llvm::FunctionType::get(
4944
Builder->getInt64Ty(),
5045
{Builder->getInt8PtrTy(), Builder->getInt32Ty()},
5146
false
5247
);
53-
Function::Create(ConstStrType, Function::ExternalLinkage, "prox_rt_const_string", ModuleOb.get());
48+
llvm::Function::Create(ConstStrType, llvm::Function::ExternalLinkage, "prox_rt_const_string", ModuleOb.get());
5449
}
5550

5651
void emitModule(IRModule* module) {
5752
for (int i = 0; i < module->funcCount; i++) {
5853
emitFunction(module->functions[i]);
5954
}
60-
ModuleOb->print(outs(), nullptr);
55+
ModuleOb->print(llvm::outs(), nullptr);
6156
}
6257

6358
void emitFunction(IRFunction* func) {
6459
// All functions return Value (Int64)
65-
FunctionType *FT = FunctionType::get(Builder->getInt64Ty(), false);
66-
Function *F = Function::Create(FT, Function::ExternalLinkage, func->name, ModuleOb.get());
60+
llvm::FunctionType *FT = llvm::FunctionType::get(Builder->getInt64Ty(), false);
61+
llvm::Function *F = llvm::Function::Create(FT, llvm::Function::ExternalLinkage, func->name, ModuleOb.get());
6762

6863
ssaValues.clear();
6964
ssaValues.resize(func->nextSsaVal + 2048, nullptr);
@@ -73,13 +68,13 @@ class LLVMEmitter {
7368
for (int i = 0; i < func->blockCount; i++) {
7469
char name[64];
7570
sprintf(name, "block%d", func->blocks[i]->id);
76-
blockMap[func->blocks[i]] = BasicBlock::Create(*Context, name, F);
71+
blockMap[func->blocks[i]] = llvm::BasicBlock::Create(*Context, name, F);
7772
}
7873

7974
// Pass 2: Emit instructions (except Phi operands)
8075
for (int i = 0; i < func->blockCount; i++) {
8176
IRBasicBlock* irBlock = func->blocks[i];
82-
BasicBlock* llvmBlock = blockMap[irBlock];
77+
llvm::BasicBlock* llvmBlock = blockMap[irBlock];
8378
Builder->SetInsertPoint(llvmBlock);
8479

8580
IRInstruction* instr = irBlock->first;
@@ -94,10 +89,10 @@ class LLVMEmitter {
9489
IRInstruction* instr = func->blocks[i]->first;
9590
while (instr) {
9691
if (instr->opcode == IR_OP_PHI) {
97-
PHINode* phi = cast<PHINode>(ssaValues[instr->result]);
92+
llvm::PHINode* phi = llvm::cast<llvm::PHINode>(ssaValues[instr->result]);
9893
for (int k = 0; k < instr->operandCount; k += 2) {
99-
Value* val = getOperand(instr->operands[k]);
100-
BasicBlock* incomingBB = blockMap[instr->operands[k+1].as.block];
94+
llvm::Value* val = getOperand(instr->operands[k]);
95+
llvm::BasicBlock* incomingBB = blockMap[instr->operands[k+1].as.block];
10196
if (val && incomingBB) {
10297
phi->addIncoming(val, incomingBB);
10398
}
@@ -112,40 +107,40 @@ class LLVMEmitter {
112107
Builder->SetInsertPoint(&F->back());
113108
// Return NIL (0x7ffc000000000001)
114109
uint64_t nilVal = 0x7ffc000000000001;
115-
Builder->CreateRet(ConstantInt::get(*Context, APInt(64, nilVal, false)));
110+
Builder->CreateRet(llvm::ConstantInt::get(*Context, llvm::APInt(64, nilVal, false)));
116111
}
117112

118113
// Verify function
119114
std::string err;
120-
raw_string_ostream os(err);
121-
if (verifyFunction(*F, &os)) {
115+
llvm::raw_string_ostream os(err);
116+
if (llvm::verifyFunction(*F, &os)) {
122117
std::cerr << "LLVM Verification Error: " << os.str() << "\n";
123118
}
124119
}
125120

126121
void emitInstruction(IRInstruction* instr) {
127122
switch (instr->opcode) {
128123
case IR_OP_CONST: {
129-
Value* v = nullptr;
124+
llvm::Value* v = nullptr;
130125
if (IS_NUMBER(instr->operands[0].as.constant)) {
131126
// Just a double encoded as int64
132127
double num = AS_NUMBER(instr->operands[0].as.constant);
133128
uint64_t bits;
134129
memcpy(&bits, &num, sizeof(double));
135-
v = ConstantInt::get(*Context, APInt(64, bits, false));
130+
v = llvm::ConstantInt::get(*Context, llvm::APInt(64, bits, false));
136131
} else if (IS_STRING(instr->operands[0].as.constant)) {
137132
// Define global string constant
138133
ObjString* strObj = AS_STRING(instr->operands[0].as.constant);
139-
Constant *StrConstant = ConstantDataArray::getString(*Context, strObj->chars);
140-
GlobalVariable *ValidStr = new GlobalVariable(*ModuleOb, StrConstant->getType(), true,
141-
GlobalValue::PrivateLinkage, StrConstant, ".str");
134+
llvm::Constant *StrConstant = llvm::ConstantDataArray::getString(*Context, strObj->chars);
135+
llvm::GlobalVariable *ValidStr = new llvm::GlobalVariable(*ModuleOb, StrConstant->getType(), true,
136+
llvm::GlobalValue::PrivateLinkage, StrConstant, ".str");
142137

143138
// Call runtime to create ObjString
144-
Function *AllocFunc = ModuleOb->getFunction("prox_rt_const_string");
145-
Value* Zero = Builder->getInt32(0);
146-
Value* Args[] = { Zero, Zero };
139+
llvm::Function *AllocFunc = ModuleOb->getFunction("prox_rt_const_string");
140+
llvm::Value* Zero = Builder->getInt32(0);
141+
llvm::Value* Args[] = { Zero, Zero };
147142
// GEP to get pointer to char array
148-
Value* StrPtr = Builder->CreateInBoundsGEP(StrConstant->getType(), ValidStr, Args);
143+
llvm::Value* StrPtr = Builder->CreateInBoundsGEP(StrConstant->getType(), ValidStr, Args);
149144

150145
v = Builder->CreateCall(AllocFunc, {
151146
StrPtr,
@@ -156,9 +151,9 @@ class LLVMEmitter {
156151
break;
157152
}
158153
case IR_OP_ADD: {
159-
Value* L = getOperand(instr->operands[0]);
160-
Value* R = getOperand(instr->operands[1]);
161-
Function *AddFunc = ModuleOb->getFunction("prox_rt_add");
154+
llvm::Value* L = getOperand(instr->operands[0]);
155+
llvm::Value* R = getOperand(instr->operands[1]);
156+
llvm::Function *AddFunc = ModuleOb->getFunction("prox_rt_add");
162157
if (L && R && AddFunc) ssaValues[instr->result] = Builder->CreateCall(AddFunc, {L, R}, "addtmp");
163158
break;
164159
}
@@ -169,30 +164,13 @@ class LLVMEmitter {
169164
break;
170165
}
171166
case IR_OP_JUMP_IF: {
172-
Value* Cond = getOperand(instr->operands[0]);
173-
BasicBlock* Then = blockMap[instr->operands[1].as.block];
174-
BasicBlock* Else = blockMap[instr->operands[2].as.block];
175-
176-
// Compare Cond != FALSE (simplified); really should check for non-false/non-nil
177-
// For now assuming Cond is a boolean-like Value
178-
// Note: In Nan-boxing, False is specific tag.
179-
// We'll simplify and check if (Cond & ~TAG_MASK) != 0 for now or just check explicit False?
180-
// For this iteration, let's assume we optimized bools to i1 in IR or strict checking.
181-
// Actually, if everything is i64, we need to compare against encoded False.
182-
183-
// Hack: Compare against 0 for testing if we used 0 for false in simple tests?
184-
// But we are using full values.
185-
// Let's rely on truthiness: != False && != Nil
186-
// For MVP: Compare != encoded FALSE.
187-
188-
// uint64_t falseVal = 0x7ffc000000000002; // TAG_FALSE = 2
189-
// Value* FalseC = ConstantInt::get(*Context, APInt(64, falseVal, false));
190-
// Value* isFalse = Builder->CreateICmpEQ(Cond, FalseC, "isfalse");
191-
// Builder->CreateCondBr(isFalse, Else, Then); // Swap branches
167+
llvm::Value* Cond = getOperand(instr->operands[0]);
168+
llvm::BasicBlock* Then = blockMap[instr->operands[1].as.block];
169+
llvm::BasicBlock* Else = blockMap[instr->operands[2].as.block];
192170

193171
// Temporary: Treat 0 as false (legacy behavior until full type lowering)
194172
if (Cond) {
195-
Value* boolCond = Builder->CreateICmpNE(Cond, ConstantInt::get(*Context, APInt(64, 0)), "ifcond");
173+
llvm::Value* boolCond = Builder->CreateICmpNE(Cond, llvm::ConstantInt::get(*Context, llvm::APInt(64, 0)), "ifcond");
196174
Builder->CreateCondBr(boolCond, Then, Else);
197175
}
198176
break;
@@ -202,11 +180,11 @@ class LLVMEmitter {
202180
break;
203181
}
204182
case IR_OP_RETURN: {
205-
Value* V = getOperand(instr->operands[0]);
183+
llvm::Value* V = getOperand(instr->operands[0]);
206184
// Default return NIL
207185
if (!V) {
208186
uint64_t nilVal = 0x7ffc000000000001;
209-
V = ConstantInt::get(*Context, APInt(64, nilVal, false));
187+
V = llvm::ConstantInt::get(*Context, llvm::APInt(64, nilVal, false));
210188
}
211189
Builder->CreateRet(V);
212190
break;
@@ -216,14 +194,14 @@ class LLVMEmitter {
216194
}
217195
}
218196

219-
Value* getOperand(IROperand& op) {
197+
llvm::Value* getOperand(IROperand& op) {
220198
if (op.type == OPERAND_CONST) {
221199
if (IS_NUMBER(op.as.constant)) {
222200
// Return int64 representation of double
223201
double num = AS_NUMBER(op.as.constant);
224202
uint64_t bits;
225203
memcpy(&bits, &num, sizeof(double));
226-
return ConstantInt::get(*Context, APInt(64, bits, false));
204+
return llvm::ConstantInt::get(*Context, llvm::APInt(64, bits, false));
227205
}
228206
// Other constants?
229207
return nullptr;

0 commit comments

Comments
 (0)