Skip to content

Commit

Permalink
Add design document for name mangling, fix bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
marcauberer committed Jan 20, 2023
1 parent be7a1ee commit b24f52e
Show file tree
Hide file tree
Showing 16 changed files with 206 additions and 86 deletions.
59 changes: 59 additions & 0 deletions media/specs/name-mangling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Technical Specification for Name Mangling in Spice

With Spice having support for function overloading, default parameter values, generics, etc., it is required to identify
functions, structs and types uniquely. For this purpose, Spice offers a flexible name mangling scheme for names on ABI level.

## Function mangling

### Components

- Function name
- Function type (function: `f`, procedure `p`, method function `mf` or method procedure `mp`)
- This type (struct type for methods, default `void`)
- Return type (`void` for procedures)
- Template types (separated by `_`, default: empty)
- Param types (separated by `_`, default: empty)

### Mangled name

Here is the scheme, how Spice mangles function/procedure/method names:

`_<function-type>__<this-type>__<return-type>[__<template-types>]__<function-name>[__<param-types>]`

**Example:**

Mangled name: `_mp__Vector__void__int__pushBack__int`

### Exception: main function

As the `main` function is exposed to the operating system, it is the only Spice function, where the mangling rules do not apply.
It's name stays the same on the ABI level.

### Implementation

To see the implementation for function name mangling, have a look here:
[Function::getMangledName](../../src/model/Function.cpp#:~:text=Function::getMangledName)


## Struct mangling

### Components

- Struct name
- Field types (separated by `_`, default: empty)
- Template types (separated by `_`, default: empty)

### Mangled name

Here is the scheme, how Spice mangles struct names:

`_s[__<template-types>]__<struct-name>[__field-types]`

**Example:**

`_s__int_string__Pair__int_string`

### Implementation

To see the implementation for struct name mangling, have a look here:
[Struct::gerMangledName](../../src/model/Struct.cpp#:~:text=Struct::getMangledName)
40 changes: 33 additions & 7 deletions media/test-project/os-test.spice
Original file line number Diff line number Diff line change
@@ -1,15 +1,41 @@
import "std/data/vector";
import "std/data/pair";
type T int|long;

type TestStruct<T> struct {
T f1
unsigned long length
}

p TestStruct.ctor(const unsigned long initialLength) {
this.length = initialLength;
}

p TestStruct.printLength() {
printf("%d\n", this.length);
}

type Alias alias TestStruct<long>;

f<int> main() {
Vector<Pair<int, string>> pairVector = Vector<Pair<int, string>>();
/*pairVector.pushBack(Pair<int, string>(0, "Hello"));
pairVector.pushBack(Pair<int, string>(1, "World"));
Alias a = Alias{12345l, 54321l};
a.printLength();
dyn b = Alias(12l);
b.printLength();
}

/*type T dyn;

type Vector<T> struct {
T data
}

Pair<int, string> p1 = pairVector.get(1);
printf("Hello %s!\n", p1.getSecond());*/
p Vector.ctor() {
this.data = 12;
}

f<int> main() {
Vector<int> intVec = Vector<int>();
}*/

/*type T string|char;

type TestStruct<T> struct {
Expand Down
20 changes: 10 additions & 10 deletions src/model/Function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ std::string Function::getMangledName() const {
if (!returnType.is(TY_DYN))
returnTyStr = returnType.getName(false, true);

// Template type string
std::stringstream templateTyStr;
for (size_t i = 0; i < templateTypes.size(); i++) {
if (i != 0)
templateTyStr << "_";
templateTyStr << templateTypes.at(i).getName(false, true);
}

// Param type string
std::stringstream paramTyStr;
for (size_t i = 0; i < paramList.size(); i++) {
Expand All @@ -65,14 +73,6 @@ std::string Function::getMangledName() const {
paramTyStr << "?";
}

// Template type string
std::stringstream templateTyStr;
for (size_t i = 0; i < templateTypes.size(); i++) {
if (i != 0)
templateTyStr << "_";
templateTyStr << templateTypes.at(i).getName(false, true);
}

// Construct mangled name
std::stringstream mangledName;
mangledName << "_" << functionTyStr << "__" << thisTyStr << "__" << returnTyStr;
Expand All @@ -92,10 +92,10 @@ std::string Function::getMangledName() const {
*
* @return String representation as function signature
*/
std::string Function::getSignature() const {
std::string Function::getSignature(bool withThisType /*=true*/) const {
// Build this type string
std::stringstream thisTyStr;
if (!thisType.is(TY_DYN)) {
if (withThisType && !thisType.is(TY_DYN)) {
thisTyStr << thisType.getBaseType().getSubType();
const std::vector<SymbolType> &thisTemplateTypes = thisType.getTemplateTypes();
if (!thisTemplateTypes.empty()) {
Expand Down
2 changes: 1 addition & 1 deletion src/model/Function.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class Function {
// Public methods
[[nodiscard]] std::vector<SymbolType> getParamTypes() const;
[[nodiscard]] std::string getMangledName() const;
[[nodiscard]] std::string getSignature() const;
[[nodiscard]] std::string getSignature(bool withThisType = true) const;
[[nodiscard]] inline bool isMethod() const { return !thisType.is(TY_DYN); }
[[nodiscard]] inline bool isFunction() const { return !isMethod() && !returnType.is(TY_DYN); }
[[nodiscard]] inline bool isProcedure() const { return !isMethod() && returnType.is(TY_DYN); }
Expand Down
24 changes: 21 additions & 3 deletions src/symboltablebuilder/Scope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,15 @@ void Scope::renameChildScope(const std::string &oldName, const std::string &newN
*/
void Scope::copyChildScope(const std::string &oldName, const std::string &newName) {
assert(children.contains(oldName) && !children.contains(newName));
// Create copy
const Scope *origChildScope = children.at(oldName);
auto newChildBlock = new Scope(*origChildScope);

// Add newChildBlock as parent of all children for tracking de-allocation
for (const auto &child : newChildBlock->children)
child.second->parents.push_back(newChildBlock);

// Insert the new child scope as child to the current scope
children.insert({newName, newChildBlock});
}

Expand Down Expand Up @@ -217,16 +221,28 @@ void Scope::collectWarnings(std::vector<CompilerWarning> &warnings) const { // N

switch (entry.getType().getSuperType()) {
case TY_FUNCTION: {
// Skip generic function entries
if (!entry.getType().getTemplateTypes().empty())
continue;

warningType = UNUSED_FUNCTION;
warningMessage = "The function '" + entry.declNode->getFctManifestations()->front()->getSignature() + "' is unused";
warningMessage = "The function '" + key + "' is unused";
break;
}
case TY_PROCEDURE: {
// Skip generic procedure entries
if (!entry.getType().getTemplateTypes().empty())
continue;

warningType = UNUSED_PROCEDURE;
warningMessage = "The procedure '" + entry.declNode->getFctManifestations()->front()->getSignature() + "' is unused";
warningMessage = "The procedure '" + key + "' is unused";
break;
}
case TY_STRUCT: {
// Skip generic struct entries
if (!entry.getType().getTemplateTypes().empty())
continue;

if (entry.scope->type == SCOPE_GLOBAL) {
warningType = UNUSED_STRUCT;
warningMessage = "The struct '" + entry.name + "' is unused";
Expand Down Expand Up @@ -283,9 +299,11 @@ void Scope::collectWarnings(std::vector<CompilerWarning> &warnings) const { // N
// Add warning
warnings.emplace_back(entry.getDeclCodeLoc(), warningType, warningMessage);
}

// Visit children
for (const auto &child : children)
child.second->collectWarnings(warnings);
if (!child.second->isGenericScope)
child.second->collectWarnings(warnings);
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/symboltablebuilder/Scope.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class Scope {
const ScopeType type;
SymbolTable symbolTable = SymbolTable(parent == nullptr ? nullptr : &parent->symbolTable, this);
const CodeLoc *codeLoc = nullptr;
bool isGenericScope = false;

private:
// Private members
Expand Down
2 changes: 1 addition & 1 deletion src/symboltablebuilder/SymbolTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ SymbolTableEntry *SymbolTable::insertAnonymous(const SymbolType &type, ASTNode *
* @param originalName Original symbol name
* @param newName New symbol name
*/
void SymbolTable::copy(const std::string &originalName, const std::string &newName) {
void SymbolTable::copySymbol(const std::string &originalName, const std::string &newName) {
SymbolTableEntry *entryToCopy = lookupStrict(originalName);
assert(entryToCopy != nullptr);
symbols.insert({newName, *entryToCopy});
Expand Down
2 changes: 1 addition & 1 deletion src/symboltablebuilder/SymbolTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class SymbolTable {
// Public methods
SymbolTableEntry *insert(const std::string &name, const SymbolSpecifiers &specifiers, ASTNode *declNode);
SymbolTableEntry *insertAnonymous(const SymbolType &type, ASTNode *declNode);
void copy(const std::string &originalName, const std::string &newName);
void copySymbol(const std::string &originalName, const std::string &newName);
SymbolTableEntry *lookup(const std::string &symbolName);
SymbolTableEntry *lookupStrict(const std::string &symbolName);
SymbolTableEntry *lookupByIndex(unsigned int orderIndex);
Expand Down
4 changes: 4 additions & 0 deletions src/symboltablebuilder/SymbolTableBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ std::any SymbolTableBuilder::visitMainFctDef(MainFctDefNode *node) {

// Create scope for main function body
node->fctScope = currentScope = rootScope->createChildScope(node->getScopeId(), SCOPE_FUNC_PROC_BODY, &node->codeLoc);
currentScope->isGenericScope = false;

// Declare variable for the return value in the function scope
SymbolTableEntry *resultVarEntry = node->fctScope->insert(RETURN_VARIABLE_NAME, SymbolSpecifiers::of(TY_INT), node);
Expand Down Expand Up @@ -80,6 +81,7 @@ std::any SymbolTableBuilder::visitFctDef(FctDefNode *node) {

// Create scope for the function
node->fctScope = currentScope = currentScope->createChildScope(node->getScopeId(), SCOPE_FUNC_PROC_BODY, &node->codeLoc);
currentScope->isGenericScope = node->hasTemplateTypes;

// Create symbol for 'this' variable
if (node->isMethod) {
Expand Down Expand Up @@ -141,6 +143,7 @@ std::any SymbolTableBuilder::visitProcDef(ProcDefNode *node) {

// Create scope for the procedure
node->procScope = currentScope = currentScope->createChildScope(node->getScopeId(), SCOPE_FUNC_PROC_BODY, &node->codeLoc);
currentScope->isGenericScope = node->hasTemplateTypes;

// Create symbol for 'this' variable
if (node->isMethod) {
Expand Down Expand Up @@ -182,6 +185,7 @@ std::any SymbolTableBuilder::visitStructDef(StructDefNode *node) {
// Create scope for the struct
node->structScope = currentScope =
rootScope->createChildScope(STRUCT_SCOPE_PREFIX + node->structName, SCOPE_STRUCT, &node->codeLoc);
currentScope->isGenericScope = node->isGeneric;

// Visit struct fields
for (FieldNode *field : node->fields())
Expand Down
25 changes: 19 additions & 6 deletions src/typechecker/FunctionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,11 @@ Function *FunctionManager::matchFunction(Scope *matchScope, const std::string &r
matchScope = substantiatedStructBodyScope;
}

// We found a match! -> Check if it needs to be substantiated
// We found a match! -> Set the actual candidate and its entry to used
candidate.used = true;
candidate.entry->used = true;

// Check if the function is generic needs to be substantiated
if (presetFunction.templateTypes.empty()) {
assert(matchScope->functions.contains(defCodeLocStr) && matchScope->functions.at(defCodeLocStr).contains(mangledName));
matches.push_back(&matchScope->functions.at(defCodeLocStr).at(mangledName));
Expand Down Expand Up @@ -211,17 +215,25 @@ Function *FunctionManager::matchFunction(Scope *matchScope, const std::string &r
substantiatedFunction->genericSubstantiation = true;
substantiatedFunction->declNode->getFctManifestations()->push_back(substantiatedFunction);

// Copy function entry
const std::string newSignature = substantiatedFunction->getSignature();
matchScope->symbolTable.copySymbol(presetFunction.entry->name, newSignature);
matchScope->lookupStrict(presetFunction.entry->name)->used = true;
candidate.entry = matchScope->lookupStrict(newSignature);
candidate.entry->used = true;

// Copy function scope
matchScope->copyChildScope(presetFunction.getSignature(), substantiatedFunction->getSignature());
matchScope->copyChildScope(presetFunction.getSignature(false), newSignature);
Scope *childScope = matchScope->getChildScope(newSignature);
childScope->isGenericScope = false;

// Insert symbols for generic type names with concrete types into the child block
Scope *childBlock = matchScope->getChildScope(substantiatedFunction->getSignature());
for (const auto &[typeName, concreteType] : candidate.typeMapping)
childBlock->insertGenericType(typeName, GenericType(concreteType));
childScope->insertGenericType(typeName, GenericType(concreteType));

// Substantiate the 'this' symbol table entry in the new function scope
if (presetFunction.isMethod() && !presetFunction.templateTypes.empty()) {
SymbolTableEntry *thisEntry = childBlock->lookupStrict(THIS_VARIABLE_NAME);
SymbolTableEntry *thisEntry = childScope->lookupStrict(THIS_VARIABLE_NAME);
assert(thisEntry != nullptr);
thisEntry->updateType(requestedThisType.toPointer(callNode), /*overwriteExistingType=*/true);
}
Expand Down Expand Up @@ -357,7 +369,8 @@ bool FunctionManager::matchArgTypes(Function &candidate, const std::vector<Symbo
return false;

// If the already known and the given type match, accept the type straight away and move to the next param
candidate.paramList.at(i) = Param{knownConcreteType, false};
SymbolType newParamType = originalParamType.replaceBaseType(knownConcreteType);
candidate.paramList.at(i) = Param{newParamType, false};
continue;
}

Expand Down
2 changes: 1 addition & 1 deletion src/typechecker/FunctionManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class FunctionManager {
[[nodiscard]] static bool matchArgTypes(Function &candidate, const std::vector<SymbolType> &requestedParamTypes,
TypeMapping &typeMapping);
[[nodiscard]] static const GenericType *getGenericTypeOfCandidateByName(const Function &candidate,
const std::string &templateTypeName);
const std::string &templateTypeName);
};

} // namespace spice::compiler
18 changes: 15 additions & 3 deletions src/typechecker/StructManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@ Struct *StructManager::matchStruct(Scope *matchScope, const std::string &request
// Map field types from generic to concrete
substantiateFieldTypes(candidate, candidate.typeMapping);

// We found a match! -> Check if it needs to be substantiated
// We found a match! -> Set the actual candidate and its entry to used
candidate.used = true;
candidate.entry->used = true;

// Check if it needs to be substantiated
if (presetStruct.templateTypes.empty()) {
assert(matchScope->structs.contains(defCodeLocStr) && matchScope->structs.at(defCodeLocStr).contains(mangledName));
matches.push_back(&matchScope->structs.at(defCodeLocStr).at(mangledName));
Expand All @@ -101,12 +105,20 @@ Struct *StructManager::matchStruct(Scope *matchScope, const std::string &request
Struct *substantiatedStruct = insertSubstantiation(matchScope, candidate, presetStruct.declNode);
substantiatedStruct->genericSubstantiation = true;
substantiatedStruct->declNode->getStructManifestations()->push_back(substantiatedStruct);

// Copy function entry
const std::string newSignature = substantiatedStruct->getSignature();
matchScope->symbolTable.copySymbol(candidate.name, newSignature);
matchScope->lookupStrict(candidate.name)->used = true;
candidate.entry = matchScope->lookupStrict(newSignature);

// Copy struct scope
const std::string newScopeName = STRUCT_SCOPE_PREFIX + substantiatedStruct->getSignature();
const std::string newScopeName = STRUCT_SCOPE_PREFIX + newSignature;
matchScope->copyChildScope(STRUCT_SCOPE_PREFIX + presetStruct.name, newScopeName);
substantiatedStruct->structScope = matchScope->getChildScope(newScopeName);
substantiatedStruct->structScope->isGenericScope = false;

// Replace field types with concrete template types
substantiatedStruct->structScope = matchScope->getChildScope(newScopeName);
assert(substantiatedStruct->structScope != nullptr);
for (size_t i = 0; i < candidate.fieldTypes.size(); i++) {
// Replace field type with concrete template type
Expand Down

0 comments on commit b24f52e

Please sign in to comment.