Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
fda1f55
basic set up
konradbausch Aug 25, 2025
1296ba9
Very simple modification of function
konradbausch Oct 7, 2025
1545144
Hopefully implemented rename
konradbausch Oct 7, 2025
6384319
Now working as before
konradbausch Oct 7, 2025
9e4deb7
Starting to modify function calls, bind by match still missing
konradbausch Oct 7, 2025
6cfb386
Forgot to commit and push, both sides working now for 2 arguments
konradbausch Oct 27, 2025
662bc66
need to use fresh
konradbausch Nov 25, 2025
b4dceee
Booth sides fresh
konradbausch Nov 25, 2025
f2f892e
Made it create match
konradbausch Nov 25, 2025
1d77886
Added comments so i dont forget why i did certain things
konradbausch Nov 25, 2025
595cd9b
app side working i think
konradbausch Dec 2, 2025
b0c1464
Booth sides working llvm test work, js some not
konradbausch Dec 2, 2025
3d680e7
clean up
konradbausch Dec 3, 2025
b0c93a7
rebased
konradbausch Dec 15, 2025
a505d75
renaming
konradbausch Dec 8, 2025
1d57965
HOF working, except for when giving a direct reference
konradbausch Dec 9, 2025
a348be9
transformed map{myfunc} to map{t => myfunc{t})
konradbausch Dec 9, 2025
d50e100
further bargs fixing
konradbausch Dec 15, 2025
3175a2e
still some issues
konradbausch Dec 15, 2025
c90155c
Prevent endless recursion and added a missing transform
konradbausch Jan 2, 2026
8c51c78
llvm working, but reparse breaks
konradbausch Jan 11, 2026
a933339
one more transform that might is necesarry
konradbausch Jan 12, 2026
7836a0c
Fixing last error in llvm tests
konradbausch Jan 12, 2026
20fc2e0
Refactoring for ease of readabilit, formating and removing unneeded c…
konradbausch Jan 12, 2026
dca1221
performancegit add effekt/
konradbausch Jan 19, 2026
4c579f1
benchmark file
konradbausch Jan 19, 2026
12e3225
modified benchmark script
konradbausch Jan 19, 2026
4fbfca6
adding arity raising to scheme
konradbausch Jan 19, 2026
da5bd0e
modified benchmark
konradbausch Jan 19, 2026
338826b
added my benchmarks to show the best case performance increase
konradbausch Jan 19, 2026
417cfa2
removed acidentally commited metals
konradbausch Jan 19, 2026
4eca678
Added check files to satisfy tests
konradbausch Jan 19, 2026
2889896
creating parameterized benchmarks
konradbausch Feb 15, 2026
f7ba9a0
added val keyword
konradbausch Feb 15, 2026
6cc5de0
index error
konradbausch Feb 15, 2026
35c98ae
changed names
konradbausch Feb 15, 2026
84c7ab5
tryint to prevent constant folding
konradbausch Feb 19, 2026
5d27012
automatic nice plot
konradbausch Feb 22, 2026
a2005c6
trying without record reconstruction
konradbausch Feb 24, 2026
19af48a
made benchmark script output json, read from yml
konradbausch Feb 24, 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
168 changes: 168 additions & 0 deletions benchmark-compare.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#!/bin/bash
set -e

SKIP_COMPILE=0
if [ "$1" = "--skip-compile" ]; then
SKIP_COMPILE=1
fi

BACKENDS=("llvm")
WARMUP=10
RUNS=50
TARGET_BRANCH="main"
OUTPUT_DIR="benchmark-results"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
CURRENT_BRANCH=$(git branch --show-current)
CURRENT_BRANCH_SAFE="${CURRENT_BRANCH//\//-}"

cleanup() {
local current=$(git branch --show-current)
if [ "$current" != "$CURRENT_BRANCH" ]; then
echo ""
echo "Interrupted! Switching back to $CURRENT_BRANCH..."
git checkout -q "$CURRENT_BRANCH"
fi
exit 1
}

trap cleanup SIGINT SIGTERM

if ! command -v hyperfine &> /dev/null; then
echo "Error: hyperfine is not installed"
exit 1
fi

if [ "$CURRENT_BRANCH" = "$TARGET_BRANCH" ]; then
echo "Error: You are currently on the $TARGET_BRANCH branch"
echo "Please switch to your feature branch first"
exit 1
fi

mkdir -p "$OUTPUT_DIR"

declare -A BENCHMARKS=(
["arity_raising/record_passing"]="25000000"
["arity_raising/matrix_determinant"]="2000000"
["large_records/10"]="2000000"
["large_records/20"]="2000000"
["nested_records/10"]="2000000"
["nested_records/20"]="2000000"
)

echo "Comparing: $CURRENT_BRANCH vs $TARGET_BRANCH"
echo "Backends: ${BACKENDS[*]}"
echo "Runs: $RUNS, Warmup: $WARMUP"
echo "Skip compilation: $([ $SKIP_COMPILE -eq 1 ] && echo 'yes' || echo 'no')"
echo ""

for backend in "${BACKENDS[@]}"; do
OUT_CURRENT="out-${CURRENT_BRANCH_SAFE}-${backend}"
OUT_MAIN="out-main-${backend}"
mkdir -p "$OUT_CURRENT" "$OUT_MAIN"
done

if [ $SKIP_COMPILE -eq 0 ]; then

if ! git diff-index --quiet HEAD --; then
echo "Error: You have uncommitted changes"
echo "Please commit or stash your changes before running this script"
exit 1
fi

echo "=== Building compiler on $CURRENT_BRANCH ==="
sbt install
echo ""

echo "=== Compiling benchmarks on $CURRENT_BRANCH ==="
for backend in "${BACKENDS[@]}"; do
OUT_CURRENT="out-${CURRENT_BRANCH_SAFE}-${backend}"
echo "Backend: $backend"

for bench_path in "${!BENCHMARKS[@]}"; do
bench_name=$(basename "$bench_path")
source_file="examples/benchmarks/${bench_path}.effekt"
echo " $bench_name"
effekt --backend="$backend" --build -o "$OUT_CURRENT" "$source_file"
done
done
echo ""

echo "=== Building compiler on $TARGET_BRANCH ==="
git checkout -q "$TARGET_BRANCH"
sbt install
echo ""

echo "=== Compiling benchmarks on $TARGET_BRANCH ==="
for backend in "${BACKENDS[@]}"; do
OUT_MAIN="out-main-${backend}"
echo "Backend: $backend"

for bench_path in "${!BENCHMARKS[@]}"; do
bench_name=$(basename "$bench_path")
source_file="examples/benchmarks/${bench_path}.effekt"
echo " $bench_name"
effekt --backend="$backend" --build -o "$OUT_MAIN" "$source_file"
done
done
echo ""

echo "=== Switching back to $CURRENT_BRANCH ==="
git checkout -q "$CURRENT_BRANCH"
echo ""
else
echo "=== Skipping compilation (using existing binaries) ==="
echo ""
fi

echo "=== Starting benchmarks ==="
echo ""

for backend in "${BACKENDS[@]}"; do
echo "=== Benchmarking backend: $backend ==="

OUT_CURRENT="out-${CURRENT_BRANCH_SAFE}-${backend}"
OUT_MAIN="out-main-${backend}"

comparison_file="${OUTPUT_DIR}/comparison_${backend}_${CURRENT_BRANCH_SAFE}_vs_main_${TIMESTAMP}.md"
echo "# $CURRENT_BRANCH vs main ($backend)" > "$comparison_file"
echo "Date: $(date)" >> "$comparison_file"
echo "Runs: $RUNS, Warmup: $WARMUP" >> "$comparison_file"
echo "" >> "$comparison_file"

for bench_path in "${!BENCHMARKS[@]}"; do
bench_name=$(basename "$bench_path")
params=${BENCHMARKS[$bench_path]}

echo " $bench_name"

case $backend in
llvm)
current_exec="./$OUT_CURRENT/${bench_name}"
target_exec="./$OUT_MAIN/${bench_name}"
;;
js)
current_exec="node $OUT_CURRENT/${bench_name}.js"
target_exec="node $OUT_MAIN/${bench_name}.js"
;;
chez-callcc)
current_exec="scheme --script $OUT_CURRENT/${bench_name}.ss"
target_exec="scheme --script $OUT_MAIN/${bench_name}.ss"
;;
esac

echo "## $bench_name" >> "$comparison_file"
hyperfine \
--warmup "$WARMUP" \
--runs "$RUNS" \
--export-markdown - \
--command-name "main" "$target_exec $params" \
--command-name "$CURRENT_BRANCH" "$current_exec $params" \
2>&1 | tee -a "$comparison_file"
echo "" >> "$comparison_file"
done

echo "Results: $comparison_file"
echo ""
done

echo "Done! Results in: $OUTPUT_DIR/"
229 changes: 229 additions & 0 deletions effekt/shared/src/main/scala/effekt/core/ArityRaising.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package effekt
package core
import effekt.context.Context
import effekt.core.optimizer.Deadcode
import effekt.typer.Typer.checkMain
import effekt.symbols.Symbol.fresh
import effekt.lexer.TokenKind
import effekt.core.Type.instantiate
import effekt.generator.llvm.Transformer.BlockContext
import effekt.machine.Transformer.BlocksParamsContext

object ArityRaising extends Phase[CoreTransformed, CoreTransformed] {
override val phaseName: String = "arity raising"

override def run(input: CoreTransformed)(using C: Context): Option[CoreTransformed] = input match {
case CoreTransformed(source, tree, mod, core) =>
implicit val pctx: DeclarationContext = new DeclarationContext(core.declarations, core.externs)
Context.module = mod
val main = C.ensureMainExists(mod)
val res = Deadcode.remove(main, core)
val transformed = Context.timed(phaseName, source.name) { transform(res) }
Some(CoreTransformed(source, tree, mod, transformed))
}

def transform(decl: ModuleDecl)(using Context, DeclarationContext): ModuleDecl = decl match {
case ModuleDecl(path, includes, declarations, externs, definitions, exports) =>
ModuleDecl(path, includes, declarations, externs, definitions map transform, exports)
}

def transform(toplevel: Toplevel)(using C: Context, DC: DeclarationContext): Toplevel = toplevel match {
case Toplevel.Def(id, block) => Toplevel.Def(id, transform(block)(using C, DC, Set.empty))
case Toplevel.Val(id, binding) => Toplevel.Val(id, transform(binding)(using C, DC, Set.empty))
}

def transform(block: Block)(using C: Context, DC: DeclarationContext, boundBlockParams: Set[Id]): Block = block match {
case Block.BlockVar(id, annotatedTpe, annotatedCapt) => block
case Block.BlockLit(tparams, cparams, vparams, bparams, body) =>
def flattenParam(param: ValueParam): (List[ValueParam], List[(Id, Expr)]) = param match {
case ValueParam(paramId, tpe @ ValueType.Data(name, targs)) =>
DC.findData(name) match {
case Some(Data(_, List(), List(Constructor(ctor, List(), fields)))) =>
val (flatParams, allBindings, fieldVars) = fields.map { case Field(fieldName, fieldType) =>
val freshId = Id(fieldName)
val (params, bindings) = flattenParam(ValueParam(freshId, fieldType))
(params, bindings, ValueVar(freshId, fieldType))
}.unzip3

val binding = (paramId, Make(tpe, ctor, List(), fieldVars))
(flatParams.flatten, allBindings.flatten :+ binding)

case _ => (List(param), List())
}
case _ => (List(param), List())
}

val flattened = vparams.map(flattenParam)
val (allParams, allBindings) = flattened.unzip

val newBody = allBindings.flatten.foldRight(transform(body)(using C, DC, boundBlockParams ++ bparams.map(_.id))) {
case ((id, expr), body) => Let(id, expr, body)
}

Block.BlockLit(tparams, cparams, allParams.flatten, bparams, newBody)

case Block.Unbox(pure) =>
Block.Unbox(transform(pure))

case Block.New(Implementation(interface, operations)) =>
Block.New(Implementation(interface, operations.map {
case Operation(name, tparams, cparams, vparams, bparams, body) =>
Operation(name, tparams, cparams, vparams, bparams, transform(body)(using C, DC, boundBlockParams ++ bparams.map(_.id)))
}))
}

// Helper to check if a type needs flattening
def needsFlattening(tpe: ValueType)(using DC: DeclarationContext): Boolean = tpe match {
case ValueType.Data(name, _) =>
DC.findData(name) match {
case Some(Data(_, List(), List(Constructor(_, List(), _)))) => true
case _ => false
}
case _ => false
}

def wrapBlockVarIfNeeded(barg: BlockVar, annotatedTpe: BlockType)(using C: Context, DC: DeclarationContext, boundBlockParams: Set[Id]): Block =
annotatedTpe match {
case BlockType.Function(tparams, cparams, vparams, bparamTpes, result) if vparams.exists(needsFlattening) =>
val values = vparams.map { tpe =>
val freshId = Id("x")
(ValueParam(freshId, tpe), ValueVar(freshId, tpe))
}
val blocks = bparamTpes.zip(cparams).map { case (tpe, capt) =>
val freshId = Id("f")
(BlockParam(freshId, tpe, Set(capt)), BlockVar(freshId, tpe, Set(capt)))
}
val call = Stmt.App(barg, List(), values.map(_._2), blocks.map(_._2))
BlockLit(tparams, cparams, values.map(_._1), blocks.map(_._1), transform(call)(using C, DC, boundBlockParams ++ blocks.map(_._1.id)))


case _ => transform(barg)
}

def transform(stmt: Stmt)(using C: Context, DC: DeclarationContext, boundBlockParams: Set[Id]): Stmt = stmt match {
case Stmt.App(callee @ BlockVar(id, BlockType.Function(tparams, cparams, vparamsTypes, bparamTypes, returnTpe), annotatedCapt), targs, vargs, bargs) if !boundBlockParams.contains(id) =>
def flattenArg(arg: Expr, argType: ValueType): (List[Expr], List[ValueType], List[(Expr, Id, List[ValueParam])]) = argType match {
case ValueType.Data(name, targs) =>
DC.findData(name) match {
case Some(Data(_, List(), List(Constructor(ctor, List(), fields)))) =>
val fieldParams = fields.map { case Field(name, tpe) => ValueParam(Id(name), tpe) }
val nestedResults = fieldParams.map { param => flattenArg(ValueVar(param.id, param.tpe), param.tpe) }
val (nestedVars, nestedTypes, nestedMatches) = nestedResults.unzip3
val thisMatch = (arg, ctor, fieldParams)
(nestedVars.flatten, nestedTypes.flatten, thisMatch :: nestedMatches.flatten)

case _ => (List(arg), List(argType), List())
}
case _ => (List(arg), List(argType), List())
}

val flattened = (vargs zip vparamsTypes).map { case (arg, tpe) => flattenArg(arg, tpe) }
val (allArgs, allTypes, allMatches) = flattened.unzip3

val transformedBargs = bargs.map { barg =>
barg match {
// This handles:
// val res = myList.map {myFunc}
// by making it:
// val res = myList.map {t => myFunc(t)}
// but only if the arity of myFunc changes
case bvar @ BlockVar(id, annotatedTpe, annotatedCapt) if !boundBlockParams.contains(id) =>
wrapBlockVarIfNeeded(bvar, annotatedTpe)

case BlockLit(btparams, bcparams, bvparams, bbparams, body) =>
BlockLit(btparams, bcparams, bvparams, bbparams, transform(body)(using C, DC, boundBlockParams ++ bbparams.map(_.id)))

case _ =>
transform(barg)
}
}

val newCalleTpe: BlockType.Function = BlockType.Function(tparams, cparams, allTypes.flatten, bparamTypes, returnTpe)
val newCallee = BlockVar(id, newCalleTpe, annotatedCapt)
val innerApp = Stmt.App(newCallee, targs, allArgs.flatten, transformedBargs)

allMatches.flatten.foldRight(innerApp) {
case ((scrutinee, ctor, params), body) =>
val resultTpe = instantiate(newCalleTpe, targs, bargs.map(_.capt)).result
Stmt.Match(scrutinee, resultTpe, List((ctor, BlockLit(List(), List(), params, List(), body))), None)
}

case Stmt.App(callee, targs, vargs, bargs) =>
Stmt.App(callee, targs, vargs map transform, bargs map transform)

case Stmt.Def(id, block, rest) =>
Stmt.Def(id, transform(block), transform(rest))

case Stmt.Let(id, binding, rest) =>
Stmt.Let(id, transform(binding), transform(rest))

case Stmt.Return(expr) =>
Stmt.Return(transform(expr))

case Stmt.Val(id, binding, body) =>
Stmt.Val(id, transform(binding), transform(body))

case Stmt.Invoke(callee, method, methodTpe, targs, vargs, bargs) =>
Stmt.Invoke(transform(callee), method, methodTpe, targs, vargs map transform, bargs map transform)

case Stmt.If(cond, thn, els) =>
Stmt.If(transform(cond), transform(thn), transform(els))
case Stmt.Match(scrutinee, tpe, clauses, default) =>
Stmt.Match(transform(scrutinee), tpe, clauses.map { case (id, BlockLit(tparams, cparams, vparams, bparams, body)) =>
(id, BlockLit(tparams, cparams, vparams, bparams, transform(body)(using C, DC, boundBlockParams ++ bparams.map(_.id))))
}, default map transform)

case Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) =>
Stmt.ImpureApp(id, callee, targs, vargs map transform, bargs map transform, transform(body))

case Stmt.Region(BlockLit(tparams, cparams, vparams, bparams, body)) =>
Stmt.Region(BlockLit(tparams, cparams, vparams, bparams, transform(body)(using C, DC, boundBlockParams ++ bparams.map(_.id))))

case Stmt.Alloc(id, init, region, body) =>
Stmt.Alloc(id, transform(init), region, transform(body))

case Stmt.Var(ref, init, capture, body) =>
Stmt.Var(ref, transform(init), capture, transform(body))

case Stmt.Get(id, annotatedTpe, ref, annotatedCapt, body) =>
Stmt.Get(id, annotatedTpe, ref, annotatedCapt, transform(body))

case Stmt.Put(ref, annotatedCapt, value, body) =>
Stmt.Put(ref, annotatedCapt, transform(value), transform(body))

case Stmt.Reset(BlockLit(tparams, cparams, vparams, bparams, body)) =>
Stmt.Reset(BlockLit(tparams, cparams, vparams, bparams, transform(body)(using C, DC, boundBlockParams ++ bparams.map(_.id))))

case Stmt.Shift(prompt, k, body) =>
// k is a continuation (block param), so add it to boundBlockParams
Stmt.Shift(prompt, k, transform(body)(using C, DC, boundBlockParams + k.id))

case Stmt.Resume(k, body) =>
Stmt.Resume(k, transform(body))

case Stmt.Hole(tpe, span) =>
Stmt.Hole(tpe, span)
}

def transform(pure: Expr)(using C: Context, DC: DeclarationContext, boundBlockParams: Set[Id]): Expr = pure match {
case Expr.ValueVar(id, annotatedType) => pure

case Expr.Literal(value, annotatedType) => pure

case Expr.Box(bvar @ BlockVar(id, annotatedTpe, annotatedCapt), annotatedCapture) if !boundBlockParams.contains(id) =>
Expr.Box(wrapBlockVarIfNeeded(bvar, annotatedTpe), annotatedCapture)

case Expr.Box(b, annotatedCapture) =>
Expr.Box(transform(b), annotatedCapture)

case Expr.PureApp(b, targs, vargs) =>
Expr.PureApp(b, targs, vargs map transform)

case Expr.Make(data, tag, targs, vargs) =>
Expr.Make(data, tag, targs, vargs map transform)
}

def transform(valueType: ValueType.Data)(using C: Context, DC: DeclarationContext): ValueType.Data = valueType match {
case ValueType.Data(symbol, targs) => valueType
}
}
Loading