Skip to content

Commit 8ee31a7

Browse files
authored
Merge pull request #653 from scratchcpp/refactor_type_analyzer
Refactor static type analysis
2 parents 3619dcc + f2de9fb commit 8ee31a7

34 files changed

+4104
-13165
lines changed

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
99
option(LIBSCRATCHCPP_BUILD_UNIT_TESTS "Build unit tests" ON)
1010
option(LIBSCRATCHCPP_NETWORK_SUPPORT "Support for downloading projects" ON)
1111
option(LIBSCRATCHCPP_PRINT_LLVM_IR "Print LLVM IR of compiled Scratch scripts (for debugging)" OFF)
12+
option(LIBSCRATCHCPP_ENABLE_CODE_ANALYZER "Analyze Scratch scripts to enable various optimizations" ON)
1213
option(LIBSCRATCHCPP_ENABLE_SANITIZER "Enable sanitizer to detect memory issues" OFF)
1314

1415
if (LIBSCRATCHCPP_ENABLE_SANITIZER)
@@ -26,6 +27,7 @@ install(TARGETS scratchcpp DESTINATION lib)
2627
target_sources(scratchcpp
2728
PUBLIC
2829
include/scratchcpp/global.h
30+
include/scratchcpp/enum_bitmask.h
2931
include/scratchcpp/project.h
3032
include/scratchcpp/scratchconfiguration.h
3133
include/scratchcpp/iengine.h
@@ -120,6 +122,10 @@ if(LIBSCRATCHCPP_PRINT_LLVM_IR)
120122
target_compile_definitions(scratchcpp PRIVATE PRINT_LLVM_IR)
121123
endif()
122124

125+
if(LIBSCRATCHCPP_ENABLE_CODE_ANALYZER)
126+
target_compile_definitions(scratchcpp PRIVATE ENABLE_CODE_ANALYZER)
127+
endif()
128+
123129
# Macros
124130
target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_LIBRARY)
125131
target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION="${PROJECT_VERSION}")

include/scratchcpp/compiler.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <vector>
77

88
#include "global.h"
9+
#include "enum_bitmask.h"
910
#include "spimpl.h"
1011

1112
namespace libscratchcpp
@@ -31,12 +32,12 @@ class LIBSCRATCHCPP_EXPORT Compiler
3132
public:
3233
enum class StaticType
3334
{
34-
Void,
35-
Number,
36-
Bool,
37-
String,
38-
Pointer,
39-
Unknown
35+
Void = 0,
36+
Number = 1 << 0,
37+
Bool = 1 << 1,
38+
String = 1 << 2,
39+
Pointer = 1 << 3,
40+
Unknown = Number | Bool | String
4041
};
4142

4243
enum class CodeType
@@ -161,4 +162,6 @@ class LIBSCRATCHCPP_EXPORT Compiler
161162
spimpl::unique_impl_ptr<CompilerPrivate> impl;
162163
};
163164

165+
constexpr bool enable_enum_bitmask(Compiler::StaticType);
166+
164167
} // namespace libscratchcpp

include/scratchcpp/enum_bitmask.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
#pragma once
4+
5+
#include <type_traits>
6+
7+
namespace libscratchcpp
8+
{
9+
10+
// https://andreasfertig.com/blog/2024/01/cpp20-concepts-applied
11+
// OR operator |
12+
template<typename T>
13+
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)
14+
{
15+
using underlying = std::underlying_type_t<T>;
16+
return static_cast<T>(static_cast<underlying>(lhs) | static_cast<underlying>(rhs));
17+
}
18+
19+
// OR assignment operator |=
20+
template<typename T>
21+
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)
22+
{
23+
using underlying = std::underlying_type_t<T>;
24+
lhs = static_cast<T>(static_cast<underlying>(lhs) | static_cast<underlying>(rhs));
25+
return lhs;
26+
}
27+
28+
// AND operator &
29+
template<typename T>
30+
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)
31+
{
32+
using underlying = std::underlying_type_t<T>;
33+
return static_cast<T>(static_cast<underlying>(lhs) & static_cast<underlying>(rhs));
34+
}
35+
36+
// AND assignment operator &=
37+
template<typename T>
38+
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)
39+
{
40+
using underlying = std::underlying_type_t<T>;
41+
lhs = static_cast<T>(static_cast<underlying>(lhs) & static_cast<underlying>(rhs));
42+
return lhs;
43+
}
44+
45+
// NOT operator ~
46+
template<typename T>
47+
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)
48+
{
49+
using underlying = std::underlying_type_t<T>;
50+
return static_cast<T>(~static_cast<underlying>(value));
51+
}
52+
53+
} // namespace libscratchcpp

src/engine/internal/llvm/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ target_sources(scratchcpp
22
PRIVATE
33
llvmbuildutils.cpp
44
llvmbuildutils.h
5+
llvmcodeanalyzer.cpp
6+
llvmcodeanalyzer.h
57
llvmcodebuilder.cpp
68
llvmcodebuilder.h
79
llvmregister.h
@@ -19,8 +21,6 @@ target_sources(scratchcpp
1921
llvmtypes.h
2022
llvmfunctions.cpp
2123
llvmfunctions.h
22-
llvmtypeanalyzer.cpp
23-
llvmtypeanalyzer.h
2424
llvmcompilercontext.cpp
2525
llvmcompilercontext.h
2626
llvmexecutablecode.cpp

src/engine/internal/llvm/instructions/control.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,12 @@ LLVMInstruction *Control::buildSelect(LLVMInstruction *ins)
7979
llvm::Value *trueValue;
8080
llvm::Value *falseValue;
8181

82-
if (type == Compiler::StaticType::Unknown) {
83-
trueValue = m_utils.createValue(arg2.second);
84-
falseValue = m_utils.createValue(arg3.second);
85-
} else {
82+
if (m_utils.isSingleType(type)) {
8683
trueValue = m_utils.castValue(arg2.second, type);
8784
falseValue = m_utils.castValue(arg3.second, type);
85+
} else {
86+
trueValue = m_utils.createValue(arg2.second);
87+
falseValue = m_utils.createValue(arg3.second);
8888
}
8989

9090
ins->functionReturnReg->value = m_builder.CreateSelect(cond, trueValue, falseValue);

src/engine/internal/llvm/instructions/functions.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,12 @@ LLVMInstruction *Functions::buildFunctionCall(LLVMInstruction *ins)
5454
llvm::Value *ret = m_builder.CreateCall(m_utils.functions().resolveFunction(ins->functionName, llvm::FunctionType::get(retType, types, false)), args);
5555

5656
if (ins->functionReturnReg) {
57-
if (ins->functionReturnReg->type() == Compiler::StaticType::Unknown) {
57+
if (m_utils.isSingleType(ins->functionReturnReg->type()))
58+
ins->functionReturnReg->value = ret;
59+
else {
5860
ins->functionReturnReg->value = m_utils.addAlloca(retType);
5961
m_builder.CreateStore(ret, ins->functionReturnReg->value);
60-
} else
61-
ins->functionReturnReg->value = ret;
62+
}
6263

6364
if (ins->functionReturnReg->type() == Compiler::StaticType::String)
6465
m_utils.freeStringLater(ins->functionReturnReg->value);

src/engine/internal/llvm/instructions/lists.cpp

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -63,21 +63,28 @@ ProcessResult Lists::process(LLVMInstruction *ins)
6363

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

7072
return ins->next;
7173
}
7274

7375
LLVMInstruction *Lists::buildRemoveListItem(LLVMInstruction *ins)
7476
{
77+
// No-op in empty lists
78+
if (ins->targetType == Compiler::StaticType::Void)
79+
return ins->next;
80+
7581
llvm::LLVMContext &llvmCtx = m_utils.llvmCtx();
7682
llvm::Function *function = m_utils.function();
7783

7884
assert(ins->args.size() == 1);
7985
const auto &arg = ins->args[0];
80-
LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);
86+
87+
LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);
8188

8289
// Range check
8390
llvm::Value *min = llvm::ConstantFP::get(llvmCtx, llvm::APFloat(0.0));
@@ -107,12 +114,7 @@ LLVMInstruction *Lists::buildAppendToList(LLVMInstruction *ins)
107114
assert(ins->args.size() == 1);
108115
const auto &arg = ins->args[0];
109116
Compiler::StaticType type = m_utils.optimizeRegisterType(arg.second);
110-
LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);
111-
112-
Compiler::StaticType listType = Compiler::StaticType::Unknown;
113-
114-
if (m_utils.warp())
115-
listType = m_utils.typeAnalyzer().listType(ins->workList, ins, Compiler::StaticType::Unknown, false);
117+
LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);
116118

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

133135
// Otherwise call appendEmpty()
134136
m_builder.SetInsertPoint(elseBlock);
135137
itemPtr = m_builder.CreateCall(m_utils.functions().resolve_list_append_empty(), listPtr.ptr);
136-
m_utils.createReusedValueStore(arg.second, itemPtr, type, listType);
138+
// NOTE: Items created using appendEmpty() are always numbers
139+
m_utils.createValueStore(arg.second, itemPtr, Compiler::StaticType::Number, type);
137140
m_builder.CreateBr(nextBlock);
138141

139142
m_builder.SetInsertPoint(nextBlock);
@@ -149,12 +152,7 @@ LLVMInstruction *Lists::buildInsertToList(LLVMInstruction *ins)
149152
const auto &indexArg = ins->args[0];
150153
const auto &valueArg = ins->args[1];
151154
Compiler::StaticType type = m_utils.optimizeRegisterType(valueArg.second);
152-
LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);
153-
154-
Compiler::StaticType listType = Compiler::StaticType::Unknown;
155-
156-
if (m_utils.warp())
157-
listType = m_utils.typeAnalyzer().listType(ins->workList, ins, Compiler::StaticType::Unknown, false);
155+
LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);
158156

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

175173
m_builder.CreateBr(nextBlock);
176174

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

181179
LLVMInstruction *Lists::buildListReplace(LLVMInstruction *ins)
182180
{
181+
// No-op in empty lists
182+
if (ins->targetType == Compiler::StaticType::Void)
183+
return ins->next;
184+
183185
llvm::LLVMContext &llvmCtx = m_utils.llvmCtx();
184186
llvm::Function *function = m_utils.function();
185187

186188
assert(ins->args.size() == 2);
187189
const auto &indexArg = ins->args[0];
188190
const auto &valueArg = ins->args[1];
189191
Compiler::StaticType type = m_utils.optimizeRegisterType(valueArg.second);
190-
LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);
191-
192-
Compiler::StaticType listType = Compiler::StaticType::Unknown;
192+
LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);
193193

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

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

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

229228
LLVMInstruction *Lists::buildGetListItem(LLVMInstruction *ins)
230229
{
230+
// Return empty string for empty lists
231+
if (ins->targetType == Compiler::StaticType::Void) {
232+
LLVMConstantRegister nullReg(Compiler::StaticType::String, "");
233+
ins->functionReturnReg->value = m_utils.createValue(static_cast<LLVMRegister *>(&nullReg));
234+
return ins->next;
235+
}
236+
231237
assert(ins->args.size() == 1);
232238
const auto &arg = ins->args[0];
233-
LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);
239+
LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);
234240

235-
Compiler::StaticType listType = Compiler::StaticType::Unknown;
236-
237-
if (m_utils.warp())
238-
listType = m_utils.typeAnalyzer().listType(ins->workList, ins, Compiler::StaticType::Unknown, false);
241+
Compiler::StaticType listType = ins->functionReturnReg->type();
239242

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

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

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

253255
return ins->next;
254256
}
255257

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

@@ -267,12 +269,9 @@ LLVMInstruction *Lists::buildGetListItemIndex(LLVMInstruction *ins)
267269
{
268270
assert(ins->args.size() == 1);
269271
const auto &arg = ins->args[0];
270-
LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);
271-
272-
Compiler::StaticType listType = Compiler::StaticType::Unknown;
272+
LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);
273273

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

277276
ins->functionReturnReg->value = m_builder.CreateSIToFP(m_utils.getListItemIndex(listPtr, listType, arg.second), m_builder.getDoubleTy());
278277
return ins->next;
@@ -282,12 +281,9 @@ LLVMInstruction *Lists::buildListContainsItem(LLVMInstruction *ins)
282281
{
283282
assert(ins->args.size() == 1);
284283
const auto &arg = ins->args[0];
285-
LLVMListPtr &listPtr = m_utils.listPtr(ins->workList);
286-
287-
Compiler::StaticType listType = Compiler::StaticType::Unknown;
284+
LLVMListPtr &listPtr = m_utils.listPtr(ins->targetList);
288285

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

292288
llvm::Value *index = m_utils.getListItemIndex(listPtr, listType, arg.second);
293289
ins->functionReturnReg->value = m_builder.CreateICmpSGT(index, llvm::ConstantInt::get(m_builder.getInt64Ty(), -1, true));

src/engine/internal/llvm/instructions/procedures.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ LLVMInstruction *Procedures::buildCallProcedure(LLVMInstruction *ins)
5959

6060
// Add procedure args
6161
for (const auto &arg : ins->args) {
62-
if (arg.first == Compiler::StaticType::Unknown)
63-
args.push_back(m_utils.createValue(arg.second));
64-
else
62+
if (m_utils.isSingleType(arg.first))
6563
args.push_back(m_utils.castValue(arg.second, arg.first));
64+
else
65+
args.push_back(m_utils.createValue(arg.second));
6666
}
6767

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

0 commit comments

Comments
 (0)