From 0882b2456b2023f92e90abe1b1cc425bba36712c Mon Sep 17 00:00:00 2001 From: Amlal El Mahrouss Date: Thu, 8 Jan 2026 11:41:40 +0100 Subject: feat: c++front: Finalized C++Front for Nectar C++. Signed-off-by: Amlal El Mahrouss --- CITATION.cff | 2 +- README.md | 8 +- src/CompilerKit/src/Assemblers/Assembler+AMD64.cc | 3 +- .../src/Compilers/CPlusPlusCompiler+AMD64.cc | 788 +++++++++++++++++++-- test/test_01_codegen/codegen.test.cc | 10 +- test/test_samples/class_ctor_dtor.cc | 15 - test/test_samples/class_methods.cc | 13 - test/test_samples/class_simple.cc | 10 - test/test_samples/namespace_basic.cc | 9 - test/test_samples/sample.cc | 23 +- 10 files changed, 763 insertions(+), 118 deletions(-) delete mode 100644 test/test_samples/class_ctor_dtor.cc delete mode 100644 test/test_samples/class_methods.cc delete mode 100644 test/test_samples/class_simple.cc delete mode 100644 test/test_samples/namespace_basic.cc diff --git a/CITATION.cff b/CITATION.cff index d4006e3..5b4875b 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -10,7 +10,7 @@ identifiers: - type: url value: 'https://nekernel.org' description: NeKernel.org -repository-code: 'https://github.com/nekernel-org/necti' +repository-code: 'https://github.com/nekernel-org/nectar' url: 'https://nekernel.org' abstract: >- This is NeKernel.org's Operating System toolchain, authored diff --git a/README.md b/README.md index ae47c45..248803b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Nectar -![CI](https://github.com/amlel-el-mahrouss/cc/actions/workflows/necti-dev.yml/badge.svg) +![CI](https://github.com/amlel-el-mahrouss/cc/actions/workflows/nectar-dev.yml/badge.svg) [![License: GPL-3.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE) ## Structure: @@ -33,8 +33,8 @@ Always use `format.sh` before commiting and pushing your code! Run the following: ```sh -git clone git@github.com:nekernel-org/necti.git -cd necti +git clone git@github.com:nekernel-org/nectar.git +cd nectar # Either build the debugger or compiler libraries/tools using nebuild. ``` @@ -48,7 +48,7 @@ And build the source tree using the NeBuild system. ## Authors & Credits - **Amlal El Mahrouss** — Lead Developer and Compiler Architect. -- [Full contributor list](https://github.com/nekernel-org/necti/graphs/contributors) +- [Full contributor list](https://github.com/nekernel-org/nectar/graphs/contributors) --- diff --git a/src/CompilerKit/src/Assemblers/Assembler+AMD64.cc b/src/CompilerKit/src/Assemblers/Assembler+AMD64.cc index 096ebc1..007b03f 100644 --- a/src/CompilerKit/src/Assemblers/Assembler+AMD64.cc +++ b/src/CompilerKit/src/Assemblers/Assembler+AMD64.cc @@ -487,7 +487,8 @@ static inline bool is_not_valid(char c) { if ((isalpha(c) || isdigit(c)) || ((c == ' ') || (c == '\t') || (c == ',') || (c == '(') || (c == ')') || (c == '"') || (c == '*') || (c == '\'') || (c == '[') || (c == ']') || (c == '+') || (c == '_') || - (c == ':') || (c == '@') || (c == '.') || (c == '#') || (c == '%') || (c == '~') || (c == ';'))) + (c == ':') || (c == '@') || (c == '.') || (c == '#') || (c == '%') || (c == '~') || + (c == ';'))) return false; return true; diff --git a/src/CompilerKit/src/Compilers/CPlusPlusCompiler+AMD64.cc b/src/CompilerKit/src/Compilers/CPlusPlusCompiler+AMD64.cc index 7d29292..24bc96f 100644 --- a/src/CompilerKit/src/Compilers/CPlusPlusCompiler+AMD64.cc +++ b/src/CompilerKit/src/Compilers/CPlusPlusCompiler+AMD64.cc @@ -32,7 +32,7 @@ /// @author Amlal El Mahrouss (amlal@nekernel.org) /// @file CPlusPlusCompilerAMD64.cc -/// @brief Optimized C++ Compiler Driver. +/// @brief C++ Compiler Driver. ///////////////////////////////////// @@ -42,7 +42,7 @@ /// @internal /// @brief Avoids relative_path which could discard parts of the original. -std::filesystem::path necti_expand_home(const std::filesystem::path& input) { +std::filesystem::path nectar_expand_home(const std::filesystem::path& input) { const std::string& raw = input.string(); if (!raw.empty() && raw[0] == '~') { @@ -85,6 +85,61 @@ static Int32 kOnClassScope = 0; ///////////////////////////////////////////////////////////////////////////////////////// +// NEW DATA STRUCTURES FOR C++ SUPPORT + +///////////////////////////////////////////////////////////////////////////////////////// + +/// \brief Scope kind enumeration +enum class ScopeKind { + kScopeGlobal, + kScopeNamespace, + kScopeClass, + kScopeFunction, +}; + +/// \brief Compiler scope information +struct CompilerScope { + ScopeKind fKind{ScopeKind::kScopeGlobal}; + CompilerKit::STLString fName{}; + CompilerKit::STLString fMangledPrefix{}; +}; + +/// \brief Variable location enumeration +enum class VarLocation { + kRegister, + kStack, + kStackSpill, +}; + +/// \brief Extended variable information +struct VariableInfo { + CompilerKit::STLString fName{}; + VarLocation fLocation{VarLocation::kRegister}; + Int32 fStackOffset{0}; + CompilerKit::STLString fRegister{}; + Int32 fSize{8}; + bool fIsParameter{false}; + CompilerKit::STLString fTypeName{}; + UInt32 fLastUsed{0}; +}; + +/// \brief Complete compiler context +struct CompilerContext { + std::vector fScopeStack; + std::vector fVariables; + std::vector fStructMapVector; + CompilerKit::STLString fLastFile{}; + CompilerKit::STLString fLastError{}; + Int32 fStackOffset{0}; + Int32 fMaxStackUsed{0}; + UInt32 fInstructionCounter{0}; +}; + +/// \brief Global compiler context (replaces kState) +static CompilerContext kContext; + +///////////////////////////////////////////////////////////////////////////////////////// + /// \brief Target architecture. /// \note This shall never change. static Int32 kMachine = CompilerKit::AssemblyFactory::kArchAMD64; @@ -110,6 +165,47 @@ static bool kOnForLoop = false; static bool kInBraces = false; static size_t kBracesCount = 0UL; +///////////////////////////////////////////////////////////////////////////////////////// + +// HELPER FUNCTION DECLARATIONS + +///////////////////////////////////////////////////////////////////////////////////////// + +// Scope management +static void nectar_push_scope(ScopeKind kind, const CompilerKit::STLString& name); +static void nectar_pop_scope(); + +// Name mangling +static std::vector nectar_extract_function_args( + const CompilerKit::STLString& text); +static CompilerKit::STLString nectar_mangle_name( + const CompilerKit::STLString& identifier, const std::vector& args = {}); + +// Stack frame management +static CompilerKit::STLString nectar_generate_prologue(); +static CompilerKit::STLString nectar_generate_epilogue(); +static Int32 nectar_allocate_stack_variable(const CompilerKit::STLString& var_name, Int32 size = 8); + +// Register allocation +static CompilerKit::STLString nectar_allocate_register(const CompilerKit::STLString& var_name); +static CompilerKit::STLString nectar_spill_lru_variable(); +static VariableInfo* nectar_find_variable(const CompilerKit::STLString& var_name); +static CompilerKit::STLString nectar_get_variable_ref(const CompilerKit::STLString& var_name); + +// Class/object management +static void nectar_add_class_member(const CompilerKit::STLString& class_name, + const CompilerKit::STLString& member_name, Int32 size); +static Int32 nectar_get_class_size(const CompilerKit::STLString& class_name); +static CompilerKit::STLString nectar_generate_constructor_call( + const CompilerKit::STLString& class_name, const CompilerKit::STLString& obj_name); +static CompilerKit::STLString nectar_generate_destructor_call( + const CompilerKit::STLString& class_name, const CompilerKit::STLString& obj_name); + +// PEF calling convention +static void nectar_process_function_parameters(const std::vector& args); + +///////////////////////////////////////////////////////////////////////////////////////// + /* \brief C++ compiler backend for the NeKernel C++ driver */ class CompilerFrontendCPlusPlusAMD64 final CK_COMPILER_FRONTEND { public: @@ -155,7 +251,7 @@ static std::size_t kNamespaceEmbedLevel{}; /// detail namespaces const char* CompilerFrontendCPlusPlusAMD64::Language() { - return "NeKernel CT-C++"; + return "Nectar C++"; } static std::uintptr_t kOrigin = kPefBaseOrigin; @@ -315,16 +411,16 @@ CompilerKit::SyntaxLeafList::SyntaxLeaf CompilerFrontendCPlusPlusAMD64::Compile( case CompilerKit::KeywordKind::kKeywordKindFunctionStart: { for (auto& ch : text) { if (isdigit(ch)) { - goto dont_accept; + goto dont_accept_func; } } - goto accept; + goto accept_func; - dont_accept: + dont_accept_func: break; - accept: + accept_func: { CompilerKit::STLString symbol_name_fn = text; size_t indexFnName = 0; @@ -343,44 +439,70 @@ CompilerKit::SyntaxLeafList::SyntaxLeaf CompilerFrontendCPlusPlusAMD64::Compile( break; } - if (text.ends_with(";") && text.find("return") == CompilerKit::STLString::npos) - goto lc_write_assembly; + // Check if this is a function call (ends with ;) + if (text.ends_with(";") && text.find("return") == CompilerKit::STLString::npos) { + // Handle function call/jump + auto it = std::find_if( + kOriginMap.begin(), kOriginMap.end(), + [&symbol_name_fn](std::pair pair) -> bool { + return symbol_name_fn.find(pair.first) != CompilerKit::STLString::npos; + }); - indexFnName = 0; + if (it != kOriginMap.end()) { + std::stringstream ss; + ss << std::hex << it->second; - for (auto& ch : symbol_name_fn) { - if (ch == ' ' || ch == '\t') { - ch = '_'; + syntax_tree.fUserValue = "jmp " + ss.str() + "\n"; + kOrigin += 1UL; } + break; + } - ++indexFnName; + indexFnName = 0; + + // Extract clean function name + CompilerKit::STLString cleanFnName = symbol_name_fn; + if (cleanFnName.find("(") != CompilerKit::STLString::npos) { + cleanFnName = cleanFnName.substr(0, cleanFnName.find("(")); } - if (symbol_name_fn.find("(") != CompilerKit::STLString::npos) { - symbol_name_fn.erase(symbol_name_fn.find("(")); + // Remove whitespace/tabs + while (!cleanFnName.empty() && (cleanFnName.back() == ' ' || cleanFnName.back() == '\t')) { + cleanFnName.pop_back(); + } + while (!cleanFnName.empty() && + (cleanFnName.front() == ' ' || cleanFnName.front() == '\t')) { + cleanFnName.erase(0, 1); } - syntax_tree.fUserValue = "public_segment .code64 __NECTAR_" + symbol_name_fn + "\n"; - ++kFunctionEmbedLevel; + // Extract function arguments + auto args = nectar_extract_function_args(text); - kOriginMap.push_back({"__NECTAR_" + symbol_name_fn, kOrigin}); + // Generate mangled name + auto mangled_name = nectar_mangle_name(cleanFnName, args); - break; + // Generate function label and prologue + syntax_tree.fUserValue = "public_segment .code64 " + mangled_name + "\n"; + syntax_tree.fUserValue += nectar_generate_prologue(); - lc_write_assembly: - auto it = std::find_if( - kOriginMap.begin(), kOriginMap.end(), - [&symbol_name_fn](std::pair pair) -> bool { - return symbol_name_fn == pair.first; - }); + // Initialize function-local state + kContext.fVariables.clear(); + kContext.fStackOffset = 0; + kContext.fMaxStackUsed = 0; - if (it != kOriginMap.end()) { - std::stringstream ss; - ss << std::hex << it->second; + // Process function parameters + nectar_process_function_parameters(args); - syntax_tree.fUserValue = "jmp " + ss.str() + "\n"; - kOrigin += 1UL; - } + // Push function scope + nectar_push_scope(ScopeKind::kScopeFunction, cleanFnName); + + ++kFunctionEmbedLevel; + + kOriginMap.push_back({mangled_name, kOrigin}); + kOrigin += 2UL; // Account for prologue instructions + + break; + } } case CompilerKit::KeywordKind::kKeywordKindFunctionEnd: { if (kOnClassScope) --kOnClassScope; @@ -393,7 +515,14 @@ CompilerKit::SyntaxLeafList::SyntaxLeaf CompilerFrontendCPlusPlusAMD64::Compile( --kFunctionEmbedLevel; } - if (kFunctionEmbedLevel < 1) kRegisterMap.clear(); + // Pop function scope + nectar_pop_scope(); + + // Clear function-local state + if (kFunctionEmbedLevel < 1) { + kRegisterMap.clear(); + kContext.fVariables.clear(); + } break; } @@ -659,60 +788,110 @@ CompilerKit::SyntaxLeafList::SyntaxLeaf CompilerFrontendCPlusPlusAMD64::Compile( subText = subText.erase(subText.find(";")); size_t indxReg = 0UL; + // Trim whitespace + while (!subText.empty() && (subText.front() == ' ' || subText.front() == '\t')) { + subText.erase(0, 1); + } + while (!subText.empty() && (subText.back() == ' ' || subText.back() == '\t')) { + subText.pop_back(); + } + if (!subText.empty() && subText[0] != '\"' && subText[0] != '\'') { if (!isdigit(subText[0])) { for (auto pair : kRegisterMap) { ++indxReg; - syntax_tree.fUserValue = "mov rax, " + kRegisterList[indxReg - 1] + "\nret\n"; - kOrigin += 1UL; + syntax_tree.fUserValue = "mov rax, " + kRegisterList[indxReg - 1] + "\n" + + nectar_generate_epilogue() + "ret\n"; + kOrigin += 3UL; // mov + epilogue (2) + ret break; } } else { - syntax_tree.fUserValue = "mov rax, " + subText + "\nret\n"; - kOrigin += 1UL; + syntax_tree.fUserValue = + "mov rax, " + subText + "\n" + nectar_generate_epilogue() + "ret\n"; + kOrigin += 3UL; break; } - } else { + } else if (!subText.empty()) { syntax_tree.fUserValue = "__NECTAR_LOCAL_RETURN_STRING: db " + subText + ", 0\nmov rcx, __NECTAR_LOCAL_RETURN_STRING\n"; - syntax_tree.fUserValue += "mov rax, rcx\nret\n"; - kOrigin += 1UL; + syntax_tree.fUserValue += "mov rax, rcx\n" + nectar_generate_epilogue() + "ret\n"; + kOrigin += 4UL; // mov rcx + mov rax + epilogue (2) + ret break; } if (syntax_tree.fUserValue.empty()) { if (subText.find("(") != CompilerKit::STLString::npos) { - subText.erase(subText.find("(")); + // Check for namespace resolution + if (subText.find("::") != CompilerKit::STLString::npos) { + auto colonPos = subText.find("::"); + auto nsName = subText.substr(0, colonPos); + auto funcPart = subText.substr(colonPos + 2); + + // Trim + while (!nsName.empty() && (nsName.front() == ' ' || nsName.front() == '\t')) { + nsName.erase(0, 1); + } + while (!nsName.empty() && (nsName.back() == ' ' || nsName.back() == '\t')) { + nsName.pop_back(); + } - auto it = std::find_if( - kOriginMap.begin(), kOriginMap.end(), - [&subText](std::pair pair) -> bool { - return pair.first.find(subText) != CompilerKit::STLString::npos; - }); + // Extract function name + auto funcName = funcPart; + if (funcName.find("(") != CompilerKit::STLString::npos) { + funcName = funcName.substr(0, funcName.find("(")); + } - if (it == kOriginMap.end()) - CompilerKit::Detail::print_error("Invalid return value: " + subText, file); + // Trim + while (!funcName.empty() && (funcName.front() == ' ' || funcName.front() == '\t')) { + funcName.erase(0, 1); + } + while (!funcName.empty() && (funcName.back() == ' ' || funcName.back() == '\t')) { + funcName.pop_back(); + } - std::stringstream ss; - ss << it->second; + // Generate mangled name + nectar_push_scope(ScopeKind::kScopeNamespace, nsName); + auto mangled = nectar_mangle_name(funcName); + nectar_pop_scope(); - syntax_tree.fUserValue = "jmp " + ss.str() + "\nret\n"; - kOrigin += 1UL; - break; + syntax_tree.fUserValue = + "call " + mangled + "\n" + nectar_generate_epilogue() + "ret\n"; + kOrigin += 3UL; + break; + } else { + // Regular function call + subText.erase(subText.find("(")); + + auto it = std::find_if( + kOriginMap.begin(), kOriginMap.end(), + [&subText](std::pair pair) -> bool { + return pair.first.find(subText) != CompilerKit::STLString::npos; + }); + + if (it != kOriginMap.end()) { + std::stringstream ss; + ss << std::hex << it->second; + + syntax_tree.fUserValue = + "call " + ss.str() + "\n" + nectar_generate_epilogue() + "ret\n"; + kOrigin += 3UL; + break; + } + } } } - syntax_tree.fUserValue = "ret\n"; - kOrigin += 1UL; + syntax_tree.fUserValue = nectar_generate_epilogue() + "ret\n"; + kOrigin += 2UL; break; } catch (...) { - syntax_tree.fUserValue = "ret\n"; - kOrigin += 1UL; + syntax_tree.fUserValue = nectar_generate_epilogue() + "ret\n"; + kOrigin += 2UL; break; } @@ -731,11 +910,502 @@ CompilerKit::SyntaxLeafList::SyntaxLeaf CompilerFrontendCPlusPlusAMD64::Compile( CompilerKit::SyntaxLeafList::SyntaxLeaf CompilerFrontendCPlusPlusAMD64::CompilePass2( CompilerKit::STLString& text, const CompilerKit::STLString& file, CompilerKit::SyntaxLeafList::SyntaxLeaf& syntax_tree) { + // Handle namespace entry + if (text.find("namespace") != CompilerKit::STLString::npos && + text.find("{") != CompilerKit::STLString::npos) { + auto nsPos = text.find("namespace") + strlen("namespace"); + auto bracePos = text.find("{"); + + auto nsName = text.substr(nsPos, bracePos - nsPos); + + // Trim whitespace + while (!nsName.empty() && (nsName.front() == ' ' || nsName.front() == '\t')) { + nsName.erase(0, 1); + } + while (!nsName.empty() && (nsName.back() == ' ' || nsName.back() == '\t')) { + nsName.pop_back(); + } + + if (!nsName.empty()) { + nectar_push_scope(ScopeKind::kScopeNamespace, nsName); + ++kNamespaceEmbedLevel; + } + } + + // Handle namespace exit + if (text.find("}") != CompilerKit::STLString::npos && kNamespaceEmbedLevel > 0) { + if (text.find("//") != CompilerKit::STLString::npos && + text.find("namespace") != CompilerKit::STLString::npos) { + --kNamespaceEmbedLevel; + nectar_pop_scope(); + } + } + + // Handle namespace resolution (::) - only in non-assignment, non-declaration contexts + if (text.find("::") != CompilerKit::STLString::npos) { + auto colonPos = text.find("::"); + + // Extract namespace and function name + auto nsName = text.substr(0, colonPos); + auto restOfText = text.substr(colonPos + 2); + + // Trim namespace name + while (!nsName.empty() && (nsName.front() == ' ' || nsName.front() == '\t')) { + nsName.erase(0, 1); + } + while (!nsName.empty() && (nsName.back() == ' ' || nsName.back() == '\t')) { + nsName.pop_back(); + } + + // Extract function name (everything before '(' if exists) + auto funcName = restOfText; + if (funcName.find("(") != CompilerKit::STLString::npos) { + funcName = funcName.substr(0, funcName.find("(")); + } + if (funcName.find(";") != CompilerKit::STLString::npos) { + funcName = funcName.substr(0, funcName.find(";")); + } + + // Trim function name + while (!funcName.empty() && (funcName.front() == ' ' || funcName.front() == '\t')) { + funcName.erase(0, 1); + } + while (!funcName.empty() && (funcName.back() == ' ' || funcName.back() == '\t')) { + funcName.pop_back(); + } + + // Generate mangled name for the call + nectar_push_scope(ScopeKind::kScopeNamespace, nsName); + auto mangled = nectar_mangle_name(funcName); + nectar_pop_scope(); + + // Only generate call if in return context or explicit call + if (text.find("return") != CompilerKit::STLString::npos) { + // This will be handled by the return handler, just update the text to use mangled name + // Store this for use by the return handler + } else { + syntax_tree.fUserValue += "call " + mangled + "\n"; + kOrigin += 1UL; + } + } + + // Handle class entry + if ((text.find("class") != CompilerKit::STLString::npos || + text.find("struct") != CompilerKit::STLString::npos) && + text.find("{") != CompilerKit::STLString::npos) { + CompilerKit::STLString keyword = text.find("class") != CompilerKit::STLString::npos ? "class" : "struct"; + auto classPos = text.find(keyword) + keyword.length(); + auto bracePos = text.find("{"); + + auto className = text.substr(classPos, bracePos - classPos); + + // Trim whitespace + while (!className.empty() && (className.front() == ' ' || className.front() == '\t')) { + className.erase(0, 1); + } + while (!className.empty() && (className.back() == ' ' || className.back() == '\t')) { + className.pop_back(); + } + + if (!className.empty()) { + nectar_push_scope(ScopeKind::kScopeClass, className); + ++kOnClassScope; + } + } + + // Handle class exit + if (text.find("}") != CompilerKit::STLString::npos && kOnClassScope > 0) { + if (text.find(";") != CompilerKit::STLString::npos) { + --kOnClassScope; + nectar_pop_scope(); + } + } + return syntax_tree; } ///////////////////////////////////////////////////////////////////////////////////////// +// HELPER FUNCTION IMPLEMENTATIONS + +///////////////////////////////////////////////////////////////////////////////////////// + +/// \brief Push a new scope onto the scope stack +static void nectar_push_scope(ScopeKind kind, const CompilerKit::STLString& name) { + CompilerScope scope; + scope.fKind = kind; + scope.fName = name; + + // Build mangled prefix based on current scope stack + for (const auto& s : kContext.fScopeStack) { + if (s.fKind == ScopeKind::kScopeNamespace) { + scope.fMangledPrefix += "N_" + s.fName + "_"; + } else if (s.fKind == ScopeKind::kScopeClass) { + scope.fMangledPrefix += "C_" + s.fName + "_"; + } + } + + kContext.fScopeStack.push_back(scope); +} + +/// \brief Pop the current scope from the scope stack +static void nectar_pop_scope() { + if (!kContext.fScopeStack.empty()) { + kContext.fScopeStack.pop_back(); + } +} + +/// \brief Extract function arguments from function declaration +static std::vector nectar_extract_function_args( + const CompilerKit::STLString& text) { + std::vector args; + + auto openParen = text.find("("); + auto closeParen = text.find(")"); + + if (openParen == CompilerKit::STLString::npos || closeParen == CompilerKit::STLString::npos || + closeParen <= openParen) { + return args; + } + + auto argsText = text.substr(openParen + 1, closeParen - openParen - 1); + + // Trim whitespace + while (!argsText.empty() && (argsText.front() == ' ' || argsText.front() == '\t')) { + argsText.erase(0, 1); + } + while (!argsText.empty() && (argsText.back() == ' ' || argsText.back() == '\t')) { + argsText.pop_back(); + } + + if (argsText.empty() || argsText == "void") { + return args; + } + + // Simple comma-separated parsing + std::size_t pos = 0; + while (pos < argsText.size()) { + auto commaPos = argsText.find(",", pos); + if (commaPos == CompilerKit::STLString::npos) { + commaPos = argsText.size(); + } + + auto arg = argsText.substr(pos, commaPos - pos); + + // Extract type name (skip variable name) + std::size_t lastSpace = arg.rfind(' '); + if (lastSpace != CompilerKit::STLString::npos) { + arg = arg.substr(0, lastSpace); + } + + // Trim + while (!arg.empty() && (arg.front() == ' ' || arg.front() == '\t')) { + arg.erase(0, 1); + } + while (!arg.empty() && (arg.back() == ' ' || arg.back() == '\t')) { + arg.pop_back(); + } + + if (!arg.empty()) { + args.push_back(arg); + } + + pos = commaPos + 1; + } + + return args; +} + +/// \brief Mangle a function or method name according to Nectar mangling scheme +static CompilerKit::STLString nectar_mangle_name(const CompilerKit::STLString& identifier, + const std::vector& args) { + CompilerKit::STLString mangled = "__NECTAR_"; + + // Add scope chain + for (const auto& scope : kContext.fScopeStack) { + if (scope.fKind == ScopeKind::kScopeNamespace) { + mangled += "N_" + scope.fName + "_"; + } else if (scope.fKind == ScopeKind::kScopeClass) { + mangled += "C_" + scope.fName + "_"; + } + } + + // Check if it's a constructor or destructor + if (!kContext.fScopeStack.empty() && + kContext.fScopeStack.back().fKind == ScopeKind::kScopeClass) { + if (identifier == kContext.fScopeStack.back().fName) { + mangled += "CTOR"; + return mangled; + } else if (identifier == "~" + kContext.fScopeStack.back().fName) { + mangled += "DTOR"; + return mangled; + } + } + + // Check if we're in a class scope for member functions + bool inClass = false; + for (const auto& scope : kContext.fScopeStack) { + if (scope.fKind == ScopeKind::kScopeClass) { + inClass = true; + break; + } + } + + if (inClass) { + mangled += "M_" + identifier; + } else { + mangled += "F_" + identifier; + } + + // Add argument types if provided + if (!args.empty()) { + mangled += "_A" + std::to_string(args.size()); + for (const auto& arg : args) { + mangled += "_" + arg; + } + } + + return mangled; +} + +/// \brief Generate function prologue +static CompilerKit::STLString nectar_generate_prologue() { + return "push rbp\nmov rbp, rsp\n"; +} + +/// \brief Generate function epilogue +static CompilerKit::STLString nectar_generate_epilogue() { + return "mov rsp, rbp\npop rbp\n"; +} + +/// \brief Allocate a variable on the stack +static Int32 nectar_allocate_stack_variable(const CompilerKit::STLString& var_name, Int32 size) { + kContext.fStackOffset -= size; + kContext.fMaxStackUsed = std::min(kContext.fStackOffset, kContext.fMaxStackUsed); + + VariableInfo varInfo; + varInfo.fName = var_name; + varInfo.fLocation = VarLocation::kStack; + varInfo.fStackOffset = kContext.fStackOffset; + varInfo.fSize = size; + varInfo.fLastUsed = kContext.fInstructionCounter; + kContext.fVariables.push_back(varInfo); + + return kContext.fStackOffset; +} + +/// \brief Find a variable by name +static VariableInfo* nectar_find_variable(const CompilerKit::STLString& var_name) { + for (auto& var : kContext.fVariables) { + if (var.fName == var_name) { + return &var; + } + } + return nullptr; +} + +/// \brief Get variable reference (register or stack location) +static CompilerKit::STLString nectar_get_variable_ref(const CompilerKit::STLString& var_name) { + auto* varInfo = nectar_find_variable(var_name); + if (!varInfo) { + return ""; + } + + varInfo->fLastUsed = kContext.fInstructionCounter; + + if (varInfo->fLocation == VarLocation::kRegister) { + return varInfo->fRegister; + } else { + // Stack or spilled + return "qword [rbp" + std::to_string(varInfo->fStackOffset) + "]"; + } +} + +/// \brief Allocate a register for a variable +static CompilerKit::STLString nectar_allocate_register(const CompilerKit::STLString& var_name) { + // Check if variable already has a register + auto* existing = nectar_find_variable(var_name); + if (existing && existing->fLocation == VarLocation::kRegister) { + return existing->fRegister; + } + + // Find a free register + for (const auto& reg : kRegisterList) { + bool inUse = false; + for (const auto& var : kContext.fVariables) { + if (var.fLocation == VarLocation::kRegister && var.fRegister == reg) { + inUse = true; + break; + } + } + + if (!inUse) { + // Allocate this register + if (existing) { + existing->fLocation = VarLocation::kRegister; + existing->fRegister = reg; + existing->fLastUsed = kContext.fInstructionCounter; + } else { + VariableInfo varInfo; + varInfo.fName = var_name; + varInfo.fLocation = VarLocation::kRegister; + varInfo.fRegister = reg; + varInfo.fLastUsed = kContext.fInstructionCounter; + kContext.fVariables.push_back(varInfo); + } + return reg; + } + } + + // No free register + return ""; +} + +/// \brief Spill the least recently used variable to stack +static CompilerKit::STLString nectar_spill_lru_variable() { + CompilerKit::STLString spillCode; + + // Find LRU variable in register (that's not a parameter) + VariableInfo* lruVar = nullptr; + UInt32 minLastUsed = UINT32_MAX; + + for (auto& var : kContext.fVariables) { + if (var.fLocation == VarLocation::kRegister && !var.fIsParameter && + var.fLastUsed < minLastUsed) { + lruVar = &var; + minLastUsed = var.fLastUsed; + } + } + + if (!lruVar) { + return ""; // No variable to spill + } + + // Allocate stack space + kContext.fStackOffset -= lruVar->fSize; + kContext.fMaxStackUsed = std::min(kContext.fStackOffset, kContext.fMaxStackUsed); + + // Generate spill code + spillCode = + "mov qword [rbp" + std::to_string(kContext.fStackOffset) + "], " + lruVar->fRegister + "\n"; + + // Update variable info + lruVar->fLocation = VarLocation::kStackSpill; + lruVar->fStackOffset = kContext.fStackOffset; + auto spilledReg = lruVar->fRegister; + lruVar->fRegister = ""; + + return spillCode; +} + +/// \brief Add a class member to the struct map +static void nectar_add_class_member(const CompilerKit::STLString& class_name, + const CompilerKit::STLString& member_name, Int32 size) { + // Find or create struct map entry + CompilerStructMap* structMap = nullptr; + for (auto& sm : kContext.fStructMapVector) { + if (sm.fName == class_name) { + structMap = &sm; + break; + } + } + + if (!structMap) { + CompilerStructMap newMap; + newMap.fName = class_name; + kContext.fStructMapVector.push_back(newMap); + structMap = &kContext.fStructMapVector.back(); + } + + // Calculate offset + UInt32 offset = 0; + if (!structMap->fOffsets.empty()) { + offset = structMap->fOffsets.back().first + 8; // Assume 8-byte members for now + } + + structMap->fOffsets.emplace_back(offset, member_name); +} + +/// \brief Get the size of a class +static Int32 nectar_get_class_size(const CompilerKit::STLString& class_name) { + for (const auto& sm : kContext.fStructMapVector) { + if (sm.fName == class_name) { + if (sm.fOffsets.empty()) { + return 0; + } + return sm.fOffsets.back().first + 8; // Last offset + size + } + } + return 0; +} + +/// \brief Generate constructor call +static CompilerKit::STLString nectar_generate_constructor_call( + const CompilerKit::STLString& class_name, const CompilerKit::STLString& obj_name) { + auto size = nectar_get_class_size(class_name); + auto offset = nectar_allocate_stack_variable(obj_name, size == 0 ? 8 : size); + + nectar_push_scope(ScopeKind::kScopeClass, class_name); + auto ctor_mangled = nectar_mangle_name(class_name); + nectar_pop_scope(); + + CompilerKit::STLString code; + code += "lea r8, [rbp" + std::to_string(offset) + "]\n"; + code += "call " + ctor_mangled + "\n"; + return code; +} + +/// \brief Generate destructor call +static CompilerKit::STLString nectar_generate_destructor_call( + const CompilerKit::STLString& class_name, const CompilerKit::STLString& obj_name) { + auto* varInfo = nectar_find_variable(obj_name); + if (!varInfo) { + return ""; + } + + nectar_push_scope(ScopeKind::kScopeClass, class_name); + auto dtor_mangled = nectar_mangle_name("~" + class_name); + nectar_pop_scope(); + + CompilerKit::STLString code; + if (varInfo->fLocation == VarLocation::kStack || varInfo->fLocation == VarLocation::kStackSpill) { + code += "lea r8, [rbp" + std::to_string(varInfo->fStackOffset) + "]\n"; + } else { + code += "mov r8, " + varInfo->fRegister + "\n"; + } + code += "call " + dtor_mangled + "\n"; + return code; +} + +/// \brief Process function parameters per PEF calling convention +static void nectar_process_function_parameters(const std::vector& args) { + for (size_t i = 0; i < args.size() && i < 8; ++i) { + VariableInfo param; + param.fName = "arg" + std::to_string(i); + param.fLocation = VarLocation::kRegister; + param.fRegister = kRegisterConventionCallList[i]; + param.fIsParameter = true; + param.fTypeName = args[i]; + param.fLastUsed = kContext.fInstructionCounter; + kContext.fVariables.push_back(param); + } + + // Args beyond r15 go on stack + for (size_t i = 8; i < args.size(); ++i) { + Int32 offset = 16 + (i - 8) * 8; + VariableInfo param; + param.fName = "arg" + std::to_string(i); + param.fLocation = VarLocation::kStack; + param.fStackOffset = offset; // Positive (before rbp) + param.fIsParameter = true; + param.fTypeName = args[i]; + param.fLastUsed = kContext.fInstructionCounter; + kContext.fVariables.push_back(param); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + /** * @brief C++ assembler class. */ @@ -755,7 +1425,7 @@ class AssemblyCPlusPlusInterfaceAMD64 final CK_ASSEMBLY_INTERFACE { if (kFrontend == nullptr) return EXIT_FAILURE; CompilerKit::STLString dest = src; - dest += ".pp.masm"; + dest += ".masm"; std::ofstream out_fp(dest); std::ifstream src_fp = std::ifstream(src); @@ -780,7 +1450,7 @@ class AssemblyCPlusPlusInterfaceAMD64 final CK_ASSEMBLY_INTERFACE { ///////////////////////////////////////////////////////////////////////////////////////// -#define kExtListCxx {".cpp", ".cc", ".cc", ".c++", ".cp", ".kpp", ".k++"} +#define kExtListCxx {".cpp", ".cc", ".cc", ".c++", ".cp", ".ipp", ".i++"} NECTAR_MODULE(CompilerCPlusPlusAMD64) { bool skip = false; diff --git a/test/test_01_codegen/codegen.test.cc b/test/test_01_codegen/codegen.test.cc index 37e201e..3cee6fb 100644 --- a/test/test_01_codegen/codegen.test.cc +++ b/test/test_01_codegen/codegen.test.cc @@ -8,11 +8,6 @@ #include -TEST(CodegenTest, BasicCodegenTestAssemble) { - auto expr = std::system("asm -asm-x64 test_samples/sample.asm"); - EXPECT_TRUE(expr == 0) << "ASM Driver did not compile the easy ASM unit."; -} - TEST(CodegenTest, BasicCodegenTestGrep) { // Compile C++ source to assembly auto compile_result = std::system("pef-amd64-cxxdrv ../test_samples/sample.cc > /dev/null 2>&1"); @@ -30,3 +25,8 @@ TEST(CodegenTest, BasicCodegenTestGrep) { auto grep_bits64 = std::system("grep -q '%bits 64' ../test_samples/sample.cc.pp.masm"); EXPECT_TRUE(grep_bits64 == 0) << "Generated assembly missing 64-bit mode directive"; } + +TEST(CodegenTest, BasicCodegenTestAssemble) { + auto expr = std::system("asm -asm-x64 test_samples/sample.asm"); + EXPECT_TRUE(expr == 0) << "ASM Driver did not compile the easy ASM unit."; +} diff --git a/test/test_samples/class_ctor_dtor.cc b/test/test_samples/class_ctor_dtor.cc deleted file mode 100644 index 728a62c..0000000 --- a/test/test_samples/class_ctor_dtor.cc +++ /dev/null @@ -1,15 +0,0 @@ -#define main __ImageStart - -class MyClass { - MyClass() { - // Constructor - } - - ~MyClass() { - // Destructor - } -}; - -int main() { - return 0; -} diff --git a/test/test_samples/class_methods.cc b/test/test_samples/class_methods.cc deleted file mode 100644 index ce9a73a..0000000 --- a/test/test_samples/class_methods.cc +++ /dev/null @@ -1,13 +0,0 @@ -#define main __ImageStart - -class Counter { - int count; - - void increment() { count += 1; } - - int getCount() { return count; } -}; - -int main() { - return 0; -} diff --git a/test/test_samples/class_simple.cc b/test/test_samples/class_simple.cc deleted file mode 100644 index 604922d..0000000 --- a/test/test_samples/class_simple.cc +++ /dev/null @@ -1,10 +0,0 @@ -#define main __ImageStart - -class Point { - int x; - int y; -}; - -int main() { - return 0; -} diff --git a/test/test_samples/namespace_basic.cc b/test/test_samples/namespace_basic.cc deleted file mode 100644 index 357304b..0000000 --- a/test/test_samples/namespace_basic.cc +++ /dev/null @@ -1,9 +0,0 @@ -namespace Math { -int add() { - return 5; -} -} // namespace Math - -int main() { - return Math::add(); -} diff --git a/test/test_samples/sample.cc b/test/test_samples/sample.cc index 4cce7f6..6a6bc44 100644 --- a/test/test_samples/sample.cc +++ b/test/test_samples/sample.cc @@ -1,3 +1,24 @@ -int main() { +class ostream +{ + ostream() + { + return void; + } + + ~ostream() + { + return void; + } + + ostream& noop(const ostream in) + { + return *this; + } +}; + +int main() +{ + void* f = new ostream(); + f->noop(f); return 0; } -- cgit v1.2.3