Skip to content

Commit

Permalink
Add auto dtor call on scope exit (#287)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcauberer authored Jul 1, 2023
1 parent 8b3570b commit 89da880
Show file tree
Hide file tree
Showing 57 changed files with 828 additions and 486 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci-cpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ jobs:
uses: actions/cache@v3
with:
path: /home/runner/work/spice/llvm
key: llvm-16.0.5
key: llvm-16.0.6

- name: Setup LLVM
if: steps.cache-llvm.outputs.cache-hit != 'true'
run: |
cd ..
rm -rf llvm
git clone --depth 1 --branch llvmorg-16.0.5 https://github.com/llvm/llvm-project llvm
git clone --depth 1 --branch llvmorg-16.0.6 https://github.com/llvm/llvm-project llvm
mkdir ./llvm/build
cd ./llvm/build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS_RELEASE="-O2" -DLLVM_ENABLE_RTTI=ON -GNinja ../llvm
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ jobs:
uses: actions/cache@v3
with:
path: /home/runner/work/spice/llvm
key: llvm-16.0.5
key: llvm-16.0.6

- name: Setup LLVM
if: steps.cache-llvm.outputs.cache-hit != 'true'
run: |
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
cd ..
git clone --depth 1 --branch llvmorg-16.0.5 https://github.com/llvm/llvm-project llvm
git clone --depth 1 --branch llvmorg-16.0.6 https://github.com/llvm/llvm-project llvm
mkdir ./llvm/build
cd ./llvm/build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS_RELEASE="-O2" -DLLVM_ENABLE_RTTI=ON -GNinja ../llvm
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ jobs:
uses: actions/cache@v3
with:
path: /home/runner/work/spice/spice/llvm
key: llvm-16.0.5-linux-x64
key: llvm-16.0.6-linux-x64

- name: Setup LLVM
if: steps.cache-llvm.outputs.cache-hit != 'true'
run: |
git clone --depth 1 --branch llvmorg-16.0.5 https://github.com/llvm/llvm-project.git llvm
git clone --depth 1 --branch llvmorg-16.0.6 https://github.com/llvm/llvm-project.git llvm
mkdir ./llvm/build
cd ./llvm/build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS_RELEASE="-O2" -DLLVM_ENABLE_RTTI=ON -GNinja -Wno-dev -Wattributes ../llvm
Expand Down Expand Up @@ -116,12 +116,12 @@ jobs:
uses: actions/cache@v3
with:
path: D:/a/spice/spice/llvm
key: llvm-16.0.5-win-x64
key: llvm-16.0.6-win-x64

- name: Setup LLVM
if: steps.cache-llvm.outputs.cache-hit != 'true'
run: |
git clone --depth 1 --branch llvmorg-16.0.5 https://github.com/llvm/llvm-project.git llvm
git clone --depth 1 --branch llvmorg-16.0.6 https://github.com/llvm/llvm-project.git llvm
setx /M PATH "%PATH%;C:\mingw64\mingw64\bin"
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
echo "Adding MinGW to path done."
Expand Down Expand Up @@ -214,7 +214,7 @@ jobs:
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
with:
version: v1.18.2
version: v1.19.1
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .run/spice.run.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spice" type="CMakeRunConfiguration" factoryName="Application" PROGRAM_PARAMS="build -d -O0 ../../media/test-project/test.spice" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Spice" TARGET_NAME="spice" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="Spice" RUN_TARGET_NAME="spice">
<configuration default="false" name="spice" type="CMakeRunConfiguration" factoryName="Application" PROGRAM_PARAMS="run -d -O0 -ir ../../media/test-project/test.spice" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Spice" TARGET_NAME="spice" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="Spice" RUN_TARGET_NAME="spice">
<envs>
<env name="RUN_TESTS" value="OFF" />
<env name="SPICE_STD_DIR" value="$PROJECT_DIR$/std" />
Expand Down
2 changes: 1 addition & 1 deletion dev-setup.bat
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ echo done.

REM - Clone LLVM
echo [Step 2] Cloning LLVM (Could take a while) ...
git clone --depth 1 --branch llvmorg-16.0.5 https://github.com/llvm/llvm-project llvm
git clone --depth 1 --branch llvmorg-16.0.6 https://github.com/llvm/llvm-project llvm
echo done.

REM - Build LLVM
Expand Down
2 changes: 1 addition & 1 deletion dev-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ colored_echo "done."

# Clone LLVM
colored_echo "[Step 2] Cloning LLVM (Could take a while) ... "
git clone --depth 1 --branch llvmorg-16.0.5 https://github.com/llvm/llvm-project llvm
git clone --depth 1 --branch llvmorg-16.0.6 https://github.com/llvm/llvm-project llvm
colored_echo "done."

# Build LLVM
Expand Down
38 changes: 18 additions & 20 deletions media/test-project/test.spice
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import "std/math/fct";
type TestStruct struct {
long lng
String str
int i
}

f<int> main() {
printf("Abs (int): %d\n", abs(123));
printf("Abs (int): %d\n", abs(-137));
printf("Abs (short): %d\n", abs(56s));
printf("Abs (short): %d\n", abs(-3s));
printf("Abs (long): %d\n", abs(1234567890l));
printf("Abs (long): %d\n", abs(-987654321l));
printf("Abs (double): %f\n", abs(56.123));
printf("Abs (double): %f\n", abs(-348.12));

printf("Deg2Rad: %f\n", degToRad(420.0));
p TestStruct.dtor() {
printf("Dtor called\n");
}

printf("Sin (double): %f\n", sin(78.345));
printf("Sin (int): %f\n", sin(23));
printf("Sin (short): %f\n", sin(-68s));
printf("Sin (long): %f\n", sin(359l));
f<TestStruct> fct(int& ref) {
TestStruct ts = TestStruct{ 6l, String("test string"), ref };
return ts;
}

printf("Cos (double): %f\n", cos(78.345));
printf("Cos (int): %f\n", cos(23));
printf("Cos (short): %f\n", cos(-68s));
printf("Cos (long): %f\n", cos(359l));
f<int> main() {
int test = 987654;
const TestStruct res = fct(test);
printf("Long: %d\n", res.lng);
printf("String: %s\n", res.str.getRaw());
printf("Int: %d\n", res.i);
}
10 changes: 10 additions & 0 deletions src/ast/ASTNodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -727,9 +727,15 @@ class StmtLstNode : public ASTNode {

// Other methods
[[nodiscard]] bool returnsOnAllControlPaths(bool *overrideUnreachable) const override;
void resizeToNumberOfManifestations(size_t manifestationCount) override {
ASTNode::resizeToNumberOfManifestations(manifestationCount);
dtorFunctions.resize(manifestationCount, std::vector<std::pair<SymbolTableEntry *, Function *>>());
}

// Public members
size_t complexity = 0;
// Outer vector: manifestation index; inner vector: list of dtor functions
std::vector<std::vector<std::pair<SymbolTableEntry *, Function *>>> dtorFunctions;
};

// ========================================================= TypeLstNode =========================================================
Expand Down Expand Up @@ -1076,6 +1082,10 @@ class ReturnStmtNode : public ASTNode {

// Other methods
[[nodiscard]] bool returnsOnAllControlPaths(bool *overrideUnreachable) const override { return true; }
[[nodiscard]] StmtLstNode *getParentScopeNode() const {
assert(dynamic_cast<StmtLstNode *>(parent->parent) != nullptr);
return static_cast<StmtLstNode *>(parent->parent);
}

// Public members
bool hasReturnValue = false;
Expand Down
16 changes: 13 additions & 3 deletions src/irgenerator/GenStatements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@
namespace spice::compiler {

std::any IRGenerator::visitStmtLst(const StmtLstNode *node) {
// Generate instructions in the scope
for (const ASTNode *stmt : node->children) {
if (!stmt)
continue;
// Check if we can cancel generating instructions for this code branch
if (blockAlreadyTerminated || stmt->unreachable)
return nullptr;
break;
// Visit child
visit(stmt);
}

// Generate cleanup code of this scope, e.g. dtor calls for struct instances
generateScopeCleanup(node);

return nullptr;
}

Expand Down Expand Up @@ -92,13 +97,18 @@ std::any IRGenerator::visitReturnStmt(const ReturnStmtNode *node) {
}
}

// Generate scope cleanup code
generateScopeCleanup(node->getParentScopeNode());

// Set block to terminated
blockAlreadyTerminated = true;

// Create return instruction
if (returnValue != nullptr) { // Return value
if (returnValue != nullptr) {
// Return with value
builder.CreateRet(returnValue);
} else { // Return without value
} else {
// Return without value
builder.CreateRetVoid();
}

Expand Down
25 changes: 23 additions & 2 deletions src/irgenerator/GenValues.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,19 @@ std::any IRGenerator::visitFctCall(const FctCallNode *node) {
result = builder.CreateCall(callee, argValues);
}

// Attach address to anonymous symbol to keep track of deallocation
if (returnSType.is(TY_STRUCT) || data.isCtorCall()) {
SymbolTableEntry *anonymousSymbol = currentScope->symbolTable.lookupAnonymous(node->codeLoc);
if (anonymousSymbol != nullptr) {
if (data.isCtorCall()) {
anonymousSymbol->updateAddress(thisPtr);
} else {
llvm::Value *resultPtr = insertAlloca(result->getType());
anonymousSymbol->updateAddress(resultPtr);
}
}
}

// In case this is a constructor call, return the thisPtr as pointer
if (data.isCtorCall())
return ExprResult{.ptr = thisPtr};
Expand Down Expand Up @@ -331,6 +344,7 @@ std::any IRGenerator::visitStructInstantiation(const StructInstantiationNode *no
canBeConstant &= fieldValue.constant != nullptr;
}

ExprResult result;
if (canBeConstant) { // All field values are constants, so we can create a global constant struct instantiation
// Collect constants
std::vector<llvm::Constant *> constants;
Expand All @@ -345,7 +359,7 @@ std::any IRGenerator::visitStructInstantiation(const StructInstantiationNode *no
llvm::Constant *constantStruct = llvm::ConstantStruct::get(structType, constants);
llvm::Value *constantAddr = createGlobalConst(ANON_GLOBAL_STRUCT_NAME, constantStruct);

return ExprResult{.constant = constantStruct, .ptr = constantAddr};
result = {.constant = constantStruct, .ptr = constantAddr};
} else { // We have at least one non-immediate value, so we need to take normal struct instantiation as fallback
llvm::Value *structAddr = insertAlloca(structType);

Expand All @@ -361,8 +375,15 @@ std::any IRGenerator::visitStructInstantiation(const StructInstantiationNode *no
builder.CreateStore(itemValue, currentFieldAddress, storeVolatile);
}

return ExprResult{.ptr = structAddr};
result = {.ptr = structAddr};
}

// Attach address to anonymous symbol to keep track of deallocation
SymbolTableEntry *returnSymbol = currentScope->symbolTable.lookupAnonymous(node->codeLoc);
if (returnSymbol != nullptr)
returnSymbol->updateAddress(result.ptr);

return result;
}

std::any IRGenerator::visitDataType(const DataTypeNode *node) {
Expand Down
38 changes: 38 additions & 0 deletions src/irgenerator/IRGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <SourceFile.h>
#include <exception/IRError.h>
#include <symboltablebuilder/SymbolTableBuilder.h>

#include <llvm/BinaryFormat/Dwarf.h>
#include <llvm/IR/Verifier.h>
Expand Down Expand Up @@ -395,6 +396,43 @@ void IRGenerator::autoDeReferencePtr(llvm::Value *&ptr, SymbolType &symbolType,
}
}

void IRGenerator::generateScopeCleanup(const StmtLstNode *node) const {
// Do not clean up if the block is already terminated
if (blockAlreadyTerminated)
return;

// Call all dtor functions
for (auto [entry, dtor] : node->dtorFunctions.at(manIdx))
generateDtorCall(entry, dtor, node);
}

void IRGenerator::generateDtorCall(SymbolTableEntry *entry, Function *dtor, const StmtLstNode *node) const {
assert(dtor != nullptr);

// Retrieve metadata for the function
const std::string mangledName = dtor->getMangledName();
const bool isImported = dtor->entry->scope->isImportedBy(rootScope);
const bool isDownCall = !isImported && dtor->isDownCall(node);

// Function is not defined in the current module -> declare it
// This can happen when:
// 1) If this is an imported source file
// 2) This is a down-call to a function, which is defined later in the same file
if (isImported || isDownCall) {
llvm::FunctionType *fctType = llvm::FunctionType::get(builder.getVoidTy(), builder.getPtrTy(), false);
module->getOrInsertFunction(mangledName, fctType);
}

// Get callee function
llvm::Function *callee = module->getFunction(mangledName);
assert(callee != nullptr);

// Generate function call
llvm::Value *structPtr = entry->getAddress();
assert(structPtr != nullptr);
builder.CreateCall(callee, structPtr);
}

llvm::GlobalVariable *IRGenerator::createGlobalConst(const std::string &baseName, llvm::Constant *constant) {
// Get unused name
const std::string globalName = getUnusedGlobalName(baseName);
Expand Down
2 changes: 2 additions & 0 deletions src/irgenerator/IRGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ class IRGenerator : private CompilerPass, public ParallelizableASTVisitor {
bool isDecl);
llvm::Value *createShallowCopy(llvm::Value *oldAddress, llvm::Type *varType, llvm::Value *targetAddress,
const std::string &name = "", bool isVolatile = false);
void generateScopeCleanup(const StmtLstNode *node) const;
void generateDtorCall(SymbolTableEntry *entry, Function *dtor, const StmtLstNode *node) const;
void autoDeReferencePtr(llvm::Value *&ptr, SymbolType &symbolType, Scope *accessScope) const;
llvm::GlobalVariable *createGlobalConst(const std::string &baseName, llvm::Constant *constant);
llvm::Constant *createGlobalStringConst(const std::string &baseName, const std::string &value, const CodeLoc &codeLoc);
Expand Down
12 changes: 12 additions & 0 deletions src/model/Function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,4 +198,16 @@ bool Function::isFullySubstantiated() const { return hasSubstantiatedParams() &&
*/
const CodeLoc &Function::getDeclCodeLoc() const { return declNode->codeLoc; }

/**
* Check for a call node if it is a down call. This is always the case, when the function is called from within the same
* source file and at a code location above the definition
*
* @return Down call or not
*/
bool Function::isDownCall(const ASTNode *callNode) const {
const CodeLoc &callLoc = callNode->codeLoc;
const CodeLoc &defLoc = declNode->codeLoc;
return defLoc.line > callLoc.line || (defLoc.line == callLoc.line && defLoc.col > callLoc.col);
}

} // namespace spice::compiler
1 change: 1 addition & 0 deletions src/model/Function.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class Function {
[[nodiscard]] bool hasSubstantiatedGenerics() const;
[[nodiscard]] bool isFullySubstantiated() const;
[[nodiscard]] const CodeLoc &getDeclCodeLoc() const;
[[nodiscard]] bool isDownCall(const ASTNode *callNode) const;

// Public members
std::string name;
Expand Down
15 changes: 2 additions & 13 deletions src/symboltablebuilder/Scope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,19 +99,8 @@ std::vector<SymbolTableEntry *> Scope::getVarsGoingOutOfScope() { // NOLINT(misc
// Skip 'this' variables
if (name == THIS_VARIABLE_NAME)
continue;
// For dtor calls, only anonymous structs are relevant
if (entry.getType().is(TY_STRUCT) && !entry.isDead() && entry.name.starts_with("anon."))
varsGoingOutOfScope.push_back(&symbolTable.symbols.at(name));
}

// Collect all variables in the child scopes
for (const auto &[_, child] : children) {
const ScopeType scopeType = child->type;
// Exclude enum, global, struct and thread body (is a LLVM function) scopes
if (scopeType != SCOPE_ENUM && scopeType != SCOPE_GLOBAL && scopeType != SCOPE_STRUCT && scopeType != SCOPE_THREAD_BODY) {
std::vector childVars = child->getVarsGoingOutOfScope();
varsGoingOutOfScope.insert(varsGoingOutOfScope.end(), childVars.begin(), childVars.end());
}
// Found variable, that goes out of scope
varsGoingOutOfScope.push_back(&symbolTable.symbols.at(name));
}

return varsGoingOutOfScope;
Expand Down
1 change: 0 additions & 1 deletion src/symboltablebuilder/Scope.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ enum ScopeType {
SCOPE_WHILE_BODY,
SCOPE_FOR_BODY,
SCOPE_FOREACH_BODY,
SCOPE_THREAD_BODY,
SCOPE_UNSAFE_BODY,
SCOPE_ANONYMOUS_BLOCK_BODY
};
Expand Down
Loading

0 comments on commit 89da880

Please sign in to comment.