Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix NRVO for ternary expressions #711

Merged
merged 1 commit into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 47 additions & 47 deletions media/test-project/test.spice
Original file line number Diff line number Diff line change
@@ -1,50 +1,50 @@
import "std/data/map";
import "std/data/vector";

import "bootstrap/util/common-util";
import "bootstrap/source-file-intf";

type MockSourceFile struct : ISourceFile {
string fileName
unsigned long lineCount = 0l
}

p MockSourceFile.ctor(string fileName, unsigned long lineCount = 0l) {
this.fileName = fileName;
this.lineCount = lineCount;
}

f<unsigned long> MockSourceFile.getLineCount() {
return this.lineCount;
}

f<string> MockSourceFile.getFileName() {
return this.fileName;
}

f<int> main() {
Map<int, string> map;
assert map.getSize() == 0l;
assert map.isEmpty();
map.insert(1, "Hello");
assert map.getSize() == 1l;
assert !map.isEmpty();
map.insert(2, "World");
assert map.getSize() == 2l;
map.insert(3, "Foo");
assert map.getSize() == 3l;
map.insert(4, "Bar");
assert map.getSize() == 4l;
assert map.contains(1);
assert map.contains(2);
assert map.contains(3);
assert map.contains(4);
assert map.get(1) == "Hello";
assert map[2] == "World";
assert map.get(3) == "Foo";
assert map.get(4) == "Bar";
map.remove(2);
assert map.getSize() == 3l;
assert !map.contains(2);
assert !map.isEmpty();
map.remove(1);
assert map.getSize() == 2l;
assert !map.contains(1);
assert !map.isEmpty();
string& foo = map.get(3);
assert foo == "Foo";
foo = "Baz";
assert map[3] == "Baz";
Result<string> bar = map.getSafe(4);
assert bar.isOk();
assert bar.unwrap() == "Bar";
Result<string> baz = map.getSafe(5);
assert baz.isErr();
map.remove(3);
assert map.getSize() == 1l;
assert !map.contains(3);
assert !map.isEmpty();
map.remove(4);
assert map.getSize() == 0l;
assert !map.contains(4);
assert map.isEmpty();
printf("All assertions passed!\n");
// getLastFragment
String lastFragment = getLastFragment(String("this.is.a.test.haystack"), ".");
printf("%s", lastFragment);
assert lastFragment == "haystack";
lastFragment = getLastFragment(String(""), ";");
assert lastFragment == "";
lastFragment = getLastFragment(String("x-y-z-"), "-");
assert lastFragment == "";
// getCircularImportMessage
const MockSourceFile msf1 = MockSourceFile("file1.spice", 1l);
const MockSourceFile msf2 = MockSourceFile("file2.spice", 12l);
const MockSourceFile msf3 = MockSourceFile("file3.spice", 123l);
const MockSourceFile msf4 = MockSourceFile("file4.spice", 1234l);
Vector<const ISourceFile*> sourceFiles;
unsafe {
sourceFiles.pushBack((const ISourceFile*) &msf1);
sourceFiles.pushBack((const ISourceFile*) &msf2);
sourceFiles.pushBack((const ISourceFile*) &msf3);
sourceFiles.pushBack((const ISourceFile*) &msf4);
}
const String cim = getCircularImportMessage(sourceFiles);
printf("Circular error message:\n%s\n", cim);
const String versionInfo = buildVersionInfo();
printf("Version info:\n%s\n", versionInfo);
printf("All assertions passed!");
}
11 changes: 10 additions & 1 deletion src/irgenerator/GenExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,16 @@ std::any IRGenerator::visitTernaryExpr(const TernaryExprNode *node) {
resultValue = phiInst;
}

return LLVMExprResult{.value = resultValue};
// If we have an anonymous symbol for this ternary expr, make sure that it has an address to reference.
SymbolTableEntry *anonymousSymbol = currentScope->symbolTable.lookupAnonymous(node->codeLoc);
llvm::Value *resultPtr = nullptr;
if (anonymousSymbol != nullptr) {
resultPtr = insertAlloca(anonymousSymbol->getQualType().toLLVMType(sourceFile));
insertStore(resultValue, resultPtr);
anonymousSymbol->updateAddress(resultPtr);
}

return LLVMExprResult{.value = resultValue, .ptr = resultPtr, .entry = anonymousSymbol};
}

std::any IRGenerator::visitLogicalOrExpr(const LogicalOrExprNode *node) {
Expand Down
4 changes: 2 additions & 2 deletions src/symboltablebuilder/SymbolTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ SymbolTableEntry *SymbolTable::lookupAnonymous(const CodeLoc &codeLoc, size_t nu
}

/**
* Check if a capture exists in the current or any parent scope scope and return it if possible
* Check if a capture exists in the current or any parent scope and return it if possible
*
* @param name Name of the desired captured symbol
* @return Capture / nullptr if the capture was not found
Expand Down Expand Up @@ -237,7 +237,7 @@ Capture *SymbolTable::lookupCaptureStrict(const std::string &name) {
}

/**
* Set capturing for this scope to required.
* Set capturing for this scope required.
*/
void SymbolTable::setCapturingRequired() { capturingRequired = true; }

Expand Down
34 changes: 23 additions & 11 deletions src/typechecker/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -975,15 +975,15 @@ std::any TypeChecker::visitTernaryExpr(TernaryExprNode *node) {
return visit(node->condition);

// Visit condition
const QualType conditionType = std::any_cast<ExprResult>(visit(node->condition)).type;
HANDLE_UNRESOLVED_TYPE_ER(conditionType)
const QualType trueType = node->isShortened ? conditionType : std::any_cast<ExprResult>(visit(node->trueExpr)).type;
const auto condition = std::any_cast<ExprResult>(visit(node->condition));
HANDLE_UNRESOLVED_TYPE_ER(condition.type)
const auto [trueType, trueEntry] = node->isShortened ? condition : std::any_cast<ExprResult>(visit(node->trueExpr));
HANDLE_UNRESOLVED_TYPE_ER(trueType)
const QualType falseType = std::any_cast<ExprResult>(visit(node->falseExpr)).type;
const auto [falseType, falseEntry] = std::any_cast<ExprResult>(visit(node->falseExpr));
HANDLE_UNRESOLVED_TYPE_ER(falseType)

// Check if the condition evaluates to bool
if (!conditionType.is(TY_BOOL))
if (!condition.type.is(TY_BOOL))
SOFT_ERROR_ER(node->condition, OPERATOR_WRONG_DATA_TYPE, "Condition operand in ternary must be a bool")

// Check if trueType and falseType are matching
Expand All @@ -994,7 +994,22 @@ std::any TypeChecker::visitTernaryExpr(TernaryExprNode *node) {
"True and false operands in ternary must be of same data type. Got " + trueType.getName(true) + " and " +
falseType.getName(true))

return ExprResult{node->setEvaluatedSymbolType(trueType, manIdx)};
// If there is an anonymous symbol attached to left or right, remove it,
// since the result takes over the ownership of any destructible object.
const bool removeAnonymousSymbolTrueSide = trueEntry && trueEntry->anonymous;
if (removeAnonymousSymbolTrueSide)
currentScope->symbolTable.deleteAnonymous(trueEntry->name);
const bool removeAnonymousSymbolFalseSide = falseEntry && falseEntry->anonymous;
if (removeAnonymousSymbolFalseSide)
currentScope->symbolTable.deleteAnonymous(falseEntry->name);

// Create a new anonymous symbol for the result if required
const QualType& returnType = trueType;
SymbolTableEntry *anonymousSymbol = nullptr;
if (removeAnonymousSymbolTrueSide || removeAnonymousSymbolFalseSide)
anonymousSymbol = currentScope->symbolTable.insertAnonymous(returnType, node);

return ExprResult{node->setEvaluatedSymbolType(trueType, manIdx), anonymousSymbol};
}

std::any TypeChecker::visitLogicalOrExpr(LogicalOrExprNode *node) {
Expand Down Expand Up @@ -1745,10 +1760,8 @@ std::any TypeChecker::visitFctCall(FctCallNode *node) {
return ExprResult{node->setEvaluatedSymbolType(QualType(TY_UNRESOLVED), manIdx)};
assert(data.calleeParentScope != nullptr);

// Only ordinary function calls can be constructors
if (data.isCtorCall()) {
assert(data.thisType.is(TY_STRUCT));
}
// If the call is no ordinary call, it must be a constructor, which takes a struct as this type.
assert(data.isOrdinaryCall() || data.thisType.is(TY_STRUCT));
}

if (!data.isFctPtrCall()) {
Expand Down Expand Up @@ -1812,7 +1825,6 @@ std::any TypeChecker::visitFctCall(FctCallNode *node) {
"Could not find struct candidate for struct '" + signature + "'. Do the template types match?")
}
returnType = returnType.getWithBodyScope(spiceStruct->scope).replaceBaseType(returnBaseType);

returnType = mapImportedScopeTypeToLocalType(returnType.getBase().getBodyScope(), returnType);

// Add anonymous symbol to keep track of de-allocation
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
haystackCircular error message:
*-----*
| |
| file1.spice
| |
| file2.spice
| |
| file3.spice
| |
| file4.spice
| |
*-----*

Version info:
Spice version: 0.20.7 linux/amd64
Git hash: 1da7aaeeb4ea2f1132b1a1a94ce6dd3cd753c47c
LLVM version: 19.1.7
built by: GitHub Actions

(c) Marc Auberer 2021-2025
All assertions passed!
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import "std/data/vector";

import "bootstrap/util/common-util";
import "bootstrap/source-file-intf";

type MockSourceFile struct : ISourceFile {
string fileName
unsigned long lineCount = 0l
}

p MockSourceFile.ctor(string fileName, unsigned long lineCount = 0l) {
this.fileName = fileName;
this.lineCount = lineCount;
}

f<unsigned long> MockSourceFile.getLineCount() {
return this.lineCount;
}

f<string> MockSourceFile.getFileName() {
return this.fileName;
}

f<int> main() {
// getLastFragment
String lastFragment = getLastFragment(String("this.is.a.test.haystack"), ".");
printf("%s", lastFragment);
assert lastFragment == "haystack";
lastFragment = getLastFragment(String(""), ";");
assert lastFragment == "";
lastFragment = getLastFragment(String("x-y-z-"), "-");
assert lastFragment == "";
// getCircularImportMessage
const MockSourceFile msf1 = MockSourceFile("file1.spice", 1l);
const MockSourceFile msf2 = MockSourceFile("file2.spice", 12l);
const MockSourceFile msf3 = MockSourceFile("file3.spice", 123l);
const MockSourceFile msf4 = MockSourceFile("file4.spice", 1234l);
Vector<const ISourceFile*> sourceFiles;
unsafe {
sourceFiles.pushBack((const ISourceFile*) &msf1);
sourceFiles.pushBack((const ISourceFile*) &msf2);
sourceFiles.pushBack((const ISourceFile*) &msf3);
sourceFiles.pushBack((const ISourceFile*) &msf4);
}
const String cim = getCircularImportMessage(sourceFiles);
printf("Circular error message:\n%s\n", cim);
// buildVersionInfo
const String versionInfo = buildVersionInfo();
printf("Version info:\n%s\n", versionInfo);

printf("All assertions passed!");
}
Loading