feat(ir,vm): #45 bitwise operatörler + #38 global değişken IR (Seçenek A)

#45 — BAND, BOR, SHL, SHR (binary) ve BNOT (unary ~) opcode'ları eklendi.
  Bileşik atama &=, |=, <<=, >>= destekleniyor.
  Sabit katlama güncellendi. ^ (XOR) CARET çakışması nedeniyle atlandı.

#38 — LOAD_GLOBAL / STORE_GLOBAL opcode'ları eklendi (Seçenek A: gerçek global slot).
  IRProgram.globalCount + globalNames; Interpreter.globalSlots_.
  Global init ifadeleri main'in başında üretiliyor.
  Tüm fonksiyonlar global alana erişebilir.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
saqut 2026-06-20 15:00:32 +03:00
parent 2d641863d8
commit 808efc5b4a
9 changed files with 190 additions and 31 deletions

View File

@ -55,6 +55,13 @@ enum class Opcode {
DIV, // UYARI: sıfıra bölme → runtime_error fırlatılır
MOD,
// --- Bitsel (tümü: slots[dest] = slots[left] OP slots[right]) ---
BAND, // slots[left] & slots[right]
BOR, // slots[left] | slots[right]
SHL, // slots[left] << slots[right]
SHR, // slots[left] >> slots[right]
BNOT, // ~slots[src] → slots[dest] (tekli operatör; src kullanır, left/right değil)
// --- Karşılaştırma (sonuç: 1 = doğru, 0 = yanlış) ---
LESS, // slots[left] < slots[right]
LESS_EQUAL, // slots[left] <= slots[right]
@ -75,6 +82,10 @@ enum class Opcode {
RETURN, // Bu frame'i kapat, slots[src]'yi caller'a ilet.
// --- Global değişken erişimi ---
LOAD_GLOBAL, // slots[dest] = globalSlots[intValue]
STORE_GLOBAL, // globalSlots[intValue] = slots[src]
// --- Dış dünya (FFI — Foreign Function Interface) ---
CALLHOST, // Host (C++) fonksiyonunu çağır. Şu an sadece "print" destekli.
// Dönüş değeri yok; sadece yan etki (stdout'a yazmak gibi).
@ -91,6 +102,13 @@ inline const char* opcodeName(Opcode op) {
case Opcode::MUL: return "MUL";
case Opcode::DIV: return "DIV";
case Opcode::MOD: return "MOD";
case Opcode::BAND: return "BAND";
case Opcode::BOR: return "BOR";
case Opcode::SHL: return "SHL";
case Opcode::SHR: return "SHR";
case Opcode::BNOT: return "BNOT";
case Opcode::LOAD_GLOBAL: return "LOAD_GLOBAL";
case Opcode::STORE_GLOBAL: return "STORE_GLOBAL";
case Opcode::LESS: return "LESS";
case Opcode::LESS_EQUAL: return "LESS_EQUAL";
case Opcode::GREATER: return "GREATER";

View File

@ -21,6 +21,10 @@ static const char* opSymbol(Opcode op) {
case Opcode::MUL: return "*";
case Opcode::DIV: return "/";
case Opcode::MOD: return "%";
case Opcode::BAND: return "&";
case Opcode::BOR: return "|";
case Opcode::SHL: return "<<";
case Opcode::SHR: return ">>";
case Opcode::LESS: return "<";
case Opcode::LESS_EQUAL: return "<=";
case Opcode::GREATER: return ">";
@ -35,6 +39,7 @@ static bool isBinaryOp(Opcode op) {
switch (op) {
case Opcode::ADD: case Opcode::SUB: case Opcode::MUL:
case Opcode::DIV: case Opcode::MOD:
case Opcode::BAND: case Opcode::BOR: case Opcode::SHL: case Opcode::SHR:
case Opcode::LESS: case Opcode::LESS_EQUAL:
case Opcode::GREATER: case Opcode::GREATER_EQUAL:
case Opcode::EQUAL_EQUAL: case Opcode::NOT_EQUAL:
@ -113,6 +118,15 @@ void IRFunction::dump() const {
}
std::cout << ")";
} else if (ins.opcode == Opcode::BNOT) {
std::cout << slot(ins.dest) << " = ~" << slot(ins.src);
} else if (ins.opcode == Opcode::LOAD_GLOBAL) {
std::cout << slot(ins.dest) << " = global[" << ins.intValue << "]";
} else if (ins.opcode == Opcode::STORE_GLOBAL) {
std::cout << "global[" << ins.intValue << "] = " << slot(ins.src);
} else if (ins.opcode == Opcode::RETURN) {
std::cout << slot(ins.src);
}

View File

@ -17,25 +17,40 @@
IRProgram IRGenerator::generate(ASTNode* programNode, SymbolTable& /*symbolTable*/) {
IRProgram program;
// ProgramNode'un her çocuğunu gez.
// Bizi ilgilendiren: FunctionDecl. StructDecl/GlobalVar → TODO.
// 1. Geçiş: global VariableDecl'leri topla ve kayıt et
std::vector<VariableDeclNode*> globalVars;
for (ASTNode* child : programNode->getChildren()) {
if (child->kind == ASTKind::VariableDecl) {
auto* vd = (VariableDeclNode*)child;
nameToGlobal_[vd->name] = globalCount_++;
program.globalCount++;
program.globalNames.push_back(vd->name);
globalVars.push_back(vd);
}
}
// 2. Geçiş: fonksiyonları üret
for (ASTNode* child : programNode->getChildren()) {
if (child->kind == ASTKind::FunctionDecl) {
// Her fonksiyon üretimi için sıfırla
nameToSlot_.clear();
nextSlot_ = 0;
// IRFunction oluştur, currentFunction_ olarak işaretle
auto* fnDecl = (FunctionDeclNode*)child;
IRFunction irFn(fnDecl->name, (int)fnDecl->params.size());
program.addFunction(std::move(irFn));
// addFunction std::move yaptığı için pointer'ı haritadan alalım
currentFunction_ = program.findFunction(fnDecl->name);
generateFunction(child);
// main'in başında global değişkenlerin init ifadelerini üret
if (fnDecl->name == "main") {
for (VariableDeclNode* gv : globalVars) {
if (gv->initExpr) {
int initSlot = generateExpression(gv->initExpr);
emitStoreGlobal(initSlot, nameToGlobal_[gv->name]);
}
}
}
// Fonksiyon bitti — toplam slot sayısını kaydet
generateFunction(child);
currentFunction_->slotCount = nextSlot_;
}
}
@ -344,13 +359,15 @@ int IRGenerator::generateExpression(ASTNode* node) {
}
// ── Değişken ismi: n, first, second ... ──────────────────────────────
// Bu değişkenin değeri zaten bir slotta. O slotu döndür.
case ASTKind::Identifier: {
auto* id = (IdentifierNode*)node;
std::string name = id->parserToken.token ? id->parserToken.token->token : "";
// Önce builtin mi? (print gibi) — identifier olarak gelen builtin fonksiyon
// çağrıları CallExpression içinde yakalanıyor, burada sadece değişken kalır
if (isGlobal(name)) {
int tempSlot = freshSlot();
emitLoadGlobal(tempSlot, getGlobalIndex(name));
return tempSlot;
}
return lookupVariable(name);
}
@ -358,44 +375,59 @@ int IRGenerator::generateExpression(ASTNode* node) {
case ASTKind::BinaryExpression: {
auto* bin = (BinaryExpressionNode*)node;
// Atama operatörleri: x = expr, x += expr ...
// Sol taraf bir değişken, sağ taraf hesaplanır ve o değişkene yazılır.
// Atama operatörleri: x = expr
if (bin->Operator == TokenType::EQUAL) {
// Sağ tarafı hesapla
int rhsSlot = generateExpression(bin->Right);
// Sol taraf değişkenin slotunu bul
auto* lhsId = (IdentifierNode*)bin->Left;
std::string varName = lhsId->parserToken.token->token;
int varSlot = lookupVariable(varName);
// Sonucu değişkenin slotuna kopyala
if (rhsSlot != varSlot) {
emitLoadSlot(varSlot, rhsSlot);
if (isGlobal(varName)) {
emitStoreGlobal(rhsSlot, getGlobalIndex(varName));
return rhsSlot;
}
int varSlot = lookupVariable(varName);
if (rhsSlot != varSlot) emitLoadSlot(varSlot, rhsSlot);
return varSlot;
}
// Birleşik atama: += -= *= /= %=
// Birleşik atama: += -= *= /= %= &= |= <<= >>=
// x OP= y ≡ x = x OP y
if (bin->Operator == TokenType::PLUS_EQUAL ||
bin->Operator == TokenType::MINUS_EQUAL ||
bin->Operator == TokenType::STAR_EQUAL ||
bin->Operator == TokenType::SLASH_EQUAL ||
bin->Operator == TokenType::PERCENT_EQUAL) {
bin->Operator == TokenType::PERCENT_EQUAL ||
bin->Operator == TokenType::AMPERSAND_EQUAL ||
bin->Operator == TokenType::PIPE_EQUAL ||
bin->Operator == TokenType::LSHIFT_EQUAL ||
bin->Operator == TokenType::RSHIFT_EQUAL) {
auto* lhsId = (IdentifierNode*)bin->Left;
std::string varName = lhsId->parserToken.token->token;
int varSlot = lookupVariable(varName);
int rhsSlot = generateExpression(bin->Right);
Opcode arithOp = Opcode::ADD;
if (bin->Operator == TokenType::MINUS_EQUAL) arithOp = Opcode::SUB;
else if (bin->Operator == TokenType::STAR_EQUAL) arithOp = Opcode::MUL;
else if (bin->Operator == TokenType::SLASH_EQUAL) arithOp = Opcode::DIV;
else if (bin->Operator == TokenType::PERCENT_EQUAL) arithOp = Opcode::MOD;
if (bin->Operator == TokenType::MINUS_EQUAL) arithOp = Opcode::SUB;
else if (bin->Operator == TokenType::STAR_EQUAL) arithOp = Opcode::MUL;
else if (bin->Operator == TokenType::SLASH_EQUAL) arithOp = Opcode::DIV;
else if (bin->Operator == TokenType::PERCENT_EQUAL) arithOp = Opcode::MOD;
else if (bin->Operator == TokenType::AMPERSAND_EQUAL) arithOp = Opcode::BAND;
else if (bin->Operator == TokenType::PIPE_EQUAL) arithOp = Opcode::BOR;
else if (bin->Operator == TokenType::LSHIFT_EQUAL) arithOp = Opcode::SHL;
else if (bin->Operator == TokenType::RSHIFT_EQUAL) arithOp = Opcode::SHR;
int resultSlot = freshSlot();
if (isGlobal(varName)) {
int currentSlot = freshSlot();
emitLoadGlobal(currentSlot, getGlobalIndex(varName));
emitBinaryOp(arithOp, resultSlot, currentSlot, rhsSlot);
emitStoreGlobal(resultSlot, getGlobalIndex(varName));
return resultSlot;
}
int varSlot = lookupVariable(varName);
emitBinaryOp(arithOp, resultSlot, varSlot, rhsSlot);
emitLoadSlot(varSlot, resultSlot);
return varSlot;
@ -412,12 +444,17 @@ int IRGenerator::generateExpression(ASTNode* node) {
emitLoadConst(zeroSlot, 0);
emitBinaryOp(Opcode::SUB, resultSlot, zeroSlot, operandSlot);
} else if (bin->Operator == TokenType::BANG) {
// !x → (x == 0): sıfırsa 1, değilse 0 — her zaman 0 ya da 1
// !x → (x == 0): sıfırsa 1, değilse 0
int zeroSlot = freshSlot();
emitLoadConst(zeroSlot, 0);
emitBinaryOp(Opcode::EQUAL_EQUAL, resultSlot, operandSlot, zeroSlot);
} else if (bin->Operator == TokenType::TILDE) {
// ~x — bitsel değil
Instruction ins(Opcode::BNOT);
ins.dest = resultSlot;
ins.src = operandSlot;
currentFunction_->instructions.push_back(std::move(ins));
} else {
// Diğer unary operatörler (ör. ~) → TODO
emitLoadSlot(resultSlot, operandSlot);
}
return resultSlot;
@ -438,6 +475,12 @@ int IRGenerator::generateExpression(ASTNode* node) {
case TokenType::EQUAL_EQUAL: return generateBinaryArithmetic(Opcode::EQUAL_EQUAL, bin->Left, bin->Right);
case TokenType::BANG_EQUAL: return generateBinaryArithmetic(Opcode::NOT_EQUAL, bin->Left, bin->Right);
// Bitsel operatörler
case TokenType::AMPERSAND: return generateBinaryArithmetic(Opcode::BAND, bin->Left, bin->Right);
case TokenType::PIPE: return generateBinaryArithmetic(Opcode::BOR, bin->Left, bin->Right);
case TokenType::LSHIFT: return generateBinaryArithmetic(Opcode::SHL, bin->Left, bin->Right);
case TokenType::RSHIFT: return generateBinaryArithmetic(Opcode::SHR, bin->Left, bin->Right);
// Mantıksal operatörler: kısa devre dallanmasıyla üretilir (ADR-008).
// NOT: sıradan ikili işlem değil — b, a'nın değerine göre atlanabilir.
case TokenType::AMPERSAND_AMPERSAND: {
@ -593,6 +636,29 @@ void IRGenerator::emitLoadSlot(int destSlot, int srcSlot) {
currentFunction_->instructions.push_back(std::move(ins));
}
void IRGenerator::emitLoadGlobal(int destSlot, int globalIndex) {
Instruction ins(Opcode::LOAD_GLOBAL);
ins.dest = destSlot;
ins.intValue = globalIndex;
currentFunction_->instructions.push_back(std::move(ins));
}
void IRGenerator::emitStoreGlobal(int srcSlot, int globalIndex) {
Instruction ins(Opcode::STORE_GLOBAL);
ins.src = srcSlot;
ins.intValue = globalIndex;
currentFunction_->instructions.push_back(std::move(ins));
}
bool IRGenerator::isGlobal(const std::string& name) const {
return nameToGlobal_.count(name) > 0;
}
int IRGenerator::getGlobalIndex(const std::string& name) const {
auto it = nameToGlobal_.find(name);
return (it != nameToGlobal_.end()) ? it->second : -1;
}
void IRGenerator::emitBinaryOp(Opcode op, int destSlot, int leftSlot, int rightSlot) {
Instruction ins(op);
ins.dest = destSlot;

View File

@ -56,6 +56,8 @@ private:
void emitLoadConst(int destSlot, int value);
void emitLoadSlot(int destSlot, int srcSlot);
void emitLoadGlobal(int destSlot, int globalIndex);
void emitStoreGlobal(int srcSlot, int globalIndex);
void emitBinaryOp(Opcode op, int destSlot, int leftSlot, int rightSlot);
void emitReturn(int srcSlot);
// Koşulsuz atlama yazar; instruction indeksini döndürür (backpatch için).
@ -88,9 +90,15 @@ private:
IRFunction* currentFunction_ = nullptr; // şu an üretilen fonksiyon
int nextSlot_ = 0; // sıradaki boş slot numarası
// Değişken ismi → slot numarası.
// Sınırlama: aynı isimdeki farklı scope değişkenleri çakışır (TODO).
// Değişken ismi → slot numarası (lokal).
std::unordered_map<std::string, int> nameToSlot_;
// Global değişken ismi → global index
std::unordered_map<std::string, int> nameToGlobal_;
int globalCount_ = 0;
bool isGlobal(const std::string& name) const;
int getGlobalIndex(const std::string& name) const;
};
#endif // SAQUT_IR_GENERATOR

View File

@ -3,6 +3,14 @@
void IRProgram::dump() const {
std::cout << "IR DUMP\n\n";
if (globalCount > 0) {
std::cout << "GLOBALS (" << globalCount << ")\n";
for (int i = 0; i < (int)globalNames.size(); i++)
std::cout << " global[" << i << "] = " << globalNames[i] << "\n";
std::cout << "\n";
}
for (const auto& name : functionOrder) {
auto it = functions.find(name);
if (it != functions.end()) it->second.dump();

View File

@ -29,6 +29,10 @@ struct IRProgram {
// Ekleme sırası (dump'ta orijinal sırayla göstermek için)
std::vector<std::string> functionOrder;
// Global değişkenler (LOAD_GLOBAL / STORE_GLOBAL için)
int globalCount = 0;
std::vector<std::string> globalNames; // index → isim (dump için)
// Yeni fonksiyon ekle
void addFunction(IRFunction fn) {
functionOrder.push_back(fn.name);

View File

@ -104,6 +104,10 @@ private:
case TokenType::GREATER:
case TokenType::LESS_EQUAL:
case TokenType::GREATER_EQUAL:
case TokenType::AMPERSAND:
case TokenType::PIPE:
case TokenType::LSHIFT:
case TokenType::RSHIFT:
case TokenType::AMPERSAND_AMPERSAND:
case TokenType::PIPE_PIPE:
return true;
@ -125,6 +129,10 @@ private:
case TokenType::GREATER: return l > r ? 1 : 0;
case TokenType::LESS_EQUAL: return l <= r ? 1 : 0;
case TokenType::GREATER_EQUAL: return l >= r ? 1 : 0;
case TokenType::AMPERSAND: return l & r;
case TokenType::PIPE: return l | r;
case TokenType::LSHIFT: return l << r;
case TokenType::RSHIFT: return l >> r;
case TokenType::AMPERSAND_AMPERSAND: return (l && r) ? 1 : 0;
case TokenType::PIPE_PIPE: return (l || r) ? 1 : 0;
default: return 0;

View File

@ -3,6 +3,9 @@
#include <stdexcept>
int Interpreter::run() {
// Global slot'ları sıfırla
globalSlots_.assign(program_.globalCount, Value::fromInt(0));
IRFunction* mainFunction = program_.findFunction("main");
if (!mainFunction)
throw std::runtime_error("Çalışma hatası: 'main' fonksiyonu bulunamadı");
@ -70,6 +73,35 @@ int Interpreter::run() {
break;
}
// ── Bitsel ────────────────────────────────────────────────────────
case Opcode::BAND:
frame.slots[instr.dest] = Value::fromInt(
frame.slots[instr.left].intValue & frame.slots[instr.right].intValue);
break;
case Opcode::BOR:
frame.slots[instr.dest] = Value::fromInt(
frame.slots[instr.left].intValue | frame.slots[instr.right].intValue);
break;
case Opcode::SHL:
frame.slots[instr.dest] = Value::fromInt(
frame.slots[instr.left].intValue << frame.slots[instr.right].intValue);
break;
case Opcode::SHR:
frame.slots[instr.dest] = Value::fromInt(
frame.slots[instr.left].intValue >> frame.slots[instr.right].intValue);
break;
case Opcode::BNOT:
frame.slots[instr.dest] = Value::fromInt(~frame.slots[instr.src].intValue);
break;
// ── Global değişken erişimi ────────────────────────────────────────
case Opcode::LOAD_GLOBAL:
frame.slots[instr.dest] = globalSlots_[instr.intValue];
break;
case Opcode::STORE_GLOBAL:
globalSlots_[instr.intValue] = frame.slots[instr.src];
break;
// ── Karşılaştırma ─────────────────────────────────────────────────
case Opcode::LESS:
frame.slots[instr.dest] = Value::fromInt(

View File

@ -28,6 +28,7 @@ public:
private:
IRProgram& program_;
std::vector<CallFrame> callStack_;
std::vector<Value> globalSlots_;
// Host (C++) fonksiyon çağrısı — şu an sadece "print" destekli
void executeHostFunction(const std::string& name,