/
vit1251
/
cmm
Обзор
Документация
Войти
/
vit1251
/
cmm
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/codegen.cpp
1 784 строки
85 KB
Vitold S
update
09 авг 2026, 02:25
09 авг 2026, 02:25
a36ae1e
Код
Авторство
О чём код?
#include "codegen.h" #include "llvm/IR/Function.h" #include "llvm/IR/Type.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/Verifier.h" #include "llvm/IR/Intrinsics.h" #include "llvm/IR/GlobalVariable.h" #include "llvm/IR/InlineAsm.h" #include "llvm/IR/Constants.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Support/FileSystem.h" #include "llvm/Target/TargetMachine.h" #include "llvm/MC/TargetRegistry.h" #include "llvm/Support/TargetSelect.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/TargetParser/Host.h" #include "types_ser.h" #include <optional> #include <unordered_map> Codegen::Codegen() : ctx_(std::make_unique<llvm::LLVMContext>()), mod_(std::make_unique<llvm::Module>("cmm_module", *ctx_)), builder_(std::make_unique<llvm::IRBuilder<>>(*ctx_)) { llvm::InitializeAllTargetInfos(); llvm::InitializeAllTargets(); llvm::InitializeAllTargetMCs(); llvm::InitializeAllAsmParsers(); llvm::InitializeAllAsmPrinters(); auto target_triple = llvm::sys::getDefaultTargetTriple(); mod_->setTargetTriple(target_triple); std::string error; auto target = llvm::TargetRegistry::lookupTarget(target_triple, error); if (target) { std::optional<llvm::Reloc::Model> rm; auto tm = target->createTargetMachine(target_triple, "generic", "", {}, rm); mod_->setDataLayout(tm->createDataLayout()); } } Codegen::~Codegen() = default; llvm::LLVMContext& Codegen::ctx() { return *ctx_; } // Parse type args from a generic type name like "Result<WavWriter,i32>" static std::vector<std::string> parse_type_args(const std::string& name) { auto start = name.find('<'); auto end = name.rfind('>'); if (start == std::string::npos || end == std::string::npos || end <= start) return {}; std::string inner = name.substr(start + 1, end - start - 1); std::vector<std::string> args; int depth = 0; size_t begin = 0; for (size_t i = 0; i < inner.size(); i++) { if (inner[i] == '<') depth++; else if (inner[i] == '>') depth--; else if (inner[i] == ',' && depth == 0) { args.push_back(inner.substr(begin, i - begin)); begin = i + 1; } } if (begin < inner.size()) args.push_back(inner.substr(begin)); return args; } llvm::Type* Codegen::monomorphize_struct(const std::string& base, const std::string& full_name) { auto gen_it = structs_.find(base); if (gen_it == structs_.end() || gen_it->second.type_params.empty()) { fprintf(stderr, "error: '%s' is not a generic struct\n", base.c_str()); return llvm::PointerType::get(ctx(), 0); } auto& gen = gen_it->second; auto args = parse_type_args(full_name); if (args.size() != gen.type_params.size()) { fprintf(stderr, "error: wrong number of type args for '%s': expected %zu, got %zu\n", base.c_str(), gen.type_params.size(), args.size()); return llvm::PointerType::get(ctx(), 0); } // Build substitution map std::unordered_map<std::string, std::string> subst; for (size_t i = 0; i < gen.type_params.size(); i++) subst[gen.type_params[i]] = args[i]; // Resolve field types with substitution std::vector<llvm::Type*> field_types; StructInfo info; info.type_params = gen.type_params; // keep original params for (size_t i = 0; i < gen.field_names.size(); i++) { std::string ft = gen.field_type_names[i]; // Substitute type params in field type name for (auto& [param, arg] : subst) { size_t pos; while ((pos = ft.find(param)) != std::string::npos) { ft.replace(pos, param.size(), arg); } } field_types.push_back(resolve_type(ft)); info.field_names.push_back(gen.field_names[i]); info.field_type_names.push_back(ft); info.field_index[gen.field_names[i]] = i; } info.type = llvm::StructType::create(*ctx_, field_types, full_name); structs_[full_name] = info; // Concrete method generation is deferred to after all functions are processed return info.type; } llvm::Type* Codegen::resolve_type(const std::string& name) { if (name == "i8" || name == "u8") return llvm::Type::getInt8Ty(ctx()); if (name == "i16" || name == "u16") return llvm::Type::getInt16Ty(ctx()); if (name == "i32" || name == "u32") return llvm::Type::getInt32Ty(ctx()); if (name == "i64" || name == "u64") return llvm::Type::getInt64Ty(ctx()); if (name == "f32") return llvm::Type::getFloatTy(ctx()); if (name == "f64") return llvm::Type::getDoubleTy(ctx()); if (name == "bool") return llvm::Type::getInt1Ty(ctx()); if (name == "void") return llvm::Type::getVoidTy(ctx()); if (!name.empty() && name[0] == '*') return llvm::PointerType::get(ctx(), 0); // Array type: [N]T if (!name.empty() && name[0] == '[') { size_t end = name.find(']'); if (end != std::string::npos) { std::string count_str = name.substr(1, end - 1); std::string elem_name = name.substr(end + 1); auto* elem_ty = resolve_type(elem_name); uint64_t count = std::stoull(count_str); return llvm::ArrayType::get(elem_ty, count); } } // Resolve type aliases auto ait = use_aliases_.find(name); if (ait != use_aliases_.end()) { return resolve_type(ait->second); } // Generic type: Result<T,E> — check if base is a generic struct auto ga_pos = name.find('<'); if (ga_pos != std::string::npos) { std::string base = name.substr(0, ga_pos); auto si = structs_.find(base); if (si != structs_.end() && !si->second.type_params.empty()) { return monomorphize_struct(base, name); } } // Check registered structs (by value) auto it = structs_.find(name); if (it != structs_.end()) return it->second.type; // struct by value fprintf(stderr, "warning: unknown type '%s', treating as ptr\n", name.c_str()); return llvm::PointerType::get(ctx(), 0); } // ---- Struct registration ---- void Codegen::register_struct(const StructDecl& sd) { StructInfo info; info.type_params = sd.type_params; // Store a pointer to the decl for monomorphization (safe — prog outlives codegen) // We use a non-const reference hack; the decl is const here but we won't modify it // Actually store by copying needed fields into a local StructDecl for (unsigned i = 0; i < sd.fields.size(); i++) { info.field_names.push_back(sd.fields[i].name); info.field_type_names.push_back(sd.fields[i].type_name); info.field_index[sd.fields[i].name] = i; } if (!sd.type_params.empty()) { // Generic struct — don't create LLVM type yet // Just store the info for monomorphization structs_[sd.name] = info; return; } // Concrete struct — resolve field types and create LLVM type std::vector<llvm::Type*> field_types; for (auto& tn : info.field_type_names) field_types.push_back(resolve_type(tn)); info.type = llvm::StructType::create(*ctx_, field_types, sd.name); structs_[sd.name] = info; } // ---- Generate ---- void Codegen::resolve_imports(Program& prog, const std::string& main_file_path) { use_aliases_.clear(); use_allowed_.clear(); fn_source_module_.clear(); // Build function → module map for (auto& fn : prog.functions) { if (fn->is_extern) continue; fn_source_module_[fn->name] = fn->source_module; } // Determine import mode for each module: // 0 = not directly imported (transitive dep, always available) // 1 = `use module;` (no symbols imported) // 2 = `use module::{*};` (wildcard — all symbols) // 3 = `use module::{a,b};` (filtered — only listed symbols) std::unordered_map<std::string, int> module_mode; for (auto& imp : prog.imports) { int mode = 1; if (imp.wildcard) mode = 2; else if (!imp.use_items.empty()) mode = 3; auto it = module_mode.find(imp.path); if (it == module_mode.end()) { module_mode[imp.path] = mode; } else { if (mode > it->second) it->second = mode; } } // Helper: check if a source file belongs to a given module path auto file_belongs_to_module = [](const std::string& fmod, const std::string& mod_path) -> bool { if (fmod == mod_path + ".cmm") return true; // Match "path/to/module.cmm" against "to/module" or "module" std::string prefix = "/" + mod_path + ".cmm"; if (fmod.size() >= prefix.size() && fmod.compare(fmod.size() - prefix.size(), prefix.size(), prefix) == 0) return true; // Also match direct subpath: "net/tcp.cmm" for module "net/tcp" if (fmod.find(mod_path + ".cmm") != std::string::npos) return true; return false; }; // Allow functions from wildcard modules and from modules not directly imported for (auto& [fname, fmod] : fn_source_module_) { bool in_imported = false; bool allowed = false; for (auto& [mod_path, mode] : module_mode) { if (file_belongs_to_module(fmod, mod_path)) { in_imported = true; if (mode == 2) allowed = true; break; } } if (!in_imported || allowed) use_allowed_.insert(fname); } // For filtered modules, add only the explicitly imported names + aliases for (auto& imp : prog.imports) { if (imp.wildcard || imp.use_items.empty()) continue; for (auto& item : imp.use_items) { use_allowed_.insert(item.name); use_allowed_.insert(item.alias.empty() ? item.name : item.alias); if (!item.alias.empty()) use_aliases_[item.alias] = item.name; } } // When a struct is imported, also allow its public static methods std::vector<std::string> imported_structs; for (auto& sd : prog.structs) { if (use_allowed_.count(sd.name)) imported_structs.push_back(sd.name); } for (auto& [alias, original] : use_aliases_) { if (use_allowed_.count(alias)) imported_structs.push_back(original); } std::sort(imported_structs.begin(), imported_structs.end(), [](const std::string& a, const std::string& b) { return a.length() > b.length(); }); for (auto& fn : prog.functions) { if (!fn->is_pub || !fn->is_method) continue; for (auto& sd_name : imported_structs) { std::string prefix = sd_name + "_"; if (fn->name.rfind(prefix, 0) == 0) { use_allowed_.insert(fn->name); break; } } } // Always allow: main and all extern functions use_allowed_.insert("main"); for (auto& fn : prog.functions) if (fn->is_extern) use_allowed_.insert(fn->name); } void Codegen::generate(Program& prog, const std::string& module_name) { // Set module name mod_->setModuleIdentifier(module_name); // Register structs first (skip if already registered from .o import) for (auto& sd : prog.structs) { if (structs_.count(sd.name)) continue; register_struct(sd); } // Move generic impl methods out of prog.functions // Detect by checking if any param type contains '<' (generic type reference) std::vector<std::unique_ptr<FnDecl>> nongeneric_fns; for (auto& fn : prog.functions) { if (fn->is_extern) { nongeneric_fns.push_back(std::move(fn)); continue; } bool is_gen = false; // Check if this function references generic types (has '<' in params or return) for (auto& p : fn->params) { if (p.type_name.find('<') != std::string::npos) { is_gen = true; break; } } if (!is_gen && fn->ret_type_name.find('<') != std::string::npos) is_gen = true; if (is_gen) { // Find the base generic struct name (everything before first '<' in params) std::string base; for (auto& p : fn->params) { auto pos = p.type_name.find('<'); if (pos != std::string::npos) { auto stars = p.type_name.rfind('*', pos); base = p.type_name.substr((stars != std::string::npos) ? stars + 1 : 0, pos - ((stars != std::string::npos) ? stars + 1 : 0)); break; } } if (!base.empty() && base.find('*') == std::string::npos) { // Store as generic template auto& gm = generic_methods_[base].emplace_back(); gm.name = fn->name; gm.ret_type_name = fn->ret_type_name; gm.params = fn->params; gm.is_extern = false; gm.is_pub = fn->is_pub; gm.loc = fn->loc; gm.body = std::move(fn->body); } continue; // skip this fn, not added to nongeneric_fns } nongeneric_fns.push_back(std::move(fn)); } prog.functions.swap(nongeneric_fns); // Process remaining (non-generic) functions for (auto& fn : prog.functions) { if (!fn->is_extern) { codegen_fn_decl(*fn); } else { auto* ret_type = resolve_type(fn->ret_type_name); std::vector<llvm::Type*> param_types; for (auto& p : fn->params) param_types.push_back(resolve_type(p.type_name)); auto* ft = llvm::FunctionType::get(ret_type, param_types, fn->is_var_arg); auto* func = llvm::Function::Create(ft, llvm::Function::ExternalLinkage, fn->name, mod_.get()); // Extern functions must have default visibility (for linker resolution) // Non-extern pub functions are also default // Non-extern non-pub are hidden // Set calling convention if (fn->call_conv == "C" || fn->call_conv == "cdecl") func->setCallingConv(llvm::CallingConv::C); else if (fn->call_conv == "stdcall") func->setCallingConv(llvm::CallingConv::X86_StdCall); else if (fn->call_conv == "fastcall") func->setCallingConv(llvm::CallingConv::X86_FastCall); } } // Auto-generate _start only for standalone (no libs linked) bool has_link = false; for (auto& fn : prog.functions) if (fn->is_extern && !fn->lib_name.empty()) { has_link = true; break; } if (!has_link) generate_start(); // Deferred monomorphization: generate concrete methods for all generic instantiations // (instantiations were tracked in structs_ during resolve_type calls) for (auto& [name, si] : structs_) { // Check if this is a concrete instantiation (contains _ in name but base is generic) auto ga_pos = name.find('<'); if (ga_pos == std::string::npos) continue; // not a generic type std::string base = name.substr(0, ga_pos); auto gen_it = structs_.find(base); if (gen_it == structs_.end() || gen_it->second.type_params.empty()) continue; // Only generate if there are methods for this base if (generic_methods_.count(base) == 0) continue; auto targs = parse_type_args(name); if (targs.size() != gen_it->second.type_params.size()) continue; monomorphize_methods(base, name, targs); } // Emit .cmm_types section std::string type_data = serialize_cmm_types(prog, module_name); if (!type_data.empty()) { auto* arr = llvm::ConstantDataArray::getString(ctx(), type_data, false); auto* gv = new llvm::GlobalVariable(*mod_, arr->getType(), true, llvm::GlobalValue::PrivateLinkage, arr, "__cmm_types_data"); gv->setSection(".cmm_types"); gv->setAlignment(llvm::MaybeAlign(1)); } } // ---- Function codegen ---- void Codegen::codegen_fn_decl(FnDecl& fn) { auto* ret_type = resolve_type(fn.ret_type_name); std::vector<llvm::Type*> param_types; for (auto& p : fn.params) param_types.push_back(resolve_type(p.type_name)); auto* ft = llvm::FunctionType::get(ret_type, param_types, fn.is_var_arg); // Name mangling: все функции кроме main, extern, #[no_mangle] std::string fn_name = fn.name; if (fn_name != "main" && !fn.is_extern && !fn.is_no_mangle) { std::string mod = fn.source_module; size_t slash = mod.rfind('/'); if (slash != std::string::npos) mod = mod.substr(slash + 1); size_t dot = mod.rfind('.'); if (dot != std::string::npos) mod = mod.substr(0, dot); fn_name = "_Cmm_" + mod + "_" + fn_name; } auto* func = llvm::Function::Create(ft, llvm::Function::ExternalLinkage, fn_name, mod_.get()); // Put each function in its own section for --gc-sections if (fn_name != "main") func->setSection(".text." + fn_name); if (fn.name == "main") func->setVisibility(llvm::GlobalValue::DefaultVisibility); else func->setVisibility(fn.is_pub ? llvm::GlobalValue::DefaultVisibility : llvm::GlobalValue::HiddenVisibility); current_func_ = func; fn_.locals.clear(); fn_.local_types.clear(); fn_.dropped.clear(); fn_.defer_stack.clear(); auto* entry = llvm::BasicBlock::Create(ctx(), "entry", func); builder_->SetInsertPoint(entry); size_t idx = 0; for (auto& arg : func->args()) { arg.setName(fn.params[idx].name); auto* alloca = builder_->CreateAlloca(arg.getType(), nullptr, fn.params[idx].name); builder_->CreateStore(&arg, alloca); fn_.locals[fn.params[idx].name] = alloca; fn_.local_types[fn.params[idx].name] = fn.params[idx].type_name; idx++; } auto* result = codegen_block(*fn.body, ret_type); if (ret_type->isVoidTy()) { if (!builder_->GetInsertBlock()->getTerminator()) builder_->CreateRetVoid(); } else if (result) { if (!builder_->GetInsertBlock()->getTerminator()) builder_->CreateRet(result); } else { if (!builder_->GetInsertBlock()->getTerminator()) builder_->CreateRet(llvm::ConstantInt::get(ret_type, 0)); } if (llvm::verifyFunction(*func, &llvm::outs())) { fprintf(stderr, "error: function verification failed in '%s'\n", fn.name.c_str()); func->print(llvm::outs()); } current_func_ = nullptr; } // ---- .o import helpers ---- bool Codegen::import_struct(const StructDecl& sd) { if (structs_.count(sd.name)) return false; register_struct(sd); return true; } void Codegen::import_fn(const std::string& fn_name, llvm::Type* ret_type, std::vector<llvm::Type*> param_types) { if (mod_->getFunction(fn_name)) return; auto* ft = llvm::FunctionType::get(ret_type, param_types, false); llvm::Function::Create(ft, llvm::Function::ExternalLinkage, fn_name, mod_.get()); } void Codegen::monomorphize_methods(const std::string& base, const std::string& full_name, const std::vector<std::string>& type_args) { auto gm = generic_methods_.find(base); if (gm == generic_methods_.end()) return; auto* concrete_type = structs_[full_name].type; if (!concrete_type) return; auto& sinfo = structs_[full_name]; auto& gen = structs_[base]; // Build mangled concrete type name: match parser mangling (skip >) std::string type_mangled; for (char c : full_name) { if (c == '<' || c == ',') type_mangled += '_'; else if (c == '>') continue; else type_mangled += c; } // Find field indices auto idx_is_ok = sinfo.field_index.find("is_ok"); auto idx_ok = sinfo.field_index.find("ok"); auto idx_err = sinfo.field_index.find("err"); if (idx_is_ok == sinfo.field_index.end() || idx_ok == sinfo.field_index.end()) return; // Get abort function auto* abort_fn = mod_->getFunction("abort"); if (!abort_fn) { auto* void_ty = llvm::Type::getVoidTy(ctx()); auto* ft = llvm::FunctionType::get(void_ty, false); abort_fn = llvm::Function::Create(ft, llvm::Function::ExternalLinkage, "abort", mod_.get()); } // Build substitution map: template param names → concrete type names std::unordered_map<std::string, std::string> subst; auto& gen_info = structs_[base]; auto targs = parse_type_args(full_name); for (size_t i = 0; i < gen_info.type_params.size() && i < targs.size(); i++) subst[gen_info.type_params[i]] = targs[i]; // Substitute type params in a string auto subst_str = [&](const std::string& s) -> std::string { std::string r = s; for (auto& [p, a] : subst) { size_t pos; while ((pos = r.find(p)) != std::string::npos) r.replace(pos, p.size(), a); } return r; }; // Build the mangled type prefix from the generic template name // template name format: base_T_E_method (e.g., Result_T_E_is_ok) // type_prefix = base + "_" + param1 + "_" + param2 + ... = Result_T_E std::string type_prefix = base; for (auto& tp : gen_info.type_params) type_prefix += "_" + tp; for (auto& tpl : gm->second) { // Skip templates that don't start with the expected prefix if (tpl.name.size() <= type_prefix.size() || tpl.name.substr(0, type_prefix.size()) != type_prefix || tpl.name[type_prefix.size()] != '_') continue; std::string method_no_underscore = tpl.name.substr(type_prefix.size() + 1); std::string concrete_name = type_mangled + "_" + method_no_underscore; // Resolve param types with substitution std::vector<llvm::Type*> param_types; for (auto& p : tpl.params) param_types.push_back(resolve_type(subst_str(p.type_name))); auto* ret_type = resolve_type(subst_str(tpl.ret_type_name)); auto* ft = llvm::FunctionType::get(ret_type, param_types, false); // Check if function already exists (declaration from method dispatch) auto* func = mod_->getFunction(concrete_name); if (func) { // Replace declaration with definition — remove old one // LLVM doesn't allow replacing, so just delete and recreate func->eraseFromParent(); } func = llvm::Function::Create(ft, llvm::Function::ExternalLinkage, concrete_name, mod_.get()); func->setVisibility(llvm::GlobalValue::HiddenVisibility); current_func_ = func; auto* entry = llvm::BasicBlock::Create(ctx(), "entry", func); builder_->SetInsertPoint(entry); // Allocate self auto* self_alloca = builder_->CreateAlloca(llvm::PointerType::get(ctx(), 0), nullptr, "self"); auto* self_val = func->arg_begin(); builder_->CreateStore(&*self_val, self_alloca); // GEP to is_ok field auto* is_ok_gep = builder_->CreateStructGEP(concrete_type, &*self_val, idx_is_ok->second, "is_ok_gep"); auto* is_ok = builder_->CreateLoad(llvm::Type::getInt8Ty(ctx()), is_ok_gep, "is_ok"); auto* is_ok_bool = builder_->CreateICmpNE(is_ok, llvm::ConstantInt::get(llvm::Type::getInt8Ty(ctx()), 0)); // if !is_ok → abort auto* ok_bb = llvm::BasicBlock::Create(ctx(), "ok", func); auto* abort_bb = llvm::BasicBlock::Create(ctx(), "abort", func); builder_->CreateCondBr(is_ok_bool, ok_bb, abort_bb); builder_->SetInsertPoint(abort_bb); std::vector<llvm::Value*> no_args; builder_->CreateCall(abort_fn, no_args); builder_->CreateUnreachable(); builder_->SetInsertPoint(ok_bb); // Load the ok field and return auto* ok_gep = builder_->CreateStructGEP(concrete_type, &*self_val, idx_ok->second, "ok_gep"); auto* ok_val = builder_->CreateLoad(ret_type, ok_gep, "ok_val"); builder_->CreateRet(ok_val); llvm::verifyFunction(*func); } current_func_ = nullptr; } // ---- _start generation ---- void Codegen::generate_start() { auto* main_fn = mod_->getFunction("main"); if (!main_fn) return; if (mod_->getFunction("_start")) return; auto* i64_ty = llvm::Type::getInt64Ty(ctx()); auto* i32_ty = llvm::Type::getInt32Ty(ctx()); auto* void_ty = llvm::Type::getVoidTy(ctx()); auto* ptr_ty = llvm::PointerType::get(ctx(), 0); // Ensure sys_exit is declared (resolved from .s file by linker) auto* exit_fn = mod_->getFunction("sys_exit"); if (!exit_fn) { auto* exit_ft = llvm::FunctionType::get(i64_ty, {i64_ty}, false); exit_fn = llvm::Function::Create(exit_ft, llvm::Function::ExternalLinkage, "sys_exit", mod_.get()); } auto* ft = llvm::FunctionType::get(void_ty, false); auto* start = llvm::Function::Create(ft, llvm::Function::ExternalLinkage, "_start", mod_.get()); auto* entry = llvm::BasicBlock::Create(ctx(), "entry", start); builder_->SetInsertPoint(entry); llvm::Value* ret; // Check if main expects argc/argv (main(i32, **u8)) if (main_fn->arg_size() == 2) { // Read argc/argv from initial stack (x86_64 ABI: [rsp] = argc, [rsp+8] = argv) auto* read_rsp = llvm::InlineAsm::get( llvm::FunctionType::get(i64_ty, false), "mov %rsp, $0", "=r", true); auto* rsp_val = builder_->CreateCall(read_rsp, {}); auto* argc_ptr = builder_->CreateIntToPtr(rsp_val, ptr_ty); auto* argc = builder_->CreateLoad(i32_ty, argc_ptr); auto* argv_gep = builder_->CreateGEP(i64_ty, argc_ptr, llvm::ConstantInt::get(i64_ty, 1)); auto* argv = builder_->CreateLoad(ptr_ty, argv_gep); std::vector<llvm::Value*> main_args = {argc, argv}; ret = builder_->CreateCall(main_fn, main_args); } else { std::vector<llvm::Value*> main_args; ret = builder_->CreateCall(main_fn, main_args); } auto* ext = builder_->CreateSExt(ret, i64_ty); builder_->CreateCall(exit_fn, {ext}); builder_->CreateUnreachable(); if (llvm::verifyFunction(*start, &llvm::outs())) { fprintf(stderr, "error: _start verification failed\n"); start->print(llvm::outs()); } } llvm::AllocaInst* Codegen::create_entry_alloca(llvm::Type* ty, const std::string& name) { if (current_func_) { auto& entry_block = current_func_->getEntryBlock(); if (entry_block.empty()) { // Function has empty entry block — insert at end return new llvm::AllocaInst(ty, 0, name, &entry_block); } llvm::IRBuilder<> entry_builder(&entry_block, entry_block.getFirstInsertionPt()); return entry_builder.CreateAlloca(ty, nullptr, name); } return builder_->CreateAlloca(ty, nullptr, name); } // ---- Block ---- llvm::Value* Codegen::codegen_block(Block& block, llvm::Type* expected_type) { fn_.defer_stack.emplace_back(); llvm::Value* last_val = nullptr; for (auto& stmt : block.stmts) last_val = codegen_stmt(*stmt); if (block.result) last_val = codegen_expr(*block.result, expected_type); // Emit deferred expressions in LIFO order auto& defers = fn_.defer_stack.back(); for (auto it = defers.rbegin(); it != defers.rend(); ++it) { if (auto* defer_expr = it->get()) { auto* v = codegen_expr(*defer_expr, nullptr); if (v) last_val = v; } } fn_.defer_stack.pop_back(); if (block.result) return last_val; if (last_val && expected_type && last_val->getType() == expected_type && !expected_type->isVoidTy()) return last_val; return nullptr; } // ---- Statement ---- llvm::Value* Codegen::codegen_stmt(Stmt& stmt) { switch (stmt.kind) { case Stmt::Let: { fn_.dropped.erase(stmt.let_name); // разрешить переобъявление после drop auto* target_ty = resolve_type(stmt.let_type); auto* init = codegen_expr(*stmt.let_init, target_ty); // Convert types if needed if (init->getType() != target_ty) { if (init->getType()->isFloatTy() || init->getType()->isDoubleTy()) { if (target_ty->isIntegerTy()) init = builder_->CreateFPToSI(init, target_ty); } else if (init->getType()->isIntegerTy()) { if (target_ty->isFloatTy() || target_ty->isDoubleTy()) init = builder_->CreateSIToFP(init, target_ty); else if (target_ty->isIntegerTy()) init = builder_->CreateIntCast(init, target_ty, false); } } auto* alloca = create_entry_alloca(target_ty, stmt.let_name); builder_->CreateStore(init, alloca); fn_.locals[stmt.let_name] = alloca; fn_.local_types[stmt.let_name] = stmt.let_type; return nullptr; } case Stmt::ExprStmt: return codegen_expr(*stmt.expr, nullptr); case Stmt::If: { auto* cond = codegen_expr(*stmt.if_cond, llvm::Type::getInt1Ty(ctx())); if (cond->getType()->isIntegerTy() && cond->getType()->getIntegerBitWidth() > 1) cond = builder_->CreateICmpNE(cond, llvm::ConstantInt::get(cond->getType(), 0)); auto* func = current_func_; auto* then_bb = llvm::BasicBlock::Create(ctx(), "then", func); auto* else_bb = llvm::BasicBlock::Create(ctx(), "else"); auto* merge_bb = llvm::BasicBlock::Create(ctx(), "ifend"); builder_->CreateCondBr(cond, then_bb, else_bb); builder_->SetInsertPoint(then_bb); auto* then_val = codegen_block(*stmt.if_then, nullptr); if (!builder_->GetInsertBlock()->getTerminator()) builder_->CreateBr(merge_bb); then_bb = builder_->GetInsertBlock(); func->insert(func->end(), else_bb); builder_->SetInsertPoint(else_bb); llvm::Value* else_val = nullptr; if (stmt.if_else) else_val = codegen_block(*stmt.if_else, nullptr); if (!builder_->GetInsertBlock()->getTerminator()) builder_->CreateBr(merge_bb); else_bb = builder_->GetInsertBlock(); func->insert(func->end(), merge_bb); builder_->SetInsertPoint(merge_bb); if (then_val && else_val && then_val->getType() == else_val->getType()) { auto* phi = builder_->CreatePHI(then_val->getType(), 2); phi->addIncoming(then_val, then_bb); phi->addIncoming(else_val, else_bb); return phi; } return then_val ? then_val : else_val; } case Stmt::For: { auto* func = current_func_; if (stmt.for_kind == Stmt::Infinite) { auto* body_bb = llvm::BasicBlock::Create(ctx(), "forbody", func); auto* end_bb = llvm::BasicBlock::Create(ctx(), "forend"); builder_->CreateBr(body_bb); builder_->SetInsertPoint(body_bb); codegen_block(*stmt.for_body, nullptr); if (!builder_->GetInsertBlock()->getTerminator()) builder_->CreateBr(body_bb); func->insert(func->end(), end_bb); builder_->SetInsertPoint(end_bb); } else if (stmt.for_kind == Stmt::While) { auto* cond_bb = llvm::BasicBlock::Create(ctx(), "forcond", func); auto* body_bb = llvm::BasicBlock::Create(ctx(), "forbody"); auto* end_bb = llvm::BasicBlock::Create(ctx(), "forend"); builder_->CreateBr(cond_bb); builder_->SetInsertPoint(cond_bb); auto* cond = codegen_expr(*stmt.for_cond, llvm::Type::getInt1Ty(ctx())); if (cond->getType()->isIntegerTy() && cond->getType()->getIntegerBitWidth() > 1) cond = builder_->CreateICmpNE(cond, llvm::ConstantInt::get(cond->getType(), 0)); builder_->CreateCondBr(cond, body_bb, end_bb); func->insert(func->end(), body_bb); builder_->SetInsertPoint(body_bb); codegen_block(*stmt.for_body, nullptr); if (!builder_->GetInsertBlock()->getTerminator()) builder_->CreateBr(cond_bb); func->insert(func->end(), end_bb); builder_->SetInsertPoint(end_bb); } else if (stmt.for_kind == Stmt::Iter) { auto* cond_bb = llvm::BasicBlock::Create(ctx(), "forcond", func); auto* body_bb = llvm::BasicBlock::Create(ctx(), "forbody"); auto* end_bb = llvm::BasicBlock::Create(ctx(), "forend"); auto* range_expr = stmt.for_iter.get(); llvm::Value* start_val, *end_val; if (range_expr->kind == Expr::RangeLiteral) { start_val = codegen_expr(*range_expr->range_start, llvm::Type::getInt64Ty(ctx())); end_val = codegen_expr(*range_expr->range_end, llvm::Type::getInt64Ty(ctx())); } else { start_val = llvm::ConstantInt::get(llvm::Type::getInt64Ty(ctx()), 0); end_val = llvm::ConstantInt::get(llvm::Type::getInt64Ty(ctx()), 0); } auto* counter = create_entry_alloca(llvm::Type::getInt64Ty(ctx()), stmt.for_var); fn_.locals[stmt.for_var] = counter; fn_.local_types[stmt.for_var] = "i64"; builder_->CreateStore(start_val, counter); builder_->CreateBr(cond_bb); builder_->SetInsertPoint(cond_bb); auto* x_val = builder_->CreateLoad(llvm::Type::getInt64Ty(ctx()), counter, stmt.for_var); auto* cond = builder_->CreateICmpSLT(x_val, end_val); builder_->CreateCondBr(cond, body_bb, end_bb); func->insert(func->end(), body_bb); builder_->SetInsertPoint(body_bb); codegen_block(*stmt.for_body, nullptr); auto* x_cur = builder_->CreateLoad(llvm::Type::getInt64Ty(ctx()), counter); auto* x_next = builder_->CreateAdd(x_cur, llvm::ConstantInt::get(llvm::Type::getInt64Ty(ctx()), 1)); builder_->CreateStore(x_next, counter); if (!builder_->GetInsertBlock()->getTerminator()) builder_->CreateBr(cond_bb); func->insert(func->end(), end_bb); builder_->SetInsertPoint(end_bb); } return nullptr; } case Stmt::BlockStmt: return codegen_block(*stmt.block, nullptr); case Stmt::Defer: { fn_.defer_stack.back().push_back(std::move(stmt.defer_expr)); return nullptr; } case Stmt::Return: { // Emit all pending defers (top-down, LIFO within each level) for (auto& level : fn_.defer_stack) { for (auto it = level.rbegin(); it != level.rend(); ++it) { if (auto* expr = it->get()) codegen_expr(*expr, nullptr); } } // Evaluate return expression and return auto* ret_type = current_func_->getReturnType(); if (stmt.ret_expr) { auto* val = codegen_expr(*stmt.ret_expr, ret_type); builder_->CreateRet(val); } else { builder_->CreateRetVoid(); } // Create unreachable block for code after return auto* after = llvm::BasicBlock::Create(ctx(), "after_ret", current_func_); builder_->SetInsertPoint(after); return nullptr; } } return nullptr; } // ---- Expression ---- llvm::Value* Codegen::codegen_expr(Expr& expr, llvm::Type* expected) { switch (expr.kind) { case Expr::IntLiteral: { llvm::Type* ty; if (!expr.literal_type.empty()) ty = resolve_type(expr.literal_type); else ty = expected ? expected : llvm::Type::getInt32Ty(ctx()); if (ty->isPointerTy() && expr.int_value == 0) return llvm::ConstantPointerNull::get(llvm::cast<llvm::PointerType>(ty)); if (ty->isPointerTy()) return llvm::ConstantInt::get(llvm::Type::getInt64Ty(ctx()), expr.int_value); return llvm::ConstantInt::get(ty, expr.int_value); } case Expr::FloatLiteral: { llvm::Type* ty; if (!expr.literal_type.empty()) ty = resolve_type(expr.literal_type); else ty = expected ? expected : llvm::Type::getDoubleTy(ctx()); return llvm::ConstantFP::get(ty, expr.float_value); } case Expr::BoolLiteral: return llvm::ConstantInt::get(llvm::Type::getInt1Ty(ctx()), expr.bool_value ? 1 : 0); case Expr::StringLiteral: return codegen_string_literal(expr.string_value); case Expr::Identifier: { // Check for use-after-drop if (fn_.dropped.count(expr.name)) { fprintf(stderr, "error: use of dropped variable '%s'\n", expr.name.c_str()); exit(1); } auto it = fn_.locals.find(expr.name); if (it != fn_.locals.end()) { auto* ty = it->second->getAllocatedType(); return builder_->CreateLoad(ty, it->second, expr.name); } fprintf(stderr, "error: undefined variable '%s'\n", expr.name.c_str()); exit(1); } case Expr::Call: { std::string call_name = expr.name; // Special handling for va_start, va_end, va_copy — LLVM intrinsics // Note: LLVM intrinsics only take va_list* as argument. // The C-level `last_arg` parameter is used at compile-time to find // the position of the first variadic argument — in LLVM it's implicit. if (call_name == "va_start" || call_name == "va_end" || call_name == "va_copy") { llvm::Intrinsic::ID intr_id; if (call_name == "va_start") intr_id = llvm::Intrinsic::vastart; else if (call_name == "va_end") intr_id = llvm::Intrinsic::vaend; else intr_id = llvm::Intrinsic::vacopy; llvm::Type* i8ptr = llvm::PointerType::get(ctx(), 0); auto* intrin = llvm::Intrinsic::getDeclaration(mod_.get(), intr_id, {i8ptr}); // Only pass the first argument (va_list pointer) to the intrinsic llvm::Value* vl_arg = codegen_expr(*expr.args[0], i8ptr); return builder_->CreateCall(intrin, {vl_arg}); } // Check name filtering: if the function exists in a module that was // imported with items, is it in the allowed list? if (!use_allowed_.empty() && !use_allowed_.count(expr.name)) { auto mod_it = fn_source_module_.find(expr.name); if (mod_it == fn_source_module_.end()) { // Not in any tracked module (extern or local) — always allowed ;; // skip filtering } else { // Extract module stem for suggestion std::string mod_path = mod_it->second; std::string mod_stem = mod_path; size_t s = mod_stem.rfind('/'); if (s != std::string::npos) mod_stem = mod_stem.substr(s + 1); size_t d = mod_stem.rfind('.'); if (d != std::string::npos) mod_stem = mod_stem.substr(0, d); fprintf(stderr, "error: '%s' is not imported (found in module '%s')\n", expr.name.c_str(), mod_stem.c_str()); fprintf(stderr, " try: use %s::{%s};\n", mod_stem.c_str(), expr.name.c_str()); exit(1); } } // Resolve aliased struct methods: Alias_method -> Original_method for (auto& [alias, original] : use_aliases_) { std::string prefix = alias + "_"; if (call_name.rfind(prefix, 0) == 0) { call_name = original + "_" + call_name.substr(prefix.length()); break; } } auto ait = use_aliases_.find(call_name); if (ait != use_aliases_.end()) call_name = ait->second; // Try the function name auto* func = mod_->getFunction(call_name); // If not found, try mangled name: _Cmm_<module>_<name> if (!func) { auto mit = fn_source_module_.find(call_name); if (mit != fn_source_module_.end()) { std::string mod = mit->second; size_t s = mod.rfind('/'); if (s != std::string::npos) mod = mod.substr(s + 1); size_t d = mod.rfind('.'); if (d != std::string::npos) mod = mod.substr(0, d); std::string mname = "_Cmm_" + mod + "_" + call_name; func = mod_->getFunction(mname); if (func) call_name = mname; } } if (func) { std::vector<llvm::Value*> call_args; auto fi = func->arg_begin(); size_t i = 0; for (; i < expr.args.size() && fi != func->arg_end(); i++, ++fi) { call_args.push_back(codegen_expr(*expr.args[i], fi->getType())); } for (; i < expr.args.size(); i++) { call_args.push_back(codegen_expr(*expr.args[i], nullptr)); } return builder_->CreateCall(func, call_args); } // Declare on the fly std::vector<llvm::Value*> call_args; for (auto& arg : expr.args) call_args.push_back(codegen_expr(*arg, nullptr)); auto* ret_type = expected ? expected : llvm::Type::getInt32Ty(ctx()); std::vector<llvm::Type*> param_tys; for (auto& arg : call_args) param_tys.push_back(arg->getType()); auto* ft = llvm::FunctionType::get(ret_type, param_tys, false); func = llvm::Function::Create(ft, llvm::Function::ExternalLinkage, expr.name, mod_.get()); return builder_->CreateCall(func, call_args); } case Expr::MethodCall: { // Check for use-after-drop in method receiver if (expr.receiver && expr.receiver->kind == Expr::Identifier && fn_.dropped.count(expr.receiver->name)) { fprintf(stderr, "error: use of dropped variable '%s'\n", expr.receiver->name.c_str()); exit(1); } // Track drop(x): mark variable as dropped (after the check above) if (expr.name == "drop" && expr.receiver && expr.receiver->kind == Expr::Identifier) { fn_.dropped.insert(expr.receiver->name); } // x.method(args) — get pointer to receiver, call Type_method(receiver_ptr, args...) llvm::Value* receiver_ptr = nullptr; std::string receiver_type; if (expr.receiver->kind == Expr::Identifier) { auto it = fn_.locals.find(expr.receiver->name); if (it != fn_.locals.end()) receiver_ptr = it->second; auto ti = fn_.local_types.find(expr.receiver->name); if (ti != fn_.local_types.end()) receiver_type = ti->second; } else if (expr.receiver->kind == Expr::FieldAccess) { auto& fa = *expr.receiver; if (fa.object->kind == Expr::Identifier) { auto ti = fn_.local_types.find(fa.object->name); if (ti != fn_.local_types.end()) { std::string base_ty = ti->second; if (!base_ty.empty() && base_ty[0] == '*') base_ty = base_ty.substr(1); auto si = structs_.find(base_ty); if (si != structs_.end()) { auto fi = si->second.field_index.find(fa.field_name); if (fi != si->second.field_index.end()) { unsigned idx = fi->second; receiver_type = si->second.field_type_names[idx]; // receiver_ptr will be set below via generic path } } } } } if (!receiver_ptr) { auto* val = codegen_expr(*expr.receiver, nullptr); receiver_ptr = create_entry_alloca(val->getType(), "rcv"); builder_->CreateStore(val, receiver_ptr); } // Resolve aliased receiver type { size_t stars = 0; while (stars < receiver_type.size() && receiver_type[stars] == '*') stars++; std::string base = receiver_type.substr(stars); auto ait = use_aliases_.find(base); if (ait != use_aliases_.end()) { receiver_type = receiver_type.substr(0, stars) + ait->second; } } // Build mangled function name: Type_method std::string mangled_name; if (!receiver_type.empty() && receiver_type[0] == '*') mangled_name = receiver_type.substr(1) + "_" + expr.name; else if (!receiver_type.empty()) mangled_name = receiver_type + "_" + expr.name; else mangled_name = expr.name; // Sanitize: match parser's mangling (skip >, replace < and , with _) std::string clean; for (char c : mangled_name) { if (c == '<' || c == ',') clean += '_'; else if (c == '>') continue; else clean += c; } mangled_name = clean; // Built-in arithmetic for primitive types std::string method = expr.name; // Special handling for inc/dec with overflow checking // These modify self in place and return VoidResult if ((method == "inc" || method == "dec") && receiver_ptr && expr.args.empty()) { if (auto* rcv_a = llvm::dyn_cast<llvm::AllocaInst>(receiver_ptr)) { llvm::Type* ty = rcv_a->getAllocatedType(); if (ty->isIntegerTy()) { auto* lhs = builder_->CreateLoad(ty, receiver_ptr); llvm::Value* one = llvm::ConstantInt::get(ty, 1); bool is_signed = true; // Determine signedness from type name if (!receiver_type.empty() && receiver_type[0] == 'u') is_signed = false; // Get overflow intrinsic llvm::Intrinsic::ID intr_id; if (method == "inc") intr_id = is_signed ? llvm::Intrinsic::sadd_with_overflow : llvm::Intrinsic::uadd_with_overflow; else intr_id = is_signed ? llvm::Intrinsic::ssub_with_overflow : llvm::Intrinsic::usub_with_overflow; auto* intrin = llvm::Intrinsic::getDeclaration(mod_.get(), intr_id, {ty}); auto* intr_res = builder_->CreateCall(intrin, {lhs, one}); auto* new_val = builder_->CreateExtractValue(intr_res, 0); auto* overflow = builder_->CreateExtractValue(intr_res, 1); // Create basic blocks for ok/err paths llvm::Function* fn = builder_->GetInsertBlock()->getParent(); auto* ok_bb = llvm::BasicBlock::Create(ctx(), "inc_ok", fn); auto* err_bb = llvm::BasicBlock::Create(ctx(), "inc_err", fn); auto* merge_bb = llvm::BasicBlock::Create(ctx(), "inc_merge", fn); builder_->CreateCondBr(overflow, err_bb, ok_bb); // Define struct types for Error and VoidResult llvm::PointerType* i8ptr = llvm::PointerType::get(ctx(), 0); llvm::StructType* err_ty = llvm::StructType::get( llvm::Type::getInt32Ty(ctx()), i8ptr); llvm::StructType* vr_ty = llvm::StructType::get(err_ty, llvm::Type::getInt1Ty(ctx())); // Common constants auto* zero_i32 = llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), 0); auto* one_i32 = llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), 1); // OK path: store new value, construct VoidResult::ok() builder_->SetInsertPoint(ok_bb); builder_->CreateStore(new_val, receiver_ptr); auto* r_ok = builder_->CreateAlloca(vr_ty, nullptr, "vr_ok"); auto* ok_err_code = builder_->CreateGEP(vr_ty, r_ok, {zero_i32, zero_i32, zero_i32}); builder_->CreateStore(zero_i32, ok_err_code); auto* ok_err_msg = builder_->CreateGEP(vr_ty, r_ok, {zero_i32, zero_i32, one_i32}); builder_->CreateStore(llvm::ConstantPointerNull::get(i8ptr), ok_err_msg); auto* ok_is_ok = builder_->CreateGEP(vr_ty, r_ok, {zero_i32, one_i32}); builder_->CreateStore(llvm::ConstantInt::get(llvm::Type::getInt1Ty(ctx()), 1), ok_is_ok); auto* void_ok = builder_->CreateLoad(vr_ty, r_ok); builder_->CreateBr(merge_bb); // Err path: don't modify value, construct VoidResult::err(overflow) builder_->SetInsertPoint(err_bb); auto* overflow_msg = builder_->CreateGlobalStringPtr("arithmetic overflow"); auto* r_err = builder_->CreateAlloca(vr_ty, nullptr, "vr_err"); auto* err_err_code = builder_->CreateGEP(vr_ty, r_err, {zero_i32, zero_i32, zero_i32}); builder_->CreateStore(one_i32, err_err_code); auto* err_err_msg = builder_->CreateGEP(vr_ty, r_err, {zero_i32, zero_i32, one_i32}); builder_->CreateStore(overflow_msg, err_err_msg); auto* err_is_ok = builder_->CreateGEP(vr_ty, r_err, {zero_i32, one_i32}); builder_->CreateStore(llvm::ConstantInt::get(llvm::Type::getInt1Ty(ctx()), 0), err_is_ok); auto* void_err = builder_->CreateLoad(vr_ty, r_err); builder_->CreateBr(merge_bb); // Merge: use phi node to select result builder_->SetInsertPoint(merge_bb); auto* result = builder_->CreatePHI(void_ok->getType(), 2, "inc_result"); result->addIncoming(void_ok, ok_bb); result->addIncoming(void_err, err_bb); return result; } } } if (method == "wrapping_add" || method == "wrapping_sub" || method == "wrapping_mul" || method == "wrapping_div" || method == "wrapping_rem" || method == "wrapping_neg" || method == "saturating_add" || method == "saturating_sub" || method == "wrapping_shl" || method == "wrapping_shr" || method == "offset") { if (auto* rcv_a = llvm::dyn_cast<llvm::AllocaInst>(receiver_ptr)) { llvm::Type* ty = rcv_a->getAllocatedType(); auto* lhs = builder_->CreateLoad(ty, receiver_ptr); auto* rhs = (method != "neg") ? codegen_expr(*expr.args[0], ty) : nullptr; auto is_float = ty->isDoubleTy() || ty->isFloatTy(); auto is_ptr = ty->isPointerTy(); // add/sub on pointers is not allowed // ptr.offset(n) — explicit pointer advance (GEP) if (method == "offset" && is_ptr) { auto* idx = codegen_expr(*expr.args[0], llvm::Type::getInt64Ty(ctx())); return builder_->CreateGEP(llvm::Type::getInt8Ty(ctx()), lhs, idx); } if (is_ptr) { break; } // fall through to function lookup // Saturated/wrapping arithmetic via LLVM intrinsics if (method == "saturating_add" || method == "saturating_sub") { bool is_add = method.find("add") != std::string::npos; bool is_signed = ty == llvm::Type::getInt8Ty(ctx()) || ty == llvm::Type::getInt16Ty(ctx()) || ty == llvm::Type::getInt32Ty(ctx()) || ty == llvm::Type::getInt64Ty(ctx()); llvm::Intrinsic::ID intr_id; if (is_add) { intr_id = is_signed ? llvm::Intrinsic::sadd_sat : llvm::Intrinsic::uadd_sat; } else { intr_id = is_signed ? llvm::Intrinsic::ssub_sat : llvm::Intrinsic::usub_sat; } auto* intrin = llvm::Intrinsic::getDeclaration(mod_.get(), intr_id, {ty}); return builder_->CreateCall(intrin, {lhs, rhs}); } if (method == "wrapping_add") return is_float ? builder_->CreateFAdd(lhs, rhs) : builder_->CreateAdd(lhs, rhs); if (method == "wrapping_sub") return is_float ? builder_->CreateFSub(lhs, rhs) : builder_->CreateSub(lhs, rhs); if (method == "wrapping_mul") return is_float ? builder_->CreateFMul(lhs, rhs) : builder_->CreateMul(lhs, rhs); if (method == "wrapping_div") return is_float ? builder_->CreateFDiv(lhs, rhs) : builder_->CreateSDiv(lhs, rhs); if (method == "wrapping_rem") return is_float ? builder_->CreateFRem(lhs, rhs) : builder_->CreateSRem(lhs, rhs); if (method == "wrapping_neg") return is_float ? builder_->CreateFNeg(lhs) : builder_->CreateNeg(lhs); if (method == "wrapping_shl") { unsigned bw = ty->getIntegerBitWidth(); auto* mask = llvm::ConstantInt::get(ty, bw - 1); return builder_->CreateShl(lhs, builder_->CreateAnd(rhs, mask)); } if (method == "wrapping_shr") { unsigned bw = ty->getIntegerBitWidth(); auto* mask = llvm::ConstantInt::get(ty, bw - 1); auto* rhs_masked = builder_->CreateAnd(rhs, mask); bool is_signed = true; if (expr.receiver->kind == Expr::Identifier) { auto it = fn_.local_types.find(expr.receiver->name); if (it != fn_.local_types.end() && !it->second.empty() && it->second[0] == 'u') is_signed = false; } else if (expr.receiver->kind == Expr::AsCast) { auto& tn = expr.receiver->as_type_name; if (!tn.empty() && tn[0] == 'u') is_signed = false; } return is_signed ? builder_->CreateAShr(lhs, rhs_masked) : builder_->CreateLShr(lhs, rhs_masked); } } } auto* func = mod_->getFunction(mangled_name); // If not found, try mangled name: _Cmm_<module>_<name> if (!func) { auto mit = fn_source_module_.find(mangled_name); if (mit != fn_source_module_.end()) { std::string mod = mit->second; size_t s = mod.rfind('/'); if (s != std::string::npos) mod = mod.substr(s + 1); size_t d = mod.rfind('.'); if (d != std::string::npos) mod = mod.substr(0, d); std::string mname = "_Cmm_" + mod + "_" + mangled_name; func = mod_->getFunction(mname); if (func) mangled_name = mname; } } if (func) { std::vector<llvm::Value*> call_args = {receiver_ptr}; auto fi = func->arg_begin(); ++fi; // skip self for (size_t i = 0; i < expr.args.size() && fi != func->arg_end(); i++, ++fi) { call_args.push_back(codegen_expr(*expr.args[i], fi->getType())); } return builder_->CreateCall(func, call_args); } // Declare on the fly std::vector<llvm::Value*> call_args = {receiver_ptr}; for (auto& arg : expr.args) call_args.push_back(codegen_expr(*arg, nullptr)); auto* ret_type = expected ? expected : llvm::Type::getInt32Ty(ctx()); std::vector<llvm::Type*> param_tys; for (auto& a : call_args) param_tys.push_back(a->getType()); auto* ft = llvm::FunctionType::get(ret_type, param_tys, false); func = llvm::Function::Create(ft, llvm::Function::ExternalLinkage, mangled_name, mod_.get()); return builder_->CreateCall(func, call_args); } case Expr::BinaryOp: { auto* left = codegen_expr(*expr.left, nullptr); auto* right = codegen_expr(*expr.right, nullptr); // Handle pointer vs integer comparisons (e.g., ptr != 0) if (left->getType()->isPointerTy() && right->getType()->isIntegerTy()) { if (auto* ci = llvm::dyn_cast<llvm::ConstantInt>(right)) { if (ci->isZero()) right = llvm::ConstantPointerNull::get(llvm::cast<llvm::PointerType>(left->getType())); } } else if (right->getType()->isPointerTy() && left->getType()->isIntegerTy()) { if (auto* ci = llvm::dyn_cast<llvm::ConstantInt>(left)) { if (ci->isZero()) left = llvm::ConstantPointerNull::get(llvm::cast<llvm::PointerType>(right->getType())); } } // Auto-cast integer ConstantInt to match the other operand's type if (left->getType()->isIntegerTy() && right->getType()->isIntegerTy() && left->getType() != right->getType()) { if (llvm::isa<llvm::ConstantInt>(left)) left = builder_->CreateIntCast(left, right->getType(), false); else if (llvm::isa<llvm::ConstantInt>(right)) right = builder_->CreateIntCast(right, left->getType(), false); } bool is_float_cmp = left->getType()->isFloatingPointTy() || right->getType()->isFloatingPointTy(); switch (expr.op) { case TokenType::EQEQ: return is_float_cmp ? builder_->CreateFCmpOEQ(left, right) : builder_->CreateICmpEQ(left, right); case TokenType::NEQ: return is_float_cmp ? builder_->CreateFCmpONE(left, right) : builder_->CreateICmpNE(left, right); case TokenType::LT: return is_float_cmp ? builder_->CreateFCmpOLT(left, right) : builder_->CreateICmpSLT(left, right); case TokenType::GT: return is_float_cmp ? builder_->CreateFCmpOGT(left, right) : builder_->CreateICmpSGT(left, right); case TokenType::LE: return is_float_cmp ? builder_->CreateFCmpOLE(left, right) : builder_->CreateICmpSLE(left, right); case TokenType::GE: return is_float_cmp ? builder_->CreateFCmpOGE(left, right) : builder_->CreateICmpSGE(left, right); case TokenType::AND: return builder_->CreateAnd(left, right); case TokenType::EQ: { // Assignment: left must be an identifier or array subscript if (expr.left->kind == Expr::Identifier) { auto it = fn_.locals.find(expr.left->name); if (it != fn_.locals.end()) { auto* target_ty = it->second->getAllocatedType(); if (right->getType() != target_ty) { if (right->getType()->isDoubleTy() && target_ty->isIntegerTy()) right = builder_->CreateFPToSI(right, target_ty); else if (right->getType()->isIntegerTy() && target_ty->isDoubleTy()) right = builder_->CreateSIToFP(right, target_ty); else if (right->getType()->isIntegerTy() && target_ty->isIntegerTy() && llvm::isa<llvm::ConstantInt>(right)) right = builder_->CreateIntCast(right, target_ty, false); } builder_->CreateStore(right, it->second); return right; } } else if (expr.left->kind == Expr::ArraySubscript) { // array[index] = value: GEP to element, then store auto& sub = *expr.left; llvm::Value* arr = nullptr; // Local array variable: use alloca directly, don't load if (sub.array->kind == Expr::Identifier) { auto it = fn_.locals.find(sub.array->name); auto ti = fn_.local_types.find(sub.array->name); if (it != fn_.locals.end() && ti != fn_.local_types.end() && !ti->second.empty() && ti->second[0] == '[') { arr = it->second; } } if (!arr) arr = codegen_expr(*sub.array, nullptr); auto* idx = codegen_expr(*sub.index, nullptr); llvm::Value* arr_ptr; if (arr->getType()->isPointerTy()) arr_ptr = arr; else { arr_ptr = create_entry_alloca(arr->getType(), "arrtmp"); builder_->CreateStore(arr, arr_ptr); } llvm::Type* elem_ty = nullptr; if (sub.array->kind == Expr::Identifier) { auto it = fn_.local_types.find(sub.array->name); if (it != fn_.local_types.end() && !it->second.empty()) { if (it->second[0] == '[') { // Array type: [N]T → element is T size_t end = it->second.find(']'); if (end != std::string::npos) { std::string elem_name = it->second.substr(end + 1); elem_ty = resolve_type(elem_name); } } else if (it->second[0] == '*') { // Pointer type: *T → element is T std::string elem_name = it->second.substr(1); elem_ty = resolve_type(elem_name); } } } if (!elem_ty) elem_ty = llvm::Type::getInt8Ty(ctx()); // Cast rhs to element type if needed if (elem_ty != right->getType() && elem_ty->isIntegerTy() && right->getType()->isIntegerTy()) right = builder_->CreateIntCast(right, elem_ty, false); // Determine if arr_ptr points to an array (alloca of array) llvm::Type* pointed_ty = nullptr; if (auto* alloca = llvm::dyn_cast<llvm::AllocaInst>(arr_ptr)) pointed_ty = alloca->getAllocatedType(); if (pointed_ty && pointed_ty->isArrayTy()) { auto* zero = llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), 0); auto* gep = builder_->CreateInBoundsGEP(pointed_ty, arr_ptr, {zero, idx}, "subs"); builder_->CreateStore(right, gep); return right; } auto* gep = builder_->CreateGEP(elem_ty, arr_ptr, idx, "subs"); builder_->CreateStore(right, gep); return right; } fprintf(stderr, "warning: invalid assignment target\n"); return right; } case TokenType::OR: return builder_->CreateOr(left, right); case TokenType::PIPE: return builder_->CreateOr(left, right); default: return left; } } case Expr::UnaryOp: { auto* op = codegen_expr(*expr.left, nullptr); if (expr.op == TokenType::NOT) return builder_->CreateNot(op); return op; } case Expr::AddressOf: { if (expr.left->kind == Expr::Identifier) { auto it = fn_.locals.find(expr.left->name); if (it != fn_.locals.end()) return it->second; auto* func = mod_->getFunction(expr.left->name); // Try mangled name if (!func) { auto mit = fn_source_module_.find(expr.left->name); if (mit != fn_source_module_.end()) { std::string mod = mit->second; size_t s = mod.rfind('/'); if (s != std::string::npos) mod = mod.substr(s + 1); size_t d = mod.rfind('.'); if (d != std::string::npos) mod = mod.substr(0, d); std::string mname = "_Cmm_" + mod + "_" + expr.left->name; func = mod_->getFunction(mname); } } if (func) return func; } auto* val = codegen_expr(*expr.left, nullptr); auto* temp = create_entry_alloca(val->getType(), ""); builder_->CreateStore(val, temp); return temp; } case Expr::RangeLiteral: { auto* start = codegen_expr(*expr.range_start, llvm::Type::getInt64Ty(ctx())); auto* arr = create_entry_alloca(llvm::ArrayType::get(llvm::Type::getInt64Ty(ctx()), 2), "range"); auto* g0 = builder_->CreateGEP(llvm::ArrayType::get(llvm::Type::getInt64Ty(ctx()), 2), arr, {llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), 0), llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), 0)}); builder_->CreateStore(start, g0); if (expr.range_end) { auto* end = codegen_expr(*expr.range_end, llvm::Type::getInt64Ty(ctx())); auto* g1 = builder_->CreateGEP(llvm::ArrayType::get(llvm::Type::getInt64Ty(ctx()), 2), arr, {llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), 0), llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), 1)}); builder_->CreateStore(end, g1); } return arr; } case Expr::StructInit: { // String { ptr: p, len: l, cap: c } auto si = structs_.find(expr.name); if (si == structs_.end()) { fprintf(stderr, "error: unknown struct '%s'\n", expr.name.c_str()); return llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), 0); } auto* st = si->second.type; auto* alloca = create_entry_alloca(st, expr.name + "_init"); for (auto& [fname, fexpr] : expr.struct_fields) { auto fi = si->second.field_index.find(fname); if (fi == si->second.field_index.end()) { fprintf(stderr, "error: unknown field '%s' in struct '%s'\n", fname.c_str(), expr.name.c_str()); continue; } unsigned idx = fi->second; auto* val = codegen_expr(*fexpr, st->getElementType(idx)); auto* gep = builder_->CreateStructGEP(st, alloca, idx, fname); builder_->CreateStore(val, gep); } return builder_->CreateLoad(st, alloca, expr.name); } case Expr::FieldAccess: { llvm::Value* struct_ptr = nullptr; // Local variable: use its alloca directly to avoid copying if (expr.object->kind == Expr::Identifier) { auto it = fn_.locals.find(expr.object->name); if (it != fn_.locals.end()) { struct_ptr = it->second; } } if (!struct_ptr) { auto* obj_val = codegen_expr(*expr.object, nullptr); if (obj_val->getType()->isPointerTy()) { struct_ptr = obj_val; } else { struct_ptr = create_entry_alloca(obj_val->getType(), "fldtmp"); builder_->CreateStore(obj_val, struct_ptr); } } else { // struct_ptr may be an alloca storing a pointer (e.g. self: *Point) if (auto* alloca = llvm::dyn_cast<llvm::AllocaInst>(struct_ptr)) { if (alloca->getAllocatedType()->isPointerTy()) { struct_ptr = builder_->CreateLoad(alloca->getAllocatedType(), struct_ptr, "selfptr"); } } } std::string type_name; if (expr.object->kind == Expr::Identifier) { auto it = fn_.local_types.find(expr.object->name); if (it != fn_.local_types.end()) { type_name = it->second; if (!type_name.empty() && type_name[0] == '*') type_name = type_name.substr(1); } } if (type_name.empty()) { // Try to find the struct from the pointer's pointee type fprintf(stderr, "warning: could not determine struct type for field access\n"); return llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), 0); } auto si = structs_.find(type_name); if (si == structs_.end()) { fprintf(stderr, "error: unknown struct type for field access\n"); return llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), 0); } auto fi = si->second.field_index.find(expr.field_name); if (fi == si->second.field_index.end()) { fprintf(stderr, "error: unknown field '%s'\n", expr.field_name.c_str()); return llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), 0); } unsigned idx = fi->second; auto* st = si->second.type; auto* gep = builder_->CreateStructGEP(st, struct_ptr, idx, expr.field_name); return builder_->CreateLoad(st->getElementType(idx), gep, expr.field_name); } case Expr::Lambda: { // Lambda: generate a function, return pointer to it if (expr.lambda_params.empty() || expr.lambda_params[0].name == "self") { fprintf(stderr, "lambda: cannot have 'self' parameter\n"); return llvm::ConstantPointerNull::get(llvm::PointerType::get(ctx(), 0)); } // Save current function context (including locals) auto* saved_func = current_func_; llvm::BasicBlock* saved_block = builder_->GetInsertBlock(); auto saved_locals = std::move(fn_.locals); auto saved_local_types = std::move(fn_.local_types); auto saved_dropped = std::move(fn_.dropped); auto saved_defer = std::move(fn_.defer_stack); std::vector<llvm::Type*> param_types; for (auto& p : expr.lambda_params) param_types.push_back(resolve_type(p.type_name)); auto* ret_type = resolve_type(expr.lambda_ret_type); auto* ft = llvm::FunctionType::get(ret_type, param_types, false); auto* func = llvm::Function::Create(ft, llvm::Function::InternalLinkage, expr.lambda_name, mod_.get()); auto* entry = llvm::BasicBlock::Create(ctx(), "entry", func); current_func_ = func; builder_->SetInsertPoint(entry); fn_.locals.clear(); fn_.local_types.clear(); fn_.dropped.clear(); fn_.defer_stack.clear(); size_t idx = 0; for (auto& arg : func->args()) { arg.setName(expr.lambda_params[idx].name); auto* alloca = builder_->CreateAlloca(arg.getType(), nullptr, expr.lambda_params[idx].name); builder_->CreateStore(&arg, alloca); fn_.locals[expr.lambda_params[idx].name] = alloca; fn_.local_types[expr.lambda_params[idx].name] = expr.lambda_params[idx].type_name; idx++; } if (expr.lambda_body) { auto* result = codegen_block(*expr.lambda_body, ret_type); if (ret_type->isVoidTy()) { if (!builder_->GetInsertBlock()->getTerminator()) builder_->CreateRetVoid(); } else if (result) { if (!builder_->GetInsertBlock()->getTerminator()) builder_->CreateRet(result); } else { if (!builder_->GetInsertBlock()->getTerminator()) builder_->CreateRet(llvm::ConstantInt::get(ret_type, 0)); } } else { if (!builder_->GetInsertBlock()->getTerminator()) builder_->CreateRetVoid(); } llvm::verifyFunction(*func); // Restore original function context current_func_ = saved_func; fn_.locals = std::move(saved_locals); fn_.local_types = std::move(saved_local_types); fn_.dropped = std::move(saved_dropped); fn_.defer_stack = std::move(saved_defer); if (saved_func && saved_block) builder_->SetInsertPoint(saved_block); return func; } case Expr::ArrayLiteral: { llvm::Type* elem_ty = (expected && expected->isArrayTy()) ? expected->getArrayElementType() : nullptr; llvm::Value* first_val = nullptr; if (!expr.array_values.empty()) { // Determine element type from first value first_val = codegen_expr(*expr.array_values[0], elem_ty); if (!elem_ty) elem_ty = first_val->getType(); } else { // [val; count] — repeat first_val = codegen_expr(*expr.array_val, elem_ty); if (!elem_ty) elem_ty = first_val->getType(); } auto* arr_ty = llvm::ArrayType::get(elem_ty, expr.array_count); auto* zero = llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), 0); // Fast path: all zeros → return ConstantAggregateZero without alloca if (!expr.array_values.empty()) { // Check if all values are constant zeros bool all_zero = true; for (size_t i = 0; i < expr.array_values.size(); i++) { auto* v = codegen_expr(*expr.array_values[i], elem_ty); if (!llvm::isa<llvm::ConstantInt>(v) || !llvm::cast<llvm::ConstantInt>(v)->isZero()) { all_zero = false; break; } } if (all_zero && expr.array_values.size() == expr.array_count) { return llvm::ConstantAggregateZero::get(arr_ty); } } else { if (auto* ci = llvm::dyn_cast<llvm::ConstantInt>(first_val)) { if (ci->isZero()) return llvm::ConstantAggregateZero::get(arr_ty); } } auto* alloca = create_entry_alloca(arr_ty, "arr_lit"); if (!expr.array_values.empty()) { // [val1, val2, ...] for (size_t i = 0; i < expr.array_values.size(); i++) { auto* val = codegen_expr(*expr.array_values[i], elem_ty); auto* i_val = llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), i); auto* gep = builder_->CreateInBoundsGEP(arr_ty, alloca, {zero, i_val}, "el"); builder_->CreateStore(val, gep); } } else { // [val; count] — repeat for (uint64_t i = 0; i < expr.array_count; i++) { auto* i_val = llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), i); auto* gep = builder_->CreateInBoundsGEP(arr_ty, alloca, {zero, i_val}, "el"); builder_->CreateStore(first_val, gep); } } return builder_->CreateLoad(arr_ty, alloca, "arr"); } case Expr::ArraySubscript: { llvm::Value* arr = nullptr; // Local array variable: use alloca directly, don't load if (expr.array->kind == Expr::Identifier) { auto it = fn_.locals.find(expr.array->name); auto ti = fn_.local_types.find(expr.array->name); if (it != fn_.locals.end() && ti != fn_.local_types.end() && !ti->second.empty() && ti->second[0] == '[') { arr = it->second; } } if (!arr) arr = codegen_expr(*expr.array, nullptr); auto* idx = codegen_expr(*expr.index, nullptr); llvm::Value* arr_ptr; if (arr->getType()->isPointerTy()) arr_ptr = arr; else { arr_ptr = create_entry_alloca(arr->getType(), "arrtmp"); builder_->CreateStore(arr, arr_ptr); } llvm::Type* arr_ty = nullptr; if (auto* alloca = llvm::dyn_cast<llvm::AllocaInst>(arr_ptr)) arr_ty = alloca->getAllocatedType(); if (!arr_ty) arr_ty = arr->getType(); if (arr_ty->isArrayTy()) { auto* zero = llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), 0); auto* gep = builder_->CreateInBoundsGEP(arr_ty, arr_ptr, {zero, idx}, "subs"); return builder_->CreateLoad(arr_ty->getArrayElementType(), gep, "el"); } // Pointer fallback: determine element type from type name llvm::Type* elem_ty = nullptr; if (expr.array->kind == Expr::Identifier) { auto it = fn_.local_types.find(expr.array->name); if (it != fn_.local_types.end() && !it->second.empty() && it->second[0] == '*') { std::string elem_name = it->second.substr(1); elem_ty = resolve_type(elem_name); } } else if (expr.array->kind == Expr::FieldAccess) { auto* obj = expr.array->object.get(); if (obj->kind == Expr::Identifier) { auto it = fn_.local_types.find(obj->name); if (it != fn_.local_types.end()) { std::string type_name = it->second; if (!type_name.empty() && type_name[0] == '*') type_name = type_name.substr(1); auto si = structs_.find(type_name); if (si != structs_.end()) { auto fi = si->second.field_index.find(expr.array->field_name); if (fi != si->second.field_index.end()) { std::string ft = si->second.field_type_names[fi->second]; if (!ft.empty() && ft[0] == '*') { std::string elem_name = ft.substr(1); elem_ty = resolve_type(elem_name); } } } } } } // Fallback: strip one * from the LLVM type — for opaque pointers use i8 if (!elem_ty) elem_ty = llvm::Type::getInt8Ty(ctx()); auto* gep = builder_->CreateGEP(elem_ty, arr_ptr, idx, "subs"); return builder_->CreateLoad(elem_ty, gep, "el"); } case Expr::AsCast: { auto* val = codegen_expr(*expr.as_expr, resolve_type(expr.as_type_name)); auto* src = val->getType(); auto* dst = resolve_type(expr.as_type_name); // Determine source type name for warnings std::string src_type_name; if (expr.as_expr->kind == Expr::Identifier) { auto it = fn_.local_types.find(expr.as_expr->name); if (it != fn_.local_types.end()) src_type_name = it->second; } else if (expr.as_expr->kind == Expr::AsCast) { src_type_name = expr.as_expr->as_type_name; } // Warnings for integer casts if (src->isIntegerTy() && dst->isIntegerTy()) { unsigned src_bits = src->getIntegerBitWidth(); unsigned dst_bits = dst->getIntegerBitWidth(); if (src_bits > dst_bits) fprintf(stderr, "%s:%zu:%zu: warning: truncation in '%s as %s' (%u → %u bits)\n", expr.loc.file.c_str(), expr.loc.line, expr.loc.col, src_type_name.c_str(), expr.as_type_name.c_str(), src_bits, dst_bits); if (!src_type_name.empty()) { bool src_signed = src_type_name.size() > 0 && src_type_name[0] == 'i'; bool dst_signed = expr.as_type_name.size() > 0 && expr.as_type_name[0] == 'i'; if (src_signed != dst_signed) fprintf(stderr, "%s:%zu:%zu: warning: sign change in '%s as %s'\n", expr.loc.file.c_str(), expr.loc.line, expr.loc.col, src_type_name.c_str(), expr.as_type_name.c_str()); } if (src == dst) return val; return builder_->CreateIntCast(val, dst, false); } if (src->isFloatTy() && dst->isFloatTy()) return builder_->CreateFPCast(val, dst); if (src->isIntegerTy() && dst->isFloatTy()) return builder_->CreateSIToFP(val, dst); if (src->isFloatTy() && dst->isIntegerTy()) return builder_->CreateFPToSI(val, dst); if (src->isPointerTy() && dst->isIntegerTy()) return builder_->CreatePtrToInt(val, dst); if (src->isIntegerTy() && dst->isPointerTy()) return builder_->CreateIntToPtr(val, dst); if (src->isPointerTy() && dst->isPointerTy()) return builder_->CreatePointerCast(val, dst); fprintf(stderr, "warning: unsupported cast\n"); return val; } } return llvm::ConstantInt::get(expected ? expected : llvm::Type::getInt32Ty(ctx()), 0); } // ---- String literal ---- llvm::Value* Codegen::codegen_string_literal(const std::string& val) { // Create a global constant string: [N x i8] c"hello\00" std::string str = val; str += '\0'; // null terminate auto* str_ty = llvm::ArrayType::get(llvm::Type::getInt8Ty(ctx()), str.size()); auto* str_init = llvm::ConstantDataArray::getString(*ctx_, val, true); auto* gvar = new llvm::GlobalVariable(*mod_, str_ty, true, llvm::GlobalValue::PrivateLinkage, str_init, ".str"); gvar->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); // Return a pointer to the string data (i8*) llvm::Value* indices[] = { llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), 0), llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx()), 0) }; return builder_->CreateInBoundsGEP(str_ty, gvar, indices, "str"); } // ---- Output ---- void Codegen::printIR(const std::string& path) { if (path == "-" || path.empty()) mod_->print(llvm::outs(), nullptr); else { std::error_code ec; llvm::raw_fd_ostream dest(path, ec, llvm::sys::fs::OF_None); if (ec) { fprintf(stderr, "error: %s\n", ec.message().c_str()); return; } mod_->print(dest, nullptr); } } void Codegen::emitObject(const std::string& path) { auto target_triple = mod_->getTargetTriple(); std::string error; auto target = llvm::TargetRegistry::lookupTarget(target_triple, error); if (!target) { fprintf(stderr, "error: %s\n", error.c_str()); return; } std::optional<llvm::Reloc::Model> rm; auto tm = target->createTargetMachine(target_triple, "generic", "", {}, rm); std::error_code ec; llvm::raw_fd_ostream dest(path, ec, llvm::sys::fs::OF_None); if (ec) { fprintf(stderr, "error: %s\n", ec.message().c_str()); return; } llvm::legacy::PassManager pass; if (tm->addPassesToEmitFile(pass, dest, nullptr, llvm::CodeGenFileType::ObjectFile)) { fprintf(stderr, "error: target does not support object file emission\n"); return; } pass.run(*mod_); dest.flush(); }