diff --git a/.github/workflows/cmake-linux-x86.yml b/.github/workflows/cmake-linux-x86.yml index 90e329a..856dc30 100644 --- a/.github/workflows/cmake-linux-x86.yml +++ b/.github/workflows/cmake-linux-x86.yml @@ -23,6 +23,19 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Cache LLVM build + uses: actions/cache@v3 + id: cache-dependencies + with: + # Adjust these paths to match where LLVM is built and installed + path: | + Asa-LLVM-Compiled + /usr/lib + /usr/include + key: llvm-build-${{ hashFiles('Asa-LLVM-Compiled/llvm/**', 'Asa-LLVM-Compiled/build/CMakeCache.txt') }} + restore-keys: | + llvm-build- + - name: Set up Ninja uses: ashutoshvarma/setup-ninja@93f8b9763516f1fb9b4d9840b12d844bee17791f @@ -34,23 +47,30 @@ jobs: version: latest platform: x64 - - name: Setup LLVM - # You may pin to the exact commit or the version. - uses: ZhongRuoyu/setup-llvm@v0.1.1 - with: - llvm-version: 20 - - # - name: Setup LLVM -# run: | +# # You may pin to the exact commit or the version. +# uses: ZhongRuoyu/setup-llvm@v0.1.1 +# with: +# llvm-version: 20 + + + + - name: Setup LLVM + if: steps.cache-dependencies.outputs.cache-hit != 'true' + run: | + git clone --depth 1 https://github.com/sam-astro/Asa-LLVM-Compiled + cd Asa-LLVM-Compiled + mkdir build + cmake -S llvm -B build -G Ninja -DLLVM_TARGETS_TO_BUILD="X86" -DCMAKE_BUILD_TYPE=MinSizeRel + cd build + ninja + sudo ninja install + cd ../../ # git config --add remote.origin.fetch '^refs/heads/users/*' # git config --add remote.origin.fetch '^refs/heads/revert-*' -# git clone --depth 1 https://github.com/llvm/llvm-project.git # cd llvm-project # git fetch origin 7615503409f19ad7e2e2f946437919d0689d4b3e # cmake -S llvm -B build -G Ninja -DLLVM_TARGETS_TO_BUILD="X86" -DCMAKE_BUILD_TYPE=MinSizeRel -# cmake --build build -# cmake --install build - name: Configure CMake @@ -69,5 +89,7 @@ jobs: working-directory: ${{github.workspace}}/build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ./${{github.workspace}}/build/asa + run: | + cd ${{github.workspace}}/build + ./asa -V diff --git a/README.md b/README.md index 9456cbf..2249157 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,7 @@ - Asa Programming Language + Asa Programming Language @@ -14,6 +12,8 @@ This repository contains all of the source code for the Asa programming language compiler and standard libraries. +> There are no releases currently, and much of the current code is subject to change + [License]: LICENSE diff --git a/examples/start/main.asa b/examples/start/main.asa index cbf82dc..4da6923 100644 --- a/examples/start/main.asa +++ b/examples/start/main.asa @@ -7,6 +7,8 @@ #import Builtin.PipeOperatorTest; +#import Rendering.Window; + //#import GameLogic; //#file "./function_redefine.asa"; @@ -28,25 +30,25 @@ create::string() #inline; #replaceable; { return *(#new string); } create :: string(c : *char) { - s : string = *(#new string); + s : string = string(); puts("Creating new string from *char\n"); - len : uint32 = 10; - //for(i : 0..4_294_967_296){ - // //if(c[i] == '\0') - // // break; - // len++; + //len : uint32 = 10; + ////for(i : 0..4_294_967_296){ + //// //if(c[i] == '\0') + //// // break; + //// len++; + ////} + //s.address = malloc(10*2); + //s.length = 10; + ////len = 5; + ////s.address = malloc(5); + //for(i : 0..len){ + // s.address[i] = 'A'; + // //s.address[i] = c[i]; //} - s.address = malloc(10*2); - s.length = 10; - //len = 5; - //s.address = malloc(5); - for(i : 0..len){ - s.address[i] = 'A'; - //s.address[i] = c[i]; - } - //s.length = len; - //s.address = "String value example"; + ////s.length = len; + ////s.address = "String value example"; puts("Done!\n"); return s; } @@ -73,7 +75,7 @@ TEST_MACRO :: {printl("Macro works!")}; //} printint:: (n : uint64) #hideast { - putchar(48+n); + putchar(int(48+n)); } modifyReference::(n : ref int) #hideast { @@ -103,11 +105,13 @@ main :: (){ //var : bool; + pipeVal = pipeTest(1); + printint(pipeVal); + newline(); + s : string = string(); s = string("This works"); - pipeTest(1); - //s.length = 8; //printint(s.length); //s.size(); @@ -116,6 +120,7 @@ main :: (){ printint(s.length); newline(); s.print(); + newline(); puts("Done!"); newline(); @@ -132,7 +137,7 @@ main :: (){ //x = x+1; //putchar(*x_ptr); //putchar(32); - + // //arr : *int = malloc(8*4); //arr[2] = 72; diff --git a/modules/Builtin/asa.asa b/modules/Builtin/asa.asa index 8fd1e01..9545128 100644 --- a/modules/Builtin/asa.asa +++ b/modules/Builtin/asa.asa @@ -178,10 +178,10 @@ Strings :: module{ PipeOperatorTest :: module{ fnX :: int(x : int){ - return x*5; + return x*3; } fnY :: int(y : int){ - return y*5; + return y*2; } pipeTest::int(z : int){ return fnX(z) -> fnY(%); diff --git a/modules/Rendering/window.asa b/modules/Rendering/window.asa new file mode 100644 index 0000000..5b269e5 --- /dev/null +++ b/modules/Rendering/window.asa @@ -0,0 +1,4 @@ + +Window :: module{ + +} diff --git a/src/codegen.cpp b/src/codegen.cpp index 2414ff4..9fa0b99 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -20,6 +20,7 @@ llvm::Value* castValue(llvm::Value* value, llvm::Type* destType, bool isSrcSigne std::unordered_map unresolvedTypes; std::stack lastRetrievedElementType; +std::stack pipeOperationValue; std::unordered_map typeSigns = { {"int128", true}, @@ -90,7 +91,10 @@ struct functionID { } void print() { - console::Write(returnType + " ", console::blueFGColor); + if (uses == 0) + return; + if (returnType != "") + console::Write(returnType + " ", console::blueFGColor); console::Write(name, console::greenFGColor); console::Write("("); for (int i = 0; i < arguments.size(); i++) { @@ -100,7 +104,13 @@ struct functionID { if (i < arguments.size() - 1) console::Write(", "); } - console::WriteLine(")"); + console::Write(")"); + if (isStructReturn) { + console::Write(" ("); + console::Write("returns struct", console::yellowFGColor); + console::Write(")"); + } + console::WriteLine(); } bool compareASTNodeTypes(ASTNodeType& a, ASTNodeType& b, bool wereTypesInferred = false) { @@ -253,6 +263,12 @@ structType* getStructTypeFromLLVMType(Type*& t) return nullptr; } +void printFunctionPrototypes() +{ + for (const auto& f : functionIDs) + f->print(); +} + functionID* getFunctionFromID(std::vector& fnIDs, std::string& name, argumentList& arguments, tokenPair*& t, bool wereTypesInferred = false, bool isMemberFunction = false) { functionID* best; @@ -967,7 +983,7 @@ void* ASTNode::generateVariableExpression(int pass) return Builder->CreateLoad(targetPtr->getAllocatedType(), targetPtr, token->first + "_load"); } AllocaInst* A = (AllocaInst*)(val->val); - baseType = A->getAllocatedType(); + baseType = val->val->getType(); if (isRef || lvalue) return A; @@ -987,43 +1003,45 @@ void* ASTNode::generateReturn(int pass) if (wasError) { exit(1); } + Function* currentFunc = Builder->GetInsertBlock()->getParent(); + functionID* fnID = getFunctionIDFromFunctionPointer(functionIDs, currentFunc); // Check if we're returning a struct Type* returnType = Builder->GetInsertBlock()->getParent()->getReturnType(); - if (returnType->isStructTy()) { - // For struct returns, we need to handle this specially - // Option 1: If the function uses sret, copy to the sret parameter - Function* currentFunc = Builder->GetInsertBlock()->getParent(); - if (currentFunc->hasStructRetAttr()) { - // Get the sret parameter (first parameter) - Value* sretPtr = &*currentFunc->arg_begin(); - - // Copy the struct value to the sret location - if (RetVal->getType()->isPointerTy()) { - // If RetVal is a pointer to struct, memcpy from it - Value* structSize = ConstantInt::get(Type::getInt64Ty(*TheContext), - TheModule->getDataLayout().getTypeAllocSize(returnType)); - - // Create memcpy call - Function* memcpyFunc = Intrinsic::getDeclaration(TheModule.get(), - Intrinsic::memcpy, {sretPtr->getType(), RetVal->getType(), Type::getInt64Ty(*TheContext)}); - Builder->CreateCall(memcpyFunc, {sretPtr, RetVal, structSize, ConstantInt::get(Type::getInt1Ty(*TheContext), 0)}); - } - else { - // If RetVal is a struct value, store it - Builder->CreateStore(RetVal, sretPtr); - } - - - Builder->CreateRetVoid(); + if (fnID->isStructReturn) { + ////if (returnType->isStructTy()) { + //// For struct returns, we need to handle this specially + //// Option 1: If the function uses sret, copy to the sret parameter + //if (currentFunc->hasStructRetAttr()) { + // Get the sret parameter (first parameter) + Value* sretPtr = &*currentFunc->arg_begin(); + + // Copy the struct value to the sret location + if (RetVal->getType()->isPointerTy()) { + // If RetVal is a pointer to struct, memcpy from it + Value* structSize = ConstantInt::get(Type::getInt64Ty(*TheContext), + TheModule->getDataLayout().getTypeAllocSize(returnType)); + + // Create memcpy call + Function* memcpyFunc = Intrinsic::getDeclaration(TheModule.get(), + Intrinsic::memcpy, {sretPtr->getType(), RetVal->getType(), Type::getInt64Ty(*TheContext)}); + Builder->CreateCall(memcpyFunc, {sretPtr, RetVal, structSize, ConstantInt::get(Type::getInt1Ty(*TheContext), 0)}); } else { - // Option 2: Direct struct return (for small structs) - if (RetVal->getType()->isPointerTy()) { - // Load the struct value from the pointer - RetVal = Builder->CreateLoad(returnType, RetVal, "struct_ret_load"); - } - Builder->CreateRet(RetVal); + // If RetVal is a struct value, store it + Builder->CreateStore(RetVal, sretPtr); } + + + Builder->CreateRetVoid(); + //} + //else { + // // Option 2: Direct struct return (for small structs) + // if (RetVal->getType()->isPointerTy()) { + // // Load the struct value from the pointer + // RetVal = Builder->CreateLoad(returnType, RetVal, "struct_ret_load"); + // } + // Builder->CreateRet(RetVal); + //} } else { // Non-struct return, handle normally @@ -1397,6 +1415,19 @@ void* ASTNode::generateBinaryExpression(int pass) return nullptr; } + // Check if it is the pipe operator first + if (nodeType == Pipe_Operation) { + // add L to stack + Value* L = (Value*)(childNodes[0]->*(childNodes[0]->codegen))(pass); + pipeOperationValue.push(L); + // then process R + Value* R = (Value*)(childNodes[1]->*(childNodes[1]->codegen))(pass); + // pop stack + pipeOperationValue.pop(); + + return R; + } + Value* L = (Value*)(childNodes[0]->*(childNodes[0]->codegen))(pass); Value* R = (Value*)(childNodes[1]->*(childNodes[1]->codegen))(pass); @@ -1412,14 +1443,26 @@ void* ASTNode::generateBinaryExpression(int pass) if (L->getType() != R->getType()) { printTokenWarning(token, "Operand type mismatch, performing implicit conversion"); castToHighestAccuracy(L, R, token); + if (L->getType() != R->getType()) { + printTokenError(token, "Operands to multiply are not the same type (after automatic cast)"); + return nullptr; + } } ValueCategory category = getValueCategory(L->getType()); switch (category) { case ValueCategory::Integer: + if (!L->getType()->isIntegerTy() || !R->getType()->isIntegerTy()) { + printTokenError(token, "Multiply: operands are not both integers"); + return nullptr; + } return generateIntegerBinaryOp(L, R); case ValueCategory::Float: + if (!L->getType()->isFloatTy() || !R->getType()->isFloatTy()) { + printTokenError(token, "Multiply: operands are not both integers"); + return nullptr; + } return generateFloatBinaryOp(L, R); case ValueCategory::Pointer: return generatePointerBinaryOp(L, R); @@ -1489,6 +1532,15 @@ Value* ASTNode::generatePointerBinaryOp(Value* L, Value* R) return nullptr; } +void* ASTNode::generatePipePlaceholder(int pass) +{ + if (pipeOperationValue.size() > 0) { + return pipeOperationValue.top(); + } + printTokenError(token, "Pipe operation placeholder '%' can only be used after a pipe operation"); + return nullptr; +} + bool ASTNode::checkForOperatorOverload() { std::string operatorName = "operator." + tokenAsString(token->second); @@ -1856,7 +1908,17 @@ void* ASTNode::generateMemberAccess(int pass) isCallMemberFunction = false; baseType = lastRetrievedElementType.top(); - return Builder->CreateCall(CalleeF, ArgsV, "calltmp"); + Value* callResult = nullptr; + Type* retType = CalleeF->getReturnType(); + + // Create the call + if (retType->isVoidTy()) + Builder->CreateCall(CalleeF, ArgsV); + else + callResult = Builder->CreateCall(CalleeF, ArgsV, "calltmp"); + return callResult; + + //return Builder->CreateCall(CalleeF, ArgsV, "calltmp"); } } // If left is not pointer, assume another member access or index operator @@ -2001,7 +2063,17 @@ void* ASTNode::generateMemberAccess(int pass) isCallMemberFunction = false; baseType = lastRetrievedElementType.top(); - return Builder->CreateCall(CalleeF, ArgsV, "calltmp"); + Value* callResult = nullptr; + Type* retType = CalleeF->getReturnType(); + + // Create the call + if (retType->isVoidTy()) + Builder->CreateCall(CalleeF, ArgsV); + else + callResult = Builder->CreateCall(CalleeF, ArgsV, "calltmp"); + return callResult; + + //return Builder->CreateCall(CalleeF, ArgsV, "calltmp"); } } @@ -2221,8 +2293,14 @@ void* ASTNode::generateCallExpression(int pass) ArgsV.insert(ArgsV.begin(), sretAlloc); } - // Create the call (returns void for struct returns) - Value* callResult = Builder->CreateCall(CalleeF, ArgsV, "calltmp"); + Value* callResult = nullptr; + Type* retType = CalleeF->getReturnType(); + + // Create the call + if (retType->isVoidTy()) + Builder->CreateCall(CalleeF, ArgsV); + else + callResult = Builder->CreateCall(CalleeF, ArgsV, "calltmp"); // For struct returns, return the loaded struct value (or pointer if lvalue) if (isStructReturn) { @@ -2982,11 +3060,18 @@ int outputObjectFile(std::string& objectFilePath) return 0; } -int generateExecutable(const std::string& objectFilePath, const std::string& exeFilePath) +int generateExecutable(const std::string& irFilePath, const std::string& exeFilePath) { - // Example using clang as the linker - std::string command = "clang -o " + exeFilePath + " " + objectFilePath; - int result = std::system(command.c_str()); + // llc to convert .ll to assembly + std::string commandLLC = "llc -relocation-model=pic " + irFilePath + " -o " + irFilePath + ".s"; + int result = std::system(commandLLC.c_str()); + if (result != 0) + exit(1); + // clang as the linker + std::string commandClang = "clang -fPIE -o " + exeFilePath + " " + irFilePath + ".s -g"; + result = std::system(commandClang.c_str()); + if (result != 0) + exit(1); return result; } diff --git a/src/codegen.h b/src/codegen.h index 357193f..08bdf59 100644 --- a/src/codegen.h +++ b/src/codegen.h @@ -22,5 +22,6 @@ extern bool wasError; void initializeCodeGenerator(); int outputObjectFile(std::string& objectFilePath); -int generateExecutable(const std::string& objectFilePath, const std::string& exeFilePath); +int generateExecutable(const std::string& irFilePath, const std::string& exeFilePath); void removeUnusedPrototypes(); +void printFunctionPrototypes(); diff --git a/src/main.cpp b/src/main.cpp index f4bfbca..0311bce 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,9 +34,10 @@ int main(int argc, char** argv) {"output", required_argument, 0, 'o'}, {"optimize", required_argument, 0, 'O'}, {"compilerdebug", no_argument, 0, 'd'}, + {"version", no_argument, 0, 'V'}, {0, 0, 0, 0}}; - c = getopt_long(argc, argv, "cvqdf:o:O:0", + c = getopt_long(argc, argv, "cvqdVf:o:O:0", long_options, &option_index); if (c == -1) break; @@ -88,6 +89,10 @@ int main(int argc, char** argv) compilerDebug = true; break; + case 'V': + console::ResetColor(); + exit(0); + case '?': console::ResetColor(); exit(1); @@ -219,18 +224,47 @@ int main(int argc, char** argv) TheModule->print(errs(), nullptr); } - //// Verify the module - //if (llvm::verifyModule(*TheModule, &llvm::errs())) { - // std::cerr << "Module verification failed!\n"; - // abort(); - //} + // Write IR to /build/.ll + std::string irFilePath = projectDirectory + "build/" + baseFileName + ".ll"; + std::error_code EC; + llvm::raw_fd_ostream OS(irFilePath, EC, llvm::sys::fs::OF_None); + if (EC) { + // Handle error + llvm::errs() << "Could not open file: " << EC.message() << "\n"; + exit(1); + } + TheModule->print(OS, nullptr); + OS.close(); + + + // Verify the module + if (compilerDebug) { + console::WriteLine("\nVerifying code:"); + if (llvm::verifyModule(*TheModule, &llvm::errs())) { + std::cerr << "Module verification failed!\n"; + abort(); + } + } + + // Print out all function prototypes + if (verbosity >= 4) + printFunctionPrototypes(); - // Output the object file in project's build directory - std::string objectFilePath = outputFileName + ".o"; - outputObjectFile(objectFilePath); + //// Output the object file in project's build directory + //std::string objectFilePath = outputFileName + ".o"; + //outputObjectFile(objectFilePath); // Link the object file into executable - generateExecutable(objectFilePath, outputFileName); + generateExecutable(irFilePath, outputFileName); if (verbosity >= 1) - console::WriteLine("\n\nWrote executable to " + outputFileName); + console::WriteLine("\nWrote executable to " + outputFileName); + + + // Cleanup by deleting files only used for codegen. + if (verbosity >= 3) { + console::WriteLine("Cleaning up files: " + irFilePath); + console::WriteLine("Cleaning up files: " + irFilePath + ".s"); + } + std::filesystem::remove(irFilePath); + std::filesystem::remove(irFilePath + ".s"); } diff --git a/src/parser.cpp b/src/parser.cpp index 3b15408..c2a37fb 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -780,6 +780,7 @@ ASTNode* generateAST(const std::vector& tokens, int depth, ASTNode* case At_At: { bool isUnaryR = false; // Operates on right bool isUnaryL = false; // Left + bool noOp = false; bool isGeneralOperator = false; if (operatorDefaultNodeType.find(tokenType) != operatorDefaultNodeType.end()) { node->nodeType = operatorDefaultNodeType[tokenType]; @@ -890,6 +891,12 @@ ASTNode* generateAST(const std::vector& tokens, int depth, ASTNode* node->nodeType = Pointer_Node; isUnaryL = true; } + // No expression operator % when used in conjunction with pipe operator + else if (isUnaryR && tokenType == Percent) { + node->nodeType = Pipe_Placeholder; + node->codegen = &ASTNode::generatePipePlaceholder; + noOp = true; + } else { printTokenError(token, "Operator expected right argument"); exit(1); @@ -899,10 +906,12 @@ ASTNode* generateAST(const std::vector& tokens, int depth, ASTNode* secondTerm = generateAST(subTokens, depth + 1)->childNodes[0]; //secondTerm->nodeType = Expression_Term; - if (!isUnaryR) - node->childNodes.push_back(firstTerm); - if (!isUnaryL) - node->childNodes.push_back(secondTerm); + if (!noOp) { + if (!isUnaryR) + node->childNodes.push_back(firstTerm); + if (!isUnaryL) + node->childNodes.push_back(secondTerm); + } if (isLeaf) goto addNodeAsLeaf; break; diff --git a/src/parser.h b/src/parser.h index 366b9c1..ace6865 100644 --- a/src/parser.h +++ b/src/parser.h @@ -70,6 +70,7 @@ enum ASTNodeType { Bitwise_Shift_Left, Bitwise_Shift_Right, Pipe_Operation, + Pipe_Placeholder, Range_Node, @@ -176,6 +177,7 @@ const std::string ASTNodeTypeStrings[] = { "Bitwise_Shift_Left", "Bitwise_Shift_Right", "Pipe_Operation", + "Pipe_Placeholder", "Range_Node", @@ -272,6 +274,7 @@ struct ASTNode { void* generateIterator(int pass = 0); void* generateUnaryExpression(int pass = 0); void* generateBinaryExpression(int pass = 0); + void* generatePipePlaceholder(int pass = 0); void* generateAccessOperation(int pass = 0); void* generateMemberAccess(int pass = 0); void* generateScopeBody(int pass = 0); diff --git a/src/run.sh b/src/run.sh index f664658..b1eee5e 100644 --- a/src/run.sh +++ b/src/run.sh @@ -8,4 +8,4 @@ cmake -G Ninja ../src; echo -e "\nRunning Ninja..."; ninja -j12; echo -e "\nStarting asa..."; -./asa; +./asa -V; diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index c73f797..71f7cc0 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -176,7 +176,7 @@ int tokenize(std::string& rawFile, std::vector& tokens, std::string& fileNames.push_back(new std::string(fileName)); // Then start making tokens - int lineNumber = 1; + int lineNumber = 0; int indexInLine = 0; int startIndexInLine = 0; std::string* lineValue = new std::string("");