Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b7ffded
Fixed the linking of the uv library for Intel Macbooks
Oct 10, 2025
ebb8b9c
df: Added benchmarks
Oct 24, 2025
ca83943
Basic Bumb allocation
Sep 24, 2025
0dd2a6c
First approach of a 2nd step of memory-management, but first done in …
Oct 6, 2025
fe49498
df: Added ctrc for effekt (except for sender_receiver and server_client)
Feb 27, 2026
563f3c6
df: sender_receiver and server_client should work now as well.
Feb 27, 2026
f21ddfd
Delete cMalloc
phischu Mar 3, 2026
b2bc845
Revise acquire
phischu Mar 3, 2026
bad6968
Remove imports
phischu Mar 3, 2026
c2689fa
Fixed the linking of the uv library for Intel Macbooks
Oct 10, 2025
82da493
df: Added benchmarks
Oct 24, 2025
c699d9c
Basic Bumb allocation
Sep 24, 2025
4402387
First approach of a 2nd step of memory-management, but first done in …
Oct 6, 2025
ea2fcf4
df: Added ctrc for effekt (except for sender_receiver and server_client)
Feb 27, 2026
3801f88
df: sender_receiver and server_client should work now as well.
Feb 27, 2026
2e8cfcc
Delete cMalloc
phischu Mar 3, 2026
e8601f8
Revise acquire
phischu Mar 3, 2026
bc05601
Remove imports
phischu Mar 3, 2026
b9d68ad
Merge remote-tracking branch 'origin/final-constant-time-rc' into fin…
Mar 3, 2026
a1985c8
Insert other free call
phischu Mar 3, 2026
d0cc38e
Delete tests for now
phischu Mar 3, 2026
baa4542
Replace switch by invoke
phischu Mar 3, 2026
90fefea
Emit instructions into basic block
phischu Mar 3, 2026
42d07eb
Avoid matching on alias info
phischu Mar 3, 2026
3b5609f
Reuse loading of environment
phischu Mar 4, 2026
fd54b3c
Reorder definitions
phischu Mar 5, 2026
26e8252
Inline definitions
phischu Mar 5, 2026
5669780
More refactoring
phischu Mar 5, 2026
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
11 changes: 9 additions & 2 deletions effekt/jvm/src/main/scala/effekt/Runner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -306,17 +306,24 @@ 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
}
}
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"))
Comment on lines +310 to +326
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Someone with a Mac should test and approve this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw, this works on my machine (though I use nix-darwin, so I'm not the best guinea pig)

case os if os.contains("win") => None
case os if os.contains("linux") => Some(file("/usr/local/include"))
case os => None
Expand Down
538 changes: 393 additions & 145 deletions effekt/shared/src/main/scala/effekt/generator/llvm/Transformer.scala

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export Operand.*

enum EraserKind {
case ObjectEraser
case ChildrenEraser
case StackEraser
case StackFrameEraser
}
Expand Down
17 changes: 12 additions & 5 deletions libraries/common/array.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ extern type Array[T]
*/

extern llvm """
define void @array_erase_fields(ptr %array_pointer) {
define void @array_erase(%Object %object) {
entry:
%data_pointer = getelementptr inbounds i64, ptr %array_pointer, i64 1
%size = load i64, ptr %array_pointer, align 8
; header of arrays looks like that: rc (0), eraser (1), size (2), data (3)
%size_pointer = getelementptr inbounds i64, ptr %object, i64 2
%size = load i64, ptr %size_pointer, align 8

; data starts after 3 entries in our object
%data_pointer = getelementptr inbounds ptr, ptr %object, i64 3

%size_eq_0 = icmp eq i64 %size, 0
br i1 %size_eq_0, label %exit, label %loop
loop:
Expand All @@ -31,11 +36,13 @@ loop:
%element_pointer = getelementptr inbounds %Pos, ptr %data_pointer, i64 %loop_phi
%element = load %Pos, ptr %element_pointer

call void @erasePositive(%Pos %element)
call void @erasePositive(%Pos %element) ; we erase each child of the array

%inc = add nuw i64 %loop_phi, 1
%cmp = icmp ult i64 %inc, %size
br i1 %cmp, label %loop, label %exit
exit:
call void @free(%Object %object) ; we delete the array
ret void
}
"""
Expand All @@ -52,7 +59,7 @@ extern def unsafeAllocate[T](size: Int) at global: Array[T] =
%eraser_pointer = getelementptr ptr, ptr %calloc, i64 1
%size_pointer = getelementptr ptr, ptr %calloc, i64 2
store i64 0, ptr %calloc
store ptr @array_erase_fields, ptr %eraser_pointer
store ptr @array_erase, ptr %eraser_pointer
store i64 ${size}, ptr %size_pointer
%ret_pos = insertvalue %Pos { i64 0, ptr poison }, ptr %calloc, 1
ret %Pos %ret_pos
Expand Down
5 changes: 3 additions & 2 deletions libraries/common/bytearray.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ extern def allocate(size: Int) at global: ByteArray =
%object_alloc = tail call noalias ptr @calloc(i64 %object_size, i64 1)
store i64 0, ptr %object_alloc, align 8
%object_data_ptr = getelementptr inbounds i8, ptr %object_alloc, i64 8
store ptr @bytearray_erase_noop, ptr %object_data_ptr, align 8
store ptr @bytearray_erase, ptr %object_data_ptr, align 8
%ret_object1 = insertvalue %Pos poison, i64 ${size}, 0
%ret_object2 = insertvalue %Pos %ret_object1, ptr %object_alloc, 1
ret %Pos %ret_object2
Expand Down Expand Up @@ -180,7 +180,8 @@ extern js """
"""

extern llvm """
define void @bytearray_erase_noop(ptr readnone %0) {
define void @bytearray_erase(ptr readnone %0) {
tail call void @free(ptr %0)
ret void
}
"""
Expand Down
15 changes: 9 additions & 6 deletions libraries/common/ref.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -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(%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
}
"""
Expand Down Expand Up @@ -41,9 +44,9 @@ extern def unsafeAllocate[T]() at global: Ref[T] =
%fieldData_pointer = getelementptr ptr, ptr %ref, i64 3

store i64 0, ptr %ref, align 8
store ptr @c_ref_erase_field, ptr %refEraser, align 8
store ptr @c_ref_erase, ptr %refEraser, align 8
store i64 0, ptr %fieldTag, align 8
store ptr null, ptr %fieldData_pointer, align 8
store ptr null, ptr %fieldData_pointer, align 8

%refWrap = insertvalue %Pos { i64 0, ptr poison }, ptr %ref, 1
ret %Pos %refWrap
Expand All @@ -62,7 +65,7 @@ extern def ref[T](init: T) at global: Ref[T] =
%refField = getelementptr ptr, ptr %ref, i64 2

store i64 0, ptr %ref, align 8
store ptr @c_ref_erase_field, ptr %refEraser, align 8
store ptr @c_ref_erase, ptr %refEraser, align 8
store %Pos ${init}, ptr %refField, align 8

%refWrap = insertvalue %Pos { i64 0, ptr poison }, ptr %ref, 1
Expand Down
12 changes: 7 additions & 5 deletions libraries/llvm/bytearray.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
#define EFFEKT_BYTEARRAY_C

#include <string.h> // For memcopy
#include <stdint.h>
#include <inttypes.h>
#include <stdlib.h>

/** We represent bytearrays like positive types.
*
Expand All @@ -18,12 +17,15 @@
*/


void c_bytearray_erase_noop(void *envPtr) { (void)envPtr; }
void c_bytearray_erase(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, };
*headerPtr = (struct Header) { .rc = 0, .eraser = c_bytearray_erase, };
return (struct Pos) {
.tag = size,
.obj = objPtr,
Expand Down
14 changes: 14 additions & 0 deletions libraries/llvm/initialize-arena.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include <sys/mman.h>

// Initialize a 4 GiB arena via mmap.
// Does not work in LLVM properly, therefore it is done in C.
void* initializeArena(void) {
return mmap(
NULL, // no specific start adress
4294967296ULL, // 4 GiB
PROT_READ | PROT_WRITE, // Can read and write
MAP_PRIVATE | MAP_ANONYMOUS, // isolated, clean arena
-1, // No file descriptor
0 // Offset
);
}
6 changes: 3 additions & 3 deletions libraries/llvm/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -589,11 +589,11 @@ typedef struct {
Stack stack;
} Signal;

void c_signal_erase(void *envPtr) {
// envPtr points to a Signal _after_ the eraser, so let's adjust it to point to the beginning.
Signal *signal = (Signal*) (envPtr - offsetof(Signal, state));
void c_signal_erase(void *object) {
Signal *signal = (Signal*) object;
erasePositive(signal->value);
if (signal->stack != NULL) { eraseStack(signal->stack); }
free(signal);
}

struct Pos c_signal_make() {
Expand Down
1 change: 1 addition & 0 deletions libraries/llvm/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "bytearray.c"
#include "io.c"
#include "panic.c"
#include "initialize-arena.c"


extern void effektMain();
Expand Down
116 changes: 107 additions & 9 deletions libraries/llvm/rts.ll
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@
%String = type %Pos

; Foreign imports

declare ptr @malloc(i64)
declare void @free(ptr)
declare ptr @realloc(ptr, i64)
Expand All @@ -118,6 +117,111 @@ declare void @print(i64)
declare void @exit(i64)
declare void @llvm.assume(i1)

declare ptr @initializeArena()

; typedef struct Slot
; {
; struct Slot* next;
; void (*eraser)(void *object);
; } Slot;
%Slot = type { ptr, %Eraser }

; list of immediately available slots
@freeList = private unnamed_addr global ptr null
; list of slots where we still need to erase the children
@todoList = private unnamed_addr global ptr null
; space of unused consecutive slots
@bumpList = private unnamed_addr global ptr null

; Initializes the memory for our effekt-objects that are created by newObject and deleted by eraseObject.
define private void @initializeMemory() nounwind {
entry:
; we need this because flags for mmap differ between platforms
%startAddress = call noalias ptr @initializeArena()
store ptr %startAddress, ptr @bumpList
ret void
}


define private ptr @acquire() nounwind {
entry:
%freeHead = load ptr, ptr @freeList
%isFreeEmpty = icmp eq ptr %freeHead, null
br i1 %isFreeEmpty, label %checkTodo, label %freeReuse

; 1. Fast Path: Reuse block from freeList
freeReuse:
%freeNextPtr = getelementptr %Slot, ptr %freeHead, i32 0, i32 0
%freeNext = load ptr, ptr %freeNextPtr
store ptr %freeNext, ptr @freeList

ret ptr %freeHead

checkTodo:
%todoHead = load ptr, ptr @todoList
%isTodoEmpty = icmp eq ptr %todoHead, null
br i1 %isTodoEmpty, label %bumpAlloc, label %todoReuse

; 2. Slot Path: Reuse block from To-Do-List. Here, we have to extra call the eraser to ensure that we can reuse it.
todoReuse:
%todoNextPtr = getelementptr inbounds %Slot, ptr %todoHead, i32 0, i32 0
%todoNext = load ptr, ptr %todoNextPtr
store ptr %todoNext, ptr @todoList

; Call eraser
; reusedSlot->eraser(reusedSlot);
%eraserPtr = getelementptr inbounds %Slot, ptr %todoHead, i32 0, i32 1
%eraser = load %Eraser, ptr %eraserPtr, !alias.scope !14, !noalias !24
call void %eraser(ptr %todoHead)

ret ptr %todoHead

; 3. Fallback - Bump Path: We have to allocate a new block.
bumpAlloc:
; Load nextUnusedBlock
%fresh = load ptr, ptr @bumpList

; Move bump pointer forward by object size
%nextBump = getelementptr i8, ptr %fresh, i8 64

store ptr %nextBump, ptr @bumpList

ret ptr %fresh
}

; Pushes a slot on the top of the free-List.
define private void @pushOntoFreeList(ptr %slot) nounwind {
entry:
; oldHead = freeList
%oldHead = load ptr, ptr @freeList

; ptr->next = oldHead
%nextPtr = getelementptr %Slot, ptr %slot, i32 0, i32 0
store ptr %oldHead, ptr %nextPtr

; freeList = ptr
store ptr %slot, ptr @freeList

ret void
}

; Pushes a slot on the top of the To-Do-List.
define private void @pushOntoTodoList(ptr %slot) nounwind {
entry:
; oldHead = todoList
%oldHead = load ptr, ptr @todoList

; ptr->next = oldHead
%nextPtr = getelementptr %Slot, ptr %slot, i32 0, i32 0
store ptr %oldHead, ptr %nextPtr

; todoList = ptr
store ptr %slot, ptr @todoList

ret void
}



; Prompts

Expand All @@ -134,12 +238,8 @@ define private %Prompt @freshPrompt() {
ret %Prompt %prompt
}

; Garbage collection

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 @acquire()
%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
Expand Down Expand Up @@ -197,9 +297,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:
Expand Down
Loading