Skip to content

Commit

Permalink
Detect memory leaks (#288)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcauberer authored Jul 9, 2023
1 parent 4c72099 commit bfdf49b
Show file tree
Hide file tree
Showing 190 changed files with 3,514 additions and 3,097 deletions.
15 changes: 14 additions & 1 deletion .github/workflows/ci-cpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ jobs:
- name: Setup Ninja
run: sudo apt-get install ninja-build

- name: Setup Valgrind
if: github.event_name == 'pull_request'
run: sudo apt-get install valgrind

- name: Setup Mold
uses: rui314/setup-mold@v1

Expand Down Expand Up @@ -73,16 +77,25 @@ jobs:
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
mkdir ./bin
cd ./bin
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DSPICE_BUILT_BY="ghactions" -DSPICE_LINK_STATIC=OFF -DSPICE_RUN_COVERAGE=ON -GNinja -Wattributes ..
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DSPICE_BUILT_BY="ghactions" -DSPICE_LINK_STATIC=OFF -DSPICE_RUN_COVERAGE=ON -GNinja -Wattributes ..
cmake --build . --target spicetest -j$(nproc)
- name: Run Test target
# if: github.event_name != 'pull_request'
env:
SPICE_STD_DIR: /home/runner/work/spice/spice/std
run: |
cd ./bin/test
./spicetest --skip-github-tests
# - name: Run Test target with Valgrind
# if: github.event_name == 'pull_request'
# env:
# SPICE_STD_DIR: /home/runner/work/spice/spice/std
# run: |
# cd ./bin/test
# valgrind -q --leak-check=full ./spicetest --skip-github-tests --leak-detection

- name: Generate coverage report
run: |
cd ./bin
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ jobs:
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
with:
version: v1.19.0
version: v1.19.2
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="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">
<configuration default="false" name="spice" type="CMakeRunConfiguration" factoryName="Application" PROGRAM_PARAMS="build -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.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ colored_echo() {
colored_echo "[Step 1] Installing dependencies via Linux packages (Could take a while) ... "
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
sudo apt update -y
sudo apt-get install -y cmake make ninja-build uuid-dev openjdk-11-jre-headless
sudo apt-get install -y cmake make ninja-build valgrind ccache uuid-dev openjdk-11-jre-headless
colored_echo "done."

# Clone LLVM
Expand Down
3 changes: 2 additions & 1 deletion docs/docs/language/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ f<int> test(long input) {

### Available attributes

- `core.compiler.mangleName: bool`: Disable name mangling for the annotated function
- `core.compiler.mangle: bool`: Disable name mangling for the annotated function
- `core.compiler.mangledName: string`: Set the mangled name for the annotated function
- `core.linker.dll: bool`: Enable linkage as dll (only relevant for Windows)
63 changes: 33 additions & 30 deletions media/specs/name-mangling.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,61 @@

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.
This mangling scheme is oriented on the Itanium C++ ABI mangling scheme, but is not compatible with it completely.

## 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 functions/procedures:

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

`_<function-type>__<this-type>__<return-type>[__<template-types>]__<function-name>[__<param-types>]`
`_Z[N<this-type-length><this-type>][I<template-type-list>]<function-name-length><function-name>[]`

**Example:**

Mangled name: `_mp__Vector__void__int__pushBack__int`
Mangled name: `_Z10isRawEqualPcPc`

### Exception: main function
This is the mangled name for the following function signature: `f<bool> isRawEqual(string lhs, string rhs)`

### 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)
[Function::getMangledName](../../src/irgenerator/NameMangling.cpp#:~:text=NameMangling::mangleFunction)


## 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 structs:

Here is the scheme, how Spice mangles struct names:

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

**Example:**

`_s__int_string__Pair__int_string`
`struct.Pair`

### Implementation

To see the implementation for struct name mangling, have a look here:
[Struct::getMangledName](../../src/model/Struct.cpp#:~:text=Struct::getMangledName)
[Struct::getMangledName](../../src/irgenerator/NameMangling.cpp#:~:text=NameMangling::mangleStruct)

## Type mangling
### Mangled name
Here is the scheme, how Spice mangles types:

- Pointer (`*`) / Array with unknown size (`[]`): `P`
- Reference (`&`): `R`
- Array (`[5]`): `A<array-length>`
- Double: `d`
- Int: `i`
- Unsigned Int: `j`
- Short: `s`
- Unsigned Short: `t`
- Long: `l`
- Unsigned Long: `m`
- Byte: `a`
- Unsigned Byte / unsigned char: `h`
- Char: `c`
- String / char*: `Pc`
- Bool: `b`
- Struct / Interface / Enum: `<struct-name-length><struct-name>`
- Function: `PF[<this-type>|v][<param-types>]E`
118 changes: 113 additions & 5 deletions media/test-project/test.spice
Original file line number Diff line number Diff line change
@@ -1,12 +1,120 @@
type TestStruct struct {
import "std/iterator/number-iterator";

f<int> main() {
// Create iterator with range convinience helper
NumberIterator<int> itInt = range(1, 10);

// Test functionality with int
assert itInt.isValid();
assert itInt.get() == 1;
itInt.next();
assert itInt.get() == 2;
itInt += 3;
assert itInt.get() == 5;
assert itInt.isValid();
itInt -= 2;
assert itInt.get() == 3;
itInt.next();
dyn idxAndValueInt = itInt.getIdx();
assert idxAndValueInt.getFirst() == 3l;
assert idxAndValueInt.getSecond() == 4;
itInt++;
assert itInt.get() == 5;
itInt--;
assert itInt.get() == 4;
assert itInt.isValid();
itInt += 6;
assert itInt.get() == 10;
itInt++;
assert !itInt.isValid();

// Test functionality with long
NumberIterator<long> itLong = range(6l, 45l);
assert itLong.isValid();
assert itLong.get() == 6l;
itLong.next();
assert itLong.get() == 7l;
itLong += 3l;
assert itLong.get() == 10l;
assert itLong.get() == 10l;
itLong -= 2l;
assert itLong.get() == 8l;
itLong += 8l;
assert itLong.get() == 16l;
itLong.next();
dyn idxAndValueLong = itLong.getIdx();
assert idxAndValueLong.getFirst() == 11l;
assert idxAndValueLong.getSecond() == 17l;
itLong++;
assert itLong.get() == 18l;
itLong--;
assert itLong.get() == 17l;
assert itLong.isValid();
itLong += 28l;
assert itLong.get() == 45;
itLong++;
assert !itLong.isValid();

printf("All assertions passed!");
}

/*import "std/os/thread";

f<int> fib(int n) {
if n <= 2 { return 1; }
return fib(n - 1) + fib(n - 2);
}

p calcFib30() {
int result = fib(30);
printf("Thread returned with result: %d\n", result);
}

f<int> main() {
int threadCount = 8;
Thread[8] threads = {};
for unsigned int i = 0; i < threadCount; i++ {
threads[i] = Thread(calcFib30);
}
printf("Started all threads. Waiting for results ...\n");
for unsigned int i = 0; i < threadCount; i++ {
Thread& thread = threads[i];
thread.join();
}
printf("Program finished");
}*/

/*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() {
Alias a = Alias{12345l, (unsigned long) 54321l};
a.printLength();
dyn b = Alias(12l);
b.printLength();
}*/

/*type TestStruct struct {
long lng
String str
int i
}

p TestStruct.dtor() {
printf("Dtor called\n");
}
p TestStruct.dtor() {}

f<TestStruct> fct(int& ref) {
TestStruct ts = TestStruct{ 6l, String("test string"), ref };
Expand All @@ -19,4 +127,4 @@ f<int> main() {
printf("Long: %d\n", res.lng);
printf("String: %s\n", res.str.getRaw());
printf("Int: %d\n", res.i);
}
}*/
12 changes: 9 additions & 3 deletions media/test-project/test2.spice
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
public f<int> fibo(int n) {
if n <= 1 { return n; }
return fibo(n - 1) + fibo(n - 2);
type A dyn;

public p printFormat<A>(A element) {
printf("Sizeof output: %d\n", sizeof(element));
}

public f<A*> getAInc<A>(A* number) {
(*number)++;
return number;
}
7 changes: 6 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ set(SOURCES
irgenerator/OpRuleConversionManager.h
irgenerator/DebugInfoGenerator.cpp
irgenerator/DebugInfoGenerator.h
irgenerator/NameMangling.cpp
irgenerator/NameMangling.h
# IR optimizer
iroptimizer/IROptimizer.cpp
iroptimizer/IROptimizer.h
Expand Down Expand Up @@ -156,4 +158,7 @@ include_directories(../lib/antlr4/runtime/Cpp/runtime/src)
include_directories(${ANTLR_Spice_OUTPUT_DIR})

target_link_libraries(spice antlr4_static ${LLVM_LIBS})
add_library(spicelib STATIC ${SOURCES} ${ANTLR_Spice_CXX_OUTPUTS})
add_library(spicelib STATIC ${SOURCES} ${ANTLR_Spice_CXX_OUTPUTS})

# Add leak check target
add_custom_target(spiceleakcheck COMMAND valgrind --leak-check=full --error-exitcode=1 ./spice DEPENDS spice)
29 changes: 15 additions & 14 deletions src/Spice.g4
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ entry: (mainFunctionDef | functionDef | procedureDef | structDef | interfaceDef
mainFunctionDef: fctAttr? F LESS TYPE_INT GREATER MAIN LPAREN paramLst? RPAREN LBRACE stmtLst RBRACE;
functionDef: fctAttr? specifierLst? F LESS dataType GREATER fctName (LESS typeLst GREATER)? LPAREN paramLst? RPAREN LBRACE stmtLst RBRACE;
procedureDef: fctAttr? specifierLst? P fctName (LESS typeLst GREATER)? LPAREN paramLst? RPAREN LBRACE stmtLst RBRACE;
fctName: (IDENTIFIER DOT)? IDENTIFIER | OPERATOR overloadableOp;
structDef: specifierLst? TYPE IDENTIFIER (LESS typeLst GREATER)? STRUCT (COLON typeLst)? LBRACE field* RBRACE;
interfaceDef: specifierLst? TYPE IDENTIFIER (LESS typeLst GREATER)? INTERFACE LBRACE signature+ RBRACE;
enumDef: specifierLst? TYPE IDENTIFIER ENUM LBRACE enumItemLst RBRACE;
genericTypeDef: TYPE IDENTIFIER typeAltsLst SEMICOLON;
aliasDef: TYPE IDENTIFIER ALIAS dataType SEMICOLON;
globalVarDef: dataType IDENTIFIER (ASSIGN constant)? SEMICOLON;
fctName: (TYPE_IDENTIFIER DOT)? IDENTIFIER | OPERATOR overloadableOp;
structDef: specifierLst? TYPE TYPE_IDENTIFIER (LESS typeLst GREATER)? STRUCT (COLON typeLst)? LBRACE field* RBRACE;
interfaceDef: specifierLst? TYPE TYPE_IDENTIFIER (LESS typeLst GREATER)? INTERFACE LBRACE signature+ RBRACE;
enumDef: specifierLst? TYPE TYPE_IDENTIFIER ENUM LBRACE enumItemLst RBRACE;
genericTypeDef: TYPE TYPE_IDENTIFIER typeAltsLst SEMICOLON;
aliasDef: TYPE TYPE_IDENTIFIER ALIAS dataType SEMICOLON;
globalVarDef: dataType TYPE_IDENTIFIER (ASSIGN constant)? SEMICOLON;
extDecl: fctAttr? EXT (F LESS dataType GREATER | P) IDENTIFIER LPAREN (typeLst ELLIPSIS?)? RPAREN SEMICOLON;

// Control structures
Expand All @@ -35,7 +35,7 @@ typeAltsLst: dataType (BITWISE_OR dataType)*;
paramLst: declStmt (COMMA declStmt)*;
argLst: assignExpr (COMMA assignExpr)*;
enumItemLst: enumItem (COMMA enumItem)*;
enumItem: IDENTIFIER (ASSIGN INT_LIT)?;
enumItem: TYPE_IDENTIFIER (ASSIGN INT_LIT)?;
field: dataType IDENTIFIER (ASSIGN constant)?;
signature: specifierLst? (F LESS dataType GREATER | P) IDENTIFIER (LESS typeLst GREATER)? LPAREN typeLst? RPAREN SEMICOLON;
stmt: (declStmt | assignExpr | returnStmt | breakStmt | continueStmt) SEMICOLON;
Expand Down Expand Up @@ -75,19 +75,19 @@ multiplicativeExpr: castExpr ((MUL | DIV | REM) castExpr)*;
castExpr: LPAREN dataType RPAREN prefixUnaryExpr | prefixUnaryExpr;
prefixUnaryExpr: postfixUnaryExpr | prefixUnaryOp prefixUnaryExpr;
postfixUnaryExpr: atomicExpr | postfixUnaryExpr LBRACKET assignExpr RBRACKET | postfixUnaryExpr DOT IDENTIFIER | postfixUnaryExpr PLUS_PLUS | postfixUnaryExpr MINUS_MINUS;
atomicExpr: constant | value | IDENTIFIER (SCOPE_ACCESS IDENTIFIER)* | builtinCall | LPAREN assignExpr RPAREN;
atomicExpr: constant | value | ((IDENTIFIER | TYPE_IDENTIFIER) SCOPE_ACCESS)* (IDENTIFIER | TYPE_IDENTIFIER) | builtinCall | LPAREN assignExpr RPAREN;

// Values
value: fctCall | arrayInitialization | structInstantiation | NIL LESS dataType GREATER;
constant: DOUBLE_LIT | INT_LIT | SHORT_LIT | LONG_LIT | CHAR_LIT | STRING_LIT | TRUE | FALSE;
fctCall: IDENTIFIER (SCOPE_ACCESS IDENTIFIER)* (DOT IDENTIFIER)* (LESS typeLst GREATER)? LPAREN argLst? RPAREN;
fctCall: (IDENTIFIER SCOPE_ACCESS)* (IDENTIFIER DOT)* (IDENTIFIER | TYPE_IDENTIFIER) (LESS typeLst GREATER)? LPAREN argLst? RPAREN;
arrayInitialization: LBRACE argLst? RBRACE;
structInstantiation: IDENTIFIER (SCOPE_ACCESS IDENTIFIER)* (LESS typeLst GREATER)? LBRACE argLst? RBRACE;
structInstantiation: (IDENTIFIER SCOPE_ACCESS)* TYPE_IDENTIFIER (LESS typeLst GREATER)? LBRACE argLst? RBRACE;

// Types
dataType: specifierLst? baseDataType (MUL | BITWISE_AND | LBRACKET (INT_LIT | IDENTIFIER)? RBRACKET)*;
dataType: specifierLst? baseDataType (MUL | BITWISE_AND | LBRACKET (INT_LIT | TYPE_IDENTIFIER)? RBRACKET)*;
baseDataType: TYPE_DOUBLE | TYPE_INT | TYPE_SHORT | TYPE_LONG | TYPE_BYTE | TYPE_CHAR | TYPE_STRING | TYPE_BOOL | TYPE_DYN | customDataType | functionDataType;
customDataType: IDENTIFIER (SCOPE_ACCESS IDENTIFIER)* (LESS typeLst GREATER)?;
customDataType: (IDENTIFIER SCOPE_ACCESS)* TYPE_IDENTIFIER (LESS typeLst GREATER)?;
functionDataType: (P | F LESS dataType GREATER) LPAREN typeLst? RPAREN;

// Shorthands
Expand Down Expand Up @@ -197,7 +197,8 @@ SHORT_LIT: NUM_LIT 's';
LONG_LIT: NUM_LIT 'l';
CHAR_LIT: '\'' (~['\\\r\n] | '\\' (. | EOF)) '\'';
STRING_LIT: '"' (~["\\\r\n] | '\\' (. | EOF))* '"';
IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_]*;
IDENTIFIER: [a-z_][a-zA-Z0-9_]*;
TYPE_IDENTIFIER: [A-Z][a-zA-Z0-9_]*;
fragment NUM_LIT: NUM_LIT_S | NUM_LIT_U;
fragment NUM_LIT_S: [-](DEC_LIT | BIN_LIT | HEX_LIT | OCT_LIT);
Expand Down
Loading

0 comments on commit bfdf49b

Please sign in to comment.