diff --git a/effekt/jvm/src/main/scala/effekt/Runner.scala b/effekt/jvm/src/main/scala/effekt/Runner.scala index c5e94f5a5..f4d0a237c 100644 --- a/effekt/jvm/src/main/scala/effekt/Runner.scala +++ b/effekt/jvm/src/main/scala/effekt/Runner.scala @@ -287,9 +287,15 @@ object LLVMRunner extends Runner[String] { def libuvArgs(using C: Context): Seq[String] = val OS = System.getProperty("os.name").toLowerCase + + // only relevant for macOS + val arch: String = System.getProperty("os.arch") + assert(!OS.contains("mac") || List("aarch64", "x86_64").contains(arch), "If you have a macbook -> It should have either an aarch64 or x86_64 architecture.") + val libraries = C.config.clangLibraries.toOption.map(file).orElse { OS match { - case os if os.contains("mac") => Some(file("/opt/homebrew/lib")) + case os if os.contains("mac") && arch.equals("aarch64") => Some(file("/opt/homebrew/lib")) + case os if os.contains("mac") => Some(file("/usr/local/lib")) case os if os.contains("win") => None case os if os.contains("linux") => Some(file("/usr/local/lib")) case os => None @@ -297,7 +303,8 @@ object LLVMRunner extends Runner[String] { } val includes = C.config.clangIncludes.toOption.map(file).orElse { OS match { - case os if os.contains("mac") => Some(file("/opt/homebrew/include")) + case os if os.contains("mac") && arch.equals("aarch64") => Some(file("/opt/homebrew/include")) + case os if os.contains("mac") => Some(file("/usr/local/include")) case os if os.contains("win") => None case os if os.contains("linux") => Some(file("/usr/local/include")) case os => None diff --git a/effekt/shared/src/main/scala/effekt/generator/llvm/Transformer.scala b/effekt/shared/src/main/scala/effekt/generator/llvm/Transformer.scala index e4b41585e..8f012fd1e 100644 --- a/effekt/shared/src/main/scala/effekt/generator/llvm/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/generator/llvm/Transformer.scala @@ -3,6 +3,7 @@ package generator package llvm import effekt.machine +import effekt.machine.Variable import effekt.util.intercalate import effekt.util.messages.ErrorReporter import effekt.machine.analysis.* @@ -24,6 +25,7 @@ object Transformer { val entryInstructions = List( Call("stack", Ccc(), stackType, withEmptyStack, List()), + Call("", Ccc(), VoidType(), initializeMemory, List()), Call("_", Tailcc(false), VoidType(), transform(entry), List(LocalReference(stackType, "stack")))) val entryBlock = BasicBlock("entry", entryInstructions, RetVoid()) val entryFunction = Function(External(), Ccc(), VoidType(), "effektMain", List(), List(entryBlock)) @@ -520,12 +522,17 @@ object Transformer { kind match { case ObjectEraser => val eraser = ConstantGlobal(freshName("eraser")); - defineFunction(eraser.name, List(Parameter(environmentType, "environment"))) { + defineFunction(eraser.name, List(Parameter(objectType, "object"))) { emit(Comment(s"${kind} eraser, ${freshEnvironment.length} free variables")) + // Use call @objectEnvironment to get environment pointer + emit(Call("environment", Ccc(), environmentType, ConstantGlobal("objectEnvironment"), List(LocalReference(objectType, "object")))); + // TODO avoid unnecessary loads loadEnvironmentAt(LocalReference(environmentType, "environment"), freshEnvironment, Object); eraseValues(freshEnvironment, Set()); + + emit(Call("", Ccc(), VoidType(), ConstantGlobal("cFree"), List(LocalReference(objectType, "object")))); RetVoid() }; eraser @@ -719,6 +726,7 @@ object Transformer { emit(Load(returnAddressName, returnAddressType, returnAddressPointer, StackPointer)); } + val initializeMemory = ConstantGlobal("cInitializeMemory"); val newObject = ConstantGlobal("newObject"); val objectEnvironment = ConstantGlobal("objectEnvironment"); diff --git a/libraries/common/array.effekt b/libraries/common/array.effekt index 83a3d7c3a..7c241fd77 100644 --- a/libraries/common/array.effekt +++ b/libraries/common/array.effekt @@ -19,10 +19,10 @@ extern type Array[T] extern llvm """ declare noalias ptr @calloc(i64, i64) -define void @array_erase_fields(ptr %array_pointer) { +define void @array_erase_fields(%Object %object) { entry: - %data_pointer = getelementptr inbounds i64, ptr %array_pointer, i64 1 - %size = load i64, ptr %array_pointer, align 8 + %data_pointer = getelementptr inbounds i64, ptr %object, i64 1 + %size = load i64, ptr %object, align 8 %size_eq_0 = icmp eq i64 %size, 0 br i1 %size_eq_0, label %exit, label %loop loop: @@ -36,6 +36,7 @@ loop: %cmp = icmp ult i64 %inc, %size br i1 %cmp, label %loop, label %exit exit: + call void @free(%Object %object) ret void } """ diff --git a/libraries/common/ref.effekt b/libraries/common/ref.effekt index 81293b293..8df0ca078 100644 --- a/libraries/common/ref.effekt +++ b/libraries/common/ref.effekt @@ -10,9 +10,12 @@ extern js """ """ extern llvm """ -define void @c_ref_erase_field(ptr %0) { - %field = load %Pos, ptr %0, align 8 - tail call void @erasePositive(%Pos %field) +define void @c_ref_erase_field(%Object %object) { + %headerSize = ptrtoint ptr getelementptr (%Header, ptr null, i64 1) to i64 + %environment = getelementptr i8, ptr %object, i64 %headerSize + %field = load %Pos, ptr %environment, align 8 + call void @free(%Object %object) + call void @erasePositive(%Pos %field) ret void } """ diff --git a/libraries/llvm/bytearray.c b/libraries/llvm/bytearray.c index 175099457..3dce3a6d9 100644 --- a/libraries/llvm/bytearray.c +++ b/libraries/llvm/bytearray.c @@ -2,6 +2,7 @@ #define EFFEKT_BYTEARRAY_C #include // For memcopy +#include /** We represent bytearrays like positive types. * @@ -16,10 +17,11 @@ */ -void c_bytearray_erase_noop(void *envPtr) { (void)envPtr; } +void c_bytearray_erase_noop(void* object) { free(object); } struct Pos c_bytearray_new(const Int size) { - void *objPtr = malloc(sizeof(struct Header) + size); + int object_size = sizeof(struct Header) + size; + void *objPtr = malloc(object_size); struct Header *headerPtr = objPtr; *headerPtr = (struct Header) { .rc = 0, .eraser = c_bytearray_erase_noop, }; return (struct Pos) { diff --git a/libraries/llvm/cMalloc.c b/libraries/llvm/cMalloc.c new file mode 100644 index 000000000..fcb442996 --- /dev/null +++ b/libraries/llvm/cMalloc.c @@ -0,0 +1,124 @@ +#include +#include +#include + +// ----------------------------- +// Strukturdefinition +// ----------------------------- + +/** + * @brief Block-Struktur für die Freelist. + * + * Jeder freie Block zeigt auf den nächsten freien Block. + */ +typedef struct Block { + struct Block* next; +} Block; + +// ----------------------------- +// Globale Variablen +// ----------------------------- + +static Block* freeList = NULL; // Kopf der Freelist +static uint8_t* nextUnusedBlock = NULL; // Zeiger auf nächsten unbenutzten Block +static uint8_t* endOfChunk = NULL; // Ende des allokierten Speichers +static const int blockSize = 128; // Größe jedes Blocks (1KB) + +// ----------------------------- +// Initialisierung +// ----------------------------- + +/** + * @brief Initialisiert den großen Speicherbereich (4GB). + */ +void cInitializeMemory(void) { + size_t chunkSize = (size_t)4294967296ULL; // 4GB + uint8_t* mem = (uint8_t*)malloc(chunkSize); + if (!mem) { + fprintf(stderr, "malloc() failed!\n"); + exit(1); + } + + nextUnusedBlock = mem; + endOfChunk = mem + chunkSize; +// printf("[init] Memory initialized: %p - %p\n", (void*)mem, (void*)endOfChunk); +} + +// ----------------------------- +// Allokator +// ----------------------------- + +/** + * @brief Einfacher Speicher-Allocator. + * + * Wenn die Freelist leer ist, nimmt er den nächsten Block im Chunk. + * Wenn die Freelist nicht leer ist, nimmt er den ersten Eintrag daraus. + * + * @param size Ignoriert in diesem simplen Modell (wir geben immer 1KB). + * @return void* Zeiger auf den allokierten Block. + */ +void* cMalloc(uint8_t size) { + // 1. Falls Freelist leer ist → neuer Block + if (freeList == NULL) { + if (nextUnusedBlock + blockSize > endOfChunk) { + fprintf(stderr, "Out of memory!\n"); + return NULL; + } + + void* block = nextUnusedBlock; + nextUnusedBlock += blockSize; +// printf("[malloc] New block: %p\n", block); + return block; + } + + + // 2. Falls Freelist nicht leer ist → wiederverwenden + Block* block = freeList; + freeList = block->next; +// printf("[malloc] Reusing block: %p\n", (void*)block); +// printf("[malloc] freeList: %p\n", (void*)freeList); + return (void*)block; +// return malloc(size); +} + +// ----------------------------- +// Free-Funktion +// ----------------------------- + +/** + * @brief Gibt einen Block zurück in die Freelist. + * + * @param ptr Zeiger auf den Block. + */ +void cFree(void* ptr) { + if (!ptr) return; + + Block* block = (Block*)ptr; + block->next = freeList; + freeList = block; + +// printf("[free] Freed block: %p\n", ptr); +// free(ptr); +} + +void* cRealloc(void* ptr, uint8_t size) { + printf("cRealloc: %p, %d\n", ptr, size); + return realloc(ptr,size); +} + +// ----------------------------- +// Test / Demo +// ----------------------------- + +//int main(void) { +// cInitializeMemory(); +// +// void* a = cMalloc(1024); +// void* b = cMalloc(1024); +// cFree(a); +// void* c = cMalloc(1024); // sollte a wiederverwenden +// cFree(b); +// cFree(c); +// +// return 0; +//} diff --git a/libraries/llvm/io.c b/libraries/llvm/io.c index 57b3b22d5..d4934e572 100644 --- a/libraries/llvm/io.c +++ b/libraries/llvm/io.c @@ -397,7 +397,7 @@ void c_promise_resolve(struct Pos promise, struct Pos value, Stack stack) { } // TODO stack overflow? // We need to erase the promise now, since we consume it. - erasePositive(promise); + // erasePositive(promise); } void c_promise_await(struct Pos promise, Stack stack) { @@ -429,7 +429,7 @@ void c_promise_await(struct Pos promise, Stack stack) { break; }; // TODO hmm, stack overflow? - erasePositive(promise); + erasePositive(promise); // df: Otherwise, interleave_promises.effekt fails. } struct Pos c_promise_make() { diff --git a/libraries/llvm/main.c b/libraries/llvm/main.c index 38c5f7133..9431a0ed5 100644 --- a/libraries/llvm/main.c +++ b/libraries/llvm/main.c @@ -11,6 +11,7 @@ #include "types.c" #include "bytearray.c" +#include "cMalloc.c" #include "io.c" #include "panic.c" diff --git a/libraries/llvm/rts.ll b/libraries/llvm/rts.ll index 746ed5def..b2fedd6cc 100644 --- a/libraries/llvm/rts.ll +++ b/libraries/llvm/rts.ll @@ -96,6 +96,11 @@ ; Foreign imports +declare void @cInitializeMemory() +declare ptr @cMalloc(i64) +declare void @cFree(ptr) +declare ptr @cRealloc(ptr, i64) + declare ptr @malloc(i64) declare void @free(ptr) declare ptr @realloc(ptr, i64) @@ -137,10 +142,82 @@ define private %Prompt @freshPrompt() { ; Garbage collection +; A type for the free list +%struct.Block = type { %struct.Block* } + +@freeList = global %struct.Block* null +@nextUnusedBlock = global i8* null +@endOfChunk = global i8* null +@blockSize = global i64 1024 ; each Block is 1KB + +define private void @initializeMemory() { + ; Step 01: mem = malloc(4294967296) + %mem = call i8* @malloc(i64 4294967296) + + ; Step 02: nextUnusedBlock = mem + store i8* %mem, i8** @nextUnusedBlock + + ; Step 03: endOfChunk = mem + 4294967296 + %endPtr = getelementptr i8, i8* %mem, i64 4294967296 + store i8* %endPtr, i8** @endOfChunk + + ret void +} + +define private %Object @myMalloc(i64 %size) { +entry: + ; Step 01: Check if the free list pointer is not null + %freeList = load %struct.Block*, %struct.Block** @freeList + %isNull = icmp eq %struct.Block* %freeList, null + br i1 %isNull, label %newAllocate, label %reuse + +; In case we can recycle a block from the free list, we do so and jump to the reuse label. +reuse: + ; Step 01: block = freeList + %block = load %struct.Block*, %struct.Block** @freeList + + ; Step 02: freeList = freeList.next + %nextPtr = getelementptr %struct.Block, %struct.Block* %block, i32 0, i32 0 + %nextBlock = load %struct.Block*, %struct.Block** %nextPtr + store %struct.Block* %nextBlock, %struct.Block** @freeList + + ; Step 03: Return + %ret = bitcast %struct.Block* %block to %Object + ret %Object %ret + +; In case we do not have a block to reuse +newAllocate: + %nu = load i8*, i8** @nextUnusedBlock + %end = load i8*, i8** @endOfChunk + %blockSize = load i64, i64* @blockSize + + ; block = next_unused + %next_plus = getelementptr i8, i8* %nu, i64 %blockSize + store i8* %next_plus, i8** @nextUnusedBlock + + %ret2 = bitcast i8* %nu to %Object + ret %Object %ret2 + +} + +define private void @myFree(%Object %object) { + ; block = (Block*)object + %block = bitcast %Object %object to %struct.Block* + + ; block.next = freeList + %nextField = getelementptr %struct.Block, %struct.Block* %block, i32 0, i32 0 + %freeList = load %struct.Block*, %struct.Block** @freeList + store %struct.Block* %freeList, %struct.Block** %nextField + + ; freeList = block + store %struct.Block* %block, %struct.Block** @freeList ; <- fails + ret void +} + define private %Object @newObject(%Eraser %eraser, i64 %environmentSize) alwaysinline { %headerSize = ptrtoint ptr getelementptr (%Header, ptr null, i64 1) to i64 %size = add i64 %environmentSize, %headerSize - %object = call ptr @malloc(i64 %size) + %object = call ptr @cMalloc(i64 %size) %objectReferenceCount = getelementptr %Header, ptr %object, i64 0, i32 0 %objectEraser = getelementptr %Header, ptr %object, i64 0, i32 1 store %ReferenceCount 0, ptr %objectReferenceCount, !alias.scope !14, !noalias !24 @@ -198,9 +275,7 @@ define private void @eraseObject(%Object %object) alwaysinline { free: %objectEraser = getelementptr %Header, ptr %object, i64 0, i32 1 %eraser = load %Eraser, ptr %objectEraser, !alias.scope !14, !noalias !24 - %environment = call %Environment @objectEnvironment(%Object %object) - call void %eraser(%Environment %environment) - call void @free(%Object %object) + call void %eraser(%Object %object) br label %done done: