├── .clang-format ├── .clang-tidy ├── .gitignore ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── README.md ├── examples ├── class.lox ├── fib.lox └── helloworld.lox └── src ├── Debug.h ├── Util.h ├── compiler ├── Callstack.cpp ├── Callstack.h ├── Class.cpp ├── Expr.cpp ├── Function.cpp ├── FunctionCompiler.cpp ├── FunctionCompiler.h ├── GC.cpp ├── GC.h ├── LoxBuilder.h ├── LoxModule.cpp ├── LoxModule.h ├── MDUtil.h ├── Memory.cpp ├── Memory.h ├── ModuleCompiler.cpp ├── ModuleCompiler.h ├── Stack.cpp ├── Stack.h ├── Stmt.cpp ├── String.cpp ├── Table.cpp ├── Table.h ├── Upvalue.cpp ├── Upvalue.h ├── Value.cpp └── Value.h ├── frontend ├── AST.h ├── Error.h ├── Parser.h ├── Resolver.h ├── Scanner.h └── Token.h ├── interpreter ├── Environment.cpp ├── Environment.h ├── Interpreter.cpp ├── Interpreter.h ├── LoxCallable.h ├── LoxClass.cpp ├── LoxClass.h ├── LoxFunction.cpp ├── LoxFunction.h ├── LoxInstance.cpp ├── LoxInstance.h ├── LoxObject.cpp ├── LoxObject.h └── NativeFunction.h └── main.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | # Generated from CLion C/C++ Code Style settings 2 | BasedOnStyle: LLVM 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: BlockIndent 5 | AlignConsecutiveAssignments: None 6 | AlignOperands: Align 7 | AllowAllArgumentsOnNextLine: true 8 | AllowAllConstructorInitializersOnNextLine: false 9 | AllowAllParametersOfDeclarationOnNextLine: false 10 | AllowShortBlocksOnASingleLine: Always 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: All 13 | AllowShortIfStatementsOnASingleLine: Always 14 | AllowShortLambdasOnASingleLine: All 15 | AllowShortLoopsOnASingleLine: true 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakTemplateDeclarations: Yes 18 | BreakBeforeBraces: Custom 19 | BraceWrapping: 20 | AfterCaseLabel: false 21 | AfterClass: false 22 | AfterControlStatement: Never 23 | AfterEnum: false 24 | AfterFunction: false 25 | AfterNamespace: false 26 | AfterUnion: false 27 | BeforeCatch: false 28 | BeforeElse: false 29 | IndentBraces: false 30 | SplitEmptyFunction: false 31 | SplitEmptyRecord: true 32 | BreakBeforeBinaryOperators: None 33 | BreakBeforeTernaryOperators: true 34 | BreakConstructorInitializers: BeforeColon 35 | BreakInheritanceList: BeforeColon 36 | ColumnLimit: 120 37 | CompactNamespaces: false 38 | ContinuationIndentWidth: 4 39 | IndentCaseLabels: true 40 | IndentPPDirectives: None 41 | IndentWidth: 4 42 | KeepEmptyLinesAtTheStartOfBlocks: true 43 | MaxEmptyLinesToKeep: 2 44 | NamespaceIndentation: All 45 | ObjCSpaceAfterProperty: false 46 | ObjCSpaceBeforeProtocolList: true 47 | PointerAlignment: Right 48 | ReflowComments: false 49 | SpaceAfterCStyleCast: true 50 | SpaceAfterLogicalNot: false 51 | SpaceAfterTemplateKeyword: false 52 | SpaceBeforeAssignmentOperators: true 53 | SpaceBeforeCpp11BracedList: false 54 | SpaceBeforeCtorInitializerColon: true 55 | SpaceBeforeInheritanceColon: true 56 | SpaceBeforeParens: ControlStatements 57 | SpaceBeforeRangeBasedForLoopColon: false 58 | SpaceInEmptyParentheses: false 59 | SpacesBeforeTrailingComments: 0 60 | SpacesInAngles: false 61 | SpacesInCStyleCastParentheses: false 62 | SpacesInContainerLiterals: false 63 | SpacesInParentheses: false 64 | SpacesInSquareBrackets: false 65 | TabWidth: 4 66 | UseTab: Never 67 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | # Generated from CLion Inspection settings 2 | --- 3 | Checks: '-*, 4 | cppcoreguidelines-interfaces-global-init, 5 | cppcoreguidelines-narrowing-conversions, 6 | cppcoreguidelines-pro-type-member-init, 7 | cppcoreguidelines-pro-type-static-cast-downcast, 8 | cppcoreguidelines-slicing, 9 | google-default-arguments, 10 | google-explicit-constructor, 11 | google-runtime-operator, 12 | hicpp-exception-baseclass, 13 | hicpp-multiway-paths-covered, 14 | misc-misplaced-const, 15 | misc-new-delete-overloads, 16 | misc-no-recursion, 17 | misc-non-copyable-objects, 18 | misc-throw-by-value-catch-by-reference, 19 | misc-unconventional-assign-operator, 20 | misc-uniqueptr-reset-release, 21 | mpi-buffer-deref, 22 | mpi-type-mismatch, 23 | openmp-use-default-none, 24 | performance-faster-string-find, 25 | performance-for-range-copy, 26 | performance-implicit-conversion-in-loop, 27 | performance-inefficient-algorithm, 28 | performance-inefficient-string-concatenation, 29 | performance-inefficient-vector-operation, 30 | performance-move-const-arg, 31 | performance-move-constructor-init, 32 | performance-no-automatic-move, 33 | performance-noexcept-move-constructor, 34 | performance-trivially-destructible, 35 | performance-type-promotion-in-math-fn, 36 | performance-unnecessary-copy-initialization, 37 | performance-unnecessary-value-param, 38 | portability-simd-intrinsics, 39 | bugprone-*, 40 | clang-analyzer-*, 41 | modernize-*, 42 | readability-*, 43 | -modernize-use-trailing-return-type' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project exclude paths 2 | /cmake-build-debug/ 3 | /cmake-build-release/ 4 | /build/ 5 | .idea/ 6 | /bin/ 7 | *.ll -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.26) 2 | project(cpplox) 3 | 4 | set(CMAKE_CXX_STANDARD 23) 5 | set(CMAKE_CXX_COMPILER "g++-13") 6 | 7 | find_package(LLVM 19 REQUIRED CONFIG) 8 | 9 | message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") 10 | message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") 11 | 12 | include_directories(${LLVM_INCLUDE_DIRS} SYSTEM) 13 | separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) 14 | add_definitions(${LLVM_DEFINITIONS_LIST}) 15 | 16 | get_filename_component(binFile "../bin" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 17 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${binFile}) 18 | 19 | add_executable(cpplox src/main.cpp 20 | src/frontend/Token.h 21 | src/frontend/Scanner.h 22 | src/frontend/Error.h 23 | src/frontend/AST.h 24 | src/Util.h 25 | src/frontend/Resolver.h 26 | src/compiler/Expr.cpp 27 | src/compiler/ModuleCompiler.cpp 28 | src/compiler/Value.h 29 | src/compiler/Value.cpp 30 | src/compiler/String.cpp 31 | src/compiler/Memory.cpp 32 | src/compiler/Stmt.cpp 33 | src/compiler/ModuleCompiler.h 34 | src/compiler/LoxBuilder.h 35 | src/compiler/FunctionCompiler.cpp 36 | src/compiler/FunctionCompiler.h 37 | src/compiler/Function.cpp 38 | src/compiler/LoxModule.h 39 | src/compiler/Upvalue.cpp 40 | src/compiler/Upvalue.h 41 | src/compiler/Class.cpp 42 | src/compiler/Table.cpp 43 | src/compiler/Callstack.h 44 | src/frontend/Parser.h 45 | src/compiler/Callstack.cpp 46 | src/Debug.h 47 | src/compiler/GC.cpp 48 | src/compiler/GC.h 49 | src/compiler/Table.h 50 | src/compiler/Memory.h 51 | src/compiler/Stack.h 52 | src/compiler/Stack.cpp 53 | src/compiler/LoxModule.cpp 54 | src/compiler/MDUtil.h 55 | src/interpreter/LoxObject.cpp 56 | src/interpreter/LoxObject.h 57 | src/interpreter/LoxCallable.h 58 | src/interpreter/Interpreter.h 59 | src/interpreter/Environment.cpp 60 | src/interpreter/Environment.h 61 | src/interpreter/Interpreter.cpp 62 | src/interpreter/LoxInstance.cpp 63 | src/interpreter/LoxInstance.h 64 | src/interpreter/NativeFunction.h 65 | src/interpreter/LoxClass.h 66 | src/interpreter/LoxFunction.cpp 67 | src/interpreter/LoxFunction.h 68 | src/interpreter/LoxClass.cpp 69 | ) 70 | 71 | target_compile_options(cpplox PRIVATE 72 | $<$:/W4 /WX> 73 | $<$>:-Wall -Wextra -Wpedantic> 74 | -O2 75 | # -std=c++20 -stdlib=libc++ 76 | ) 77 | 78 | llvm_map_components_to_libnames(llvm_libs 79 | ${LLVM_TARGETS_TO_BUILD} 80 | orcjit 81 | support 82 | core 83 | irreader 84 | codegen 85 | mc 86 | mcparser 87 | option) 88 | target_link_libraries(cpplox ${llvm_libs}) 89 | 90 | #set(DART_PATH "/opt/dart-sdk-v2/bin/dart") 91 | #set(CRAFTING_INTERPRETERS_PATH "~/Projects/craftinginterpreters") 92 | 93 | set(CRAFTING_INTERPRETERS_PATH $ENV{CRAFTING_INTERPRETERS_PATH}) 94 | 95 | file(WRITE ${CMAKE_BINARY_DIR}/cpplox-compiler.sh "\ 96 | #!/bin/bash\n \ 97 | TMPFILE=$(mktemp --suffix .ll)\n \ 98 | script_dir=$(dirname \"$0\")\n \ 99 | ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/cpplox $1 -o $TMPFILE && clang $TMPFILE -o out && ./out \n" 100 | ) 101 | file(CHMOD ${CMAKE_BINARY_DIR}/cpplox-compiler.sh PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_WRITE GROUP_EXECUTE WORLD_READ WORLD_WRITE WORLD_EXECUTE) 102 | 103 | enable_testing() 104 | add_test(NAME interpreter COMMAND dart tool/bin/test.dart jlox -i ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/cpplox WORKING_DIRECTORY ${CRAFTING_INTERPRETERS_PATH}) 105 | add_test(NAME compiler COMMAND dart tool/bin/test.dart jlox -i ${CMAKE_BINARY_DIR}/cpplox-compiler.sh WORKING_DIRECTORY ${CRAFTING_INTERPRETERS_PATH}) 106 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | RUN apt-get update -y 4 | RUN apt-get upgrade -y 5 | RUN apt-get install -y wget software-properties-common gnupg ca-certificates gpg \ 6 | cmake ninja-build build-essential lsb-release zlib1g-dev libzstd-dev 7 | RUN add-apt-repository ppa:ubuntu-toolchain-r/test 8 | RUN apt install gcc-13 g++-13 -y 9 | RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 13 --slave /usr/bin/g++ g++ /usr/bin/g++-13 10 | RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null 11 | RUN apt-add-repository "deb https://apt.kitware.com/ubuntu/ $(lsb_release -cs) main" 12 | RUN apt-get update -y 13 | RUN apt-get install kitware-archive-keyring cmake -y 14 | RUN apt-get install git -y 15 | 16 | RUN wget https://apt.llvm.org/llvm.sh 17 | RUN chmod +x llvm.sh 18 | RUN ./llvm.sh 19 19 | RUN ln -s /bin/lli-19 /bin/lli 20 | RUN ln -s /bin/clang-19 /bin/clang 21 | 22 | # Get craftinginterpreters repo for the test suite 23 | ENV CRAFTING_INTERPRETERS_PATH=/craftinginterpreters 24 | RUN git clone --depth 1 https://github.com/munificent/craftinginterpreters.git $CRAFTING_INTERPRETERS_PATH 25 | RUN wget https://storage.googleapis.com/dart-archive/channels/stable/release/2.19.6/linux_packages/dart_2.19.6-1_amd64.deb 26 | RUN dpkg -i dart_2.19.6-1_amd64.deb 27 | RUN cd $CRAFTING_INTERPRETERS_PATH && make get 28 | RUN cd / 29 | 30 | WORKDIR /app 31 | 32 | CMD rm -rf build && cmake -S . -B build -G Ninja .. && ninja -C build && ninja -C build test 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 James Hamilton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cpplox 2 | 3 | `cpplox` is a Lox interpreter and LLVM compiler written in C++. By default, the `cpplox` 4 | will execute the provide script with the interpreter and if provided an output file, LLVM IR or 5 | an object file will be generated. 6 | 7 | ## LLVM Compiler 8 | 9 | The [compiler](https://github.com/mrjameshamilton/cpplox/tree/master/src/compiler) uses LLVM to compile Lox scripts to LLVM IR, 10 | which can generate textual LLVM IR or object files. 11 | 12 | To compile a Lox script, provide a filename with the `-o` command line option. 13 | 14 | LLVM IR files can then be executed with the `lli` interpreter: 15 | 16 | ```shell 17 | $ bin/cpplox examples/helloworld.lox -o helloworld.ll 18 | $ lli helloworld.ll 19 | ``` 20 | 21 | The compiler can also produce an object file which can be linked 22 | into an executable: 23 | 24 | ```shell 25 | $ bin/cpplox examples/helloworld.lox -o helloworld.o 26 | $ clang helloworld.o -o helloworld 27 | $ ./helloworld 28 | ``` 29 | 30 | ### Implementation details 31 | 32 | * NaN boxing with values (numbers, boolean, nil and object pointers) stored as `i64` 33 | * interned strings using a hash table 34 | * [upvalues](https://craftinginterpreters.com/closures.html#upvalues) for capturing closed over variables 35 | - upvalues are closed when the local goes out of scope 36 | * all functions and methods are wrapped in closures for consistency 37 | - functions have a runtime representation with their implementations as LLVM IR functions 38 | - all closures have a receiver parameter and upvalue parameter 39 | * mark & sweep garbage collector 40 | - a shadow stack is used to track locals as GC roots 41 | - temporary locals are inserted when necessary to ensure they are reachable before assignment 42 | 43 | ## Interpreter 44 | 45 | The interpreter implementation is similar to the `jlox` Java implementation from the [Crafting Interpreters](https://craftinginterpreters.com/) book 46 | with the main implementation difference being the language and the use of `std::variant` instead of the visitor pattern. 47 | 48 | ```shell 49 | $ bin/cpplox examples/helloworld.lox 50 | ``` 51 | 52 | The following additional native functions are implemented in the interpreter to allow running [Lox.lox](https://github.com/mrjameshamilton/loxlox), an Lox interpreter written in Lox: 53 | 54 | - `read()` reads a byte from `stdin` or `nil` if end of stream 55 | - `utf(byte, byte, byte, byte)` converts 1, 2, 3, or 4 bytes into a UTF string 56 | - `printerr(string)` prints a string to `stderr` 57 | - `exit(number)` exits with the specific exit code 58 | 59 | # Build 60 | 61 | The build uses cmake and ninja and produces a binary `cpplox` in the `bin` folder: 62 | 63 | ```shell 64 | $ mkdir build 65 | $ cmake -S . -G Ninja -B build 66 | $ ninja -C build 67 | $ bin/cpplox ../examples/helloworld.lox 68 | ``` 69 | 70 | # Performance 71 | 72 | As a quick performance test, running the below fibonacci example, 73 | gives the following run times (on my laptop, approximate average over several runs): 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
LLVM compilerclox
0.15 seconds0.55 seconds
85 | 86 | ```javascript 87 | fun fib(n) { 88 | if (n < 2) return n; 89 | return fib(n - 2) + fib(n - 1); 90 | } 91 | 92 | var start = clock(); 93 | print fib(40); 94 | var end = clock(); 95 | print end - start; 96 | ``` 97 | 98 | # Lox.lox 99 | 100 | Both the interpreter and compiler can execute [Lox.lox](https://github.com/mrjameshamilton/loxlox), a working-but-slow 101 | Lox interpreter written in Lox itself: 102 | 103 | ```shell 104 | $ bin/cpplox Lox.lox -o loxlox.ll 105 | $ cat examples/fib.lox | lli loxlox.ll 106 | 832040 107 | 27.0111 108 | ``` 109 | 110 | # Running tests 111 | 112 | The interpreter passes the [jlox test suite](https://github.com/munificent/craftinginterpreters/tree/master/test) which 113 | can be checked out and executed via `ninja test`: 114 | 115 | ```shell 116 | $ mkdir build 117 | $ CRAFTING_INTERPRETERS_PATH=/path/to/craftinginterpreters cmake -S . -B build -G Ninja 118 | $ ninja -C build 119 | $ ninja -C build test 120 | ``` 121 | 122 | # Docker 123 | 124 | A Dockerfile is provided that contains the required dependencies and can be 125 | used to build `cpplox` and clone & run the Crafting Interpreters test suite: 126 | 127 | ```shell 128 | $ docker build -t cpploxbuilder . 129 | $ docker run --mount type=bind,source="$(pwd)",target=/app --rm cpploxbuilder 130 | ``` 131 | -------------------------------------------------------------------------------- /examples/class.lox: -------------------------------------------------------------------------------- 1 | class Bar { 2 | bar() { 3 | return "bar"; 4 | } 5 | } 6 | 7 | class Foo < Bar { 8 | init(a) { 9 | this.a = a; 10 | } 11 | 12 | foo() { 13 | return super.bar() + " " + this.a; 14 | } 15 | } 16 | 17 | print Foo("foo").foo(); 18 | -------------------------------------------------------------------------------- /examples/fib.lox: -------------------------------------------------------------------------------- 1 | fun fib(n) { 2 | if (n < 2) return n; 3 | return fib(n - 2) + fib(n - 1); 4 | } 5 | 6 | var start = clock(); 7 | print fib(30); 8 | var end = clock(); 9 | print end - start; -------------------------------------------------------------------------------- /examples/helloworld.lox: -------------------------------------------------------------------------------- 1 | print "Hello world"; 2 | -------------------------------------------------------------------------------- /src/Debug.h: -------------------------------------------------------------------------------- 1 | #ifndef DEBUG_H 2 | #define DEBUG_H 3 | 4 | constexpr bool DEBUG = false; 5 | constexpr bool DEBUG_LOG_GC = false; 6 | constexpr bool ENABLE_RUNTIME_ASSERTS = false; 7 | constexpr bool DEBUG_STACK = false; 8 | 9 | #endif//DEBUG_H 10 | -------------------------------------------------------------------------------- /src/Util.h: -------------------------------------------------------------------------------- 1 | #ifndef LOX_LLVM_UTIL_H 2 | #define LOX_LLVM_UTIL_H 3 | template 4 | struct overloaded : Ts... { 5 | using Ts::operator()...; 6 | }; 7 | template 8 | overloaded(Ts...) -> overloaded; 9 | 10 | class Uncopyable { 11 | public: 12 | Uncopyable(const Uncopyable &) = delete; 13 | Uncopyable &operator=(const Uncopyable &) = delete; 14 | 15 | protected: 16 | Uncopyable() = default; 17 | ~Uncopyable() = default; 18 | }; 19 | 20 | // TODO: std::ranges::to_vector not found. 21 | template 22 | ContainerT to(RangeT &&range) { 23 | return ContainerT(begin(range), end(range)); 24 | } 25 | #endif//LOX_LLVM_UTIL_H 26 | -------------------------------------------------------------------------------- /src/compiler/Callstack.cpp: -------------------------------------------------------------------------------- 1 | #include "Callstack.h" 2 | 3 | #include "MDUtil.h" 4 | #include "Memory.h" 5 | 6 | namespace lox { 7 | void PushCall(LoxBuilder &Builder, Value *line, Value *name) { 8 | static auto *PushFunction([&Builder] { 9 | auto *const F = Function::Create( 10 | FunctionType::get(Builder.getVoidTy(), {Builder.getInt32Ty(), Builder.getPtrTy()}, false), 11 | Function::InternalLinkage, "$push", Builder.getModule() 12 | ); 13 | 14 | F->addFnAttr(Attribute::AlwaysInline); 15 | 16 | LoxBuilder B(Builder.getContext(), Builder.getModule(), *F); 17 | 18 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 19 | B.SetInsertPoint(EntryBasicBlock); 20 | 21 | auto *const arguments = F->args().begin(); 22 | auto *const line = arguments; 23 | auto *const name = arguments + 1; 24 | 25 | auto *const $sp = B.getModule().getCallStackPointer(); 26 | auto *const $cs = B.getModule().getCallStack(); 27 | 28 | auto *const sp = B.CreateLoad(B.getInt32Ty(), $sp); 29 | 30 | auto *const addr = B.CreateInBoundsGEP($cs->getValueType(), $cs, {B.getInt32(0), sp}); 31 | B.CreateStore(line, B.CreateStructGEP(B.getModule().getCallStruct(), addr, 0)); 32 | B.CreateStore(name, B.CreateStructGEP(B.getModule().getCallStruct(), addr, 1)); 33 | 34 | B.CreateStore(B.CreateAdd(sp, B.getInt32(1), "call+1", true, true), $sp); 35 | 36 | B.CreateRetVoid(); 37 | 38 | return F; 39 | }()); 40 | 41 | Builder.CreateCall(PushFunction, {line, name}); 42 | } 43 | 44 | void PopCall(LoxBuilder &Builder) { 45 | static auto *PopFunction([&Builder] { 46 | auto *const F = Function::Create( 47 | FunctionType::get(Builder.getVoidTy(), {}, false), Function::InternalLinkage, "$pop", 48 | Builder.getModule() 49 | ); 50 | 51 | F->addFnAttr(Attribute::AlwaysInline); 52 | 53 | LoxBuilder B(Builder.getContext(), Builder.getModule(), *F); 54 | 55 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 56 | B.SetInsertPoint(EntryBasicBlock); 57 | 58 | auto *const $sp = B.getModule().getCallStackPointer(); 59 | auto *const sp = B.CreateLoad(B.getInt32Ty(), $sp); 60 | B.CreateStore(B.CreateSub(sp, B.getInt32(1), "sp", true, true), $sp); 61 | 62 | B.CreateRetVoid(); 63 | 64 | return F; 65 | }()); 66 | 67 | Builder.CreateCall(PopFunction, {}); 68 | } 69 | 70 | void PrintStackTrace(LoxBuilder &Builder) { 71 | static auto *PrintStackTraceFunction([&Builder] { 72 | auto *const F = Function::Create( 73 | FunctionType::get(Builder.getVoidTy(), {}, false), Function::InternalLinkage, "$printStackTrace", 74 | Builder.getModule() 75 | ); 76 | 77 | LoxBuilder B(Builder.getContext(), Builder.getModule(), *F); 78 | 79 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 80 | B.SetInsertPoint(EntryBasicBlock); 81 | 82 | auto *const $sp = B.getModule().getCallStackPointer(); 83 | auto *const $cs = B.getModule().getCallStack(); 84 | 85 | auto *const sp = B.CreateLoad(B.getInt32Ty(), $sp); 86 | 87 | auto *const i = CreateEntryBlockAlloca(F, B.getInt32Ty(), "i"); 88 | 89 | B.CreateStore(B.getInt32(1), i); 90 | 91 | auto *const ForCond = B.CreateBasicBlock("for.cond"); 92 | auto *const ForBody = B.CreateBasicBlock("for.body"); 93 | auto *const ForInc = B.CreateBasicBlock("for.inc"); 94 | auto *const ForEnd = B.CreateBasicBlock("for.end"); 95 | 96 | B.CreateBr(ForCond); 97 | B.SetInsertPoint(ForCond); 98 | B.CreateCondBr(B.CreateICmpSLE(B.CreateLoad(B.getInt32Ty(), i), sp), ForBody, ForEnd); 99 | B.SetInsertPoint(ForBody); 100 | 101 | auto *const top = 102 | B.CreateSub(B.CreateLoad(B.getInt32Ty(), $sp), B.CreateLoad(B.getInt32Ty(), i), "top", true, true); 103 | auto *const addr = B.CreateInBoundsGEP($cs->getValueType(), $cs, {B.getInt32(0), top}); 104 | 105 | auto *const line = B.CreateLoad(B.getInt32Ty(), B.CreateStructGEP(B.getModule().getCallStruct(), addr, 0)); 106 | auto *const name = B.CreateLoad(B.getPtrTy(), B.CreateStructGEP(B.getModule().getCallStruct(), addr, 1)); 107 | 108 | auto *const IsScriptBlock = B.CreateBasicBlock("is.script"); 109 | auto *const IsNotScriptBlock = B.CreateBasicBlock("isnot.script"); 110 | 111 | B.CreateCondBr(B.CreateICmpEQ(sp, B.CreateLoad(B.getInt32Ty(), i)), IsScriptBlock, IsNotScriptBlock); 112 | 113 | B.SetInsertPoint(IsScriptBlock); 114 | B.PrintFErr(B.CreateGlobalCachedString("[line %d] in script\n"), {line}); 115 | B.CreateBr(ForInc); 116 | B.SetInsertPoint(IsNotScriptBlock); 117 | B.PrintFErr(B.CreateGlobalCachedString("[line %d] in %s()\n"), {line, name}); 118 | B.CreateBr(ForInc); 119 | 120 | B.SetInsertPoint(ForInc); 121 | B.CreateStore(B.CreateAdd(B.CreateLoad(B.getInt32Ty(), i), B.getInt32(1), "i+1", true, true), i); 122 | B.CreateBr(ForCond); 123 | 124 | B.SetInsertPoint(ForEnd); 125 | 126 | B.CreateRetVoid(); 127 | 128 | return F; 129 | }()); 130 | 131 | Builder.CreateCall(PrintStackTraceFunction, {}); 132 | } 133 | 134 | void CheckStackOverflow(LoxBuilder &Builder, Value *line, Value *name) { 135 | static auto *CheckStackOverflowFunction([&Builder] { 136 | auto *const F = Function::Create( 137 | FunctionType::get(Builder.getVoidTy(), {Builder.getInt32Ty(), Builder.getPtrTy()}, false), 138 | Function::InternalLinkage, "$checkStackOverflow", Builder.getModule() 139 | ); 140 | 141 | LoxBuilder B(Builder.getContext(), Builder.getModule(), *F); 142 | 143 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 144 | B.SetInsertPoint(EntryBasicBlock); 145 | auto *const arguments = F->args().begin(); 146 | auto *const line = arguments; 147 | auto *const name = arguments + 1; 148 | 149 | auto *const $sp = B.getModule().getCallStackPointer(); 150 | 151 | auto *const sp = B.CreateLoad(B.getInt32Ty(), $sp); 152 | 153 | auto mdBuilder = MDBuilder(Builder.getContext()); 154 | auto *const IsStackOverFlow = B.CreateBasicBlock("is.stackoverflow"); 155 | auto *const IsNotStackOverFlow = B.CreateBasicBlock("isnot.stackoverflow"); 156 | 157 | B.CreateCondBr( 158 | B.CreateICmpSLT(sp, B.getInt32(MAX_CALL_STACK_SIZE - 1)), IsNotStackOverFlow, IsStackOverFlow, 159 | metadata::createLikelyBranchWeights(mdBuilder) 160 | ); 161 | 162 | B.SetInsertPoint(IsStackOverFlow); 163 | B.RuntimeError(line, "Stack overflow.\n", {}, name); 164 | 165 | B.SetInsertPoint(IsNotStackOverFlow); 166 | B.CreateRetVoid(); 167 | 168 | return F; 169 | }()); 170 | 171 | Builder.CreateCall(CheckStackOverflowFunction, {line, name}); 172 | } 173 | }// namespace lox -------------------------------------------------------------------------------- /src/compiler/Callstack.h: -------------------------------------------------------------------------------- 1 | #ifndef CPPLOX_CALLSTACK_H 2 | #define CPPLOX_CALLSTACK_H 3 | 4 | #include "LoxBuilder.h" 5 | 6 | namespace lox { 7 | void PushCall(LoxBuilder &Builder, Value *line, Value *name); 8 | void PopCall(LoxBuilder &Builder); 9 | void PrintStackTrace(LoxBuilder &Builder); 10 | void CheckStackOverflow(LoxBuilder &Builder, Value *line, Value *name); 11 | }// namespace lox 12 | 13 | #endif//CPPLOX_CALLSTACK_H 14 | -------------------------------------------------------------------------------- /src/compiler/Class.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "LoxBuilder.h" 3 | #include "Stack.h" 4 | 5 | #include 6 | 7 | using namespace std::string_view_literals; 8 | 9 | namespace lox { 10 | 11 | Value *LoxBuilder::AllocateClass(Value *name) { 12 | auto *const ptr = AllocateObj(ObjType::CLASS, "class"); 13 | 14 | auto *const methods = AllocateTable(); 15 | 16 | CreateStore(name, CreateObjStructGEP(ObjType::CLASS, ptr, 1)); 17 | CreateStore(methods, CreateObjStructGEP(ObjType::CLASS, ptr, 2)); 18 | 19 | CreateInvariantStart(ptr, getSizeOf(ObjType::CLASS)); 20 | 21 | return ptr; 22 | } 23 | 24 | Value *LoxBuilder::AllocateInstance(Value *klass) { 25 | auto *const ptr = AllocateObj(ObjType::INSTANCE, "instance"); 26 | 27 | Value *fields = AllocateTable(); 28 | 29 | CreateStore(klass, CreateObjStructGEP(ObjType::INSTANCE, ptr, 1)); 30 | CreateStore(fields, CreateObjStructGEP(ObjType::INSTANCE, ptr, 2)); 31 | 32 | CreateInvariantStart(ptr, getSizeOf(ObjType::INSTANCE)); 33 | 34 | return ptr; 35 | } 36 | 37 | Value *LoxBuilder::BindMethod(Value *klass, Value *receiver, Value *key, const unsigned int line, const llvm::Function *pFunction) { 38 | assert(klass->getType() == getPtrTy()); 39 | assert(receiver->getType() == getPtrTy()); 40 | 41 | static auto *BindMethodFunction([this] { 42 | auto *const F = Function::Create( 43 | FunctionType::get( 44 | getPtrTy(), 45 | {getPtrTy(), getPtrTy(), getPtrTy(), getInt32Ty(), getPtrTy()}, 46 | false 47 | ), 48 | Function::InternalLinkage, 49 | "$bindMethod", 50 | getModule() 51 | ); 52 | 53 | LoxBuilder B(getContext(), getModule(), *F); 54 | 55 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 56 | B.SetInsertPoint(EntryBasicBlock); 57 | 58 | auto *const arguments = F->args().begin(); 59 | auto *const klass = arguments; 60 | auto *const receiver = arguments + 1; 61 | auto *const key = arguments + 2; 62 | auto *const line = arguments + 3; 63 | auto *const function = arguments + 4; 64 | 65 | auto *const methods = B.CreateLoad(B.getPtrTy(), B.CreateObjStructGEP(ObjType::CLASS, klass, 2)); 66 | auto *const method = B.TableGet(methods, key); 67 | 68 | auto *const IsUndefinedBlock = B.CreateBasicBlock("property.undefined"); 69 | auto *const IsDefinedBlock = B.CreateBasicBlock("property.defined"); 70 | 71 | B.CreateCondBr(B.IsUninitialized(method), IsUndefinedBlock, IsDefinedBlock); 72 | 73 | B.SetInsertPoint(IsUndefinedBlock); 74 | { 75 | B.RuntimeError( 76 | line, 77 | "Undefined property '%s'.\n"sv, 78 | {B.AsCString(B.ObjVal(key))}, 79 | function 80 | ); 81 | } 82 | B.SetInsertPoint(IsDefinedBlock); 83 | { 84 | auto *const ptr = B.AllocateObj(ObjType::BOUND_METHOD, "bound_method"); 85 | B.CreateStore(B.ObjVal(receiver), B.CreateObjStructGEP(ObjType::BOUND_METHOD, ptr, 1)); 86 | B.CreateStore(B.AsObj(method), B.CreateObjStructGEP(ObjType::BOUND_METHOD, ptr, 2)); 87 | 88 | B.CreateRet(ptr); 89 | } 90 | 91 | return F; 92 | }()); 93 | 94 | auto *const ptr = CreateCall( 95 | BindMethodFunction, 96 | {klass, receiver, key, getInt32(line), CreateGlobalCachedString(pFunction == nullptr ? "script" : pFunction->getName())} 97 | ); 98 | 99 | CreateInvariantStart(ptr, getSizeOf(ObjType::BOUND_METHOD)); 100 | 101 | return ptr; 102 | } 103 | }// namespace lox -------------------------------------------------------------------------------- /src/compiler/Function.cpp: -------------------------------------------------------------------------------- 1 | #include "FunctionCompiler.h" 2 | #include "LoxBuilder.h" 3 | #include "ModuleCompiler.h" 4 | #include "Stack.h" 5 | 6 | namespace lox { 7 | 8 | static Value *AllocateFunction(LoxBuilder &Builder, llvm::Function *Function, Value *name, const bool isNative) { 9 | static auto *AllocateFunctionFunction([&Builder] { 10 | auto *const F = Function::Create( 11 | FunctionType::get( 12 | Builder.getPtrTy(), 13 | {Builder.getPtrTy(), Builder.getPtrTy(), Builder.getInt32Ty(), Builder.getInt1Ty()}, false 14 | ), 15 | Function::InternalLinkage, "$allocateFunction", Builder.getModule() 16 | ); 17 | 18 | LoxBuilder B(Builder.getContext(), Builder.getModule(), *F); 19 | 20 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 21 | B.SetInsertPoint(EntryBasicBlock); 22 | 23 | auto *const arguments = F->args().begin(); 24 | 25 | auto *const functionPtr = arguments; 26 | auto *const name = arguments + 1; 27 | auto *const argSize = arguments + 2; 28 | auto *const isNative = arguments + 3; 29 | 30 | auto *const ptr = B.AllocateObj(ObjType::FUNCTION, "function"); 31 | 32 | B.CreateStore(argSize, B.CreateObjStructGEP(ObjType::FUNCTION, ptr, 1, "argSize")); 33 | B.CreateStore(functionPtr, B.CreateObjStructGEP(ObjType::FUNCTION, ptr, 2, "funcPtr")); 34 | B.CreateStore(name, B.CreateObjStructGEP(ObjType::FUNCTION, ptr, 3, "name")); 35 | B.CreateStore(isNative, B.CreateObjStructGEP(ObjType::FUNCTION, ptr, 4, "isNative")); 36 | 37 | B.CreateRet(ptr); 38 | 39 | return F; 40 | }()); 41 | 42 | auto *const ptr = Builder.CreateCall( 43 | AllocateFunctionFunction, {Function, name, 44 | Builder.getInt32(Function->arg_size() - 2),// receiver + upvalues 45 | isNative ? Builder.getTrue() : Builder.getFalse()} 46 | ); 47 | 48 | Builder.CreateInvariantStart(ptr, Builder.getSizeOf(ObjType::FUNCTION)); 49 | 50 | return ptr; 51 | } 52 | 53 | Value *LoxBuilder::AllocateClosure(llvm::Function *function, const std::string_view name, const bool isNative) { 54 | static auto *AllocationClosureFunction([this] { 55 | auto *const F = Function::Create( 56 | FunctionType::get(getPtrTy(), {getPtrTy()}, false), Function::InternalLinkage, "$allocateClosure", 57 | getModule() 58 | ); 59 | 60 | LoxBuilder B(getContext(), getModule(), *F); 61 | 62 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 63 | B.SetInsertPoint(EntryBasicBlock); 64 | 65 | auto *const function = F->args().begin(); 66 | 67 | auto *const ptr = B.AllocateObj(ObjType::CLOSURE, "closure"); 68 | 69 | B.CreateStore(function, B.CreateObjStructGEP(ObjType::CLOSURE, ptr, 1)); 70 | // An array will be allocated for the upvalues in lox::FunctionCompiler::CreateFunction. 71 | B.CreateStore(B.getNullPtr(), B.CreateObjStructGEP(ObjType::CLOSURE, ptr, 2)); 72 | B.CreateStore(B.getInt32(0), B.CreateObjStructGEP(ObjType::CLOSURE, ptr, 3)); 73 | 74 | B.CreateRet(ptr); 75 | 76 | return F; 77 | }()); 78 | 79 | auto *const ptr = DelayGC(*this, [&](LoxBuilder &B) { 80 | auto *const nameObj = AllocateString(name); 81 | auto *const functionObj = AllocateFunction(*this, function, nameObj, isNative); 82 | return B.CreateCall(AllocationClosureFunction, {functionObj}); 83 | }); 84 | 85 | if (isNative) { 86 | // Native closures won't change. 87 | CreateInvariantStart(ptr, getSizeOf(ObjType::CLOSURE)); 88 | } 89 | 90 | return ptr; 91 | } 92 | }// namespace lox 93 | -------------------------------------------------------------------------------- /src/compiler/FunctionCompiler.cpp: -------------------------------------------------------------------------------- 1 | #include "FunctionCompiler.h" 2 | #include "../Debug.h" 3 | #include "ModuleCompiler.h" 4 | 5 | namespace lox { 6 | 7 | void FunctionCompiler::compile( 8 | const std::vector &statements, const std::vector ¶meters, 9 | const std::function &entryBlockBuilder 10 | ) { 11 | 12 | Builder.SetInsertPoint(EntryBasicBlock); 13 | // Alloca's will normally be generated here with CreateEntryBlockAlloca 14 | if constexpr (DEBUG_LOG_GC) { 15 | Builder.PrintF({ 16 | Builder.CreateGlobalCachedString("# start function %s\n"), 17 | Builder.CreateGlobalCachedString(Builder.getFunction()->getName()), 18 | }); 19 | } 20 | 21 | auto *const PrologueBlock = Builder.CreateBasicBlock("prologue"); 22 | Builder.CreateBr(PrologueBlock); 23 | Builder.SetInsertPoint(PrologueBlock); 24 | 25 | beginScope(); 26 | { 27 | // The default return value is nil except for the script and initializers. 28 | if (type != LoxFunctionType::NONE && type != LoxFunctionType::INITIALIZER) { 29 | insertVariable("$returnVal", Builder.getNilVal()); 30 | } 31 | 32 | if (entryBlockBuilder) entryBlockBuilder(Builder); 33 | 34 | beginScope(); 35 | { 36 | if constexpr (DEBUG_LOG_GC) { 37 | Builder.PrintF( 38 | {Builder.CreateGlobalCachedString("## start function inner scope %s (sp %d)\n"), 39 | Builder.CreateGlobalCachedString(Builder.getFunction()->getName()), 40 | Builder.CreateLoad(Builder.getInt32Ty(), sp)} 41 | ); 42 | } 43 | // Declare parameters and store them in local variables. 44 | auto *arg = 45 | Builder.getFunction()->arg_begin() + 2 /* second arg is receiver, first is upvalues array */; 46 | for (const auto &p: parameters) { insertVariable(p.getLexeme(), arg++); } 47 | 48 | for (const auto &stmt: statements) { evaluate(stmt); } 49 | 50 | if (Builder.GetInsertBlock()->getTerminator() == nullptr) { 51 | // In the case where there was no return statement in the Lox code, 52 | // then the current block at this point will be unterminated. 53 | Builder.CreateBr(ExitBasicBlock); 54 | } 55 | 56 | Builder.SetInsertPoint(ExitBasicBlock); 57 | 58 | // Code can be generated here to close open upvalues, 59 | // since parameters go out of scope at the end of a function. 60 | } 61 | endScope(); 62 | 63 | if constexpr (DEBUG_LOG_GC) { 64 | Builder.PrintF( 65 | {Builder.CreateGlobalCachedString("## end function inner scope %s (sp %d)\n"), 66 | Builder.CreateGlobalCachedString(Builder.getFunction()->getName()), 67 | Builder.CreateLoad(Builder.getInt32Ty(), sp)} 68 | ); 69 | } 70 | 71 | auto *const ReturnBlock = Builder.CreateBasicBlock("exit"); 72 | Builder.CreateBr(ReturnBlock); 73 | Builder.SetInsertPoint(ReturnBlock); 74 | } 75 | 76 | AllocaInst *returnVal = nullptr; 77 | if (type != LoxFunctionType::NONE && type != LoxFunctionType::INITIALIZER) { 78 | returnVal = CreateEntryBlockAlloca(Builder.getFunction(), Builder.getInt64Ty(), "returnValTemp"); 79 | // Store a copy of the return value, before the $returnVal 80 | // local goes out of scope. 81 | if (const auto value = variables.lookup("$returnVal")) { 82 | Builder.CreateStore(Builder.CreateLoad(Builder.getInt64Ty(), value->value), returnVal); 83 | Builder.CreateInvariantStart(returnVal, Builder.getInt64(64)); 84 | } 85 | } 86 | 87 | endScope(); 88 | 89 | // At the beginning of the function, remember the current local variable stack pointer. 90 | IRBuilder EntryBlockBuilder(PrologueBlock, PrologueBlock->begin()); 91 | const auto locals = Builder.getModule().getLocalsStack(); 92 | EntryBlockBuilder.CreateStore(locals.CreateGetCount(EntryBlockBuilder), sp); 93 | // Create new slots for the required number of locals. 94 | locals.CreatePushN(Builder.getModule(), EntryBlockBuilder, EntryBlockBuilder.getInt32(localsCount)); 95 | 96 | // At the end of the function, reset the stack pointer then any variables allocated 97 | // in the function are no longer accessible as GC roots and can be freed. 98 | locals.CreatePopN(Builder, Builder.getInt32(localsCount)); 99 | 100 | if constexpr (DEBUG_LOG_GC) { 101 | Builder.PrintF( 102 | {Builder.CreateGlobalCachedString("# end function scope %s (sp %d)\n"), 103 | Builder.CreateGlobalCachedString(Builder.getFunction()->getName()), 104 | Builder.CreateLoad(Builder.getInt32Ty(), sp)} 105 | ); 106 | } 107 | 108 | assert(scopes.empty()); 109 | 110 | if (type == LoxFunctionType::NONE) { 111 | Builder.CreateRetVoid(); 112 | } else if (type == LoxFunctionType::INITIALIZER) { 113 | Builder.CreateRet(Builder.getFunction()->arg_begin() + 1); 114 | } else { 115 | Builder.CreateRet(Builder.CreateLoad(Builder.getInt64Ty(), returnVal)); 116 | } 117 | } 118 | 119 | }// namespace lox 120 | -------------------------------------------------------------------------------- /src/compiler/GC.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_H 2 | #define GC_H 3 | 4 | #include "LoxBuilder.h" 5 | 6 | constexpr bool STRESS_GC = false; 7 | constexpr int GC_GROWTH_FACTOR = 2; 8 | 9 | namespace lox { 10 | Function *CreateGcFunction(LoxBuilder &Builder); 11 | void MarkObject(LoxBuilder &Builder, Value *ObjectPtr); 12 | void AddGlobalGCRoot(LoxModule &Module, GlobalVariable *global); 13 | 14 | /** 15 | * The garbage collector will be disabled for the duration of the block 16 | * and executed after. 17 | * The block function must return a point to a Lox object which 18 | * will be used as an extra GC root, or nullptr. 19 | * This is useful when multiple allocations need to happen consecutively, 20 | * to avoid triggering the GC before they are all complete. 21 | * 22 | * @return the value produced by the block function. 23 | */ 24 | Value *DelayGC(LoxBuilder &B, const std::function &block); 25 | }// namespace lox 26 | 27 | #endif//GC_H 28 | -------------------------------------------------------------------------------- /src/compiler/LoxBuilder.h: -------------------------------------------------------------------------------- 1 | #ifndef LOXBUILDER_H 2 | #define LOXBUILDER_H 3 | #include "LoxModule.h" 4 | #include "Value.h" 5 | #include 6 | 7 | using namespace llvm; 8 | 9 | using LLVMIRBuilder = IRBuilder<>; 10 | //using LLVMIRBuilder = IRBuilder; 11 | 12 | namespace lox { 13 | class FunctionCompiler; 14 | class LoxBuilder : public LLVMIRBuilder { 15 | LoxModule &M; 16 | llvm::Function &Function; 17 | 18 | public: 19 | explicit LoxBuilder(LLVMContext &Context, LoxModule &Module, llvm::Function &Function) 20 | : IRBuilder(Context), M(Module), Function(Function) {} 21 | 22 | // Code generation for internal Lox functions. 23 | Value *IsTruthy(Value *value); 24 | 25 | // Code generation for checking types of values. 26 | Value *IsBool(Value *); 27 | Value *IsUninitialized(Value *value); 28 | Value *IsNil(Value *value); 29 | Value *IsNumber(Value *value); 30 | Value *IsObj(Value *value); 31 | Value *IsClosure(Value *value); 32 | Value *IsString(Value *value); 33 | Value *IsClass(Value *value); 34 | Value *IsBoundMethod(Value *value); 35 | Value *IsInstance(Value *value); 36 | Value *IsUpvalue(Value *value); 37 | 38 | // Code generation for converting an int64 to a Lox value. 39 | Value *BoolVal(Value *value); 40 | Value *ObjVal(Value *ptrValue); 41 | Value *NumberVal(Value *value); 42 | 43 | // Code generation for converting a Lox value to a native type. 44 | Value *AsBool(Value *value); 45 | Value *AsObj(Value *value); 46 | Value *AsCString(Value *value); 47 | Value *AsNumber(Value *value); 48 | Value *getUninitializedVal(); 49 | Value *getNilVal(); 50 | Value *getTrueVal(); 51 | Value *getFalseVal(); 52 | 53 | Value *ObjType(Value *value); 54 | ConstantInt *ObjTypeInt(enum ObjType); 55 | 56 | Value *getSizeOf(Type *type, Value *arraySize = nullptr); 57 | ConstantInt *getSizeOf(enum ObjType type) const; 58 | ConstantInt *getSizeOf(Type *type, unsigned int arraySize) const; 59 | Value *AllocateObj(lox::ObjType objType, std::string_view name = ""); 60 | Value *AllocateString(Value *String, Value *Length, std::string_view name = ""); 61 | Value *AllocateString(StringRef String, std::string_view name = ""); 62 | Value *AllocateClosure(llvm::Function *function, std::string_view name, bool isNative); 63 | Value *AllocateUpvalue(Value *value); 64 | Value *AllocateClass(Value *name); 65 | Value *AllocateInstance(Value *klass); 66 | Value *AllocateTable(); 67 | Value *TableSet(Value *Table, Value *Key, Value *Value); 68 | Value *TableGet(Value *Table, Value *Key); 69 | Value *TableAddAll(Value *FromTable, Value *ToTable); 70 | 71 | Value *CreateReallocate(Value *ptr, Value *oldSize, Value *newSize); 72 | Value *CreateRealloc(Value *ptr, Value *newSize, StringRef what); 73 | void CreateFree(Value *ptr, enum ObjType type, Value *arraySize); 74 | void CollectGarbage(bool force, Value *extraRoot = nullptr); 75 | Value *Concat(Value *a, Value *b); 76 | 77 | void Print(Value *value); 78 | void PrintF(std::initializer_list value); 79 | void PrintFErr(Value *message, const std::vector &values = {}); 80 | void PrintString(StringRef string); 81 | 82 | void PrintNumber(Value *value); 83 | void PrintNil(); 84 | void PrintObject(Value *value); 85 | void PrintString(Value *value); 86 | void PrintBool(Value *value); 87 | 88 | void Exit(Value *code); 89 | 90 | Value *CreateObjStructGEP(const enum ObjType objType, Value *Ptr, const unsigned Idx, const Twine &Name = "") { 91 | return CreateStructGEP(getModule().getStructType(objType), Ptr, Idx, Name); 92 | } 93 | 94 | [[nodiscard]] Constant *getNullPtr() const { 95 | return Constant::getNullValue(PointerType::getUnqual(getContext())); 96 | } 97 | 98 | 99 | Constant *CreateGlobalCachedString(const std::string_view string) { 100 | auto &strings = getModule().getStringCache(); 101 | if (strings.contains(string)) { return strings.at(string); } 102 | 103 | auto *const ptr = CreateGlobalStringPtr(string); 104 | strings[string] = ptr; 105 | return ptr; 106 | } 107 | 108 | void RuntimeError( 109 | Value *line, StringRef message, const std::vector &values, Value *location, bool freeObjects = true 110 | ); 111 | void RuntimeError( 112 | const unsigned line, const StringRef message, const std::vector &values, 113 | const llvm::Function *function 114 | ) { 115 | RuntimeError(getInt32(line), message, values, CreateGlobalCachedString(function->getName())); 116 | } 117 | 118 | [[nodiscard]] LoxModule &getModule() const { return M; } 119 | [[nodiscard]] llvm::Function *getFunction() const { return &Function; } 120 | [[nodiscard]] BasicBlock *CreateBasicBlock(const std::string_view &name) const { 121 | return BasicBlock::Create(getContext(), name, getFunction()); 122 | } 123 | Value * 124 | BindMethod(Value *klass, Value *receiver, Value *key, unsigned int line, const llvm::Function *pFunction); 125 | 126 | CallInst *CreateInvariantEnd(CallInst *start, Value *Ptr, ConstantInt *Size) { 127 | 128 | assert(isa(Ptr->getType()) && "invariant.start only applies to pointers."); 129 | if (!Size) Size = getInt64(-1); 130 | else 131 | assert(Size->getType() == getInt64Ty() && "invariant.start requires the size to be an i64"); 132 | 133 | Value *Ops[] = {start, Size, Ptr}; 134 | // Fill in the single overloaded type: memory object type. 135 | Type *ObjectPtr[1] = {Ptr->getType()}; 136 | Module *M = BB->getParent()->getParent(); 137 | llvm::Function *TheFn = Intrinsic::getDeclaration(M, Intrinsic::invariant_end, ObjectPtr); 138 | return CreateCall(TheFn, Ops); 139 | } 140 | }; 141 | }// namespace lox 142 | 143 | #endif//LOXBUILDER_H 144 | -------------------------------------------------------------------------------- /src/compiler/LoxModule.cpp: -------------------------------------------------------------------------------- 1 | #include "LoxModule.h" 2 | 3 | #include "Stack.h" 4 | 5 | namespace lox { 6 | void LoxModule::initialize() { 7 | grayStack = std::make_shared(*this, "gray"); 8 | localsStack = std::make_shared(*this, "locals"); 9 | } 10 | }// namespace lox -------------------------------------------------------------------------------- /src/compiler/LoxModule.h: -------------------------------------------------------------------------------- 1 | #ifndef LOXMODULE_H 2 | #define LOXMODULE_H 3 | #include "Value.h" 4 | 5 | #include 6 | #include 7 | 8 | constexpr unsigned int MAX_CALL_STACK_SIZE = 512; 9 | constexpr unsigned int FIRST_GC_AT = 512; 10 | 11 | namespace lox { 12 | using namespace llvm; 13 | 14 | class GlobalStack; 15 | 16 | class LoxModule : public Module { 17 | StructType *const ObjStructType = StructType::create( 18 | getContext(), 19 | {IntegerType::getInt8Ty(getContext()),// ObjType 20 | IntegerType::getInt1Ty(getContext()),// isMarked 21 | PointerType::get(getContext(), 0)}, // next 22 | "Obj" 23 | ); 24 | StructType *const StringStructType = StructType::create( 25 | getContext(), 26 | { 27 | ObjStructType, 28 | PointerType::getUnqual(getContext()), // char* ptr 29 | IntegerType::getInt32Ty(getContext()),// length 30 | IntegerType::getInt32Ty(getContext()),// hash 31 | IntegerType::getInt1Ty(getContext()), // dynamically allocated 32 | }, 33 | "String" 34 | ); 35 | StructType *const FunctionStructType = StructType::create( 36 | getContext(), 37 | { 38 | ObjStructType, 39 | IntegerType::getInt32Ty(getContext()),// arity 40 | PointerType::getUnqual(getContext()), // func ptr 41 | StringStructType->getPointerTo(), // name 42 | IntegerType::getInt1Ty(getContext()), // isNative 43 | }, 44 | "Function" 45 | ); 46 | StructType *const ClosureStructType = StructType::create( 47 | getContext(), 48 | { 49 | ObjStructType, 50 | PointerType::getUnqual(getContext()), // func obj ptr 51 | PointerType::getUnqual(getContext()), // upvalues 52 | IntegerType::getInt32Ty(getContext()),// upvalue count 53 | }, 54 | "Closure" 55 | ); 56 | StructType *const UpvalueStruct = StructType::create( 57 | getContext(), 58 | { 59 | ObjStructType, 60 | PointerType::getUnqual(getContext()), // location ptr 61 | PointerType::getUnqual(getContext()), // next 62 | IntegerType::getInt64Ty(getContext()),// closed value 63 | }, 64 | "Upvalue" 65 | ); 66 | StructType *const ClassStruct = StructType::create( 67 | getContext(), 68 | { 69 | ObjStructType, 70 | StringStructType->getPointerTo(), // name 71 | PointerType::getUnqual(getContext()),// methods 72 | }, 73 | "Class" 74 | ); 75 | StructType *const InstanceStruct = StructType::create( 76 | getContext(), 77 | { 78 | ObjStructType, 79 | ClassStruct->getPointerTo(), // klass 80 | PointerType::getUnqual(getContext()),// fields 81 | }, 82 | "Instance" 83 | ); 84 | StructType *const BoundMethodStruct = StructType::create( 85 | getContext(), 86 | { 87 | ObjStructType, 88 | // TODO: store both as pointer or both as int64? 89 | IntegerType::getInt64Ty(getContext()),// receiver 90 | PointerType::getUnqual(getContext()), // closure 91 | }, 92 | "BoundMethod" 93 | ); 94 | StructType *const TableStruct = StructType::create( 95 | getContext(), 96 | { 97 | IntegerType::getInt32Ty(getContext()),// count 98 | IntegerType::getInt32Ty(getContext()),// capacity 99 | PointerType::getUnqual(getContext()), // entries 100 | }, 101 | "Table" 102 | ); 103 | StructType *const EntryStruct = StructType::create( 104 | getContext(), 105 | { 106 | StringStructType->getPointerTo(), // key 107 | IntegerType::getInt64Ty(getContext()),// value 108 | }, 109 | "Entry" 110 | ); 111 | GlobalVariable *const objects = 112 | cast(getOrInsertGlobal("objects", PointerType::get(getContext(), 0))); 113 | GlobalVariable *const runtimeStrings = 114 | cast(getOrInsertGlobal("strings", PointerType::get(getContext(), 0))); 115 | GlobalVariable *const openUpvalues = 116 | cast(getOrInsertGlobal("openUpvalues", PointerType::get(getContext(), 0))); 117 | StructType *const Call = StructType::create( 118 | getContext(), 119 | { 120 | IntegerType::getInt32Ty(getContext()),// line 121 | PointerType::getUnqual(getContext()), // name 122 | }, 123 | "Call" 124 | ); 125 | GlobalVariable *const callstack = 126 | cast(getOrInsertGlobal("callstack", ArrayType::get(Call, MAX_CALL_STACK_SIZE))); 127 | GlobalVariable *const callstackpointer = 128 | cast(getOrInsertGlobal("callsp", IntegerType::getInt32Ty(getContext()))); 129 | GlobalVariable *const allocatedBytes = 130 | cast(getOrInsertGlobal("$allocatedBytes", IntegerType::getInt32Ty(getContext()))); 131 | GlobalVariable *const nextGC = 132 | cast(getOrInsertGlobal("$nextGC", IntegerType::getInt32Ty(getContext()))); 133 | GlobalVariable *const enableGC = 134 | cast(getOrInsertGlobal("$enableGC", IntegerType::getInt1Ty(getContext()))); 135 | std::shared_ptr grayStack; 136 | std::shared_ptr localsStack; 137 | llvm::StringMap strings; 138 | 139 | public: 140 | explicit LoxModule(LLVMContext &Context) : Module("lox", Context) { 141 | objects->setLinkage(GlobalValue::PrivateLinkage); 142 | objects->setAlignment(Align(8)); 143 | objects->setConstant(false); 144 | objects->setInitializer(ConstantPointerNull::get(PointerType::get(Context, 0))); 145 | 146 | runtimeStrings->setLinkage(GlobalValue::PrivateLinkage); 147 | runtimeStrings->setAlignment(Align(8)); 148 | runtimeStrings->setConstant(false); 149 | runtimeStrings->setInitializer(ConstantPointerNull::get(PointerType::get(Context, 0))); 150 | 151 | openUpvalues->setLinkage(GlobalValue::PrivateLinkage); 152 | openUpvalues->setAlignment(Align(8)); 153 | openUpvalues->setConstant(false); 154 | openUpvalues->setInitializer(ConstantPointerNull::get(PointerType::get(Context, 0))); 155 | 156 | callstack->setLinkage(GlobalVariable::PrivateLinkage); 157 | callstack->setAlignment(Align(8)); 158 | callstack->setConstant(false); 159 | callstack->setInitializer(Constant::getNullValue(ArrayType::get(Call, MAX_CALL_STACK_SIZE))); 160 | 161 | callstackpointer->setLinkage(GlobalVariable::PrivateLinkage); 162 | callstackpointer->setAlignment(Align(8)); 163 | callstackpointer->setConstant(false); 164 | callstackpointer->setInitializer(ConstantInt::get(IntegerType::getInt32Ty(getContext()), 0)); 165 | 166 | allocatedBytes->setLinkage(GlobalVariable::PrivateLinkage); 167 | allocatedBytes->setAlignment(Align(8)); 168 | allocatedBytes->setConstant(false); 169 | allocatedBytes->setInitializer(ConstantInt::get(IntegerType::getInt32Ty(getContext()), 0)); 170 | 171 | nextGC->setLinkage(GlobalVariable::PrivateLinkage); 172 | nextGC->setAlignment(Align(8)); 173 | nextGC->setConstant(false); 174 | nextGC->setInitializer(ConstantInt::get(IntegerType::getInt32Ty(getContext()), FIRST_GC_AT)); 175 | 176 | enableGC->setLinkage(GlobalVariable::PrivateLinkage); 177 | enableGC->setAlignment(Align(8)); 178 | enableGC->setConstant(false); 179 | enableGC->setInitializer(ConstantInt::get(IntegerType::getInt1Ty(getContext()), 1)); 180 | 181 | initialize(); 182 | } 183 | 184 | void initialize(); 185 | 186 | StructType *getObjStructType() const { return ObjStructType; } 187 | 188 | StructType *getTableStructType() const { return TableStruct; } 189 | 190 | StructType *getEntryStructType() const { return EntryStruct; } 191 | 192 | StructType *getStructType(const ObjType objType) const { 193 | switch (objType) { 194 | case ObjType::STRING: 195 | return StringStructType; 196 | case ObjType::FUNCTION: 197 | return FunctionStructType; 198 | case ObjType::CLOSURE: 199 | return ClosureStructType; 200 | case ObjType::UPVALUE: 201 | return UpvalueStruct; 202 | case ObjType::CLASS: 203 | return ClassStruct; 204 | case ObjType::INSTANCE: 205 | return InstanceStruct; 206 | case ObjType::BOUND_METHOD: 207 | return BoundMethodStruct; 208 | default: 209 | throw std::runtime_error("Not implemented"); 210 | } 211 | } 212 | 213 | GlobalVariable *getObjects() const { return objects; } 214 | 215 | GlobalVariable *getOpenUpvalues() const { return openUpvalues; } 216 | 217 | GlobalVariable *getRuntimeStrings() const { return runtimeStrings; } 218 | 219 | GlobalVariable *getCallStack() const { return callstack; } 220 | 221 | Type *getCallStruct() const { return Call; } 222 | 223 | GlobalVariable *getCallStackPointer() const { return callstackpointer; } 224 | 225 | const GlobalStack &getGrayStack() const { return *grayStack; } 226 | 227 | const GlobalStack &getLocalsStack() const { return *localsStack; } 228 | 229 | GlobalVariable *getAllocatedBytes() const { return allocatedBytes; } 230 | 231 | GlobalVariable *getNextGC() const { return nextGC; } 232 | 233 | GlobalVariable *getEnableGC() const { return enableGC; } 234 | 235 | StringMap &getStringCache() { return strings; } 236 | }; 237 | }// namespace lox 238 | 239 | #endif//LOXMODULE_H 240 | -------------------------------------------------------------------------------- /src/compiler/MDUtil.h: -------------------------------------------------------------------------------- 1 | #ifndef MDBUILDERUTIL_H 2 | #define MDBUILDERUTIL_H 3 | #include 4 | 5 | namespace lox::metadata { 6 | inline MDNode *createLikelyBranchWeights(MDBuilder &Builder) { 7 | // TODO: Use MDBuilder::createLikelyBranchWeights when updating LLVM. 8 | // Value chosen to match UR_NONTAKEN_WEIGHT, see BranchProbabilityInfo.cpp 9 | return Builder.createBranchWeights((1U << 20) - 1, 1); 10 | } 11 | 12 | inline bool hasMetadata(Value *value, const StringRef name) { 13 | if (auto *const i = dyn_cast(value); i && i->hasMetadata(name)) { 14 | return true; 15 | } 16 | 17 | if (auto *const g = dyn_cast(value); g && g->hasMetadata(name)) { 18 | return true; 19 | } 20 | 21 | return false; 22 | } 23 | 24 | inline MDNode *getMetadata(Value *value, const StringRef name) { 25 | if (auto *const i = dyn_cast(value); i && i->hasMetadata()) { 26 | return i->getMetadata(name); 27 | } 28 | 29 | if (auto *const g = dyn_cast(value); g && g->hasMetadata()) { 30 | return g->getMetadata(name); 31 | } 32 | 33 | return nullptr; 34 | } 35 | 36 | inline void setMetadata(Value *value, const StringRef name, MDNode *node) { 37 | if (auto *const i = dyn_cast(value)) { 38 | i->setMetadata(name, node); 39 | } 40 | 41 | if (auto *const g = dyn_cast(value)) { 42 | g->setMetadata(name, node); 43 | } 44 | } 45 | 46 | inline void copyMetadata(Value *src, Value *dest) { 47 | if (auto *const i = dyn_cast(dest)) { 48 | if (isa(src)) { 49 | i->copyMetadata(*cast(src)); 50 | } else if (auto *const d = dyn_cast(src)) { 51 | SmallVector> MDs; 52 | d->getAllMetadata(MDs); 53 | for (auto &[kindID, snd]: MDs) { 54 | MDNode *node = snd; 55 | i->setMetadata(kindID, node); 56 | } 57 | } 58 | } 59 | 60 | if (auto *const g = dyn_cast(dest)) { 61 | if (isa(src)) { 62 | g->copyMetadata(cast(src), 0); 63 | } else if (const auto *const d = dyn_cast(src)) { 64 | SmallVector> MDs; 65 | d->getAllMetadata(MDs); 66 | for (auto &[kindID, snd]: MDs) { 67 | MDNode *node = snd; 68 | g->setMetadata(kindID, node); 69 | } 70 | } 71 | } 72 | } 73 | 74 | inline void eraseMetadata(Value *value) { 75 | if (auto *const i = dyn_cast(value)) { 76 | i->eraseMetadataIf([](unsigned, auto *) { return true; }); 77 | } 78 | 79 | if (auto *const g = dyn_cast(value)) { 80 | g->eraseMetadataIf([](unsigned, auto *) { return true; }); 81 | } 82 | } 83 | } 84 | 85 | #endif//MDBUILDERUTIL_H 86 | -------------------------------------------------------------------------------- /src/compiler/Memory.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMORY_H 2 | #define MEMORY_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "LoxBuilder.h" 9 | 10 | using namespace llvm; 11 | 12 | namespace lox { 13 | AllocaInst *CreateEntryBlockAlloca(Function *TheFunction, Type *type, std::string_view VarName, const std::function &, AllocaInst *)> &entryBuilder = nullptr); 14 | void FreeObject(LoxBuilder &Builder, Value *value); 15 | void FreeObjects(LoxBuilder &Builder); 16 | }// namespace lox 17 | 18 | #endif//MEMORY_H 19 | -------------------------------------------------------------------------------- /src/compiler/ModuleCompiler.cpp: -------------------------------------------------------------------------------- 1 | #include "ModuleCompiler.h" 2 | #include "../Debug.h" 3 | #include "FunctionCompiler.h" 4 | #include "GC.h" 5 | #include "MDUtil.h" 6 | #include "Stack.h" 7 | 8 | #include "llvm/IR/BasicBlock.h" 9 | #include "llvm/IR/DerivedTypes.h" 10 | #include "llvm/IR/Function.h" 11 | #include "llvm/IR/IRBuilder.h" 12 | #include "llvm/IR/Instructions.h" 13 | #include "llvm/IR/LegacyPassManager.h" 14 | #include "llvm/IR/Module.h" 15 | #include "llvm/IR/Type.h" 16 | #include "llvm/IR/Verifier.h" 17 | #include "llvm/MC/TargetRegistry.h" 18 | #include "llvm/Support/FileSystem.h" 19 | #include "llvm/Support/TargetSelect.h" 20 | #include "llvm/Support/raw_ostream.h" 21 | #include "llvm/Target/TargetMachine.h" 22 | #include "llvm/Target/TargetOptions.h" 23 | #include "llvm/TargetParser/Host.h" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | using namespace llvm; 31 | using namespace llvm::sys; 32 | 33 | namespace lox { 34 | 35 | static void Native( 36 | const StringRef name, const unsigned long numArgs, FunctionCompiler &ScriptCompiler, 37 | const std::function &block 38 | ) { 39 | auto &ScriptBuilder = ScriptCompiler.getBuilder(); 40 | std::vector paramTypes(numArgs, ScriptBuilder.getInt64Ty()); 41 | // The second parameter is for the receiver instance for methods 42 | // or the function object value itself for functions. 43 | paramTypes.insert(paramTypes.begin(), ScriptBuilder.getInt64Ty()); 44 | // The first parameter is the upvalues. 45 | paramTypes.insert(paramTypes.begin(), ScriptBuilder.getPtrTy()); 46 | 47 | Function *F = Function::Create( 48 | FunctionType::get(ScriptBuilder.getInt64Ty(), paramTypes, false), Function::InternalLinkage, 49 | name + "_native", ScriptBuilder.getModule() 50 | ); 51 | F->addFnAttr(Attribute::NoRecurse); 52 | F->addFnAttr(Attribute::AlwaysInline); 53 | F->addParamAttr(0, Attribute::ReadNone); 54 | 55 | LoxBuilder B(ScriptBuilder.getContext(), ScriptBuilder.getModule(), *F); 56 | 57 | auto *const EntryBlock = B.CreateBasicBlock("entry"); 58 | B.SetInsertPoint(EntryBlock); 59 | 60 | block(B, F->arg_begin() + 2); 61 | 62 | auto *const closure = ScriptBuilder.AllocateClosure(F, name, true); 63 | auto *const variable = cast(ScriptCompiler.insertVariable(name, ScriptBuilder.ObjVal(closure))); 64 | auto *const nameNode = MDString::get(ScriptBuilder.getContext(), name); 65 | auto *const arityNode = ValueAsMetadata::get(ScriptBuilder.getInt32(numArgs)); 66 | auto *const llvmFunctionName = MDString::get(ScriptBuilder.getContext(), F->getName()); 67 | metadata::setMetadata( 68 | variable, "lox-function", MDTuple::get(ScriptBuilder.getContext(), {nameNode, arityNode, llvmFunctionName}) 69 | ); 70 | } 71 | 72 | void ModuleCompiler::evaluate(const Program &program) const { 73 | // ---- Main ----- 74 | 75 | Function *F = Function::Create( 76 | FunctionType::get(Builder->getVoidTy(), {}, false), Function::InternalLinkage, "script", getModule() 77 | ); 78 | 79 | F->addFnAttr(Attribute::NoRecurse); 80 | Builder->getFunction()->addFnAttr(Attribute::NoRecurse); 81 | 82 | FunctionCompiler ScriptCompiler(getContext(), getModule(), *F, LoxFunctionType::NONE); 83 | 84 | CreateGcFunction(*Builder); 85 | 86 | ScriptCompiler.compile(program, {}, [&ScriptCompiler](LoxBuilder &B) { 87 | ScriptCompiler.insertVariable("$initString", B.ObjVal(B.AllocateString("init")), true); 88 | 89 | Native("clock", 0, ScriptCompiler, [](LoxBuilder &B, Argument *) { 90 | static const FunctionCallee clock = 91 | B.getModule().getOrInsertFunction("clock", FunctionType::get(B.getInt64Ty(), {}, false)); 92 | B.CreateRet(B.NumberVal(B.CreateFDiv( 93 | B.CreateSIToFP(B.CreateCall(clock), B.getDoubleTy()), ConstantFP::get(B.getDoubleTy(), 1000000.0) 94 | ))); 95 | }); 96 | 97 | Native("exit", 1, ScriptCompiler, [](LoxBuilder &B, Argument *args) { 98 | B.Exit(B.CreateFPToSI(B.AsNumber(args), B.getInt32Ty())); 99 | }); 100 | 101 | Native("read", 0, ScriptCompiler, [](LoxBuilder &B, Argument *) { 102 | static const FunctionCallee getchar = 103 | B.getModule().getOrInsertFunction("getchar", FunctionType::get(B.getInt8Ty(), {}, false)); 104 | auto *const result = B.CreateCall(getchar); 105 | B.CreateRet(B.CreateSelect( 106 | B.CreateICmpEQ(result, B.getInt8(-1)), B.getNilVal(), 107 | B.NumberVal(B.CreateSIToFP(result, B.getDoubleTy())) 108 | )); 109 | }); 110 | 111 | Native("printerr", 1, ScriptCompiler, [](LoxBuilder &B, Argument *args) { 112 | B.PrintFErr(B.CreateGlobalCachedString("%s\n"), {B.AsCString(args)}); 113 | B.CreateRet(B.getNilVal()); 114 | }); 115 | 116 | Native("utf", 4, ScriptCompiler, [](LoxBuilder &B, Argument *args) { 117 | // example: utf(224, 174, 131, nil); -> ஃ 118 | auto *const count = CreateEntryBlockAlloca(B.getFunction(), B.getInt32Ty(), "count"); 119 | B.CreateStore(B.getInt32(0), count); 120 | 121 | auto *const type = ArrayType::get(B.getInt8Ty(), 4); 122 | auto *const bytes = CreateEntryBlockAlloca(B.getFunction(), type, "bytes"); 123 | 124 | for (int i = 0; i < 4; i++) { 125 | B.CreateStore( 126 | B.CreateSelect( 127 | B.IsNil(args + i), B.getInt8(0), B.CreateFPToSI(B.AsNumber(args + i), B.getInt8Ty()) 128 | ), 129 | B.CreateInBoundsGEP(type, bytes, {B.getInt8(0), B.getInt8(i)}) 130 | ); 131 | 132 | auto *const c = B.CreateLoad(B.getInt32Ty(), count); 133 | B.CreateStore(B.CreateSelect(B.IsNil(args + i), c, B.CreateAdd(B.getInt32(1), c)), count); 134 | } 135 | 136 | auto *const length = B.CreateLoad(B.getInt32Ty(), count); 137 | auto *const allocsize = B.CreateAdd(B.getInt32(1), length, "lengthwithnullterminator", true, true); 138 | auto *const chars = B.CreateRealloc( 139 | B.getNullPtr(), B.CreateSExt(B.getSizeOf(B.getInt8Ty(), allocsize), B.getInt64Ty()), "string" 140 | ); 141 | 142 | B.CreateMemCpy(chars, Align(8), bytes, Align(8), length); 143 | 144 | B.CreateStore(/* null terminator */ B.getInt8(0), B.CreateInBoundsGEP(B.getInt8Ty(), chars, {length})); 145 | 146 | B.CreateRet(B.ObjVal(B.AllocateString(chars, length))); 147 | }); 148 | }); 149 | 150 | Builder->SetInsertPoint(Builder->CreateBasicBlock("entry")); 151 | auto *const runtimeStringsTable = Builder->AllocateTable(); 152 | Builder->CreateStore(runtimeStringsTable, getModule().getRuntimeStrings()); 153 | Builder->CreateCall(F); 154 | 155 | if constexpr (ENABLE_RUNTIME_ASSERTS) { 156 | auto *const locals = Builder->getModule().getLocalsStack().CreateGetCount(*Builder); 157 | auto *const IsZeroBlock = Builder->CreateBasicBlock("is.empty"); 158 | auto *const IsNotZeroBlock = Builder->CreateBasicBlock("is.notempty"); 159 | 160 | Builder->CreateCondBr(Builder->CreateICmpEQ(Builder->getInt32(0), locals), IsZeroBlock, IsNotZeroBlock); 161 | Builder->SetInsertPoint(IsNotZeroBlock); 162 | Builder->RuntimeError( 163 | Builder->getInt32(0), "locals not zero (%d)\n", {locals}, Builder->CreateGlobalCachedString("assert") 164 | ); 165 | 166 | Builder->SetInsertPoint(IsZeroBlock); 167 | } 168 | 169 | FreeObjects(*Builder); 170 | 171 | Builder->CreateRet(Builder->getInt32(0)); 172 | } 173 | 174 | bool ModuleCompiler::initializeTarget() const { 175 | const auto TargetTriple = getDefaultTargetTriple(); 176 | InitializeAllTargetInfos(); 177 | InitializeAllTargets(); 178 | InitializeAllTargetMCs(); 179 | InitializeAllAsmParsers(); 180 | InitializeAllAsmPrinters(); 181 | auto &M = getModule(); 182 | 183 | std::string Error; 184 | const auto *const Target = TargetRegistry::lookupTarget(TargetTriple, Error); 185 | if (!Target) { 186 | std::cerr << Error; 187 | return false; 188 | } 189 | const auto *const CPU = "generic"; 190 | const auto *const Features = ""; 191 | 192 | const TargetOptions opt; 193 | auto *const TheTargetMachine = Target->createTargetMachine(TargetTriple, CPU, Features, opt, Reloc::PIC_); 194 | 195 | M.setTargetTriple(TargetTriple); 196 | M.setDataLayout(TheTargetMachine->createDataLayout()); 197 | 198 | this->TheTargetMachine = TheTargetMachine; 199 | 200 | return true; 201 | } 202 | 203 | bool ModuleCompiler::optimize() const { 204 | if (!this->TheTargetMachine) { return false; } 205 | LoopAnalysisManager LAM; 206 | FunctionAnalysisManager FAM; 207 | CGSCCAnalysisManager CGAM; 208 | ModuleAnalysisManager MAM; 209 | PassBuilder PB; 210 | 211 | // PB.printPassNames(outs()); 212 | PB.registerModuleAnalyses(MAM); 213 | PB.registerCGSCCAnalyses(CGAM); 214 | PB.registerFunctionAnalyses(FAM); 215 | PB.registerLoopAnalyses(LAM); 216 | PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); 217 | 218 | ModulePassManager MPM = PB.buildPerModuleDefaultPipeline(OptimizationLevel::O2); 219 | 220 | MPM.run(getModule(), MAM); 221 | 222 | return true; 223 | } 224 | 225 | bool ModuleCompiler::writeIR(const std::string_view Filename) const { 226 | if (!this->TheTargetMachine) { return false; } 227 | std::error_code ec; 228 | auto out = raw_fd_ostream(Filename, ec); 229 | getModule().print(out, nullptr); 230 | out.close(); 231 | return ec.value() == 0; 232 | } 233 | 234 | bool ModuleCompiler::writeObject(const std::string_view Filename) const { 235 | if (!this->TheTargetMachine) { return false; } 236 | std::error_code EC; 237 | raw_fd_ostream dest(Filename, EC, sys::fs::OF_None); 238 | 239 | if (EC) { 240 | std::cerr << "Could not open file: " << EC.message(); 241 | return false; 242 | } 243 | 244 | legacy::PassManager pass; 245 | 246 | if (constexpr auto FileType = CodeGenFileType::ObjectFile; 247 | TheTargetMachine->addPassesToEmitFile(pass, dest, nullptr, FileType)) { 248 | std::cerr << "TheTargetMachine can't emit a file of this type"; 249 | return false; 250 | } 251 | 252 | pass.run(getModule()); 253 | dest.flush(); 254 | 255 | std::cout << "Wrote " << Filename << "\n"; 256 | return true; 257 | } 258 | }// namespace lox 259 | -------------------------------------------------------------------------------- /src/compiler/ModuleCompiler.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPILER_H 2 | #define COMPILER_H 3 | 4 | #include "../frontend/AST.h" 5 | #include "LoxBuilder.h" 6 | #include "LoxModule.h" 7 | 8 | #include 9 | #include 10 | 11 | using namespace llvm; 12 | using namespace llvm::sys; 13 | 14 | namespace lox { 15 | 16 | class ModuleCompiler { 17 | std::shared_ptr Context = std::make_shared(); 18 | std::unique_ptr M = std::make_unique(*Context); 19 | std::unique_ptr Builder; 20 | mutable TargetMachine *TheTargetMachine{}; 21 | 22 | public: 23 | ModuleCompiler() { 24 | Function *MainFunction = Function::Create( 25 | FunctionType::get(IntegerType::getInt32Ty(*Context), false), Function::ExternalLinkage, "main", *M 26 | ); 27 | Builder = std::make_unique(*Context, *M, *MainFunction); 28 | } 29 | 30 | [[nodiscard]] LoxModule &getModule() const { return *M; } 31 | 32 | [[nodiscard]] LLVMContext &getContext() const { return *Context; } 33 | 34 | bool initializeTarget() const; 35 | void evaluate(const Program &program) const; 36 | bool optimize() const; 37 | [[nodiscard]] bool writeIR(std::string_view Filename) const; 38 | [[nodiscard]] bool writeObject(std::string_view Filename) const; 39 | }; 40 | 41 | }// namespace lox 42 | 43 | #endif//COMPILER_H 44 | -------------------------------------------------------------------------------- /src/compiler/Stack.cpp: -------------------------------------------------------------------------------- 1 | #include "Stack.h" 2 | 3 | #include "../Debug.h" 4 | #include "GC.h" 5 | #include "Memory.h" 6 | 7 | #include 8 | 9 | namespace lox { 10 | 11 | static void ensureCapacity(LoxModule &M, IRBuilder<> &Builder, Value *stack, StructType *type, Value *size) { 12 | assert(size->getType() == Builder.getInt32Ty()); 13 | 14 | static auto *EnsureCapacityFunction([&Builder, &M] { 15 | auto *const F = Function::Create( 16 | FunctionType::get( 17 | Builder.getVoidTy(), 18 | {Builder.getPtrTy(), Builder.getPtrTy(), Builder.getPtrTy(), Builder.getInt32Ty()}, false 19 | ), 20 | Function::InternalLinkage, "$stackEnsureCapacity", M 21 | ); 22 | 23 | F->addFnAttr(Attribute::AlwaysInline); 24 | 25 | LoxBuilder B(Builder.getContext(), M, *F); 26 | 27 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 28 | B.SetInsertPoint(EntryBasicBlock); 29 | 30 | auto *const arguments = F->args().begin(); 31 | auto *const $stack = arguments + 0; 32 | auto *const $count = arguments + 1; 33 | auto *const $capacity = arguments + 2; 34 | auto *const size = arguments + 3; 35 | 36 | if (DEBUG_STACK) { B.PrintF({B.CreateGlobalCachedString("ensure cap stack with %d\n"), size}); } 37 | 38 | auto *const stack = B.CreateLoad(B.getPtrTy(), $stack); 39 | auto *const count = B.CreateLoad(B.getInt32Ty(), $count); 40 | auto *const capacity = B.CreateLoad(B.getInt32Ty(), $capacity); 41 | 42 | auto *const GrowBlock = B.CreateBasicBlock("grow"); 43 | auto *const EndBlock = B.CreateBasicBlock("end"); 44 | auto *const DontGrowBlock = DEBUG_STACK ? B.CreateBasicBlock("dont.grow") : EndBlock; 45 | 46 | B.CreateCondBr(B.CreateICmpSLT(capacity, size), GrowBlock, DontGrowBlock); 47 | 48 | if constexpr (DEBUG_STACK) { 49 | B.SetInsertPoint(DontGrowBlock); 50 | B.PrintF( 51 | {B.CreateGlobalCachedString("don't grow realloc stack: %p (count: %d, capacity: %d)\n"), stack, 52 | count, capacity} 53 | ); 54 | B.CreateBr(EndBlock); 55 | } 56 | 57 | B.SetInsertPoint(GrowBlock); 58 | auto *const newCapacity = B.CreateSelect( 59 | B.CreateICmpSLT(size, B.getInt32(8)), B.getInt32(8), 60 | B.CreateMul(size, B.getInt32(GROWTH_FACTOR), "newcapacity", true, true) 61 | ); 62 | B.CreateStore(newCapacity, $capacity); 63 | 64 | auto *const newSize = B.getSizeOf(B.getPtrTy(), newCapacity); 65 | 66 | if constexpr (DEBUG_STACK || DEBUG_LOG_GC) { 67 | B.PrintF({B.CreateGlobalCachedString("grow realloc stack: %p with new size %d\n"), stack, newSize}); 68 | } 69 | 70 | auto *const result = B.CreateRealloc(stack, newSize, "stack"); 71 | 72 | auto *const IsNullBlock = B.CreateBasicBlock("error.realloc"); 73 | auto *const OkBlock = B.CreateBasicBlock("ok.realloc"); 74 | 75 | B.CreateCondBr(B.CreateIsNull(result), IsNullBlock, OkBlock); 76 | B.SetInsertPoint(IsNullBlock); 77 | { 78 | B.RuntimeError( 79 | B.getInt32(0), "Could not reallocate %d for %p\n", {newSize, stack}, 80 | B.CreateGlobalCachedString("ensureCapacity") 81 | ); 82 | } 83 | B.SetInsertPoint(OkBlock); 84 | { 85 | if constexpr (DEBUG_STACK || DEBUG_LOG_GC) { 86 | B.PrintF( 87 | {B.CreateGlobalCachedString( 88 | "realloc stack: %p; %p -> %p (count: %d, capacity: %d, newCapacity: %d)\n" 89 | ), 90 | $stack, stack, result, count, capacity, newCapacity} 91 | ); 92 | } 93 | B.CreateStore(result, $stack); 94 | 95 | // Initialize the new entries with nullptr. 96 | auto *const i = CreateEntryBlockAlloca(F, B.getInt32Ty(), "i"); 97 | 98 | B.CreateStore(count, i); 99 | 100 | auto *const ForCond = B.CreateBasicBlock("for.cond"); 101 | auto *const ForBody = B.CreateBasicBlock("for.body"); 102 | auto *const ForInc = B.CreateBasicBlock("for.inc"); 103 | auto *const ForEnd = B.CreateBasicBlock("for.end"); 104 | 105 | B.CreateBr(ForCond); 106 | B.SetInsertPoint(ForCond); 107 | { 108 | B.CreateCondBr(B.CreateICmpSLT(B.CreateLoad(B.getInt32Ty(), i), newCapacity), ForBody, ForEnd); 109 | B.SetInsertPoint(ForBody); 110 | 111 | auto *const addr = B.CreateInBoundsGEP(B.getPtrTy(), result, B.CreateLoad(B.getInt32Ty(), i)); 112 | 113 | if constexpr (DEBUG_STACK) { 114 | B.PrintF( 115 | {B.CreateGlobalCachedString("set value: (result: %p) %d %p -> %p\n"), result, 116 | B.CreateLoad(B.getInt32Ty(), i), B.getNullPtr(), addr} 117 | ); 118 | } 119 | 120 | B.CreateStore(B.getNullPtr(), addr); 121 | 122 | B.CreateBr(ForInc); 123 | B.SetInsertPoint(ForInc); 124 | { 125 | B.CreateStore( 126 | B.CreateAdd(B.CreateLoad(B.getInt32Ty(), i), B.getInt32(1), "i+1", true, true), i 127 | ); 128 | B.CreateBr(ForCond); 129 | } 130 | } 131 | B.SetInsertPoint(ForEnd); 132 | { B.CreateBr(EndBlock); } 133 | } 134 | 135 | B.SetInsertPoint(EndBlock); 136 | B.CreateRetVoid(); 137 | 138 | return F; 139 | }()); 140 | 141 | auto *const $stack = Builder.CreateStructGEP(type, stack, 0); 142 | auto *const $count = Builder.CreateStructGEP(type, stack, 1); 143 | auto *const $capacity = Builder.CreateStructGEP(type, stack, 2); 144 | 145 | Builder.CreateCall(EnsureCapacityFunction, {$stack, $count, $capacity, size}); 146 | } 147 | 148 | Value *GlobalStack::CreateGetCount(IRBuilder<> &B) const { 149 | return B.CreateLoad(B.getInt32Ty(), B.CreateStructGEP(StackStruct, stack, 1)); 150 | } 151 | 152 | void GlobalStack::CreateSet(LoxBuilder &B, Value *index, Value *value) const { 153 | auto *const $stack = B.CreateStructGEP(StackStruct, stack, 0); 154 | auto *const stack = B.CreateLoad(B.getPtrTy(), $stack); 155 | auto *const addr = B.CreateInBoundsGEP(B.getPtrTy(), stack, index); 156 | 157 | if constexpr (DEBUG_STACK) { 158 | B.PrintF({B.CreateGlobalCachedString("set value: %d %p -> %p\n"), index, value, addr}); 159 | } 160 | 161 | B.CreateStore(value, addr); 162 | } 163 | 164 | // Create N new slots on the stack 165 | // The new slots will be initialized to nullptr only if the stack storage 166 | // needs to be reallocated. 167 | void GlobalStack::CreatePushN(LoxModule &M, IRBuilder<> &Builder, Value *N) const { 168 | auto *const count = CreateGetCount(Builder); 169 | auto *const size = Builder.CreateAdd(N, count, "newCount", true, true); 170 | ensureCapacity(M, Builder, stack, StackStruct, size); 171 | Builder.CreateStore(size, Builder.CreateStructGEP(StackStruct, stack, 1)); 172 | } 173 | 174 | 175 | void GlobalStack::CreatePush(LoxModule &M, IRBuilder<> &Builder, Value *Object) const { 176 | static auto *PushFunction([&Builder, &M, this] { 177 | auto *const F = Function::Create( 178 | FunctionType::get(Builder.getVoidTy(), {Builder.getPtrTy(), Builder.getPtrTy()}, false), 179 | Function::InternalLinkage, "$stackPush", M 180 | ); 181 | 182 | LoxBuilder B(Builder.getContext(), M, *F); 183 | 184 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 185 | B.SetInsertPoint(EntryBasicBlock); 186 | 187 | auto *const stackGlobal = F->args().begin(); 188 | auto *const $stack = B.CreateStructGEP(StackStruct, stackGlobal, 0); 189 | auto *const $count = B.CreateStructGEP(StackStruct, stackGlobal, 1); 190 | auto *const objPtr = stackGlobal + 1; 191 | 192 | if constexpr (DEBUG_STACK || DEBUG_LOG_GC) { 193 | B.PrintF({B.CreateGlobalCachedString("push stack %p; ptr %p\n"), $stack, stackGlobal}); 194 | } 195 | 196 | auto *const count = B.CreateLoad(B.getInt32Ty(), $count); 197 | 198 | ensureCapacity(M, B, stackGlobal, StackStruct, B.CreateAdd(B.getInt32(1), count, "count+1", true, true)); 199 | 200 | auto *const ptr = B.CreateLoad(B.getPtrTy(), $stack); 201 | auto *const addr = B.CreateInBoundsGEP(B.getPtrTy(), ptr, count); 202 | if constexpr (DEBUG_STACK || DEBUG_LOG_GC) { 203 | B.PrintF({B.CreateGlobalCachedString("push (%p, %p) = %p\n"), ptr, addr, objPtr}); 204 | } 205 | B.CreateStore(objPtr, addr); 206 | 207 | auto *const newCount = B.CreateAdd(B.getInt32(1), count, "newcount", true, true); 208 | B.CreateStore(newCount, $count); 209 | 210 | B.CreateRetVoid(); 211 | 212 | return F; 213 | }()); 214 | 215 | Builder.CreateCall(PushFunction, {stack, Object}); 216 | } 217 | 218 | void GlobalStack::CreatePopN(LoxBuilder &Builder, Value *N) const { 219 | static auto *PopFunction([&] { 220 | auto *const F = Function::Create( 221 | FunctionType::get(Builder.getVoidTy(), {Builder.getPtrTy(), Builder.getInt32Ty()}, false), 222 | Function::InternalLinkage, "$stackPopN", Builder.getModule() 223 | ); 224 | 225 | LoxBuilder B(Builder.getContext(), Builder.getModule(), *F); 226 | 227 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 228 | B.SetInsertPoint(EntryBasicBlock); 229 | 230 | auto *const arguments = F->args().begin(); 231 | auto *const $count = B.CreateStructGEP(StackStruct, arguments, 1); 232 | auto *const N = arguments + 1; 233 | 234 | auto *const count = B.CreateLoad(B.getInt32Ty(), $count); 235 | 236 | B.CreateStore(B.CreateSub(count, N, "count", true, true), $count); 237 | 238 | B.CreateRetVoid(); 239 | 240 | return F; 241 | }()); 242 | 243 | Builder.CreateCall(PopFunction, {stack, N}); 244 | } 245 | 246 | void GlobalStack::CreatePopAll(LoxBuilder &Builder, Function *FunctionPointer) const { 247 | static auto *IterateFunction([&] { 248 | auto *const F = Function::Create( 249 | FunctionType::get(Builder.getVoidTy(), {Builder.getPtrTy(), Builder.getPtrTy()}, false), 250 | Function::InternalLinkage, "$stackPopAll", Builder.getModule() 251 | ); 252 | 253 | LoxBuilder B(Builder.getContext(), Builder.getModule(), *F); 254 | 255 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 256 | B.SetInsertPoint(EntryBasicBlock); 257 | 258 | auto *const arguments = F->args().begin(); 259 | auto *const $stack = B.CreateStructGEP(StackStruct, arguments, 0); 260 | auto *const $count = B.CreateStructGEP(StackStruct, arguments, 1); 261 | auto *const function = arguments + 1; 262 | 263 | auto *const count = B.CreateLoad(B.getInt32Ty(), $count); 264 | 265 | if constexpr (DEBUG_LOG_GC) { B.PrintF({B.CreateGlobalCachedString("--iterate stack (%d)--\n"), count}); } 266 | 267 | auto *const WhileCond = B.CreateBasicBlock("while.cond"); 268 | auto *const WhileBody = B.CreateBasicBlock("while.body"); 269 | auto *const WhileEnd = B.CreateBasicBlock("while.end"); 270 | 271 | B.CreateBr(WhileCond); 272 | B.SetInsertPoint(WhileCond); 273 | B.CreateCondBr(B.CreateICmpSGT(B.CreateLoad(B.getInt32Ty(), $count), B.getInt32(0)), WhileBody, WhileEnd); 274 | B.SetInsertPoint(WhileBody); 275 | { 276 | auto *const newCount = 277 | B.CreateSub(B.CreateLoad(B.getInt32Ty(), $count), B.getInt32(1), "newCount", true, true); 278 | B.CreateStore(newCount, $count); 279 | auto *const addr = B.CreateInBoundsGEP(B.getPtrTy(), B.CreateLoad(B.getPtrTy(), $stack), newCount); 280 | auto *const ptr = B.CreateLoad(B.getPtrTy(), addr); 281 | 282 | B.CreateCall(FunctionType::get(B.getVoidTy(), {B.getPtrTy()}, false), function, {ptr}); 283 | 284 | B.CreateBr(WhileCond); 285 | } 286 | B.SetInsertPoint(WhileEnd); 287 | B.CreateRetVoid(); 288 | 289 | return F; 290 | }()); 291 | 292 | Builder.CreateCall(IterateFunction, {stack, FunctionPointer}); 293 | } 294 | 295 | void GlobalStack::CreateIterateObjectValues(LoxBuilder &Builder, Function *FunctionPointer) const { 296 | static auto *IterateFunction([&] { 297 | auto *const F = Function::Create( 298 | FunctionType::get(Builder.getVoidTy(), {Builder.getPtrTy(), Builder.getPtrTy()}, false), 299 | Function::InternalLinkage, "$iterateStack", Builder.getModule() 300 | ); 301 | 302 | LoxBuilder B(Builder.getContext(), Builder.getModule(), *F); 303 | 304 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 305 | B.SetInsertPoint(EntryBasicBlock); 306 | 307 | auto *const arguments = F->args().begin(); 308 | auto *const $stack = B.CreateStructGEP(StackStruct, arguments, 0); 309 | auto *const $count = B.CreateStructGEP(StackStruct, arguments, 1); 310 | auto *const function = arguments + 1; 311 | 312 | auto *const stack = B.CreateLoad(B.getPtrTy(), $stack); 313 | auto *const count = B.CreateLoad(B.getInt32Ty(), $count); 314 | 315 | if constexpr (DEBUG_STACK || DEBUG_LOG_GC) { 316 | B.PrintF({B.CreateGlobalCachedString("--iterate stack values (%d)--\n"), count}); 317 | } 318 | auto *const i = CreateEntryBlockAlloca(F, B.getInt32Ty(), "i"); 319 | 320 | B.CreateStore(B.getInt32(1), i); 321 | 322 | auto *const ForCond = B.CreateBasicBlock("for.cond"); 323 | auto *const ForBody = B.CreateBasicBlock("for.body"); 324 | auto *const ForInc = B.CreateBasicBlock("for.inc"); 325 | auto *const ForEnd = B.CreateBasicBlock("for.end"); 326 | 327 | B.CreateBr(ForCond); 328 | B.SetInsertPoint(ForCond); 329 | B.CreateCondBr(B.CreateICmpSLE(B.CreateLoad(B.getInt32Ty(), i), count), ForBody, ForEnd); 330 | B.SetInsertPoint(ForBody); 331 | 332 | auto *const top = B.CreateSub(B.CreateLoad(B.getInt32Ty(), $count), B.CreateLoad(B.getInt32Ty(), i)); 333 | auto *const addr = B.CreateInBoundsGEP(B.getPtrTy(), stack, top); 334 | 335 | auto *const ptr = B.CreateLoad(B.getPtrTy(), addr); 336 | 337 | if constexpr (DEBUG_STACK || DEBUG_LOG_GC) { 338 | B.PrintF( 339 | {B.CreateGlobalCachedString("iter ptr %d: %p %p\n"), B.CreateLoad(B.getInt32Ty(), i), addr, ptr} 340 | ); 341 | } 342 | 343 | auto *const IsObjBlock = B.CreateBasicBlock("is.obj"); 344 | auto *const EndBlock = B.CreateBasicBlock("end.obj"); 345 | auto *const IsNotNull = B.CreateBasicBlock("is.notnull"); 346 | auto *const IsNotObjBlock = DEBUG_LOG_GC ? B.CreateBasicBlock("is.not.obj") : EndBlock; 347 | 348 | B.CreateCondBr(B.CreateIsNull(ptr), IsNotObjBlock, IsNotNull); 349 | B.SetInsertPoint(IsNotNull); 350 | 351 | auto *const value = B.CreateLoad(B.getInt64Ty(), ptr); 352 | 353 | if constexpr (DEBUG_STACK || DEBUG_LOG_GC) { 354 | B.PrintF({B.CreateGlobalCachedString("iter value: %d\n"), value}); 355 | } 356 | auto *const CheckObjBlock = B.CreateBasicBlock("check.obj"); 357 | 358 | B.CreateCondBr(B.IsNil(value), IsNotObjBlock, CheckObjBlock); 359 | 360 | B.SetInsertPoint(CheckObjBlock); 361 | B.CreateCondBr(B.IsObj(value), IsObjBlock, IsNotObjBlock); 362 | B.SetInsertPoint(IsObjBlock); 363 | if constexpr (DEBUG_STACK || DEBUG_LOG_GC) { 364 | B.PrintF({B.CreateGlobalCachedString("calling function %p(%d, %p)\n"), function, value, value}); 365 | } 366 | B.CreateCall(FunctionType::get(B.getVoidTy(), {B.getPtrTy()}, false), function, {(B.AsObj(value))}); 367 | B.CreateBr(EndBlock); 368 | 369 | if constexpr (DEBUG_LOG_GC) { 370 | B.SetInsertPoint(IsNotObjBlock); 371 | B.PrintF({B.CreateGlobalCachedString("not object %p\n"), ptr}); 372 | B.CreateBr(EndBlock); 373 | } 374 | 375 | B.SetInsertPoint(EndBlock); 376 | 377 | B.CreateBr(ForInc); 378 | 379 | B.SetInsertPoint(ForInc); 380 | B.CreateStore(B.CreateAdd(B.CreateLoad(B.getInt32Ty(), i), B.getInt32(1), "i+1", true, true), i); 381 | B.CreateBr(ForCond); 382 | 383 | B.SetInsertPoint(ForEnd); 384 | 385 | B.CreateRetVoid(); 386 | return F; 387 | }()); 388 | 389 | Builder.CreateCall(IterateFunction, {stack, FunctionPointer}); 390 | } 391 | 392 | void GlobalStack::CreateFree(LoxBuilder &Builder) const { 393 | Builder.IRBuilder::CreateFree(Builder.CreateLoad(Builder.getPtrTy(), stack)); 394 | } 395 | }// namespace lox -------------------------------------------------------------------------------- /src/compiler/Stack.h: -------------------------------------------------------------------------------- 1 | #ifndef STACK_H 2 | #define STACK_H 3 | #include "LoxBuilder.h" 4 | #include "LoxModule.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace llvm; 11 | 12 | constexpr unsigned int GROWTH_FACTOR = 2; 13 | 14 | namespace lox { 15 | 16 | // Creates a stack global variable that can store 17 | // pointers. 18 | class GlobalStack { 19 | LoxModule &M; 20 | StructType *const StackStruct = StructType::create( 21 | M.getContext(), 22 | { 23 | PointerType::getUnqual(M.getContext()), 24 | IntegerType::getInt32Ty(M.getContext()), 25 | IntegerType::getInt32Ty(M.getContext()), 26 | }, 27 | "Stack" 28 | ); 29 | std::string_view name; 30 | GlobalVariable *const stack = cast(M.getOrInsertGlobal( 31 | ("stack_" + name).str(), 32 | StackStruct 33 | )); 34 | 35 | public: 36 | explicit GlobalStack(LoxModule &M, const std::string_view name) : M{M}, name(name) { 37 | stack->setLinkage(GlobalVariable::PrivateLinkage); 38 | stack->setAlignment(Align(8)); 39 | stack->setConstant(false); 40 | stack->setInitializer(ConstantAggregateZero::get(StackStruct)); 41 | } 42 | 43 | Value *CreateGetCount(IRBuilder<> &B) const; 44 | 45 | void CreateSet(LoxBuilder &B, Value *index, Value *value) const; 46 | 47 | void CreatePush(LoxModule &M, IRBuilder<> &Builder, Value *Object) const; 48 | void CreatePushN(LoxModule &M, IRBuilder<> &Builder, Value *N) const; 49 | void CreatePopN(LoxBuilder &Builder, Value *N) const; 50 | void CreatePopAll(LoxBuilder &Builder, Function *FunctionPointer) const; 51 | void CreateIterateObjectValues(LoxBuilder &Builder, Function *FunctionPointer) const; 52 | void CreateFree(LoxBuilder &Builder) const; 53 | }; 54 | }// namespace lox 55 | #endif//STACK_H 56 | -------------------------------------------------------------------------------- /src/compiler/Stmt.cpp: -------------------------------------------------------------------------------- 1 | #include "FunctionCompiler.h" 2 | #include "GC.h" 3 | #include "MDUtil.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace lox { 9 | 10 | void FunctionCompiler::evaluate(const Stmt &stmt) { std::visit(*this, stmt); } 11 | 12 | void FunctionCompiler::operator()(const BlockStmtPtr &blockStmt) { 13 | beginScope(); 14 | for (auto &statement: blockStmt->statements) { evaluate(statement); } 15 | endScope(); 16 | } 17 | 18 | static Function * 19 | CreateLLVMFunction(LoxBuilder &Builder, const FunctionStmtPtr &functionStmt, const std::string_view name) { 20 | std::vector paramTypes(functionStmt->parameters.size(), Builder.getInt64Ty()); 21 | // The second parameter is for the receiver instance for methods 22 | // or the function object value itself for functions. 23 | paramTypes.insert(paramTypes.begin(), Builder.getInt64Ty()); 24 | // The first parameter is the upvalues. 25 | paramTypes.insert(paramTypes.begin(), Builder.getPtrTy()); 26 | auto *const FT = FunctionType::get(IntegerType::getInt64Ty(Builder.getContext()), paramTypes, false); 27 | 28 | return Function::Create(FT, Function::InternalLinkage, name, Builder.getModule()); 29 | } 30 | 31 | void FunctionCompiler::CreateFunction( 32 | const FunctionStmtPtr &functionStmt, const std::string_view name, 33 | const std::function &initializer = [](auto *V) -> Value * { return V; } 34 | ) { 35 | auto *const F = CreateLLVMFunction(Builder, functionStmt, name); 36 | 37 | if (functionStmt->type == LoxFunctionType::INITIALIZER) { 38 | // Initializers always return their instance which is the second parameter. 39 | F->addParamAttr(1, Attribute::Returned); 40 | } 41 | 42 | auto *const closurePtr = Builder.AllocateClosure(F, functionStmt->name.getLexeme(), false); 43 | 44 | initializer(closurePtr); 45 | 46 | if (functionStmt->type == LoxFunctionType::FUNCTION) { 47 | auto *const variable = 48 | insertVariable(functionStmt->name.getLexeme(), Builder.ObjVal(closurePtr), !isGlobalScope()); 49 | auto *nameNode = MDString::get(Builder.getContext(), name); 50 | auto *arityNode = ValueAsMetadata::get(Builder.getInt32(functionStmt->parameters.size())); 51 | metadata::setMetadata(variable, "lox-function", MDTuple::get(Builder.getContext(), {nameNode, arityNode, nameNode})); 52 | } 53 | 54 | FunctionCompiler C(Builder.getContext(), Builder.getModule(), *F, functionStmt->type, this); 55 | C.compile(functionStmt->body, functionStmt->parameters, [&, &C](LoxBuilder &B) { 56 | if (C.type == LoxFunctionType::METHOD || C.type == LoxFunctionType::INITIALIZER) { 57 | C.insertVariable("this", B.getFunction()->arg_begin() + 1, true); 58 | } else if (C.type == LoxFunctionType::FUNCTION) { 59 | // For functions, use the 2nd parameter for the function itself. 60 | // This improves recursive calling performance since there is no 61 | // need for an upvalue any longer. 62 | auto *const variable = C.insertVariable(name, B.getFunction()->arg_begin() + 1); 63 | auto *nameNode = MDString::get(Builder.getContext(), name); 64 | auto *arityNode = ValueAsMetadata::get(Builder.getInt32(functionStmt->parameters.size())); 65 | metadata::setMetadata( 66 | variable, "lox-function", MDTuple::get(Builder.getContext(), {nameNode, arityNode, nameNode}) 67 | ); 68 | } 69 | }); 70 | 71 | // Store captured variables in the closure's upvalue array. 72 | if (!C.upvalues.empty()) { 73 | F->addParamAttr(0, Attribute::NonNull); 74 | F->addParamAttr(0, Attribute::ReadOnly); 75 | F->addParamAttr(0, Attribute::NoUndef); 76 | 77 | if constexpr (DEBUG_UPVALUES) { Builder.PrintF({Builder.CreateGlobalCachedString("capture variables\n")}); } 78 | 79 | auto *const upvaluesArraySize = 80 | Builder.getSizeOf(Builder.getModule().getStructType(ObjType::UPVALUE), C.upvalues.size()); 81 | 82 | auto *const upvaluesArrayPtr = Builder.CreateReallocate( 83 | Builder.getNullPtr(), Builder.getInt32(0), Builder.CreateTrunc(upvaluesArraySize, Builder.getInt32Ty()) 84 | ); 85 | 86 | // Initialize upvalues to nullptr. 87 | for (const auto &upvalue: C.upvalues) { 88 | auto *const upvalueIndex = Builder.CreateInBoundsGEP( 89 | Builder.getPtrTy(), upvaluesArrayPtr, Builder.getInt32(upvalue->index), "upvalueIndex" 90 | ); 91 | Builder.CreateStore(Builder.getNullPtr(), upvalueIndex); 92 | } 93 | 94 | Builder.CreateStore( 95 | upvaluesArrayPtr, 96 | Builder.CreateStructGEP( 97 | Builder.getModule().getStructType(ObjType::CLOSURE), closurePtr, 2, "closure.upvalues" 98 | ) 99 | ); 100 | Builder.CreateStore( 101 | Builder.getInt32(C.upvalues.size()), 102 | Builder.CreateStructGEP( 103 | Builder.getModule().getStructType(ObjType::CLOSURE), closurePtr, 3, "closure.upvaluesCount" 104 | ) 105 | ); 106 | for (const auto &upvalue: C.upvalues) { 107 | auto *const upvalueIndex = Builder.CreateInBoundsGEP( 108 | Builder.getPtrTy(), upvaluesArrayPtr, Builder.getInt32(upvalue->index), "upvalueIndex" 109 | ); 110 | Builder.CreateStore(upvalue->isLocal ? captureLocal(upvalue->value) : upvalue->value, upvalueIndex); 111 | } 112 | 113 | Builder.CreateInvariantStart(upvaluesArrayPtr, upvaluesArraySize); 114 | 115 | } else { 116 | F->addParamAttr(0, Attribute::ReadNone); 117 | } 118 | 119 | Builder.CreateInvariantStart(closurePtr, Builder.getSizeOf(ObjType::CLOSURE)); 120 | } 121 | 122 | void FunctionCompiler::operator()(const FunctionStmtPtr &functionStmt) { 123 | CreateFunction(functionStmt, functionStmt->name.getLexeme()); 124 | } 125 | 126 | void FunctionCompiler::operator()(const ExpressionStmtPtr &expressionStmt) { evaluate(expressionStmt->expression); } 127 | 128 | void FunctionCompiler::operator()(const PrintStmtPtr &printStmt) { Builder.Print(evaluate(printStmt->expression)); } 129 | 130 | void FunctionCompiler::operator()(const ReturnStmtPtr &returnStmt) { 131 | if (returnStmt->expression.has_value()) { 132 | auto *const returnVal = variables.lookup("$returnVal")->value; 133 | Builder.CreateStore(evaluate(returnStmt->expression.value()), returnVal); 134 | } 135 | Builder.CreateBr(ExitBasicBlock); 136 | 137 | // Create somewhere to generate code that appears after a return e.g. 138 | // fun foo() { 139 | // print "foo"; 140 | // return; 141 | // print "bar"; 142 | // } 143 | Builder.SetInsertPoint(Builder.CreateBasicBlock("unreachable")); 144 | } 145 | 146 | void FunctionCompiler::operator()(const VarStmtPtr &varStmt) { 147 | if (isGlobalScope()) { 148 | // Global variables can be re-declared. 149 | if (auto *const variable = lookupGlobal(varStmt->name.getLexeme())) { 150 | Builder.CreateStore(evaluate(varStmt->initializer), variable); 151 | return; 152 | } 153 | } 154 | 155 | insertVariable(varStmt->name.getLexeme(), evaluate(varStmt->initializer)); 156 | } 157 | 158 | void FunctionCompiler::operator()(const WhileStmtPtr &whileStmt) { 159 | auto *const Cond = Builder.CreateBasicBlock("Cond"); 160 | auto *const Body = Builder.CreateBasicBlock("Loop"); 161 | auto *const Exit = Builder.CreateBasicBlock("Exit"); 162 | 163 | Builder.CreateBr(Cond); 164 | Builder.SetInsertPoint(Cond); 165 | Builder.CreateCondBr(Builder.IsTruthy(evaluate(whileStmt->condition)), Body, Exit); 166 | Builder.SetInsertPoint(Body); 167 | evaluate(whileStmt->body); 168 | Builder.CreateBr(Cond); 169 | Builder.SetInsertPoint(Exit); 170 | } 171 | 172 | void FunctionCompiler::operator()(const IfStmtPtr &ifStmt) { 173 | auto *const TrueBlock = Builder.CreateBasicBlock("if.true"); 174 | auto *const EndBlock = Builder.CreateBasicBlock("if.end"); 175 | auto *const FalseBlock = ifStmt->elseBranch.has_value() ? Builder.CreateBasicBlock("else") : EndBlock; 176 | Builder.CreateCondBr(Builder.IsTruthy(evaluate(ifStmt->condition)), TrueBlock, FalseBlock); 177 | Builder.SetInsertPoint(TrueBlock); 178 | evaluate(ifStmt->thenBranch); 179 | Builder.CreateBr(EndBlock); 180 | if (ifStmt->elseBranch.has_value()) { 181 | Builder.SetInsertPoint(FalseBlock); 182 | evaluate(ifStmt->elseBranch.value()); 183 | Builder.CreateBr(EndBlock); 184 | } 185 | Builder.SetInsertPoint(EndBlock); 186 | } 187 | 188 | void FunctionCompiler::operator()(const ClassStmtPtr &classStmt) { 189 | const auto className = classStmt->name.getLexeme(); 190 | auto *const klass = DelayGC(Builder, [&className](LoxBuilder &B) { 191 | auto *const nameObj = B.AllocateString(className, ("class_" + className).str()); 192 | return B.AllocateClass(nameObj); 193 | }); 194 | auto *const methods = 195 | Builder.CreateLoad(Builder.getPtrTy(), Builder.CreateObjStructGEP(ObjType::CLASS, klass, 2)); 196 | 197 | auto *const variable = insertVariable(className, Builder.ObjVal(klass), !isGlobalScope()); 198 | auto *nameNode = MDString::get(Builder.getContext(), className); 199 | metadata::setMetadata(variable, "lox-class", MDTuple::get(Builder.getContext(), {nameNode})); 200 | 201 | if (classStmt->super_class.has_value()) { 202 | // Copy all methods from the superclass methods table, to the subclass 203 | // to support inheritance. Do this before adding methods to the sub-class 204 | // to support overloaded methods. 205 | 206 | auto *const superclassVariable = lookupVariable(*classStmt->super_class.value()); 207 | 208 | auto *const value = Builder.CreateLoad(Builder.getInt64Ty(), superclassVariable); 209 | auto *const IsClassBlock = Builder.CreateBasicBlock("superclass.valid"); 210 | auto *const IsNotClassBlock = Builder.CreateBasicBlock("superclass.invalid"); 211 | auto *const EndBlock = Builder.CreateBasicBlock("superclass.end"); 212 | 213 | auto mdBuilder = MDBuilder(Builder.getContext()); 214 | Builder.CreateCondBr( 215 | metadata::hasMetadata(superclassVariable, "lox-class") ? Builder.getTrue() : Builder.IsClass(value), IsClassBlock, 216 | IsNotClassBlock, metadata::createLikelyBranchWeights(mdBuilder) 217 | ); 218 | Builder.SetInsertPoint(IsClassBlock); 219 | 220 | beginScope();// Create a new scope since the "super" variable 221 | // could be declared multiple times, for different classes. 222 | insertVariable("super", value, true); 223 | 224 | auto *const superklass = Builder.AsObj(value); 225 | auto *const supermethods = 226 | Builder.CreateLoad(Builder.getPtrTy(), Builder.CreateObjStructGEP(ObjType::CLASS, superklass, 2)); 227 | Builder.TableAddAll(supermethods, methods); 228 | Builder.CreateBr(EndBlock); 229 | 230 | Builder.SetInsertPoint(IsNotClassBlock); 231 | Builder.RuntimeError( 232 | classStmt->super_class->get()->name.getLine(), "Superclass must be a class.\n", {}, 233 | Builder.getFunction() 234 | ); 235 | 236 | Builder.SetInsertPoint(EndBlock); 237 | } 238 | 239 | for (auto &method: classStmt->methods) { 240 | CreateFunction(method, (className + "." + method->name.getLexeme()).str(), [&](Value *closure) { 241 | // The initializer will add the method to the methods table, 242 | // making the closure reachable ASAP (creation of upvalues in CreateFunction can trigger GC). 243 | auto *const function = 244 | Builder.CreateLoad(Builder.getPtrTy(), Builder.CreateObjStructGEP(ObjType::CLOSURE, closure, 1)); 245 | auto *const name = Builder.CreateLoad( 246 | Builder.getInt64Ty(), Builder.CreateObjStructGEP(ObjType::FUNCTION, function, 3) 247 | ); 248 | Builder.TableSet(methods, Builder.AsObj(name), Builder.ObjVal(closure)); 249 | }); 250 | } 251 | 252 | if (classStmt->super_class.has_value()) { endScope(); } 253 | } 254 | }// namespace lox -------------------------------------------------------------------------------- /src/compiler/String.cpp: -------------------------------------------------------------------------------- 1 | #include "Memory.h" 2 | #include "ModuleCompiler.h" 3 | #include "Stack.h" 4 | #include "Value.h" 5 | #include 6 | 7 | namespace lox { 8 | 9 | // Use a hash table for string interning. 10 | Value *FindStringEntry(LoxBuilder &Builder, Value *Table, Value *String, Value *Length, Value *Hash) { 11 | static auto *FindStringFunction([&Builder] { 12 | auto *const F = Function::Create( 13 | FunctionType::get( 14 | Builder.getPtrTy(), 15 | {Builder.getPtrTy(), Builder.getPtrTy(), Builder.getInt32Ty(), Builder.getInt32Ty()}, 16 | false 17 | ), 18 | Function::InternalLinkage, 19 | "$tableFindString", 20 | Builder.getModule() 21 | ); 22 | 23 | LoxBuilder B(Builder.getContext(), Builder.getModule(), *F); 24 | 25 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 26 | B.SetInsertPoint(EntryBasicBlock); 27 | 28 | auto *const arguments = F->arg_begin(); 29 | auto *const table = arguments; 30 | auto *const string = arguments + 1; 31 | auto *const length = arguments + 2; 32 | auto *const hash = arguments + 3; 33 | 34 | auto *const count = B.CreateLoad(B.getInt32Ty(), B.CreateStructGEP(B.getModule().getTableStructType(), table, 0)); 35 | 36 | auto *const IsEmptyBlock = B.CreateBasicBlock("table.empty"); 37 | auto *const NotEmptyBlock = B.CreateBasicBlock("table.notempty"); 38 | 39 | B.CreateCondBr(B.CreateICmpEQ(B.getInt32(0), count), IsEmptyBlock, NotEmptyBlock); 40 | 41 | B.SetInsertPoint(IsEmptyBlock); 42 | B.CreateRet(B.getNullPtr()); 43 | 44 | B.SetInsertPoint(NotEmptyBlock); 45 | 46 | auto *const index = CreateEntryBlockAlloca(F, B.getInt32Ty(), "index"); 47 | auto *const capacity = B.CreateLoad(B.getInt32Ty(), B.CreateStructGEP(B.getModule().getTableStructType(), table, 1)); 48 | B.CreateStore(B.CreateURem(hash, capacity), index); 49 | 50 | auto *const ForStartBlock = B.CreateBasicBlock("for.start"); 51 | 52 | B.CreateBr(ForStartBlock); 53 | B.SetInsertPoint(ForStartBlock); 54 | auto *const entries = B.CreateLoad(B.getPtrTy(), B.CreateStructGEP(B.getModule().getTableStructType(), table, 2)); 55 | auto *const entry = B.CreateInBoundsGEP(B.getModule().getEntryStructType(), entries, B.CreateLoad(B.getInt32Ty(), index), "entry"); 56 | auto *const entryKey = B.CreateLoad(B.getPtrTy(), B.CreateStructGEP(B.getModule().getEntryStructType(), entry, 0)); 57 | 58 | auto *const KeyIsNullBlock = B.CreateBasicBlock("key.null"); 59 | auto *const KeyIsNotNullBlock = B.CreateBasicBlock("key.notnull"); 60 | auto *const CheckKeyIsSameBlock = B.CreateBasicBlock("key.issame?"); 61 | auto *const EndIfBlock = B.CreateBasicBlock("key.endif"); 62 | 63 | B.CreateCondBr(B.CreateIsNull(entryKey), KeyIsNullBlock, KeyIsNotNullBlock); 64 | 65 | B.SetInsertPoint(KeyIsNullBlock); 66 | 67 | auto *const IsNilBlock = B.CreateBasicBlock("value.isnil"); 68 | 69 | auto *const entryValue = B.CreateLoad(B.getInt64Ty(), B.CreateStructGEP(B.getModule().getEntryStructType(), entry, 1)); 70 | B.CreateCondBr(B.IsNil(entryValue), IsNilBlock, EndIfBlock); 71 | 72 | B.SetInsertPoint(IsNilBlock); 73 | B.CreateRet(B.getNullPtr()); 74 | 75 | B.SetInsertPoint(KeyIsNotNullBlock); 76 | B.CreateBr(CheckKeyIsSameBlock); 77 | 78 | B.SetInsertPoint(CheckKeyIsSameBlock); 79 | 80 | auto *const SameLengthBlock = B.CreateBasicBlock("same.length"); 81 | auto *const SameHashBlock = B.CreateBasicBlock("same.hash"); 82 | auto *const SameStringBlock = B.CreateBasicBlock("same.string"); 83 | 84 | auto *const keyLength = B.CreateLoad(B.getInt32Ty(), B.CreateObjStructGEP(ObjType::STRING, entryKey, 2)); 85 | B.CreateCondBr(B.CreateICmpEQ(keyLength, length), SameLengthBlock, EndIfBlock); 86 | 87 | B.SetInsertPoint(SameLengthBlock); 88 | auto *const keyHash = B.CreateLoad(B.getInt32Ty(), B.CreateObjStructGEP(ObjType::STRING, entryKey, 3)); 89 | B.CreateCondBr(B.CreateICmpEQ(keyHash, hash), SameHashBlock, EndIfBlock); 90 | 91 | B.SetInsertPoint(SameHashBlock); 92 | auto *const keyString = B.CreateLoad(B.getPtrTy(), B.CreateObjStructGEP(ObjType::STRING, entryKey, 1)); 93 | static const auto MemCmp = B.getModule().getOrInsertFunction( 94 | "memcmp", 95 | FunctionType::get(B.getInt32Ty(), {B.getPtrTy(), B.getPtrTy(), B.getInt64Ty()}, false) 96 | ); 97 | B.CreateCondBr(B.CreateICmpEQ(B.CreateCall(MemCmp, {string, keyString, length}), B.getInt32(0)), SameStringBlock, EndIfBlock); 98 | 99 | B.SetInsertPoint(SameStringBlock); 100 | B.CreateRet(B.CreateLoad(B.getPtrTy(), B.CreateStructGEP(B.getModule().getEntryStructType(), entry, 0))); 101 | 102 | B.SetInsertPoint(EndIfBlock); 103 | 104 | // index = (index + 1) & (capacity - 1) === index = index % capacity 105 | B.CreateStore( 106 | B.CreateAnd(B.CreateAdd(B.CreateLoad(B.getInt32Ty(), index), B.getInt32(1), "index+1", true, true), B.CreateSub(capacity, B.getInt32(1), "capacity-1", true, true)), 107 | index 108 | ); 109 | 110 | B.CreateBr(ForStartBlock); 111 | 112 | return F; 113 | }()); 114 | 115 | return Builder.CreateCall(FindStringFunction, {Table, String, Length, Hash}); 116 | } 117 | 118 | static Value *StringHash(LoxBuilder &Builder, Value *String, Value *Length) { 119 | static auto *StrHashFunction([&Builder] { 120 | // FNV-1a hash function. 121 | auto *const F = Function::Create( 122 | FunctionType::get( 123 | Builder.getInt32Ty(), 124 | {Builder.getPtrTy(), Builder.getInt32Ty()}, 125 | false 126 | ), 127 | Function::InternalLinkage, 128 | "$strHash", 129 | Builder.getModule() 130 | ); 131 | 132 | LoxBuilder B(Builder.getContext(), Builder.getModule(), *F); 133 | 134 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 135 | B.SetInsertPoint(EntryBasicBlock); 136 | 137 | auto *const arguments = F->args().begin(); 138 | 139 | auto *const str = arguments; 140 | auto *const len = arguments + 1; 141 | 142 | auto *const hash = CreateEntryBlockAlloca(F, B.getInt32Ty(), "hash"); 143 | auto *const i = CreateEntryBlockAlloca(F, B.getInt32Ty(), "i"); 144 | 145 | B.CreateStore(B.getInt32(-2128831035), hash); 146 | B.CreateStore(B.getInt32(0), i); 147 | 148 | auto *const ForCond = B.CreateBasicBlock("for.cond"); 149 | auto *const ForBody = B.CreateBasicBlock("for.body"); 150 | auto *const ForInc = B.CreateBasicBlock("for.inc"); 151 | auto *const ForEnd = B.CreateBasicBlock("for.end"); 152 | 153 | B.CreateBr(ForCond); 154 | B.SetInsertPoint(ForCond); 155 | B.CreateCondBr(B.CreateICmpSLT(B.CreateLoad(B.getInt32Ty(), i), len), ForBody, ForEnd); 156 | B.SetInsertPoint(ForBody); 157 | 158 | auto *const char_index = B.CreateInBoundsGEP(B.getInt8Ty(), str, B.CreateLoad(B.getInt32Ty(), i)); 159 | auto *const char_ = B.CreateLoad(B.getInt8Ty(), char_index); 160 | auto *const xor_ = B.CreateXor(B.CreateZExt(char_, B.getInt32Ty()), B.CreateLoad(B.getInt32Ty(), hash)); 161 | auto *const mul = B.CreateMul(xor_, B.getInt32(16777619), "xor", true, true); 162 | 163 | B.CreateStore(mul, hash); 164 | 165 | B.CreateBr(ForInc); 166 | B.SetInsertPoint(ForInc); 167 | B.CreateStore(B.CreateAdd(B.CreateLoad(B.getInt32Ty(), i), B.getInt32(1), "i+1", true, true), i); 168 | B.CreateBr(ForCond); 169 | 170 | B.SetInsertPoint(ForEnd); 171 | B.CreateRet(B.CreateLoad(B.getInt32Ty(), hash)); 172 | 173 | return F; 174 | }()); 175 | 176 | return Builder.CreateCall(StrHashFunction, {String, Length}); 177 | } 178 | 179 | Value *LoxBuilder::AllocateString(Value *String, Value *Length, const std::string_view name) { 180 | static auto *AllocateStringFunction([this] { 181 | auto *const F = Function::Create( 182 | FunctionType::get( 183 | getPtrTy(), 184 | {getPtrTy(), getInt32Ty()}, 185 | false 186 | ), 187 | Function::InternalLinkage, 188 | "$allocateString", 189 | getModule() 190 | ); 191 | 192 | LoxBuilder B(getContext(), getModule(), *F); 193 | 194 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 195 | B.SetInsertPoint(EntryBasicBlock); 196 | 197 | auto *const arguments = F->args().begin(); 198 | 199 | auto *const String = arguments; 200 | auto *const Length = arguments + 1; 201 | 202 | auto *const ptr = B.AllocateObj(ObjType::STRING); 203 | 204 | B.CreateStore(String, B.CreateObjStructGEP(ObjType::STRING, ptr, 1)); 205 | B.CreateStore(Length, B.CreateObjStructGEP(ObjType::STRING, ptr, 2)); 206 | B.CreateStore(StringHash(B, String, Length), B.CreateObjStructGEP(ObjType::STRING, ptr, 3)); 207 | B.CreateStore(B.getTrue(), B.CreateObjStructGEP(ObjType::STRING, ptr, 4)); 208 | 209 | B.TableSet(B.CreateLoad(B.getPtrTy(), B.getModule().getRuntimeStrings()), ptr, B.getNilVal()); 210 | 211 | B.CreateRet(ptr); 212 | 213 | return F; 214 | }()); 215 | 216 | return CreateCall(AllocateStringFunction, {String, Length}, name); 217 | } 218 | 219 | Value *LoxBuilder::AllocateString(const StringRef String, const std::string_view name) { 220 | static auto *AllocateStringFunction([this] { 221 | auto *const F = Function::Create( 222 | FunctionType::get( 223 | getPtrTy(), 224 | {getPtrTy(), getInt32Ty(), getInt32Ty()}, 225 | false 226 | ), 227 | Function::InternalLinkage, 228 | "$allocateString", 229 | getModule() 230 | ); 231 | 232 | LoxBuilder B(getContext(), getModule(), *F); 233 | 234 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 235 | B.SetInsertPoint(EntryBasicBlock); 236 | 237 | auto *const arguments = F->args().begin(); 238 | 239 | auto *const String = arguments; 240 | auto *const Length = arguments + 1; 241 | auto *const hash = arguments + 2; 242 | 243 | auto *const interned = FindStringEntry(B, B.CreateLoad(B.getPtrTy(), B.getModule().getRuntimeStrings()), String, Length, hash); 244 | 245 | auto *const IsInternedBlock = B.CreateBasicBlock("is.interned"); 246 | auto *const NotInternedBlock = B.CreateBasicBlock("end"); 247 | 248 | B.CreateCondBr(B.CreateIsNull(interned), NotInternedBlock, IsInternedBlock); 249 | 250 | B.SetInsertPoint(IsInternedBlock); 251 | B.CreateRet(interned); 252 | 253 | B.SetInsertPoint(NotInternedBlock); 254 | 255 | auto *const ptr = B.AllocateObj(ObjType::STRING); 256 | 257 | B.CreateStore(String, B.CreateObjStructGEP(ObjType::STRING, ptr, 1)); 258 | B.CreateStore(Length, B.CreateObjStructGEP(ObjType::STRING, ptr, 2)); 259 | B.CreateStore(hash, B.CreateObjStructGEP(ObjType::STRING, ptr, 3)); 260 | B.CreateStore(B.getFalse(), B.CreateObjStructGEP(ObjType::STRING, ptr, 4)); 261 | 262 | B.TableSet(B.CreateLoad(B.getPtrTy(), B.getModule().getRuntimeStrings()), ptr, B.getNilVal()); 263 | 264 | B.CreateRet(ptr); 265 | 266 | return F; 267 | }()); 268 | 269 | // FNV-1a hash function. 270 | unsigned int hash = -2128831035; 271 | for (const char i: String) { 272 | hash ^= i; 273 | hash *= 16777619; 274 | } 275 | 276 | auto *const ptr = CreateCall(AllocateStringFunction, {CreateGlobalCachedString(String), getInt32(String.size()), getInt32(hash)}, name); 277 | 278 | CreateInvariantStart(ptr, getSizeOf(ObjType::STRING)); 279 | 280 | return ptr; 281 | } 282 | 283 | Value *LoxBuilder::Concat(Value *a, Value *b) { 284 | static auto *ConcatFunction([this] { 285 | auto *const F = Function::Create( 286 | FunctionType::get( 287 | getPtrTy(), 288 | {getInt64Ty(), getInt64Ty()}, 289 | false 290 | ), 291 | Function::InternalLinkage, 292 | "$concat", 293 | getModule() 294 | ); 295 | 296 | LoxBuilder B(getContext(), getModule(), *F); 297 | 298 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 299 | B.SetInsertPoint(EntryBasicBlock); 300 | 301 | auto *const arguments = F->args().begin(); 302 | 303 | auto *const a = B.AsObj(arguments); 304 | auto *const b = B.AsObj(arguments + 1); 305 | 306 | auto *const String0Length = B.CreateLoad(B.getInt32Ty(), B.CreateObjStructGEP(ObjType::STRING, a, 2), "length"); 307 | auto *const String1Length = B.CreateLoad(B.getInt32Ty(), B.CreateObjStructGEP(ObjType::STRING, b, 2), "length"); 308 | auto *const String0String = B.CreateLoad(B.getPtrTy(), B.CreateObjStructGEP(ObjType::STRING, a, 1), "string"); 309 | auto *const String1String = B.CreateLoad(B.getPtrTy(), B.CreateObjStructGEP(ObjType::STRING, b, 1), "string"); 310 | 311 | auto *const NewLength = B.CreateAdd( 312 | String0Length, 313 | String1Length, 314 | "NewLength", 315 | true, 316 | true 317 | ); 318 | 319 | auto *const StringMalloc = B.CreateRealloc( 320 | B.getNullPtr(), 321 | B.CreateSExt(B.CreateAdd(B.getInt32(1), NewLength, "NewLength", true, true), B.getInt64Ty()), "concat string" 322 | ); 323 | 324 | B.CreateMemCpy( 325 | StringMalloc, 326 | Align(8), 327 | String0String, 328 | Align(8), 329 | B.CreateSExt(String0Length, B.getInt64Ty()) 330 | ); 331 | 332 | B.CreateMemCpy( 333 | B.CreateInBoundsGEP( 334 | B.getInt8Ty(), 335 | StringMalloc, 336 | {B.CreateSExt(String0Length, B.getInt64Ty())} 337 | ), 338 | Align(8), 339 | String1String, 340 | Align(8), 341 | B.CreateSExt( 342 | B.CreateAdd( 343 | String1Length, 344 | B.getInt32(1), 345 | "size", 346 | true, 347 | true 348 | ), 349 | B.getInt64Ty() 350 | ) 351 | ); 352 | 353 | auto *const interned = FindStringEntry(B, B.CreateLoad(B.getPtrTy(), B.getModule().getRuntimeStrings()), StringMalloc, NewLength, StringHash(B, StringMalloc, NewLength)); 354 | 355 | auto *const IsInternedBlock = B.CreateBasicBlock("is.interned"); 356 | auto *const NotInternedBlock = B.CreateBasicBlock("end"); 357 | 358 | B.CreateCondBr(B.CreateIsNull(interned), NotInternedBlock, IsInternedBlock); 359 | 360 | B.SetInsertPoint(IsInternedBlock); 361 | // Temporary string not required anymore. 362 | B.IRBuilder::CreateFree(StringMalloc); 363 | B.CreateRet(interned); 364 | 365 | B.SetInsertPoint(NotInternedBlock); 366 | 367 | auto *const NewString = B.AllocateString( 368 | StringMalloc, 369 | NewLength, 370 | "NewString" 371 | ); 372 | 373 | B.CreateRet(NewString); 374 | 375 | return F; 376 | }()); 377 | 378 | auto *const ptr = CreateCall(ConcatFunction, {a, b}); 379 | 380 | CreateInvariantStart(ptr, getSizeOf(ObjType::STRING)); 381 | 382 | return ptr; 383 | } 384 | }// namespace lox 385 | -------------------------------------------------------------------------------- /src/compiler/Table.h: -------------------------------------------------------------------------------- 1 | #ifndef TABLE_H 2 | #define TABLE_H 3 | #include "LoxBuilder.h" 4 | #include 5 | 6 | namespace lox { 7 | constexpr bool DEBUG_TABLE_ENTRIES = false; 8 | void IterateTable(LoxBuilder &Builder, Value *Table, Function *FunctionPtr); 9 | Value *TableDelete(LoxBuilder &Builder, Value *Table, Value *Key); 10 | } 11 | #endif -------------------------------------------------------------------------------- /src/compiler/Upvalue.cpp: -------------------------------------------------------------------------------- 1 | #include "Upvalue.h" 2 | #include "../Debug.h" 3 | #include "FunctionCompiler.h" 4 | 5 | #define DEBUG false 6 | 7 | namespace lox { 8 | Value *LoxBuilder::AllocateUpvalue(Value *value) { 9 | const auto ptr = AllocateObj(ObjType::UPVALUE); 10 | 11 | CreateStore(value, CreateObjStructGEP(ObjType::UPVALUE, ptr, 1)); 12 | CreateStore(getNullPtr(), CreateObjStructGEP(ObjType::UPVALUE, ptr, 2)); 13 | CreateStore(getNilVal(), CreateObjStructGEP(ObjType::UPVALUE, ptr, 3)); 14 | 15 | return ptr; 16 | } 17 | 18 | Value *FunctionCompiler::captureLocal(Value *local) { 19 | static auto *CaptureLocalFunction([this] { 20 | auto *const F = Function::Create( 21 | FunctionType::get( 22 | Builder.getPtrTy(), 23 | {Builder.getPtrTy()}, 24 | false 25 | ), 26 | Function::InternalLinkage, 27 | "$captureLocal", 28 | Builder.getModule() 29 | ); 30 | 31 | LoxBuilder B(Builder.getContext(), Builder.getModule(), *F); 32 | 33 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 34 | B.SetInsertPoint(EntryBasicBlock); 35 | 36 | auto *const arguments = F->args().begin(); 37 | 38 | auto *const local = arguments; 39 | 40 | auto *const openUpvalues = B.getModule().getOpenUpvalues(); 41 | auto *const upvalue = CreateEntryBlockAlloca(B.getFunction(), B.getPtrTy(), "upvalue"); 42 | B.CreateStore(B.CreateLoad(B.getPtrTy(), openUpvalues), upvalue); 43 | 44 | auto *const WhileCond = B.CreateBasicBlock("while.cond"); 45 | auto *const WhileBody = B.CreateBasicBlock("while.body"); 46 | auto *const WhileEnd = B.CreateBasicBlock("while.end"); 47 | 48 | B.CreateBr(WhileCond); 49 | B.SetInsertPoint(WhileCond); 50 | B.CreateCondBr(B.CreateIsNotNull(B.CreateLoad(B.getPtrTy(), upvalue)), WhileBody, WhileEnd); 51 | B.SetInsertPoint(WhileBody); 52 | { 53 | auto *const IsSameBlock1 = B.CreateBasicBlock("IsSame1"); 54 | auto *const INotsSameBlock1 = B.CreateBasicBlock("NotIsSame1"); 55 | 56 | auto *const upvalueLocation = B.CreateLoad( 57 | B.getPtrTy(), 58 | B.CreateObjStructGEP(ObjType::UPVALUE, B.CreateLoad(B.getPtrTy(), upvalue), 1, "location") 59 | ); 60 | 61 | B.CreateCondBr( 62 | B.CreateICmpEQ( 63 | B.getInt64(0), B.CreatePtrDiff(B.getPtrTy(), upvalueLocation, local) 64 | ), 65 | IsSameBlock1, 66 | INotsSameBlock1 67 | ); 68 | 69 | B.SetInsertPoint(IsSameBlock1); 70 | { 71 | B.CreateRet(B.CreateLoad(B.getPtrTy(), upvalue)); 72 | } 73 | 74 | B.SetInsertPoint(INotsSameBlock1); 75 | { 76 | LoadInst *ptr = B.CreateLoad(B.getPtrTy(), upvalue); 77 | 78 | B.CreateStore( 79 | B.CreateLoad( 80 | B.getPtrTy(), 81 | B.CreateObjStructGEP(ObjType::UPVALUE, ptr, 2, "next") 82 | ), 83 | upvalue 84 | ); 85 | B.CreateBr(WhileCond); 86 | } 87 | } 88 | B.SetInsertPoint(WhileEnd); 89 | 90 | auto *const upvaluePtr = B.AllocateUpvalue(local); 91 | 92 | B.CreateStore( 93 | B.CreateLoad(B.getPtrTy(), openUpvalues), 94 | B.CreateObjStructGEP(ObjType::UPVALUE, upvaluePtr, 2, "next") 95 | ); 96 | 97 | B.CreateStore( 98 | upvaluePtr, 99 | openUpvalues 100 | ); 101 | 102 | B.CreateRet(upvaluePtr); 103 | 104 | return F; 105 | }()); 106 | 107 | return Builder.CreateCall(CaptureLocalFunction, {local}); 108 | } 109 | 110 | void closeUpvalues(LoxBuilder &Builder, Value *local) { 111 | static auto *CloseUpvalueFunction([&Builder] { 112 | auto *const F = Function::Create( 113 | FunctionType::get( 114 | Builder.getVoidTy(), 115 | {Builder.getPtrTy()}, 116 | false 117 | ), 118 | Function::InternalLinkage, 119 | "$closeUpvalue", 120 | Builder.getModule() 121 | ); 122 | 123 | LoxBuilder B(Builder.getContext(), Builder.getModule(), *F); 124 | 125 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 126 | B.SetInsertPoint(EntryBasicBlock); 127 | 128 | auto *const arguments = F->args().begin(); 129 | auto *const local = arguments; 130 | 131 | auto *const openUpvalues = B.getModule().getOpenUpvalues(); 132 | auto *const upvalue = CreateEntryBlockAlloca(F, B.getPtrTy(), "upvalue"); 133 | auto *const previous = CreateEntryBlockAlloca(F, B.getPtrTy(), "previous"); 134 | 135 | if constexpr (DEBUG_UPVALUES) { 136 | B.PrintF({B.CreateGlobalCachedString("closing upvalue(%p)\n"), local}); 137 | B.PrintF({B.CreateGlobalCachedString("openUpvalues = %p\n"), B.CreateLoad(B.getPtrTy(), openUpvalues)}); 138 | } 139 | 140 | B.CreateStore(B.CreateLoad(B.getPtrTy(), openUpvalues), upvalue); 141 | B.CreateStore(B.getNullPtr(), previous); 142 | 143 | auto *const WhileCond = B.CreateBasicBlock("while.cond"); 144 | auto *const WhileBody = B.CreateBasicBlock("while.body"); 145 | auto *const WhileEnd = B.CreateBasicBlock("while.end"); 146 | 147 | 148 | B.CreateBr(WhileCond); 149 | B.SetInsertPoint(WhileCond); 150 | B.CreateCondBr(B.CreateIsNotNull(B.CreateLoad(B.getPtrTy(), upvalue)), WhileBody, WhileEnd); 151 | B.SetInsertPoint(WhileBody); 152 | { 153 | auto *const FoundBlock = B.CreateBasicBlock("IsSame1"); 154 | auto *const ContinueBlock = B.CreateBasicBlock("NotIsSame1"); 155 | 156 | auto *const upvalueLocation = B.CreateLoad( 157 | B.getPtrTy(), 158 | B.CreateObjStructGEP(ObjType::UPVALUE, B.CreateLoad(B.getPtrTy(), upvalue), 1, "location") 159 | ); 160 | 161 | if constexpr (DEBUG_UPVALUES) { 162 | B.PrintF({B.CreateGlobalCachedString("current = ")}); 163 | B.Print(B.ObjVal(B.CreateLoad(B.getPtrTy(), upvalue))); 164 | B.PrintF({B.CreateGlobalCachedString("%p == %p?\n"), upvalueLocation, local}); 165 | } 166 | 167 | B.CreateCondBr( 168 | B.CreateICmpEQ( 169 | B.getInt64(0), B.CreatePtrDiff(B.getPtrTy(), upvalueLocation, local) 170 | ), 171 | FoundBlock, 172 | ContinueBlock 173 | ); 174 | 175 | B.SetInsertPoint(FoundBlock); 176 | 177 | auto *const foundUpvalue = B.CreateLoad(B.getPtrTy(), upvalue); 178 | auto *const closed = B.CreateObjStructGEP(ObjType::UPVALUE, foundUpvalue, 3, "closed"); 179 | auto *const loc = B.CreateObjStructGEP(ObjType::UPVALUE, foundUpvalue, 1, "loc"); 180 | 181 | if constexpr (DEBUG_UPVALUES) { 182 | B.PrintF({B.CreateGlobalCachedString("before = ")}); 183 | B.Print(B.ObjVal(B.CreateLoad(B.getPtrTy(), upvalue))); 184 | } 185 | 186 | // Close the upvalue by copying the value from the current location. 187 | B.CreateStore( 188 | B.CreateLoad(Builder.getInt64Ty(), B.CreateLoad(Builder.getPtrTy(), loc)), 189 | closed 190 | ); 191 | 192 | // And then setting the new location to the "closed" field. 193 | B.CreateStore( 194 | closed, 195 | B.CreateObjStructGEP(ObjType::UPVALUE, B.CreateLoad(Builder.getPtrTy(), upvalue), 1, "loc") 196 | ); 197 | 198 | if constexpr (DEBUG_UPVALUES) { 199 | // The upvalue should now be closed: the location value 200 | // should point to the value inside its own struct. 201 | B.PrintF({B.CreateGlobalCachedString("after = ")}); 202 | B.Print(B.ObjVal(B.CreateLoad(B.getPtrTy(), upvalue))); 203 | } 204 | 205 | // Remove the closed upvalue from the open upvalues list. 206 | auto *const IsFirstBlock = B.CreateBasicBlock("first"); 207 | auto *const IsFirstElseBlock = B.CreateBasicBlock("first.else"); 208 | auto *const EndIsFirstBlock = B.CreateBasicBlock("first.end"); 209 | B.CreateCondBr(B.CreateIsNull(B.CreateLoad(B.getPtrTy(), previous)), IsFirstBlock, IsFirstElseBlock); 210 | B.SetInsertPoint(IsFirstBlock); 211 | { 212 | // openupvalues = upvalue->next 213 | B.CreateStore(B.CreateLoad(B.getPtrTy(), B.CreateObjStructGEP(ObjType::UPVALUE, B.CreateLoad(B.getPtrTy(), upvalue), 2, "next")), openUpvalues); 214 | 215 | B.CreateBr(EndIsFirstBlock); 216 | } 217 | B.SetInsertPoint(IsFirstElseBlock); 218 | { 219 | // previous->next = upvalue->next; 220 | B.CreateStore( 221 | B.CreateLoad( 222 | B.getPtrTy(), 223 | B.CreateObjStructGEP(ObjType::UPVALUE, B.CreateLoad(B.getPtrTy(), upvalue), 2, "next") 224 | ), 225 | B.CreateObjStructGEP(ObjType::UPVALUE, previous, 2, "next") 226 | ); 227 | 228 | B.CreateBr(EndIsFirstBlock); 229 | } 230 | B.SetInsertPoint(EndIsFirstBlock); 231 | { 232 | B.CreateStore( 233 | B.CreateLoad( 234 | B.getPtrTy(), 235 | B.CreateObjStructGEP(ObjType::UPVALUE, B.CreateLoad(B.getPtrTy(), upvalue), 2, "next") 236 | ), 237 | upvalue 238 | ); 239 | 240 | B.CreateBr(WhileCond); 241 | } 242 | B.SetInsertPoint(ContinueBlock); 243 | { 244 | B.CreateStore(B.CreateLoad(B.getPtrTy(), upvalue), previous); 245 | B.CreateStore( 246 | B.CreateLoad( 247 | B.getPtrTy(), 248 | B.CreateObjStructGEP(ObjType::UPVALUE, B.CreateLoad(B.getPtrTy(), upvalue), 2, "next") 249 | ), 250 | upvalue 251 | ); 252 | 253 | B.CreateBr(WhileCond); 254 | } 255 | } 256 | B.SetInsertPoint(WhileEnd); 257 | 258 | if constexpr (DEBUG_UPVALUES) { 259 | B.PrintF({B.CreateGlobalCachedString("openUpvalues end = %p\n"), B.CreateLoad(B.getPtrTy(), openUpvalues)}); 260 | } 261 | 262 | B.CreateRetVoid(); 263 | 264 | return F; 265 | }()); 266 | 267 | Builder.CreateCall(CloseUpvalueFunction, {local}); 268 | } 269 | 270 | void IterateUpvalues(LoxBuilder &Builder, Function *FunctionPointer) { 271 | static auto *IterateUpvaluesFunction([&Builder] { 272 | auto *const F = Function::Create( 273 | FunctionType::get( 274 | Builder.getVoidTy(), 275 | {Builder.getPtrTy()}, 276 | false 277 | ), 278 | Function::InternalLinkage, 279 | "$iterateUpvalues", 280 | Builder.getModule() 281 | ); 282 | 283 | LoxBuilder B(Builder.getContext(), Builder.getModule(), *F); 284 | 285 | auto *const EntryBasicBlock = B.CreateBasicBlock("entry"); 286 | B.SetInsertPoint(EntryBasicBlock); 287 | 288 | if constexpr (DEBUG_LOG_GC) { 289 | B.PrintString("--iterate upvalues--"); 290 | } 291 | 292 | auto *const openUpvalues = B.getModule().getOpenUpvalues(); 293 | auto *const upvalue = CreateEntryBlockAlloca(B.getFunction(), B.getPtrTy(), "upvalue"); 294 | B.CreateStore(B.CreateLoad(B.getPtrTy(), openUpvalues), upvalue); 295 | 296 | auto *const WhileCond = B.CreateBasicBlock("while.cond"); 297 | auto *const WhileBody = B.CreateBasicBlock("while.body"); 298 | auto *const WhileEnd = B.CreateBasicBlock("while.end"); 299 | 300 | B.CreateBr(WhileCond); 301 | B.SetInsertPoint(WhileCond); 302 | B.CreateCondBr(B.CreateIsNotNull(B.CreateLoad(B.getPtrTy(), upvalue)), WhileBody, WhileEnd); 303 | B.SetInsertPoint(WhileBody); 304 | { 305 | auto *const ptr = B.CreateLoad(B.getPtrTy(), upvalue); 306 | B.CreateCall( 307 | FunctionType::get(B.getVoidTy(), {B.getPtrTy()}, false), 308 | F->arg_begin(), 309 | {ptr} 310 | ); 311 | 312 | B.CreateStore( 313 | B.CreateLoad( 314 | B.getPtrTy(), 315 | B.CreateObjStructGEP(ObjType::UPVALUE, ptr, 2, "next") 316 | ), 317 | upvalue 318 | ); 319 | B.CreateBr(WhileCond); 320 | } 321 | B.SetInsertPoint(WhileEnd); 322 | 323 | B.CreateRetVoid(); 324 | 325 | return F; 326 | }()); 327 | 328 | Builder.CreateCall(IterateUpvaluesFunction, {FunctionPointer}); 329 | } 330 | }// namespace lox 331 | -------------------------------------------------------------------------------- /src/compiler/Upvalue.h: -------------------------------------------------------------------------------- 1 | #ifndef CPPLOX_UPVALUE_H 2 | #define CPPLOX_UPVALUE_H 3 | 4 | #include "LoxBuilder.h" 5 | 6 | 7 | constexpr bool DEBUG_UPVALUES = false; 8 | 9 | namespace lox { 10 | struct Upvalue { 11 | unsigned long index; 12 | Value *value; 13 | bool isLocal; 14 | }; 15 | 16 | void closeUpvalues(LoxBuilder &Builder, Value *local); 17 | void IterateUpvalues(LoxBuilder &Builder, Function *FunctionPointer); 18 | }// namespace lox 19 | #endif//CPPLOX_UPVALUE_H 20 | -------------------------------------------------------------------------------- /src/compiler/Value.h: -------------------------------------------------------------------------------- 1 | #ifndef OBJECT_H 2 | #define OBJECT_H 3 | 4 | #include 5 | 6 | constexpr uint64_t SIGN_BIT = 0x8000000000000000; 7 | constexpr uint64_t QNAN = 0x7ffc000000000000; 8 | 9 | constexpr uint64_t TAG_UNINITIALIZED = 0; 10 | constexpr uint64_t TAG_NIL = 1; 11 | constexpr uint64_t TAG_FALSE = 2; 12 | constexpr uint64_t TAG_TRUE = 3; 13 | 14 | constexpr uint64_t FALSE_VAL = QNAN | TAG_FALSE; 15 | constexpr uint64_t TRUE_VAL = QNAN | TAG_TRUE; 16 | constexpr uint64_t NIL_VAL = QNAN | TAG_NIL; 17 | constexpr uint64_t UNINITIALIZED_VAL = QNAN | TAG_UNINITIALIZED; 18 | 19 | namespace lox { 20 | enum class ObjType { 21 | STRING = 1, 22 | FUNCTION = 2, 23 | CLOSURE = 3, 24 | UPVALUE = 4, 25 | CLASS = 5, 26 | INSTANCE = 6, 27 | BOUND_METHOD = 7, 28 | }; 29 | } 30 | 31 | #endif//OBJECT_H 32 | -------------------------------------------------------------------------------- /src/frontend/AST.h: -------------------------------------------------------------------------------- 1 | #ifndef LOX_LLVM_AST_H 2 | #define LOX_LLVM_AST_H 3 | 4 | #include "../Util.h" 5 | #include "Token.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace lox { 14 | 15 | enum class UnaryOp { 16 | BANG = TokenType::BANG, 17 | MINUS = TokenType::MINUS 18 | }; 19 | 20 | enum class BinaryOp { 21 | PLUS = TokenType::PLUS, 22 | MINUS = TokenType::MINUS, 23 | SLASH = TokenType::SLASH, 24 | STAR = TokenType::STAR, 25 | GREATER = TokenType::GREATER, 26 | GREATER_EQUAL = TokenType::GREATER_EQUAL, 27 | LESS = TokenType::LESS, 28 | LESS_EQUAL = TokenType::LESS_EQUAL, 29 | BANG_EQUAL = TokenType::BANG_EQUAL, 30 | EQUAL_EQUAL = TokenType::EQUAL_EQUAL 31 | }; 32 | 33 | enum class LogicalOp { 34 | OR = TokenType::OR, 35 | AND = TokenType::AND 36 | }; 37 | 38 | enum class LoxFunctionType { 39 | NONE, 40 | FUNCTION, 41 | INITIALIZER, 42 | METHOD 43 | }; 44 | 45 | struct BinaryExpr; 46 | struct CallExpr; 47 | struct GetExpr; 48 | struct SetExpr; 49 | struct ThisExpr; 50 | struct SuperExpr; 51 | struct GroupingExpr; 52 | struct LiteralExpr; 53 | struct LogicalExpr; 54 | struct UnaryExpr; 55 | struct VarExpr; 56 | struct AssignExpr; 57 | 58 | using BinaryExprPtr = std::unique_ptr; 59 | using CallExprPtr = std::unique_ptr; 60 | using GetExprPtr = std::unique_ptr; 61 | using SetExprPtr = std::unique_ptr; 62 | using ThisExprPtr = std::unique_ptr; 63 | using SuperExprPtr = std::unique_ptr; 64 | using GroupingExprPtr = std::unique_ptr; 65 | using LiteralExprPtr = std::unique_ptr; 66 | using LogicalExprPtr = std::unique_ptr; 67 | using UnaryExprPtr = std::unique_ptr; 68 | using VarExprPtr = std::unique_ptr; 69 | using AssignExprPtr = std::unique_ptr; 70 | 71 | using Expr = std::variant< 72 | BinaryExprPtr, 73 | CallExprPtr, 74 | GetExprPtr, 75 | SetExprPtr, 76 | ThisExprPtr, 77 | SuperExprPtr, 78 | GroupingExprPtr, 79 | LiteralExprPtr, 80 | LogicalExprPtr, 81 | UnaryExprPtr, 82 | VarExprPtr, 83 | AssignExprPtr>; 84 | 85 | struct Assignable : private Uncopyable { 86 | Token name; 87 | mutable signed long distance = -1; 88 | mutable bool isCaptured = false; 89 | explicit Assignable(const Token &name) : name{name} { 90 | } 91 | }; 92 | 93 | struct BinaryExpr final : private Uncopyable { 94 | Expr left; 95 | Token token; 96 | BinaryOp op; 97 | Expr right; 98 | explicit BinaryExpr(Expr left, const Token &token, const BinaryOp op, Expr right) 99 | : left(std::move(left)), token{token}, op{op}, right{std::move(right)} {} 100 | }; 101 | 102 | struct CallExpr : private Uncopyable { 103 | Expr callee; 104 | Token keyword; 105 | std::vector arguments; 106 | explicit CallExpr(Expr callee, const Token &keyword, std::vector arguments) 107 | : callee{std::move(callee)}, keyword{keyword}, arguments{std::move(arguments)} {} 108 | }; 109 | 110 | struct GetExpr : private Uncopyable { 111 | Expr object; 112 | Token name; 113 | explicit GetExpr(Expr object, const Token &name) 114 | : object{std::move(object)}, name{name} {} 115 | }; 116 | 117 | struct SetExpr : Uncopyable { 118 | Expr object; 119 | Token name; 120 | Expr value; 121 | explicit SetExpr(Expr object, const Token &name, Expr value) 122 | : object{std::move(object)}, name{name}, value{std::move(value)} {} 123 | }; 124 | 125 | struct ThisExpr : Assignable { 126 | explicit ThisExpr(const Token &name) : Assignable(name) {} 127 | }; 128 | 129 | struct SuperExpr : Assignable { 130 | Token method; 131 | explicit SuperExpr(const Token &name, const Token &method) 132 | : Assignable(name), method{method} {} 133 | }; 134 | 135 | struct UnaryExpr : Uncopyable { 136 | Token token; 137 | UnaryOp op; 138 | Expr expression; 139 | explicit UnaryExpr(const Token &token, const UnaryOp op, Expr expression) 140 | : token{token}, op{op}, expression{std::move(expression)} {} 141 | }; 142 | 143 | struct GroupingExpr : Uncopyable { 144 | Expr expression; 145 | explicit GroupingExpr(Expr expression) : expression{std::move(expression)} {} 146 | }; 147 | 148 | struct LiteralExpr : Uncopyable { 149 | Literal literal; 150 | explicit LiteralExpr(const Literal &literal) : literal{literal} {} 151 | }; 152 | 153 | struct LogicalExpr : Uncopyable { 154 | Expr left; 155 | LogicalOp op; 156 | Expr right; 157 | explicit LogicalExpr(Expr left, const LogicalOp op, Expr right) 158 | : left{std::move(left)}, op{op}, right{std::move(right)} {} 159 | }; 160 | 161 | struct VarExpr : Assignable { 162 | explicit VarExpr(const Token &name) : Assignable(name) {} 163 | }; 164 | 165 | struct AssignExpr : Assignable { 166 | Expr value; 167 | AssignExpr(const Token &name, Expr value) : Assignable(name), value{std::move(value)} {} 168 | }; 169 | 170 | struct ExpressionStmt; 171 | struct FunctionStmt; 172 | struct ReturnStmt; 173 | struct IfStmt; 174 | struct PrintStmt; 175 | struct VarStmt; 176 | struct BlockStmt; 177 | struct WhileStmt; 178 | struct ClassStmt; 179 | 180 | using ExpressionStmtPtr = std::shared_ptr; 181 | using FunctionStmtPtr = std::shared_ptr; 182 | using ReturnStmtPtr = std::shared_ptr; 183 | using IfStmtPtr = std::shared_ptr; 184 | using PrintStmtPtr = std::shared_ptr; 185 | using VarStmtPtr = std::shared_ptr; 186 | using BlockStmtPtr = std::shared_ptr; 187 | using WhileStmtPtr = std::shared_ptr; 188 | using ClassStmtPtr = std::shared_ptr; 189 | 190 | using Stmt = std::variant< 191 | ExpressionStmtPtr, 192 | FunctionStmtPtr, 193 | ReturnStmtPtr, 194 | IfStmtPtr, 195 | PrintStmtPtr, 196 | VarStmtPtr, 197 | BlockStmtPtr, 198 | WhileStmtPtr, 199 | ClassStmtPtr>; 200 | 201 | using StmtList = std::vector; 202 | 203 | struct ExpressionStmt : Uncopyable { 204 | Expr expression; 205 | explicit ExpressionStmt(Expr expression) : expression{std::move(expression)} {} 206 | }; 207 | 208 | struct IfStmt : Uncopyable { 209 | Expr condition; 210 | Stmt thenBranch; 211 | std::optional elseBranch; 212 | explicit IfStmt(Expr condition, Stmt thenBranch, std::optional elseBranch) 213 | : condition{std::move(condition)}, thenBranch{std::move(thenBranch)}, elseBranch{std::move(elseBranch)} {} 214 | }; 215 | 216 | struct FunctionStmt : Uncopyable { 217 | Token name; 218 | LoxFunctionType type; 219 | std::vector parameters; 220 | StmtList body; 221 | explicit FunctionStmt(const Token &name, const LoxFunctionType type, std::vector parameters, StmtList body) 222 | : name{name}, type{type}, parameters{std::move(parameters)}, body{std::move(body)} {} 223 | }; 224 | 225 | struct ReturnStmt : Uncopyable { 226 | Token keyword; 227 | std::optional expression; 228 | explicit ReturnStmt(const Token &keyword, std::optional expression) 229 | : keyword{keyword}, expression{std::move(expression)} {} 230 | }; 231 | 232 | struct PrintStmt : Uncopyable { 233 | Expr expression; 234 | explicit PrintStmt(Expr expression) : expression{std::move(expression)} {} 235 | }; 236 | 237 | struct VarStmt : Uncopyable { 238 | Token name; 239 | Expr initializer; 240 | explicit VarStmt(const Token &name, Expr initializer) : name{name}, initializer{std::move(initializer)} {} 241 | }; 242 | 243 | struct BlockStmt : Uncopyable { 244 | StmtList statements; 245 | explicit BlockStmt(StmtList statements) 246 | : statements{std::move(statements)} {} 247 | }; 248 | 249 | struct WhileStmt : Uncopyable { 250 | Expr condition; 251 | Stmt body; 252 | WhileStmt(Expr condition, Stmt body) 253 | : condition{std::move(condition)}, 254 | body{std::move(body)} {} 255 | }; 256 | 257 | struct ClassStmt { 258 | Token name; 259 | std::optional super_class; 260 | std::vector methods; 261 | ClassStmt(const Token &name, std::optional super_class, std::vector methods) 262 | : name{name}, 263 | super_class{std::move(super_class)}, 264 | methods{std::move(methods)} {} 265 | }; 266 | 267 | using Program = std::vector; 268 | 269 | }// namespace lox 270 | 271 | #endif//LOX_LLVM_AST_H 272 | -------------------------------------------------------------------------------- /src/frontend/Error.h: -------------------------------------------------------------------------------- 1 | #ifndef LOX_LLVM_ERROR_H 2 | #define LOX_LLVM_ERROR_H 3 | 4 | #include "Token.h" 5 | #include 6 | #include 7 | 8 | namespace lox { 9 | static bool hadError = false; 10 | static bool hadRuntimeError = false; 11 | 12 | static void report(const long unsigned int line, const std::string_view where, const std::string_view message) { 13 | std::cerr << "[line " << line << "] Error" << where << ": " << message << "\n"; 14 | hadError = true; 15 | } 16 | 17 | static void error(const long unsigned int line, const std::string_view message) { 18 | report(line, "", message); 19 | } 20 | 21 | static void error(const Token &token, const std::string_view message) { 22 | if (token.getType() == END) { 23 | report(token.getLine(), " at end", message); 24 | } else { 25 | report(token.getLine(), " at '" + std::string(token.getLexeme()) + "'", message); 26 | } 27 | } 28 | 29 | struct runtime_error final : std::runtime_error { 30 | Token token; 31 | explicit runtime_error(const Token &token, const std::string &message) : std::runtime_error(message), token{token} {} 32 | }; 33 | 34 | static void runtimeError(const runtime_error &error) { 35 | std::cerr << error.what() << "\n[line " << error.token.getLine() << "]"; 36 | hadRuntimeError = true; 37 | } 38 | 39 | 40 | }// namespace lox 41 | #endif//LOX_LLVM_ERROR_H 42 | -------------------------------------------------------------------------------- /src/frontend/Parser.h: -------------------------------------------------------------------------------- 1 | #ifndef PARSER_H 2 | #define PARSER_H 3 | 4 | #include "../frontend/AST.h" 5 | #include "Error.h" 6 | #include "Token.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std::literals::string_literals; 13 | 14 | namespace lox { 15 | class ParseError final : public std::runtime_error { 16 | public: 17 | explicit ParseError(const std::string &arg) : runtime_error(arg) {} 18 | }; 19 | 20 | class Parser { 21 | public: 22 | explicit Parser(const std::vector &tokens) : tokens{tokens} {} 23 | 24 | Program parse() { 25 | auto program = Program(); 26 | try { 27 | while (!isAtEnd()) { 28 | if (auto decl = declaration(); decl.has_value()) { 29 | program.push_back(std::move(decl.value())); 30 | } 31 | } 32 | } catch (const std::invalid_argument &e) { 33 | std::cout << "error: " << e.what() << "\n" 34 | << std::flush; 35 | } 36 | return program; 37 | } 38 | 39 | private: 40 | std::vector tokens; 41 | int current = 0; 42 | 43 | using parserFn = Expr (Parser::*)(); 44 | 45 | std::optional declaration() { 46 | try { 47 | if (match(CLASS)) return classDeclaration(); 48 | if (match(VAR)) return varDeclaration(); 49 | if (match(FUN)) return function(LoxFunctionType::FUNCTION); 50 | 51 | return std::make_optional(statement()); 52 | } catch (ParseError &) { 53 | synchronize(); 54 | return std::make_optional(); 55 | } 56 | } 57 | 58 | ClassStmtPtr classDeclaration() { 59 | auto name = consume(IDENTIFIER, "Expect class name."); 60 | 61 | std::optional superclass; 62 | if (match(LESS)) { 63 | consume(IDENTIFIER, "Expect superclass name."); 64 | superclass = std::make_unique(previous()); 65 | } 66 | 67 | consume(LEFT_BRACE, "Expect '{' before class body."); 68 | 69 | std::vector methods; 70 | while (!check(RIGHT_BRACE) && !isAtEnd()) { 71 | methods.push_back(function(LoxFunctionType::METHOD)); 72 | } 73 | 74 | consume(RIGHT_BRACE, "Expect '}' after class body."); 75 | 76 | return std::make_shared(name, std::move(superclass), std::move(methods)); 77 | } 78 | 79 | VarStmtPtr varDeclaration() { 80 | const Token name = consume(IDENTIFIER, "Expect variable name."); 81 | Expr initializer = match(EQUAL) ? expression() : std::make_unique(nullptr); 82 | consume(SEMICOLON, "Expect ';' after variable declaration."); 83 | return std::make_shared(name, std::move(initializer)); 84 | } 85 | 86 | WhileStmtPtr whileStatement() { 87 | consume(LEFT_PAREN, "Expect '(' after 'while'."); 88 | Expr condition = expression(); 89 | consume(RIGHT_PAREN, "Expect ')' after condition."); 90 | Stmt body = statement(); 91 | 92 | return std::make_shared(std::move(condition), std::move(body)); 93 | } 94 | 95 | Stmt statement() { 96 | if (match(PRINT)) return printStatement(); 97 | if (match(RETURN)) return returnStatement(); 98 | if (match(WHILE)) return whileStatement(); 99 | if (match(FOR)) return forStatement(); 100 | if (match(IF)) return ifStatement(); 101 | if (match(LEFT_BRACE)) return std::make_shared(block()); 102 | 103 | return expressionStatement(); 104 | } 105 | 106 | Stmt forStatement() { 107 | consume(LEFT_PAREN, "Expect '(' after 'for'."); 108 | 109 | std::optional initializer; 110 | if (match(SEMICOLON)) { 111 | initializer = {}; 112 | } else if (match(VAR)) { 113 | initializer = varDeclaration(); 114 | } else { 115 | initializer = expressionStatement(); 116 | } 117 | 118 | std::optional condition = {}; 119 | if (!check(SEMICOLON)) { 120 | condition = expression(); 121 | } 122 | consume(SEMICOLON, "Expect ';' after loop condition."); 123 | 124 | std::optional increment = {}; 125 | if (!check(RIGHT_PAREN)) { 126 | increment = expression(); 127 | } 128 | consume(RIGHT_PAREN, "Expect ')' after for clauses."); 129 | 130 | Stmt body = statement(); 131 | 132 | if (increment.has_value()) { 133 | StmtList statements; 134 | statements.push_back(std::move(body)); 135 | statements.emplace_back(std::make_shared(std::move(increment.value()))); 136 | body = std::make_shared(std::move(statements)); 137 | } 138 | 139 | if (!condition.has_value()) { 140 | condition = std::make_unique(true); 141 | } 142 | 143 | body = std::make_shared(std::move(condition.value()), std::move(body)); 144 | 145 | if (initializer.has_value()) { 146 | StmtList b; 147 | b.push_back(std::move(initializer.value())); 148 | b.push_back(std::move(body)); 149 | body = std::make_shared(std::move(b)); 150 | } 151 | 152 | return body; 153 | } 154 | 155 | PrintStmtPtr printStatement() { 156 | Expr value = expression(); 157 | consume(SEMICOLON, "Expect ';' after value."); 158 | return std::make_shared(std::move(value)); 159 | } 160 | 161 | IfStmtPtr ifStatement() { 162 | consume(LEFT_PAREN, "Expect '(' after 'if'."); 163 | auto condition = expression(); 164 | consume(RIGHT_PAREN, "Expect ')' after if condition."); 165 | 166 | auto thenBranch = statement(); 167 | std::optional elseBranch = {}; 168 | if (match(ELSE)) { 169 | elseBranch = statement(); 170 | } 171 | 172 | return std::make_shared(std::move(condition), std::move(thenBranch), std::move(elseBranch)); 173 | } 174 | 175 | ExpressionStmtPtr expressionStatement() { 176 | Expr expr = expression(); 177 | consume(SEMICOLON, "Expect ';' after expression."); 178 | return std::make_shared(std::move(expr)); 179 | } 180 | 181 | FunctionStmtPtr function(const LoxFunctionType type) { 182 | const auto kind = type == LoxFunctionType::FUNCTION ? "function"s : "method"s; 183 | Token name = consume(IDENTIFIER, "Expect " + kind + " name."); 184 | consume(LEFT_PAREN, "Expect '(' after " + kind + " name."); 185 | std::vector parameters; 186 | if (!check(RIGHT_PAREN)) { 187 | do { 188 | if (parameters.size() >= 255) { 189 | error(peek(), "Can't have more than 255 parameters."); 190 | } 191 | 192 | parameters.push_back(consume(IDENTIFIER, "Expect parameter name.")); 193 | } while (match(COMMA)); 194 | } 195 | consume(RIGHT_PAREN, "Expect ')' after parameters."); 196 | consume(LEFT_BRACE, "Expect '{' before " + kind + " body."); 197 | StmtList body = block(); 198 | 199 | return std::make_shared( 200 | name, 201 | type == LoxFunctionType::METHOD && name.getLexeme() == "init" ? LoxFunctionType::INITIALIZER : type, 202 | parameters, 203 | std::move(body) 204 | ); 205 | } 206 | 207 | ReturnStmtPtr returnStatement() { 208 | Token keyword = previous(); 209 | std::optional value; 210 | if (!check(SEMICOLON)) { 211 | value = expression(); 212 | } 213 | 214 | consume(SEMICOLON, "Expect ';' after return value."); 215 | return std::make_shared(keyword, std::move(value)); 216 | } 217 | 218 | StmtList block() { 219 | std::vector statements; 220 | 221 | while (!check(RIGHT_BRACE) && !isAtEnd()) { 222 | if (auto decl = declaration(); decl.has_value()) { 223 | statements.push_back(std::move(decl.value())); 224 | } 225 | } 226 | 227 | consume(RIGHT_BRACE, "Expect '}' after block."); 228 | return statements; 229 | } 230 | 231 | 232 | Expr parseBinaryExpr( 233 | const std::initializer_list &types, 234 | Expr expr, 235 | const parserFn &f 236 | ) { 237 | 238 | while (match(types)) { 239 | auto token = previous(); 240 | expr = std::make_unique(std::move(expr), token, static_cast(token.getType()), std::invoke(f, this)); 241 | } 242 | 243 | return expr; 244 | } 245 | 246 | Expr expression() { 247 | return assignment(); 248 | } 249 | 250 | Expr assignment() { 251 | auto expr = or_(); 252 | 253 | if (match(EQUAL)) { 254 | const auto equals = previous(); 255 | auto value = assignment(); 256 | 257 | if (std::holds_alternative(expr)) { 258 | const auto name = std::get(expr)->name; 259 | return std::make_unique(name, std::move(value)); 260 | } 261 | if (std::holds_alternative(expr)) { 262 | const auto &getExpr = std::get(expr); 263 | return std::make_unique(std::move(getExpr->object), getExpr->name, std::move(value)); 264 | } 265 | 266 | lox::error(equals, "Invalid assignment target."); 267 | } 268 | 269 | return expr; 270 | } 271 | 272 | Expr or_() { 273 | auto expr = and_(); 274 | 275 | while (match(OR)) { 276 | auto right = and_(); 277 | expr = std::make_unique(std::move(expr), LogicalOp::OR, std::move(right)); 278 | } 279 | 280 | return expr; 281 | } 282 | 283 | Expr and_() { 284 | auto expr = equality(); 285 | 286 | while (match(AND)) { 287 | auto right = equality(); 288 | expr = std::make_unique(std::move(expr), LogicalOp::AND, std::move(right)); 289 | } 290 | 291 | return expr; 292 | } 293 | 294 | Expr equality() { 295 | return parseBinaryExpr({BANG_EQUAL, EQUAL_EQUAL}, comparison(), &Parser::comparison); 296 | } 297 | 298 | Expr comparison() { 299 | return parseBinaryExpr({GREATER, GREATER_EQUAL, LESS, LESS_EQUAL}, term(), &Parser::term); 300 | } 301 | 302 | Expr term() { 303 | return parseBinaryExpr({MINUS, PLUS}, factor(), &Parser::factor); 304 | } 305 | 306 | Expr factor() { 307 | return parseBinaryExpr({SLASH, STAR}, unary(), &Parser::unary); 308 | } 309 | 310 | Expr unary() { 311 | if (match({BANG, MINUS})) { 312 | const Token token = previous(); 313 | auto right = unary(); 314 | return std::make_unique(token, static_cast(token.getType()), std::move(right)); 315 | } 316 | 317 | return call(); 318 | } 319 | 320 | Expr call() { 321 | Expr expr = primary(); 322 | 323 | while (true) { 324 | if (match(LEFT_PAREN)) { 325 | expr = finishCall(expr); 326 | } else if (match(DOT)) { 327 | Token name = consume(IDENTIFIER, "Expect property name after '.'."); 328 | expr = std::make_unique(std::move(expr), name); 329 | } else { 330 | break; 331 | } 332 | } 333 | 334 | return expr; 335 | } 336 | 337 | Expr finishCall(Expr &callee) { 338 | std::vector arguments; 339 | 340 | if (!check(RIGHT_PAREN)) { 341 | do { 342 | if (arguments.size() >= 255) { 343 | lox::error(peek(), "Can't have more than 255 arguments."); 344 | } 345 | arguments.push_back(expression()); 346 | } while (match(COMMA)); 347 | } 348 | 349 | consume(RIGHT_PAREN, "Expect ')' after arguments."); 350 | 351 | return std::make_unique(std::move(callee), previous(), std::move(arguments)); 352 | } 353 | 354 | Expr primary() { 355 | if (match(FALSE)) return std::make_unique(false); 356 | if (match(TRUE)) return std::make_unique(true); 357 | if (match(NIL)) return std::make_unique(nullptr); 358 | 359 | if (match({NUMBER, STRING})) { 360 | return std::make_unique(previous().getLiteral()); 361 | } 362 | 363 | if (match(THIS)) return std::make_unique(previous()); 364 | 365 | if (match(SUPER)) { 366 | Token keyword = previous(); 367 | consume(DOT, "Expect '.' after 'super'."); 368 | Token method = consume(IDENTIFIER, "Expect superclass method name."); 369 | return std::make_unique(keyword, method); 370 | } 371 | 372 | if (match(IDENTIFIER)) { 373 | return std::make_unique(previous()); 374 | } 375 | 376 | if (match(LEFT_PAREN)) { 377 | auto expr = expression(); 378 | consume(RIGHT_PAREN, "Expect ')' after expression."); 379 | return std::make_unique(std::move(expr)); 380 | } 381 | 382 | throw error(peek(), "Expect expression."); 383 | } 384 | 385 | static ParseError error(const Token &token, const std::string &message) { 386 | lox::error(token, message); 387 | return ParseError{message}; 388 | } 389 | 390 | void synchronize() { 391 | advance(); 392 | 393 | while (!isAtEnd()) { 394 | if (previous().getType() == SEMICOLON) return; 395 | 396 | switch (peek().getType()) { 397 | case CLASS: 398 | case FUN: 399 | case VAR: 400 | case FOR: 401 | case IF: 402 | case WHILE: 403 | case PRINT: 404 | case RETURN: 405 | return; 406 | default: { 407 | } 408 | } 409 | 410 | advance(); 411 | } 412 | } 413 | 414 | Token consume(const TokenType type, const std::string &message) { 415 | if (check(type)) 416 | return advance(); 417 | 418 | throw error(peek(), message); 419 | } 420 | 421 | template 422 | bool match(const std::initializer_list &types) { 423 | for (auto &type: types) { 424 | if (check(type)) { 425 | advance(); 426 | return true; 427 | } 428 | } 429 | return false; 430 | } 431 | 432 | bool match(const TokenType type) { return match({type}); } 433 | 434 | bool check(const TokenType type) { 435 | return !isAtEnd() && (peek().getType() == type); 436 | } 437 | 438 | Token advance() { 439 | if (!isAtEnd()) 440 | current++; 441 | return previous(); 442 | } 443 | 444 | bool isAtEnd() { return peek().getType() == END; } 445 | 446 | Token peek() { return tokens.at(current); } 447 | 448 | Token previous() { return tokens.at(current - 1); } 449 | }; 450 | }// namespace lox 451 | 452 | #endif//PARSER_H 453 | -------------------------------------------------------------------------------- /src/frontend/Resolver.h: -------------------------------------------------------------------------------- 1 | #ifndef RESOLVER_H 2 | #define RESOLVER_H 3 | #include "AST.h" 4 | #include "Error.h" 5 | #include 6 | 7 | using namespace std::literals; 8 | 9 | namespace lox { 10 | 11 | class Resolver { 12 | enum class ClassType { 13 | NONE, 14 | CLASS, 15 | SUBCLASS 16 | }; 17 | 18 | using Scope = std::unordered_map; 19 | std::vector scopes; 20 | LoxFunctionType currentFunction = LoxFunctionType::NONE; 21 | ClassType currentClass = ClassType::NONE; 22 | 23 | void beginScope() { 24 | scopes.emplace_back(); 25 | } 26 | 27 | void endScope() { 28 | scopes.pop_back(); 29 | } 30 | 31 | void declare(const Token &name) { 32 | if (scopes.empty()) return; 33 | auto &scope = scopes.back(); 34 | if (scope.contains(name.getLexeme())) { 35 | lox::error(name, "Already a variable with this name in this scope."); 36 | } 37 | scope[name.getLexeme()] = false; 38 | } 39 | 40 | void define(const Token &name) { 41 | if (scopes.empty()) return; 42 | scopes.back()[name.getLexeme()] = true; 43 | } 44 | 45 | void resolveLocal(const Assignable &expr, const Token &name) const { 46 | if (scopes.empty()) return; 47 | 48 | for (signed i = static_cast(scopes.size()) - 1; i >= 0; i--) { 49 | if (scopes.at(i).contains(name.getLexeme())) { 50 | expr.distance = static_cast(scopes.size() - 1 - i); 51 | return; 52 | } 53 | } 54 | } 55 | 56 | void resolveFunction(const FunctionStmtPtr &function, const LoxFunctionType functionType) { 57 | const LoxFunctionType enclosingFunction = currentFunction; 58 | currentFunction = functionType; 59 | 60 | beginScope(); 61 | for (auto ¶m: function->parameters) { 62 | declare(param); 63 | define(param); 64 | } 65 | resolve(function->body); 66 | endScope(); 67 | currentFunction = enclosingFunction; 68 | } 69 | 70 | public: 71 | void operator()(const BlockStmtPtr &blockStmt) { 72 | beginScope(); 73 | resolve(blockStmt->statements); 74 | endScope(); 75 | } 76 | 77 | void operator()(const FunctionStmtPtr &functionStmt) { 78 | declare(functionStmt->name); 79 | define(functionStmt->name); 80 | resolveFunction(functionStmt, LoxFunctionType::FUNCTION); 81 | } 82 | 83 | void operator()(const ExpressionStmtPtr &expressionStmt) { 84 | resolve(expressionStmt->expression); 85 | } 86 | 87 | void operator()(const PrintStmtPtr &printStmt) { 88 | resolve(printStmt->expression); 89 | } 90 | 91 | void operator()(const ReturnStmtPtr &returnStmt) { 92 | if (currentFunction == LoxFunctionType::NONE) { 93 | lox::error(returnStmt->keyword, "Can't return from top-level code."); 94 | } else if (returnStmt->expression.has_value() && currentFunction == LoxFunctionType::INITIALIZER) { 95 | lox::error(returnStmt->keyword, "Can't return a value from an initializer."); 96 | } 97 | 98 | resolve(returnStmt->expression); 99 | } 100 | 101 | void operator()(const VarStmtPtr &varStmt) { 102 | declare(varStmt->name); 103 | resolve(varStmt->initializer); 104 | define(varStmt->name); 105 | } 106 | 107 | void operator()(const WhileStmtPtr &whileStmt) { 108 | resolve(whileStmt->condition); 109 | resolve(whileStmt->body); 110 | } 111 | 112 | void operator()(const IfStmtPtr &ifStmt) { 113 | resolve(ifStmt->condition); 114 | resolve(ifStmt->thenBranch); 115 | if (ifStmt->elseBranch.has_value()) resolve(ifStmt->elseBranch.value()); 116 | } 117 | 118 | void operator()(const ClassStmtPtr &classStmt) { 119 | const ClassType enclosingClass = currentClass; 120 | currentClass = ClassType::CLASS; 121 | declare(classStmt->name); 122 | define(classStmt->name); 123 | 124 | if (classStmt->super_class.has_value() && 125 | classStmt->name.getLexeme() == classStmt->super_class.value()->name.getLexeme()) { 126 | lox::error(classStmt->super_class.value()->name, "A class can't inherit from itself."); 127 | } 128 | 129 | if (classStmt->super_class.has_value()) { 130 | currentClass = ClassType::SUBCLASS; 131 | this->operator()(classStmt->super_class.value()); 132 | } 133 | 134 | if (classStmt->super_class.has_value()) { 135 | beginScope(); 136 | scopes.back()["super"] = true; 137 | } 138 | 139 | beginScope(); 140 | scopes.back()["this"] = true; 141 | 142 | for (auto &method: classStmt->methods) { 143 | resolveFunction(method, method->type); 144 | } 145 | 146 | endScope(); 147 | 148 | if (classStmt->super_class.has_value()) { 149 | endScope(); 150 | } 151 | 152 | currentClass = enclosingClass; 153 | } 154 | 155 | void operator()(const AssignExprPtr &assignExpr) { 156 | resolve(assignExpr->value); 157 | resolveLocal(*assignExpr, assignExpr->name); 158 | } 159 | 160 | void operator()(const BinaryExprPtr &binaryExpr) { 161 | resolve(binaryExpr->left); 162 | resolve(binaryExpr->right); 163 | } 164 | 165 | void operator()(const CallExprPtr &callExpr) { 166 | resolve(callExpr->callee); 167 | for (auto &arg: callExpr->arguments) { 168 | resolve(arg); 169 | } 170 | } 171 | 172 | void operator()(const GetExprPtr &getExpr) { 173 | resolve(getExpr->object); 174 | } 175 | 176 | void operator()(const SetExprPtr &setExpr) { 177 | resolve(setExpr->object); 178 | resolve(setExpr->value); 179 | } 180 | 181 | void operator()(const ThisExprPtr &thisExpr) const { 182 | if (currentClass == ClassType::NONE) { 183 | lox::error(thisExpr->name, "Can't use 'this' outside of a class."); 184 | return; 185 | } 186 | 187 | resolveLocal(*thisExpr, thisExpr->name); 188 | } 189 | 190 | void operator()(const SuperExprPtr &superExpr) const { 191 | if (currentClass == ClassType::NONE) { 192 | lox::error(superExpr->name, "Can't use 'super' outside of a class."); 193 | } else if (currentClass != ClassType::SUBCLASS) { 194 | lox::error(superExpr->name, "Can't use 'super' in a class with no superclass."); 195 | } 196 | resolveLocal(*superExpr, superExpr->name); 197 | } 198 | 199 | void operator()(const VarExprPtr &varExpr) { 200 | if (!scopes.empty() && 201 | scopes.back().contains(varExpr->name.getLexeme()) && 202 | !scopes.back()[varExpr->name.getLexeme()]) { 203 | lox::error(varExpr->name, "Can't read local variable in its own initializer."); 204 | return; 205 | } 206 | 207 | resolveLocal(*varExpr, varExpr->name); 208 | } 209 | 210 | void operator()(const GroupingExprPtr &groupingExpr) { 211 | resolve(groupingExpr->expression); 212 | } 213 | 214 | void operator()(const LiteralExprPtr &) const {} 215 | 216 | void operator()(const LogicalExprPtr &logicalExpr) { 217 | resolve(logicalExpr->left); 218 | resolve(logicalExpr->right); 219 | } 220 | 221 | void operator()(const UnaryExprPtr &unaryExpr) { 222 | resolve(unaryExpr->expression); 223 | } 224 | 225 | private: 226 | void resolve(const std::optional &opt) { 227 | if (opt.has_value()) resolve(opt.value()); 228 | } 229 | 230 | void resolve(const Expr &expr) { 231 | std::visit(*this, expr); 232 | } 233 | 234 | void resolve(const Stmt &stmt) { 235 | std::visit(*this, stmt); 236 | } 237 | 238 | public: 239 | void resolve(const Program &program) { 240 | for (auto &item: program) { 241 | resolve(item); 242 | } 243 | } 244 | }; 245 | }// namespace lox 246 | #endif// RESOLVER_H -------------------------------------------------------------------------------- /src/frontend/Scanner.h: -------------------------------------------------------------------------------- 1 | #include "Error.h" 2 | #include "Token.h" 3 | #include 4 | #include 5 | #include 6 | 7 | namespace lox { 8 | class Scanner { 9 | public: 10 | explicit Scanner(std::string Source) : source{std::move(Source)} {} 11 | 12 | std::vector& scanTokens() { 13 | while (!isAtEnd()) { 14 | // We are at the beginning of the next lexeme. 15 | start = current; 16 | scanToken(); 17 | } 18 | 19 | tokens.emplace_back(END, "", "", line); 20 | 21 | return tokens; 22 | } 23 | 24 | private: 25 | const std::string source; 26 | long unsigned int start = 0; 27 | long unsigned int current = 0; 28 | long unsigned int line = 1; 29 | std::vector tokens = std::vector(); 30 | 31 | void addToken(const TokenType type) { 32 | addToken(type, nullptr); 33 | } 34 | 35 | void addToken(TokenType type, Literal literal) { 36 | auto lexeme = std::string_view(source).substr(start, current - start); 37 | tokens.emplace_back(type, lexeme, literal, line); 38 | } 39 | 40 | char advance() { return source[current++]; } 41 | 42 | [[nodiscard]] bool isAtEnd() const { return current >= source.length(); } 43 | 44 | void scanToken() { 45 | switch (const char c = advance()) { 46 | case '(': 47 | addToken(LEFT_PAREN); 48 | break; 49 | case ')': 50 | addToken(RIGHT_PAREN); 51 | break; 52 | case '{': 53 | addToken(LEFT_BRACE); 54 | break; 55 | case '}': 56 | addToken(RIGHT_BRACE); 57 | break; 58 | case ',': 59 | addToken(COMMA); 60 | break; 61 | case '.': 62 | addToken(DOT); 63 | break; 64 | case '-': 65 | addToken(MINUS); 66 | break; 67 | case '+': 68 | addToken(PLUS); 69 | break; 70 | case ';': 71 | addToken(SEMICOLON); 72 | break; 73 | case '*': 74 | addToken(STAR); 75 | break; 76 | case '/': 77 | if (match('/')) { 78 | // A comment goes until the end of the line. 79 | while (peek() != '\n' && !isAtEnd()) advance(); 80 | } else { 81 | addToken(SLASH); 82 | } 83 | break; 84 | case '!': 85 | addToken(match('=') ? BANG_EQUAL : BANG); 86 | break; 87 | case '=': 88 | addToken(match('=') ? EQUAL_EQUAL : EQUAL); 89 | break; 90 | case '<': 91 | addToken(match('=') ? LESS_EQUAL : LESS); 92 | break; 93 | case '>': 94 | addToken(match('=') ? GREATER_EQUAL : GREATER); 95 | break; 96 | case ' ': 97 | case '\r': 98 | case '\t': 99 | // Ignore whitespace. 100 | break; 101 | case '\n': 102 | line++; 103 | break; 104 | case '"': 105 | string(); 106 | break; 107 | default: { 108 | if (isDigit(c)) { 109 | number(); 110 | } else if (isAlpha(c)) { 111 | identifier(); 112 | } else { 113 | if (isDigit(c)) { 114 | number(); 115 | } else { 116 | lox::error(line, "Unexpected character."); 117 | } 118 | } 119 | break; 120 | } 121 | } 122 | } 123 | 124 | void identifier() { 125 | while (isAlphaNumeric(peek())) advance(); 126 | 127 | const auto text = std::string_view(source).substr(start, current - start); 128 | TokenType type; 129 | 130 | if (text == "and") 131 | type = AND; 132 | else if (text == "class") 133 | type = CLASS; 134 | else if (text == "else") 135 | type = ELSE; 136 | else if (text == "false") 137 | type = FALSE; 138 | else if (text == "for") 139 | type = FOR; 140 | else if (text == "fun") 141 | type = FUN; 142 | else if (text == "if") 143 | type = IF; 144 | else if (text == "nil") 145 | type = NIL; 146 | else if (text == "or") 147 | type = OR; 148 | else if (text == "print") 149 | type = PRINT; 150 | else if (text == "return") 151 | type = RETURN; 152 | else if (text == "super") 153 | type = SUPER; 154 | else if (text == "this") 155 | type = THIS; 156 | else if (text == "true") 157 | type = TRUE; 158 | else if (text == "var") 159 | type = VAR; 160 | else if (text == "while") 161 | type = WHILE; 162 | else 163 | type = IDENTIFIER; 164 | addToken(type); 165 | } 166 | 167 | void number() { 168 | while (isDigit(peek())) advance(); 169 | 170 | // Look for a fractional part. 171 | if (peek() == '.' && isDigit(peekNext())) { 172 | // Consume the "." 173 | advance(); 174 | 175 | while (isDigit(peek())) advance(); 176 | } 177 | 178 | addToken(NUMBER, std::stod(source.substr(start, current - start))); 179 | } 180 | 181 | void string() { 182 | while (peek() != '"' && !isAtEnd()) { 183 | if (peek() == '\n') line++; 184 | advance(); 185 | } 186 | 187 | if (isAtEnd()) { 188 | lox::error(line, "Unterminated string."); 189 | return; 190 | } 191 | 192 | // The closing ". 193 | advance(); 194 | 195 | // Trim the surrounding quotes. 196 | addToken(STRING, std::string_view(source).substr(start + 1, current - 1 - start - 1)); 197 | } 198 | 199 | static bool isAlpha(const char c) { 200 | return (c >= 'a' && c <= 'z') || 201 | (c >= 'A' && c <= 'Z') || 202 | c == '_'; 203 | } 204 | 205 | static bool isAlphaNumeric(const char c) { 206 | return isAlpha(c) || isDigit(c); 207 | } 208 | 209 | static bool isDigit(const char c) { 210 | return c >= '0' && c <= '9'; 211 | } 212 | 213 | bool match(const char expected) { 214 | if (isAtEnd()) return false; 215 | if (source[current] != expected) return false; 216 | current++; 217 | return true; 218 | } 219 | 220 | [[nodiscard]] char peek() const { 221 | if (isAtEnd()) return '\0'; 222 | return source[current]; 223 | } 224 | 225 | [[nodiscard]] char peekNext() const { 226 | if (current + 1 >= source.length()) return '\0'; 227 | return source[current + 1]; 228 | } 229 | }; 230 | }// namespace lox 231 | -------------------------------------------------------------------------------- /src/frontend/Token.h: -------------------------------------------------------------------------------- 1 | #ifndef LOX_LLVM_TOKEN_H 2 | #define LOX_LLVM_TOKEN_H 3 | #include 4 | #include 5 | 6 | namespace lox { 7 | enum TokenType { 8 | // Single-character tokens. 9 | LEFT_PAREN, 10 | RIGHT_PAREN, 11 | LEFT_BRACE, 12 | RIGHT_BRACE, 13 | COMMA, 14 | DOT, 15 | MINUS, 16 | PLUS, 17 | SEMICOLON, 18 | SLASH, 19 | STAR, 20 | 21 | // One or two character tokens. 22 | BANG, 23 | BANG_EQUAL, 24 | EQUAL, 25 | EQUAL_EQUAL, 26 | GREATER, 27 | GREATER_EQUAL, 28 | LESS, 29 | LESS_EQUAL, 30 | 31 | // Literals. 32 | IDENTIFIER, 33 | STRING, 34 | NUMBER, 35 | 36 | // Keywords. 37 | AND, 38 | CLASS, 39 | ELSE, 40 | FALSE, 41 | FUN, 42 | FOR, 43 | IF, 44 | NIL, 45 | OR, 46 | PRINT, 47 | RETURN, 48 | SUPER, 49 | THIS, 50 | TRUE, 51 | VAR, 52 | WHILE, 53 | 54 | END 55 | }; 56 | 57 | using Literal = std::variant; 58 | 59 | class Token { 60 | TokenType type; 61 | std::string_view lexeme; 62 | Literal literal; 63 | unsigned int line; 64 | 65 | public: 66 | explicit Token(const TokenType type, const std::string_view lexeme, const Literal &literal, const unsigned int line) 67 | : type{type}, lexeme{lexeme}, literal{literal}, line{line} {} 68 | 69 | [[nodiscard]] TokenType getType() const { return type; } 70 | 71 | [[nodiscard]] unsigned int getLine() const { return line; } 72 | 73 | [[nodiscard]] std::string_view getLexeme() const { return lexeme; } 74 | 75 | [[nodiscard]] Literal getLiteral() const { return literal; } 76 | }; 77 | }// namespace lox 78 | #endif//LOX_LLVM_TOKEN_H 79 | -------------------------------------------------------------------------------- /src/interpreter/Environment.cpp: -------------------------------------------------------------------------------- 1 | #include "Environment.h" 2 | 3 | namespace lox { 4 | 5 | void Environment::define(const std::string_view name, const LoxObject &value) { values[name] = value; } 6 | 7 | LoxObject &Environment::getAt(const unsigned long distance, const std::string_view &name) { 8 | return ancestor(distance)->values[name]; 9 | } 10 | 11 | EnvironmentPtr Environment::ancestor(const unsigned long distance) { 12 | auto environment = shared_from_this(); 13 | for (unsigned long i = 0; i < distance; i++) { environment = environment->enclosing; } 14 | return environment; 15 | } 16 | 17 | LoxObject &Environment::get(const Token &name) { 18 | if (values.contains(name.getLexeme())) { return values[name.getLexeme()]; } 19 | 20 | if (enclosing != nullptr) { return enclosing->get(name); } 21 | 22 | throw runtime_error(name, "Undefined variable '" + std::string(name.getLexeme()) + "'."); 23 | } 24 | 25 | void Environment::assign(const Token &name, const LoxObject &value) { 26 | if (values.contains(name.getLexeme())) { 27 | values[name.getLexeme()] = value; 28 | return; 29 | } 30 | 31 | if (enclosing != nullptr) { 32 | enclosing->assign(name, value); 33 | return; 34 | } 35 | 36 | throw runtime_error(name, std::format("Undefined variable '{}'.", name.getLexeme())); 37 | } 38 | 39 | void Environment::assignAt(const unsigned long distance, const Token &name, const LoxObject &value) { 40 | ancestor(distance)->values[name.getLexeme()] = value; 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/interpreter/Environment.h: -------------------------------------------------------------------------------- 1 | #ifndef ENVIRONMENT_H 2 | #define ENVIRONMENT_H 3 | 4 | #include "../frontend/Token.h" 5 | #include "LoxObject.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace lox { 12 | class Environment; 13 | using EnvironmentPtr = std::shared_ptr; 14 | 15 | class Environment : public std::enable_shared_from_this { 16 | std::unordered_map values; 17 | EnvironmentPtr enclosing; 18 | public: 19 | explicit Environment() = default; 20 | explicit Environment(EnvironmentPtr environment) : enclosing{std::move(environment)} {} 21 | 22 | EnvironmentPtr get_enclosing() const { return enclosing; } 23 | void define(std::string_view name, const LoxObject &value = LoxNil{}); 24 | LoxObject &getAt(unsigned long distance, const std::string_view &name); 25 | EnvironmentPtr ancestor(unsigned long distance); 26 | LoxObject &get(const Token &name); 27 | void assign(const Token &name, const LoxObject &value); 28 | void assignAt(unsigned long distance, const Token &name, const LoxObject &value); 29 | }; 30 | }// namespace lox 31 | 32 | 33 | #endif//ENVIRONMENT_H 34 | -------------------------------------------------------------------------------- /src/interpreter/Interpreter.cpp: -------------------------------------------------------------------------------- 1 | #include "Interpreter.h" 2 | #include "LoxClass.h" 3 | #include "LoxFunction.h" 4 | #include "LoxInstance.h" 5 | #include "NativeFunction.h" 6 | 7 | #include 8 | 9 | constexpr int MAX_CALL_DEPTH = 512; 10 | 11 | namespace lox { 12 | 13 | static LoxNumber checkNumberOperand(const Token &op, const LoxObject &operand) { 14 | if (std::holds_alternative(operand)) return std::get(operand); 15 | throw runtime_error(op, "Operand must be a number."); 16 | } 17 | 18 | static void checkNumberOperands(const Token &op, const LoxObject &left, const LoxObject &right) { 19 | if (std::holds_alternative(left) && std::holds_alternative(right)) return; 20 | 21 | throw runtime_error(op, "Operands must be numbers."); 22 | } 23 | 24 | Interpreter::Interpreter() { 25 | globals->define("clock", std::make_shared([](const std::vector &) -> LoxObject { 26 | const auto now = std::chrono::system_clock::now().time_since_epoch(); 27 | return LoxNumber(std::chrono::duration_cast(now).count()); 28 | })); 29 | globals->define( 30 | "exit", std::make_shared( 31 | [](const std::vector &arguments) -> LoxObject { 32 | const auto token = Token(IDENTIFIER, "", nullptr, 0); 33 | exit(static_cast(checkNumberOperand(token, arguments.at(0)))); 34 | }, 35 | 1 36 | ) 37 | ); 38 | globals->define("read", std::make_shared([](const std::vector &) -> LoxObject { 39 | const int c = getchar(); 40 | if (c == -1) { return LoxNil(); } 41 | return LoxNumber(static_cast(c)); 42 | })); 43 | globals->define( 44 | "utf", 45 | std::make_shared( 46 | [](const std::vector &args) -> LoxObject { 47 | int byte_count = 0; 48 | for (int i = 0; i < 4; i++) { 49 | if (i > 0 && std::holds_alternative(args[i])) continue; 50 | 51 | if (!std::holds_alternative(args[i]) || 52 | (std::get(args[i]) < 0 || std::get(args[i]) > 255)) { 53 | const auto token = Token(IDENTIFIER, "", nullptr, 0); 54 | throw lox::runtime_error(token, "utf parameter should be a number between 0 and 255."); 55 | } 56 | 57 | byte_count++; 58 | } 59 | 60 | char bytes[byte_count]; 61 | std::transform( 62 | args.begin(), args.end() - 4 + byte_count, bytes, 63 | [](const LoxObject &value) -> char { 64 | return std::holds_alternative(value) 65 | ? 0 66 | : static_cast(std::get(value)); 67 | } 68 | ); 69 | 70 | return LoxString(bytes, byte_count); 71 | }, 72 | 4 73 | ) 74 | ); 75 | globals->define( 76 | "printerr", std::make_shared( 77 | [](const std::vector &arguments) -> LoxObject { 78 | std::cerr << lox::to_string(arguments[0]) << std::endl; 79 | return LoxNil(); 80 | }, 81 | 1 82 | ) 83 | ); 84 | } 85 | 86 | [[nodiscard]] LoxObject &Interpreter::lookUpVariable(const Token &name, const Assignable &expr) const { 87 | if (expr.distance == -1) { return globals->get(name); } 88 | return environment->getAt(expr.distance, name.getLexeme()); 89 | } 90 | 91 | StmtResult Interpreter::operator()(const ClassStmtPtr &classStmt) { 92 | std::optional> super_class; 93 | if (classStmt->super_class.has_value()) { 94 | if (const auto &s = (*this)(classStmt->super_class.value()); 95 | std::holds_alternative(s) && 96 | dynamic_cast(std::get(s).get())) { 97 | super_class = std::reinterpret_pointer_cast(std::get(s)); 98 | } else { 99 | throw runtime_error(classStmt->super_class.value()->name, "Superclass must be a class."); 100 | } 101 | } 102 | 103 | environment->define(classStmt->name.getLexeme()); 104 | 105 | if (super_class.has_value()) { 106 | environment = std::make_shared(environment); 107 | environment->define("super", super_class.value()); 108 | } 109 | 110 | std::unordered_map methods; 111 | 112 | for (auto &method: classStmt->methods) { 113 | methods[method->name.getLexeme()] = 114 | std::make_shared(method, environment, method->type == LoxFunctionType::INITIALIZER); 115 | } 116 | 117 | if (super_class.has_value()) { environment = environment->get_enclosing(); } 118 | 119 | environment->assign( 120 | classStmt->name, std::make_shared(classStmt->name.getLexeme(), super_class, std::move(methods)) 121 | ); 122 | 123 | return Nothing(); 124 | } 125 | 126 | LoxObject Interpreter::operator()(const CallExprPtr &callExpr) { 127 | if (function_depth > MAX_CALL_DEPTH) { throw lox::runtime_error(callExpr->keyword, "Stack overflow."); } 128 | 129 | const auto &callee = evaluate(callExpr->callee); 130 | 131 | std::vector arguments; 132 | for (auto &argument: callExpr->arguments) { arguments.push_back(evaluate(argument)); } 133 | 134 | if (std::holds_alternative(callee)) { 135 | const auto &callable = std::get(callee); 136 | if (static_cast(arguments.size()) != callable->arity()) { 137 | throw runtime_error( 138 | callExpr->keyword, 139 | std::format("Expected {} arguments but got {}.", callable->arity(), arguments.size()) 140 | ); 141 | } 142 | function_depth++; 143 | auto lox_object = (*callable)(*this, arguments); 144 | function_depth--; 145 | return lox_object; 146 | } 147 | 148 | throw runtime_error(callExpr->keyword, "Can only call functions and classes."); 149 | } 150 | 151 | StmtResult Interpreter::operator()(const FunctionStmtPtr &functionStmt) { 152 | const auto name = functionStmt->name.getLexeme(); 153 | auto function = std::make_shared(functionStmt, environment); 154 | environment->define(name, std::move(function)); 155 | return Nothing(); 156 | } 157 | 158 | StmtResult Interpreter::executeBlock(const StmtList &statements, const EnvironmentPtr &newEnvironment) { 159 | const auto previous = environment; 160 | environment = newEnvironment; 161 | 162 | for (const auto &statement: statements) { 163 | if (auto result = evaluate(statement); std::holds_alternative(result)) { 164 | environment = previous; 165 | return result; 166 | } 167 | } 168 | 169 | environment = previous; 170 | 171 | return Nothing(); 172 | } 173 | 174 | LoxObject Interpreter::operator()(const GetExprPtr &getExpr) { 175 | if (const auto &object = evaluate(getExpr->object); std::holds_alternative(object)) { 176 | return std::get(object)->get(getExpr->name); 177 | } 178 | 179 | throw runtime_error(getExpr->name, "Only instances have properties."); 180 | } 181 | 182 | LoxObject Interpreter::operator()(const SetExprPtr &setExpr) { 183 | const auto &object = evaluate(setExpr->object); 184 | 185 | if (!std::holds_alternative(object)) { 186 | throw runtime_error(setExpr->name, "Only instances have fields."); 187 | } 188 | 189 | auto value = evaluate(setExpr->value); 190 | std::get(object)->set(setExpr->name, value); 191 | return value; 192 | } 193 | 194 | LoxObject Interpreter::operator()(const SuperExprPtr &superExpr) const { 195 | const auto &callable = std::get(environment->getAt(superExpr->distance, "super")); 196 | const auto &super_class = std::reinterpret_pointer_cast(callable); 197 | const auto &instance = std::get(environment->getAt(superExpr->distance - 1, "this")); 198 | const auto &method = super_class->findMethod(superExpr->method.getLexeme()); 199 | if (method == nullptr) { 200 | throw runtime_error( 201 | superExpr->method, std::format("Undefined property '{}'.", std::string(superExpr->method.getLexeme())) 202 | ); 203 | } 204 | return method->bind(instance); 205 | } 206 | 207 | StmtResult Interpreter::operator()(const IfStmtPtr &ifStmtPtr) { 208 | if (isTruthy(evaluate(ifStmtPtr->condition))) { return std::move(evaluate(ifStmtPtr->thenBranch)); } 209 | 210 | if (ifStmtPtr->elseBranch.has_value()) { return evaluate(ifStmtPtr->elseBranch.value()); } 211 | 212 | return Nothing(); 213 | } 214 | 215 | StmtResult Interpreter::operator()(const ExpressionStmtPtr &expressionStmt) { 216 | evaluate(expressionStmt->expression); 217 | return Nothing(); 218 | } 219 | 220 | StmtResult Interpreter::operator()(const PrintStmtPtr &printStmt) { 221 | const auto object = evaluate(printStmt->expression); 222 | std::cout << lox::to_string(object) << std::endl; 223 | return Nothing(); 224 | } 225 | 226 | StmtResult Interpreter::operator()(const VarStmtPtr &varStmt) { 227 | const auto value = evaluate(varStmt->initializer); 228 | environment->define(varStmt->name.getLexeme(), value); 229 | return Nothing(); 230 | } 231 | 232 | StmtResult Interpreter::operator()(const WhileStmtPtr &whileStmt) { 233 | while (isTruthy(evaluate(whileStmt->condition))) { 234 | if (auto result = evaluate(whileStmt->body); std::holds_alternative(result)) { return result; } 235 | } 236 | return Nothing(); 237 | } 238 | 239 | StmtResult Interpreter::operator()(const ReturnStmtPtr &returnStmt) { 240 | LoxObject value = LoxNil(); 241 | 242 | if (returnStmt->expression.has_value()) { value = evaluate(returnStmt->expression.value()); } 243 | 244 | return Return(value); 245 | } 246 | 247 | StmtResult Interpreter::operator()(const BlockStmtPtr &blockStmt) { 248 | return executeBlock(blockStmt->statements, std::make_shared(environment)); 249 | } 250 | 251 | LoxObject Interpreter::operator()(const BinaryExprPtr &binaryExpr) { 252 | const auto &left = evaluate(binaryExpr->left); 253 | const auto &right = evaluate(binaryExpr->right); 254 | 255 | switch (binaryExpr->op) { 256 | case BinaryOp::PLUS: { 257 | if (std::holds_alternative(left) && std::holds_alternative(right)) { 258 | return std::get(left) + std::get(right); 259 | } 260 | 261 | if (std::holds_alternative(left) && std::holds_alternative(right)) { 262 | return std::get(left) + std::get(right); 263 | } 264 | 265 | throw runtime_error(binaryExpr->token, "Operands must be two numbers or two strings."); 266 | } 267 | case BinaryOp::MINUS: 268 | checkNumberOperands(binaryExpr->token, left, right); 269 | return std::get(left) - std::get(right); 270 | case BinaryOp::SLASH: 271 | checkNumberOperands(binaryExpr->token, left, right); 272 | return std::get(left) / std::get(right); 273 | case BinaryOp::STAR: 274 | checkNumberOperands(binaryExpr->token, left, right); 275 | return std::get(left) * std::get(right); 276 | case BinaryOp::GREATER: 277 | checkNumberOperands(binaryExpr->token, left, right); 278 | return std::get(left) > std::get(right); 279 | case BinaryOp::GREATER_EQUAL: 280 | checkNumberOperands(binaryExpr->token, left, right); 281 | return std::get(left) >= std::get(right); 282 | case BinaryOp::LESS: 283 | checkNumberOperands(binaryExpr->token, left, right); 284 | return std::get(left) < std::get(right); 285 | case BinaryOp::LESS_EQUAL: 286 | checkNumberOperands(binaryExpr->token, left, right); 287 | return std::get(left) <= std::get(right); 288 | case BinaryOp::BANG_EQUAL: 289 | return left != right; 290 | case BinaryOp::EQUAL_EQUAL: 291 | return left == right; 292 | } 293 | 294 | std::unreachable(); 295 | } 296 | LoxObject Interpreter::operator()(const ThisExprPtr &thisExpr) const { 297 | return lookUpVariable(thisExpr->name, *thisExpr); 298 | } 299 | 300 | LoxObject Interpreter::operator()(const GroupingExprPtr &groupingExpr) { 301 | return evaluate(groupingExpr->expression); 302 | } 303 | 304 | LoxObject Interpreter::operator()(const LiteralExprPtr &literalExpr) const { 305 | return std::visit( 306 | overloaded{ 307 | [](const bool value) -> LoxObject { return value; }, 308 | [](const double value) -> LoxObject { return value; }, 309 | [](const std::string_view value) -> LoxObject { return std::string(value); }, 310 | [](const std::nullptr_t) -> LoxObject { return LoxNil(); }, 311 | }, 312 | literalExpr->literal 313 | ); 314 | } 315 | 316 | LoxObject Interpreter::operator()(const LogicalExprPtr &logicalExpr) { 317 | auto left = evaluate(logicalExpr->left); 318 | 319 | if (logicalExpr->op == LogicalOp::OR) { 320 | if (isTruthy(left)) return left; 321 | } else { 322 | if (!isTruthy(left)) return left; 323 | } 324 | 325 | return evaluate(logicalExpr->right); 326 | } 327 | 328 | LoxObject Interpreter::operator()(const UnaryExprPtr &unaryExpr) { 329 | const auto &result = evaluate(unaryExpr->expression); 330 | switch (unaryExpr->op) { 331 | case UnaryOp::MINUS: { 332 | return -checkNumberOperand(unaryExpr->token, result); 333 | } 334 | case UnaryOp::BANG: 335 | return !isTruthy(result); 336 | } 337 | 338 | std::unreachable(); 339 | } 340 | 341 | LoxObject Interpreter::operator()(const VarExprPtr &varExpr) const { 342 | return lookUpVariable(varExpr->name, *varExpr); 343 | } 344 | 345 | LoxObject Interpreter::operator()(const AssignExprPtr &assignExpr) { 346 | const auto &value = evaluate(assignExpr->value); 347 | if (assignExpr->distance == -1) { 348 | globals->assign(assignExpr->name, value); 349 | } else { 350 | environment->assignAt(assignExpr->distance, assignExpr->name, value); 351 | } 352 | return value; 353 | } 354 | 355 | }// namespace lox 356 | -------------------------------------------------------------------------------- /src/interpreter/Interpreter.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERPRETER1_H 2 | #define INTERPRETER1_H 3 | #include "../frontend/AST.h" 4 | #include "Environment.h" 5 | 6 | namespace lox { 7 | struct Return { 8 | LoxObject &value; 9 | ~Return() = default; 10 | }; 11 | struct Nothing {}; 12 | using StmtResult = std::variant; 13 | 14 | class Interpreter { 15 | EnvironmentPtr globals = std::make_shared(); 16 | EnvironmentPtr environment = globals; 17 | int function_depth = 0; 18 | 19 | public: 20 | Interpreter(); 21 | StmtResult operator()(const ExpressionStmtPtr &expressionStmt); 22 | StmtResult operator()(const IfStmtPtr &ifStmtPtr); 23 | StmtResult operator()(const PrintStmtPtr &printStmt); 24 | StmtResult operator()(const VarStmtPtr &varStmt); 25 | StmtResult operator()(const FunctionStmtPtr &functionStmt); 26 | StmtResult operator()(const ReturnStmtPtr &returnStmt); 27 | StmtResult operator()(const BlockStmtPtr &blockStmt); 28 | StmtResult operator()(const WhileStmtPtr &whileStmt); 29 | StmtResult operator()(const ClassStmtPtr &classStmt); 30 | LoxObject operator()(const BinaryExprPtr &binaryExpr); 31 | LoxObject operator()(const CallExprPtr &callExpr); 32 | LoxObject operator()(const GetExprPtr &getExpr); 33 | LoxObject operator()(const SetExprPtr &setExpr); 34 | LoxObject operator()(const ThisExprPtr &thisExpr) const; 35 | LoxObject operator()(const SuperExprPtr &superExpr) const; 36 | LoxObject operator()(const GroupingExprPtr &groupingExpr); 37 | LoxObject operator()(const LiteralExprPtr &literalExpr) const; 38 | LoxObject operator()(const LogicalExprPtr &logicalExpr); 39 | LoxObject operator()(const UnaryExprPtr &unaryExpr); 40 | LoxObject operator()(const VarExprPtr &varExpr) const; 41 | LoxObject operator()(const AssignExprPtr &assignExpr); 42 | 43 | 44 | StmtResult executeBlock(const StmtList &statements, const EnvironmentPtr &newEnvironment); 45 | 46 | [[nodiscard]] LoxObject &lookUpVariable(const Token &name, const Assignable &expr) const; 47 | 48 | LoxObject evaluate(const Expr &expr) { return std::visit(*this, expr); } 49 | StmtResult evaluate(const Stmt &stmt) { return std::visit(*this, stmt); } 50 | void evaluate(const Program &program) { 51 | try { 52 | for (const auto &stmt: program) { evaluate(stmt); } 53 | } catch (const runtime_error &e) { runtimeError(e); } 54 | } 55 | }; 56 | }// namespace lox 57 | #endif//INTERPRETER1_H 58 | -------------------------------------------------------------------------------- /src/interpreter/LoxCallable.h: -------------------------------------------------------------------------------- 1 | #ifndef LOXCALLABLE_H 2 | #define LOXCALLABLE_H 3 | #include "Interpreter.h" 4 | #include "LoxObject.h" 5 | 6 | namespace lox { 7 | 8 | struct LoxCallable { 9 | int _arity = 0; 10 | explicit LoxCallable(const int arity) : _arity{arity} {} 11 | virtual ~LoxCallable() = default; 12 | 13 | virtual LoxObject operator()(Interpreter &interpreter, const std::vector &arguments) = 0; 14 | virtual std::string to_string() = 0; 15 | virtual int arity() { return this->_arity; }; 16 | }; 17 | 18 | }// namespace lox 19 | 20 | #endif//LOXCALLABLE_H 21 | -------------------------------------------------------------------------------- /src/interpreter/LoxClass.cpp: -------------------------------------------------------------------------------- 1 | #include "LoxClass.h" 2 | #include "LoxFunction.h" 3 | #include "LoxInstance.h" 4 | #include "LoxObject.h" 5 | 6 | namespace lox { 7 | 8 | LoxObject LoxClass::operator()(Interpreter &interpreter, const std::vector &arguments) { 9 | const auto &instance = std::make_shared(shared_from_this()); 10 | if (const auto &initializer = this->initializer; initializer != nullptr) { 11 | const auto &function = initializer->bind(instance); 12 | const auto &callable = std::reinterpret_pointer_cast(function); 13 | (*callable)(interpreter, arguments); 14 | } 15 | return instance; 16 | } 17 | 18 | LoxFunctionPtr LoxClass::findMethod(const std::string_view method_name) { 19 | if (methods.contains(method_name)) { return methods[method_name]; } 20 | 21 | if (superClass.has_value()) { return superClass.value()->findMethod(method_name); } 22 | 23 | return nullptr; 24 | } 25 | 26 | int LoxClass::arity() { 27 | return this->initializer == nullptr ? 0 28 | : std::reinterpret_pointer_cast(this->initializer)->arity(); 29 | } 30 | 31 | std::string LoxClass::to_string() { return std::string(name); } 32 | }// namespace lox -------------------------------------------------------------------------------- /src/interpreter/LoxClass.h: -------------------------------------------------------------------------------- 1 | #ifndef LOXCLASS_H 2 | #define LOXCLASS_H 3 | #include "LoxCallable.h" 4 | 5 | #include 6 | 7 | namespace lox { 8 | 9 | struct LoxClass final : LoxCallable, std::enable_shared_from_this { 10 | std::string_view name; 11 | std::optional> superClass; 12 | std::unordered_map methods; 13 | LoxFunctionPtr initializer; 14 | 15 | explicit LoxClass( 16 | const std::string_view &name, const std::optional> &superClass, 17 | const std::unordered_map &methods 18 | ) 19 | : LoxCallable(0), name{name}, superClass{superClass}, methods{methods} { 20 | this->initializer = findMethod("init"); 21 | } 22 | 23 | ~LoxClass() override = default; 24 | 25 | LoxObject operator()(Interpreter &interpreter, const std::vector &arguments) override; 26 | LoxFunctionPtr findMethod(std::string_view method_name); 27 | int arity() override; 28 | std::string to_string() override; 29 | }; 30 | }// namespace lox 31 | #endif//LOXCLASS_H 32 | -------------------------------------------------------------------------------- /src/interpreter/LoxFunction.cpp: -------------------------------------------------------------------------------- 1 | #include "LoxFunction.h" 2 | 3 | namespace lox { 4 | 5 | LoxObject LoxFunction::operator()(Interpreter &interpreter, const std::vector &arguments) { 6 | const auto environment = std::make_shared(closure); 7 | for (int i = 0; i < static_cast(declaration->parameters.size()); i++) { 8 | environment->define(declaration->parameters[i].getLexeme(), arguments[i]); 9 | } 10 | 11 | if (const auto &result = interpreter.executeBlock(declaration->body, environment); 12 | std::holds_alternative(result)) { 13 | if (isInitializer) { return std::move(closure->getAt(0, "this")); } 14 | 15 | return std::move(std::get(result).value); 16 | } 17 | 18 | if (isInitializer) { return std::move(closure->getAt(0, "this")); } 19 | 20 | return LoxNil(); 21 | } 22 | 23 | LoxFunctionPtr LoxFunction::bind(const LoxInstancePtr &instance) { 24 | auto environment = std::make_shared(closure); 25 | environment->define("this", instance); 26 | return std::make_shared(declaration, environment, isInitializer); 27 | } 28 | 29 | std::string LoxFunction::to_string() { return std::format("", std::string(declaration->name.getLexeme())); } 30 | }// namespace lox -------------------------------------------------------------------------------- /src/interpreter/LoxFunction.h: -------------------------------------------------------------------------------- 1 | #ifndef LOXFUNCTION_H 2 | #define LOXFUNCTION_H 3 | #include "LoxCallable.h" 4 | 5 | namespace lox { 6 | 7 | struct LoxFunction final : LoxCallable { 8 | std::shared_ptr declaration; 9 | EnvironmentPtr closure; 10 | bool isInitializer; 11 | 12 | explicit LoxFunction( 13 | const std::shared_ptr &declaration, const EnvironmentPtr &closure, 14 | const bool isInitializer = false 15 | ) 16 | : LoxCallable(static_cast(declaration->parameters.size())), declaration{declaration}, closure{closure}, 17 | isInitializer{isInitializer} {} 18 | 19 | ~LoxFunction() override = default; 20 | 21 | LoxObject operator()(Interpreter &interpreter, const std::vector &arguments) override; 22 | LoxFunctionPtr bind(const LoxInstancePtr &instance); 23 | std::string to_string() override; 24 | }; 25 | }// namespace lox 26 | 27 | #endif//LOXFUNCTION_H 28 | -------------------------------------------------------------------------------- /src/interpreter/LoxInstance.cpp: -------------------------------------------------------------------------------- 1 | #include "LoxInstance.h" 2 | #include "LoxClass.h" 3 | #include "LoxFunction.h" 4 | 5 | #include 6 | 7 | namespace lox { 8 | 9 | LoxObject LoxInstance::get(const Token &name) { 10 | if (fields.contains(name.getLexeme())) { return fields[name.getLexeme()]; } 11 | 12 | if (const auto method = klass->findMethod(name.getLexeme()); method != nullptr) { 13 | const auto instance = shared_from_this(); 14 | return std::reinterpret_pointer_cast(method->bind(instance)); 15 | } 16 | 17 | throw runtime_error(name, "Undefined property '" + std::string(name.getLexeme()) + "'."); 18 | } 19 | 20 | void LoxInstance::set(const Token &name, const LoxObject &value) { fields[name.getLexeme()] = value; } 21 | 22 | std::string LoxInstance::to_string() const { return std::format("{} instance", this->klass->name); } 23 | 24 | }// namespace lox -------------------------------------------------------------------------------- /src/interpreter/LoxInstance.h: -------------------------------------------------------------------------------- 1 | #ifndef LOXINSTANCE_H 2 | #define LOXINSTANCE_H 3 | #include "LoxObject.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace lox { 10 | 11 | struct LoxInstance : std::enable_shared_from_this { 12 | LoxClassPtr klass; 13 | std::unordered_map fields; 14 | 15 | explicit LoxInstance(LoxClassPtr klass) : klass{std::move(klass)} {} 16 | 17 | LoxObject get(const Token &name); 18 | void set(const Token &name, const LoxObject &value); 19 | std::string to_string() const; 20 | }; 21 | 22 | }// namespace lox 23 | 24 | #endif//LOXINSTANCE_H 25 | -------------------------------------------------------------------------------- /src/interpreter/LoxObject.cpp: -------------------------------------------------------------------------------- 1 | #include "LoxObject.h" 2 | #include "../Util.h" 3 | #include "LoxCallable.h" 4 | #include "LoxInstance.h" 5 | 6 | #include 7 | 8 | namespace lox { 9 | 10 | bool isTruthy(const LoxObject &object) { 11 | if (std::holds_alternative(object)) return false; 12 | if (std::holds_alternative(object)) return std::get(object); 13 | return true; 14 | } 15 | 16 | std::string to_string(const LoxObject &object) { 17 | return std::visit( 18 | overloaded{ 19 | [](const LoxBoolean value) -> std::string { return value ? "true" : "false"; }, 20 | [](const LoxNumber value) -> std::string { return std::format("{:g}", value); }, 21 | [](const LoxString &value) -> std::string { return value; }, 22 | [](const LoxCallablePtr &callable) -> std::string { return callable->to_string(); }, 23 | [](const LoxInstancePtr &instance) -> std::string { return instance->to_string(); }, 24 | [](LoxNil) -> std::string { return "nil"; }, 25 | }, 26 | object 27 | ); 28 | } 29 | }// namespace lox -------------------------------------------------------------------------------- /src/interpreter/LoxObject.h: -------------------------------------------------------------------------------- 1 | #ifndef LOXOBJECT_H 2 | #define LOXOBJECT_H 3 | #include "../frontend/Error.h" 4 | #include 5 | #include 6 | #include 7 | 8 | namespace lox { 9 | 10 | // Lox runtime types. 11 | using LoxNil = std::nullptr_t; 12 | using LoxString = std::string; 13 | using LoxNumber = double; 14 | using LoxBoolean = bool; 15 | struct LoxCallable; 16 | struct LoxFunction; 17 | struct LoxClass; 18 | struct LoxInstance; 19 | using LoxCallablePtr = std::shared_ptr; 20 | using LoxFunctionPtr = std::shared_ptr; 21 | using LoxInstancePtr = std::shared_ptr; 22 | using LoxClassPtr = std::shared_ptr; 23 | using LoxObject = std::variant; 24 | 25 | bool isTruthy(const LoxObject &object); 26 | std::string to_string(const LoxObject &object); 27 | 28 | }// namespace lox 29 | 30 | #endif//LOXOBJECT_H 31 | -------------------------------------------------------------------------------- /src/interpreter/NativeFunction.h: -------------------------------------------------------------------------------- 1 | #ifndef NATIVEFUNCTION_H 2 | #define NATIVEFUNCTION_H 3 | #include "LoxCallable.h" 4 | 5 | #include 6 | 7 | namespace lox { 8 | 9 | struct NativeFunction final : LoxCallable { 10 | using NativeFnType = std::function &)>; 11 | NativeFnType function; 12 | 13 | explicit NativeFunction(NativeFnType function, const int arity = 0) 14 | : LoxCallable(arity), function{std::move(function)} {} 15 | 16 | ~NativeFunction() override = default; 17 | 18 | LoxObject operator()(Interpreter & /*interpreter*/, const std::vector &arguments) override { 19 | return function(arguments); 20 | } 21 | 22 | std::string to_string() override { return ""; } 23 | }; 24 | }// namespace lox 25 | 26 | #endif//NATIVEFUNCTION_H 27 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "compiler/ModuleCompiler.h" 2 | #include "frontend/Parser.h" 3 | #include "frontend/Resolver.h" 4 | #include "frontend/Scanner.h" 5 | #include "interpreter/Interpreter.h" 6 | 7 | #include "llvm/Support/CommandLine.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | using namespace llvm; 16 | using namespace lox; 17 | 18 | cl::opt InputFilename(cl::Positional, cl::desc(""), cl::Required); 19 | cl::opt OutputFilename("o", cl::desc("Output LLVM IR file"), cl::value_desc("")); 20 | cl::opt DontOptimize("dontoptimize", cl::desc("Don't optimize the LLVM IR")); 21 | 22 | std::string read_string_from_file(const std::string &file_path) { 23 | const std::ifstream input_stream(file_path, std::ios_base::binary); 24 | 25 | if (input_stream.fail()) { 26 | throw std::runtime_error("Failed to open file"); 27 | } 28 | 29 | std::stringstream buffer; 30 | buffer << input_stream.rdbuf(); 31 | 32 | return buffer.str(); 33 | } 34 | 35 | int main(const int argc, char **argv) { 36 | cl::ParseCommandLineOptions(argc, argv); 37 | 38 | if (InputFilename.empty()) { 39 | std::cout << "source must not be empty"; 40 | return 64; 41 | } 42 | 43 | Scanner Scanner(read_string_from_file(InputFilename)); 44 | const auto &tokens = Scanner.scanTokens(); 45 | Parser Parser(tokens); 46 | const auto &ast = Parser.parse(); 47 | if (hadError) return 65; 48 | 49 | Resolver resolver; 50 | resolver.resolve(ast); 51 | if (hadError) return 65; 52 | 53 | if (!OutputFilename.empty()) { 54 | const ModuleCompiler ModuleCompiler; 55 | ModuleCompiler.evaluate(ast); 56 | 57 | if (!ModuleCompiler.initializeTarget()) { 58 | std::cout << "Could not initialize target machine." << std::endl; 59 | return 65; 60 | } 61 | 62 | if (!DontOptimize.getValue()) { 63 | if (!ModuleCompiler.optimize()) { 64 | std::cout << "Could not optimize." << std::endl; 65 | return 65; 66 | } 67 | } 68 | const auto filename = OutputFilename.getValue(); 69 | if (filename.ends_with(".o")) { 70 | ModuleCompiler.writeObject(filename); 71 | } else if (filename.ends_with(".ll")) { 72 | ModuleCompiler.writeIR(filename); 73 | } else { 74 | std::cout << "Output file should have .ll or .o extension." << std::endl; 75 | return 65; 76 | } 77 | } else { 78 | Interpreter Interpreter; 79 | Interpreter.evaluate(ast); 80 | 81 | if (hadRuntimeError) return 70; 82 | } 83 | 84 | return 0; 85 | } 86 | --------------------------------------------------------------------------------