diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h index f5b2c7fb..b42f976d 100644 --- a/include/scratchcpp/dev/compiler.h +++ b/include/scratchcpp/dev/compiler.h @@ -46,6 +46,10 @@ class LIBSCRATCHCPP_EXPORT Compiler void addConstValue(const Value &value); void addVariableValue(Variable *variable); void addListContents(List *list); + void addListItem(List *list); + void addListItemIndex(List *list); + void addListContains(List *list); + void addListSize(List *list); void addInput(const std::string &name); void createAdd(); @@ -80,6 +84,16 @@ class LIBSCRATCHCPP_EXPORT Compiler void createVariableWrite(Variable *variable); + void createListClear(List *list); + void createListRemove(List *list); + void createListAppend(List *list); + void createListInsert(List *list); + void createListReplace(List *list); + + void beginIfStatement(); + void beginElseBranch(); + void endIf(); + void moveToIf(std::shared_ptr substack); void moveToIfElse(std::shared_ptr substack1, std::shared_ptr substack2); void moveToRepeatLoop(std::shared_ptr substack); diff --git a/include/scratchcpp/list.h b/include/scratchcpp/list.h index 8ce30c85..ad85b3a3 100644 --- a/include/scratchcpp/list.h +++ b/include/scratchcpp/list.h @@ -37,6 +37,21 @@ class LIBSCRATCHCPP_EXPORT List : public Entity Monitor *monitor() const; void setMonitor(Monitor *monitor); + /*! Returns a pointer to the raw list data. */ + inline ValueData *data() const { return m_dataPtr->data(); } + + /*! + * Returns a pointer to the list size. + * \note This is used internally by compiled code for various optimizations. + */ + inline size_t *sizePtr() { return &m_size; } + + /*! + * Returns a pointer to the allocated list size. + * \note This is used internally by compiled code for various optimizations. + */ + inline const size_t *allocatedSizePtr() const { return m_dataPtr->sizePtr(); } + /*! Returns the list size. */ inline size_t size() const { return m_size; } @@ -96,16 +111,19 @@ class LIBSCRATCHCPP_EXPORT List : public Entity m_size--; } - /*! Inserts an item at index. */ - inline void insert(size_t index, const ValueData &value) + /*! Inserts an empty item at index and returns the reference to it. Can be used for custom initialization. */ + inline ValueData &insertEmpty(size_t index) { assert(index >= 0 && index <= size()); m_size++; reserve(getAllocSize(m_size)); std::rotate(m_dataPtr->rbegin() + m_dataPtr->size() - m_size, m_dataPtr->rbegin() + m_dataPtr->size() - m_size + 1, m_dataPtr->rend() - index); - value_assign_copy(&m_dataPtr->operator[](index), &value); + return m_dataPtr->operator[](index); } + /*! Inserts an item at index. */ + inline void insert(size_t index, const ValueData &value) { value_assign_copy(&insertEmpty(index), &value); } + /*! Inserts an item at index. */ inline void insert(size_t index, const Value &value) { insert(index, value.data()); } diff --git a/include/scratchcpp/target.h b/include/scratchcpp/target.h index b9d89eb5..20d1a389 100644 --- a/include/scratchcpp/target.h +++ b/include/scratchcpp/target.h @@ -53,6 +53,8 @@ class LIBSCRATCHCPP_EXPORT Target : public Drawable int findList(const std::string &listName) const; int findListById(const std::string &id) const; + List **listData(); + const std::vector> &blocks() const; int addBlock(std::shared_ptr block); std::shared_ptr blockAt(int index) const; diff --git a/include/scratchcpp/veque.h b/include/scratchcpp/veque.h index f75877c7..a994ebc6 100644 --- a/include/scratchcpp/veque.h +++ b/include/scratchcpp/veque.h @@ -375,6 +375,11 @@ namespace veque return _size; } + // For libscratchcpp List + inline const size_type *sizePtr() const noexcept { + return &_size; + } + size_type max_size() const noexcept { constexpr auto compile_time_limit = std::min( diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp index 1dde27ab..c8f134fa 100644 --- a/src/dev/engine/compiler.cpp +++ b/src/dev/engine/compiler.cpp @@ -45,9 +45,15 @@ std::shared_ptr Compiler::compile(std::shared_ptr startBl impl->block = startBlock; while (impl->block) { - if (impl->block->compileFunction()) + if (impl->block->compileFunction()) { + assert(impl->customIfStatementCount == 0); impl->block->compile(this); - else { + + if (impl->customIfStatementCount > 0) { + std::cerr << "error: if statement created by block '" << impl->block->opcode() << "' not terminated" << std::endl; + assert(false); + } + } else { std::cout << "warning: unsupported block: " << impl->block->opcode() << std::endl; impl->unsupportedBlocks.insert(impl->block->opcode()); } @@ -94,6 +100,30 @@ void Compiler::addListContents(List *list) impl->builder->addListContents(list); } +/*! Adds the item with index from the last value of the given list to the code. */ +void Compiler::addListItem(List *list) +{ + impl->builder->addListItem(list); +} + +/*! Adds the index of the item from the last value of the given list to the code. */ +void Compiler::addListItemIndex(List *list) +{ + impl->builder->addListItemIndex(list); +} + +/*! Adds the result of a list contains item from the check to the code. */ +void Compiler::addListContains(List *list) +{ + impl->builder->addListContains(list); +} + +/*! Adds the length of the given list to the code. */ +void Compiler::addListSize(List *list) +{ + impl->builder->addListSize(list); +} + /*! Compiles the given input (resolved by name) and adds it to the compiled code. */ void Compiler::addInput(const std::string &name) { @@ -262,6 +292,68 @@ void Compiler::createVariableWrite(Variable *variable) impl->builder->createVariableWrite(variable); } +/*! Creates a clear list operation. */ +void Compiler::createListClear(List *list) +{ + impl->builder->createListClear(list); +} + +/*! + * Creates a remove item from list operation. + * \note The index starts with 0 and is cast to number, special strings like "last" are not handled. + */ +void Compiler::createListRemove(List *list) +{ + impl->builder->createListRemove(list); +} + +/*! Creates a list append operation using the last value. */ +void Compiler::createListAppend(List *list) +{ + impl->builder->createListAppend(list); +} + +/*! Creates a list insert operation using the last 2 values (index, value). */ +void Compiler::createListInsert(List *list) +{ + impl->builder->createListInsert(list); +} + +/*! Creates a list replace operation using the last 2 values (index, value). */ +void Compiler::createListReplace(List *list) +{ + impl->builder->createListReplace(list); +} + +/*! + * Starts a custom if statement. + * \note The if statement must be terminated using endIf() after compiling your block. + */ +void Compiler::beginIfStatement() +{ + impl->builder->beginIfStatement(); + impl->customIfStatementCount++; +} + +/*! Starts the else branch of custom if statement. */ +void Compiler::beginElseBranch() +{ + impl->builder->beginElseBranch(); +} + +/*! Ends custom if statement. */ +void Compiler::endIf() +{ + if (impl->customIfStatementCount == 0) { + std::cerr << "error: called Compiler::endIf() without an if statement"; + assert(false); + return; + } + + impl->builder->endIf(); + impl->customIfStatementCount--; +} + /*! Jumps to the given if substack. */ void Compiler::moveToIf(std::shared_ptr substack) { diff --git a/src/dev/engine/compiler_p.h b/src/dev/engine/compiler_p.h index 059d143b..0a23d71d 100644 --- a/src/dev/engine/compiler_p.h +++ b/src/dev/engine/compiler_p.h @@ -31,6 +31,7 @@ struct CompilerPrivate Target *target = nullptr; std::shared_ptr block; + int customIfStatementCount = 0; std::vector, std::shared_ptr>, SubstackType>> substackTree; bool substackHit = false; bool warp = false; diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h index 8ca7fbea..30d60cfe 100644 --- a/src/dev/engine/internal/icodebuilder.h +++ b/src/dev/engine/internal/icodebuilder.h @@ -23,6 +23,10 @@ class ICodeBuilder virtual void addConstValue(const Value &value) = 0; virtual void addVariableValue(Variable *variable) = 0; virtual void addListContents(List *list) = 0; + virtual void addListItem(List *list) = 0; + virtual void addListItemIndex(List *list) = 0; + virtual void addListContains(List *list) = 0; + virtual void addListSize(List *list) = 0; virtual void createAdd() = 0; virtual void createSub() = 0; @@ -56,6 +60,12 @@ class ICodeBuilder virtual void createVariableWrite(Variable *variable) = 0; + virtual void createListClear(List *list) = 0; + virtual void createListRemove(List *list) = 0; + virtual void createListAppend(List *list) = 0; + virtual void createListInsert(List *list) = 0; + virtual void createListReplace(List *list) = 0; + virtual void beginIfStatement() = 0; virtual void beginElseBranch() = 0; virtual void endIf() = 0; diff --git a/src/dev/engine/internal/llvm/CMakeLists.txt b/src/dev/engine/internal/llvm/CMakeLists.txt index a7833f5b..1a4ce89a 100644 --- a/src/dev/engine/internal/llvm/CMakeLists.txt +++ b/src/dev/engine/internal/llvm/CMakeLists.txt @@ -9,6 +9,7 @@ target_sources(scratchcpp llvmcoroutine.cpp llvmcoroutine.h llvmvariableptr.h + llvmlistptr.h llvmprocedure.h llvmtypes.cpp llvmtypes.h diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index 5a304a71..cd791169 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "llvmcodebuilder.h" #include "llvmexecutablecode.h" @@ -36,6 +37,7 @@ LLVMCodeBuilder::LLVMCodeBuilder(Target *target, const std::string &id, bool war m_regs.push_back({}); initTypes(); createVariableMap(); + createListMap(); } std::shared_ptr LLVMCodeBuilder::finalize() @@ -56,12 +58,13 @@ std::shared_ptr LLVMCodeBuilder::finalize() m_builder.setFastMathFlags(fmf); // Create function - // void *f(Target *, ValueData **) + // void *f(Target *, ValueData **, List **) llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); - llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, { pointerType, pointerType }, false); + llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, { pointerType, pointerType, pointerType }, false); llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "f", m_module.get()); llvm::Value *targetPtr = func->getArg(0); llvm::Value *targetVariables = func->getArg(1); + llvm::Value *targetLists = func->getArg(2); llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_ctx, "entry", func); m_builder.SetInsertPoint(entry); @@ -92,6 +95,20 @@ std::shared_ptr LLVMCodeBuilder::finalize() m_scopeVariables.clear(); m_scopeVariables.push_back({}); + // Create list pointers + for (auto &[list, listPtr] : m_listPtrs) { + listPtr.ptr = getListPtr(targetLists, list); + + listPtr.dataPtr = m_builder.CreateAlloca(m_valueDataType->getPointerTo()); + m_builder.CreateStore(m_builder.CreateCall(resolve_list_data(), listPtr.ptr), listPtr.dataPtr); + + listPtr.sizePtr = m_builder.CreateCall(resolve_list_size_ptr(), listPtr.ptr); + listPtr.allocatedSizePtr = m_builder.CreateCall(resolve_list_alloc_size_ptr(), listPtr.ptr); + + listPtr.dataPtrDirty = m_builder.CreateAlloca(m_builder.getInt1Ty()); + m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty); + } + // Execute recorded steps for (const LLVMInstruction &step : m_instructions) { switch (step.type) { @@ -470,10 +487,131 @@ std::shared_ptr LLVMCodeBuilder::finalize() break; } + case LLVMInstruction::Type::ClearList: { + assert(step.args.size() == 0); + const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + m_builder.CreateCall(resolve_list_clear(), listPtr.ptr); + // NOTE: Clearing doesn't deallocate (see List::clear()), so there's no need to update the data pointer + break; + } + + case LLVMInstruction::Type::RemoveListItem: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + llvm::Value *index = m_builder.CreateFPToUI(castValue(arg.second, arg.first), m_builder.getInt64Ty()); + m_builder.CreateCall(resolve_list_remove(), { listPtr.ptr, index }); + // NOTE: Removing doesn't deallocate (see List::removeAt()), so there's no need to update the data pointer + break; + } + + case LLVMInstruction::Type::AppendToList: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + Compiler::StaticType type = optimizeRegisterType(arg.second); + const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + // 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); + llvm::Value *isAllocated = m_builder.CreateICmpUGT(allocatedSize, size); + llvm::BasicBlock *ifBlock = llvm::BasicBlock::Create(m_ctx, "", func); + llvm::BasicBlock *elseBlock = llvm::BasicBlock::Create(m_ctx, "", func); + llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_ctx, "", func); + m_builder.CreateCondBr(isAllocated, ifBlock, elseBlock); + + // If there's enough space, use the allocated memory + m_builder.SetInsertPoint(ifBlock); + llvm::Value *itemPtr = getListItem(listPtr, size, func); + createInitialValueStore(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(resolve_list_append_empty(), listPtr.ptr); + createInitialValueStore(arg.second, itemPtr, type); + m_builder.CreateStore(m_builder.getInt1(true), listPtr.dataPtrDirty); + m_builder.CreateBr(nextBlock); + + m_builder.SetInsertPoint(nextBlock); + // TODO: Implement list type prediction + break; + } + + case LLVMInstruction::Type::InsertToList: { + assert(step.args.size() == 2); + const auto &indexArg = step.args[0]; + const auto &valueArg = step.args[1]; + Compiler::StaticType type = optimizeRegisterType(valueArg.second); + const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + // dataPtrDirty + llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr); + llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr); + m_builder.CreateStore(m_builder.CreateICmpEQ(allocatedSize, size), listPtr.dataPtrDirty); + + // Insert + llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty()); + llvm::Value *itemPtr = m_builder.CreateCall(resolve_list_insert_empty(), { listPtr.ptr, index }); + createInitialValueStore(valueArg.second, itemPtr, type); + // TODO: Implement list type prediction + break; + } + + case LLVMInstruction::Type::ListReplace: { + assert(step.args.size() == 2); + const auto &indexArg = step.args[0]; + const auto &valueArg = step.args[1]; + Compiler::StaticType type = optimizeRegisterType(valueArg.second); + const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty()); + llvm::Value *itemPtr = getListItem(listPtr, index, func); + createValueStore(valueArg.second, itemPtr, type, listPtr.type); + // TODO: Implement list type prediction + break; + } + + case LLVMInstruction::Type::GetListItem: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + llvm::Value *index = m_builder.CreateFPToUI(castValue(arg.second, arg.first), m_builder.getInt64Ty()); + step.functionReturnReg->value = getListItem(listPtr, index, func); + step.functionReturnReg->type = listPtr.type; + break; + } + + case LLVMInstruction::Type::GetListSize: { + assert(step.args.size() == 0); + const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr); + step.functionReturnReg->value = m_builder.CreateUIToFP(size, m_builder.getDoubleTy()); + break; + } + + case LLVMInstruction::Type::GetListItemIndex: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + step.functionReturnReg->value = m_builder.CreateSIToFP(getListItemIndex(listPtr, arg.second, func), m_builder.getDoubleTy()); + break; + } + + case LLVMInstruction::Type::ListContainsItem: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + llvm::Value *index = getListItemIndex(listPtr, arg.second, func); + step.functionReturnReg->value = m_builder.CreateICmpSGT(index, llvm::ConstantInt::get(m_builder.getInt64Ty(), -1, true)); + break; + } + case LLVMInstruction::Type::Yield: if (!m_warp) { freeHeap(); syncVariables(targetVariables); + reloadLists(); coro->createSuspend(); reloadVariables(targetVariables); } @@ -788,6 +926,47 @@ void LLVMCodeBuilder::addListContents(List *list) { } +void LLVMCodeBuilder::addListItem(List *list) +{ + LLVMInstruction ins(LLVMInstruction::Type::GetListItem); + ins.workList = list; + m_listPtrs[list] = LLVMListPtr(); + + assert(m_tmpRegs.size() >= 1); + ins.args.push_back({ Compiler::StaticType::Number, m_tmpRegs[0] }); + + m_tmpRegs.erase(m_tmpRegs.end() - 1, m_tmpRegs.end()); + + auto ret = std::make_shared(Compiler::StaticType::Unknown); + ret->isRawValue = false; + ins.functionReturnReg = ret; + m_regs[m_currentFunction].push_back(ret); + m_tmpRegs.push_back(ret); + + m_instructions.push_back(ins); +} + +void LLVMCodeBuilder::addListItemIndex(List *list) +{ + LLVMInstruction &ins = createOp(LLVMInstruction::Type::GetListItemIndex, Compiler::StaticType::Number, Compiler::StaticType::Unknown, 1); + ins.workList = list; + m_listPtrs[list] = LLVMListPtr(); +} + +void LLVMCodeBuilder::addListContains(List *list) +{ + LLVMInstruction &ins = createOp(LLVMInstruction::Type::ListContainsItem, Compiler::StaticType::Bool, Compiler::StaticType::Unknown, 1); + ins.workList = list; + m_listPtrs[list] = LLVMListPtr(); +} + +void LLVMCodeBuilder::addListSize(List *list) +{ + LLVMInstruction &ins = createOp(LLVMInstruction::Type::GetListSize, Compiler::StaticType::Number, {}, 0); + ins.workList = list; + m_listPtrs[list] = LLVMListPtr(); +} + void LLVMCodeBuilder::createAdd() { createOp(LLVMInstruction::Type::Add, Compiler::StaticType::Number, Compiler::StaticType::Number, 2); @@ -925,6 +1104,41 @@ void LLVMCodeBuilder::createVariableWrite(Variable *variable) m_variablePtrs[variable] = LLVMVariablePtr(); } +void LLVMCodeBuilder::createListClear(List *list) +{ + LLVMInstruction &ins = createOp(LLVMInstruction::Type::ClearList, Compiler::StaticType::Void, Compiler::StaticType::Void, 0); + ins.workList = list; + m_listPtrs[list] = LLVMListPtr(); +} + +void LLVMCodeBuilder::createListRemove(List *list) +{ + LLVMInstruction &ins = createOp(LLVMInstruction::Type::RemoveListItem, Compiler::StaticType::Void, Compiler::StaticType::Number, 1); + ins.workList = list; + m_listPtrs[list] = LLVMListPtr(); +} + +void LLVMCodeBuilder::createListAppend(List *list) +{ + LLVMInstruction &ins = createOp(LLVMInstruction::Type::AppendToList, Compiler::StaticType::Void, Compiler::StaticType::Unknown, 1); + ins.workList = list; + m_listPtrs[list] = LLVMListPtr(); +} + +void LLVMCodeBuilder::createListInsert(List *list) +{ + LLVMInstruction &ins = createOp(LLVMInstruction::Type::InsertToList, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, 2); + ins.workList = list; + m_listPtrs[list] = LLVMListPtr(); +} + +void LLVMCodeBuilder::createListReplace(List *list) +{ + LLVMInstruction &ins = createOp(LLVMInstruction::Type::ListReplace, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, 2); + ins.workList = list; + m_listPtrs[list] = LLVMListPtr(); +} + void LLVMCodeBuilder::beginIfStatement() { LLVMInstruction ins(LLVMInstruction::Type::BeginIf); @@ -1031,6 +1245,36 @@ void LLVMCodeBuilder::createVariableMap() } } +void LLVMCodeBuilder::createListMap() +{ + if (!m_target) + return; + + // Map list pointers to list array indices + const auto &lists = m_target->lists(); + List **listData = m_target->listData(); + const size_t len = lists.size(); + m_targetListMap.clear(); + m_targetListMap.reserve(len); + + size_t i, j; + + for (i = 0; i < len; i++) { + List *list = lists[i].get(); + + // Find this list + for (j = 0; j < len; j++) { + if (listData[j] == list) + break; + } + + if (j < len) + m_targetListMap[list] = j; + else + assert(false); + } +} + void LLVMCodeBuilder::pushScopeLevel() { m_scopeVariables.push_back({}); @@ -1315,6 +1559,22 @@ llvm::Value *LLVMCodeBuilder::getVariablePtr(llvm::Value *targetVariables, Varia return m_builder.CreateIntToPtr(addr, m_valueDataType->getPointerTo()); } +llvm::Value *LLVMCodeBuilder::getListPtr(llvm::Value *targetLists, List *list) +{ + if (!m_target->isStage() && list->target() == m_target) { + // If this is a local sprite list, use the list array at runtime (for clones) + assert(m_targetListMap.find(list) != m_targetListMap.cend()); + const size_t index = m_targetListMap[list]; + auto pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); + llvm::Value *ptr = m_builder.CreateGEP(pointerType, targetLists, m_builder.getInt64(index)); + return m_builder.CreateLoad(pointerType, ptr); + } + + // Otherwise create a raw pointer at compile time + llvm::Value *addr = m_builder.getInt64((uintptr_t)list); + return m_builder.CreateIntToPtr(addr, m_valueDataType->getPointerTo()); +} + void LLVMCodeBuilder::syncVariables(llvm::Value *targetVariables) { // Copy stack variables to the actual variables @@ -1336,15 +1596,45 @@ void LLVMCodeBuilder::reloadVariables(llvm::Value *targetVariables) } } +void LLVMCodeBuilder::reloadLists() +{ + // Reload list data pointers + for (auto &[list, listPtr] : m_listPtrs) + m_builder.CreateStore(m_builder.CreateCall(resolve_list_data(), listPtr.ptr), listPtr.dataPtr); +} + +void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr, llvm::Function *func) +{ + // dataPtr = dirty ? list_data(list) : dataPtr + // dirty = false + llvm::Value *dirty = m_builder.CreateLoad(m_builder.getInt1Ty(), listPtr.dataPtrDirty); + llvm::Value *dataPtr = m_builder.CreateSelect(dirty, m_builder.CreateCall(resolve_list_data(), listPtr.ptr), m_builder.CreateLoad(m_valueDataType->getPointerTo(), listPtr.dataPtr)); + m_builder.CreateStore(dataPtr, listPtr.dataPtr); + m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty); +} + LLVMInstruction &LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount) +{ + std::vector types; + types.reserve(argCount); + + for (size_t i = 0; i < argCount; i++) + types.push_back(argType); + + return createOp(type, retType, types, argCount); +} + +LLVMInstruction &LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const std::vector &argTypes, size_t argCount) { LLVMInstruction ins(type); assert(m_tmpRegs.size() >= argCount); size_t j = 0; - for (size_t i = m_tmpRegs.size() - argCount; i < m_tmpRegs.size(); i++) - ins.args.push_back({ argType, m_tmpRegs[i] }); + for (size_t i = m_tmpRegs.size() - argCount; i < m_tmpRegs.size(); i++) { + ins.args.push_back({ argTypes[j], m_tmpRegs[i] }); + j++; + } m_tmpRegs.erase(m_tmpRegs.end() - argCount, m_tmpRegs.end()); @@ -1383,7 +1673,7 @@ void LLVMCodeBuilder::createValueStore(LLVMRegisterPtr reg, llvm::Value *targetP case Compiler::StaticType::Bool: { // Write number to bool value directly and change type llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0); - llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0); + llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 1); m_builder.CreateStore(converted, ptr); m_builder.CreateStore(m_builder.getInt32(static_cast(mappedType)), typePtr); break; @@ -1402,7 +1692,7 @@ void LLVMCodeBuilder::createValueStore(LLVMRegisterPtr reg, llvm::Value *targetP // Write bool to number value directly and change type llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0); m_builder.CreateStore(converted, ptr); - llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0); + llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 1); m_builder.CreateStore(converted, ptr); m_builder.CreateStore(m_builder.getInt32(static_cast(mappedType)), typePtr); break; @@ -1436,12 +1726,48 @@ void LLVMCodeBuilder::createValueStore(LLVMRegisterPtr reg, llvm::Value *targetP } } +void LLVMCodeBuilder::createInitialValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType) +{ + llvm::Value *converted = nullptr; + + if (sourceType != Compiler::StaticType::Unknown) + converted = castValue(reg, sourceType); + + auto it = std::find_if(TYPE_MAP.begin(), TYPE_MAP.end(), [sourceType](const std::pair &pair) { return pair.second == sourceType; }); + const ValueType mappedType = it == TYPE_MAP.cend() ? ValueType::Number : it->first; // unknown type can be ignored + + llvm::Value *valuePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0); + llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 1); + m_builder.CreateStore(m_builder.getInt32(static_cast(mappedType)), typePtr); + + switch (sourceType) { + case Compiler::StaticType::Number: + case Compiler::StaticType::Bool: + // Write number/bool directly + m_builder.CreateStore(converted, valuePtr); + break; + + case Compiler::StaticType::String: + m_builder.CreateCall(resolve_value_assign_cstring(), { targetPtr, converted }); + break; + + case Compiler::StaticType::Unknown: + m_builder.CreateCall(resolve_value_assign_copy(), { targetPtr, reg->value }); + break; + + default: + assert(false); + break; + } +} + void LLVMCodeBuilder::createValueCopy(llvm::Value *source, llvm::Value *target) { // NOTE: This doesn't copy strings, but only the pointers copyStructField(source, target, 0, m_valueDataType, m_builder.getInt64Ty()); // value copyStructField(source, target, 1, m_valueDataType, m_builder.getInt32Ty()); // type - copyStructField(source, target, 2, m_valueDataType, m_builder.getInt64Ty()); // string size + /* 2: padding */ + copyStructField(source, target, 3, m_valueDataType, m_builder.getInt64Ty()); // string size } void LLVMCodeBuilder::copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType) @@ -1451,6 +1777,62 @@ void LLVMCodeBuilder::copyStructField(llvm::Value *source, llvm::Value *target, m_builder.CreateStore(m_builder.CreateLoad(fieldType, sourceField), targetField); } +llvm::Value *LLVMCodeBuilder::getListItem(const LLVMListPtr &listPtr, llvm::Value *index, llvm::Function *func) +{ + updateListDataPtr(listPtr, func); + return m_builder.CreateGEP(m_valueDataType, m_builder.CreateLoad(m_valueDataType->getPointerTo(), listPtr.dataPtr), index); +} + +llvm::Value *LLVMCodeBuilder::getListItemIndex(const LLVMListPtr &listPtr, LLVMRegisterPtr item, llvm::Function *func) +{ + llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr); + llvm::BasicBlock *condBlock = llvm::BasicBlock::Create(m_ctx, "", func); + llvm::BasicBlock *bodyBlock = llvm::BasicBlock::Create(m_ctx, "", func); + llvm::BasicBlock *cmpIfBlock = llvm::BasicBlock::Create(m_ctx, "", func); + llvm::BasicBlock *cmpElseBlock = llvm::BasicBlock::Create(m_ctx, "", func); + llvm::BasicBlock *notFoundBlock = llvm::BasicBlock::Create(m_ctx, "", func); + llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_ctx, "", func); + + // index = 0 + llvm::Value *index = m_builder.CreateAlloca(m_builder.getInt64Ty()); + m_builder.CreateStore(m_builder.getInt64(0), index); + m_builder.CreateBr(condBlock); + + // while (index < size) + m_builder.SetInsertPoint(condBlock); + llvm::Value *cond = m_builder.CreateICmpULT(m_builder.CreateLoad(m_builder.getInt64Ty(), index), size); + m_builder.CreateCondBr(cond, bodyBlock, notFoundBlock); + + // if (list[index] == item) + m_builder.SetInsertPoint(bodyBlock); + LLVMRegisterPtr currentItem = std::make_shared(listPtr.type); + currentItem->isRawValue = false; + currentItem->value = getListItem(listPtr, m_builder.CreateLoad(m_builder.getInt64Ty(), index), func); + llvm::Value *cmp = createComparison(currentItem, item, Comparison::EQ); + m_builder.CreateCondBr(cmp, cmpIfBlock, cmpElseBlock); + + // goto nextBlock + m_builder.SetInsertPoint(cmpIfBlock); + m_builder.CreateBr(nextBlock); + + // else index++ + m_builder.SetInsertPoint(cmpElseBlock); + m_builder.CreateStore(m_builder.CreateAdd(m_builder.CreateLoad(m_builder.getInt64Ty(), index), m_builder.getInt64(1)), index); + m_builder.CreateBr(condBlock); + + // notFoundBlock: + // index = -1 + // goto nextBlock + m_builder.SetInsertPoint(notFoundBlock); + m_builder.CreateStore(llvm::ConstantInt::get(llvm::Type::getInt64Ty(m_ctx), -1, true), index); + m_builder.CreateBr(nextBlock); + + // nextBlock: + m_builder.SetInsertPoint(nextBlock); + + return m_builder.CreateLoad(m_builder.getInt64Ty(), index); +} + llvm::Value *LLVMCodeBuilder::createValue(LLVMRegisterPtr reg) { if (reg->isConstValue) { @@ -1458,13 +1840,28 @@ llvm::Value *LLVMCodeBuilder::createValue(LLVMRegisterPtr reg) llvm::Constant *value = castConstValue(reg->constValue, TYPE_MAP[reg->constValue.type()]); llvm::Value *ret = m_builder.CreateAlloca(m_valueDataType); - if (reg->constValue.type() == ValueType::String) - value = llvm::ConstantExpr::getPtrToInt(value, m_valueDataType->getElementType(0)); - else - value = llvm::ConstantExpr::getBitCast(value, m_valueDataType->getElementType(0)); + switch (reg->constValue.type()) { + case ValueType::Number: + value = llvm::ConstantExpr::getBitCast(value, m_valueDataType->getElementType(0)); + break; + + case ValueType::Bool: + // Assuming union type is int64 + value = m_builder.getInt64(reg->constValue.toBool()); + break; + + case ValueType::String: + value = llvm::ConstantExpr::getPtrToInt(value, m_valueDataType->getElementType(0)); + break; + + default: + assert(false); + break; + } llvm::Constant *type = m_builder.getInt32(static_cast(reg->constValue.type())); - llvm::Constant *constValue = llvm::ConstantStruct::get(m_valueDataType, { value, type, m_builder.getInt64(0) }); + llvm::Constant *padding = m_builder.getInt32(0); + llvm::Constant *constValue = llvm::ConstantStruct::get(m_valueDataType, { value, type, padding, m_builder.getInt64(0) }); m_builder.CreateStore(constValue, ret); return ret; @@ -1769,6 +2166,48 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_value_lower() return resolveFunction("value_lower", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false)); } +llvm::FunctionCallee LLVMCodeBuilder::resolve_list_clear() +{ + llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); + return resolveFunction("list_clear", llvm::FunctionType::get(m_builder.getVoidTy(), { listPtr }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_list_remove() +{ + llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); + return resolveFunction("list_remove", llvm::FunctionType::get(m_builder.getVoidTy(), { listPtr, m_builder.getInt64Ty() }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_list_append_empty() +{ + llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); + return resolveFunction("list_append_empty", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_list_insert_empty() +{ + llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); + return resolveFunction("list_insert_empty", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr, m_builder.getInt64Ty() }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_list_data() +{ + llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); + return resolveFunction("list_data", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_list_size_ptr() +{ + llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); + return resolveFunction("list_size_ptr", llvm::FunctionType::get(m_builder.getInt64Ty()->getPointerTo()->getPointerTo(), { listPtr }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_list_alloc_size_ptr() +{ + llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); + return resolveFunction("list_alloc_size_ptr", llvm::FunctionType::get(m_builder.getInt64Ty()->getPointerTo()->getPointerTo(), { listPtr }, false)); +} + llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp() { llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h index 6117b02f..ef52b110 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.h +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h @@ -11,6 +11,7 @@ #include "llvminstruction.h" #include "llvmcoroutine.h" #include "llvmvariableptr.h" +#include "llvmlistptr.h" namespace libscratchcpp { @@ -28,6 +29,10 @@ class LLVMCodeBuilder : public ICodeBuilder void addConstValue(const Value &value) override; void addVariableValue(Variable *variable) override; void addListContents(List *list) override; + void addListItem(List *list) override; + void addListItemIndex(List *list) override; + void addListContains(List *list) override; + void addListSize(List *list) override; void createAdd() override; void createSub() override; @@ -61,6 +66,12 @@ class LLVMCodeBuilder : public ICodeBuilder void createVariableWrite(Variable *variable) override; + void createListClear(List *list) override; + void createListRemove(List *list) override; + void createListAppend(List *list) override; + void createListInsert(List *list) override; + void createListReplace(List *list) override; + void beginIfStatement() override; void beginElseBranch() override; void endIf() override; @@ -83,6 +94,7 @@ class LLVMCodeBuilder : public ICodeBuilder void initTypes(); void createVariableMap(); + void createListMap(); void pushScopeLevel(); void popScopeLevel(); @@ -99,14 +111,21 @@ class LLVMCodeBuilder : public ICodeBuilder llvm::Value *removeNaN(llvm::Value *num); llvm::Value *getVariablePtr(llvm::Value *targetVariables, Variable *variable); + llvm::Value *getListPtr(llvm::Value *targetLists, List *list); void syncVariables(llvm::Value *targetVariables); void reloadVariables(llvm::Value *targetVariables); + void reloadLists(); + void updateListDataPtr(const LLVMListPtr &listPtr, llvm::Function *func); LLVMInstruction &createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount); + LLVMInstruction &createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const std::vector &argTypes, size_t argCount); void createValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType); + void createInitialValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType); void createValueCopy(llvm::Value *source, llvm::Value *target); void copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType); + llvm::Value *getListItem(const LLVMListPtr &listPtr, llvm::Value *index, llvm::Function *func); + llvm::Value *getListItemIndex(const LLVMListPtr &listPtr, LLVMRegisterPtr item, llvm::Function *func); llvm::Value *createValue(LLVMRegisterPtr reg); llvm::Value *createComparison(LLVMRegisterPtr arg1, LLVMRegisterPtr arg2, Comparison type); @@ -129,13 +148,24 @@ class LLVMCodeBuilder : public ICodeBuilder llvm::FunctionCallee resolve_value_equals(); llvm::FunctionCallee resolve_value_greater(); llvm::FunctionCallee resolve_value_lower(); + llvm::FunctionCallee resolve_list_clear(); + llvm::FunctionCallee resolve_list_remove(); + llvm::FunctionCallee resolve_list_append_empty(); + llvm::FunctionCallee resolve_list_insert_empty(); + llvm::FunctionCallee resolve_list_data(); + llvm::FunctionCallee resolve_list_size_ptr(); + llvm::FunctionCallee resolve_list_alloc_size_ptr(); llvm::FunctionCallee resolve_strcasecmp(); Target *m_target = nullptr; + std::unordered_map m_targetVariableMap; std::unordered_map m_variablePtrs; std::vector> m_scopeVariables; + std::unordered_map m_targetListMap; + std::unordered_map m_listPtrs; + std::string m_id; llvm::LLVMContext m_ctx; std::unique_ptr m_module; diff --git a/src/dev/engine/internal/llvm/llvmexecutablecode.cpp b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp index 3357be56..eb8b8b03 100644 --- a/src/dev/engine/internal/llvm/llvmexecutablecode.cpp +++ b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp @@ -55,7 +55,7 @@ void LLVMExecutableCode::run(ExecutionContext *context) ctx->setFinished(done); } else { Target *target = ctx->target(); - void *handle = m_mainFunction(target, target->variableData()); + void *handle = m_mainFunction(target, target->variableData(), target->listData()); if (!handle) ctx->setFinished(true); diff --git a/src/dev/engine/internal/llvm/llvmexecutablecode.h b/src/dev/engine/internal/llvm/llvmexecutablecode.h index e066cd9c..26e412dd 100644 --- a/src/dev/engine/internal/llvm/llvmexecutablecode.h +++ b/src/dev/engine/internal/llvm/llvmexecutablecode.h @@ -11,6 +11,7 @@ namespace libscratchcpp { class Target; +class List; class LLVMExecutionContext; class LLVMExecutableCode : public ExecutableCode @@ -32,7 +33,7 @@ class LLVMExecutableCode : public ExecutableCode private: uint64_t lookupFunction(const std::string &name); - using MainFunctionType = void *(*)(Target *, ValueData **); + using MainFunctionType = void *(*)(Target *, ValueData **, List **); using ResumeFunctionType = bool (*)(void *); static LLVMExecutionContext *getContext(ExecutionContext *context); diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h index 41186cad..afc13d1d 100644 --- a/src/dev/engine/internal/llvm/llvminstruction.h +++ b/src/dev/engine/internal/llvm/llvminstruction.h @@ -42,6 +42,15 @@ struct LLVMInstruction Exp10, WriteVariable, ReadVariable, + ClearList, + RemoveListItem, + AppendToList, + InsertToList, + ListReplace, + GetListItem, + GetListSize, + GetListItemIndex, + ListContainsItem, Yield, BeginIf, BeginElse, @@ -63,6 +72,7 @@ struct LLVMInstruction std::vector> args; // target type, register LLVMRegisterPtr functionReturnReg; Variable *workVariable = nullptr; // for variables + List *workList = nullptr; // for lists }; } // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmlistptr.h b/src/dev/engine/internal/llvm/llvmlistptr.h new file mode 100644 index 00000000..6b427572 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmlistptr.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace llvm +{ + +class Value; + +} + +namespace libscratchcpp +{ + +struct LLVMListPtr +{ + llvm::Value *ptr = nullptr; + llvm::Value *dataPtr = nullptr; + llvm::Value *sizePtr = nullptr; + llvm::Value *allocatedSizePtr = nullptr; + llvm::Value *dataPtrDirty = nullptr; + Compiler::StaticType type = Compiler::StaticType::Unknown; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmtypes.cpp b/src/dev/engine/internal/llvm/llvmtypes.cpp index bbd21f28..5dfc6075 100644 --- a/src/dev/engine/internal/llvm/llvmtypes.cpp +++ b/src/dev/engine/internal/llvm/llvmtypes.cpp @@ -14,10 +14,11 @@ llvm::StructType *LLVMTypes::createValueDataType(llvm::IRBuilder<> *builder) llvm::Type *unionType = builder->getInt64Ty(); // 64 bits is the largest size llvm::Type *valueType = llvm::Type::getInt32Ty(ctx); // Assuming ValueType is a 32-bit enum + llvm::Type *padding = llvm::Type::getInt32Ty(ctx); // Padding for alignment llvm::Type *sizeType = llvm::Type::getInt64Ty(ctx); // size_t llvm::StructType *ret = llvm::StructType::create(ctx, "ValueData"); - ret->setBody({ unionType, valueType, sizeType }); + ret->setBody({ unionType, valueType, padding, sizeType }); return ret; } diff --git a/src/scratch/CMakeLists.txt b/src/scratch/CMakeLists.txt index 926cb2b0..71d6c8c0 100644 --- a/src/scratch/CMakeLists.txt +++ b/src/scratch/CMakeLists.txt @@ -14,6 +14,8 @@ target_sources(scratchcpp list.cpp list_p.cpp list_p.h + list_functions.cpp + list_functions.h block.cpp block_p.cpp block_p.h diff --git a/src/scratch/list_functions.cpp b/src/scratch/list_functions.cpp new file mode 100644 index 00000000..a10cb669 --- /dev/null +++ b/src/scratch/list_functions.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "list_functions.h" + +using namespace libscratchcpp; + +extern "C" +{ + void list_clear(List *list) + { + list->clear(); + } + + void list_remove(List *list, size_t index) + { + list->removeAt(index); + } + + ValueData *list_append_empty(List *list) + { + return &list->appendEmpty(); + } + + ValueData *list_insert_empty(List *list, size_t index) + { + return &list->insertEmpty(index); + } + + ValueData *list_get_item(List *list, size_t index) + { + return &list->operator[](index); + } + + ValueData *list_data(List *list) + { + return list->data(); + } + + size_t *list_size_ptr(List *list) + { + return list->sizePtr(); + } + + const size_t *list_alloc_size_ptr(List *list) + { + return list->allocatedSizePtr(); + } + + size_t list_size(List *list) + { + return list->size(); + } +} diff --git a/src/scratch/list_functions.h b/src/scratch/list_functions.h new file mode 100644 index 00000000..05edc4c4 --- /dev/null +++ b/src/scratch/list_functions.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class List; +struct ValueData; + +extern "C" +{ + void list_clear(List *list); + void list_remove(List *list, size_t index); + + ValueData *list_append_empty(List *list); + ValueData *list_insert_empty(List *list, size_t index); + + ValueData *list_get_item(List *list, size_t index); + ValueData *list_data(List *list); + size_t *list_size_ptr(List *list); + const size_t *list_alloc_size_ptr(List *list); + size_t list_size(List *list); +} + +} // namespace libscratchcpp diff --git a/src/scratch/target.cpp b/src/scratch/target.cpp index c25b2f38..d1e4224f 100644 --- a/src/scratch/target.cpp +++ b/src/scratch/target.cpp @@ -34,6 +34,9 @@ Target::~Target() { if (impl->variableData) free(impl->variableData); + + if (impl->listData) + free(impl->listData); } /*! Returns true. */ @@ -150,6 +153,7 @@ int Target::addList(std::shared_ptr list) return it - impl->lists.begin(); impl->lists.push_back(list); + impl->listDataDirty = true; list->setTarget(this); return impl->lists.size() - 1; @@ -186,6 +190,31 @@ int Target::findListById(const std::string &id) const return it - impl->lists.begin(); } +/*! Returns an array of list pointers (for optimized list access). */ +List **Target::listData() +{ + if (impl->listDataDirty) { + const size_t len = impl->lists.size(); + + if (len == 0) { + impl->listDataDirty = false; + return nullptr; + } + + if (impl->listData) + impl->listData = (List **)realloc(impl->listData, len * sizeof(List *)); + else + impl->listData = (List **)malloc(len * sizeof(List *)); + + for (size_t i = 0; i < len; i++) + impl->listData[i] = impl->lists[i].get(); + + impl->listDataDirty = false; + } + + return impl->listData; +} + /*! Returns the list of blocks. */ const std::vector> &Target::blocks() const { diff --git a/src/scratch/target_p.h b/src/scratch/target_p.h index fc18bfb8..1261330a 100644 --- a/src/scratch/target_p.h +++ b/src/scratch/target_p.h @@ -31,6 +31,8 @@ struct TargetPrivate bool variableDataDirty = true; ValueData **variableData = nullptr; std::vector> lists; + bool listDataDirty = true; + List **listData = nullptr; std::vector> blocks; std::vector> comments; int costumeIndex = -1; diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp index e20ab74a..9042a515 100644 --- a/test/dev/compiler/compiler_test.cpp +++ b/test/dev/compiler/compiler_test.cpp @@ -132,6 +132,70 @@ TEST_F(CompilerTest, AddListContents) compile(compiler, block); } +TEST_F(CompilerTest, AddListItem) +{ + Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("a", ""); + block->setCompileFunction([](Compiler *compiler) { + List list1("", ""), list2("", ""); + EXPECT_CALL(*m_builder, addListItem(&list1)); + compiler->addListItem(&list1); + + EXPECT_CALL(*m_builder, addListItem(&list2)); + compiler->addListItem(&list2); + }); + + compile(compiler, block); +} + +TEST_F(CompilerTest, AddListItemIndex) +{ + Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("a", ""); + block->setCompileFunction([](Compiler *compiler) { + List list1("", ""), list2("", ""); + EXPECT_CALL(*m_builder, addListItemIndex(&list1)); + compiler->addListItemIndex(&list1); + + EXPECT_CALL(*m_builder, addListItemIndex(&list2)); + compiler->addListItemIndex(&list2); + }); + + compile(compiler, block); +} + +TEST_F(CompilerTest, AddListSize) +{ + Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("a", ""); + block->setCompileFunction([](Compiler *compiler) { + List list1("", ""), list2("", ""); + EXPECT_CALL(*m_builder, addListSize(&list1)); + compiler->addListSize(&list1); + + EXPECT_CALL(*m_builder, addListSize(&list2)); + compiler->addListSize(&list2); + }); + + compile(compiler, block); +} + +TEST_F(CompilerTest, AddListContains) +{ + Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("a", ""); + block->setCompileFunction([](Compiler *compiler) { + List list1("", ""), list2("", ""); + EXPECT_CALL(*m_builder, addListContains(&list1)); + compiler->addListContains(&list1); + + EXPECT_CALL(*m_builder, addListContains(&list2)); + compiler->addListContains(&list2); + }); + + compile(compiler, block); +} + TEST_F(CompilerTest, AddInput) { Compiler compiler(&m_engine, &m_target); @@ -571,6 +635,28 @@ TEST_F(CompilerTest, CreateVariableWrite) compile(compiler, block); } +TEST_F(CompilerTest, CustomIfStatement) +{ + Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginIfStatement()); + compiler->beginIfStatement(); + EXPECT_CALL(*m_builder, endIf()); + compiler->endIf(); + + EXPECT_CALL(*m_builder, beginIfStatement()); + compiler->beginIfStatement(); + EXPECT_CALL(*m_builder, beginElseBranch()); + compiler->beginElseBranch(); + EXPECT_CALL(*m_builder, endIf()); + compiler->endIf(); + }); + + compile(compiler, block); +} + TEST_F(CompilerTest, MoveToIf) { Compiler compiler(&m_engine, &m_target); diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp index 58e467d4..268e5802 100644 --- a/test/dev/llvm/llvmcodebuilder_test.cpp +++ b/test/dev/llvm/llvmcodebuilder_test.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -20,7 +21,7 @@ class LLVMCodeBuilderTest : public testing::Test public: void SetUp() override { - test_function(nullptr, nullptr); // force dependency + test_function(nullptr, nullptr, nullptr, nullptr); // force dependency } void createBuilder(Target *target, bool warp) { m_builder = std::make_unique(target, "test", warp); } @@ -1899,6 +1900,592 @@ TEST_F(LLVMCodeBuilderTest, ReadVariable) ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); } +TEST_F(LLVMCodeBuilderTest, ClearList) +{ + EngineMock engine; + Stage stage; + Sprite sprite; + sprite.setEngine(&engine); + EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage)); + + std::unordered_map strings; + + auto globalList1 = std::make_shared("", ""); + auto globalList2 = std::make_shared("", ""); + auto globalList3 = std::make_shared("", ""); + stage.addList(globalList1); + stage.addList(globalList2); + stage.addList(globalList3); + + auto localList1 = std::make_shared("", ""); + auto localList2 = std::make_shared("", ""); + auto localList3 = std::make_shared("", ""); + sprite.addList(localList1); + sprite.addList(localList2); + sprite.addList(localList3); + + globalList1->append(1); + globalList1->append(2); + globalList1->append(3); + strings[globalList1.get()] = globalList1->toString(); + + globalList2->append("Lorem"); + globalList2->append("ipsum"); + globalList2->append(-4.52); + strings[globalList2.get()] = globalList2->toString(); + + globalList3->append(true); + globalList3->append(false); + globalList3->append(true); + strings[globalList3.get()] = globalList3->toString(); + + localList1->append("dolor"); + localList1->append("sit"); + localList1->append("amet"); + strings[localList1.get()] = localList1->toString(); + + localList2->append(10); + localList2->append(9.8); + localList2->append(true); + strings[localList2.get()] = localList2->toString(); + + localList3->append("test"); + localList3->append(1.2); + localList3->append(false); + strings[localList3.get()] = localList3->toString(); + + createBuilder(&sprite, true); + + m_builder->createListClear(globalList1.get()); + m_builder->createListClear(globalList3.get()); + m_builder->createListClear(localList1.get()); + m_builder->createListClear(localList2.get()); + + auto code = m_builder->finalize(); + auto ctx = code->createExecutionContext(&sprite); + code->run(ctx.get()); + + ASSERT_TRUE(globalList1->empty()); + ASSERT_EQ(globalList2->toString(), strings[globalList2.get()]); + ASSERT_TRUE(globalList3->empty()); + + ASSERT_TRUE(localList1->empty()); + ASSERT_TRUE(localList2->empty()); + ASSERT_EQ(localList3->toString(), strings[localList3.get()]); +} + +TEST_F(LLVMCodeBuilderTest, RemoveFromList) +{ + EngineMock engine; + Stage stage; + Sprite sprite; + sprite.setEngine(&engine); + EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage)); + + std::unordered_map strings; + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + globalList->append(1); + globalList->append(2); + globalList->append(3); + + localList->append("Lorem"); + localList->append("ipsum"); + localList->append("dolor"); + localList->append("sit"); + strings[localList.get()] = localList->toString(); + + createBuilder(&sprite, true); + + m_builder->addConstValue(1); + m_builder->createListRemove(globalList.get()); + + m_builder->addConstValue(3); + m_builder->createListRemove(localList.get()); + + auto code = m_builder->finalize(); + auto ctx = code->createExecutionContext(&sprite); + code->run(ctx.get()); + + ASSERT_EQ(globalList->toString(), "13"); + ASSERT_EQ(localList->toString(), "Lorem ipsum dolor"); +} + +TEST_F(LLVMCodeBuilderTest, AppendToList) +{ + EngineMock engine; + Stage stage; + Sprite sprite; + sprite.setEngine(&engine); + EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage)); + + std::unordered_map strings; + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + globalList->append(1); + globalList->append(2); + globalList->append(3); + + localList->append("Lorem"); + localList->append("ipsum"); + localList->append("dolor"); + localList->append("sit"); + strings[localList.get()] = localList->toString(); + + createBuilder(&sprite, true); + + m_builder->addConstValue(1); + m_builder->createListAppend(globalList.get()); + + m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get()); + + m_builder->addConstValue(3); + m_builder->createListAppend(localList.get()); + + m_builder->createListClear(localList.get()); + + m_builder->addConstValue(true); + m_builder->createListAppend(localList.get()); + + m_builder->addConstValue(false); + m_builder->createListAppend(localList.get()); + + m_builder->addConstValue("hello world"); + m_builder->createListAppend(localList.get()); + + auto code = m_builder->finalize(); + auto ctx = code->createExecutionContext(&sprite); + code->run(ctx.get()); + + ASSERT_EQ(globalList->toString(), "1 2 3 1 test"); + ASSERT_EQ(localList->toString(), "true false hello world"); +} + +TEST_F(LLVMCodeBuilderTest, InsertToList) +{ + EngineMock engine; + Stage stage; + Sprite sprite; + sprite.setEngine(&engine); + EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage)); + + std::unordered_map strings; + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + globalList->append(1); + globalList->append(2); + globalList->append(3); + + localList->append("Lorem"); + localList->append("ipsum"); + localList->append("dolor"); + localList->append("sit"); + strings[localList.get()] = localList->toString(); + + createBuilder(&sprite, true); + + m_builder->addConstValue(2); + m_builder->addConstValue(1); + m_builder->createListInsert(globalList.get()); + + m_builder->addConstValue(3); + m_builder->addConstValue("test"); + m_builder->createListInsert(globalList.get()); + + m_builder->addConstValue(0); + m_builder->addConstValue(3); + m_builder->createListInsert(localList.get()); + + m_builder->createListClear(localList.get()); + + m_builder->addConstValue(0); + m_builder->addConstValue(true); + m_builder->createListInsert(localList.get()); + + m_builder->addConstValue(0); + m_builder->addConstValue(false); + m_builder->createListInsert(localList.get()); + + m_builder->addConstValue(1); + m_builder->addConstValue("hello world"); + m_builder->createListInsert(localList.get()); + + auto code = m_builder->finalize(); + auto ctx = code->createExecutionContext(&sprite); + code->run(ctx.get()); + + ASSERT_EQ(globalList->toString(), "1 2 1 test 3"); + ASSERT_EQ(localList->toString(), "false hello world true"); +} + +TEST_F(LLVMCodeBuilderTest, ListReplace) +{ + EngineMock engine; + Stage stage; + Sprite sprite; + sprite.setEngine(&engine); + EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage)); + + std::unordered_map strings; + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + globalList->append(1); + globalList->append(2); + globalList->append(3); + + localList->append("Lorem"); + localList->append("ipsum"); + localList->append("dolor"); + localList->append("sit"); + strings[localList.get()] = localList->toString(); + + createBuilder(&sprite, true); + + m_builder->addConstValue(2); + m_builder->addConstValue(1); + m_builder->createListReplace(globalList.get()); + + m_builder->addConstValue(1); + m_builder->addConstValue("test"); + m_builder->createListReplace(globalList.get()); + + m_builder->addConstValue(0); + m_builder->addConstValue(3); + m_builder->createListReplace(localList.get()); + + m_builder->addConstValue(2); + m_builder->addConstValue(true); + m_builder->createListReplace(localList.get()); + + m_builder->addConstValue(3); + m_builder->addConstValue("hello world"); + m_builder->createListReplace(localList.get()); + + auto code = m_builder->finalize(); + auto ctx = code->createExecutionContext(&sprite); + code->run(ctx.get()); + + ASSERT_EQ(globalList->toString(), "1 test 1"); + ASSERT_EQ(localList->toString(), "3 ipsum true hello world"); +} + +TEST_F(LLVMCodeBuilderTest, GetListItem) +{ + EngineMock engine; + Stage stage; + Sprite sprite; + sprite.setEngine(&engine); + EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage)); + + std::unordered_map strings; + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + globalList->append(1); + globalList->append(2); + globalList->append(3); + + localList->append("Lorem"); + localList->append("ipsum"); + localList->append("dolor"); + localList->append("sit"); + strings[localList.get()] = localList->toString(); + + createBuilder(&sprite, true); + + m_builder->addConstValue(2); + m_builder->addListItem(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(1); + m_builder->addConstValue("test"); + m_builder->createListReplace(globalList.get()); + + m_builder->addConstValue(0); + m_builder->addListItem(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(0); + m_builder->addListItem(localList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(2); + m_builder->addListItem(localList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(3); + m_builder->addListItem(localList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + static const std::string expected = + "3\n" + "1\n" + "Lorem\n" + "dolor\n" + "sit\n"; + + auto code = m_builder->finalize(); + auto ctx = code->createExecutionContext(&sprite); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); + + ASSERT_EQ(globalList->toString(), "1 test 3"); + ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit"); +} + +TEST_F(LLVMCodeBuilderTest, GetListSize) +{ + EngineMock engine; + Stage stage; + Sprite sprite; + sprite.setEngine(&engine); + EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + globalList->append(1); + globalList->append(2); + globalList->append(3); + + localList->append("Lorem"); + localList->append("ipsum"); + localList->append("dolor"); + localList->append("sit"); + + createBuilder(&sprite, true); + + m_builder->addListSize(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addListSize(localList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + static const std::string expected = + "3\n" + "4\n"; + + auto code = m_builder->finalize(); + auto ctx = code->createExecutionContext(&sprite); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); + + ASSERT_EQ(globalList->toString(), "123"); + ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit"); +} + +TEST_F(LLVMCodeBuilderTest, GetListItemIndex) +{ + EngineMock engine; + Stage stage; + Sprite sprite; + sprite.setEngine(&engine); + EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + globalList->append(1); + globalList->append(2); + globalList->append(3); + + localList->append("Lorem"); + localList->append("ipsum"); + localList->append("dolor"); + localList->append("sit"); + + createBuilder(&sprite, true); + + m_builder->addConstValue(2); + m_builder->addListItemIndex(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(1); + m_builder->addListItemIndex(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(0); + m_builder->addListItemIndex(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(1); + m_builder->addConstValue("test"); + m_builder->createListReplace(globalList.get()); + + m_builder->addConstValue(2); + m_builder->addListItemIndex(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(1); + m_builder->addListItemIndex(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue("test"); + m_builder->addListItemIndex(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue("abc"); + m_builder->addListItemIndex(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue("doLor"); + m_builder->addListItemIndex(localList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(true); + m_builder->addListItemIndex(localList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue("site"); + m_builder->addListItemIndex(localList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + static const std::string expected = + "1\n" + "0\n" + "-1\n" + "-1\n" + "0\n" + "1\n" + "-1\n" + "2\n" + "-1\n" + "-1\n"; + + auto code = m_builder->finalize(); + auto ctx = code->createExecutionContext(&sprite); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); + + ASSERT_EQ(globalList->toString(), "1 test 3"); + ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit"); +} + +TEST_F(LLVMCodeBuilderTest, ListContainsItem) +{ + EngineMock engine; + Stage stage; + Sprite sprite; + sprite.setEngine(&engine); + EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + globalList->append(1); + globalList->append(2); + globalList->append(3); + + localList->append("Lorem"); + localList->append("ipsum"); + localList->append("dolor"); + localList->append("sit"); + + createBuilder(&sprite, true); + + m_builder->addConstValue(2); + m_builder->addListContains(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(1); + m_builder->addListContains(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(0); + m_builder->addListContains(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(1); + m_builder->addConstValue("test"); + m_builder->createListReplace(globalList.get()); + + m_builder->addConstValue(2); + m_builder->addListContains(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(1); + m_builder->addListContains(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue("test"); + m_builder->addListContains(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue("abc"); + m_builder->addListContains(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue("doLor"); + m_builder->addListContains(localList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(true); + m_builder->addListContains(localList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue("site"); + m_builder->addListContains(localList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + static const std::string expected = + "true\n" + "true\n" + "false\n" + "false\n" + "true\n" + "true\n" + "false\n" + "true\n" + "false\n" + "false\n"; + + auto code = m_builder->finalize(); + auto ctx = code->createExecutionContext(&sprite); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); + + ASSERT_EQ(globalList->toString(), "1 test 3"); + ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit"); +} + TEST_F(LLVMCodeBuilderTest, Yield) { auto build = [this]() { diff --git a/test/dev/llvm/llvmexecutablecode_test.cpp b/test/dev/llvm/llvmexecutablecode_test.cpp index 8af37328..84e86d15 100644 --- a/test/dev/llvm/llvmexecutablecode_test.cpp +++ b/test/dev/llvm/llvmexecutablecode_test.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include #include @@ -18,7 +20,7 @@ class LLVMExecutableCodeTest : public testing::Test { m_module = std::make_unique("test", m_ctx); m_builder = std::make_unique>(m_ctx); - test_function(nullptr, nullptr); // force dependency + test_function(nullptr, nullptr, nullptr, nullptr); // force dependency llvm::InitializeNativeTarget(); llvm::InitializeNativeTargetAsmPrinter(); @@ -29,7 +31,7 @@ class LLVMExecutableCodeTest : public testing::Test llvm::Function *beginMainFunction() { - // void *f(Target *, ValueData **, ValueData **) + // void *f(Target *, ValueData **, List **) llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, { pointerType, pointerType, pointerType }, false); llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "f", m_module.get()); @@ -55,12 +57,12 @@ class LLVMExecutableCodeTest : public testing::Test void addTestFunction(llvm::Function *mainFunc) { auto ptrType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); - auto func = m_module->getOrInsertFunction("test_function", llvm::FunctionType::get(m_builder->getVoidTy(), { ptrType, ptrType }, false)); + auto func = m_module->getOrInsertFunction("test_function", llvm::FunctionType::get(m_builder->getVoidTy(), { ptrType, ptrType, ptrType, ptrType }, false)); llvm::Constant *mockInt = llvm::ConstantInt::get(llvm::Type::getInt64Ty(m_ctx), (uintptr_t)&m_mock, false); llvm::Constant *mockPtr = llvm::ConstantExpr::getIntToPtr(mockInt, ptrType); - m_builder->CreateCall(func, { mockPtr, mainFunc->getArg(0) }); + m_builder->CreateCall(func, { mockPtr, mainFunc->getArg(0), mainFunc->getArg(1), mainFunc->getArg(2) }); } void addTestPrintFunction(llvm::Value *arg1, llvm::Value *arg2) @@ -94,6 +96,9 @@ TEST_F(LLVMExecutableCodeTest, CreateExecutionContext) TEST_F(LLVMExecutableCodeTest, MainFunction) { + m_target.addVariable(std::make_shared("", "")); + m_target.addList(std::make_shared("", "")); + auto f = beginMainFunction(); addTestFunction(f); endFunction(nullPointer()); @@ -105,7 +110,7 @@ TEST_F(LLVMExecutableCodeTest, MainFunction) auto ctx = code.createExecutionContext(&m_target); ASSERT_FALSE(code.isFinished(ctx.get())); - EXPECT_CALL(m_mock, f(&m_target)); + EXPECT_CALL(m_mock, f(&m_target, m_target.variableData(), m_target.listData())); code.run(ctx.get()); ASSERT_TRUE(code.isFinished(ctx.get())); @@ -124,11 +129,13 @@ TEST_F(LLVMExecutableCodeTest, MainFunction) // Test with another context Target anotherTarget; + anotherTarget.addVariable(std::make_shared("", "")); + anotherTarget.addList(std::make_shared("", "")); auto anotherCtx = code.createExecutionContext(&anotherTarget); ASSERT_FALSE(code.isFinished(anotherCtx.get())); ASSERT_TRUE(code.isFinished(ctx.get())); - EXPECT_CALL(m_mock, f(&anotherTarget)); + EXPECT_CALL(m_mock, f(&anotherTarget, anotherTarget.variableData(), anotherTarget.listData())); code.run(anotherCtx.get()); ASSERT_TRUE(code.isFinished(anotherCtx.get())); ASSERT_TRUE(code.isFinished(ctx.get())); diff --git a/test/dev/llvm/testfunctions.cpp b/test/dev/llvm/testfunctions.cpp index 86ecb8b6..8555ee24 100644 --- a/test/dev/llvm/testfunctions.cpp +++ b/test/dev/llvm/testfunctions.cpp @@ -11,10 +11,10 @@ static int counter = 0; extern "C" { - void test_function(TestMock *mock, Target *target) + void test_function(TestMock *mock, Target *target, ValueData **varData, List **listData) { if (mock) - mock->f(target); + mock->f(target, varData, listData); } void test_print_function(ValueData *arg1, ValueData *arg2) diff --git a/test/dev/llvm/testfunctions.h b/test/dev/llvm/testfunctions.h index 94f8a76a..c20c8efa 100644 --- a/test/dev/llvm/testfunctions.h +++ b/test/dev/llvm/testfunctions.h @@ -6,10 +6,11 @@ namespace libscratchcpp class TestMock; class Target; class ValueData; +class List; extern "C" { - void test_function(TestMock *mock, Target *target); + void test_function(TestMock *mock, Target *target, ValueData **varData, List **listData); void test_print_function(ValueData *arg1, ValueData *arg2); void test_function_no_args(Target *target); diff --git a/test/dev/llvm/testmock.h b/test/dev/llvm/testmock.h index 9b26d1fa..176eeff2 100644 --- a/test/dev/llvm/testmock.h +++ b/test/dev/llvm/testmock.h @@ -6,11 +6,13 @@ namespace libscratchcpp { class Target; +struct ValueData; +class List; class TestMock { public: - MOCK_METHOD(void, f, (Target *)); + MOCK_METHOD(void, f, (Target *, ValueData **, List **)); }; } // namespace libscratchcpp diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h index 0028f76b..4aa0daeb 100644 --- a/test/mocks/codebuildermock.h +++ b/test/mocks/codebuildermock.h @@ -13,6 +13,10 @@ class CodeBuilderMock : public ICodeBuilder MOCK_METHOD(void, addConstValue, (const Value &), (override)); MOCK_METHOD(void, addVariableValue, (Variable *), (override)); MOCK_METHOD(void, addListContents, (List *), (override)); + MOCK_METHOD(void, addListItem, (List *), (override)); + MOCK_METHOD(void, addListItemIndex, (List *), (override)); + MOCK_METHOD(void, addListContains, (List *), (override)); + MOCK_METHOD(void, addListSize, (List *), (override)); MOCK_METHOD(void, createAdd, (), (override)); MOCK_METHOD(void, createSub, (), (override)); @@ -46,6 +50,12 @@ class CodeBuilderMock : public ICodeBuilder MOCK_METHOD(void, createVariableWrite, (Variable *), (override)); + MOCK_METHOD(void, createListClear, (List *), (override)); + MOCK_METHOD(void, createListRemove, (List *), (override)); + MOCK_METHOD(void, createListAppend, (List *), (override)); + MOCK_METHOD(void, createListInsert, (List *), (override)); + MOCK_METHOD(void, createListReplace, (List *), (override)); + MOCK_METHOD(void, beginIfStatement, (), (override)); MOCK_METHOD(void, beginElseBranch, (), (override)); MOCK_METHOD(void, endIf, (), (override)); diff --git a/test/scratch_classes/CMakeLists.txt b/test/scratch_classes/CMakeLists.txt index 279727bb..511cf190 100644 --- a/test/scratch_classes/CMakeLists.txt +++ b/test/scratch_classes/CMakeLists.txt @@ -16,6 +16,7 @@ gtest_discover_tests(blockprototype_test) add_executable( list_test list_test.cpp + list_functions_test.cpp ) target_link_libraries( diff --git a/test/scratch_classes/list_functions_test.cpp b/test/scratch_classes/list_functions_test.cpp new file mode 100644 index 00000000..2f10e80f --- /dev/null +++ b/test/scratch_classes/list_functions_test.cpp @@ -0,0 +1,148 @@ +#include +#include + +#include + +using namespace libscratchcpp; + +TEST(ListFunctionsTest, Clear) +{ + List list("", ""); + list.append(1); + list.append(2); + list.append(3); + + list_clear(&list); +} + +TEST(ListFunctionsTest, Remove) +{ + List list("", "test list"); + list.append("Lorem"); + list.append("ipsum"); + list.append("dolor"); + list.append("sit"); + list.append("amet"); + + list_remove(&list, 1); + ASSERT_EQ(list.toString(), "Lorem dolor sit amet"); + list_remove(&list, 3); + ASSERT_EQ(list.toString(), "Lorem dolor sit"); + list_remove(&list, 0); + ASSERT_EQ(list.toString(), "dolor sit"); + list_remove(&list, 1); + ASSERT_EQ(list.toString(), "dolor"); + list_remove(&list, 0); + ASSERT_TRUE(list.empty()); +} + +TEST(ListFunctionsTest, AppendEmpty) +{ + List list("", "test list"); + list.append("Lorem"); + list.append("ipsum"); + + ValueData *v = list_append_empty(&list); + value_init(v); + value_assign_double(v, 5); + ASSERT_EQ(list.toString(), "Lorem ipsum 5"); + + v = list_append_empty(&list); + value_init(v); + value_assign_string(v, "test"); + ASSERT_EQ(list.toString(), "Lorem ipsum 5 test"); +} + +TEST(ListFunctionsTest, InsertEmpty) +{ + List list("", "test list"); + list.append("Lorem"); + list.append("ipsum"); + + ValueData *v = list_insert_empty(&list, 0); + value_init(v); + value_assign_double(v, 5); + ASSERT_EQ(list.toString(), "5 Lorem ipsum"); + + v = list_insert_empty(&list, 2); + value_init(v); + value_assign_string(v, "test"); + ASSERT_EQ(list.toString(), "5 Lorem test ipsum"); +} + +TEST(ListFunctionsTest, GetItem) +{ + List list("", "test list"); + list.append("Lorem"); + list.append("ipsum"); + list.append("dolor"); + list.append("sit"); + list.append("amet"); + + ASSERT_EQ(list_get_item(&list, 0), &list[0]); + ASSERT_EQ(list_get_item(&list, 1), &list[1]); + ASSERT_EQ(list_get_item(&list, 2), &list[2]); + ASSERT_EQ(list_get_item(&list, 3), &list[3]); + ASSERT_EQ(list_get_item(&list, 4), &list[4]); +} + +TEST(ListFunctionsTest, Data) +{ + List list("", ""); + list.append("Lorem"); + list.append("ipsum"); + list.append("dolor"); + list.append("sit"); + list.append("amet"); + + ValueData *data = list_data(&list); + ASSERT_EQ(data, list.data()); +} + +TEST(ListFunctionsTest, SizePtr) +{ + List list("", ""); + list.append("Lorem"); + list.append("ipsum"); + list.append("dolor"); + list.append("sit"); + list.append("amet"); + + size_t *sizePtr = list_size_ptr(&list); + ASSERT_EQ(sizePtr, list.sizePtr()); +} + +TEST(ListFunctionsTest, AllocSizePtr) +{ + List list("", ""); + list.append("Lorem"); + list.append("ipsum"); + list.append("dolor"); + list.append("sit"); + list.append("amet"); + + const size_t *sizePtr = list_alloc_size_ptr(&list); + ASSERT_EQ(sizePtr, list.allocatedSizePtr()); +} + +TEST(ListFunctionsTest, Size) +{ + { + List list("", ""); + list.append("Lorem"); + list.append("ipsum"); + list.append("dolor"); + list.append("sit"); + list.append("amet"); + + ASSERT_EQ(list_size(&list), 5); + } + + { + List list("", ""); + list.append("1"); + list.append("2"); + + ASSERT_EQ(list_size(&list), 2); + } +} diff --git a/test/scratch_classes/list_test.cpp b/test/scratch_classes/list_test.cpp index a6574b5d..587996e8 100644 --- a/test/scratch_classes/list_test.cpp +++ b/test/scratch_classes/list_test.cpp @@ -40,6 +40,65 @@ TEST(ListTest, Monitor) ASSERT_EQ(list.monitor(), &monitor); } +TEST(ListTest, Data) +{ + List list("", ""); + list.append("Lorem"); + list.append("ipsum"); + list.append("dolor"); + list.append("sit"); + list.append("amet"); + + ValueData *data = list.data(); + ASSERT_EQ(&data[0], &list[0]); + ASSERT_EQ(&data[1], &list[1]); + ASSERT_EQ(&data[2], &list[2]); + ASSERT_EQ(&data[3], &list[3]); + ASSERT_EQ(&data[4], &list[4]); +} + +TEST(ListTest, SizePtr) +{ + List list("", "test list"); + ASSERT_TRUE(list.sizePtr()); + ASSERT_EQ(*list.sizePtr(), 0); + const size_t *ptr = list.sizePtr(); + ASSERT_TRUE(list.empty()); + + list.append("Lorem"); + list.append("ipsum"); + ASSERT_EQ(*list.sizePtr(), 2); + ASSERT_FALSE(list.empty()); + + list.append("dolor"); + ASSERT_EQ(*list.sizePtr(), 3); + ASSERT_EQ(list.sizePtr(), ptr); + ASSERT_FALSE(list.empty()); + + list.removeAt(0); + ASSERT_EQ(*list.sizePtr(), 2); + ASSERT_FALSE(list.empty()); + + *list.sizePtr() = 100; + ASSERT_EQ(*list.sizePtr(), 100); + ASSERT_EQ(list.size(), 100); +} + +TEST(ListTest, AllocatedSizePtr) +{ + List list("", "test list"); + ASSERT_TRUE(list.allocatedSizePtr()); + ASSERT_EQ(*list.allocatedSizePtr(), 0); + const size_t *ptr = list.allocatedSizePtr(); + + list.append("Lorem"); + list.append("ipsum"); + + ASSERT_GT(*list.allocatedSizePtr(), 0); + ASSERT_EQ(list.allocatedSizePtr(), ptr); + ASSERT_NE(list.allocatedSizePtr(), list.sizePtr()); +} + TEST(ListTest, Size) { List list("", "test list"); diff --git a/test/scratch_classes/target_test.cpp b/test/scratch_classes/target_test.cpp index f83f46fa..93ef3330 100644 --- a/test/scratch_classes/target_test.cpp +++ b/test/scratch_classes/target_test.cpp @@ -119,9 +119,17 @@ TEST(TargetTest, Lists) Target target; ASSERT_EQ(target.addList(l1), 0); + ASSERT_TRUE(target.listData()); + ASSERT_EQ(target.listData()[0], l1.get()); ASSERT_EQ(target.addList(l2), 1); ASSERT_EQ(target.addList(l3), 2); + ASSERT_EQ(target.listData()[0], l1.get()); + ASSERT_EQ(target.listData()[1], l2.get()); + ASSERT_EQ(target.listData()[2], l3.get()); ASSERT_EQ(target.addList(l2), 1); // add existing list + ASSERT_EQ(target.listData()[0], l1.get()); + ASSERT_EQ(target.listData()[1], l2.get()); + ASSERT_EQ(target.listData()[2], l3.get()); ASSERT_EQ(l1->target(), &target); ASSERT_EQ(l2->target(), &target);