diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ca8c1aad..8eff2a4a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -project(libscratchcpp VERSION 0.13.0 LANGUAGES C CXX) +project(libscratchcpp VERSION 0.13.2 LANGUAGES C CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_STANDARD 17) @@ -21,6 +21,7 @@ add_library(scratchcpp SHARED) add_subdirectory(src) include_directories(src) # TODO: Remove this line include_directories(include) +install(TARGETS scratchcpp DESTINATION lib) if (LIBSCRATCHCPP_COMPUTED_GOTO) target_compile_definitions(scratchcpp PRIVATE ENABLE_COMPUTED_GOTO) @@ -95,26 +96,27 @@ else() endif() include(FetchContent) -set(ZIP_SRC thirdparty/zip/src) -set(UTFCPP_SRC thirdparty/utfcpp/source) -add_library(zip SHARED - ${ZIP_SRC}/zip.c - ${ZIP_SRC}/zip.h - ${ZIP_SRC}/miniz.h -) -target_include_directories(scratchcpp PUBLIC ${ZIP_SRC}) +# zip +include(build/zip.cmake) +target_link_libraries(scratchcpp PRIVATE zip) +# utfcpp +set(UTFCPP_SRC thirdparty/utfcpp/source) target_include_directories(scratchcpp PUBLIC ${UTFCPP_SRC}) + +# spimpl include_directories(thirdparty/spimpl) +# JSON FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz) FetchContent_MakeAvailable(json) - target_link_libraries(scratchcpp PRIVATE nlohmann_json::nlohmann_json) -target_link_libraries(scratchcpp PRIVATE zip) + +# Audio target_link_libraries(scratchcpp PRIVATE scratchcpp-audio) +# Network if (LIBSCRATCHCPP_NETWORK_SUPPORT) FetchContent_Declare(cpr GIT_REPOSITORY https://github.com/libcpr/cpr.git GIT_TAG 225b7454877805f089b3895260438e929bd6d123) # 09-22-2024 @@ -123,18 +125,25 @@ if (LIBSCRATCHCPP_NETWORK_SUPPORT) target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_NETWORK_SUPPORT) endif() +# LLVM if (LIBSCRATCHCPP_USE_LLVM) include(build/HunterPackages.cmake) include(build/LLVM.cmake) target_link_libraries(scratchcpp PRIVATE LLVM) endif() +if(LIBSCRATCHCPP_PRINT_LLVM_IR) + target_compile_definitions(scratchcpp PRIVATE PRINT_LLVM_IR) +endif() + +# Macros 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}) target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION_MINOR=${PROJECT_VERSION_MINOR}) target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION_PATCH=${PROJECT_VERSION_PATCH}) +# Unit tests if (LIBSCRATCHCPP_BUILD_UNIT_TESTS) enable_testing() add_subdirectory(test) diff --git a/build/zip.cmake b/build/zip.cmake new file mode 100644 index 000000000..1165aba84 --- /dev/null +++ b/build/zip.cmake @@ -0,0 +1,10 @@ +set(ZIP_SRC ${PROJECT_SOURCE_DIR}/thirdparty/zip/src) + +add_library(zip SHARED + ${ZIP_SRC}/zip.c + ${ZIP_SRC}/zip.h + ${ZIP_SRC}/miniz.h +) + +target_include_directories(zip PUBLIC ${ZIP_SRC}) +install(TARGETS zip DESTINATION lib) diff --git a/include/scratchcpp/asset.h b/include/scratchcpp/asset.h index c5c10d88d..9870c03a4 100644 --- a/include/scratchcpp/asset.h +++ b/include/scratchcpp/asset.h @@ -19,7 +19,7 @@ class LIBSCRATCHCPP_EXPORT Asset : public Entity Asset(const std::string &name, const std::string &id, const std::string &format); Asset(const Asset &) = delete; - virtual ~Asset() { } + virtual ~Asset(); void setId(const std::string &id); @@ -38,6 +38,7 @@ class LIBSCRATCHCPP_EXPORT Asset : public Entity protected: virtual void processData(unsigned int size, void *data) { } + virtual bool isClone() const { return false; } private: spimpl::unique_impl_ptr impl; diff --git a/include/scratchcpp/iextension.h b/include/scratchcpp/iextension.h index a53b83567..0a4bff08e 100644 --- a/include/scratchcpp/iextension.h +++ b/include/scratchcpp/iextension.h @@ -2,9 +2,7 @@ #pragma once -#include - -#include "global.h" +#include "value_functions.h" namespace libscratchcpp { @@ -27,6 +25,9 @@ class LIBSCRATCHCPP_EXPORT IExtension /*! Returns the description of the extension. */ virtual std::string description() const = 0; + /*! Returns the block color of the extension. */ + virtual Rgb color() const = 0; + /*! Override this method to register blocks. */ virtual void registerBlocks(IEngine *engine) = 0; diff --git a/include/scratchcpp/sound.h b/include/scratchcpp/sound.h index b4906b1ee..2c95d32d2 100644 --- a/include/scratchcpp/sound.h +++ b/include/scratchcpp/sound.h @@ -43,6 +43,7 @@ class LIBSCRATCHCPP_EXPORT Sound : public Asset protected: void processData(unsigned int size, void *data) override; + virtual bool isClone() const override; private: void stopCloneSounds(); diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 536c266bc..f3319207d 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -4,11 +4,13 @@ add_library(scratchcpp-audio STATIC) set(MINIAUDIO_SRC internal/thirdparty/miniaudio) if (LIBSCRATCHCPP_AUDIO_SUPPORT) - add_library(miniaudio STATIC + add_library(miniaudio SHARED ${MINIAUDIO_SRC}/miniaudio.c ${MINIAUDIO_SRC}/miniaudio.h ) target_include_directories(scratchcpp-audio PUBLIC ${CMAKE_CURRENT_LIST_DIR}/${MINIAUDIO_SRC}) + install(TARGETS miniaudio DESTINATION lib) + target_link_libraries(scratchcpp-audio PRIVATE miniaudio) target_compile_definitions(scratchcpp-audio PUBLIC LIBSCRATCHCPP_AUDIO_SUPPORT) endif() diff --git a/src/blocks/controlblocks.cpp b/src/blocks/controlblocks.cpp index ab5aab69d..aa4ebbce2 100644 --- a/src/blocks/controlblocks.cpp +++ b/src/blocks/controlblocks.cpp @@ -25,6 +25,11 @@ std::string ControlBlocks::description() const return name() + " blocks"; } +Rgb ControlBlocks::color() const +{ + return rgb(255, 171, 25); +} + void ControlBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/controlblocks.h b/src/blocks/controlblocks.h index aa0c87104..623ab651f 100644 --- a/src/blocks/controlblocks.h +++ b/src/blocks/controlblocks.h @@ -43,6 +43,7 @@ class ControlBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/blocks/customblocks.cpp b/src/blocks/customblocks.cpp index aee3b7573..8066a025e 100644 --- a/src/blocks/customblocks.cpp +++ b/src/blocks/customblocks.cpp @@ -20,6 +20,11 @@ std::string CustomBlocks::description() const return name(); } +Rgb CustomBlocks::color() const +{ + return rgb(255, 102, 128); +} + void CustomBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/customblocks.h b/src/blocks/customblocks.h index a40e37c3f..234828860 100644 --- a/src/blocks/customblocks.h +++ b/src/blocks/customblocks.h @@ -24,6 +24,7 @@ class CustomBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/blocks/eventblocks.cpp b/src/blocks/eventblocks.cpp index 757e2bda7..85ec92354 100644 --- a/src/blocks/eventblocks.cpp +++ b/src/blocks/eventblocks.cpp @@ -30,6 +30,11 @@ std::string libscratchcpp::EventBlocks::description() const return "Event blocks"; } +Rgb EventBlocks::color() const +{ + return rgb(255, 191, 0); +} + void EventBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/eventblocks.h b/src/blocks/eventblocks.h index e4d28f4a6..9ddca5b60 100644 --- a/src/blocks/eventblocks.h +++ b/src/blocks/eventblocks.h @@ -38,6 +38,7 @@ class EventBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/blocks/listblocks.cpp b/src/blocks/listblocks.cpp index 135dd0bdb..4302c3bc8 100644 --- a/src/blocks/listblocks.cpp +++ b/src/blocks/listblocks.cpp @@ -23,6 +23,11 @@ std::string ListBlocks::description() const return "List blocks"; } +Rgb ListBlocks::color() const +{ + return rgb(255, 102, 26); +} + void ListBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/listblocks.h b/src/blocks/listblocks.h index 69e49aa39..ed621314f 100644 --- a/src/blocks/listblocks.h +++ b/src/blocks/listblocks.h @@ -28,6 +28,7 @@ class ListBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index f142f34c9..4a5ddf31c 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -42,6 +42,11 @@ std::string LooksBlocks::description() const return name() + " blocks"; } +Rgb LooksBlocks::color() const +{ + return rgb(153, 102, 255); +} + void LooksBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 8881d7eb0..eca733de1 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -60,6 +60,7 @@ class LooksBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index c5a96004a..62fd28ae7 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -29,6 +29,11 @@ std::string MotionBlocks::description() const return name() + " blocks"; } +Rgb MotionBlocks::color() const +{ + return rgb(76, 151, 255); +} + void MotionBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index b545822ea..b86e9481c 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -45,6 +45,7 @@ class MotionBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; diff --git a/src/blocks/operatorblocks.cpp b/src/blocks/operatorblocks.cpp index 376ae27a5..567d4a155 100644 --- a/src/blocks/operatorblocks.cpp +++ b/src/blocks/operatorblocks.cpp @@ -18,6 +18,11 @@ std::string OperatorBlocks::description() const return "Operator blocks"; } +Rgb OperatorBlocks::color() const +{ + return rgb(89, 192, 89); +} + void OperatorBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/operatorblocks.h b/src/blocks/operatorblocks.h index e9dd932aa..f2f5b7298 100644 --- a/src/blocks/operatorblocks.h +++ b/src/blocks/operatorblocks.h @@ -55,6 +55,7 @@ class OperatorBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/blocks/sensingblocks.cpp b/src/blocks/sensingblocks.cpp index af1349ed1..f5b18ce79 100644 --- a/src/blocks/sensingblocks.cpp +++ b/src/blocks/sensingblocks.cpp @@ -34,6 +34,11 @@ std::string SensingBlocks::description() const return name() + " blocks"; } +Rgb SensingBlocks::color() const +{ + return rgb(92, 177, 214); +} + void SensingBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/sensingblocks.h b/src/blocks/sensingblocks.h index 8153a18bd..5b3fc6380 100644 --- a/src/blocks/sensingblocks.h +++ b/src/blocks/sensingblocks.h @@ -60,6 +60,7 @@ class SensingBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; diff --git a/src/blocks/soundblocks.cpp b/src/blocks/soundblocks.cpp index 6cc2aa7b8..f001fa7bb 100644 --- a/src/blocks/soundblocks.cpp +++ b/src/blocks/soundblocks.cpp @@ -34,6 +34,11 @@ std::string SoundBlocks::description() const return name() + " blocks"; } +Rgb SoundBlocks::color() const +{ + return rgb(207, 99, 207); +} + void SoundBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/soundblocks.h b/src/blocks/soundblocks.h index d31b8a620..0535eb73e 100644 --- a/src/blocks/soundblocks.h +++ b/src/blocks/soundblocks.h @@ -37,6 +37,7 @@ class SoundBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; diff --git a/src/blocks/variableblocks.cpp b/src/blocks/variableblocks.cpp index 81961e49a..17236a304 100644 --- a/src/blocks/variableblocks.cpp +++ b/src/blocks/variableblocks.cpp @@ -23,6 +23,11 @@ std::string VariableBlocks::description() const return "Variable blocks"; } +Rgb VariableBlocks::color() const +{ + return rgb(255, 140, 26); +} + void VariableBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/variableblocks.h b/src/blocks/variableblocks.h index 2372660bb..f4b2922cc 100644 --- a/src/blocks/variableblocks.h +++ b/src/blocks/variableblocks.h @@ -27,6 +27,7 @@ class VariableBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp index 9567f69f7..0ca0524dc 100644 --- a/src/dev/blocks/controlblocks.cpp +++ b/src/dev/blocks/controlblocks.cpp @@ -26,6 +26,11 @@ std::string ControlBlocks::description() const return name() + " blocks"; } +Rgb ControlBlocks::color() const +{ + return rgb(255, 171, 25); +} + void ControlBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "control_forever", &compileForever); diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h index 178508249..0f44982c5 100644 --- a/src/dev/blocks/controlblocks.h +++ b/src/dev/blocks/controlblocks.h @@ -12,6 +12,7 @@ class ControlBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/dev/blocks/customblocks.cpp b/src/dev/blocks/customblocks.cpp index e78048bcf..655e4d3b5 100644 --- a/src/dev/blocks/customblocks.cpp +++ b/src/dev/blocks/customblocks.cpp @@ -20,6 +20,11 @@ std::string CustomBlocks::description() const return name(); } +Rgb CustomBlocks::color() const +{ + return rgb(255, 102, 128); +} + void CustomBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "procedures_definition", [](Compiler *) -> CompilerValue * { return nullptr; }); diff --git a/src/dev/blocks/customblocks.h b/src/dev/blocks/customblocks.h index 55572ba94..b2a2613f7 100644 --- a/src/dev/blocks/customblocks.h +++ b/src/dev/blocks/customblocks.h @@ -12,6 +12,7 @@ class CustomBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/dev/blocks/eventblocks.cpp b/src/dev/blocks/eventblocks.cpp index a2b18b3ba..ba4d2663b 100644 --- a/src/dev/blocks/eventblocks.cpp +++ b/src/dev/blocks/eventblocks.cpp @@ -24,6 +24,11 @@ std::string libscratchcpp::EventBlocks::description() const return "Event blocks"; } +Rgb EventBlocks::color() const +{ + return rgb(255, 191, 0); +} + void EventBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "event_whentouchingobject", &compileWhenTouchingObject); diff --git a/src/dev/blocks/eventblocks.h b/src/dev/blocks/eventblocks.h index 34454ebc1..512bae0cc 100644 --- a/src/dev/blocks/eventblocks.h +++ b/src/dev/blocks/eventblocks.h @@ -12,6 +12,7 @@ class EventBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp index 653875e45..64b441357 100644 --- a/src/dev/blocks/listblocks.cpp +++ b/src/dev/blocks/listblocks.cpp @@ -20,6 +20,11 @@ std::string ListBlocks::description() const return "List blocks"; } +Rgb ListBlocks::color() const +{ + return rgb(255, 102, 26); +} + void ListBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "data_addtolist", &compileAddToList); diff --git a/src/dev/blocks/listblocks.h b/src/dev/blocks/listblocks.h index 3a8d01261..02dbd79ce 100644 --- a/src/dev/blocks/listblocks.h +++ b/src/dev/blocks/listblocks.h @@ -14,6 +14,7 @@ class ListBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/dev/blocks/looksblocks.cpp b/src/dev/blocks/looksblocks.cpp index 0d471fb04..8da59e7a9 100644 --- a/src/dev/blocks/looksblocks.cpp +++ b/src/dev/blocks/looksblocks.cpp @@ -14,6 +14,11 @@ std::string LooksBlocks::description() const return name() + " blocks"; } +Rgb LooksBlocks::color() const +{ + return rgb(153, 102, 255); +} + void LooksBlocks::registerBlocks(IEngine *engine) { } diff --git a/src/dev/blocks/looksblocks.h b/src/dev/blocks/looksblocks.h index 8bfa5f185..b75297549 100644 --- a/src/dev/blocks/looksblocks.h +++ b/src/dev/blocks/looksblocks.h @@ -12,6 +12,7 @@ class LooksBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; }; diff --git a/src/dev/blocks/motionblocks.cpp b/src/dev/blocks/motionblocks.cpp index e44a673c0..a249cdffa 100644 --- a/src/dev/blocks/motionblocks.cpp +++ b/src/dev/blocks/motionblocks.cpp @@ -14,6 +14,11 @@ std::string MotionBlocks::description() const return name() + " blocks"; } +Rgb MotionBlocks::color() const +{ + return rgb(76, 151, 255); +} + void MotionBlocks::registerBlocks(IEngine *engine) { } diff --git a/src/dev/blocks/motionblocks.h b/src/dev/blocks/motionblocks.h index 125b40738..44d7c0bd1 100644 --- a/src/dev/blocks/motionblocks.h +++ b/src/dev/blocks/motionblocks.h @@ -12,6 +12,7 @@ class MotionBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; }; diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index b962d3c7d..508d41a1e 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -21,6 +21,11 @@ std::string OperatorBlocks::description() const return "Operator blocks"; } +Rgb OperatorBlocks::color() const +{ + return rgb(89, 192, 89); +} + void OperatorBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "operator_add", &compileAdd); diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index 1804e9313..647512e6c 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -14,6 +14,7 @@ class OperatorBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/dev/blocks/sensingblocks.cpp b/src/dev/blocks/sensingblocks.cpp index 42896ecb6..8e3b18cac 100644 --- a/src/dev/blocks/sensingblocks.cpp +++ b/src/dev/blocks/sensingblocks.cpp @@ -14,6 +14,11 @@ std::string SensingBlocks::description() const return name() + " blocks"; } +Rgb SensingBlocks::color() const +{ + return rgb(92, 177, 214); +} + void SensingBlocks::registerBlocks(IEngine *engine) { } diff --git a/src/dev/blocks/sensingblocks.h b/src/dev/blocks/sensingblocks.h index ab934fb08..9a0f367f5 100644 --- a/src/dev/blocks/sensingblocks.h +++ b/src/dev/blocks/sensingblocks.h @@ -12,6 +12,7 @@ class SensingBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; }; diff --git a/src/dev/blocks/soundblocks.cpp b/src/dev/blocks/soundblocks.cpp index 4a440a9a4..df7611ab0 100644 --- a/src/dev/blocks/soundblocks.cpp +++ b/src/dev/blocks/soundblocks.cpp @@ -14,6 +14,11 @@ std::string SoundBlocks::description() const return name() + " blocks"; } +Rgb SoundBlocks::color() const +{ + return rgb(207, 99, 207); +} + void SoundBlocks::registerBlocks(IEngine *engine) { } diff --git a/src/dev/blocks/soundblocks.h b/src/dev/blocks/soundblocks.h index afae6c9c5..f605c7b64 100644 --- a/src/dev/blocks/soundblocks.h +++ b/src/dev/blocks/soundblocks.h @@ -12,6 +12,7 @@ class SoundBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; }; diff --git a/src/dev/blocks/variableblocks.cpp b/src/dev/blocks/variableblocks.cpp index 177d8dabf..9e77c7bf0 100644 --- a/src/dev/blocks/variableblocks.cpp +++ b/src/dev/blocks/variableblocks.cpp @@ -21,6 +21,11 @@ std::string VariableBlocks::description() const return "Variable blocks"; } +Rgb VariableBlocks::color() const +{ + return rgb(255, 140, 26); +} + void VariableBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "data_variable", &compileVariable); diff --git a/src/dev/blocks/variableblocks.h b/src/dev/blocks/variableblocks.h index 9cb0bcc47..423085d33 100644 --- a/src/dev/blocks/variableblocks.h +++ b/src/dev/blocks/variableblocks.h @@ -12,6 +12,7 @@ class VariableBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/dev/engine/internal/llvm/CMakeLists.txt b/src/dev/engine/internal/llvm/CMakeLists.txt index e1bb3ef58..38b7e4d2d 100644 --- a/src/dev/engine/internal/llvm/CMakeLists.txt +++ b/src/dev/engine/internal/llvm/CMakeLists.txt @@ -16,6 +16,7 @@ target_sources(scratchcpp llvmtypes.cpp llvmtypes.h llvmfunctions.cpp + llvmloopscope.h llvmcompilercontext.cpp llvmcompilercontext.h llvmexecutablecode.cpp diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index e300f7055..0c0a97434 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -16,12 +16,16 @@ #include "llvmifstatement.h" #include "llvmloop.h" #include "llvmtypes.h" +#include "llvmloopscope.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 } }; +static const std::unordered_set + VAR_LIST_READ_INSTRUCTIONS = { LLVMInstruction::Type::ReadVariable, LLVMInstruction::Type::GetListItem, LLVMInstruction::Type::GetListItemIndex, LLVMInstruction::Type::ListContainsItem }; + LLVMCodeBuilder::LLVMCodeBuilder(LLVMCompilerContext *ctx, BlockPrototype *procedurePrototype) : m_ctx(ctx), m_target(ctx->target()), @@ -44,8 +48,8 @@ std::shared_ptr LLVMCodeBuilder::finalize() { if (!m_warp) { // Do not create coroutine if there are no yield instructions nor non-warp procedure calls - auto it = std::find_if(m_instructions.begin(), m_instructions.end(), [](const LLVMInstruction &step) { - return step.type == LLVMInstruction::Type::Yield || (step.type == LLVMInstruction::Type::CallProcedure && step.procedurePrototype && !step.procedurePrototype->warp()); + auto it = std::find_if(m_instructions.begin(), m_instructions.end(), [](const std::shared_ptr &step) { + return step->type == LLVMInstruction::Type::Yield || (step->type == LLVMInstruction::Type::CallProcedure && step->procedurePrototype && !step->procedurePrototype->warp()); }); if (it == m_instructions.end()) @@ -105,7 +109,18 @@ std::shared_ptr LLVMCodeBuilder::finalize() // All variables are currently created on the stack and synced later (seems to be faster) // NOTE: Strings are NOT copied, only the pointer and string size are copied varPtr.stackPtr = m_builder.CreateAlloca(m_valueDataType); - varPtr.onStack = false; // use heap before the first assignment + + // If there are no write operations outside loops, initialize the stack variable now + Variable *variable = var; + auto it = std::find_if(m_variableInstructions.begin(), m_variableInstructions.end(), [variable](const std::shared_ptr &ins) { + return ins->type == LLVMInstruction::Type::WriteVariable && ins->workVariable == variable && !ins->loopScope; + }); + + if (it == m_variableInstructions.end()) { + createValueCopy(ptr, varPtr.stackPtr); + varPtr.onStack = true; + } else + varPtr.onStack = false; // use heap before the first assignment } // Create list pointers @@ -122,12 +137,16 @@ std::shared_ptr LLVMCodeBuilder::finalize() m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty); } + assert(m_loopScope == -1); + m_loopScope = -1; m_scopeVariables.clear(); m_scopeLists.clear(); pushScopeLevel(); // Execute recorded steps - for (const LLVMInstruction &step : m_instructions) { + for (const auto insPtr : m_instructions) { + const LLVMInstruction &step = *insPtr; + switch (step.type) { case LLVMInstruction::Type::FunctionCall: { std::vector types; @@ -616,6 +635,8 @@ std::shared_ptr LLVMCodeBuilder::finalize() LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable]; varPtr.changed = true; + const bool safe = isVarOrListTypeSafe(insPtr, varPtr.type); + // Initialize stack variable on first assignment if (!varPtr.onStack) { varPtr.onStack = true; @@ -636,6 +657,9 @@ std::shared_ptr LLVMCodeBuilder::finalize() m_builder.CreateStore(m_builder.getInt32(static_cast(mappedType)), typeField); } + if (!safe) + varPtr.type = Compiler::StaticType::Unknown; + createValueStore(arg.second, varPtr.stackPtr, type, varPtr.type); varPtr.type = type; m_scopeVariables.back()[&varPtr] = varPtr.type; @@ -644,8 +668,12 @@ std::shared_ptr LLVMCodeBuilder::finalize() case LLVMInstruction::Type::ReadVariable: { assert(step.args.size() == 0); - const LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable]; - step.functionReturnReg->value = varPtr.onStack ? varPtr.stackPtr : varPtr.heapPtr; + LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable]; + + if (!isVarOrListTypeSafe(insPtr, varPtr.type)) + varPtr.type = Compiler::StaticType::Unknown; + + step.functionReturnReg->value = varPtr.onStack && !(step.loopCondition && !m_warp) ? varPtr.stackPtr : varPtr.heapPtr; step.functionReturnReg->setType(varPtr.type); break; } @@ -668,7 +696,10 @@ std::shared_ptr LLVMCodeBuilder::finalize() case LLVMInstruction::Type::RemoveListItem: { assert(step.args.size() == 1); const auto &arg = step.args[0]; - const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; // Range check llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)); @@ -707,6 +738,9 @@ std::shared_ptr LLVMCodeBuilder::finalize() typeMap[&listPtr] = listPtr.type; } + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; + // Check if enough space is allocated llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr); llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr); @@ -752,6 +786,9 @@ std::shared_ptr LLVMCodeBuilder::finalize() typeMap[&listPtr] = listPtr.type; } + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; + llvm::Value *oldAllocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr); // Range check @@ -788,6 +825,9 @@ std::shared_ptr LLVMCodeBuilder::finalize() Compiler::StaticType type = optimizeRegisterType(valueArg.second); LLVMListPtr &listPtr = m_listPtrs[step.workList]; + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; + // Range check llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)); llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr); @@ -831,7 +871,10 @@ std::shared_ptr LLVMCodeBuilder::finalize() case LLVMInstruction::Type::GetListItem: { assert(step.args.size() == 1); const auto &arg = step.args[0]; - const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)); llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr); @@ -859,7 +902,11 @@ std::shared_ptr LLVMCodeBuilder::finalize() case LLVMInstruction::Type::GetListItemIndex: { assert(step.args.size() == 1); const auto &arg = step.args[0]; - const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; + step.functionReturnReg->value = m_builder.CreateSIToFP(getListItemIndex(listPtr, arg.second), m_builder.getDoubleTy()); break; } @@ -867,7 +914,11 @@ std::shared_ptr LLVMCodeBuilder::finalize() case LLVMInstruction::Type::ListContainsItem: { assert(step.args.size() == 1); const auto &arg = step.args[0]; - const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; + llvm::Value *index = getListItemIndex(listPtr, arg.second); step.functionReturnReg->value = m_builder.CreateICmpSGT(index, llvm::ConstantInt::get(m_builder.getInt64Ty(), -1, true)); break; @@ -1007,6 +1058,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() loops.push_back(loop); pushScopeLevel(); + pushLoopScope(true); break; } @@ -1036,6 +1088,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() // Switch to body branch m_builder.SetInsertPoint(body); pushScopeLevel(); + pushLoopScope(true); break; } @@ -1057,6 +1110,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() // Switch to body branch m_builder.SetInsertPoint(body); pushScopeLevel(); + pushLoopScope(true); break; } @@ -1090,6 +1144,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() loops.pop_back(); popScopeLevel(); + popLoopScope(); break; } @@ -1198,18 +1253,18 @@ CompilerValue *LLVMCodeBuilder::addFunctionCall(const std::string &functionName, { assert(argTypes.size() == args.size()); - LLVMInstruction ins(LLVMInstruction::Type::FunctionCall); - ins.functionName = functionName; + auto ins = std::make_shared(LLVMInstruction::Type::FunctionCall, currentLoopScope(), m_loopCondition); + ins->functionName = functionName; for (size_t i = 0; i < args.size(); i++) - ins.args.push_back({ argTypes[i], static_cast(args[i]) }); + ins->args.push_back({ argTypes[i], static_cast(args[i]) }); if (returnType != Compiler::StaticType::Void) { auto reg = std::make_shared(returnType); reg->isRawValue = true; - ins.functionReturnReg = reg.get(); + ins->functionReturnReg = reg.get(); m_instructions.push_back(ins); - return addReg(reg); + return addReg(reg, ins); } m_instructions.push_back(ins); @@ -1219,14 +1274,14 @@ CompilerValue *LLVMCodeBuilder::addFunctionCall(const std::string &functionName, CompilerValue *LLVMCodeBuilder::addTargetFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) { CompilerValue *ret = addFunctionCall(functionName, returnType, argTypes, args); - m_instructions.back().functionTargetArg = true; + m_instructions.back()->functionTargetArg = true; return ret; } CompilerValue *LLVMCodeBuilder::addFunctionCallWithCtx(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) { CompilerValue *ret = addFunctionCall(functionName, returnType, argTypes, args); - m_instructions.back().functionCtxArg = true; + m_instructions.back()->functionCtxArg = true; return ret; } @@ -1234,7 +1289,7 @@ CompilerConstant *LLVMCodeBuilder::addConstValue(const Value &value) { auto constReg = std::make_shared(TYPE_MAP[value.type()], value); auto reg = std::reinterpret_pointer_cast(constReg); - return static_cast(static_cast(addReg(reg))); + return static_cast(static_cast(addReg(reg, nullptr))); } CompilerValue *LLVMCodeBuilder::addLoopIndex() @@ -1249,63 +1304,85 @@ CompilerValue *LLVMCodeBuilder::addLocalVariableValue(CompilerLocalVariable *var CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable) { - LLVMInstruction ins(LLVMInstruction::Type::ReadVariable); - ins.workVariable = variable; - m_variablePtrs[variable] = LLVMVariablePtr(); + auto ins = std::make_shared(LLVMInstruction::Type::ReadVariable, currentLoopScope(), m_loopCondition); + ins->workVariable = variable; + + if (m_variablePtrs.find(variable) == m_variablePtrs.cend()) + m_variablePtrs[variable] = LLVMVariablePtr(); auto ret = std::make_shared(Compiler::StaticType::Unknown); ret->isRawValue = false; - ins.functionReturnReg = ret.get(); + ins->functionReturnReg = ret.get(); m_instructions.push_back(ins); - return addReg(ret); + m_variableInstructions.push_back(m_instructions.back()); + return addReg(ret, ins); } CompilerValue *LLVMCodeBuilder::addListContents(List *list) { - LLVMInstruction ins(LLVMInstruction::Type::GetListContents); + LLVMInstruction ins(LLVMInstruction::Type::GetListContents, currentLoopScope(), m_loopCondition); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + return createOp(ins, Compiler::StaticType::String); } CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index) { - LLVMInstruction ins(LLVMInstruction::Type::GetListItem); - ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); + auto ins = std::make_shared(LLVMInstruction::Type::GetListItem, currentLoopScope(), m_loopCondition); + ins->workList = list; - ins.args.push_back({ Compiler::StaticType::Number, static_cast(index) }); + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + + ins->args.push_back({ Compiler::StaticType::Number, static_cast(index) }); auto ret = std::make_shared(Compiler::StaticType::Unknown); ret->isRawValue = false; - ins.functionReturnReg = ret.get(); + ins->functionReturnReg = ret.get(); m_instructions.push_back(ins); - return addReg(ret); + m_listInstructions.push_back(m_instructions.back()); + return addReg(ret, ins); } CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item) { - LLVMInstruction ins(LLVMInstruction::Type::GetListItemIndex); + LLVMInstruction ins(LLVMInstruction::Type::GetListItemIndex, currentLoopScope(), m_loopCondition); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); - return createOp(ins, Compiler::StaticType::Number, Compiler::StaticType::Unknown, { item }); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + + auto ret = createOp(ins, Compiler::StaticType::Number, Compiler::StaticType::Unknown, { item }); + m_listInstructions.push_back(m_instructions.back()); + return ret; } CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item) { - LLVMInstruction ins(LLVMInstruction::Type::ListContainsItem); + LLVMInstruction ins(LLVMInstruction::Type::ListContainsItem, currentLoopScope(), m_loopCondition); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); - return createOp(ins, Compiler::StaticType::Bool, Compiler::StaticType::Unknown, { item }); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + + auto ret = createOp(ins, Compiler::StaticType::Bool, Compiler::StaticType::Unknown, { item }); + m_listInstructions.push_back(m_instructions.back()); + return ret; } CompilerValue *LLVMCodeBuilder::addListSize(List *list) { - LLVMInstruction ins(LLVMInstruction::Type::GetListSize); + LLVMInstruction ins(LLVMInstruction::Type::GetListSize, currentLoopScope(), m_loopCondition); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + return createOp(ins, Compiler::StaticType::Number); } @@ -1324,14 +1401,14 @@ CompilerValue *LLVMCodeBuilder::addProcedureArgument(const std::string &name) const auto index = it - argNames.begin(); const Compiler::StaticType type = getProcedureArgType(m_procedurePrototype->argumentTypes()[index]); - LLVMInstruction ins(LLVMInstruction::Type::ProcedureArg); + auto ins = std::make_shared(LLVMInstruction::Type::ProcedureArg, currentLoopScope(), m_loopCondition); auto ret = std::make_shared(type); ret->isRawValue = (type != Compiler::StaticType::Unknown); - ins.functionReturnReg = ret.get(); - ins.procedureArgIndex = index; + ins->functionReturnReg = ret.get(); + ins->procedureArgIndex = index; m_instructions.push_back(ins); - return addReg(ret); + return addReg(ret, ins); } CompilerValue *LLVMCodeBuilder::createAdd(CompilerValue *operand1, CompilerValue *operand2) @@ -1504,111 +1581,168 @@ void LLVMCodeBuilder::createLocalVariableWrite(CompilerLocalVariable *variable, void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *value) { - LLVMInstruction ins(LLVMInstruction::Type::WriteVariable); + LLVMInstruction ins(LLVMInstruction::Type::WriteVariable, currentLoopScope(), m_loopCondition); ins.workVariable = variable; - m_variablePtrs[variable] = LLVMVariablePtr(); createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { value }); + + if (m_variablePtrs.find(variable) == m_variablePtrs.cend()) + m_variablePtrs[variable] = LLVMVariablePtr(); + + if (m_loopScope >= 0) { + auto scope = m_loopScopes[m_loopScope]; + m_variablePtrs[variable].loopVariableWrites[scope].push_back(m_instructions.back()); + } + + m_variableInstructions.push_back(m_instructions.back()); } void LLVMCodeBuilder::createListClear(List *list) { - LLVMInstruction ins(LLVMInstruction::Type::ClearList); + LLVMInstruction ins(LLVMInstruction::Type::ClearList, currentLoopScope(), m_loopCondition); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); createOp(ins, Compiler::StaticType::Void); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); } void LLVMCodeBuilder::createListRemove(List *list, CompilerValue *index) { - LLVMInstruction ins(LLVMInstruction::Type::RemoveListItem); + LLVMInstruction ins(LLVMInstruction::Type::RemoveListItem, currentLoopScope(), m_loopCondition); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Number, { index }); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); } void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item) { - LLVMInstruction ins(LLVMInstruction::Type::AppendToList); + LLVMInstruction ins(LLVMInstruction::Type::AppendToList, currentLoopScope(), m_loopCondition); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { item }); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + + if (m_loopScope >= 0) { + auto scope = m_loopScopes[m_loopScope]; + m_listPtrs[list].loopListWrites[scope].push_back(m_instructions.back()); + } + + m_listInstructions.push_back(m_instructions.back()); } void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, CompilerValue *item) { - LLVMInstruction ins(LLVMInstruction::Type::InsertToList); + LLVMInstruction ins(LLVMInstruction::Type::InsertToList, currentLoopScope(), m_loopCondition); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item }); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + + if (m_loopScope >= 0) { + auto scope = m_loopScopes[m_loopScope]; + m_listPtrs[list].loopListWrites[scope].push_back(m_instructions.back()); + } + + m_listInstructions.push_back(m_instructions.back()); } void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, CompilerValue *item) { - LLVMInstruction ins(LLVMInstruction::Type::ListReplace); + LLVMInstruction ins(LLVMInstruction::Type::ListReplace, currentLoopScope(), m_loopCondition); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item }); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + + if (m_loopScope >= 0) { + auto scope = m_loopScopes[m_loopScope]; + m_listPtrs[list].loopListWrites[scope].push_back(m_instructions.back()); + } + + m_listInstructions.push_back(m_instructions.back()); } void LLVMCodeBuilder::beginIfStatement(CompilerValue *cond) { - LLVMInstruction ins(LLVMInstruction::Type::BeginIf); - ins.args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); + auto ins = std::make_shared(LLVMInstruction::Type::BeginIf, currentLoopScope(), m_loopCondition); + ins->args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); m_instructions.push_back(ins); } void LLVMCodeBuilder::beginElseBranch() { - m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::BeginElse)); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::BeginElse, currentLoopScope(), m_loopCondition)); } void LLVMCodeBuilder::endIf() { - m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::EndIf)); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::EndIf, currentLoopScope(), m_loopCondition)); } void LLVMCodeBuilder::beginRepeatLoop(CompilerValue *count) { - LLVMInstruction ins(LLVMInstruction::Type::BeginRepeatLoop); - ins.args.push_back({ Compiler::StaticType::Number, static_cast(count) }); + assert(!m_loopCondition); + + auto ins = std::make_shared(LLVMInstruction::Type::BeginRepeatLoop, currentLoopScope(), m_loopCondition); + ins->args.push_back({ Compiler::StaticType::Number, static_cast(count) }); m_instructions.push_back(ins); + pushLoopScope(false); } void LLVMCodeBuilder::beginWhileLoop(CompilerValue *cond) { - LLVMInstruction ins(LLVMInstruction::Type::BeginWhileLoop); - ins.args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); + assert(m_loopCondition); + m_loopCondition = false; + + auto ins = std::make_shared(LLVMInstruction::Type::BeginWhileLoop, currentLoopScope(), m_loopCondition); + ins->args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); m_instructions.push_back(ins); + pushLoopScope(false); } void LLVMCodeBuilder::beginRepeatUntilLoop(CompilerValue *cond) { - LLVMInstruction ins(LLVMInstruction::Type::BeginRepeatUntilLoop); - ins.args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); + assert(m_loopCondition); + m_loopCondition = false; + + auto ins = std::make_shared(LLVMInstruction::Type::BeginRepeatUntilLoop, currentLoopScope(), m_loopCondition); + ins->args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); m_instructions.push_back(ins); + pushLoopScope(false); } void LLVMCodeBuilder::beginLoopCondition() { - m_instructions.push_back({ LLVMInstruction::Type::BeginLoopCondition }); + assert(!m_loopCondition); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::BeginLoopCondition, currentLoopScope(), m_loopCondition)); + m_loopCondition = true; } void LLVMCodeBuilder::endLoop() { if (!m_warp) - m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::Yield)); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::Yield, currentLoopScope(), m_loopCondition)); - m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::EndLoop)); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::EndLoop, currentLoopScope(), m_loopCondition)); + popLoopScope(); } void LLVMCodeBuilder::yield() { - m_instructions.push_back({ LLVMInstruction::Type::Yield }); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::Yield, currentLoopScope(), m_loopCondition)); + + if (m_loopScope >= 0) + m_loopScopes[m_loopScope]->containsYield = true; } void LLVMCodeBuilder::createStop() { - m_instructions.push_back({ LLVMInstruction::Type::Stop }); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::Stop, currentLoopScope(), m_loopCondition)); } void LLVMCodeBuilder::createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args) @@ -1621,7 +1755,7 @@ void LLVMCodeBuilder::createProcedureCall(BlockPrototype *prototype, const Compi for (BlockPrototype::ArgType type : procedureArgs) types.push_back(getProcedureArgType(type)); - LLVMInstruction ins(LLVMInstruction::Type::CallProcedure); + LLVMInstruction ins(LLVMInstruction::Type::CallProcedure, currentLoopScope(), m_loopCondition); ins.procedurePrototype = prototype; createOp(ins, Compiler::StaticType::Void, types, args); } @@ -1734,6 +1868,36 @@ void LLVMCodeBuilder::popScopeLevel() m_heap.pop_back(); } +void LLVMCodeBuilder::pushLoopScope(bool buildPhase) +{ + if (buildPhase) + m_loopScope = m_loopScopeCounter++; + else { + auto scope = std::make_shared(); + m_loopScopes.push_back(scope); + + if (m_loopScope >= 0) { + auto currentScope = m_loopScopes[m_loopScope]; + currentScope->childScopes.push_back(scope); + scope->parentScope = currentScope; + } + + m_loopScope = m_loopScopes.size() - 1; + } + + m_loopScopeTree.push_back(m_loopScope); +} + +void LLVMCodeBuilder::popLoopScope() +{ + m_loopScopeTree.pop_back(); + + if (m_loopScopeTree.empty()) { + m_loopScope = -1; + } else + m_loopScope = m_loopScopeTree.back(); +} + std::string LLVMCodeBuilder::getMainFunctionName(BlockPrototype *procedurePrototype) { return procedurePrototype ? "proc." + procedurePrototype->procCode() : "script"; @@ -1783,8 +1947,9 @@ void LLVMCodeBuilder::verifyFunction(llvm::Function *func) } } -LLVMRegister *LLVMCodeBuilder::addReg(std::shared_ptr reg) +LLVMRegister *LLVMCodeBuilder::addReg(std::shared_ptr reg, std::shared_ptr ins) { + reg->instruction = ins; m_regs.push_back(reg); return reg.get(); } @@ -1997,7 +2162,7 @@ llvm::Constant *LLVMCodeBuilder::castConstValue(const Value &value, Compiler::St } } -Compiler::StaticType LLVMCodeBuilder::optimizeRegisterType(LLVMRegister *reg) +Compiler::StaticType LLVMCodeBuilder::optimizeRegisterType(LLVMRegister *reg) const { Compiler::StaticType ret = reg->type(); @@ -2119,6 +2284,204 @@ void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr) m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty); } +bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType) const +{ + std::unordered_set processed; + int counter = 0; + return isVarOrListTypeSafe(ins, expectedType, processed, counter); +} + +bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, std::unordered_set &log, int &c) const +{ + /* + * The main part of the loop type analyzer. + * + * This is a recursive function which is called when variable + * or list instruction is created. It checks the last write to + * the variable or list in one of the loop scopes. + * + * If the last write operation writes a value with a different + * type, it will return false, otherwise true. + * + * If the last written value is from a variable or list, this + * function is called for it to check its type safety (that's + * why it is recursive). + * + * If the variable or list had a write operation before (in + * the same, parent or child loop scope), it is checked + * recursively. + */ + + if (!ins) + return false; + + /* + * If we are processing something that has been already + * processed, it means there's a case like this: + * x = x + * + * or this: + * x = y + * ... + * y = x + * + * Increment counter to ignore last n write operations. + */ + if (log.find(ins.get()) != log.cend()) + c++; + else + log.insert(ins.get()); + + assert(std::find(m_instructions.begin(), m_instructions.end(), ins) != m_instructions.end()); + const LLVMVariablePtr *varPtr = ins->workVariable ? &m_variablePtrs.at(ins->workVariable) : nullptr; + const LLVMListPtr *listPtr = ins->workList ? &m_listPtrs.at(ins->workList) : nullptr; + assert((varPtr || listPtr) && !(varPtr && listPtr)); + auto scope = ins->loopScope; + + // If we aren't in a loop, we're safe + if (!scope) + return true; + + // If the loop scope contains a suspend and this is a non-warp script, the type may change between suspend and resume + if (scope->containsYield && !m_warp) + return false; + + std::shared_ptr write; + const auto &instructions = varPtr ? m_variableInstructions : m_listInstructions; + + // Find this instruction + auto it = std::find(instructions.begin(), instructions.end(), ins); + assert(it != instructions.end()); + + // Find previous write instruction in this, parent or child loop scope + size_t index = it - instructions.begin(); + + if (varPtr && index > 0) { // this is only needed for variables + bool found = false; + + do { + index--; + write = instructions[index]; + const bool isWrite = (VAR_LIST_READ_INSTRUCTIONS.find(write->type) == VAR_LIST_READ_INSTRUCTIONS.cend()); + found = (write->loopScope && isWrite && write->workVariable == ins->workVariable); + } while (index > 0 && !found); + + if (found) { + // Check if the write operation is in this or child scope + auto parentScope = write->loopScope; + + while (parentScope && parentScope != scope) + parentScope = parentScope->parentScope; + + if (!parentScope) { + // Check if the write operation is in any of the parent scopes + parentScope = scope; + + do { + parentScope = parentScope->parentScope; + } while (parentScope && parentScope != write->loopScope); + } + + // If there was a write operation before this instruction (in this, parent or child scope), check it + if (parentScope) { + if (parentScope == scope) + return isVarOrListWriteResultTypeSafe(write, expectedType, true, log, c); + else + return isVarOrListTypeSafe(write, expectedType, log, c); + } + } + } + + const auto &loopWrites = varPtr ? varPtr->loopVariableWrites : listPtr->loopListWrites; + + // Find root loop scope + auto checkScope = scope; + + while (checkScope->parentScope) { + checkScope = checkScope->parentScope; + } + + // Get all write operations in all loop scopes (from the root loop scope) + std::vector> lastWrites; + + while (checkScope) { + auto it = loopWrites.find(checkScope); + + if (it != loopWrites.cend()) { + assert(!it->second.empty()); + const auto &writes = it->second; + + for (auto w : writes) + lastWrites.push_back(w); + } + + if (checkScope->childScopes.empty()) + checkScope = nullptr; + else + checkScope = checkScope->childScopes.back(); + } + + // If there aren't any write operations or all of them are ignored, we're safe + if (c >= lastWrites.size()) + return true; + + if (varPtr) + write = lastWrites[lastWrites.size() - c - 1]; // Ignore last c writes + else { + // If this is a list instruction, check last write operations except current + for (long i = lastWrites.size() - c - 1; i >= 0; i--) { // Ignore last c writes + if (lastWrites[i] == ins) + continue; + + if (!isVarOrListWriteResultTypeSafe(lastWrites[i], expectedType, false, log, c)) + return false; + } + } + + bool safe = true; + + if (VAR_LIST_READ_INSTRUCTIONS.find(ins->type) == VAR_LIST_READ_INSTRUCTIONS.cend()) // write + safe = isVarOrListWriteResultTypeSafe(ins, expectedType, false, log, c); + + if (safe) + return write ? isVarOrListWriteResultTypeSafe(write, expectedType, false, log, c) : true; + else + return false; +} + +bool LLVMCodeBuilder::isVarOrListWriteResultTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set &log, int &c) + const +{ + const LLVMVariablePtr *varPtr = ins->workVariable ? &m_variablePtrs.at(ins->workVariable) : nullptr; + const LLVMListPtr *listPtr = ins->workList ? &m_listPtrs.at(ins->workList) : nullptr; + assert((varPtr || listPtr) && !(varPtr && listPtr)); + + // If the write operation writes the value of another variable, recursively check its type safety + const auto arg = ins->args.back().second; // value is always the last argument + auto argIns = arg->instruction; + + if (argIns && (argIns->type == LLVMInstruction::Type::ReadVariable || argIns->type == LLVMInstruction::Type::GetListItem)) + return isVarOrListTypeSafe(argIns, expectedType, log, c); + + // Check written type + const bool typeMatches = (optimizeRegisterType(arg) == expectedType); + + if (varPtr) + return typeMatches && (varPtr->type == expectedType || ignoreSavedType); + else + return typeMatches && (listPtr->type == expectedType || ignoreSavedType); +} + +LLVMRegister *LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args) +{ + return createOp({ type, currentLoopScope(), m_loopCondition }, retType, argType, args); +} + +LLVMRegister *LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) +{ + return createOp({ type, currentLoopScope(), m_loopCondition }, retType, argTypes, args); +} + LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args) { std::vector types; @@ -2132,22 +2495,27 @@ LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::St LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) { - m_instructions.push_back(ins); - LLVMInstruction &createdIns = m_instructions.back(); + auto createdIns = std::make_shared(ins); + m_instructions.push_back(createdIns); for (size_t i = 0; i < args.size(); i++) - createdIns.args.push_back({ argTypes[i], static_cast(args[i]) }); + createdIns->args.push_back({ argTypes[i], static_cast(args[i]) }); if (retType != Compiler::StaticType::Void) { auto ret = std::make_shared(retType); ret->isRawValue = true; - createdIns.functionReturnReg = ret.get(); - return addReg(ret); + createdIns->functionReturnReg = ret.get(); + return addReg(ret, createdIns); } return nullptr; } +std::shared_ptr LLVMCodeBuilder::currentLoopScope() const +{ + return m_loopScope >= 0 ? m_loopScopes[m_loopScope] : nullptr; +} + void LLVMCodeBuilder::createValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType) { llvm::Value *converted = nullptr; diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h index f6a6142ee..fb49fe64b 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.h +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h @@ -20,6 +20,7 @@ namespace libscratchcpp class LLVMCompilerContext; class LLVMConstantRegister; +class LLVMLoopScope; class LLVMCodeBuilder : public ICodeBuilder { @@ -119,6 +120,8 @@ class LLVMCodeBuilder : public ICodeBuilder void createListMap(); void pushScopeLevel(); void popScopeLevel(); + void pushLoopScope(bool buildPhase); + void popLoopScope(); std::string getMainFunctionName(BlockPrototype *procedurePrototype); std::string getResumeFunctionName(BlockPrototype *procedurePrototype); @@ -126,7 +129,7 @@ class LLVMCodeBuilder : public ICodeBuilder llvm::Function *getOrCreateFunction(const std::string &name, llvm::FunctionType *type); void verifyFunction(llvm::Function *func); - LLVMRegister *addReg(std::shared_ptr reg); + LLVMRegister *addReg(std::shared_ptr reg, std::shared_ptr ins); llvm::Value *addAlloca(llvm::Type *type); void freeLater(llvm::Value *value); @@ -134,7 +137,7 @@ class LLVMCodeBuilder : public ICodeBuilder llvm::Value *castValue(LLVMRegister *reg, Compiler::StaticType targetType); llvm::Value *castRawValue(LLVMRegister *reg, Compiler::StaticType targetType); llvm::Constant *castConstValue(const Value &value, Compiler::StaticType targetType); - Compiler::StaticType optimizeRegisterType(LLVMRegister *reg); + Compiler::StaticType optimizeRegisterType(LLVMRegister *reg) const; llvm::Type *getType(Compiler::StaticType type); Compiler::StaticType getProcedureArgType(BlockPrototype::ArgType type); llvm::Value *isNaN(llvm::Value *num); @@ -146,9 +149,15 @@ class LLVMCodeBuilder : public ICodeBuilder void reloadVariables(llvm::Value *targetVariables); void reloadLists(); void updateListDataPtr(const LLVMListPtr &listPtr); + bool isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType) const; + bool isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, std::unordered_set &log, int &c) const; + bool isVarOrListWriteResultTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set &log, int &c) const; + LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args); + LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {}); LLVMRegister *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args); LLVMRegister *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {}); + std::shared_ptr currentLoopScope() const; void createValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType); void createReusedValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType); @@ -215,7 +224,7 @@ class LLVMCodeBuilder : public ICodeBuilder llvm::StructType *m_valueDataType = nullptr; llvm::FunctionType *m_resumeFuncType = nullptr; - std::vector m_instructions; + std::vector> m_instructions; std::vector> m_regs; std::vector> m_localVars; BlockPrototype *m_procedurePrototype = nullptr; @@ -223,6 +232,13 @@ class LLVMCodeBuilder : public ICodeBuilder bool m_warp = false; int m_defaultArgCount = 0; + long m_loopScope = -1; // index + std::vector> m_loopScopes; + long m_loopScopeCounter = 0; // replacement for m_loopScopes size in build phase + std::vector m_loopScopeTree; + bool m_loopCondition = false; // whether we're currently compiling a loop condition + std::vector> m_variableInstructions; + std::vector> m_listInstructions; std::vector> m_heap; // scopes std::shared_ptr m_output; diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h index ddb39677b..8e88e8876 100644 --- a/src/dev/engine/internal/llvm/llvminstruction.h +++ b/src/dev/engine/internal/llvm/llvminstruction.h @@ -10,6 +10,7 @@ namespace libscratchcpp { class BlockPrototype; +class LLVMLoopScope; struct LLVMInstruction { @@ -77,8 +78,10 @@ struct LLVMInstruction ProcedureArg }; - LLVMInstruction(Type type) : - type(type) + LLVMInstruction(Type type, std::shared_ptr loopScope, bool loopCondition) : + type(type), + loopScope(loopScope), + loopCondition(loopCondition) { } @@ -92,6 +95,8 @@ struct LLVMInstruction List *workList = nullptr; // for lists BlockPrototype *procedurePrototype = nullptr; size_t procedureArgIndex = 0; + std::shared_ptr loopScope; + bool loopCondition = false; // whether the instruction is part of a loop condition }; } // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmlistptr.h b/src/dev/engine/internal/llvm/llvmlistptr.h index 6b427572e..ca97bc0e8 100644 --- a/src/dev/engine/internal/llvm/llvmlistptr.h +++ b/src/dev/engine/internal/llvm/llvmlistptr.h @@ -3,6 +3,7 @@ #pragma once #include +#include namespace llvm { @@ -14,6 +15,9 @@ class Value; namespace libscratchcpp { +class LLVMLoopScope; +class LLVMInstruction; + struct LLVMListPtr { llvm::Value *ptr = nullptr; @@ -22,6 +26,9 @@ struct LLVMListPtr llvm::Value *allocatedSizePtr = nullptr; llvm::Value *dataPtrDirty = nullptr; Compiler::StaticType type = Compiler::StaticType::Unknown; + + // Used in build phase to check the type safety of lists in loops + std::unordered_map, std::vector>> loopListWrites; // loop scope, write instructions }; } // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmloopscope.h b/src/dev/engine/internal/llvm/llvmloopscope.h new file mode 100644 index 000000000..859c2cb1f --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmloopscope.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +namespace libscratchcpp +{ + +struct LLVMLoopScope +{ + bool containsYield = false; + std::shared_ptr parentScope; + std::vector> childScopes; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmregisterbase.h b/src/dev/engine/internal/llvm/llvmregisterbase.h index 2267a6dfb..33aace036 100644 --- a/src/dev/engine/internal/llvm/llvmregisterbase.h +++ b/src/dev/engine/internal/llvm/llvmregisterbase.h @@ -15,12 +15,15 @@ class Value; namespace libscratchcpp { +class LLVMInstruction; + struct LLVMRegisterBase { virtual const Value &constValue() const = 0; llvm::Value *value = nullptr; bool isRawValue = false; + std::shared_ptr instruction; }; } // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmvariableptr.h b/src/dev/engine/internal/llvm/llvmvariableptr.h index c2525dfd6..28105f191 100644 --- a/src/dev/engine/internal/llvm/llvmvariableptr.h +++ b/src/dev/engine/internal/llvm/llvmvariableptr.h @@ -3,6 +3,7 @@ #pragma once #include +#include namespace llvm { @@ -14,6 +15,9 @@ class Value; namespace libscratchcpp { +class LLVMLoopScope; +class LLVMInstruction; + struct LLVMVariablePtr { llvm::Value *stackPtr = nullptr; @@ -21,6 +25,9 @@ struct LLVMVariablePtr Compiler::StaticType type = Compiler::StaticType::Unknown; bool onStack = false; bool changed = false; + + // Used in build phase to check the type safety of variables in loops + std::unordered_map, std::vector>> loopVariableWrites; // loop scope, write instructions }; } // namespace libscratchcpp diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index c0bd3173d..e5d8f0cc0 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -362,17 +362,13 @@ void Engine::stop() // https://github.com/scratchfoundation/scratch-vm/blob/f1aa92fad79af17d9dd1c41eeeadca099339a9f1/src/engine/runtime.js#L2057-L2081 if (m_activeThread) { stopThread(m_activeThread.get()); - // NOTE: The project should continue running even after "stop all" is called and the remaining threads should be stepped once. - // The remaining threads can even start new threads which will ignore the "stop all" call and will "restart" the project. - // This is probably a bug in the Scratch VM, but let's keep it here to keep it compatible. - m_threadsToStop = m_threads; // Remove threads owned by clones because clones are going to be deleted (#547) m_threads.erase( std::remove_if( m_threads.begin(), m_threads.end(), - [](std::shared_ptr thread) { + [this](std::shared_ptr thread) { assert(thread); Target *target = thread->target(); assert(target); @@ -380,13 +376,20 @@ void Engine::stop() if (!target->isStage()) { Sprite *sprite = static_cast(target); - if (sprite->isClone()) + if (sprite->isClone()) { + m_threadAboutToStop(thread.get()); return true; + } } return false; }), m_threads.end()); + + // NOTE: The project should continue running even after "stop all" is called and the remaining threads should be stepped once. + // The remaining threads can even start new threads which will ignore the "stop all" call and will "restart" the project. + // This is probably a bug in the Scratch VM, but let's keep it here to keep it compatible. + m_threadsToStop = m_threads; } else { // If there isn't any active thread, it means the project was stopped from the outside // In this case all threads should be removed and the project should be considered stopped @@ -636,7 +639,6 @@ void Engine::run() { start(); eventLoop(true); - finalize(); } void Engine::runEventLoop() @@ -777,7 +779,12 @@ void Engine::eventLoop(bool untilProjectStops) m_clock->sleep(sleepTime); } - finalize(); + m_eventLoopMutex.lock(); + m_threads.clear(); + m_running = false; + m_frameActivity = false; + m_redrawRequested = false; + m_eventLoopMutex.unlock(); } bool Engine::isRunning() const @@ -1317,16 +1324,26 @@ void Engine::moveDrawableForwardLayers(Drawable *drawable, int layers) if (it == m_sortedDrawables.end()) return; - auto target = it + layers; + auto target = it; + int layersAbs = std::abs(layers); - if (target <= m_sortedDrawables.begin()) { - moveDrawableToBack(drawable); - return; - } + for (int i = 0; i < layersAbs; i++) { + if (target <= m_sortedDrawables.begin()) { + moveDrawableToBack(drawable); + return; + } - if (target >= m_sortedDrawables.end()) { - moveDrawableToFront(drawable); - return; + if (target >= m_sortedDrawables.end()) { + moveDrawableToFront(drawable); + return; + } + + Drawable *currentDrawable; + + do { + currentDrawable = *target; + target += layers / layersAbs; + } while (currentDrawable->isTextBubble() && static_cast(currentDrawable)->text().empty()); } if (layers > 0) @@ -1482,8 +1499,10 @@ void Engine::setExtensions(const std::vector &newExtensions) // Register blocks of default extensions const auto &defaultExtensions = Blocks::extensions(); - for (auto ext : defaultExtensions) + for (auto ext : defaultExtensions) { ext->registerBlocks(this); + ext->onInit(this); + } // Register blocks of custom extensions for (auto ext : m_extensions) { @@ -1962,15 +1981,6 @@ void Engine::compileMonitor(std::shared_ptr monitor) #endif // USE_LLVM } -void Engine::finalize() -{ - m_eventLoopMutex.lock(); - m_threads.clear(); - m_running = false; - m_redrawRequested = false; - m_eventLoopMutex.unlock(); -} - void Engine::deleteClones() { m_eventLoopMutex.lock(); diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h index ed4b562dd..33cd172c6 100644 --- a/src/engine/internal/engine.h +++ b/src/engine/internal/engine.h @@ -206,7 +206,6 @@ class Engine : public IEngine std::vector> stepThreads(); void stepThread(std::shared_ptr thread); void eventLoop(bool untilProjectStops = false); - void finalize(); void deleteClones(); void removeExecutableClones(); void addVarOrListMonitor(std::shared_ptr monitor, Target *target); diff --git a/src/internal/scratch3reader.cpp b/src/internal/scratch3reader.cpp index 7e9033266..1d7168dce 100644 --- a/src/internal/scratch3reader.cpp +++ b/src/internal/scratch3reader.cpp @@ -562,9 +562,11 @@ void Scratch3Reader::read() if (m_zipReader->open()) { // Parse the JSON try { - std::string jsonStr; - m_zipReader->readFileToString("project.json", jsonStr); - m_json = json::parse(jsonStr); + char *buf; + const size_t size = m_zipReader->readFile("project.json", (void **)&buf); + assert(buf); + m_json = json::parse(buf, buf + size); + free(buf); } catch (std::exception &e) { printErr("invalid JSON file", e.what()); } diff --git a/src/internal/zipreader.cpp b/src/internal/zipreader.cpp index fd8a48db6..f4ee39a4e 100644 --- a/src/internal/zipreader.cpp +++ b/src/internal/zipreader.cpp @@ -36,7 +36,7 @@ void ZipReader::close() size_t ZipReader::readFile(const std::string &fileName, void **buf) { if (!m_zip) { - buf = nullptr; + *buf = nullptr; return 0; } diff --git a/src/project_p.cpp b/src/project_p.cpp index c8c40e204..5dd383311 100644 --- a/src/project_p.cpp +++ b/src/project_p.cpp @@ -121,7 +121,9 @@ bool ProjectPrivate::tryLoad(IProjectReader *reader) // Load asset data for (size_t i = 0; i < assets.size(); i++) { const std::string &data = assetData[i]; - assets[assetNames[i]]->setData(data.size(), static_cast(const_cast(data.c_str()))); + char *ptr = (char *)malloc(data.size() * sizeof(char)); + strncpy(ptr, data.data(), data.size()); + assets[assetNames[i]]->setData(data.size(), ptr); } } else { diff --git a/src/scratch/asset.cpp b/src/scratch/asset.cpp index 79ffd4b1c..d17b68bdf 100644 --- a/src/scratch/asset.cpp +++ b/src/scratch/asset.cpp @@ -14,6 +14,15 @@ Asset::Asset(const std::string &name, const std::string &id, const std::string & impl->updateFileName(id); } +/*! Destroys Asset. */ +Asset::~Asset() +{ + if (impl->data && !impl->dataCloned) { + free(impl->data); + impl->data = nullptr; + } +} + /*! Sets the ID (MD5 hash) of the asset file. */ void Asset::setId(const std::string &id) { @@ -51,11 +60,15 @@ unsigned int Asset::dataSize() const return impl->dataSize; } -/*! Sets the asset data. */ +/*! Sets the asset data (will be deallocated when the object is destroyed). */ void Asset::setData(unsigned int size, void *data) { + if (impl->data) + free(impl->data); + impl->dataSize = size; impl->data = data; + impl->dataCloned = isClone(); processData(size, data); } diff --git a/src/scratch/asset_p.h b/src/scratch/asset_p.h index d004fb2f4..604eeb1e6 100644 --- a/src/scratch/asset_p.h +++ b/src/scratch/asset_p.h @@ -19,8 +19,9 @@ struct AssetPrivate std::string name; std::string dataFormat; std::string fileName; - const void *data = nullptr; + void *data = nullptr; unsigned int dataSize = 0; + bool dataCloned = false; Target *target = nullptr; }; diff --git a/src/scratch/sound.cpp b/src/scratch/sound.cpp index 64fcf35a0..cd3db865e 100644 --- a/src/scratch/sound.cpp +++ b/src/scratch/sound.cpp @@ -116,6 +116,11 @@ void Sound::processData(unsigned int size, void *data) std::cerr << "Failed to load sound " << name() << std::endl; } +bool Sound::isClone() const +{ + return impl->cloneRoot; +} + void Sound::stopCloneSounds() { Target *target = this->target(); diff --git a/src/scratch/value_functions.cpp b/src/scratch/value_functions.cpp index 7dced3575..3d0f3c8a1 100644 --- a/src/scratch/value_functions.cpp +++ b/src/scratch/value_functions.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include "value_functions_p.h" @@ -382,6 +383,10 @@ extern "C" return ret; } + // snprintf() is locale-dependent + std::string oldLocale = std::setlocale(LC_NUMERIC, nullptr); + std::setlocale(LC_NUMERIC, "C"); + const int maxlen = 26; // should be enough for any number char *buffer = (char *)malloc((maxlen + 1) * sizeof(char)); @@ -429,6 +434,9 @@ extern "C" } } + // Restore old locale + std::setlocale(LC_NUMERIC, oldLocale.c_str()); + return buffer; } diff --git a/test/assets/asset_test.cpp b/test/assets/asset_test.cpp index 5bd5920be..86dcfd84c 100644 --- a/test/assets/asset_test.cpp +++ b/test/assets/asset_test.cpp @@ -29,12 +29,24 @@ TEST(AssetTest, Data) TestAsset asset; ASSERT_EQ(asset.data(), nullptr); - static char data[5] = "abcd"; + char *data = (char *)malloc(4 * sizeof(char)); + strncpy(data, "abcd", 4); + asset.setData(5, data); ASSERT_EQ(asset.data(), data); ASSERT_EQ(asset.size, 5); ASSERT_EQ(asset.processedData, data); ASSERT_EQ(asset.callCount, 1); + + // Should deallocate in setData() + data = (char *)malloc(11 * sizeof(char)); + strncpy(data, "Hello world!", 11); + + asset.setData(5, data); + ASSERT_EQ(asset.data(), data); + ASSERT_EQ(asset.size, 5); + ASSERT_EQ(asset.processedData, data); + ASSERT_EQ(asset.callCount, 2); } TEST(AssetTest, Target) diff --git a/test/assets/sound_test.cpp b/test/assets/sound_test.cpp index d3b81bf2a..b83d5a999 100644 --- a/test/assets/sound_test.cpp +++ b/test/assets/sound_test.cpp @@ -59,16 +59,20 @@ TEST_F(SoundTest, ProcessData) Sound sound("sound1", "a", "wav"); sound.setRate(44100); - const char *data = "abc"; - void *dataPtr = const_cast(static_cast(data)); + char *data = (char *)malloc(4 * sizeof(char)); + strncpy(data, "abcd", 4); EXPECT_CALL(*m_player, isLoaded()).WillOnce(Return(false)); - EXPECT_CALL(*m_player, load(3, dataPtr, 44100)).WillOnce(Return(true)); - sound.setData(3, dataPtr); + EXPECT_CALL(*m_player, load(3, data, 44100)).WillOnce(Return(true)); + sound.setData(3, data); + + // Should deallocate in setData() + data = (char *)malloc(11 * sizeof(char)); + strncpy(data, "Hello world!", 11); EXPECT_CALL(*m_player, isLoaded()).WillOnce(Return(true)); EXPECT_CALL(*m_player, load).Times(0); - sound.setData(3, dataPtr); + sound.setData(3, data); } TEST_F(SoundTest, SetVolume) @@ -151,12 +155,12 @@ TEST_F(SoundTest, Clone) sound->setRate(44100); sound->setSampleCount(10000); - const char *data = "abc"; - void *dataPtr = const_cast(static_cast(data)); + char *data = (char *)malloc(4 * sizeof(char)); + strncpy(data, "abcd", 4); EXPECT_CALL(*m_player, isLoaded()).WillOnce(Return(false)); - EXPECT_CALL(*m_player, load(3, dataPtr, 44100)).WillOnce(Return(true)); - sound->setData(3, dataPtr); + EXPECT_CALL(*m_player, load(3, data, 44100)).WillOnce(Return(true)); + sound->setData(3, data); auto clonePlayer = std::make_shared(); EXPECT_CALL(m_playerFactory, createAudioPlayer()).WillOnce(Return(clonePlayer)); diff --git a/test/compiler/CMakeLists.txt b/test/compiler/CMakeLists.txt index 9dc335dfb..0b7d501a9 100644 --- a/test/compiler/CMakeLists.txt +++ b/test/compiler/CMakeLists.txt @@ -9,6 +9,7 @@ target_link_libraries( compiler_test GTest::gtest_main scratchcpp + zip nlohmann_json::nlohmann_json ) diff --git a/test/compiler/testextension.cpp b/test/compiler/testextension.cpp index cb001061d..ce26bbc3a 100644 --- a/test/compiler/testextension.cpp +++ b/test/compiler/testextension.cpp @@ -18,6 +18,11 @@ std::string TestExtension::description() const return ""; } +Rgb TestExtension::color() const +{ + return rgb(0, 0, 0); +} + void TestExtension::registerBlocks(IEngine *engine) { engine->addInput(this, "INPUT1", INPUT1); diff --git a/test/compiler/testextension.h b/test/compiler/testextension.h index c7bdf102d..6efc819d8 100644 --- a/test/compiler/testextension.h +++ b/test/compiler/testextension.h @@ -25,6 +25,7 @@ class TestExtension : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp index 723c81011..e107097b8 100644 --- a/test/dev/llvm/llvmcodebuilder_test.cpp +++ b/test/dev/llvm/llvmcodebuilder_test.cpp @@ -2832,6 +2832,39 @@ TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend) v = m_builder->addVariableValue(localVar.get()); m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(0); + m_builder->createVariableWrite(localVar.get(), v); + + m_builder->beginLoopCondition(); + v = m_builder->createCmpLT(m_builder->addVariableValue(localVar.get()), m_builder->addConstValue(3)); + m_builder->beginWhileLoop(v); + m_builder->endLoop(); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(0); + m_builder->createVariableWrite(localVar.get(), v); + + m_builder->beginLoopCondition(); + v = m_builder->createCmpEQ(m_builder->addVariableValue(localVar.get()), m_builder->addConstValue(2)); + m_builder->beginRepeatUntilLoop(v); + m_builder->endLoop(); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + std::string expected = "hello world\n" "-4.8\n"; @@ -2851,6 +2884,55 @@ TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend) testing::internal::CaptureStdout(); code->run(ctx.get()); ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); + ASSERT_FALSE(code->isFinished(ctx.get())); + + globalVar->setValue("test"); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), ""); + ASSERT_FALSE(code->isFinished(ctx.get())); + + globalVar->setValue(true); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "true\n"); + ASSERT_FALSE(code->isFinished(ctx.get())); + + localVar->setValue(1); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), ""); + ASSERT_FALSE(code->isFinished(ctx.get())); + + localVar->setValue("2"); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), ""); + ASSERT_FALSE(code->isFinished(ctx.get())); + + localVar->setValue(3); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "3\n"); + ASSERT_FALSE(code->isFinished(ctx.get())); + + localVar->setValue(1); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), ""); + ASSERT_FALSE(code->isFinished(ctx.get())); + + localVar->setValue(2); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "2\n"); } TEST_F(LLVMCodeBuilderTest, ListsAfterSuspend) @@ -3893,192 +3975,1735 @@ TEST_F(LLVMCodeBuilderTest, LoopVariables) ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); } -TEST_F(LLVMCodeBuilderTest, LoopLists) +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis1) { Stage stage; Sprite sprite; sprite.setEngine(&m_engine); EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); - auto globalList1 = std::make_shared("", ""); - stage.addList(globalList1); + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); - auto globalList2 = std::make_shared("", ""); - stage.addList(globalList2); + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); - auto localList = std::make_shared("", ""); - sprite.addList(localList); + createBuilder(&sprite, true); - auto counter1 = std::make_shared("", ""); - auto counter2 = std::make_shared("", ""); - sprite.addVariable(counter1); - sprite.addVariable(counter2); + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); - createBuilder(&sprite, true); + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a string is assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); - auto resetLists = [this, globalList1, globalList2, localList]() { - m_builder->createListClear(globalList1.get()); - m_builder->createListClear(globalList2.get()); - m_builder->createListClear(localList.get()); + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(localVar.get(), v); + } + m_builder->endLoop(); - m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(1)); - m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(2)); + std::string expected = + "5.25\n" + "0\n"; - m_builder->createListAppend(globalList2.get(), m_builder->addConstValue("hello")); - m_builder->createListAppend(globalList2.get(), m_builder->addConstValue("world")); + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} - m_builder->createListAppend(localList.get(), m_builder->addConstValue(false)); - m_builder->createListAppend(localList.get(), m_builder->addConstValue(true)); - }; +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis2) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); - auto checkLists = [this, globalList1, globalList2, localList]() { - CompilerValue *v = m_builder->addListItem(globalList1.get(), m_builder->addConstValue(0)); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); - v = m_builder->addListItem(globalList2.get(), m_builder->addConstValue(1)); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); - v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); - }; + createBuilder(&sprite, true); - // repeat (2) - resetLists(); + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); - CompilerValue *v, *v1, *v2; v = m_builder->addConstValue(2); m_builder->beginRepeatLoop(v); { - v = m_builder->addConstValue("hello world"); - m_builder->createListAppend(globalList1.get(), v); - - v1 = m_builder->addConstValue(0); - v2 = m_builder->addConstValue(8.5); - m_builder->createListInsert(globalList2.get(), v1, v2); + // Type is known here because a number is assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); - v1 = m_builder->addConstValue(1); - v2 = m_builder->addConstValue(-4.25); - m_builder->createListReplace(localList.get(), v1, v2); + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(localVar.get(), v); } m_builder->endLoop(); - checkLists(); + std::string expected = + "5.25\n" + "12.5\n"; - // repeat(0) - resetLists(); + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} - v = m_builder->addConstValue(0); - m_builder->beginRepeatLoop(v); - { - v = m_builder->addConstValue("hello world"); - m_builder->createListAppend(globalList1.get(), v); +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis3) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); - v1 = m_builder->addConstValue(0); - v2 = m_builder->addConstValue(8.5); - m_builder->createListInsert(globalList2.get(), v1, v2); + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); - v1 = m_builder->addConstValue(1); - v2 = m_builder->addConstValue(-4.25); - m_builder->createListReplace(localList.get(), v1, v2); - } - m_builder->endLoop(); + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); - checkLists(); + createBuilder(&sprite, true); - // while (counter1 < 5) { ... until (counter2 == 3) {... counter2++ }; while (false) { ... }; counter1++ } - resetLists(); + CompilerValue *v = m_builder->addConstValue(0); + m_builder->createVariableWrite(globalVar.get(), v); - v = m_builder->addConstValue(0); - m_builder->createVariableWrite(counter1.get(), v); + v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); - m_builder->beginLoopCondition(); - v1 = m_builder->addVariableValue(counter1.get()); - v2 = m_builder->addConstValue(5); - v = m_builder->createCmpLT(v1, v2); - m_builder->beginWhileLoop(v); + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); { - v = m_builder->addConstValue(0); - m_builder->createVariableWrite(counter2.get(), v); + // Type is unknown here because a variable with unknown type is assigned (the variable has unknown type because a string is assigned later) + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); - m_builder->beginLoopCondition(); - v1 = m_builder->addVariableValue(counter2.get()); - v2 = m_builder->addConstValue(3); - v = m_builder->createCmpEQ(v1, v2); - m_builder->beginRepeatUntilLoop(v); + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); { - v1 = m_builder->addConstValue(0); - v2 = m_builder->addConstValue(8.5); - m_builder->createListInsert(globalList2.get(), v1, v2); - - v1 = m_builder->addConstValue(1); - v2 = m_builder->addConstValue(-4.25); - m_builder->createListReplace(localList.get(), v1, v2); - - v1 = m_builder->addVariableValue(counter2.get()); - v2 = m_builder->addConstValue(1); - v = m_builder->createAdd(v1, v2); - m_builder->createVariableWrite(counter2.get(), v); - } - m_builder->endLoop(); + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); - m_builder->beginLoopCondition(); - v = m_builder->addConstValue(false); - m_builder->beginWhileLoop(v); - { - v = m_builder->addConstValue("hello world"); - m_builder->createListAppend(globalList1.get(), v); + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); } m_builder->endLoop(); - - v1 = m_builder->addVariableValue(counter1.get()); - v2 = m_builder->addConstValue(1); - v = m_builder->createAdd(v1, v2); - m_builder->createVariableWrite(counter1.get(), v); } m_builder->endLoop(); - checkLists(); + std::string expected = + "5.25\n" + "0\n"; - // until (true) - resetLists(); + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} - m_builder->beginLoopCondition(); - v = m_builder->addConstValue(true); - m_builder->beginRepeatUntilLoop(v); - { - v = m_builder->addConstValue("hello world"); - m_builder->createListAppend(globalList1.get(), v); +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis4) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); - v1 = m_builder->addConstValue(0); - v2 = m_builder->addConstValue(8.5); - m_builder->createListInsert(globalList2.get(), v1, v2); + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); - v1 = m_builder->addConstValue(1); - v2 = m_builder->addConstValue(-4.25); - m_builder->createListReplace(localList.get(), v1, v2); + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(0); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is known here because a variable is assigned which has a number assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "12.5\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis5) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a string variable is assigned later which has a number assigned as well + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "12.5\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis6) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is known here because a variable with known type is assigned later (even though the variable has a string assigned later, there's a number assigned before the read operation) + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(10); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "10\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis7) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a variable with a known, but different type is assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(10); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis8) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a variable with a known, but different type is assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis9) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", "", "123"); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + + // Type is unknown here because a variable of unknown type is assigned before the read operation + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(10); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5\n" + "5\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis10) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", "", "123"); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a variable of unknown type is assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "123\n" + "0\n" + "12.5\n" + "12.5\n" + "0\n" + "12.5\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis11) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", "", "123"); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because the variable is assigned to itself later + // This case is not checked because the code isn't considered valid + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "123\n" + "123\n" + "123\n" + "123\n" + "123\n" + "123\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis12) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", "", "123"); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because another variable is assigned later, but it has this variable assigned + // This case is not checked because the code isn't considered valid + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "123\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, LoopLists) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList1 = std::make_shared("", ""); + stage.addList(globalList1); + + auto globalList2 = std::make_shared("", ""); + stage.addList(globalList2); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + auto counter1 = std::make_shared("", ""); + auto counter2 = std::make_shared("", ""); + sprite.addVariable(counter1); + sprite.addVariable(counter2); + + createBuilder(&sprite, true); + + auto resetLists = [this, globalList1, globalList2, localList]() { + m_builder->createListClear(globalList1.get()); + m_builder->createListClear(globalList2.get()); + m_builder->createListClear(localList.get()); + + m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(1)); + m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(2)); + + m_builder->createListAppend(globalList2.get(), m_builder->addConstValue("hello")); + m_builder->createListAppend(globalList2.get(), m_builder->addConstValue("world")); + + m_builder->createListAppend(localList.get(), m_builder->addConstValue(false)); + m_builder->createListAppend(localList.get(), m_builder->addConstValue(true)); + }; + + auto checkLists = [this, globalList1, globalList2, localList]() { + CompilerValue *v = m_builder->addListItem(globalList1.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addListItem(globalList2.get(), m_builder->addConstValue(1)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + }; + + // repeat (2) + resetLists(); + + CompilerValue *v, *v1, *v2; + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("hello world"); + m_builder->createListAppend(globalList1.get(), v); + + v1 = m_builder->addConstValue(0); + v2 = m_builder->addConstValue(8.5); + m_builder->createListInsert(globalList2.get(), v1, v2); + + v1 = m_builder->addConstValue(1); + v2 = m_builder->addConstValue(-4.25); + m_builder->createListReplace(localList.get(), v1, v2); + } + m_builder->endLoop(); + + checkLists(); + + // repeat(0) + resetLists(); + + v = m_builder->addConstValue(0); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("hello world"); + m_builder->createListAppend(globalList1.get(), v); + + v1 = m_builder->addConstValue(0); + v2 = m_builder->addConstValue(8.5); + m_builder->createListInsert(globalList2.get(), v1, v2); + + v1 = m_builder->addConstValue(1); + v2 = m_builder->addConstValue(-4.25); + m_builder->createListReplace(localList.get(), v1, v2); + } + m_builder->endLoop(); + + checkLists(); + + // while (counter1 < 5) { ... until (counter2 == 3) {... counter2++ }; while (false) { ... }; counter1++ } + resetLists(); + + v = m_builder->addConstValue(0); + m_builder->createVariableWrite(counter1.get(), v); + + m_builder->beginLoopCondition(); + v1 = m_builder->addVariableValue(counter1.get()); + v2 = m_builder->addConstValue(5); + v = m_builder->createCmpLT(v1, v2); + m_builder->beginWhileLoop(v); + { + v = m_builder->addConstValue(0); + m_builder->createVariableWrite(counter2.get(), v); + + m_builder->beginLoopCondition(); + v1 = m_builder->addVariableValue(counter2.get()); + v2 = m_builder->addConstValue(3); + v = m_builder->createCmpEQ(v1, v2); + m_builder->beginRepeatUntilLoop(v); + { + v1 = m_builder->addConstValue(0); + v2 = m_builder->addConstValue(8.5); + m_builder->createListInsert(globalList2.get(), v1, v2); + + v1 = m_builder->addConstValue(1); + v2 = m_builder->addConstValue(-4.25); + m_builder->createListReplace(localList.get(), v1, v2); + + v1 = m_builder->addVariableValue(counter2.get()); + v2 = m_builder->addConstValue(1); + v = m_builder->createAdd(v1, v2); + m_builder->createVariableWrite(counter2.get(), v); + } + m_builder->endLoop(); + + m_builder->beginLoopCondition(); + v = m_builder->addConstValue(false); + m_builder->beginWhileLoop(v); + { + v = m_builder->addConstValue("hello world"); + m_builder->createListAppend(globalList1.get(), v); + } + m_builder->endLoop(); + + v1 = m_builder->addVariableValue(counter1.get()); + v2 = m_builder->addConstValue(1); + v = m_builder->createAdd(v1, v2); + m_builder->createVariableWrite(counter1.get(), v); + } + m_builder->endLoop(); + + checkLists(); + + // until (true) + resetLists(); + + m_builder->beginLoopCondition(); + v = m_builder->addConstValue(true); + m_builder->beginRepeatUntilLoop(v); + { + v = m_builder->addConstValue("hello world"); + m_builder->createListAppend(globalList1.get(), v); + + v1 = m_builder->addConstValue(0); + v2 = m_builder->addConstValue(8.5); + m_builder->createListInsert(globalList2.get(), v1, v2); + + v1 = m_builder->addConstValue(1); + v2 = m_builder->addConstValue(-4.25); + m_builder->createListReplace(localList.get(), v1, v2); + } + m_builder->endLoop(); + + checkLists(); + + std::string expected = + "1\n" + "8.5\n" + "false\n" + "1\n" + "world\n" + "false\n" + "1\n" + "8.5\n" + "false\n" + "1\n" + "world\n" + "false\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + ; + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis1) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a string is added later + v = m_builder->createSub(m_builder->addListSize(localList.get()), m_builder->addConstValue(1)); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue("test"); + m_builder->createListAppend(localList.get(), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "0\n" + "1\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis2) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is known here because a number is assigned later + v = m_builder->addConstValue(0); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(12.5); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "12.5\n" + "-1\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis3) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(globalList.get()); + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(0); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a list item with unknown type is inserted (the item has unknown type because a string is assigned later) + v = m_builder->addConstValue(0); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListInsert(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue("test"); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "2\n" + "1\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis4) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(globalList.get()); + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(0); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is known here because a number list item is added later + v = m_builder->createSub(m_builder->addListSize(localList.get()), m_builder->addConstValue(1)); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(12.5); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "12.5\n" + "0\n" + "1\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis5) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(globalList.get()); + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a string list item is assigned later which becomes a number later + v = m_builder->addConstValue(0); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue(12.5); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "-1\n" + "0\n" + "12.5\n" + "-1\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis6) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(globalList.get()); + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(-156.07); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a list item with unknown type is added later + v = m_builder->createSub(m_builder->addListSize(localList.get()), m_builder->addConstValue(1)); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(10); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + + v = m_builder->createSub(m_builder->addListSize(globalList.get()), m_builder->addConstValue(1)); + v = m_builder->addListItem(globalList.get(), v); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "0\n" + "1\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis7) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a list item with unknown type is assigned later + v = m_builder->addConstValue(0); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue(10); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "-1\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis8) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a list item with a unknown type is assigned later + v = m_builder->addConstValue(0); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "-1\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis9) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue(5); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + + // Type is unknown here because a list item with unknown type is assigned before the read operation + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(10); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue("test"); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); } m_builder->endLoop(); - checkLists(); + std::string expected = + "5\n" + "0\n" + "1\n" + "5\n" + "0\n" + "1\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis10) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + localList->append(123); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a list item with unknown type is assigned later + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue(12.5); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); std::string expected = + "123\n" + "0\n" "1\n" - "8.5\n" - "false\n" + "0\n" + "-1\n" + "0\n" + "12.5\n" + "-1\n" + "0\n" + "12.5\n" + "-1\n" + "0\n" + "12.5\n" + "-1\n" + "0\n" + "12.5\n" + "-1\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis11) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + localList->append(123); + localList->append("10"); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because an item from the same list is assigned later, but the list has unknown type + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(1)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue(12.5); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "123\n" + "0\n" "1\n" - "world\n" - "false\n" + "10\n" + "-1\n" + "0\n" + "10\n" + "-1\n" + "0\n" + "10\n" + "-1\n" + "0\n" + "10\n" + "-1\n" + "0\n" + "10\n" + "-1\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis12) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + localList->append(123); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because an item from another list is assigned later, but it has an item from this list assigned + // This case is not checked because the code isn't considered valid + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "123\n" + "0\n" "1\n" - "8.5\n" - "false\n" + "0\n" + "-1\n" + "0\n" + "0\n" + "-1\n" + "0\n" + "0\n" + "-1\n" + "0\n" + "0\n" + "-1\n" + "0\n" + "0\n" + "-1\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VarAndListLoopTypeAnalysis) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto var = std::make_shared("", ""); + sprite.addVariable(var); + + auto list = std::make_shared("", ""); + sprite.addList(list); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(-2); + m_builder->createVariableWrite(var.get(), v); + + m_builder->createListClear(list.get()); + + v = m_builder->addConstValue(5.25); + m_builder->createListAppend(list.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a variable value with unknown type is added later + v = m_builder->createSub(m_builder->addListSize(list.get()), m_builder->addConstValue(1)); + v = m_builder->addListItem(list.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(list.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(list.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addVariableValue(var.get()); + m_builder->createListAppend(list.get(), v); + + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(var.get(), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" "1\n" - "world\n" - "false\n"; + "-2\n" + "0\n" + "1\n" + "0\n" + "0\n" + "1\n"; auto code = m_builder->finalize(); Script script(&sprite, nullptr, nullptr); script.setCode(code); - ; Thread thread(&sprite, nullptr, &script); auto ctx = code->createExecutionContext(&thread); testing::internal::CaptureStdout(); diff --git a/test/dev/test_api/testextension.cpp b/test/dev/test_api/testextension.cpp index c637e522e..24eebec20 100644 --- a/test/dev/test_api/testextension.cpp +++ b/test/dev/test_api/testextension.cpp @@ -21,6 +21,11 @@ std::string TestExtension::description() const return ""; } +Rgb TestExtension::color() const +{ + return rgb(0, 0, 0); +} + void TestExtension::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "test_simple", &compileSimple); diff --git a/test/dev/test_api/testextension.h b/test/dev/test_api/testextension.h index e69bbcdd7..12f4e7297 100644 --- a/test/dev/test_api/testextension.h +++ b/test/dev/test_api/testextension.h @@ -10,6 +10,7 @@ class TestExtension : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp index cb0c17b68..944f5e05c 100644 --- a/test/engine/engine_test.cpp +++ b/test/engine/engine_test.cpp @@ -1362,6 +1362,11 @@ TEST(EngineTest, MoveDrawableForwardLayers) std::vector sprites; createTargets(&engine, sprites); + const auto &targets = engine.targets(); + + for (auto target : targets) + target->bubble()->setText("test"); + engine.moveDrawableForwardLayers(sprites[4], 2); ASSERT_EQ(sprites[0]->layerOrder(), 1); ASSERT_EQ(sprites[1]->layerOrder(), 5); @@ -1421,6 +1426,20 @@ TEST(EngineTest, MoveDrawableForwardLayers) ASSERT_EQ(sprites[2]->bubble()->layerOrder(), 8); ASSERT_EQ(sprites[3]->bubble()->layerOrder(), 9); ASSERT_EQ(sprites[4]->bubble()->layerOrder(), 11); + + sprites[1]->bubble()->setText(""); + sprites[3]->bubble()->setText(""); + engine.moveDrawableForwardLayers(sprites[2], 8); + ASSERT_EQ(sprites[0]->layerOrder(), 1); + ASSERT_EQ(sprites[1]->layerOrder(), 4); + ASSERT_EQ(sprites[2]->layerOrder(), 11); + ASSERT_EQ(sprites[3]->layerOrder(), 3); + ASSERT_EQ(sprites[4]->layerOrder(), 2); + ASSERT_EQ(sprites[0]->bubble()->layerOrder(), 9); + ASSERT_EQ(sprites[1]->bubble()->layerOrder(), 6); + ASSERT_EQ(sprites[2]->bubble()->layerOrder(), 7); + ASSERT_EQ(sprites[3]->bubble()->layerOrder(), 8); + ASSERT_EQ(sprites[4]->bubble()->layerOrder(), 10); } TEST(EngineTest, MoveDrawableBackwardLayers) @@ -1429,6 +1448,11 @@ TEST(EngineTest, MoveDrawableBackwardLayers) std::vector sprites; createTargets(&engine, sprites); + const auto &targets = engine.targets(); + + for (auto target : targets) + target->bubble()->setText("test"); + engine.moveDrawableBackwardLayers(sprites[4], -2); ASSERT_EQ(sprites[0]->layerOrder(), 1); ASSERT_EQ(sprites[1]->layerOrder(), 5); @@ -1488,6 +1512,20 @@ TEST(EngineTest, MoveDrawableBackwardLayers) ASSERT_EQ(sprites[2]->bubble()->layerOrder(), 8); ASSERT_EQ(sprites[3]->bubble()->layerOrder(), 9); ASSERT_EQ(sprites[4]->bubble()->layerOrder(), 11); + + sprites[1]->bubble()->setText(""); + sprites[3]->bubble()->setText(""); + engine.moveDrawableBackwardLayers(sprites[2], -8); + ASSERT_EQ(sprites[0]->layerOrder(), 1); + ASSERT_EQ(sprites[1]->layerOrder(), 4); + ASSERT_EQ(sprites[2]->layerOrder(), 11); + ASSERT_EQ(sprites[3]->layerOrder(), 3); + ASSERT_EQ(sprites[4]->layerOrder(), 2); + ASSERT_EQ(sprites[0]->bubble()->layerOrder(), 9); + ASSERT_EQ(sprites[1]->bubble()->layerOrder(), 6); + ASSERT_EQ(sprites[2]->bubble()->layerOrder(), 7); + ASSERT_EQ(sprites[3]->bubble()->layerOrder(), 8); + ASSERT_EQ(sprites[4]->bubble()->layerOrder(), 10); } TEST(EngineTest, MoveDrawableBehindOther) diff --git a/test/extensions/testextension.cpp b/test/extensions/testextension.cpp index d7a8dd5ba..5d122d483 100644 --- a/test/extensions/testextension.cpp +++ b/test/extensions/testextension.cpp @@ -14,6 +14,11 @@ std::string TestExtension::description() const return "Test extension"; } +Rgb TestExtension::color() const +{ + return rgb(0, 0, 0); +} + void TestExtension::registerBlocks(IEngine *engine) { engine->clear(); diff --git a/test/extensions/testextension.h b/test/extensions/testextension.h index 7bad056da..7e89e77d4 100644 --- a/test/extensions/testextension.h +++ b/test/extensions/testextension.h @@ -10,6 +10,7 @@ class TestExtension : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; diff --git a/test/mocks/extensionmock.h b/test/mocks/extensionmock.h index 19f47d6ee..e0750c374 100644 --- a/test/mocks/extensionmock.h +++ b/test/mocks/extensionmock.h @@ -10,6 +10,7 @@ class ExtensionMock : public IExtension public: MOCK_METHOD(std::string, name, (), (const, override)); MOCK_METHOD(std::string, description, (), (const, override)); + MOCK_METHOD(Rgb, color, (), (const, override)); MOCK_METHOD(void, registerBlocks, (IEngine *), (override)); MOCK_METHOD(void, onInit, (IEngine *), (override)); diff --git a/test/scratch_classes/drawable_test.cpp b/test/scratch_classes/drawable_test.cpp index 407af05d1..86c227ef1 100644 --- a/test/scratch_classes/drawable_test.cpp +++ b/test/scratch_classes/drawable_test.cpp @@ -40,7 +40,7 @@ TEST(DrawableTest, LayerOrder) ASSERT_EQ(drawable.layerOrder(), 0); } -TEST(TargetTest, Engine) +TEST(DrawableTest, Engine) { Drawable drawable; ASSERT_EQ(drawable.engine(), nullptr); diff --git a/test/scratch_classes/testextension.cpp b/test/scratch_classes/testextension.cpp index cd848ca6f..9d21e10eb 100644 --- a/test/scratch_classes/testextension.cpp +++ b/test/scratch_classes/testextension.cpp @@ -14,6 +14,11 @@ std::string TestExtension::description() const return ""; } +Rgb TestExtension::color() const +{ + return rgb(0, 0, 0); +} + void TestExtension::registerBlocks(IEngine *engine) { } diff --git a/test/scratch_classes/testextension.h b/test/scratch_classes/testextension.h index 88162cbaf..d15b20945 100644 --- a/test/scratch_classes/testextension.h +++ b/test/scratch_classes/testextension.h @@ -10,6 +10,7 @@ class TestExtension : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; }; diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp index 76462ebed..50eb360b4 100644 --- a/test/scratch_classes/value_test.cpp +++ b/test/scratch_classes/value_test.cpp @@ -1342,6 +1342,9 @@ TEST(ValueTest, ToBool) TEST(ValueTest, ToString) { + std::string oldLocale = std::setlocale(LC_NUMERIC, nullptr); + std::setlocale(LC_NUMERIC, "sk_SK.UTF-8"); + std::vector cStrings; Value v = 2147483647; cStrings.push_back(value_toCString(&v.data())); @@ -1614,6 +1617,8 @@ TEST(ValueTest, ToString) for (char *s : cStrings) free(s); + + std::setlocale(LC_NUMERIC, oldLocale.c_str()); } TEST(ValueTest, ToRgba) @@ -2921,6 +2926,9 @@ TEST(ValueTest, DoubleIsInt) TEST(ValueTest, DoubleToCString) { + std::string oldLocale = std::setlocale(LC_NUMERIC, nullptr); + std::setlocale(LC_NUMERIC, "sk_SK.UTF-8"); + char *ret; ret = value_doubleToCString(0.0); ASSERT_EQ(strcmp(ret, "0"), 0); @@ -3021,6 +3029,8 @@ TEST(ValueTest, DoubleToCString) ret = value_doubleToCString(std::numeric_limits::quiet_NaN()); ASSERT_EQ(strcmp(ret, "NaN"), 0); free(ret); + + std::setlocale(LC_NUMERIC, oldLocale.c_str()); } TEST(ValueTest, BoolToCString) diff --git a/test/scratchconfiguration/extension1.cpp b/test/scratchconfiguration/extension1.cpp index 2e8fa183b..b49f2b0e7 100644 --- a/test/scratchconfiguration/extension1.cpp +++ b/test/scratchconfiguration/extension1.cpp @@ -6,3 +6,8 @@ std::string Extension1::name() const { return "ext 1"; } + +Rgb Extension1::color() const +{ + return rgb(0, 0, 0); +} diff --git a/test/scratchconfiguration/extension1.h b/test/scratchconfiguration/extension1.h index f7f8009e6..ea08ea875 100644 --- a/test/scratchconfiguration/extension1.h +++ b/test/scratchconfiguration/extension1.h @@ -9,6 +9,7 @@ class Extension1 : public ExtensionBase { public: std::string name() const override; + Rgb color() const override; }; } // namespace libscratchcpp diff --git a/test/scratchconfiguration/extension2.cpp b/test/scratchconfiguration/extension2.cpp index 5c58c11ed..5efe05614 100644 --- a/test/scratchconfiguration/extension2.cpp +++ b/test/scratchconfiguration/extension2.cpp @@ -6,3 +6,8 @@ std::string Extension2::name() const { return "ext 2"; } + +Rgb Extension2::color() const +{ + return rgb(0, 0, 0); +} diff --git a/test/scratchconfiguration/extension2.h b/test/scratchconfiguration/extension2.h index 22caf7f1f..a6e03cd81 100644 --- a/test/scratchconfiguration/extension2.h +++ b/test/scratchconfiguration/extension2.h @@ -9,6 +9,7 @@ class Extension2 : public ExtensionBase { public: std::string name() const override; + Rgb color() const override; }; } // namespace libscratchcpp diff --git a/test/scratchconfiguration/extension3.cpp b/test/scratchconfiguration/extension3.cpp index fb28af9b1..021524f1f 100644 --- a/test/scratchconfiguration/extension3.cpp +++ b/test/scratchconfiguration/extension3.cpp @@ -6,3 +6,8 @@ std::string Extension3::name() const { return "ext 3"; } + +Rgb Extension3::color() const +{ + return rgb(0, 0, 0); +} diff --git a/test/scratchconfiguration/extension3.h b/test/scratchconfiguration/extension3.h index d434d7541..5ebe8c4de 100644 --- a/test/scratchconfiguration/extension3.h +++ b/test/scratchconfiguration/extension3.h @@ -9,6 +9,7 @@ class Extension3 : public ExtensionBase { public: std::string name() const override; + Rgb color() const override; }; } // namespace libscratchcpp diff --git a/test/stop_all_bypass.sb3 b/test/stop_all_bypass.sb3 index b2c826897..6d42e0d36 100644 Binary files a/test/stop_all_bypass.sb3 and b/test/stop_all_bypass.sb3 differ