diff --git a/.github/workflows/utests-llvm.yml b/.github/workflows/utests-llvm.yml new file mode 100644 index 00000000..d7d1ace8 --- /dev/null +++ b/.github/workflows/utests-llvm.yml @@ -0,0 +1,28 @@ +name: Unit tests (LLVM compiler) + +on: + push: + branches: '*' + pull_request: + branches: [ "master" ] + +env: + BUILD_TYPE: Debug + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBSCRATCHCPP_BUILD_UNIT_TESTS=ON -DLIBSCRATCHCPP_USE_LLVM=ON + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j$(nproc --all) + + - name: Run unit tests + run: ctest --test-dir build -V diff --git a/CMakeLists.txt b/CMakeLists.txt index 20c14a01..29f589fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -project(libscratchcpp VERSION 0.11.0 LANGUAGES C CXX) +project(libscratchcpp VERSION 0.12.0 LANGUAGES C CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_STANDARD 17) @@ -9,6 +9,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) option(LIBSCRATCHCPP_BUILD_UNIT_TESTS "Build unit tests" ON) option(LIBSCRATCHCPP_NETWORK_SUPPORT "Support for downloading projects" ON) option(LIBSCRATCHCPP_COMPUTED_GOTO "Support for computed goto" ON) +option(LIBSCRATCHCPP_USE_LLVM "Compile scripts to LLVM IR (work in progress)" OFF) +option(LIBSCRATCHCPP_PRINT_LLVM_IR "Print LLVM IR of compiled Scratch scripts (for debugging)" OFF) if (NOT (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")) # Computed goto not supported on anything except GCC @@ -37,6 +39,7 @@ target_sources(scratchcpp include/scratchcpp/sound.h include/scratchcpp/value.h include/scratchcpp/valuedata.h + include/scratchcpp/value_functions.h include/scratchcpp/entity.h include/scratchcpp/variable.h include/scratchcpp/list.h @@ -45,7 +48,6 @@ target_sources(scratchcpp include/scratchcpp/field.h include/scratchcpp/script.h include/scratchcpp/broadcast.h - include/scratchcpp/compiler.h include/scratchcpp/virtualmachine.h include/scratchcpp/blockprototype.h include/scratchcpp/block.h @@ -65,6 +67,25 @@ target_sources(scratchcpp include/scratchcpp/imonitorhandler.h ) +if (LIBSCRATCHCPP_USE_LLVM) + target_compile_definitions(scratchcpp PUBLIC USE_LLVM) + target_sources(scratchcpp + PUBLIC + include/scratchcpp/dev/compiler.h + include/scratchcpp/dev/executablecode.h + include/scratchcpp/dev/executioncontext.h + ) + + if(LIBSCRATCHCPP_PRINT_LLVM_IR) + target_compile_definitions(scratchcpp PRIVATE PRINT_LLVM_IR) + endif() +else() + target_sources(scratchcpp + PUBLIC + include/scratchcpp/compiler.h + ) +endif() + include(FetchContent) set(ZIP_SRC thirdparty/zip/src) set(UTFCPP_SRC thirdparty/utfcpp/source) @@ -88,12 +109,18 @@ target_link_libraries(scratchcpp PRIVATE scratchcpp-audio) if (LIBSCRATCHCPP_NETWORK_SUPPORT) FetchContent_Declare(cpr GIT_REPOSITORY https://github.com/libcpr/cpr.git - GIT_TAG 3b15fa82ea74739b574d705fea44959b58142eb8) # 1.10.5 + GIT_TAG 225b7454877805f089b3895260438e929bd6d123) # 09-22-2024 FetchContent_MakeAvailable(cpr) target_link_libraries(scratchcpp PRIVATE cpr::cpr) target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_NETWORK_SUPPORT) endif() +if (LIBSCRATCHCPP_USE_LLVM) + include(build/HunterPackages.cmake) + include(build/LLVM.cmake) + target_link_libraries(scratchcpp PRIVATE LLVM) +endif() + target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_LIBRARY) target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION="${PROJECT_VERSION}") target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}) diff --git a/build/HunterPackages.cmake b/build/HunterPackages.cmake new file mode 100644 index 00000000..9dc8f703 --- /dev/null +++ b/build/HunterPackages.cmake @@ -0,0 +1,31 @@ +# https://layle.me/posts/using-llvm-with-cmake/ +# HUNTER_URL is the URL to the latest source code archive on GitHub +# HUNTER_SHA1 is the hash of the downloaded archive + +set(OLD_PROJECT_NAME ${PROJECT_NAME}) +set(PROJECT_NAME "") + +set(HUNTER_URL "https://github.com/scratchcpp/hunter/archive/ee768cdd2c027b5be346f114e726d4b0c4296de6.zip") +set(HUNTER_SHA1 "4A018750743AC656A859C99C655723EAF68EE038") + +set(HUNTER_LLVM_VERSION 19.1.0) +set(HUNTER_LLVM_CMAKE_ARGS + LLVM_ENABLE_CRASH_OVERRIDES=OFF + LLVM_ENABLE_ASSERTIONS=ON + LLVM_ENABLE_ZLIB=OFF + LLVM_ENABLE_RTTI=ON + LLVM_BUILD_EXAMPLES=OFF + LLVM_BUILD_TOOLS=OFF + LLVM_BUILD_LLVM_DYLIB=ON + LLVM_INCLUDE_EXAMPLES=OFF + LLVM_TARGETS_TO_BUILD=host +) + +set(HUNTER_PACKAGES LLVM) + +include(FetchContent) +message(STATUS "Fetching hunter...") +FetchContent_Declare(SetupHunter GIT_REPOSITORY https://github.com/cpp-pm/gate) +FetchContent_MakeAvailable(SetupHunter) + +set(PROJECT_NAME ${OLD_PROJECT_NAME}) diff --git a/build/LLVM.cmake b/build/LLVM.cmake new file mode 100644 index 00000000..bc3b7a03 --- /dev/null +++ b/build/LLVM.cmake @@ -0,0 +1,52 @@ +# https://layle.me/posts/using-llvm-with-cmake/ +# This is an INTERFACE target for LLVM, usage: +# target_link_libraries(${PROJECT_NAME} LLVM) +# The include directories and compile definitions will be properly handled. + +set(CMAKE_FOLDER_LLVM "${CMAKE_FOLDER}") +if(CMAKE_FOLDER) + set(CMAKE_FOLDER "${CMAKE_FOLDER}/LLVM") +else() + set(CMAKE_FOLDER "LLVM") +endif() + +# Find LLVM +find_package(LLVM REQUIRED CONFIG) + +message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") +message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") + +# Split the definitions properly (https://weliveindetail.github.io/blog/post/2017/07/17/notes-setup.html) +separate_arguments(LLVM_DEFINITIONS) + +# Some diagnostics (https://stackoverflow.com/a/17666004/1806760) +message(STATUS "LLVM libraries: ${LLVM_LIBRARIES}") +message(STATUS "LLVM includes: ${LLVM_INCLUDE_DIRS}") +message(STATUS "LLVM definitions: ${LLVM_DEFINITIONS}") +message(STATUS "LLVM tools: ${LLVM_TOOLS_BINARY_DIR}") + +if (NOT TARGET LLVM) + add_library(LLVM INTERFACE) +endif() + +target_include_directories(LLVM SYSTEM INTERFACE ${LLVM_INCLUDE_DIRS}) +target_link_libraries(LLVM INTERFACE ${LLVM_AVAILABLE_LIBS}) +target_compile_definitions(LLVM INTERFACE ${LLVM_DEFINITIONS} -DNOMINMAX) + +set(CMAKE_FOLDER "${CMAKE_FOLDER_LLVM}") +unset(CMAKE_FOLDER_LLVM) + +# MSVC-specific options +if(MSVC) + # This assumes the installed LLVM was built in Release mode + set(CMAKE_C_FLAGS_RELWITHDEBINFO "/ZI /Od /Ob0 /DNDEBUG" CACHE STRING "" FORCE) + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/ZI /Od /Ob0 /DNDEBUG" CACHE STRING "" FORCE) + + if(${LLVM_USE_CRT_RELEASE} STREQUAL "MD") + set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDLL) + elseif(${LLVM_USE_CRT_RELEASE} STREQUAL "MT") + set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded) + else() + message(FATAL_ERROR "Unsupported LLVM_USE_CRT_RELEASE=${LLVM_USE_CRT_RELEASE}") + endif() +endif() diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h new file mode 100644 index 00000000..3b579023 --- /dev/null +++ b/include/scratchcpp/dev/compiler.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include "../global.h" +#include "../spimpl.h" + +namespace libscratchcpp +{ + +class IEngine; +class Target; +class ExecutableCode; +class Variable; +class List; +class Input; +class Field; +class CompilerPrivate; + +/*! \brief The Compiler class provides API for compiling Scratch scripts. */ +class LIBSCRATCHCPP_EXPORT Compiler +{ + public: + enum class StaticType + { + Void, + Number, + Bool, + String, + Unknown + }; + + Compiler(IEngine *engine, Target *target); + Compiler(const Compiler &) = delete; + + IEngine *engine() const; + Target *target() const; + std::shared_ptr block() const; + + std::shared_ptr compile(std::shared_ptr startBlock); + + void addFunctionCall(const std::string &functionName, StaticType returnType = StaticType::Void, const std::vector &argTypes = {}); + void addConstValue(const Value &value); + void addVariableValue(Variable *variable); + void addListContents(List *list); + void addInput(const std::string &name); + + void createAdd(); + void createSub(); + void createMul(); + void createDiv(); + + void createCmpEQ(); + void createCmpGT(); + void createCmpLT(); + + void createAnd(); + void createOr(); + void createNot(); + + void moveToIf(std::shared_ptr substack); + void moveToIfElse(std::shared_ptr substack1, std::shared_ptr substack2); + void moveToRepeatLoop(std::shared_ptr substack); + void moveToWhileLoop(std::shared_ptr substack); + void moveToRepeatUntilLoop(std::shared_ptr substack); + void beginLoopCondition(); + void warp(); + + Input *input(const std::string &name) const; + Field *field(const std::string &name) const; + + const std::unordered_set &unsupportedBlocks() const; + + private: + void addInput(Input *input); + + spimpl::unique_impl_ptr impl; +}; + +} // namespace libscratchcpp diff --git a/include/scratchcpp/dev/executablecode.h b/include/scratchcpp/dev/executablecode.h new file mode 100644 index 00000000..54efea83 --- /dev/null +++ b/include/scratchcpp/dev/executablecode.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "../global.h" + +namespace libscratchcpp +{ + +class ExecutionContext; +class Target; + +/*! \brief The ExecutableCode class represents the code of a compiled Scratch script. */ +class LIBSCRATCHCPP_EXPORT ExecutableCode +{ + public: + virtual ~ExecutableCode() { } + + /*! Runs the script until it finishes or yields. */ + virtual void run(ExecutionContext *context) = 0; + + /*! Stops the code. isFinished() will return true. */ + virtual void kill(ExecutionContext *context) = 0; + + /*! Resets the code to run from the start. */ + virtual void reset(ExecutionContext *context) = 0; + + /*! Returns true if the code is stopped or finished. */ + virtual bool isFinished(ExecutionContext *context) const = 0; + + /*! Pauses the script (when it's executed using run() again) until resolvePromise() is called. */ + virtual void promise() = 0; + + /*! Resolves the promise and resumes the script. */ + virtual void resolvePromise() = 0; + + /*! Creates an execution context for the given Target. */ + virtual std::shared_ptr createExecutionContext(Target *target) const = 0; +}; + +} // namespace libscratchcpp diff --git a/include/scratchcpp/dev/executioncontext.h b/include/scratchcpp/dev/executioncontext.h new file mode 100644 index 00000000..7d7ac877 --- /dev/null +++ b/include/scratchcpp/dev/executioncontext.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../global.h" +#include "../spimpl.h" + +namespace libscratchcpp +{ + +class Target; +class ExecutionContextPrivate; + +/*! \brief The ExecutionContext represents the execution context of a target (can be a clone) with variables, lists, etc. */ +class LIBSCRATCHCPP_EXPORT ExecutionContext +{ + public: + ExecutionContext(Target *target); + ExecutionContext(const ExecutionContext &) = delete; + virtual ~ExecutionContext() { } + + Target *target() const; + + private: + spimpl::unique_impl_ptr impl; +}; + +} // namespace libscratchcpp diff --git a/include/scratchcpp/drawable.h b/include/scratchcpp/drawable.h index 8424bf59..1958ccec 100644 --- a/include/scratchcpp/drawable.h +++ b/include/scratchcpp/drawable.h @@ -3,6 +3,7 @@ #pragma once #include "global.h" +#include "signal.h" #include "spimpl.h" namespace libscratchcpp @@ -27,6 +28,7 @@ class LIBSCRATCHCPP_EXPORT Drawable int layerOrder() const; virtual void setLayerOrder(int newLayerOrder); + sigslot::signal &layerOrderChanged() const; IEngine *engine() const; virtual void setEngine(IEngine *engine); diff --git a/include/scratchcpp/inputvalue.h b/include/scratchcpp/inputvalue.h index 5ecc87df..32c4cb0f 100644 --- a/include/scratchcpp/inputvalue.h +++ b/include/scratchcpp/inputvalue.h @@ -11,7 +11,6 @@ namespace libscratchcpp { class Block; -class Compiler; class Entity; class InputValuePrivate; diff --git a/include/scratchcpp/keyevent.h b/include/scratchcpp/keyevent.h index 2dd80715..012deb89 100644 --- a/include/scratchcpp/keyevent.h +++ b/include/scratchcpp/keyevent.h @@ -31,6 +31,8 @@ class LIBSCRATCHCPP_EXPORT KeyEvent Type type() const; const std::string &name() const; + friend bool operator==(const KeyEvent &ev1, const KeyEvent &ev2) { return ev1.name() == ev2.name(); } + private: spimpl::impl_ptr impl; }; diff --git a/include/scratchcpp/rect.h b/include/scratchcpp/rect.h index cedcbe6e..b4771f3f 100644 --- a/include/scratchcpp/rect.h +++ b/include/scratchcpp/rect.h @@ -32,9 +32,14 @@ class LIBSCRATCHCPP_EXPORT Rect double width() const; double height() const; + void clamp(double left, double top, double right, double bottom); void snapToInt(); bool intersects(const Rect &rect) const; + bool contains(double x, double y) const; + + static void intersected(const Rect &a, const Rect &b, Rect &dst); + static void united(const Rect &a, const Rect &b, Rect &dst); private: spimpl::impl_ptr impl; diff --git a/include/scratchcpp/script.h b/include/scratchcpp/script.h index dbf82b51..895aaaf7 100644 --- a/include/scratchcpp/script.h +++ b/include/scratchcpp/script.h @@ -14,6 +14,9 @@ namespace libscratchcpp class Target; class Block; class IEngine; +#ifdef USE_LLVM +class ExecutableCode; +#endif class Value; class Thread; class Variable; @@ -34,6 +37,11 @@ class LIBSCRATCHCPP_EXPORT Script const std::vector &bytecodeVector() const; void setBytecode(const std::vector &code); +#ifdef USE_LLVM + ExecutableCode *code() const; + void setCode(std::shared_ptr code); +#endif + void setHatPredicateBytecode(const std::vector &code); bool runHatPredicate(Target *target); diff --git a/include/scratchcpp/textbubble.h b/include/scratchcpp/textbubble.h index ce5395f1..baec8945 100644 --- a/include/scratchcpp/textbubble.h +++ b/include/scratchcpp/textbubble.h @@ -11,7 +11,7 @@ namespace libscratchcpp class TextBubblePrivate; /*! \brief The TextBubble class represents a text bubble created using say or think block. */ -class TextBubble : public Drawable +class LIBSCRATCHCPP_EXPORT TextBubble : public Drawable { public: enum class Type diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h index df4db60e..96c781f3 100644 --- a/include/scratchcpp/value.h +++ b/include/scratchcpp/value.h @@ -22,38 +22,18 @@ class LIBSCRATCHCPP_EXPORT Value { public: /*! Constructs a number Value. */ - Value(float numberValue) - { - value_init(&m_data); - value_assign_float(&m_data, numberValue); - } - - /*! Constructs a number Value. */ - Value(double numberValue) + Value(double numberValue = 0.0) { value_init(&m_data); value_assign_double(&m_data, numberValue); } /*! Constructs a number Value. */ - Value(int numberValue = 0) - { - value_init(&m_data); - value_assign_int(&m_data, numberValue); - } - - /*! Constructs a number Value. */ - Value(size_t numberValue) - { - value_init(&m_data); - value_assign_size_t(&m_data, numberValue); - } - - /*! Constructs a number Value. */ - Value(long numberValue) + template::value>> + Value(T numberValue) { value_init(&m_data); - value_assign_long(&m_data, numberValue); + value_assign_double(&m_data, numberValue); } /*! Constructs a boolean Value. */ @@ -77,13 +57,6 @@ class LIBSCRATCHCPP_EXPORT Value value_assign_cstring(&m_data, stringValue); } - /*! Constructs a special Value. */ - Value(SpecialValue specialValue) - { - value_init(&m_data); - value_assign_special(&m_data, specialValue); - } - /*! Constructs value from ValueData. */ Value(const ValueData &v) { @@ -181,27 +154,16 @@ class LIBSCRATCHCPP_EXPORT Value /*! Replaces the value with modulo of the value and the given value. */ void mod(const Value &v) { value_mod(&m_data, &v.m_data, &m_data); } - const Value &operator=(float v) - { - value_assign_float(&m_data, v); - return *this; - } - const Value &operator=(double v) { value_assign_double(&m_data, v); return *this; } - const Value &operator=(int v) + template::value>> + const Value &operator=(T v) { - value_assign_int(&m_data, v); - return *this; - } - - const Value &operator=(long v) - { - value_assign_long(&m_data, v); + value_assign_double(&m_data, v); return *this; } diff --git a/include/scratchcpp/value_functions.h b/include/scratchcpp/value_functions.h index f5707166..d4409a0b 100644 --- a/include/scratchcpp/value_functions.h +++ b/include/scratchcpp/value_functions.h @@ -11,15 +11,10 @@ extern "C" LIBSCRATCHCPP_EXPORT void value_init(ValueData *v); - LIBSCRATCHCPP_EXPORT void value_assign_float(ValueData *v, float numberValue); LIBSCRATCHCPP_EXPORT void value_assign_double(ValueData *v, double numberValue); - LIBSCRATCHCPP_EXPORT void value_assign_int(ValueData *v, int numberValue); - LIBSCRATCHCPP_EXPORT void value_assign_size_t(ValueData *v, size_t numberValue); - LIBSCRATCHCPP_EXPORT void value_assign_long(ValueData *v, long numberValue); LIBSCRATCHCPP_EXPORT void value_assign_bool(ValueData *v, bool boolValue); LIBSCRATCHCPP_EXPORT void value_assign_string(ValueData *v, const std::string &stringValue); LIBSCRATCHCPP_EXPORT void value_assign_cstring(ValueData *v, const char *stringValue); - LIBSCRATCHCPP_EXPORT void value_assign_special(ValueData *v, SpecialValue specialValue); LIBSCRATCHCPP_EXPORT void value_assign_copy(ValueData *v, const ValueData *another); LIBSCRATCHCPP_EXPORT bool value_isInfinity(const ValueData *v); @@ -36,8 +31,14 @@ extern "C" LIBSCRATCHCPP_EXPORT double value_toDouble(const ValueData *v); LIBSCRATCHCPP_EXPORT bool value_toBool(const ValueData *v); LIBSCRATCHCPP_EXPORT void value_toString(const ValueData *v, std::string *dst); + LIBSCRATCHCPP_EXPORT char *value_toCString(const ValueData *v); LIBSCRATCHCPP_EXPORT void value_toUtf16(const ValueData *v, std::u16string *dst); + LIBSCRATCHCPP_EXPORT char *value_doubleToCString(double v); + LIBSCRATCHCPP_EXPORT const char *value_boolToCString(bool v); + LIBSCRATCHCPP_EXPORT double value_stringToDouble(const char *s); + LIBSCRATCHCPP_EXPORT bool value_stringToBool(const char *s); + LIBSCRATCHCPP_EXPORT void value_add(const ValueData *v1, const ValueData *v2, ValueData *dst); LIBSCRATCHCPP_EXPORT void value_subtract(const ValueData *v1, const ValueData *v2, ValueData *dst); LIBSCRATCHCPP_EXPORT void value_multiply(const ValueData *v1, const ValueData *v2, ValueData *dst); diff --git a/include/scratchcpp/valuedata.h b/include/scratchcpp/valuedata.h index 58c863cf..571928ca 100644 --- a/include/scratchcpp/valuedata.h +++ b/include/scratchcpp/valuedata.h @@ -9,22 +9,11 @@ namespace libscratchcpp { -enum class LIBSCRATCHCPP_EXPORT SpecialValue -{ - Infinity, - NegativeInfinity, - NaN -}; - enum class LIBSCRATCHCPP_EXPORT ValueType { - Integer = 0, - Double = 1, - Bool = 2, - String = 3, - Infinity = -1, - NegativeInfinity = -2, - NaN = -3 + Number = 0, + Bool = 1, + String = 2 }; extern "C" @@ -32,10 +21,10 @@ extern "C" /*! \brief The ValueData struct holds the data of Value. It's used in compiled Scratch code for better performance. */ struct LIBSCRATCHCPP_EXPORT ValueData { + // NOTE: Any changes must also be done in the LLVM code builder! union { - long intValue; - double doubleValue; + double numberValue; bool boolValue; char *stringValue; }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5e570d8d..f4e1fcc2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,8 +11,15 @@ target_sources(scratchcpp rect_p.h ) -add_subdirectory(blocks) +if (NOT LIBSCRATCHCPP_USE_LLVM) + add_subdirectory(blocks) +endif() + add_subdirectory(engine) add_subdirectory(internal) add_subdirectory(scratch) add_subdirectory(audio) + +if (LIBSCRATCHCPP_USE_LLVM) + add_subdirectory(dev) +endif() diff --git a/src/blocks/controlblocks.cpp b/src/blocks/controlblocks.cpp index ca3c4884..ab5aab69 100644 --- a/src/blocks/controlblocks.cpp +++ b/src/blocks/controlblocks.cpp @@ -59,6 +59,7 @@ void ControlBlocks::registerBlocks(IEngine *engine) engine->addFieldValue(this, "all", StopAll); engine->addFieldValue(this, "this script", StopThisScript); engine->addFieldValue(this, "other scripts in sprite", StopOtherScriptsInSprite); + engine->addFieldValue(this, "other scripts in stage", StopOtherScriptsInSprite); } void ControlBlocks::compileRepeatForever(Compiler *compiler) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 5d2cf55a..f142f34c 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -964,8 +964,9 @@ void LooksBlocks::randomBackdropImpl(VirtualMachine *vm) if (Stage *stage = vm->engine()->stage()) { std::size_t count = stage->costumes().size(); - if (count > 0) - stage->setCostumeIndex(rng->randint(0, count - 1)); + if (count > 1) { + stage->setCostumeIndex(rng->randintExcept(0, count - 1, stage->costumeIndex())); + } } } diff --git a/src/blocks/operatorblocks.cpp b/src/blocks/operatorblocks.cpp index cd200ad6..376ae27a 100644 --- a/src/blocks/operatorblocks.cpp +++ b/src/blocks/operatorblocks.cpp @@ -261,9 +261,9 @@ unsigned int OperatorBlocks::op_ln(VirtualMachine *vm) { const Value &v = *vm->getInput(0, 1); if (v < 0) - vm->replaceReturnValue(Value(SpecialValue::NaN), 1); + vm->replaceReturnValue(std::numeric_limits::quiet_NaN(), 1); else if (v == 0 || v.isNaN()) - vm->replaceReturnValue(Value(SpecialValue::NegativeInfinity), 1); + vm->replaceReturnValue(-std::numeric_limits::infinity(), 1); else if (!v.isInfinity()) vm->replaceReturnValue(std::log(v.toDouble()), 1); return 0; @@ -273,9 +273,9 @@ unsigned int OperatorBlocks::op_log(VirtualMachine *vm) { const Value &v = *vm->getInput(0, 1); if (v < 0) - vm->replaceReturnValue(Value(SpecialValue::NaN), 1); + vm->replaceReturnValue(std::numeric_limits::quiet_NaN(), 1); else if (v == 0 || v.isNaN()) - vm->replaceReturnValue(Value(SpecialValue::NegativeInfinity), 1); + vm->replaceReturnValue(-std::numeric_limits::infinity(), 1); else if (!v.isInfinity()) vm->replaceReturnValue(std::log10(v.toDouble()), 1); return 0; @@ -284,7 +284,9 @@ unsigned int OperatorBlocks::op_log(VirtualMachine *vm) unsigned int OperatorBlocks::op_eexp(VirtualMachine *vm) { const Value *v = vm->getInput(0, 1); - if (v->isNegativeInfinity()) + if (v->isNaN()) + vm->replaceReturnValue(1, 1); + else if (v->isNegativeInfinity()) vm->replaceReturnValue(0, 1); else if (!v->isInfinity()) vm->replaceReturnValue(std::exp(v->toDouble()), 1); @@ -294,7 +296,9 @@ unsigned int OperatorBlocks::op_eexp(VirtualMachine *vm) unsigned int OperatorBlocks::op_10exp(VirtualMachine *vm) { const Value *v = vm->getInput(0, 1); - if (v->isNegativeInfinity()) + if (v->isNaN()) + vm->replaceReturnValue(1, 1); + else if (v->isNegativeInfinity()) vm->replaceReturnValue(0, 1); else if (!v->isInfinity()) vm->replaceReturnValue(std::pow(10, v->toDouble()), 1); diff --git a/src/dev/CMakeLists.txt b/src/dev/CMakeLists.txt new file mode 100644 index 00000000..696178cd --- /dev/null +++ b/src/dev/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(blocks) +add_subdirectory(engine) diff --git a/src/dev/blocks/CMakeLists.txt b/src/dev/blocks/CMakeLists.txt new file mode 100644 index 00000000..d7416856 --- /dev/null +++ b/src/dev/blocks/CMakeLists.txt @@ -0,0 +1,115 @@ +target_sources(scratchcpp + PRIVATE + blocks.cpp + blocks.h +) + +# Motion blocks +option(LIBSCRATCHCPP_ENABLE_MOTION_BLOCKS "Motion blocks support" ON) +if (LIBSCRATCHCPP_ENABLE_MOTION_BLOCKS) + target_compile_definitions(scratchcpp PRIVATE ENABLE_MOTION_BLOCKS) + target_sources(scratchcpp + PRIVATE + motionblocks.cpp + motionblocks.h + ) +endif() + +# Looks blocks +option(LIBSCRATCHCPP_ENABLE_LOOKS_BLOCKS "Looks blocks support" ON) +if (LIBSCRATCHCPP_ENABLE_LOOKS_BLOCKS) + target_compile_definitions(scratchcpp PRIVATE ENABLE_LOOKS_BLOCKS) + target_sources(scratchcpp + PRIVATE + looksblocks.cpp + looksblocks.h + ) +endif() + +# Sound blocks +option(LIBSCRATCHCPP_ENABLE_SOUND_BLOCKS "Sound blocks support" ON) +if (LIBSCRATCHCPP_ENABLE_SOUND_BLOCKS) + target_compile_definitions(scratchcpp PRIVATE ENABLE_SOUND_BLOCKS) + target_sources(scratchcpp + PRIVATE + soundblocks.cpp + soundblocks.h + ) +endif() + +# Event blocks +option(LIBSCRATCHCPP_ENABLE_EVENT_BLOCKS "Event blocks support" ON) +if (LIBSCRATCHCPP_ENABLE_EVENT_BLOCKS) + target_compile_definitions(scratchcpp PRIVATE ENABLE_EVENT_BLOCKS) + target_sources(scratchcpp + PRIVATE + eventblocks.cpp + eventblocks.h + ) +endif() + +# Control blocks +option(LIBSCRATCHCPP_ENABLE_CONTROL_BLOCKS "Control blocks support" ON) +if (LIBSCRATCHCPP_ENABLE_CONTROL_BLOCKS) + target_compile_definitions(scratchcpp PRIVATE ENABLE_CONTROL_BLOCKS) + target_sources(scratchcpp + PRIVATE + controlblocks.cpp + controlblocks.h + ) +endif() + +# Sensing blocks +option(LIBSCRATCHCPP_ENABLE_SENSING_BLOCKS "Sensing blocks support" ON) +if (LIBSCRATCHCPP_ENABLE_SENSING_BLOCKS) + target_compile_definitions(scratchcpp PRIVATE ENABLE_SENSING_BLOCKS) + target_sources(scratchcpp + PRIVATE + sensingblocks.cpp + sensingblocks.h + ) +endif() + +# Operator blocks +option(LIBSCRATCHCPP_ENABLE_OPERATOR_BLOCKS "Operator blocks support" ON) +if (LIBSCRATCHCPP_ENABLE_OPERATOR_BLOCKS) + target_compile_definitions(scratchcpp PRIVATE ENABLE_OPERATOR_BLOCKS) + target_sources(scratchcpp + PRIVATE + operatorblocks.cpp + operatorblocks.h + ) +endif() + +# Variable blocks +option(LIBSCRATCHCPP_ENABLE_VARIABLE_BLOCKS "Variable blocks support" ON) +if (LIBSCRATCHCPP_ENABLE_VARIABLE_BLOCKS) + target_compile_definitions(scratchcpp PRIVATE ENABLE_VARIABLE_BLOCKS) + target_sources(scratchcpp + PRIVATE + variableblocks.cpp + variableblocks.h + ) +endif() + +# List blocks +option(LIBSCRATCHCPP_ENABLE_LIST_BLOCKS "List blocks support" ON) +if (LIBSCRATCHCPP_ENABLE_LIST_BLOCKS) + target_compile_definitions(scratchcpp PRIVATE ENABLE_LIST_BLOCKS) + target_sources(scratchcpp + PRIVATE + listblocks.cpp + listblocks.h + ) +endif() + +# Custom blocks +option(LIBSCRATCHCPP_ENABLE_CUSTOM_BLOCKS "Custom blocks support" ON) +if (LIBSCRATCHCPP_ENABLE_CUSTOM_BLOCKS) + target_compile_definitions(scratchcpp PRIVATE ENABLE_CUSTOM_BLOCKS) + target_sources(scratchcpp + PRIVATE + customblocks.cpp + customblocks.h + ) +endif() diff --git a/src/dev/blocks/blocks.cpp b/src/dev/blocks/blocks.cpp new file mode 100644 index 00000000..c352547b --- /dev/null +++ b/src/dev/blocks/blocks.cpp @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "blocks.h" + +#ifdef ENABLE_MOTION_BLOCKS +#include "motionblocks.h" +#endif +#ifdef ENABLE_LOOKS_BLOCKS +#include "looksblocks.h" +#endif +#ifdef ENABLE_SOUND_BLOCKS +#include "soundblocks.h" +#endif +#ifdef ENABLE_EVENT_BLOCKS +#include "eventblocks.h" +#endif +#ifdef ENABLE_CONTROL_BLOCKS +#include "controlblocks.h" +#endif +#ifdef ENABLE_SENSING_BLOCKS +#include "sensingblocks.h" +#endif +#ifdef ENABLE_OPERATOR_BLOCKS +#include "operatorblocks.h" +#endif +#ifdef ENABLE_VARIABLE_BLOCKS +#include "variableblocks.h" +#endif +#ifdef ENABLE_LIST_BLOCKS +#include "listblocks.h" +#endif +#ifdef ENABLE_CUSTOM_BLOCKS +#include "customblocks.h" +#endif + +using namespace libscratchcpp; + +Blocks Blocks::m_instance; + +const std::vector> &Blocks::extensions() +{ + return m_instance.m_extensions; +} + +Blocks::Blocks() +{ +#ifdef ENABLE_MOTION_BLOCKS + m_extensions.push_back(std::make_shared()); +#endif +#ifdef ENABLE_LOOKS_BLOCKS + m_extensions.push_back(std::make_shared()); +#endif +#ifdef ENABLE_SOUND_BLOCKS + m_extensions.push_back(std::make_shared()); +#endif +#ifdef ENABLE_EVENT_BLOCKS + m_extensions.push_back(std::make_shared()); +#endif +#ifdef ENABLE_CONTROL_BLOCKS + m_extensions.push_back(std::make_shared()); +#endif +#ifdef ENABLE_SENSING_BLOCKS + m_extensions.push_back(std::make_shared()); +#endif +#ifdef ENABLE_OPERATOR_BLOCKS + m_extensions.push_back(std::make_shared()); +#endif +#ifdef ENABLE_VARIABLE_BLOCKS + m_extensions.push_back(std::make_shared()); +#endif +#ifdef ENABLE_LIST_BLOCKS + m_extensions.push_back(std::make_shared()); +#endif +#ifdef ENABLE_CUSTOM_BLOCKS + m_extensions.push_back(std::make_shared()); +#endif +} + +void Blocks::registerExtensions() +{ + for (auto ext : m_extensions) + ScratchConfiguration::registerExtension(ext); +} diff --git a/src/dev/blocks/blocks.h b/src/dev/blocks/blocks.h new file mode 100644 index 00000000..56be7a34 --- /dev/null +++ b/src/dev/blocks/blocks.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +namespace libscratchcpp +{ + +class IExtension; + +class Blocks +{ + public: + static const std::vector> &extensions(); + + private: + Blocks(); + void registerExtensions(); + + static Blocks m_instance; // use static initialization + std::vector> m_extensions; +}; + +} // namespace libscratchcpp diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp new file mode 100644 index 00000000..937e5943 --- /dev/null +++ b/src/dev/blocks/controlblocks.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "controlblocks.h" + +using namespace libscratchcpp; + +std::string ControlBlocks::name() const +{ + return "Control"; +} + +std::string ControlBlocks::description() const +{ + return name() + " blocks"; +} + +void ControlBlocks::registerBlocks(IEngine *engine) +{ +} diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h new file mode 100644 index 00000000..155f9296 --- /dev/null +++ b/src/dev/blocks/controlblocks.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class ControlBlocks : public IExtension +{ + public: + std::string name() const override; + std::string description() const override; + + void registerBlocks(IEngine *engine) override; +}; + +} // namespace libscratchcpp diff --git a/src/dev/blocks/customblocks.cpp b/src/dev/blocks/customblocks.cpp new file mode 100644 index 00000000..4a1657b3 --- /dev/null +++ b/src/dev/blocks/customblocks.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "customblocks.h" + +using namespace libscratchcpp; + +std::string CustomBlocks::name() const +{ + return "Custom blocks"; +} + +std::string CustomBlocks::description() const +{ + return name(); +} + +void CustomBlocks::registerBlocks(IEngine *engine) +{ +} diff --git a/src/dev/blocks/customblocks.h b/src/dev/blocks/customblocks.h new file mode 100644 index 00000000..433afa36 --- /dev/null +++ b/src/dev/blocks/customblocks.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class CustomBlocks : public IExtension +{ + public: + std::string name() const override; + std::string description() const override; + + void registerBlocks(IEngine *engine) override; +}; + +} // namespace libscratchcpp diff --git a/src/dev/blocks/eventblocks.cpp b/src/dev/blocks/eventblocks.cpp new file mode 100644 index 00000000..07477984 --- /dev/null +++ b/src/dev/blocks/eventblocks.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "eventblocks.h" + +using namespace libscratchcpp; + +std::string EventBlocks::name() const +{ + return "Events"; +} + +std::string libscratchcpp::EventBlocks::description() const +{ + return "Event blocks"; +} + +void EventBlocks::registerBlocks(IEngine *engine) +{ +} diff --git a/src/dev/blocks/eventblocks.h b/src/dev/blocks/eventblocks.h new file mode 100644 index 00000000..c395c3de --- /dev/null +++ b/src/dev/blocks/eventblocks.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class EventBlocks : public IExtension +{ + public: + std::string name() const override; + std::string description() const override; + + void registerBlocks(IEngine *engine) override; +}; + +} // namespace libscratchcpp diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp new file mode 100644 index 00000000..8b4602ad --- /dev/null +++ b/src/dev/blocks/listblocks.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "listblocks.h" + +using namespace libscratchcpp; + +std::string ListBlocks::name() const +{ + return "Lists"; +} + +std::string ListBlocks::description() const +{ + return "List blocks"; +} + +void ListBlocks::registerBlocks(IEngine *engine) +{ +} diff --git a/src/dev/blocks/listblocks.h b/src/dev/blocks/listblocks.h new file mode 100644 index 00000000..edf01e8c --- /dev/null +++ b/src/dev/blocks/listblocks.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class ListBlocks : public IExtension +{ + public: + std::string name() const override; + std::string description() const override; + + void registerBlocks(IEngine *engine) override; +}; + +} // namespace libscratchcpp diff --git a/src/dev/blocks/looksblocks.cpp b/src/dev/blocks/looksblocks.cpp new file mode 100644 index 00000000..0d471fb0 --- /dev/null +++ b/src/dev/blocks/looksblocks.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "looksblocks.h" + +using namespace libscratchcpp; + +std::string LooksBlocks::name() const +{ + return "Looks"; +} + +std::string LooksBlocks::description() const +{ + return name() + " blocks"; +} + +void LooksBlocks::registerBlocks(IEngine *engine) +{ +} diff --git a/src/dev/blocks/looksblocks.h b/src/dev/blocks/looksblocks.h new file mode 100644 index 00000000..8bfa5f18 --- /dev/null +++ b/src/dev/blocks/looksblocks.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class LooksBlocks : public IExtension +{ + public: + std::string name() const override; + std::string description() const override; + + void registerBlocks(IEngine *engine) override; +}; + +} // namespace libscratchcpp diff --git a/src/dev/blocks/motionblocks.cpp b/src/dev/blocks/motionblocks.cpp new file mode 100644 index 00000000..e44a673c --- /dev/null +++ b/src/dev/blocks/motionblocks.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "motionblocks.h" + +using namespace libscratchcpp; + +std::string MotionBlocks::name() const +{ + return "Motion"; +} + +std::string MotionBlocks::description() const +{ + return name() + " blocks"; +} + +void MotionBlocks::registerBlocks(IEngine *engine) +{ +} diff --git a/src/dev/blocks/motionblocks.h b/src/dev/blocks/motionblocks.h new file mode 100644 index 00000000..125b4073 --- /dev/null +++ b/src/dev/blocks/motionblocks.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class MotionBlocks : public IExtension +{ + public: + std::string name() const override; + std::string description() const override; + + void registerBlocks(IEngine *engine) override; +}; + +} // namespace libscratchcpp diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp new file mode 100644 index 00000000..8d4cbb92 --- /dev/null +++ b/src/dev/blocks/operatorblocks.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "operatorblocks.h" + +using namespace libscratchcpp; + +std::string OperatorBlocks::name() const +{ + return "Operators"; +} + +std::string OperatorBlocks::description() const +{ + return "Operator blocks"; +} + +void OperatorBlocks::registerBlocks(IEngine *engine) +{ +} diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h new file mode 100644 index 00000000..af5bd7b5 --- /dev/null +++ b/src/dev/blocks/operatorblocks.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class OperatorBlocks : public IExtension +{ + public: + std::string name() const override; + std::string description() const override; + + void registerBlocks(IEngine *engine) override; +}; + +} // namespace libscratchcpp diff --git a/src/dev/blocks/sensingblocks.cpp b/src/dev/blocks/sensingblocks.cpp new file mode 100644 index 00000000..42896ecb --- /dev/null +++ b/src/dev/blocks/sensingblocks.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "sensingblocks.h" + +using namespace libscratchcpp; + +std::string SensingBlocks::name() const +{ + return "Sensing"; +} + +std::string SensingBlocks::description() const +{ + return name() + " blocks"; +} + +void SensingBlocks::registerBlocks(IEngine *engine) +{ +} diff --git a/src/dev/blocks/sensingblocks.h b/src/dev/blocks/sensingblocks.h new file mode 100644 index 00000000..ab934fb0 --- /dev/null +++ b/src/dev/blocks/sensingblocks.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class SensingBlocks : public IExtension +{ + public: + std::string name() const override; + std::string description() const override; + + void registerBlocks(IEngine *engine) override; +}; + +} // namespace libscratchcpp diff --git a/src/dev/blocks/soundblocks.cpp b/src/dev/blocks/soundblocks.cpp new file mode 100644 index 00000000..4a440a9a --- /dev/null +++ b/src/dev/blocks/soundblocks.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "soundblocks.h" + +using namespace libscratchcpp; + +std::string SoundBlocks::name() const +{ + return "Sound"; +} + +std::string SoundBlocks::description() const +{ + return name() + " blocks"; +} + +void SoundBlocks::registerBlocks(IEngine *engine) +{ +} diff --git a/src/dev/blocks/soundblocks.h b/src/dev/blocks/soundblocks.h new file mode 100644 index 00000000..afae6c9c --- /dev/null +++ b/src/dev/blocks/soundblocks.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class SoundBlocks : public IExtension +{ + public: + std::string name() const override; + std::string description() const override; + + void registerBlocks(IEngine *engine) override; +}; + +} // namespace libscratchcpp diff --git a/src/dev/blocks/variableblocks.cpp b/src/dev/blocks/variableblocks.cpp new file mode 100644 index 00000000..6d0f464f --- /dev/null +++ b/src/dev/blocks/variableblocks.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "variableblocks.h" + +using namespace libscratchcpp; + +std::string VariableBlocks::name() const +{ + return "Variables"; +} + +std::string VariableBlocks::description() const +{ + return "Variable blocks"; +} + +void VariableBlocks::registerBlocks(IEngine *engine) +{ +} diff --git a/src/dev/blocks/variableblocks.h b/src/dev/blocks/variableblocks.h new file mode 100644 index 00000000..6c4144ac --- /dev/null +++ b/src/dev/blocks/variableblocks.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class VariableBlocks : public IExtension +{ + public: + std::string name() const override; + std::string description() const override; + + void registerBlocks(IEngine *engine) override; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/CMakeLists.txt b/src/dev/engine/CMakeLists.txt new file mode 100644 index 00000000..0f4018fd --- /dev/null +++ b/src/dev/engine/CMakeLists.txt @@ -0,0 +1,19 @@ +target_sources(scratchcpp + PRIVATE + compiler.cpp + compiler_p.cpp + compiler_p.h + executioncontext.cpp + executioncontext_p.cpp + executioncontext_p.h + internal/icodebuilder.h + internal/icodebuilderfactory.h + internal/codebuilderfactory.cpp + internal/codebuilderfactory.h + internal/llvmcodebuilder.cpp + internal/llvmcodebuilder.h + internal/llvmexecutablecode.cpp + internal/llvmexecutablecode.h + internal/llvmexecutioncontext.cpp + internal/llvmexecutioncontext.h +) diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp new file mode 100644 index 00000000..7ff6737e --- /dev/null +++ b/src/dev/engine/compiler.cpp @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include + +#include "compiler_p.h" +#include "internal/icodebuilderfactory.h" +#include "internal/icodebuilder.h" + +using namespace libscratchcpp; + +/*! Constructs Compiler. */ +Compiler::Compiler(IEngine *engine, Target *target) : + impl(spimpl::make_unique_impl(engine, target)) +{ +} + +/*! Returns the Engine of the project. */ +IEngine *Compiler::engine() const +{ + return impl->engine; +} + +/*! Returns the Target of this compiler. */ +Target *Compiler::target() const +{ + return impl->target; +} + +/*! Returns currently compiled block. */ +std::shared_ptr Compiler::block() const +{ + return impl->block; +} + +/*! Compiles the script starting with the given block. */ +std::shared_ptr Compiler::compile(std::shared_ptr startBlock) +{ + impl->builder = impl->builderFactory->create(startBlock->id(), false); + impl->substackTree.clear(); + impl->substackHit = false; + impl->warp = false; + impl->block = startBlock; + + while (impl->block) { + if (impl->block->compileFunction()) + impl->block->compile(this); + else { + std::cout << "warning: unsupported block: " << impl->block->opcode() << std::endl; + impl->unsupportedBlocks.insert(impl->block->opcode()); + } + + if (impl->substackHit) { + impl->substackHit = false; + continue; + } + + if (impl->block) + impl->block = impl->block->next(); + + if (!impl->block && !impl->substackTree.empty()) + impl->substackEnd(); + } + + return impl->builder->finalize(); +} + +/*! + * Adds a call to the given function.\n + * For example: extern "C" bool some_block(Target *target, double arg1, const char *arg2) + */ +void Compiler::addFunctionCall(const std::string &functionName, StaticType returnType, const std::vector &argTypes) +{ + impl->builder->addFunctionCall(functionName, returnType, argTypes); +} + +/*! Adds a constant value to the compiled code. */ +void Compiler::addConstValue(const Value &value) +{ + impl->builder->addConstValue(value); +} + +/*! Adds the given variable to the code (to read it). */ +void Compiler::addVariableValue(Variable *variable) +{ + impl->builder->addVariableValue(variable); +} + +/*! Adds the given list to the code (to read its string representation). */ +void Compiler::addListContents(List *list) +{ + impl->builder->addListContents(list); +} + +/*! Compiles the given input (resolved by name) and adds it to the compiled code. */ +void Compiler::addInput(const std::string &name) +{ + addInput(impl->block->inputAt(impl->block->findInput(name)).get()); +} + +/*! Creates an add instruction from the last 2 values. */ +void Compiler::createAdd() +{ + impl->builder->createAdd(); +} + +/*! Creates a subtract instruction from the last 2 values. */ +void Compiler::createSub() +{ + impl->builder->createSub(); +} + +/*! Creates a multiply instruction from the last 2 values. */ +void Compiler::createMul() +{ + impl->builder->createMul(); +} + +/*! Creates a divide instruction from the last 2 values. */ +void Compiler::createDiv() +{ + impl->builder->createDiv(); +} + +/*! Creates an equality comparison instruction using the last 2 values. */ +void Compiler::createCmpEQ() +{ + impl->builder->createCmpEQ(); +} + +/*! Creates a greater than comparison instruction using the last 2 values. */ +void Compiler::createCmpGT() +{ + impl->builder->createCmpGT(); +} + +/*! Creates a lower than comparison instruction using the last 2 values. */ +void Compiler::createCmpLT() +{ + impl->builder->createCmpLT(); +} + +/*! Creates an AND operation using the last 2 values. */ +void Compiler::createAnd() +{ + impl->builder->createAnd(); +} + +/*! Creates an OR operation using the last 2 values. */ +void Compiler::createOr() +{ + impl->builder->createOr(); +} + +/*! Creates a NOT operation using the last value. */ +void Compiler::createNot() +{ + impl->builder->createNot(); +} + +/*! Jumps to the given if substack. */ +void Compiler::moveToIf(std::shared_ptr substack) +{ + if (!substack) + return; // ignore empty if statements + + impl->substackHit = true; + impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::IfStatement }); + impl->block = substack; + impl->builder->beginIfStatement(); +} + +/*! Jumps to the given if/else substack. The second substack is used for the else branch. */ +void Compiler::moveToIfElse(std::shared_ptr substack1, std::shared_ptr substack2) +{ + if (!substack1 && !substack2) + return; // ignore empty if statements + + impl->substackHit = true; + impl->substackTree.push_back({ { impl->block, substack2 }, CompilerPrivate::SubstackType::IfStatement }); + impl->block = substack1; + impl->builder->beginIfStatement(); + + if (!impl->block) + impl->substackEnd(); +} + +/*! Jumps to the given repeat loop substack. */ +void Compiler::moveToRepeatLoop(std::shared_ptr substack) +{ + impl->substackHit = true; + impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::Loop }); + impl->block = substack; + impl->builder->beginRepeatLoop(); + + if (!impl->block) + impl->substackEnd(); +} + +/*! Jumps to the given while loop substack. */ +void Compiler::moveToWhileLoop(std::shared_ptr substack) +{ + impl->substackHit = true; + impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::Loop }); + impl->block = substack; + impl->builder->beginWhileLoop(); + + if (!impl->block) + impl->substackEnd(); +} + +/*! Jumps to the given until loop substack. */ +void Compiler::moveToRepeatUntilLoop(std::shared_ptr substack) +{ + impl->substackHit = true; + impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::Loop }); + impl->block = substack; + impl->builder->beginRepeatUntilLoop(); + + if (!impl->block) + impl->substackEnd(); +} + +/*! Begins a while/until loop condition. */ +void Compiler::beginLoopCondition() +{ + impl->builder->beginLoopCondition(); +} + +/*! Makes current script run without screen refresh. */ +void Compiler::warp() +{ + impl->warp = true; +} + +/*! Convenience method which returns the field with the given name. */ +Input *Compiler::input(const std::string &name) const +{ + return impl->block->inputAt(impl->block->findInput(name)).get(); +} + +/*! Convenience method which returns the field with the given name. */ +Field *Compiler::field(const std::string &name) const +{ + return impl->block->fieldAt(impl->block->findField(name)).get(); +} + +/*! Returns unsupported block opcodes which were found when compiling. */ +const std::unordered_set &Compiler::unsupportedBlocks() const +{ + return impl->unsupportedBlocks; +} + +void Compiler::addInput(Input *input) +{ + if (!input) { + addConstValue(Value()); + return; + } + + switch (input->type()) { + case Input::Type::Shadow: + case Input::Type::NoShadow: { + if (input->pointsToDropdownMenu()) + addConstValue(input->selectedMenuItem()); + else { + auto previousBlock = impl->block; + impl->block = input->valueBlock(); + + if (impl->block) { + if (impl->block->compileFunction()) + impl->block->compile(this); + else { + std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl; + impl->unsupportedBlocks.insert(impl->block->opcode()); + addConstValue(Value()); + } + } else + addConstValue(input->primaryValue()->value()); + + impl->block = previousBlock; + } + + break; + } + + case Input::Type::ObscuredShadow: { + auto previousBlock = impl->block; + impl->block = input->valueBlock(); + if (impl->block) { + if (impl->block->compileFunction()) + impl->block->compile(this); + else { + std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl; + impl->unsupportedBlocks.insert(impl->block->opcode()); + addConstValue(Value()); + } + } else + input->primaryValue()->compile(this); + + impl->block = previousBlock; + break; + } + } +} diff --git a/src/dev/engine/compiler_p.cpp b/src/dev/engine/compiler_p.cpp new file mode 100644 index 00000000..cff14fe9 --- /dev/null +++ b/src/dev/engine/compiler_p.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "compiler_p.h" + +#include "internal/codebuilderfactory.h" +#include "internal/icodebuilder.h" + +using namespace libscratchcpp; + +CompilerPrivate::CompilerPrivate(IEngine *engine, Target *target) : + engine(engine), + target(target) +{ + if (!builderFactory) + builderFactory = CodeBuilderFactory::instance().get(); +} + +void CompilerPrivate::substackEnd() +{ + auto &parent = substackTree.back(); + + switch (parent.second) { + case SubstackType::Loop: + // Yield at loop end if not running without screen refresh + /*if (!warp) + builder->yield();*/ + + builder->endLoop(); + break; + + case SubstackType::IfStatement: + if (parent.first.second) { + builder->beginElseBranch(); + block = parent.first.second; + parent.first.second = nullptr; + return; + } else + builder->endIf(); + + break; + } + + auto parentBlock = parent.first.first; + + if (parentBlock) + block = parentBlock->next(); + else + block = nullptr; + + substackTree.pop_back(); + + if (!block && !substackTree.empty()) + substackEnd(); +} diff --git a/src/dev/engine/compiler_p.h b/src/dev/engine/compiler_p.h new file mode 100644 index 00000000..059d143b --- /dev/null +++ b/src/dev/engine/compiler_p.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include + +namespace libscratchcpp +{ + +class IEngine; +class Target; +class Block; +class ICodeBuilderFactory; +class ICodeBuilder; + +struct CompilerPrivate +{ + enum class SubstackType + { + Loop, + IfStatement + }; + + CompilerPrivate(IEngine *engine, Target *target); + + void substackEnd(); + + IEngine *engine = nullptr; + Target *target = nullptr; + + std::shared_ptr block; + std::vector, std::shared_ptr>, SubstackType>> substackTree; + bool substackHit = false; + bool warp = false; + + static inline ICodeBuilderFactory *builderFactory = nullptr; + std::shared_ptr builder; + + std::unordered_set unsupportedBlocks; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/executioncontext.cpp b/src/dev/engine/executioncontext.cpp new file mode 100644 index 00000000..c45156c6 --- /dev/null +++ b/src/dev/engine/executioncontext.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "executioncontext_p.h" + +using namespace libscratchcpp; + +/*! Constructs ExecutionContext. */ +ExecutionContext::ExecutionContext(Target *target) : + impl(spimpl::make_unique_impl(target)) +{ +} + +/*! Returns the Target of this context. */ +Target *ExecutionContext::target() const +{ + return impl->target; +} diff --git a/src/dev/engine/executioncontext_p.cpp b/src/dev/engine/executioncontext_p.cpp new file mode 100644 index 00000000..f2efa1e1 --- /dev/null +++ b/src/dev/engine/executioncontext_p.cpp @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "executioncontext_p.h" + +using namespace libscratchcpp; + +ExecutionContextPrivate::ExecutionContextPrivate(Target *target) : + target(target) +{ +} diff --git a/src/dev/engine/executioncontext_p.h b/src/dev/engine/executioncontext_p.h new file mode 100644 index 00000000..fd7fad51 --- /dev/null +++ b/src/dev/engine/executioncontext_p.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace libscratchcpp +{ + +class Target; + +struct ExecutionContextPrivate +{ + ExecutionContextPrivate(Target *target); + + Target *target = nullptr; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/codebuilderfactory.cpp b/src/dev/engine/internal/codebuilderfactory.cpp new file mode 100644 index 00000000..5f955bdc --- /dev/null +++ b/src/dev/engine/internal/codebuilderfactory.cpp @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "codebuilderfactory.h" +#include "llvmcodebuilder.h" + +using namespace libscratchcpp; + +std::shared_ptr CodeBuilderFactory::m_instance = std::make_shared(); + +std::shared_ptr CodeBuilderFactory::instance() +{ + return m_instance; +} + +std::shared_ptr CodeBuilderFactory::create(const std::string &id, bool warp) const +{ + return std::make_shared(id, warp); +} diff --git a/src/dev/engine/internal/codebuilderfactory.h b/src/dev/engine/internal/codebuilderfactory.h new file mode 100644 index 00000000..8de574ab --- /dev/null +++ b/src/dev/engine/internal/codebuilderfactory.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "icodebuilderfactory.h" + +namespace libscratchcpp +{ + +class CodeBuilderFactory : public ICodeBuilderFactory +{ + public: + static std::shared_ptr instance(); + std::shared_ptr create(const std::string &id, bool warp) const override; + + private: + static std::shared_ptr m_instance; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h new file mode 100644 index 00000000..f5852098 --- /dev/null +++ b/src/dev/engine/internal/icodebuilder.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class Value; +class Variable; +class List; +class ExecutableCode; + +class ICodeBuilder +{ + public: + virtual ~ICodeBuilder() { } + + virtual std::shared_ptr finalize() = 0; + + virtual void addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const std::vector &argTypes) = 0; + virtual void addConstValue(const Value &value) = 0; + virtual void addVariableValue(Variable *variable) = 0; + virtual void addListContents(List *list) = 0; + + virtual void createAdd() = 0; + virtual void createSub() = 0; + virtual void createMul() = 0; + virtual void createDiv() = 0; + + virtual void createCmpEQ() = 0; + virtual void createCmpGT() = 0; + virtual void createCmpLT() = 0; + + virtual void createAnd() = 0; + virtual void createOr() = 0; + virtual void createNot() = 0; + + virtual void beginIfStatement() = 0; + virtual void beginElseBranch() = 0; + virtual void endIf() = 0; + + virtual void beginRepeatLoop() = 0; + virtual void beginWhileLoop() = 0; + virtual void beginRepeatUntilLoop() = 0; + virtual void beginLoopCondition() = 0; + virtual void endLoop() = 0; + + virtual void yield() = 0; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/icodebuilderfactory.h b/src/dev/engine/internal/icodebuilderfactory.h new file mode 100644 index 00000000..4438e5f8 --- /dev/null +++ b/src/dev/engine/internal/icodebuilderfactory.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class ICodeBuilder; + +class ICodeBuilderFactory +{ + public: + virtual ~ICodeBuilderFactory() { } + + virtual std::shared_ptr create(const std::string &id, bool warp) const = 0; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp new file mode 100644 index 00000000..22f3d552 --- /dev/null +++ b/src/dev/engine/internal/llvmcodebuilder.cpp @@ -0,0 +1,1267 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include + +#include "llvmcodebuilder.h" +#include "llvmexecutablecode.h" + +using namespace libscratchcpp; + +static std::unordered_map + TYPE_MAP = { { ValueType::Number, Compiler::StaticType::Number }, { ValueType::Bool, Compiler::StaticType::Bool }, { ValueType::String, Compiler::StaticType::String } }; + +LLVMCodeBuilder::LLVMCodeBuilder(const std::string &id, bool warp) : + m_id(id), + m_module(std::make_unique(id, m_ctx)), + m_builder(m_ctx), + m_defaultWarp(warp), + m_warp(warp) +{ + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetAsmParser(); + + m_constValues.push_back({}); + m_regs.push_back({}); + initTypes(); +} + +std::shared_ptr LLVMCodeBuilder::finalize() +{ + // Do not create coroutine if there are no yield instructions + if (!m_warp) { + auto it = std::find_if(m_steps.begin(), m_steps.end(), [](const Step &step) { return step.type == Step::Type::Yield; }); + + if (it == m_steps.end()) + m_warp = true; + } + + // Create function + // void *f(Target *) + llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); + llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, pointerType, false); + llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "f", m_module.get()); + + llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_ctx, "entry", func); + m_builder.SetInsertPoint(entry); + + // Init coroutine + Coroutine coro; + + if (!m_warp) + coro = initCoroutine(func); + + std::vector ifStatements; + std::vector loops; + m_heap.clear(); + + // Execute recorded steps + for (const Step &step : m_steps) { + switch (step.type) { + case Step::Type::FunctionCall: { + std::vector types; + std::vector args; + + // Add target pointer arg + assert(func->arg_size() == 1); + types.push_back(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0)); + args.push_back(func->getArg(0)); + + // Args + for (auto &arg : step.args) { + types.push_back(getType(arg.first)); + args.push_back(castValue(arg.second, arg.first)); + } + + llvm::Value *ret = m_builder.CreateCall(resolveFunction(step.functionName, llvm::FunctionType::get(getType(step.functionReturnType), types, false)), args); + + if (step.functionReturnReg) { + step.functionReturnReg->value = ret; + + if (step.functionReturnType == Compiler::StaticType::String) + m_heap.push_back(step.functionReturnReg->value); + } + + break; + } + + case Step::Type::Add: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + llvm::Value *num1 = removeNaN(castValue(arg1.second, arg1.first)); + llvm::Value *num2 = removeNaN(castValue(arg2.second, arg2.first)); + step.functionReturnReg->value = m_builder.CreateFAdd(num1, num2); + break; + } + + case Step::Type::Sub: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + llvm::Value *num1 = removeNaN(castValue(arg1.second, arg1.first)); + llvm::Value *num2 = removeNaN(castValue(arg2.second, arg2.first)); + step.functionReturnReg->value = m_builder.CreateFSub(num1, num2); + break; + } + + case Step::Type::Mul: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + llvm::Value *num1 = removeNaN(castValue(arg1.second, arg1.first)); + llvm::Value *num2 = removeNaN(castValue(arg2.second, arg2.first)); + step.functionReturnReg->value = m_builder.CreateFMul(num1, num2); + break; + } + + case Step::Type::Div: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + llvm::Value *num1 = removeNaN(castValue(arg1.second, arg1.first)); + llvm::Value *num2 = removeNaN(castValue(arg2.second, arg2.first)); + step.functionReturnReg->value = m_builder.CreateFDiv(num1, num2); + break; + } + + case Step::Type::CmpEQ: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0].second; + const auto &arg2 = step.args[1].second; + step.functionReturnReg->value = createComparison(arg1, arg2, Comparison::EQ); + break; + } + + case Step::Type::CmpGT: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0].second; + const auto &arg2 = step.args[1].second; + step.functionReturnReg->value = createComparison(arg1, arg2, Comparison::GT); + break; + } + + case Step::Type::CmpLT: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0].second; + const auto &arg2 = step.args[1].second; + step.functionReturnReg->value = createComparison(arg1, arg2, Comparison::LT); + break; + } + + case Step::Type::And: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + llvm::Value *bool1 = castValue(arg1.second, arg1.first); + llvm::Value *bool2 = castValue(arg2.second, arg2.first); + step.functionReturnReg->value = m_builder.CreateAnd(bool1, bool2); + break; + } + + case Step::Type::Or: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + llvm::Value *bool1 = castValue(arg1.second, arg1.first); + llvm::Value *bool2 = castValue(arg2.second, arg2.first); + step.functionReturnReg->value = m_builder.CreateOr(bool1, bool2); + break; + } + + case Step::Type::Not: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + llvm::Value *value = castValue(arg.second, arg.first); + step.functionReturnReg->value = m_builder.CreateNot(value); + break; + } + + case Step::Type::Yield: + if (!m_warp) { + freeHeap(); + m_builder.CreateStore(m_builder.getInt1(true), coro.didSuspend); + llvm::BasicBlock *resumeBranch = llvm::BasicBlock::Create(m_ctx, "", func); + llvm::Value *noneToken = llvm::ConstantTokenNone::get(m_ctx); + llvm::Value *suspendResult = m_builder.CreateCall(llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_suspend), { noneToken, m_builder.getInt1(false) }); + llvm::SwitchInst *sw = m_builder.CreateSwitch(suspendResult, coro.suspend, 2); + sw->addCase(m_builder.getInt8(0), resumeBranch); + sw->addCase(m_builder.getInt8(1), coro.cleanup); + m_builder.SetInsertPoint(resumeBranch); + } + + break; + + case Step::Type::BeginIf: { + IfStatement statement; + statement.beforeIf = m_builder.GetInsertBlock(); + statement.body = llvm::BasicBlock::Create(m_ctx, "", func); + + // Use last reg + assert(step.args.size() == 1); + const auto ® = step.args[0]; + assert(reg.first == Compiler::StaticType::Bool); + freeHeap(); + statement.condition = castValue(reg.second, reg.first); + + // Switch to body branch + m_builder.SetInsertPoint(statement.body); + + ifStatements.push_back(statement); + break; + } + + case Step::Type::BeginElse: { + assert(!ifStatements.empty()); + IfStatement &statement = ifStatements.back(); + + // Jump to the branch after the if statement + assert(!statement.afterIf); + statement.afterIf = llvm::BasicBlock::Create(m_ctx, "", func); + freeHeap(); + m_builder.CreateBr(statement.afterIf); + + // Create else branch + assert(!statement.elseBranch); + statement.elseBranch = llvm::BasicBlock::Create(m_ctx, "", func); + + // Since there's an else branch, the conditional instruction should jump to it + m_builder.SetInsertPoint(statement.beforeIf); + m_builder.CreateCondBr(statement.condition, statement.body, statement.elseBranch); + + // Switch to the else branch + m_builder.SetInsertPoint(statement.elseBranch); + break; + } + + case Step::Type::EndIf: { + assert(!ifStatements.empty()); + IfStatement &statement = ifStatements.back(); + + // Jump to the branch after the if statement + if (!statement.afterIf) + statement.afterIf = llvm::BasicBlock::Create(m_ctx, "", func); + + freeHeap(); + m_builder.CreateBr(statement.afterIf); + + if (statement.elseBranch) { + } else { + // If there wasn't an 'else' branch, create a conditional instruction which skips the if statement if false + m_builder.SetInsertPoint(statement.beforeIf); + m_builder.CreateCondBr(statement.condition, statement.body, statement.afterIf); + } + + // Switch to the branch after the if statement + m_builder.SetInsertPoint(statement.afterIf); + + ifStatements.pop_back(); + break; + } + + case Step::Type::BeginRepeatLoop: { + Loop loop; + loop.isRepeatLoop = true; + + // index = 0 + llvm::Constant *zero = llvm::ConstantInt::get(m_builder.getInt64Ty(), 0, true); + loop.index = m_builder.CreateAlloca(m_builder.getInt64Ty()); + m_builder.CreateStore(zero, loop.index); + + // Create branches + llvm::BasicBlock *roundBranch = llvm::BasicBlock::Create(m_ctx, "", func); + loop.conditionBranch = llvm::BasicBlock::Create(m_ctx, "", func); + loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", func); + + // Use last reg for count + assert(step.args.size() == 1); + const auto ® = step.args[0]; + assert(reg.first == Compiler::StaticType::Number); + llvm::Value *count = castValue(reg.second, reg.first); + + // Clamp count if <= 0 (we can skip the loop if count is not positive) + llvm::Value *comparison = m_builder.CreateFCmpULE(count, llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0))); + freeHeap(); + m_builder.CreateCondBr(comparison, loop.afterLoop, roundBranch); + + // Round (Scratch-specific behavior) + m_builder.SetInsertPoint(roundBranch); + llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::round, { count->getType() }); + count = m_builder.CreateCall(roundFunc, { count }); + count = m_builder.CreateFPToSI(count, m_builder.getInt64Ty()); // cast to signed integer + + // Jump to condition branch + m_builder.CreateBr(loop.conditionBranch); + + // Check index + m_builder.SetInsertPoint(loop.conditionBranch); + + llvm::BasicBlock *body = llvm::BasicBlock::Create(m_ctx, "", func); + + if (!loop.afterLoop) + loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", func); + + llvm::Value *currentIndex = m_builder.CreateLoad(m_builder.getInt64Ty(), loop.index); + comparison = m_builder.CreateICmpULT(currentIndex, count); + m_builder.CreateCondBr(comparison, body, loop.afterLoop); + + // Switch to body branch + m_builder.SetInsertPoint(body); + + loops.push_back(loop); + break; + } + + case Step::Type::BeginWhileLoop: { + assert(!loops.empty()); + Loop &loop = loops.back(); + + // Create branches + llvm::BasicBlock *body = llvm::BasicBlock::Create(m_ctx, "", func); + loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", func); + + // Use last reg + assert(step.args.size() == 1); + const auto ® = step.args[0]; + assert(reg.first == Compiler::StaticType::Bool); + llvm::Value *condition = castValue(reg.second, reg.first); + freeHeap(); + m_builder.CreateCondBr(condition, body, loop.afterLoop); + + // Switch to body branch + m_builder.SetInsertPoint(body); + break; + } + + case Step::Type::BeginRepeatUntilLoop: { + assert(!loops.empty()); + Loop &loop = loops.back(); + + // Create branches + llvm::BasicBlock *body = llvm::BasicBlock::Create(m_ctx, "", func); + loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", func); + + // Use last reg + assert(step.args.size() == 1); + const auto ® = step.args[0]; + assert(reg.first == Compiler::StaticType::Bool); + llvm::Value *condition = castValue(reg.second, reg.first); + freeHeap(); + m_builder.CreateCondBr(condition, loop.afterLoop, body); + + // Switch to body branch + m_builder.SetInsertPoint(body); + break; + } + + case Step::Type::BeginLoopCondition: { + Loop loop; + loop.isRepeatLoop = false; + loop.conditionBranch = llvm::BasicBlock::Create(m_ctx, "", func); + freeHeap(); + m_builder.CreateBr(loop.conditionBranch); + m_builder.SetInsertPoint(loop.conditionBranch); + loops.push_back(loop); + break; + } + + case Step::Type::EndLoop: { + assert(!loops.empty()); + Loop &loop = loops.back(); + + if (loop.isRepeatLoop) { + // Increment index + llvm::Value *currentIndex = m_builder.CreateLoad(m_builder.getInt64Ty(), loop.index); + llvm::Value *incremented = m_builder.CreateAdd(currentIndex, llvm::ConstantInt::get(m_builder.getInt64Ty(), 1, true)); + m_builder.CreateStore(incremented, loop.index); + } + + // Jump to the condition branch + freeHeap(); + m_builder.CreateBr(loop.conditionBranch); + + // Switch to the branch after the loop + m_builder.SetInsertPoint(loop.afterLoop); + + loops.pop_back(); + break; + } + } + } + + freeHeap(); + + // Add final suspend point + if (!m_warp) { + llvm::BasicBlock *endBranch = llvm::BasicBlock::Create(m_ctx, "end", func); + llvm::BasicBlock *finalSuspendBranch = llvm::BasicBlock::Create(m_ctx, "finalSuspend", func); + m_builder.CreateCondBr(m_builder.CreateLoad(m_builder.getInt1Ty(), coro.didSuspend), finalSuspendBranch, endBranch); + + m_builder.SetInsertPoint(finalSuspendBranch); + llvm::Value *suspendResult = + m_builder.CreateCall(llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_suspend), { llvm::ConstantTokenNone::get(m_ctx), m_builder.getInt1(true) }); + llvm::SwitchInst *sw = m_builder.CreateSwitch(suspendResult, coro.suspend, 2); + sw->addCase(m_builder.getInt8(0), endBranch); // unreachable + sw->addCase(m_builder.getInt8(1), coro.cleanup); + + m_builder.SetInsertPoint(endBranch); + } + + // End and verify the function + if (!m_tmpRegs.empty()) { + std::cout + << "warning: " << m_tmpRegs.size() << " registers were leaked by script '" << m_module->getName().str() << "', function '" << func->getName().str() + << "' (if you see this as a regular user, this is a bug and should be reported)" << std::endl; + } + + if (m_warp) + m_builder.CreateRet(llvm::ConstantPointerNull::get(pointerType)); + else + m_builder.CreateBr(coro.freeMemRet); + + verifyFunction(func); + + // Create resume function + // bool resume(void *) + funcType = llvm::FunctionType::get(m_builder.getInt1Ty(), pointerType, false); + func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "resume", m_module.get()); + + entry = llvm::BasicBlock::Create(m_ctx, "entry", func); + m_builder.SetInsertPoint(entry); + + if (m_warp) + m_builder.CreateRet(m_builder.getInt1(true)); + else { + llvm::Value *coroHandle = func->getArg(0); + m_builder.CreateCall(llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_resume), { coroHandle }); + llvm::Value *done = m_builder.CreateCall(llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_done), { coroHandle }); + m_builder.CreateRet(done); + } + + verifyFunction(func); + +#ifdef PRINT_LLVM_IR + std::cout << std::endl << "=== LLVM IR (" << m_module->getName().str() << ") ===" << std::endl; + m_module->print(llvm::outs(), nullptr); + std::cout << "==============" << std::endl << std::endl; +#endif + + // Optimize + optimize(); + + return std::make_shared(std::move(m_module)); +} + +void LLVMCodeBuilder::addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const std::vector &argTypes) +{ + Step step(Step::Type::FunctionCall); + step.functionName = functionName; + + assert(m_tmpRegs.size() >= argTypes.size()); + size_t j = 0; + + for (size_t i = m_tmpRegs.size() - argTypes.size(); i < m_tmpRegs.size(); i++) + step.args.push_back({ argTypes[j++], m_tmpRegs[i] }); + + m_tmpRegs.erase(m_tmpRegs.end() - argTypes.size(), m_tmpRegs.end()); + + step.functionReturnType = returnType; + + if (returnType != Compiler::StaticType::Void) { + auto reg = std::make_shared(returnType); + reg->isRawValue = true; + step.functionReturnReg = reg; + m_regs[m_currentFunction].push_back(reg); + m_tmpRegs.push_back(reg); + } + + m_steps.push_back(step); +} + +void LLVMCodeBuilder::addConstValue(const Value &value) +{ + auto reg = std::make_shared(TYPE_MAP[value.type()]); + reg->isConstValue = true; + reg->constValue = value; + m_regs[m_currentFunction].push_back(reg); + m_tmpRegs.push_back(reg); +} + +void LLVMCodeBuilder::addVariableValue(Variable *variable) +{ +} + +void LLVMCodeBuilder::addListContents(List *list) +{ +} + +void LLVMCodeBuilder::createAdd() +{ + createOp(Step::Type::Add, Compiler::StaticType::Number, Compiler::StaticType::Number, 2); +} + +void LLVMCodeBuilder::createSub() +{ + createOp(Step::Type::Sub, Compiler::StaticType::Number, Compiler::StaticType::Number, 2); +} + +void LLVMCodeBuilder::createMul() +{ + createOp(Step::Type::Mul, Compiler::StaticType::Number, Compiler::StaticType::Number, 2); +} + +void LLVMCodeBuilder::createDiv() +{ + createOp(Step::Type::Div, Compiler::StaticType::Number, Compiler::StaticType::Number, 2); +} + +void LLVMCodeBuilder::createCmpEQ() +{ + createOp(Step::Type::CmpEQ, Compiler::StaticType::Bool, Compiler::StaticType::Number, 2); +} + +void LLVMCodeBuilder::createCmpGT() +{ + createOp(Step::Type::CmpGT, Compiler::StaticType::Bool, Compiler::StaticType::Number, 2); +} + +void LLVMCodeBuilder::createCmpLT() +{ + createOp(Step::Type::CmpLT, Compiler::StaticType::Bool, Compiler::StaticType::Number, 2); +} + +void LLVMCodeBuilder::createAnd() +{ + createOp(Step::Type::And, Compiler::StaticType::Bool, Compiler::StaticType::Bool, 2); +} + +void LLVMCodeBuilder::createOr() +{ + createOp(Step::Type::Or, Compiler::StaticType::Bool, Compiler::StaticType::Bool, 2); +} + +void LLVMCodeBuilder::createNot() +{ + createOp(Step::Type::Not, Compiler::StaticType::Bool, Compiler::StaticType::Bool, 1); +} + +void LLVMCodeBuilder::beginIfStatement() +{ + Step step(Step::Type::BeginIf); + assert(!m_tmpRegs.empty()); + step.args.push_back({ Compiler::StaticType::Bool, m_tmpRegs.back() }); + m_tmpRegs.pop_back(); + m_steps.push_back(step); +} + +void LLVMCodeBuilder::beginElseBranch() +{ + m_steps.push_back(Step(Step::Type::BeginElse)); +} + +void LLVMCodeBuilder::endIf() +{ + m_steps.push_back(Step(Step::Type::EndIf)); +} + +void LLVMCodeBuilder::beginRepeatLoop() +{ + Step step(Step::Type::BeginRepeatLoop); + assert(!m_tmpRegs.empty()); + step.args.push_back({ Compiler::StaticType::Number, m_tmpRegs.back() }); + m_tmpRegs.pop_back(); + m_steps.push_back(step); +} + +void LLVMCodeBuilder::beginWhileLoop() +{ + Step step(Step::Type::BeginWhileLoop); + assert(!m_tmpRegs.empty()); + step.args.push_back({ Compiler::StaticType::Bool, m_tmpRegs.back() }); + m_tmpRegs.pop_back(); + m_steps.push_back(step); +} + +void LLVMCodeBuilder::beginRepeatUntilLoop() +{ + Step step(Step::Type::BeginRepeatUntilLoop); + assert(!m_tmpRegs.empty()); + step.args.push_back({ Compiler::StaticType::Bool, m_tmpRegs.back() }); + m_tmpRegs.pop_back(); + m_steps.push_back(step); +} + +void LLVMCodeBuilder::beginLoopCondition() +{ + m_steps.push_back(Step(Step::Type::BeginLoopCondition)); +} + +void LLVMCodeBuilder::endLoop() +{ + if (!m_warp) + m_steps.push_back(Step(Step::Type::Yield)); + + m_steps.push_back(Step(Step::Type::EndLoop)); +} + +void LLVMCodeBuilder::yield() +{ + m_steps.push_back({ Step::Type::Yield }); + m_currentFunction++; + + assert(m_currentFunction == m_constValues.size()); + m_constValues.push_back({}); + + assert(m_currentFunction == m_regs.size()); + m_regs.push_back({}); +} + +void LLVMCodeBuilder::initTypes() +{ + // Create the ValueData struct + llvm::Type *unionType = m_builder.getInt64Ty(); // 64 bits is the largest size + + llvm::Type *valueType = llvm::Type::getInt32Ty(m_ctx); // Assuming ValueType is a 32-bit enum + llvm::Type *sizeType = llvm::Type::getInt64Ty(m_ctx); // size_t + + m_valueDataType = llvm::StructType::create(m_ctx, "ValueData"); + m_valueDataType->setBody({ unionType, valueType, sizeType }); +} + +LLVMCodeBuilder::Coroutine LLVMCodeBuilder::initCoroutine(llvm::Function *func) +{ + // Set presplitcoroutine attribute + func->setPresplitCoroutine(); + + // Coroutine intrinsics + llvm::Function *coroId = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_id); + llvm::Function *coroSize = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_size, m_builder.getInt64Ty()); + llvm::Function *coroBegin = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_begin); + llvm::Function *coroEnd = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_end); + llvm::Function *coroFree = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_free); + + // Init coroutine + Coroutine coro; + llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); + llvm::Constant *nullPointer = llvm::ConstantPointerNull::get(pointerType); + llvm::Value *coroIdRet = m_builder.CreateCall(coroId, { m_builder.getInt32(8), nullPointer, nullPointer, nullPointer }); + + // Allocate memory + llvm::Value *coroSizeRet = m_builder.CreateCall(coroSize, std::nullopt, "size"); + llvm::Function *mallocFunc = llvm::Function::Create(llvm::FunctionType::get(pointerType, { m_builder.getInt64Ty() }, false), llvm::Function::ExternalLinkage, "malloc", m_module.get()); + llvm::Value *alloc = m_builder.CreateCall(mallocFunc, coroSizeRet, "mem"); + + // Begin + coro.handle = m_builder.CreateCall(coroBegin, { coroIdRet, alloc }); + coro.didSuspend = m_builder.CreateAlloca(m_builder.getInt1Ty(), nullptr, "didSuspend"); + m_builder.CreateStore(m_builder.getInt1(false), coro.didSuspend); + llvm::BasicBlock *entry = m_builder.GetInsertBlock(); + + // Create suspend branch + coro.suspend = llvm::BasicBlock::Create(m_ctx, "suspend", func); + m_builder.SetInsertPoint(coro.suspend); + m_builder.CreateCall(coroEnd, { coro.handle, m_builder.getInt1(false), llvm::ConstantTokenNone::get(m_ctx) }); + m_builder.CreateRet(coro.handle); + + // Create free branches + coro.freeMemRet = llvm::BasicBlock::Create(m_ctx, "freeMemRet", func); + m_builder.SetInsertPoint(coro.freeMemRet); + m_builder.CreateFree(alloc); + m_builder.CreateRet(llvm::ConstantPointerNull::get(pointerType)); + + llvm::BasicBlock *freeBranch = llvm::BasicBlock::Create(m_ctx, "free", func); + m_builder.SetInsertPoint(freeBranch); + m_builder.CreateFree(alloc); + m_builder.CreateBr(coro.suspend); + + // Create cleanup branch + coro.cleanup = llvm::BasicBlock::Create(m_ctx, "cleanup", func); + m_builder.SetInsertPoint(coro.cleanup); + llvm::Value *mem = m_builder.CreateCall(coroFree, { coroIdRet, coro.handle }); + llvm::Value *needFree = m_builder.CreateIsNotNull(mem); + m_builder.CreateCondBr(needFree, freeBranch, coro.suspend); + + m_builder.SetInsertPoint(entry); + return coro; +} + +void LLVMCodeBuilder::verifyFunction(llvm::Function *func) +{ + if (llvm::verifyFunction(*func, &llvm::errs())) { + llvm::errs() << "error: LLVM function verficiation failed!\n"; + llvm::errs() << "script hat ID: " << m_id << "\n"; + } +} + +void LLVMCodeBuilder::optimize() +{ + llvm::PassBuilder passBuilder; + llvm::LoopAnalysisManager loopAnalysisManager; + llvm::FunctionAnalysisManager functionAnalysisManager; + llvm::CGSCCAnalysisManager cGSCCAnalysisManager; + llvm::ModuleAnalysisManager moduleAnalysisManager; + + passBuilder.registerModuleAnalyses(moduleAnalysisManager); + passBuilder.registerCGSCCAnalyses(cGSCCAnalysisManager); + passBuilder.registerFunctionAnalyses(functionAnalysisManager); + passBuilder.registerLoopAnalyses(loopAnalysisManager); + passBuilder.crossRegisterProxies(loopAnalysisManager, functionAnalysisManager, cGSCCAnalysisManager, moduleAnalysisManager); + + llvm::ModulePassManager modulePassManager = passBuilder.buildPerModuleDefaultPipeline(llvm::OptimizationLevel::O3); + modulePassManager.run(*m_module, moduleAnalysisManager); +} + +void LLVMCodeBuilder::freeHeap() +{ + // Free dynamically allocated memory + for (llvm::Value *ptr : m_heap) + m_builder.CreateFree(ptr); + + m_heap.clear(); +} + +llvm::Value *LLVMCodeBuilder::castValue(std::shared_ptr reg, Compiler::StaticType targetType) +{ + if (reg->isConstValue) + return castConstValue(reg->constValue, targetType); + + if (reg->isRawValue) + return castRawValue(reg, targetType); + + assert(reg->type != Compiler::StaticType::Void && targetType != Compiler::StaticType::Void); + + switch (targetType) { + case Compiler::StaticType::Number: + switch (reg->type) { + case Compiler::StaticType::Number: { + // Read number directly + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0); + return m_builder.CreateLoad(m_builder.getDoubleTy(), ptr); + } + + case Compiler::StaticType::Bool: { + // Read boolean and cast to double + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0); + llvm::Value *boolValue = m_builder.CreateLoad(m_builder.getInt1Ty(), ptr); + return m_builder.CreateSIToFP(boolValue, m_builder.getDoubleTy()); + } + + case Compiler::StaticType::String: { + // Convert string to double + return m_builder.CreateCall(resolve_value_toDouble(), reg->value); + } + + default: + assert(false); + return nullptr; + } + + case Compiler::StaticType::Bool: + switch (reg->type) { + case Compiler::StaticType::Number: { + // True if != 0 + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0); + llvm::Value *numberValue = m_builder.CreateLoad(m_builder.getDoubleTy(), ptr); + return m_builder.CreateFCmpONE(numberValue, llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0))); + } + + case Compiler::StaticType::Bool: { + // Read boolean directly + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0); + return m_builder.CreateLoad(m_builder.getInt1Ty(), ptr); + } + + case Compiler::StaticType::String: + // Convert string to bool + return m_builder.CreateCall(resolve_value_toBool(), reg->value); + + default: + assert(false); + return nullptr; + } + + case Compiler::StaticType::String: + switch (reg->type) { + case Compiler::StaticType::Number: + case Compiler::StaticType::Bool: { + // Cast to string + llvm::Value *ptr = m_builder.CreateCall(resolve_value_toCString(), reg->value); + m_heap.push_back(ptr); // deallocate later + return ptr; + } + + case Compiler::StaticType::String: { + // Read string pointer directly + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0); + return m_builder.CreateLoad(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), ptr); + } + + default: + assert(false); + return nullptr; + } + + default: + assert(false); + return nullptr; + } +} + +llvm::Value *LLVMCodeBuilder::castRawValue(std::shared_ptr reg, Compiler::StaticType targetType) +{ + if (reg->type == targetType) + return reg->value; + + switch (targetType) { + case Compiler::StaticType::Number: + switch (reg->type) { + case Compiler::StaticType::Bool: + // Cast bool to double + return m_builder.CreateUIToFP(reg->value, m_builder.getDoubleTy()); + + case Compiler::StaticType::String: { + // Convert string to double + return m_builder.CreateCall(resolve_value_stringToDouble(), reg->value); + } + + default: + assert(false); + return nullptr; + } + + case Compiler::StaticType::Bool: + switch (reg->type) { + case Compiler::StaticType::Number: + // Cast double to bool (true if != 0) + return m_builder.CreateFCmpONE(reg->value, llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0))); + + case Compiler::StaticType::String: + // Convert string to bool + return m_builder.CreateCall(resolve_value_stringToBool(), reg->value); + + default: + assert(false); + return nullptr; + } + + case Compiler::StaticType::String: + switch (reg->type) { + case Compiler::StaticType::Number: { + // Convert double to string + llvm::Value *ptr = m_builder.CreateCall(resolve_value_doubleToCString(), reg->value); + m_heap.push_back(ptr); // deallocate later + return ptr; + } + + case Compiler::StaticType::Bool: { + // Convert bool to string + llvm::Value *ptr = m_builder.CreateCall(resolve_value_boolToCString(), reg->value); + // NOTE: Dot not deallocate later + return ptr; + } + + default: + assert(false); + return nullptr; + } + + default: + assert(false); + return nullptr; + } +} + +llvm::Constant *LLVMCodeBuilder::castConstValue(const Value &value, Compiler::StaticType targetType) +{ + switch (targetType) { + case Compiler::StaticType::Number: { + const double nan = std::numeric_limits::quiet_NaN(); + return llvm::ConstantFP::get(m_ctx, llvm::APFloat(value.isNaN() ? nan : value.toDouble())); + } + + case Compiler::StaticType::Bool: + return m_builder.getInt1(value.toBool()); + + case Compiler::StaticType::String: + return m_builder.CreateGlobalStringPtr(value.toString()); + + default: + assert(false); + return nullptr; + } +} + +llvm::Type *LLVMCodeBuilder::getType(Compiler::StaticType type) +{ + switch (type) { + case Compiler::StaticType::Void: + return m_builder.getVoidTy(); + + case Compiler::StaticType::Number: + return m_builder.getDoubleTy(); + + case Compiler::StaticType::Bool: + return m_builder.getInt1Ty(); + + case Compiler::StaticType::String: + return llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); + + default: + assert(false); + return nullptr; + } +} + +llvm::Value *LLVMCodeBuilder::isNaN(llvm::Value *num) +{ + return m_builder.CreateFCmpUNO(num, num); +} + +llvm::Value *LLVMCodeBuilder::removeNaN(llvm::Value *num) +{ + // Replace NaN with zero + return m_builder.CreateSelect(isNaN(num), llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0)), num); +} + +void LLVMCodeBuilder::createOp(Step::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount) +{ + Step step(type); + + assert(m_tmpRegs.size() >= argCount); + size_t j = 0; + + for (size_t i = m_tmpRegs.size() - argCount; i < m_tmpRegs.size(); i++) + step.args.push_back({ argType, m_tmpRegs[i] }); + + m_tmpRegs.erase(m_tmpRegs.end() - argCount, m_tmpRegs.end()); + + auto ret = std::make_shared(retType); + ret->isRawValue = true; + step.functionReturnReg = ret; + m_regs[m_currentFunction].push_back(ret); + m_tmpRegs.push_back(ret); + + m_steps.push_back(step); +} + +llvm::Value *LLVMCodeBuilder::createValue(std::shared_ptr reg) +{ + if (reg->isConstValue) { + // Create a constant ValueData instance and store it + llvm::Constant *value = castConstValue(reg->constValue, TYPE_MAP[reg->constValue.type()]); + llvm::Value *ret = m_builder.CreateAlloca(m_valueDataType); + + if (reg->constValue.type() == ValueType::String) + value = llvm::ConstantExpr::getPtrToInt(value, m_valueDataType->getElementType(0)); + else + value = llvm::ConstantExpr::getBitCast(value, m_valueDataType->getElementType(0)); + + llvm::Constant *type = m_builder.getInt32(static_cast(reg->constValue.type())); + llvm::Constant *constValue = llvm::ConstantStruct::get(m_valueDataType, { value, type, m_builder.getInt64(0) }); + m_builder.CreateStore(constValue, ret); + + return ret; + } else if (reg->isRawValue) { + llvm::Value *value = castRawValue(reg, reg->type); + llvm::Value *ret = m_builder.CreateAlloca(m_valueDataType); + + // Store value + llvm::Value *valueField = m_builder.CreateStructGEP(m_valueDataType, ret, 0); + m_builder.CreateStore(value, valueField); + + auto it = std::find_if(TYPE_MAP.begin(), TYPE_MAP.end(), [®](const std::pair &pair) { return pair.second == reg->type; }); + + if (it == TYPE_MAP.end()) { + assert(false); + return nullptr; + } + + // Store type + llvm::Value *typeField = m_builder.CreateStructGEP(m_valueDataType, ret, 1); + ValueType type = it->first; + m_builder.CreateStore(m_builder.getInt32(static_cast(type)), typeField); + + return ret; + } else + return reg->value; +} + +llvm::Value *LLVMCodeBuilder::createComparison(std::shared_ptr arg1, std::shared_ptr arg2, Comparison type) +{ + auto type1 = arg1->type; + auto type2 = arg2->type; + + if (arg1->isConstValue && arg2->isConstValue) { + // If both operands are constant, perform the comparison at compile time + bool result = false; + + switch (type) { + case Comparison::EQ: + result = arg1->constValue == arg2->constValue; + break; + + case Comparison::GT: + result = arg1->constValue > arg2->constValue; + break; + + case Comparison::LT: + result = arg1->constValue < arg2->constValue; + break; + + default: + assert(false); + return nullptr; + } + + return m_builder.getInt1(result); + } else { + // Optimize comparison of constant with number/bool + if (arg1->isConstValue && arg1->constValue.isValidNumber() && (type2 == Compiler::StaticType::Number || type2 == Compiler::StaticType::Bool)) + type1 = Compiler::StaticType::Number; + + if (arg2->isConstValue && arg2->constValue.isValidNumber() && (type1 == Compiler::StaticType::Number || type1 == Compiler::StaticType::Bool)) + type2 = Compiler::StaticType::Number; + + // Optimize number and bool comparison + int optNumberBool = 0; + + if (type1 == Compiler::StaticType::Number && type2 == Compiler::StaticType::Bool) { + type2 = Compiler::StaticType::Number; + optNumberBool = 2; // operand 2 was bool + } + + if (type1 == Compiler::StaticType::Bool && type2 == Compiler::StaticType::Number) { + type1 = Compiler::StaticType::Number; + optNumberBool = 1; // operand 1 was bool + } + + if (type1 != type2 || type1 == Compiler::StaticType::Unknown || type2 == Compiler::StaticType::Unknown) { + // If the types are different or at least one of them + // is unknown, we must use value functions + llvm::Value *value1 = createValue(arg1); + llvm::Value *value2 = createValue(arg2); + + switch (type) { + case Comparison::EQ: + return m_builder.CreateCall(resolve_value_equals(), { value1, value2 }); + + case Comparison::GT: + return m_builder.CreateCall(resolve_value_greater(), { value1, value2 }); + + case Comparison::LT: + return m_builder.CreateCall(resolve_value_lower(), { value1, value2 }); + + default: + assert(false); + return nullptr; + } + } else { + // Compare raw values + llvm::Value *value1 = castValue(arg1, type1); + llvm::Value *value2 = castValue(arg2, type2); + assert(type1 == type2); + + switch (type1) { + case Compiler::StaticType::Number: { + // Compare two numbers + switch (type) { + case Comparison::EQ: { + llvm::Value *nan = m_builder.CreateAnd(isNaN(value1), isNaN(value2)); // NaN == NaN + llvm::Value *cmp = m_builder.CreateFCmpOEQ(value1, value2); + return m_builder.CreateSelect(nan, m_builder.getInt1(true), cmp); + } + + case Comparison::GT: { + llvm::Value *bothNan = m_builder.CreateAnd(isNaN(value1), isNaN(value2)); // NaN == NaN + llvm::Value *cmp = m_builder.CreateFCmpOGT(value1, value2); + llvm::Value *nan; + llvm::Value *nanCmp; + + if (optNumberBool == 1) { + nan = isNaN(value2); + nanCmp = castValue(arg1, Compiler::StaticType::Bool); + } else if (optNumberBool == 2) { + nan = isNaN(value1); + nanCmp = m_builder.CreateNot(castValue(arg2, Compiler::StaticType::Bool)); + } else { + nan = isNaN(value1); + nanCmp = m_builder.CreateFCmpUGT(value1, value2); + } + + return m_builder.CreateAnd(m_builder.CreateNot(bothNan), m_builder.CreateSelect(nan, nanCmp, cmp)); + } + + case Comparison::LT: { + llvm::Value *bothNan = m_builder.CreateAnd(isNaN(value1), isNaN(value2)); // NaN == NaN + llvm::Value *cmp = m_builder.CreateFCmpOLT(value1, value2); + llvm::Value *nan; + llvm::Value *nanCmp; + + if (optNumberBool == 1) { + nan = isNaN(value2); + nanCmp = m_builder.CreateNot(castValue(arg1, Compiler::StaticType::Bool)); + } else if (optNumberBool == 2) { + nan = isNaN(value1); + nanCmp = castValue(arg2, Compiler::StaticType::Bool); + } else { + nan = isNaN(value2); + nanCmp = m_builder.CreateFCmpULT(value1, value2); + } + + return m_builder.CreateAnd(m_builder.CreateNot(bothNan), m_builder.CreateSelect(nan, nanCmp, cmp)); + } + + default: + assert(false); + return nullptr; + } + } + + case Compiler::StaticType::Bool: + // Compare two booleans + switch (type) { + case Comparison::EQ: + return m_builder.CreateICmpEQ(value1, value2); + + case Comparison::GT: + // value1 && !value2 + return m_builder.CreateAnd(value1, m_builder.CreateNot(value2)); + + case Comparison::LT: + // value2 && !value1 + return m_builder.CreateAnd(value2, m_builder.CreateNot(value1)); + + default: + assert(false); + return nullptr; + } + + case Compiler::StaticType::String: { + // Compare two strings + llvm::Value *cmpRet = m_builder.CreateCall(resolve_strcasecmp(), { value1, value2 }); + + switch (type) { + case Comparison::EQ: + return m_builder.CreateICmpEQ(cmpRet, m_builder.getInt32(0)); + + case Comparison::GT: + return m_builder.CreateICmpSGT(cmpRet, m_builder.getInt32(0)); + + case Comparison::LT: + return m_builder.CreateICmpSLT(cmpRet, m_builder.getInt32(0)); + + default: + assert(false); + return nullptr; + } + } + + default: + assert(false); + return nullptr; + } + } + } +} + +llvm::FunctionCallee LLVMCodeBuilder::resolveFunction(const std::string name, llvm::FunctionType *type) +{ + return m_module->getOrInsertFunction(name, type); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_init() +{ + return resolveFunction("value_init", llvm::FunctionType::get(m_builder.getVoidTy(), m_valueDataType->getPointerTo(), false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_free() +{ + return resolveFunction("value_free", llvm::FunctionType::get(m_builder.getVoidTy(), m_valueDataType->getPointerTo(), false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_long() +{ + return resolveFunction("value_assign_long", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), m_builder.getInt64Ty() }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_double() +{ + return resolveFunction("value_assign_double", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), m_builder.getDoubleTy() }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_bool() +{ + return resolveFunction("value_assign_double", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), m_builder.getInt1Ty() }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_cstring() +{ + return resolveFunction("value_assign_cstring", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0) }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_special() +{ + return resolveFunction("value_assign_special", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), m_builder.getInt32Ty() }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_toDouble() +{ + return resolveFunction("value_toDouble", llvm::FunctionType::get(m_builder.getDoubleTy(), m_valueDataType->getPointerTo(), false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_toBool() +{ + return resolveFunction("value_toBool", llvm::FunctionType::get(m_builder.getInt1Ty(), m_valueDataType->getPointerTo(), false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_toCString() +{ + return resolveFunction("value_toCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), m_valueDataType->getPointerTo(), false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_doubleToCString() +{ + return resolveFunction("value_doubleToCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), m_builder.getDoubleTy(), false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_boolToCString() +{ + return resolveFunction("value_boolToCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), m_builder.getInt1Ty(), false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_stringToDouble() +{ + return resolveFunction("value_stringToDouble", llvm::FunctionType::get(m_builder.getDoubleTy(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_stringToBool() +{ + return resolveFunction("value_stringToBool", llvm::FunctionType::get(m_builder.getInt1Ty(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_equals() +{ + llvm::Type *valuePtr = m_valueDataType->getPointerTo(); + return resolveFunction("value_equals", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_greater() +{ + llvm::Type *valuePtr = m_valueDataType->getPointerTo(); + return resolveFunction("value_greater", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_lower() +{ + llvm::Type *valuePtr = m_valueDataType->getPointerTo(); + return resolveFunction("value_lower", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp() +{ + llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); + return resolveFunction("strcasecmp", llvm::FunctionType::get(m_builder.getInt32Ty(), { pointerType, pointerType }, false)); +} diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h new file mode 100644 index 00000000..5fb17df5 --- /dev/null +++ b/src/dev/engine/internal/llvmcodebuilder.h @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include + +#include "icodebuilder.h" + +namespace libscratchcpp +{ + +class Target; + +class LLVMCodeBuilder : public ICodeBuilder +{ + public: + LLVMCodeBuilder(const std::string &id, bool warp); + + std::shared_ptr finalize() override; + + void addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const std::vector &argTypes) override; + void addConstValue(const Value &value) override; + void addVariableValue(Variable *variable) override; + void addListContents(List *list) override; + + void createAdd() override; + void createSub() override; + void createMul() override; + void createDiv() override; + + void createCmpEQ() override; + void createCmpGT() override; + void createCmpLT() override; + + void createAnd() override; + void createOr() override; + void createNot() override; + + void beginIfStatement() override; + void beginElseBranch() override; + void endIf() override; + + void beginRepeatLoop() override; + void beginWhileLoop() override; + void beginRepeatUntilLoop() override; + void beginLoopCondition() override; + void endLoop() override; + + void yield() override; + + private: + struct Register + { + Register(Compiler::StaticType type) : + type(type) + { + } + + Compiler::StaticType type = Compiler::StaticType::Void; + llvm::Value *value = nullptr; + bool isRawValue = false; + bool isConstValue = false; + Value constValue; + }; + + struct Step + { + enum class Type + { + FunctionCall, + Add, + Sub, + Mul, + Div, + CmpEQ, + CmpGT, + CmpLT, + And, + Or, + Not, + Yield, + BeginIf, + BeginElse, + EndIf, + BeginRepeatLoop, + BeginWhileLoop, + BeginRepeatUntilLoop, + BeginLoopCondition, + EndLoop + }; + + Step(Type type) : + type(type) + { + } + + Type type; + std::string functionName; + std::vector>> args; // target type, register + Compiler::StaticType functionReturnType = Compiler::StaticType::Void; + std::shared_ptr functionReturnReg; + }; + + struct IfStatement + { + llvm::Value *condition = nullptr; + llvm::BasicBlock *beforeIf = nullptr; + llvm::BasicBlock *body = nullptr; + llvm::BasicBlock *elseBranch = nullptr; + llvm::BasicBlock *afterIf = nullptr; + }; + + struct Loop + { + bool isRepeatLoop = false; + llvm::Value *index = nullptr; + llvm::BasicBlock *conditionBranch = nullptr; + llvm::BasicBlock *afterLoop = nullptr; + }; + + struct Coroutine + { + llvm::Value *handle = nullptr; + llvm::BasicBlock *suspend = nullptr; + llvm::BasicBlock *cleanup = nullptr; + llvm::BasicBlock *freeMemRet = nullptr; + llvm::Value *didSuspend = nullptr; + }; + + struct Procedure + { + // TODO: Implement procedures + bool warp = false; + }; + + enum class Comparison + { + EQ, + GT, + LT + }; + + void initTypes(); + + Coroutine initCoroutine(llvm::Function *func); + void verifyFunction(llvm::Function *func); + void optimize(); + + void freeHeap(); + llvm::Value *castValue(std::shared_ptr reg, Compiler::StaticType targetType); + llvm::Value *castRawValue(std::shared_ptr reg, Compiler::StaticType targetType); + llvm::Constant *castConstValue(const Value &value, Compiler::StaticType targetType); + llvm::Type *getType(Compiler::StaticType type); + llvm::Value *isNaN(llvm::Value *num); + llvm::Value *removeNaN(llvm::Value *num); + + void createOp(Step::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount); + llvm::Value *createValue(std::shared_ptr reg); + llvm::Value *createComparison(std::shared_ptr arg1, std::shared_ptr arg2, Comparison type); + + llvm::FunctionCallee resolveFunction(const std::string name, llvm::FunctionType *type); + llvm::FunctionCallee resolve_value_init(); + llvm::FunctionCallee resolve_value_free(); + llvm::FunctionCallee resolve_value_assign_long(); + llvm::FunctionCallee resolve_value_assign_double(); + llvm::FunctionCallee resolve_value_assign_bool(); + llvm::FunctionCallee resolve_value_assign_cstring(); + llvm::FunctionCallee resolve_value_assign_special(); + llvm::FunctionCallee resolve_value_toDouble(); + llvm::FunctionCallee resolve_value_toBool(); + llvm::FunctionCallee resolve_value_toCString(); + llvm::FunctionCallee resolve_value_doubleToCString(); + llvm::FunctionCallee resolve_value_boolToCString(); + llvm::FunctionCallee resolve_value_stringToDouble(); + llvm::FunctionCallee resolve_value_stringToBool(); + llvm::FunctionCallee resolve_value_equals(); + llvm::FunctionCallee resolve_value_greater(); + llvm::FunctionCallee resolve_value_lower(); + llvm::FunctionCallee resolve_strcasecmp(); + + std::string m_id; + llvm::LLVMContext m_ctx; + std::unique_ptr m_module; + llvm::IRBuilder<> m_builder; + + llvm::StructType *m_valueDataType = nullptr; + + std::vector m_steps; + size_t m_currentFunction = 0; + std::vector m_constValues; + std::vector>> m_regs; + std::vector> m_tmpRegs; + bool m_defaultWarp = false; + bool m_warp = false; + + std::vector m_heap; + + std::shared_ptr m_output; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvmexecutablecode.cpp b/src/dev/engine/internal/llvmexecutablecode.cpp new file mode 100644 index 00000000..6c2059ba --- /dev/null +++ b/src/dev/engine/internal/llvmexecutablecode.cpp @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include + +#include "llvmexecutablecode.h" +#include "llvmexecutioncontext.h" + +using namespace libscratchcpp; + +LLVMExecutableCode::LLVMExecutableCode(std::unique_ptr module) : + m_ctx(std::make_unique()), + m_jit(llvm::orc::LLJITBuilder().create()) +{ + if (!m_jit) { + llvm::errs() << "error: failed to create JIT: " << toString(m_jit.takeError()) << "\n"; + return; + } + + if (!module) + return; + + std::string name = module->getName().str(); + auto err = m_jit->get()->addIRModule(llvm::orc::ThreadSafeModule(std::move(module), std::move(m_ctx))); + + if (err) { + llvm::errs() << "error: failed to add module '" << name << "' to JIT: " << toString(std::move(err)) << "\n"; + return; + } + + // Lookup functions + m_mainFunction = (MainFunctionType)lookupFunction("f"); + assert(m_mainFunction); + m_resumeFunction = (ResumeFunctionType)lookupFunction("resume"); + assert(m_resumeFunction); +} + +void LLVMExecutableCode::run(ExecutionContext *context) +{ + LLVMExecutionContext *ctx = getContext(context); + + if (ctx->finished()) + return; + + if (ctx->coroutineHandle()) { + bool done = m_resumeFunction(ctx->coroutineHandle()); + + if (done) + ctx->setCoroutineHandle(nullptr); + + ctx->setFinished(done); + } else { + void *handle = m_mainFunction(context->target()); + + if (!handle) + ctx->setFinished(true); + + ctx->setCoroutineHandle(handle); + } +} + +void LLVMExecutableCode::kill(ExecutionContext *context) +{ + LLVMExecutionContext *ctx = getContext(context); + ctx->setCoroutineHandle(nullptr); + ctx->setFinished(true); +} + +void LLVMExecutableCode::reset(ExecutionContext *context) +{ + LLVMExecutionContext *ctx = getContext(context); + ctx->setCoroutineHandle(nullptr); + ctx->setFinished(false); +} + +bool LLVMExecutableCode::isFinished(ExecutionContext *context) const +{ + return getContext(context)->finished(); +} + +void LLVMExecutableCode::promise() +{ +} + +void LLVMExecutableCode::resolvePromise() +{ +} + +std::shared_ptr LLVMExecutableCode::createExecutionContext(Target *target) const +{ + return std::make_shared(target); +} + +uint64_t LLVMExecutableCode::lookupFunction(const std::string &name) +{ + auto func = m_jit->get()->lookup(name); + + if (func) + return func->getValue(); + else { + llvm::errs() << "error: failed to lookup LLVM function: " << toString(func.takeError()) << "\n"; + return 0; + } +} + +LLVMExecutionContext *LLVMExecutableCode::getContext(ExecutionContext *context) +{ + assert(dynamic_cast(context)); + return static_cast(context); +} diff --git a/src/dev/engine/internal/llvmexecutablecode.h b/src/dev/engine/internal/llvmexecutablecode.h new file mode 100644 index 00000000..4094fee8 --- /dev/null +++ b/src/dev/engine/internal/llvmexecutablecode.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include + +namespace libscratchcpp +{ + +class Target; +class LLVMExecutionContext; + +class LLVMExecutableCode : public ExecutableCode +{ + public: + LLVMExecutableCode(std::unique_ptr module); + + void run(ExecutionContext *context) override; + void kill(libscratchcpp::ExecutionContext *context) override; + void reset(ExecutionContext *context) override; + + bool isFinished(ExecutionContext *context) const override; + + void promise() override; + void resolvePromise() override; + + std::shared_ptr createExecutionContext(Target *target) const override; + + private: + uint64_t lookupFunction(const std::string &name); + + using MainFunctionType = void *(*)(Target *); + using ResumeFunctionType = bool (*)(void *); + + static LLVMExecutionContext *getContext(ExecutionContext *context); + + std::unique_ptr m_ctx; + llvm::Expected> m_jit; + + MainFunctionType m_mainFunction; + ResumeFunctionType m_resumeFunction; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvmexecutioncontext.cpp b/src/dev/engine/internal/llvmexecutioncontext.cpp new file mode 100644 index 00000000..5934a90a --- /dev/null +++ b/src/dev/engine/internal/llvmexecutioncontext.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "llvmexecutioncontext.h" + +using namespace libscratchcpp; + +LLVMExecutionContext::LLVMExecutionContext(Target *target) : + ExecutionContext(target) +{ +} + +void *LLVMExecutionContext::coroutineHandle() const +{ + return m_coroutineHandle; +} + +void LLVMExecutionContext::setCoroutineHandle(void *newCoroutineHandle) +{ + m_coroutineHandle = newCoroutineHandle; +} + +bool LLVMExecutionContext::finished() const +{ + return m_finished; +} + +void LLVMExecutionContext::setFinished(bool newFinished) +{ + m_finished = newFinished; +} diff --git a/src/dev/engine/internal/llvmexecutioncontext.h b/src/dev/engine/internal/llvmexecutioncontext.h new file mode 100644 index 00000000..c6357819 --- /dev/null +++ b/src/dev/engine/internal/llvmexecutioncontext.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class LLVMExecutionContext : public ExecutionContext +{ + public: + LLVMExecutionContext(Target *target); + + void *coroutineHandle() const; + void setCoroutineHandle(void *newCoroutineHandle); + + bool finished() const; + void setFinished(bool newFinished); + + private: + void *m_coroutineHandle = nullptr; + bool m_finished = false; +}; + +} // namespace libscratchcpp diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt index 755e771f..71402cef 100644 --- a/src/engine/CMakeLists.txt +++ b/src/engine/CMakeLists.txt @@ -3,9 +3,6 @@ target_sources(scratchcpp virtualmachine.cpp virtualmachine_p.cpp virtualmachine_p.h - compiler.cpp - compiler_p.cpp - compiler_p.h script.cpp script_p.cpp script_p.h @@ -23,3 +20,12 @@ target_sources(scratchcpp internal/randomgenerator.cpp internal/irandomgenerator.h ) + +if(NOT LIBSCRATCHCPP_USE_LLVM) + target_sources(scratchcpp + PRIVATE + compiler.cpp + compiler_p.cpp + compiler_p.h + ) +endif() diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 6a15c9da..5a8dd135 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -7,7 +7,11 @@ #include #include #include +#ifdef USE_LLVM +#include +#else #include +#endif #include #include #include @@ -28,7 +32,11 @@ #include "timer.h" #include "clock.h" #include "audio/iaudioengine.h" +#ifdef USE_LLVM +#include "dev/blocks/blocks.h" +#else #include "blocks/blocks.h" +#endif #include "scratch/monitor_p.h" using namespace libscratchcpp; @@ -262,7 +270,9 @@ void Engine::compile() // Compile scripts to bytecode for (auto target : m_targets) { std::cout << "Compiling scripts in target " << target->name() << "..." << std::endl; +#ifndef USE_LLVM std::unordered_map procedureBytecodeMap; +#endif Compiler compiler(this, target.get()); const auto &blocks = target->blocks(); for (auto block : blocks) { @@ -272,6 +282,9 @@ void Engine::compile() auto script = std::make_shared