Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
afacb3e
Drop LLVMTypeAnalyzer
adazem009 Aug 12, 2025
1ed91fc
Add support for Compiler::StaticType bitmasking
adazem009 Aug 13, 2025
b2aeaa0
LLVMInstruction: Add writeTargetType field
adazem009 Aug 13, 2025
b497e77
Read variable/list type from LLVMInstruction
adazem009 Aug 13, 2025
f0470f8
LLVMInstruction: Rename workVariable/workList fields
adazem009 Aug 13, 2025
d46d9e0
LLVMBuildUtils: Add const to optimizeRegisterType() parameter
adazem009 Aug 14, 2025
31593f3
Add new variable type analysis algorithm
adazem009 Aug 14, 2025
507ce25
LLVMCodeAnalyzer: Move common test cases to mixed tests
adazem009 Aug 14, 2025
0a14d89
LLVMCodeAnalyzer: Implement list type analysis
adazem009 Aug 15, 2025
1d59bbe
LLVMCodeAnalyzer: Add test cases for mixed type analysis
adazem009 Aug 15, 2025
8db2ec3
Add support for mixed types in function return values
adazem009 Aug 15, 2025
31dbd17
Implement casting from mixed type values
adazem009 Aug 15, 2025
a06b40b
Support mixed types in select instruction
adazem009 Aug 15, 2025
f10c765
Add support for casting of constants to mixed type values
adazem009 Aug 15, 2025
b568942
Add support for mixed types in comparison
adazem009 Aug 15, 2025
d2b38f2
Add support for mixed types in procedure arguments
adazem009 Aug 15, 2025
81b1c57
Remove type check from get list item null register
adazem009 Aug 15, 2025
7976cc0
Enable code analysis
adazem009 Aug 15, 2025
87a025a
LLVMBuildUtils: Use type-specific functions for string casting
adazem009 Aug 15, 2025
2e1a537
LLVMBuildUtils: Use runtime switch statement for single type casting
adazem009 Aug 16, 2025
f29970a
LLVMBuildUtils: Simplify castValue()
adazem009 Aug 16, 2025
c1fe63b
Swap source and target type parameters in create store methods
adazem009 Aug 16, 2025
586529b
LLVMBuildUtils: Rename parameters in create store methods
adazem009 Aug 16, 2025
a51bfad
LLVMConstantRegister: Mark as raw by default
adazem009 Aug 16, 2025
e471eb5
Implement mixed type stores
adazem009 Aug 16, 2025
59bce80
Optimize empty lists
adazem009 Aug 16, 2025
f2de9fb
Fix out of range return value of get list item instruction
adazem009 Aug 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
option(LIBSCRATCHCPP_BUILD_UNIT_TESTS "Build unit tests" ON)
option(LIBSCRATCHCPP_NETWORK_SUPPORT "Support for downloading projects" ON)
option(LIBSCRATCHCPP_PRINT_LLVM_IR "Print LLVM IR of compiled Scratch scripts (for debugging)" OFF)
option(LIBSCRATCHCPP_ENABLE_CODE_ANALYZER "Analyze Scratch scripts to enable various optimizations" ON)
option(LIBSCRATCHCPP_ENABLE_SANITIZER "Enable sanitizer to detect memory issues" OFF)

if (LIBSCRATCHCPP_ENABLE_SANITIZER)
Expand All @@ -26,6 +27,7 @@ install(TARGETS scratchcpp DESTINATION lib)
target_sources(scratchcpp
PUBLIC
include/scratchcpp/global.h
include/scratchcpp/enum_bitmask.h
include/scratchcpp/project.h
include/scratchcpp/scratchconfiguration.h
include/scratchcpp/iengine.h
Expand Down Expand Up @@ -120,6 +122,10 @@ if(LIBSCRATCHCPP_PRINT_LLVM_IR)
target_compile_definitions(scratchcpp PRIVATE PRINT_LLVM_IR)
endif()

if(LIBSCRATCHCPP_ENABLE_CODE_ANALYZER)
target_compile_definitions(scratchcpp PRIVATE ENABLE_CODE_ANALYZER)
endif()

# Macros
target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_LIBRARY)
target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION="${PROJECT_VERSION}")
Expand Down
15 changes: 9 additions & 6 deletions include/scratchcpp/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <vector>

#include "global.h"
#include "enum_bitmask.h"
#include "spimpl.h"

namespace libscratchcpp
Expand All @@ -31,12 +32,12 @@ class LIBSCRATCHCPP_EXPORT Compiler
public:
enum class StaticType
{
Void,
Number,
Bool,
String,
Pointer,
Unknown
Void = 0,
Number = 1 << 0,
Bool = 1 << 1,
String = 1 << 2,
Pointer = 1 << 3,
Unknown = Number | Bool | String
};

enum class CodeType
Expand Down Expand Up @@ -161,4 +162,6 @@ class LIBSCRATCHCPP_EXPORT Compiler
spimpl::unique_impl_ptr<CompilerPrivate> impl;
};

constexpr bool enable_enum_bitmask(Compiler::StaticType);

} // namespace libscratchcpp
53 changes: 53 additions & 0 deletions include/scratchcpp/enum_bitmask.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <type_traits>

namespace libscratchcpp
{

// https://andreasfertig.com/blog/2024/01/cpp20-concepts-applied
// OR operator |
template<typename T>
constexpr std::enable_if_t<std::conjunction_v<std::is_enum<T>, std::is_same<bool, decltype(enable_enum_bitmask(std::declval<T>()))>>, T> operator|(const T lhs, const T rhs)
{
using underlying = std::underlying_type_t<T>;
return static_cast<T>(static_cast<underlying>(lhs) | static_cast<underlying>(rhs));
}

// OR assignment operator |=
template<typename T>
constexpr std::enable_if_t<std::conjunction_v<std::is_enum<T>, std::is_same<bool, decltype(enable_enum_bitmask(std::declval<T>()))>>, T &> operator|=(T &lhs, const T rhs)
{
using underlying = std::underlying_type_t<T>;
lhs = static_cast<T>(static_cast<underlying>(lhs) | static_cast<underlying>(rhs));
return lhs;
}

// AND operator &
template<typename T>
constexpr std::enable_if_t<std::conjunction_v<std::is_enum<T>, std::is_same<bool, decltype(enable_enum_bitmask(std::declval<T>()))>>, T> operator&(const T lhs, const T rhs)
{
using underlying = std::underlying_type_t<T>;
return static_cast<T>(static_cast<underlying>(lhs) & static_cast<underlying>(rhs));
}

// AND assignment operator &=
template<typename T>
constexpr std::enable_if_t<std::conjunction_v<std::is_enum<T>, std::is_same<bool, decltype(enable_enum_bitmask(std::declval<T>()))>>, T &> operator&=(T &lhs, const T rhs)
{
using underlying = std::underlying_type_t<T>;
lhs = static_cast<T>(static_cast<underlying>(lhs) & static_cast<underlying>(rhs));
return lhs;
}

// NOT operator ~
template<typename T>
constexpr std::enable_if_t<std::conjunction_v<std::is_enum<T>, std::is_same<bool, decltype(enable_enum_bitmask(std::declval<T>()))>>, T> operator~(const T value)
{
using underlying = std::underlying_type_t<T>;
return static_cast<T>(~static_cast<underlying>(value));
}

} // namespace libscratchcpp
4 changes: 2 additions & 2 deletions src/engine/internal/llvm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ target_sources(scratchcpp
PRIVATE
llvmbuildutils.cpp
llvmbuildutils.h
llvmcodeanalyzer.cpp
llvmcodeanalyzer.h
llvmcodebuilder.cpp
llvmcodebuilder.h
llvmregister.h
Expand All @@ -19,8 +21,6 @@ target_sources(scratchcpp
llvmtypes.h
llvmfunctions.cpp
llvmfunctions.h
llvmtypeanalyzer.cpp
llvmtypeanalyzer.h
llvmcompilercontext.cpp
llvmcompilercontext.h
llvmexecutablecode.cpp
Expand Down
8 changes: 4 additions & 4 deletions src/engine/internal/llvm/instructions/control.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ LLVMInstruction *Control::buildSelect(LLVMInstruction *ins)
llvm::Value *trueValue;
llvm::Value *falseValue;

if (type == Compiler::StaticType::Unknown) {
trueValue = m_utils.createValue(arg2.second);
falseValue = m_utils.createValue(arg3.second);
} else {
if (m_utils.isSingleType(type)) {
trueValue = m_utils.castValue(arg2.second, type);
falseValue = m_utils.castValue(arg3.second, type);
} else {
trueValue = m_utils.createValue(arg2.second);
falseValue = m_utils.createValue(arg3.second);
}

ins->functionReturnReg->value = m_builder.CreateSelect(cond, trueValue, falseValue);
Expand Down
7 changes: 4 additions & 3 deletions src/engine/internal/llvm/instructions/functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,12 @@ LLVMInstruction *Functions::buildFunctionCall(LLVMInstruction *ins)
llvm::Value *ret = m_builder.CreateCall(m_utils.functions().resolveFunction(ins->functionName, llvm::FunctionType::get(retType, types, false)), args);

if (ins->functionReturnReg) {
if (ins->functionReturnReg->type() == Compiler::StaticType::Unknown) {
if (m_utils.isSingleType(ins->functionReturnReg->type()))
ins->functionReturnReg->value = ret;
else {
ins->functionReturnReg->value = m_utils.addAlloca(retType);
m_builder.CreateStore(ret, ins->functionReturnReg->value);
} else
ins->functionReturnReg->value = ret;
}

if (ins->functionReturnReg->type() == Compiler::StaticType::String)
m_utils.freeStringLater(ins->functionReturnReg->value);
Expand Down
84 changes: 40 additions & 44 deletions src/engine/internal/llvm/instructions/lists.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,28 @@ ProcessResult Lists::process(LLVMInstruction *ins)

LLVMInstruction *Lists::buildClearList(LLVMInstruction *ins)
{
assert(ins->args.size() == 0);
LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);
m_builder.CreateCall(m_utils.functions().resolve_list_clear(), listPtr.ptr);
if (ins->targetType != Compiler::StaticType::Void) { // do not clear a list that is already empty
assert(ins->args.size() == 0);
LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);
m_builder.CreateCall(m_utils.functions().resolve_list_clear(), listPtr.ptr);
}

return ins->next;
}

LLVMInstruction *Lists::buildRemoveListItem(LLVMInstruction *ins)
{
// No-op in empty lists
if (ins->targetType == Compiler::StaticType::Void)
return ins->next;

llvm::LLVMContext &llvmCtx = m_utils.llvmCtx();
llvm::Function *function = m_utils.function();

assert(ins->args.size() == 1);
const auto &arg = ins->args[0];
LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);

LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);

// Range check
llvm::Value *min = llvm::ConstantFP::get(llvmCtx, llvm::APFloat(0.0));
Expand Down Expand Up @@ -107,12 +114,7 @@ LLVMInstruction *Lists::buildAppendToList(LLVMInstruction *ins)
assert(ins->args.size() == 1);
const auto &arg = ins->args[0];
Compiler::StaticType type = m_utils.optimizeRegisterType(arg.second);
LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);

Compiler::StaticType listType = Compiler::StaticType::Unknown;

if (m_utils.warp())
listType = m_utils.typeAnalyzer().listType(ins->workList, ins, Compiler::StaticType::Unknown, false);
LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);

// Check if enough space is allocated
llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
Expand All @@ -126,14 +128,15 @@ LLVMInstruction *Lists::buildAppendToList(LLVMInstruction *ins)
// If there's enough space, use the allocated memory
m_builder.SetInsertPoint(ifBlock);
llvm::Value *itemPtr = m_utils.getListItem(listPtr, size);
m_utils.createReusedValueStore(arg.second, itemPtr, type, listType);
m_utils.createValueStore(arg.second, itemPtr, type);
m_builder.CreateStore(m_builder.CreateAdd(size, m_builder.getInt64(1)), listPtr.sizePtr);
m_builder.CreateBr(nextBlock);

// Otherwise call appendEmpty()
m_builder.SetInsertPoint(elseBlock);
itemPtr = m_builder.CreateCall(m_utils.functions().resolve_list_append_empty(), listPtr.ptr);
m_utils.createReusedValueStore(arg.second, itemPtr, type, listType);
// NOTE: Items created using appendEmpty() are always numbers
m_utils.createValueStore(arg.second, itemPtr, Compiler::StaticType::Number, type);
m_builder.CreateBr(nextBlock);

m_builder.SetInsertPoint(nextBlock);
Expand All @@ -149,12 +152,7 @@ LLVMInstruction *Lists::buildInsertToList(LLVMInstruction *ins)
const auto &indexArg = ins->args[0];
const auto &valueArg = ins->args[1];
Compiler::StaticType type = m_utils.optimizeRegisterType(valueArg.second);
LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);

Compiler::StaticType listType = Compiler::StaticType::Unknown;

if (m_utils.warp())
listType = m_utils.typeAnalyzer().listType(ins->workList, ins, Compiler::StaticType::Unknown, false);
LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);

// Range check
llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
Expand All @@ -170,7 +168,7 @@ LLVMInstruction *Lists::buildInsertToList(LLVMInstruction *ins)
m_builder.SetInsertPoint(insertBlock);
index = m_builder.CreateFPToUI(index, m_builder.getInt64Ty());
llvm::Value *itemPtr = m_builder.CreateCall(m_utils.functions().resolve_list_insert_empty(), { listPtr.ptr, index });
m_utils.createReusedValueStore(valueArg.second, itemPtr, type, listType);
m_utils.createValueStore(valueArg.second, itemPtr, type);

m_builder.CreateBr(nextBlock);

Expand All @@ -180,19 +178,20 @@ LLVMInstruction *Lists::buildInsertToList(LLVMInstruction *ins)

LLVMInstruction *Lists::buildListReplace(LLVMInstruction *ins)
{
// No-op in empty lists
if (ins->targetType == Compiler::StaticType::Void)
return ins->next;

llvm::LLVMContext &llvmCtx = m_utils.llvmCtx();
llvm::Function *function = m_utils.function();

assert(ins->args.size() == 2);
const auto &indexArg = ins->args[0];
const auto &valueArg = ins->args[1];
Compiler::StaticType type = m_utils.optimizeRegisterType(valueArg.second);
LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);

Compiler::StaticType listType = Compiler::StaticType::Unknown;
LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);

if (m_utils.warp())
listType = m_utils.typeAnalyzer().listType(ins->workList, ins, Compiler::StaticType::Unknown, false);
Compiler::StaticType listType = ins->targetType;

// Range check
llvm::Value *min = llvm::ConstantFP::get(llvmCtx, llvm::APFloat(0.0));
Expand All @@ -208,7 +207,7 @@ LLVMInstruction *Lists::buildListReplace(LLVMInstruction *ins)
m_builder.SetInsertPoint(replaceBlock);
index = m_builder.CreateFPToUI(index, m_builder.getInt64Ty());
llvm::Value *itemPtr = m_utils.getListItem(listPtr, index);
m_utils.createValueStore(valueArg.second, itemPtr, type, listType);
m_utils.createValueStore(valueArg.second, itemPtr, listType, type);
m_builder.CreateBr(nextBlock);

m_builder.SetInsertPoint(nextBlock);
Expand All @@ -218,7 +217,7 @@ LLVMInstruction *Lists::buildListReplace(LLVMInstruction *ins)
LLVMInstruction *Lists::buildGetListContents(LLVMInstruction *ins)
{
assert(ins->args.size() == 0);
const LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);
const LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);
llvm::Value *ptr = m_builder.CreateCall(m_utils.functions().resolve_list_to_string(), listPtr.ptr);
m_utils.freeStringLater(ptr);
ins->functionReturnReg->value = ptr;
Expand All @@ -228,35 +227,38 @@ LLVMInstruction *Lists::buildGetListContents(LLVMInstruction *ins)

LLVMInstruction *Lists::buildGetListItem(LLVMInstruction *ins)
{
// Return empty string for empty lists
if (ins->targetType == Compiler::StaticType::Void) {
LLVMConstantRegister nullReg(Compiler::StaticType::String, "");
ins->functionReturnReg->value = m_utils.createValue(static_cast<LLVMRegister *>(&nullReg));
return ins->next;
}

assert(ins->args.size() == 1);
const auto &arg = ins->args[0];
LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);
LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);

Compiler::StaticType listType = Compiler::StaticType::Unknown;

if (m_utils.warp())
listType = m_utils.typeAnalyzer().listType(ins->workList, ins, Compiler::StaticType::Unknown, false);
Compiler::StaticType listType = ins->functionReturnReg->type();

llvm::Value *min = llvm::ConstantFP::get(m_utils.llvmCtx(), llvm::APFloat(0.0));
llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
size = m_builder.CreateUIToFP(size, m_builder.getDoubleTy());
llvm::Value *index = m_utils.castValue(arg.second, arg.first);
llvm::Value *inRange = m_builder.CreateAnd(m_builder.CreateFCmpOGE(index, min), m_builder.CreateFCmpOLT(index, size));

LLVMConstantRegister nullReg(listType == Compiler::StaticType::Unknown ? Compiler::StaticType::Number : listType, Value());
LLVMConstantRegister nullReg(Compiler::StaticType::String, "");
llvm::Value *null = m_utils.createValue(static_cast<LLVMRegister *>(&nullReg));

index = m_builder.CreateFPToUI(index, m_builder.getInt64Ty());
ins->functionReturnReg->value = m_builder.CreateSelect(inRange, m_utils.getListItem(listPtr, index), null);
ins->functionReturnReg->setType(listType);

return ins->next;
}

LLVMInstruction *Lists::buildGetListSize(LLVMInstruction *ins)
{
assert(ins->args.size() == 0);
const LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);
const LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);
llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
ins->functionReturnReg->value = m_builder.CreateUIToFP(size, m_builder.getDoubleTy());

Expand All @@ -267,12 +269,9 @@ LLVMInstruction *Lists::buildGetListItemIndex(LLVMInstruction *ins)
{
assert(ins->args.size() == 1);
const auto &arg = ins->args[0];
LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);

Compiler::StaticType listType = Compiler::StaticType::Unknown;
LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);

if (m_utils.warp())
listType = m_utils.typeAnalyzer().listType(ins->workList, ins, Compiler::StaticType::Unknown, false);
Compiler::StaticType listType = ins->targetType;

ins->functionReturnReg->value = m_builder.CreateSIToFP(m_utils.getListItemIndex(listPtr, listType, arg.second), m_builder.getDoubleTy());
return ins->next;
Expand All @@ -282,12 +281,9 @@ LLVMInstruction *Lists::buildListContainsItem(LLVMInstruction *ins)
{
assert(ins->args.size() == 1);
const auto &arg = ins->args[0];
LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);

Compiler::StaticType listType = Compiler::StaticType::Unknown;
LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);

if (m_utils.warp())
listType = m_utils.typeAnalyzer().listType(ins->workList, ins, Compiler::StaticType::Unknown, false);
Compiler::StaticType listType = ins->targetType;

llvm::Value *index = m_utils.getListItemIndex(listPtr, listType, arg.second);
ins->functionReturnReg->value = m_builder.CreateICmpSGT(index, llvm::ConstantInt::get(m_builder.getInt64Ty(), -1, true));
Expand Down
6 changes: 3 additions & 3 deletions src/engine/internal/llvm/instructions/procedures.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ LLVMInstruction *Procedures::buildCallProcedure(LLVMInstruction *ins)

// Add procedure args
for (const auto &arg : ins->args) {
if (arg.first == Compiler::StaticType::Unknown)
args.push_back(m_utils.createValue(arg.second));
else
if (m_utils.isSingleType(arg.first))
args.push_back(m_utils.castValue(arg.second, arg.first));
else
args.push_back(m_utils.createValue(arg.second));
}

llvm::Value *handle = m_builder.CreateCall(m_utils.functions().resolveFunction(name, type), args);
Expand Down
Loading