From 7adcd2317bc68ebabcd1834121adc08a0d446cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattis=20B=C3=B6ckle?= Date: Mon, 18 May 2026 22:00:38 +0200 Subject: [PATCH 01/15] Auto generate wrappers for C functions --- .../effekt/generator/llvm/PrettyPrinter.scala | 3 + .../effekt/generator/llvm/Transformer.scala | 101 ++++++++++++++---- .../scala/effekt/generator/llvm/Tree.scala | 2 + .../scala/effekt/machine/PrettyPrinter.scala | 1 + .../scala/effekt/machine/Transformer.scala | 28 ++++- .../src/main/scala/effekt/machine/Tree.scala | 1 + libraries/common/effekt.effekt | 32 +++++- 7 files changed, 145 insertions(+), 23 deletions(-) diff --git a/effekt/shared/src/main/scala/effekt/generator/llvm/PrettyPrinter.scala b/effekt/shared/src/main/scala/effekt/generator/llvm/PrettyPrinter.scala index ca1eb8da2..a0f7afe3a 100644 --- a/effekt/shared/src/main/scala/effekt/generator/llvm/PrettyPrinter.scala +++ b/effekt/shared/src/main/scala/effekt/generator/llvm/PrettyPrinter.scala @@ -122,6 +122,8 @@ ${indentedLines(instructions.map(show).mkString("\n"))} def show(terminator: Terminator): LLVMString = terminator match { case RetVoid() => s"ret void" + case Ret(operand) => + s"ret ${show(operand)}" case Switch(operand, defaultDest, dests) => def destAsFragment(dest: (Int, String)) = s"i64 ${dest._1}, label ${localName(dest._2)}"; s"switch ${show(operand)}, label ${localName(defaultDest)} [${spaceSeparated(dests.map(destAsFragment))}]" @@ -146,6 +148,7 @@ ${indentedLines(instructions.map(show).mkString("\n"))} case IntegerType1() => "i1" case IntegerType8() => "i8" case IntegerType64() => "i64" + case FloatType() => "float" case DoubleType() => "double" case PointerType() => "ptr" case ArrayType(size, of) => s"[$size x ${show(of)}]" 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 5100a2771..22449e542 100644 --- a/effekt/shared/src/main/scala/effekt/generator/llvm/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/generator/llvm/Transformer.scala @@ -9,15 +9,18 @@ import effekt.machine.analysis.* import scala.annotation.tailrec import scala.collection.mutable +import effekt.machine.ExternBody.StringExternBody +import effekt.machine.Variable object Transformer { - val llvmFeatureFlags: List[String] = List("llvm") + val llvmFeatureFlags: List[String] = List("llvm", "c") def transform(program: machine.Program)(using ErrorReporter): List[Definition] = program match { case machine.Program(declarations, definitions, entry) => given MC: ModuleContext = ModuleContext(); + declarations.foreach(transform); definitions.foreach(transform); val globals = MC.definitions; MC.definitions = null; @@ -27,8 +30,8 @@ object Transformer { 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)) - - declarations.map(transform) ++ globals :+ entryFunction + + globals :+ entryFunction } // context getters @@ -36,33 +39,54 @@ object Transformer { private def FC(using FC: FunctionContext): FunctionContext = FC private def BC(using BC: BlockContext): BlockContext = BC - def transform(declaration: machine.Declaration)(using ErrorReporter): Definition = + def transform(declaration: machine.Declaration)(using ErrorReporter, ModuleContext): Unit = declaration match { + case machine.Extern(functionName, parameters, returnType, async, body@StringExternBody(featureFlag, contents)) if featureFlag.matches("c") => + // FIXME: We could explicitly disallow CVoid as a type in parameter position + val transformedParameters = parameters.map { case machine.Variable(name, tpe) => Parameter(transform(tpe), name) } + val transformedReturnType = transform(returnType) + + // Function name was already parsed in machine transformer + val cFunctionName = contents.strings.head + val returnLLVMType = PrettyPrinter.show(transformedReturnType) + val parameterLLVMTypes = transformedParameters.map(param => PrettyPrinter.show(param.typ)) + + emit(Verbatim(s"declare ${returnLLVMType} @${cFunctionName}(${parameterLLVMTypes.mkString(", ")})")) + defineCWrapperFunction(functionName, transformedParameters, transformedReturnType) { + emit(Comment(s"C wrapper function for '${cFunctionName}'")) + // TODO: Check calling convention here + emit(Call("ccall", Ccc(), transformedReturnType, ConstantGlobal(cFunctionName), transformedParameters.map { case Parameter(typ, name) => LocalReference(typ, name) })) + + transformedReturnType match { + case VoidType() => RetVoid() + case _ => Ret(LocalReference(transformedReturnType, "ccall")) + } + } case machine.Extern(functionName, parameters, returnType, async, body) => val transformedParameters = parameters.map { case machine.Variable(name, tpe) => Parameter(transform(tpe), name) } if (async) { - VerbatimFunction(Tailcc(true), VoidType(), functionName, transformedParameters :+ Parameter(stackType, "stack"), transform(body)) + emit(VerbatimFunction(Tailcc(true), VoidType(), functionName, transformedParameters :+ Parameter(stackType, "stack"), transform(body))) } else { - VerbatimFunction(Ccc(), transform(returnType), functionName, transformedParameters, transform(body)) + emit(VerbatimFunction(Ccc(), transform(returnType), functionName, transformedParameters, transform(body))) } case machine.ExternType(name, tps, machine.ExternBody.StringExternBody(_, body)) => val ttps = tps match { case Nil => "" case tps => tps.mkString(", ") } - Verbatim(s"; declaration extern type ${name}${ttps} = ${body}") + emit(Verbatim(s"; declaration extern type ${name}${ttps} = ${body}")) case machine.ExternType(name, tps, machine.ExternBody.Unsupported(err)) => - Verbatim(s"; unsupported extern type ${name}: ${err}") + emit(Verbatim(s"; unsupported extern type ${name}: ${err}")) case machine.ExternInterface(name, tps, machine.ExternBody.StringExternBody(_, body)) => val ttps = tps match { case Nil => "" case tps => tps.mkString(", ") } - Verbatim(s"; declaration extern interface ${name}${ttps} = ${body}") + emit(Verbatim(s"; declaration extern interface ${name}${ttps} = ${body}")) case machine.ExternInterface(name, tps, machine.ExternBody.Unsupported(err)) => - Verbatim(s"; unsupported extern interface ${name}: ${err}") + emit(Verbatim(s"; unsupported extern interface ${name}: ${err}")) case machine.Include(ff, content) => - Verbatim("; declaration include" ++ content) + emit(Verbatim("; declaration include" ++ content)) } def transform(body: machine.ExternBody[machine.Variable])(using ErrorReporter): String = body match { @@ -442,6 +466,17 @@ object Transformer { val promptType = NamedType("Prompt"); val referenceType = NamedType("Reference"); + def transform(tpe: machine.Type.CType): Type = tpe match { + case machine.Type.CType(name) => name match { + case "CString" => PointerType() + case "CInt" => IntegerType64() + case "CDouble" => DoubleType() + case "CFloat" => FloatType() + case "CPtr" => PointerType() + case "CVoid" => VoidType() + } + } + def transform(tpe: machine.Type): Type = tpe match { case machine.Positive() => positiveType case machine.Negative() => negativeType @@ -451,21 +486,34 @@ object Transformer { case machine.Type.Byte() => IntegerType8() case machine.Type.Double() => DoubleType() case machine.Type.Reference(tpe) => referenceType + case CT@machine.Type.CType(name) => transform(CT) } def environmentSize(environment: machine.Environment): Int = environment.map { case machine.Variable(_, typ) => typeSize(typ) }.sum + def typeSize(tpe: machine.Type.CType): Int = tpe match { + case machine.Type.CType(name) => name match { + case "CString" => 8 + case "CInt" => 8 + case "CDouble" => 8 + case "CFloat" => 4 + case "CPtr" => 8 + case "CVoid" => 0 + } + } + def typeSize(tpe: machine.Type): Int = tpe match { - case machine.Positive() => 16 - case machine.Negative() => 16 - case machine.Type.Prompt() => 8 // TODO Make fat? - case machine.Type.Stack() => 8 // TODO Make fat? - case machine.Type.Int() => 8 // TODO Make fat? - case machine.Type.Byte() => 1 - case machine.Type.Double() => 8 // TODO Make fat? - case machine.Type.Reference(_) => 16 + case machine.Positive() => 16 + case machine.Negative() => 16 + case machine.Type.Prompt() => 8 // TODO Make fat? + case machine.Type.Stack() => 8 // TODO Make fat? + case machine.Type.Int() => 8 // TODO Make fat? + case machine.Type.Byte() => 1 + case machine.Type.Double() => 8 // TODO Make fat? + case machine.Type.Reference(_) => 16 + case CT@machine.Type.CType(name) => typeSize(CT) } def defineFunction(name: String, parameters: List[Parameter])(prog: (FunctionContext, BlockContext) ?=> Terminator): ModuleContext ?=> Unit = { @@ -483,6 +531,21 @@ object Transformer { emit(function) } + def defineCWrapperFunction(name: String, parameters: List[Parameter], returnType: Type)(prog: (FunctionContext, BlockContext) ?=> Terminator): ModuleContext ?=> Unit = { + implicit val FC = FunctionContext(); + implicit val BC = BlockContext(); + + val terminator = prog; + + val basicBlocks = FC.basicBlocks; FC.basicBlocks = null; + val instructions = BC.instructions; BC.instructions = null; + + val entryBlock = BasicBlock("entry", instructions, terminator); + val function = Function(Private(), Ccc(), returnType, name, parameters, entryBlock :: basicBlocks); + + emit(function) + } + def defineLabel(name: String, parameters: List[Parameter])(prog: (FunctionContext, BlockContext) ?=> Terminator): ModuleContext ?=> Unit = { implicit val FC = FunctionContext(); implicit val BC = BlockContext(); diff --git a/effekt/shared/src/main/scala/effekt/generator/llvm/Tree.scala b/effekt/shared/src/main/scala/effekt/generator/llvm/Tree.scala index bad907f93..3c4517c95 100644 --- a/effekt/shared/src/main/scala/effekt/generator/llvm/Tree.scala +++ b/effekt/shared/src/main/scala/effekt/generator/llvm/Tree.scala @@ -77,6 +77,7 @@ export Instruction.* enum Terminator { case RetVoid() + case Ret(operand: Operand) case Switch(operand: Operand, defaultDest: String, dests: List[(Int, String)]) case CondBr(condition: Operand, trueDest: String, falseDest: String) } @@ -121,6 +122,7 @@ enum Type { case IntegerType1() case IntegerType8() // required for `void*` (which only exists as `i8*` in LLVM) and `char*` case IntegerType64() + case FloatType() case DoubleType() case PointerType() case ArrayType(size: Int, of: Type) diff --git a/effekt/shared/src/main/scala/effekt/machine/PrettyPrinter.scala b/effekt/shared/src/main/scala/effekt/machine/PrettyPrinter.scala index 019cd9680..2d21baa8e 100644 --- a/effekt/shared/src/main/scala/effekt/machine/PrettyPrinter.scala +++ b/effekt/shared/src/main/scala/effekt/machine/PrettyPrinter.scala @@ -36,6 +36,7 @@ object PrettyPrinter extends ParenPrettyPrinter { case Type.Byte() => "Byte" case Type.Double() => "Double" case Type.Reference(tpe) => toDoc(tpe) <> "*" + case Type.CType(name) => s"CType(${name})" } def toDoc(defi: Definition): Doc = defi match { diff --git a/effekt/shared/src/main/scala/effekt/machine/Transformer.scala b/effekt/shared/src/main/scala/effekt/machine/Transformer.scala index d49d7cce9..86f88b3ef 100644 --- a/effekt/shared/src/main/scala/effekt/machine/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/machine/Transformer.scala @@ -88,7 +88,22 @@ object Transformer { ExternInterface(transform(id), tparams.map(transform), tBody) } + val validCTypes = List("CString", "CInt", "CDouble", "CFloat", "CPtr", "CVoid") + + def handleExternC(template: Template[core.Expr])(using ErrorReporter): Template[Variable] = template match { + case Template(strings, args) => + val cFunName = strings.head.split(" ").head.trim() + + Template(List(cFunName), args map { + case core.ValueVar(id, core.ValueType.Data(name, targs)) if validCTypes.contains(name.name.name) => + Variable(transform(id), machine.Type.CType(name.name.name)) + case _ => ErrorReporter.abort(s"In the C backend, only types '${validCTypes}' are allowed in templates") + }) + } + def transform(body: core.ExternBody[core.Expr])(using ErrorReporter): machine.ExternBody[Variable] = body match { + case core.ExternBody.StringExternBody(ff, template) if ff.matches("c") => + ExternBody.StringExternBody(ff, handleExternC(template)) case core.ExternBody.StringExternBody(ff, Template(strings, args)) => ExternBody.StringExternBody(ff, Template(strings, args map { case core.ValueVar(id, tpe) => Variable(transform(id), transform(tpe)) @@ -172,13 +187,15 @@ object Transformer { } case core.ImpureApp(id, core.BlockVar(blockName, core.BlockType.Function(_, _, vparamTypes, _, resultType), capt), targs, vargs, bargs, rest) => - val variable = Variable(transform(id), Positive()) + val variable = Variable(transform(id), transform(resultType)) transform(rest).flatMap { rest => transform(vargs, bargs).run { (values, blocks) => perhapsUnbox(values, vparamTypes).run { unboxeds => transformUnboxed(resultType) match { case Type.Positive() => Trampoline.Done(ForeignCall(variable, transform(blockName), unboxeds ++ blocks, rest)) + case Type.CType(_) => + Trampoline.Done(ForeignCall(variable, transform(blockName), unboxeds ++ blocks, rest)) case unboxedTpe => val unboxed = Variable(freshName("unboxed"), unboxedTpe) Trampoline.Done(ForeignCall(unboxed, transform(blockName), unboxeds ++ blocks, Coerce(variable, unboxed, rest))) @@ -505,6 +522,8 @@ object Transformer { transformUnboxed(resultType) match { case Type.Positive() => ForeignCall(variable, transform(blockName), unboxeds, k(variable)) + case Type.CType(_) => + ForeignCall(variable, transform(blockName), unboxeds, k(variable)) case unboxedTpe => val unboxed = Variable(freshName("unboxed"), unboxedTpe) ForeignCall(unboxed, transform(blockName), unboxeds, Coerce(variable, unboxed, k(variable))) @@ -559,8 +578,10 @@ object Transformer { Variable(transform(name), transform(tpe)) } - def transform(tpe: core.ValueType): Type = - Positive() + def transform(tpe: core.ValueType): Type = tpe match { + case core.ValueType.Data(name, _) if validCTypes.contains(name.name.name) => Type.CType(name.name.name) + case _ => Positive() + } def transformUnboxed(tpe: core.ValueType): Type = tpe match { @@ -568,6 +589,7 @@ object Transformer { case core.Type.TChar => Type.Int() case core.Type.TByte => Type.Byte() case core.Type.TDouble => Type.Double() + case core.ValueType.Data(name, _) if validCTypes.contains(name.name.name) => Type.CType(name.name.name) case _ => Positive() } diff --git a/effekt/shared/src/main/scala/effekt/machine/Tree.scala b/effekt/shared/src/main/scala/effekt/machine/Tree.scala index bf9e5514b..a789f74ef 100644 --- a/effekt/shared/src/main/scala/effekt/machine/Tree.scala +++ b/effekt/shared/src/main/scala/effekt/machine/Tree.scala @@ -221,6 +221,7 @@ enum Type { case Byte() case Double() case Reference(tpe: Type) + case CType(name: String) } export Type.{ Positive, Negative } diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index 741fda813..e9c41f4e6 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -794,4 +794,34 @@ effect splice[A](x: A): Unit // When adding an implicit value argument ?sourcePosition of this type, will have the information // of the call site unless passed explicitly. -record SourcePosition(file: String, startLine: Int, startColumn: Int, endLine: Int, endColumn: Int) \ No newline at end of file +record SourcePosition(file: String, startLine: Int, startColumn: Int, endLine: Int, endColumn: Int) + +// C backend definitions +// ===================== +// +// Type definitions & constructors for C types +// Should only be used to automatically generate effekt wrappers for C functions + +namespace C { + // FIXME: The right-hand definitions do not currently matter, + // but they do correctly represent what the types are implemented as + extern type CString = llvm "ptr" + extern type CInt = llvm "i64" + extern type CDouble = llvm "double" + extern type CFloat = llvm "float" + extern type CPtr = llvm "ptr" + extern type CVoid = llvm "void" + + extern def string(value: String): CString = + llvm """ + %conv = call ptr @c_bytearray_into_nullterminated_string(%Pos ${value}) + ret ptr %conv + """ + extern def int(value: Int): CInt = llvm "ret %Int ${value}" + extern def double(value: Double): CDouble = llvm "ret %Double ${value}" + extern def float(value: Double): CFloat = llvm """ + %trunc = fptrunc %Double ${value} to float + ret float %trunc + """ + extern def nullptr(): CPtr = llvm "ret ptr null" +} From c7fa34f21161b062cc7d1f0bb9342f9f367c8e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattis=20B=C3=B6ckle?= Date: Sat, 23 May 2026 21:45:55 +0200 Subject: [PATCH 02/15] Add option to read file config --- .../src/main/scala/effekt/EffektConfig.scala | 26 +++++++++++++++++++ effekt/jvm/src/main/scala/effekt/Runner.scala | 15 +++++++++++ 2 files changed, 41 insertions(+) diff --git a/effekt/jvm/src/main/scala/effekt/EffektConfig.scala b/effekt/jvm/src/main/scala/effekt/EffektConfig.scala index 154b49974..662b06915 100644 --- a/effekt/jvm/src/main/scala/effekt/EffektConfig.scala +++ b/effekt/jvm/src/main/scala/effekt/EffektConfig.scala @@ -4,6 +4,7 @@ import java.io.File import effekt.util.paths.file import kiama.util.REPLConfig import org.rogach.scallop.{ ScallopOption, fileConverter, fileListConverter, longConverter, stringConverter, stringListConverter } +import scala.io.Source class EffektConfig(args: Seq[String]) extends REPLConfig(args.takeWhile(_ != "--")) { // Print version information on `--version` and `--help` @@ -88,6 +89,13 @@ class EffektConfig(args: Seq[String]) extends REPLConfig(args.takeWhile(_ != "-- group = advanced ) + val configFile: ScallopOption[File] = opt[File]( + "config", + descr = "Toml config file for additional configurations", + noshort = true, + group = advanced + ) + val native: ScallopOption[Boolean] = toggle( "native", descrYes = "Optimize the executable for the native CPU. May break executable on other Devices. Only works for the LLVM backend", @@ -268,3 +276,21 @@ class EffektConfig(args: Seq[String]) extends REPLConfig(args.takeWhile(_ != "-- // force some other configs manually to initialize them when compiling with native-image server; output; filenames } + +import toml.derivation.auto._ + +case class LLVMTable(libraries: List[String], includes: List[String], linked: List[String], sources: List[String]) +case class ConfigRoot(llvm: Option[LLVMTable]) + +def readEffektConfigToml(file: String): ConfigRoot = { + val configFile = Source.fromFile(file) + val fileContents = configFile.getLines.mkString + configFile.close + + val parsed = toml.Toml.parseAs[ConfigRoot](fileContents) + parsed match { + case Left((tables, err)) => sys.error(s"Failed to parse '${file}', ${err} in ${tables}") + case Right(value) => value + } +} + diff --git a/effekt/jvm/src/main/scala/effekt/Runner.scala b/effekt/jvm/src/main/scala/effekt/Runner.scala index 5c0c3fe9c..ac37a157e 100644 --- a/effekt/jvm/src/main/scala/effekt/Runner.scala +++ b/effekt/jvm/src/main/scala/effekt/Runner.scala @@ -300,6 +300,18 @@ object LLVMRunner extends Runner[String] { lazy val clangCmd = discoverExecutable(List("clang-21", "clang-20", "clang-19", "clang-18", "clang"), List("--version")) + def additionalClangArgs(file: String): Seq[String] = { + val configFile = readEffektConfigToml(file) + val llvmConfig = configFile.llvm.getOrElseAborting(return Seq.empty) + + var additionalArgs = llvmConfig.includes.map(inc => s"-I${inc}") + additionalArgs ++= llvmConfig.libraries.map(lib => s"-L${lib}") + additionalArgs ++= llvmConfig.linked.map(link => s"-l${link}") + additionalArgs ++= llvmConfig.sources + + additionalArgs + } + def checkSetup(): Either[String, Unit] = clangCmd.getOrElseAborting { return Left("Cannot find clang. This is required to use the LLVM backend.") } Right(()) @@ -376,6 +388,9 @@ object LLVMRunner extends Runner[String] { clangArgs ++= Seq("-O3", "-flto=full") } + clangArgs ++= C.config.configFile.toOption.map(file => additionalClangArgs(file.unixPath)).getOrElse(Seq.empty) + + println(clangArgs.mkString(" ")) exec(clangArgs: _*) Some(executableFile) From 0333bb7dbddf391bfd46056c4b29ba01a7da982e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattis=20B=C3=B6ckle?= Date: Sat, 23 May 2026 21:46:29 +0200 Subject: [PATCH 03/15] Mark ctype conversion functions as pure --- libraries/common/effekt.effekt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index e9c41f4e6..04354dbfe 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -817,11 +817,11 @@ namespace C { %conv = call ptr @c_bytearray_into_nullterminated_string(%Pos ${value}) ret ptr %conv """ - extern def int(value: Int): CInt = llvm "ret %Int ${value}" - extern def double(value: Double): CDouble = llvm "ret %Double ${value}" - extern def float(value: Double): CFloat = llvm """ + extern def int(value: Int) at {}: CInt = llvm "ret %Int ${value}" + extern def double(value: Double) at {}: CDouble = llvm "ret %Double ${value}" + extern def float(value: Double) at {}: CFloat = llvm """ %trunc = fptrunc %Double ${value} to float ret float %trunc """ - extern def nullptr(): CPtr = llvm "ret ptr null" + extern def nullptr() at {}: CPtr = llvm "ret ptr null" } From e6076e82c0afc794661386e1e280ef04be7b4681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattis=20B=C3=B6ckle?= Date: Sat, 23 May 2026 21:47:31 +0200 Subject: [PATCH 04/15] Add glfw & config example --- effekt-config.toml | 5 ++++ glfw.effekt | 74 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 effekt-config.toml create mode 100644 glfw.effekt diff --git a/effekt-config.toml b/effekt-config.toml new file mode 100644 index 000000000..1017c4870 --- /dev/null +++ b/effekt-config.toml @@ -0,0 +1,5 @@ +[llvm] +libraries = [] +includes = ["vendor/include"] +linked = ["glfw3", "GL"] +sources = ["vendor/src/gl.c"] \ No newline at end of file diff --git a/glfw.effekt b/glfw.effekt new file mode 100644 index 000000000..56ac8810f --- /dev/null +++ b/glfw.effekt @@ -0,0 +1,74 @@ +extern def isNull(ptr: C::CPtr): Bool = llvm """ + %cmp = icmp eq ptr ${ptr}, null + %ext = zext i1 %cmp to i64 + %bool = insertvalue %Pos zeroinitializer, i64 %ext, 0 + ret %Pos %bool +""" + +extern def asBool(value: C::CInt): Bool = llvm """ + %cmp = icmp eq %Int ${value}, 0 + %ext = zext i1 %cmp to i64 + %bool = insertvalue %Pos zeroinitializer, i64 %ext, 0 + ret %Pos %bool +""" + +extern llvm """ + declare ptr @glfwGetProcAddress(ptr) + declare i32 @gladLoadGL(ptr) +""" + +extern def gladLoadGL(): Unit = llvm """ + call i32 @gladLoadGL(ptr noundef @glfwGetProcAddress) + ret %Pos zeroinitializer +""" + +// 0x00022002 +val GLFW_CONTEXT_VERSION_MAJOR = C::int(139266) +// 0x00022003 +val GLFW_CONTEXT_VERSION_MINOR = C::int(139267) +// 0x00022008 +val GLFW_OPENGL_PROFILE = C::int(139272) +// 0x00032001 +val GLFW_OPENGL_CORE_PROFILE = C::int(204801) + +extern def glfwInit(): C::CVoid = c "glfwInit" +extern def glfwTerminate(): C::CVoid = c "glfwTerminate" +extern def glfwWindowHint(version: C::CInt, value: C::CInt): C::CVoid = c "glfwWindowHint" + +type GLFWwindow = C::CPtr +extern def glfwCreateWindow(width: C::CInt, height: C::CInt, title: C::CString, monitor: C::CPtr, share: GLFWwindow): GLFWwindow = c "glfwCreateWindow" +extern def glfwMakeContextCurrent(window: GLFWwindow): C::CVoid = c "glfwMakeContextCurrent" +extern def glfwWindowShouldClose(window: GLFWwindow): C::CInt = c "glfwWindowShouldClose" +def glfwWindowShouldClose(window: GLFWwindow): Bool = glfwWindowShouldClose(window).asBool + +extern def glfwSwapBuffers(window: GLFWwindow): C::CVoid = c "glfwSwapBuffers" +extern def glfwPollEvents(): C::CVoid = c "glfwPollEvents" + + + +def main() = { + glfwInit() + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, C::int(3)) + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, C::int(3)) + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE) + + val window = glfwCreateWindow(C::int(800), C::int(600), C::string("Effekt Example Window"), C::nullptr(), C::nullptr()) + + if (window.isNull()) { + println("Failed to create window") + glfwTerminate() + return () + } + glfwMakeContextCurrent(window) + + gladLoadGL() + + while(not(glfwWindowShouldClose(window))) { + glfwSwapBuffers(window) + glfwPollEvents() + () + } + + glfwTerminate() + () +} \ No newline at end of file From 98ade698e05ce408a644180f2fb32fe4f4099d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattis=20B=C3=B6ckle?= Date: Mon, 8 Jun 2026 12:29:14 +0200 Subject: [PATCH 05/15] Revert "Add option to read file config" This reverts commit c7fa34f21161b062cc7d1f0bb9342f9f367c8e03. --- .../src/main/scala/effekt/EffektConfig.scala | 26 ------------------- effekt/jvm/src/main/scala/effekt/Runner.scala | 15 ----------- 2 files changed, 41 deletions(-) diff --git a/effekt/jvm/src/main/scala/effekt/EffektConfig.scala b/effekt/jvm/src/main/scala/effekt/EffektConfig.scala index 662b06915..154b49974 100644 --- a/effekt/jvm/src/main/scala/effekt/EffektConfig.scala +++ b/effekt/jvm/src/main/scala/effekt/EffektConfig.scala @@ -4,7 +4,6 @@ import java.io.File import effekt.util.paths.file import kiama.util.REPLConfig import org.rogach.scallop.{ ScallopOption, fileConverter, fileListConverter, longConverter, stringConverter, stringListConverter } -import scala.io.Source class EffektConfig(args: Seq[String]) extends REPLConfig(args.takeWhile(_ != "--")) { // Print version information on `--version` and `--help` @@ -89,13 +88,6 @@ class EffektConfig(args: Seq[String]) extends REPLConfig(args.takeWhile(_ != "-- group = advanced ) - val configFile: ScallopOption[File] = opt[File]( - "config", - descr = "Toml config file for additional configurations", - noshort = true, - group = advanced - ) - val native: ScallopOption[Boolean] = toggle( "native", descrYes = "Optimize the executable for the native CPU. May break executable on other Devices. Only works for the LLVM backend", @@ -276,21 +268,3 @@ class EffektConfig(args: Seq[String]) extends REPLConfig(args.takeWhile(_ != "-- // force some other configs manually to initialize them when compiling with native-image server; output; filenames } - -import toml.derivation.auto._ - -case class LLVMTable(libraries: List[String], includes: List[String], linked: List[String], sources: List[String]) -case class ConfigRoot(llvm: Option[LLVMTable]) - -def readEffektConfigToml(file: String): ConfigRoot = { - val configFile = Source.fromFile(file) - val fileContents = configFile.getLines.mkString - configFile.close - - val parsed = toml.Toml.parseAs[ConfigRoot](fileContents) - parsed match { - case Left((tables, err)) => sys.error(s"Failed to parse '${file}', ${err} in ${tables}") - case Right(value) => value - } -} - diff --git a/effekt/jvm/src/main/scala/effekt/Runner.scala b/effekt/jvm/src/main/scala/effekt/Runner.scala index ac37a157e..5c0c3fe9c 100644 --- a/effekt/jvm/src/main/scala/effekt/Runner.scala +++ b/effekt/jvm/src/main/scala/effekt/Runner.scala @@ -300,18 +300,6 @@ object LLVMRunner extends Runner[String] { lazy val clangCmd = discoverExecutable(List("clang-21", "clang-20", "clang-19", "clang-18", "clang"), List("--version")) - def additionalClangArgs(file: String): Seq[String] = { - val configFile = readEffektConfigToml(file) - val llvmConfig = configFile.llvm.getOrElseAborting(return Seq.empty) - - var additionalArgs = llvmConfig.includes.map(inc => s"-I${inc}") - additionalArgs ++= llvmConfig.libraries.map(lib => s"-L${lib}") - additionalArgs ++= llvmConfig.linked.map(link => s"-l${link}") - additionalArgs ++= llvmConfig.sources - - additionalArgs - } - def checkSetup(): Either[String, Unit] = clangCmd.getOrElseAborting { return Left("Cannot find clang. This is required to use the LLVM backend.") } Right(()) @@ -388,9 +376,6 @@ object LLVMRunner extends Runner[String] { clangArgs ++= Seq("-O3", "-flto=full") } - clangArgs ++= C.config.configFile.toOption.map(file => additionalClangArgs(file.unixPath)).getOrElse(Seq.empty) - - println(clangArgs.mkString(" ")) exec(clangArgs: _*) Some(executableFile) From ed0d726a75ccd3c754d0c5cf9eada6573ae597e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattis=20B=C3=B6ckle?= Date: Mon, 8 Jun 2026 13:20:36 +0200 Subject: [PATCH 06/15] Validate right side of extern types in C backend --- .../effekt/generator/llvm/Transformer.scala | 42 ++++++++--------- .../scala/effekt/machine/Transformer.scala | 47 +++++++++++++------ 2 files changed, 52 insertions(+), 37 deletions(-) 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 22449e542..9d542af59 100644 --- a/effekt/shared/src/main/scala/effekt/generator/llvm/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/generator/llvm/Transformer.scala @@ -468,38 +468,36 @@ object Transformer { def transform(tpe: machine.Type.CType): Type = tpe match { case machine.Type.CType(name) => name match { - case "CString" => PointerType() - case "CInt" => IntegerType64() - case "CDouble" => DoubleType() - case "CFloat" => FloatType() - case "CPtr" => PointerType() - case "CVoid" => VoidType() + case "ptr" => PointerType() + case "i64" => IntegerType64() + case "double" => DoubleType() + case "float" => FloatType() + case "void" => VoidType() } } def transform(tpe: machine.Type): Type = tpe match { - case machine.Positive() => positiveType - case machine.Negative() => negativeType - case machine.Type.Prompt() => promptType - case machine.Type.Stack() => resumptionType - case machine.Type.Int() => IntegerType64() - case machine.Type.Byte() => IntegerType8() - case machine.Type.Double() => DoubleType() - case machine.Type.Reference(tpe) => referenceType - case CT@machine.Type.CType(name) => transform(CT) - } + case machine.Positive() => positiveType + case machine.Negative() => negativeType + case machine.Type.Prompt() => promptType + case machine.Type.Stack() => resumptionType + case machine.Type.Int() => IntegerType64() + case machine.Type.Byte() => IntegerType8() + case machine.Type.Double() => DoubleType() + case machine.Type.Reference(tpe) => referenceType + case CT@machine.Type.CType(name) => transform(CT) + } def environmentSize(environment: machine.Environment): Int = environment.map { case machine.Variable(_, typ) => typeSize(typ) }.sum def typeSize(tpe: machine.Type.CType): Int = tpe match { case machine.Type.CType(name) => name match { - case "CString" => 8 - case "CInt" => 8 - case "CDouble" => 8 - case "CFloat" => 4 - case "CPtr" => 8 - case "CVoid" => 0 + case "ptr" => 8 + case "i64" => 8 + case "double" => 8 + case "float" => 4 + case "void" => 0 } } diff --git a/effekt/shared/src/main/scala/effekt/machine/Transformer.scala b/effekt/shared/src/main/scala/effekt/machine/Transformer.scala index 86f88b3ef..f9e4bb1f2 100644 --- a/effekt/shared/src/main/scala/effekt/machine/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/machine/Transformer.scala @@ -13,6 +13,8 @@ import effekt.util.UByte import effekt.util.{ DB, toDB } import scala.annotation.tailrec +import effekt.core.ExternBody.StringExternBody +import effekt.core.ExternBody.Unsupported object Transformer { @@ -55,7 +57,7 @@ object Transformer { Program(declarations, toplevelDefinitions ++ localDefinitions, mainEntry) } - def transform(extern: core.Extern)(using BlocksParamsContext, ErrorReporter): Declaration = extern match { + def transform(extern: core.Extern)(using BlocksParamsContext, DeclarationContext, ErrorReporter): Declaration = extern match { case core.Extern.Def(name, qualifiedSignature, tps, cparams, vparams, bparams, ret, capture, body) => // TODO delete, and/or enforce at call site (ImpureApp) if bparams.nonEmpty then ErrorReporter.abort("Foreign functions currently cannot take block arguments.") @@ -88,20 +90,35 @@ object Transformer { ExternInterface(transform(id), tparams.map(transform), tBody) } - val validCTypes = List("CString", "CInt", "CDouble", "CFloat", "CPtr", "CVoid") + val validCTypes = List("ptr", "i64", "double", "float", "void") - def handleExternC(template: Template[core.Expr])(using ErrorReporter): Template[Variable] = template match { + def getValidExternC(name: Id)(using DC: DeclarationContext)(using ErrorReporter): Option[String] = + DC.findExternData(name).flatMap(ext => ext.body match { + case StringExternBody(_, contents) => contents.strings.head match { + case tpe if validCTypes.contains(tpe) => Some(tpe) + case _ => None + } + case Unsupported(err) => None + }) + + def isValidExternC(name: Id)(using DC: DeclarationContext)(using ErrorReporter): Boolean = + getValidExternC(name).isDefined + + def handleExternC(template: Template[core.Expr])(using DC: DeclarationContext)(using ErrorReporter): Template[Variable] = template match { case Template(strings, args) => val cFunName = strings.head.split(" ").head.trim() Template(List(cFunName), args map { - case core.ValueVar(id, core.ValueType.Data(name, targs)) if validCTypes.contains(name.name.name) => - Variable(transform(id), machine.Type.CType(name.name.name)) - case _ => ErrorReporter.abort(s"In the C backend, only types '${validCTypes}' are allowed in templates") + case core.ValueVar(id, core.ValueType.Data(name, targs)) => + getValidExternC(name) match { + case Some(tpeName) => Variable(transform(id), machine.Type.CType(tpeName)) + case None => ErrorReporter.abort(s"In the C backend, only types '${validCTypes}' are allowed in templates") + } + case _ => ErrorReporter.abort(s"In the C backend, only valid extern data types are allowed") }) } - def transform(body: core.ExternBody[core.Expr])(using ErrorReporter): machine.ExternBody[Variable] = body match { + def transform(body: core.ExternBody[core.Expr])(using DeclarationContext, ErrorReporter): machine.ExternBody[Variable] = body match { case core.ExternBody.StringExternBody(ff, template) if ff.matches("c") => ExternBody.StringExternBody(ff, handleExternC(template)) case core.ExternBody.StringExternBody(ff, Template(strings, args)) => @@ -566,34 +583,34 @@ object Transformer { Clause(vparams.map(transform) ++ bparams.map(transform), transform(body).run()) } - def transform(param: core.ValueParam)(using BlocksParamsContext, ErrorReporter): Variable = + def transform(param: core.ValueParam)(using BlocksParamsContext, DeclarationContext, ErrorReporter): Variable = param match { case core.ValueParam(name, tpe) => Variable(transform(name), transform(tpe)) } - def transform(param: core.BlockParam)(using BlocksParamsContext, ErrorReporter): Variable = + def transform(param: core.BlockParam)(using BlocksParamsContext, DeclarationContext, ErrorReporter): Variable = param match { case core.BlockParam(name, tpe, capt) => Variable(transform(name), transform(tpe)) } - def transform(tpe: core.ValueType): Type = tpe match { - case core.ValueType.Data(name, _) if validCTypes.contains(name.name.name) => Type.CType(name.name.name) + def transform(tpe: core.ValueType)(using DeclarationContext, ErrorReporter): Type = tpe match { + case core.ValueType.Data(name, _) if isValidExternC(name) => Type.CType(getValidExternC(name).get) case _ => Positive() } - def transformUnboxed(tpe: core.ValueType): Type = + def transformUnboxed(tpe: core.ValueType)(using DeclarationContext, ErrorReporter): Type = tpe match { case core.Type.TInt => Type.Int() case core.Type.TChar => Type.Int() case core.Type.TByte => Type.Byte() case core.Type.TDouble => Type.Double() - case core.ValueType.Data(name, _) if validCTypes.contains(name.name.name) => Type.CType(name.name.name) + case core.ValueType.Data(name, _) if isValidExternC(name) => Type.CType(getValidExternC(name).get) case _ => Positive() } - def transform(tpe: core.BlockType)(using ErrorReporter): Type = tpe match { + def transform(tpe: core.BlockType)(using DeclarationContext, ErrorReporter): Type = tpe match { case core.Type.TState(stateType) => Type.Reference(transformUnboxed(stateType)) case core.Type.TPrompt(answer) => Type.Prompt() case core.Type.TResume(result, answer) => Type.Stack() @@ -632,7 +649,7 @@ object Transformer { def freshName(baseName: String): String = baseName + "_" + symbols.Symbol.fresh.next() - def findToplevelBlocksParams(definitions: List[core.Toplevel])(using BlocksParamsContext, ErrorReporter): Unit = + def findToplevelBlocksParams(definitions: List[core.Toplevel])(using BlocksParamsContext, DeclarationContext, ErrorReporter): Unit = definitions.foreach { case Toplevel.Def(id, core.BlockLit(tparams, cparams, vparams, bparams, body)) => noteDefinition(id, vparams.map(transform) ++ bparams.map(transform), Nil) From 5b4036dc4f2bfbdc0441ad984ff3b8da96a01ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattis=20B=C3=B6ckle?= Date: Mon, 8 Jun 2026 13:24:08 +0200 Subject: [PATCH 07/15] Remove C prefix from C types --- libraries/common/effekt.effekt | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index 04354dbfe..3299455ef 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -803,25 +803,23 @@ record SourcePosition(file: String, startLine: Int, startColumn: Int, endLine: I // Should only be used to automatically generate effekt wrappers for C functions namespace C { - // FIXME: The right-hand definitions do not currently matter, - // but they do correctly represent what the types are implemented as - extern type CString = llvm "ptr" - extern type CInt = llvm "i64" - extern type CDouble = llvm "double" - extern type CFloat = llvm "float" - extern type CPtr = llvm "ptr" - extern type CVoid = llvm "void" - - extern def string(value: String): CString = + extern type String = llvm "ptr" + extern type Int = llvm "i64" + extern type Double = llvm "double" + extern type Float = llvm "float" + extern type Ptr = llvm "ptr" + extern type Void = llvm "void" + + extern def String(value: effekt::String): String = llvm """ %conv = call ptr @c_bytearray_into_nullterminated_string(%Pos ${value}) ret ptr %conv """ - extern def int(value: Int) at {}: CInt = llvm "ret %Int ${value}" - extern def double(value: Double) at {}: CDouble = llvm "ret %Double ${value}" - extern def float(value: Double) at {}: CFloat = llvm """ + extern def Int(value: effekt::Int) at {}: Int = llvm "ret %Int ${value}" + extern def Double(value: effekt::Double) at {}: Double = llvm "ret %Double ${value}" + extern def Float(value: effekt::Double) at {}: Float = llvm """ %trunc = fptrunc %Double ${value} to float ret float %trunc """ - extern def nullptr() at {}: CPtr = llvm "ret ptr null" + extern def Nullptr() at {}: Ptr = llvm "ret ptr null" } From c61dc1a6b1664c05a005e8df208e3c5fa26f289d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattis=20B=C3=B6ckle?= Date: Mon, 8 Jun 2026 13:31:16 +0200 Subject: [PATCH 08/15] Add reverse type conversion functions C -> effekt --- libraries/common/effekt.effekt | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index 3299455ef..1a3cb2fe6 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -810,8 +810,8 @@ namespace C { extern type Ptr = llvm "ptr" extern type Void = llvm "void" - extern def String(value: effekt::String): String = - llvm """ + // Type conversion: effekt -> C + extern def String(value: effekt::String): String = llvm """ %conv = call ptr @c_bytearray_into_nullterminated_string(%Pos ${value}) ret ptr %conv """ @@ -822,4 +822,16 @@ namespace C { ret float %trunc """ extern def Nullptr() at {}: Ptr = llvm "ret ptr null" + + // Type conversion: C -> effekt + extern def toString(value: String): effekt::String = llvm """ + %conv = call ptr @c_bytearray_from_nullterminated_string(ptr ${value}) + ret %Pos %conv + """ + extern def toInt(value: Int) at {}: effekt::Int = llvm "ret %Int ${value}" + extern def toDouble(value: Double) at {}: effekt::Double = llvm "ret %Double ${value}" + extern def toFloat(value: Float) at {}: effekt::Double = llvm """ + %ext = fpext float ${value} to %Double + ret %Double %ext + """ } From 9571b56ce9bcd3cce93c4cc66aa89466412cdaf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattis=20B=C3=B6ckle?= Date: Mon, 8 Jun 2026 13:35:55 +0200 Subject: [PATCH 09/15] Add more utility functions --- libraries/common/effekt.effekt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index 1a3cb2fe6..5080b695f 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -834,4 +834,19 @@ namespace C { %ext = fpext float ${value} to %Double ret %Double %ext """ + + // Additional utility + extern def isNull(ptr: C::Ptr): Bool = llvm """ + %cmp = icmp eq ptr ${ptr}, null + %ext = zext i1 %cmp to i64 + %bool = insertvalue %Pos zeroinitializer, i64 %ext, 0 + ret %Pos %bool + """ + + extern def toBool(value: C::Int): Bool = llvm """ + %cmp = icmp ne %Int ${value}, 0 + %ext = zext i1 %cmp to i64 + %bool = insertvalue %Pos zeroinitializer, i64 %ext, 0 + ret %Pos %bool + """ } From 81c5959565b2a885df0909ca5ed9301f789806aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattis=20B=C3=B6ckle?= Date: Mon, 8 Jun 2026 13:49:10 +0200 Subject: [PATCH 10/15] Remove non-required files --- effekt-config.toml | 5 ---- glfw.effekt | 74 ---------------------------------------------- 2 files changed, 79 deletions(-) delete mode 100644 effekt-config.toml delete mode 100644 glfw.effekt diff --git a/effekt-config.toml b/effekt-config.toml deleted file mode 100644 index 1017c4870..000000000 --- a/effekt-config.toml +++ /dev/null @@ -1,5 +0,0 @@ -[llvm] -libraries = [] -includes = ["vendor/include"] -linked = ["glfw3", "GL"] -sources = ["vendor/src/gl.c"] \ No newline at end of file diff --git a/glfw.effekt b/glfw.effekt deleted file mode 100644 index 56ac8810f..000000000 --- a/glfw.effekt +++ /dev/null @@ -1,74 +0,0 @@ -extern def isNull(ptr: C::CPtr): Bool = llvm """ - %cmp = icmp eq ptr ${ptr}, null - %ext = zext i1 %cmp to i64 - %bool = insertvalue %Pos zeroinitializer, i64 %ext, 0 - ret %Pos %bool -""" - -extern def asBool(value: C::CInt): Bool = llvm """ - %cmp = icmp eq %Int ${value}, 0 - %ext = zext i1 %cmp to i64 - %bool = insertvalue %Pos zeroinitializer, i64 %ext, 0 - ret %Pos %bool -""" - -extern llvm """ - declare ptr @glfwGetProcAddress(ptr) - declare i32 @gladLoadGL(ptr) -""" - -extern def gladLoadGL(): Unit = llvm """ - call i32 @gladLoadGL(ptr noundef @glfwGetProcAddress) - ret %Pos zeroinitializer -""" - -// 0x00022002 -val GLFW_CONTEXT_VERSION_MAJOR = C::int(139266) -// 0x00022003 -val GLFW_CONTEXT_VERSION_MINOR = C::int(139267) -// 0x00022008 -val GLFW_OPENGL_PROFILE = C::int(139272) -// 0x00032001 -val GLFW_OPENGL_CORE_PROFILE = C::int(204801) - -extern def glfwInit(): C::CVoid = c "glfwInit" -extern def glfwTerminate(): C::CVoid = c "glfwTerminate" -extern def glfwWindowHint(version: C::CInt, value: C::CInt): C::CVoid = c "glfwWindowHint" - -type GLFWwindow = C::CPtr -extern def glfwCreateWindow(width: C::CInt, height: C::CInt, title: C::CString, monitor: C::CPtr, share: GLFWwindow): GLFWwindow = c "glfwCreateWindow" -extern def glfwMakeContextCurrent(window: GLFWwindow): C::CVoid = c "glfwMakeContextCurrent" -extern def glfwWindowShouldClose(window: GLFWwindow): C::CInt = c "glfwWindowShouldClose" -def glfwWindowShouldClose(window: GLFWwindow): Bool = glfwWindowShouldClose(window).asBool - -extern def glfwSwapBuffers(window: GLFWwindow): C::CVoid = c "glfwSwapBuffers" -extern def glfwPollEvents(): C::CVoid = c "glfwPollEvents" - - - -def main() = { - glfwInit() - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, C::int(3)) - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, C::int(3)) - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE) - - val window = glfwCreateWindow(C::int(800), C::int(600), C::string("Effekt Example Window"), C::nullptr(), C::nullptr()) - - if (window.isNull()) { - println("Failed to create window") - glfwTerminate() - return () - } - glfwMakeContextCurrent(window) - - gladLoadGL() - - while(not(glfwWindowShouldClose(window))) { - glfwSwapBuffers(window) - glfwPollEvents() - () - } - - glfwTerminate() - () -} \ No newline at end of file From f649493ed032313321b77177fb04a208338c1e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattis=20B=C3=B6ckle?= Date: Sat, 13 Jun 2026 13:53:01 +0200 Subject: [PATCH 11/15] Box & coerce ctypes --- .../effekt/generator/llvm/Transformer.scala | 56 ++++++++++----- .../scala/effekt/machine/PrettyPrinter.scala | 2 +- .../scala/effekt/machine/Transformer.scala | 70 +++++++++---------- .../src/main/scala/effekt/machine/Tree.scala | 11 ++- 4 files changed, 82 insertions(+), 57 deletions(-) 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 9d542af59..eae0ccfc7 100644 --- a/effekt/shared/src/main/scala/effekt/generator/llvm/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/generator/llvm/Transformer.scala @@ -56,7 +56,7 @@ object Transformer { emit(Comment(s"C wrapper function for '${cFunctionName}'")) // TODO: Check calling convention here emit(Call("ccall", Ccc(), transformedReturnType, ConstantGlobal(cFunctionName), transformedParameters.map { case Parameter(typ, name) => LocalReference(typ, name) })) - + transformedReturnType match { case VoidType() => RetVoid() case _ => Ret(LocalReference(transformedReturnType, "ccall")) @@ -413,6 +413,10 @@ object Transformer { shareValues(List(value), freeVariables(rest)) emit(Call(name.name, Ccc(), transform(name.tpe), ConstantGlobal("coerceNegPos"), List(transform(value)))) eraseValues(List(name), freeVariables(rest)) + case (machine.Type.CTpe(machine.CType.Void), machine.Type.Positive()) => + () + case (machine.Type.Positive(), machine.Type.CTpe(machine.CType.Void)) => + () case (from, into) => val coerce = (from, into) match { case (machine.Type.Int(), machine.Type.Positive()) => "coerceIntPos" @@ -421,6 +425,8 @@ object Transformer { case (machine.Type.Positive(), machine.Type.Byte()) => "coercePosByte" case (machine.Type.Double(), machine.Type.Positive()) => "coerceDoublePos" case (machine.Type.Positive(), machine.Type.Double()) => "coercePosDouble" + case (machine.Type.CTpe(tpe), machine.Type.Positive()) => coerceCTpePos(tpe) + case (machine.Type.Positive(), machine.Type.CTpe(tpe)) => coercePosCTpe(tpe) case (tpe1, tpe2) => sys.error(s"Should not coerce $tpe1 to $tpe2") } emit(Call(name.name, Ccc(), transform(name.tpe), ConstantGlobal(coerce), List(transform(value)))) @@ -450,6 +456,22 @@ object Transformer { case machine.Variable(name, tpe) => LocalReference(transform(tpe), name) } + def coerceCTpePos(tpe: machine.CType): String = tpe match { + case machine.CType.Ptr => "coercePtrPos" + case machine.CType.I64 => "coerceIntPos" + case machine.CType.Double => "coerceDoublePos" + case machine.CType.Float => "coerceFloatPos" + case machine.CType.Void => sys.error("Should not coerce from Void to Pos") + } + + def coercePosCTpe(tpe: machine.CType): String = tpe match { + case machine.CType.Ptr => "coercePosPtr" + case machine.CType.I64 => "coercePosInt" + case machine.CType.Double => "coercePosDouble" + case machine.CType.Float => "coercePosFloat" + case machine.CType.Void => sys.error("Should not coerce from Pos to Void") + } + val positiveType = NamedType("Pos"); // TODO multiple methods (should be pointer to vtable) val negativeType = NamedType("Neg"); @@ -466,14 +488,12 @@ object Transformer { val promptType = NamedType("Prompt"); val referenceType = NamedType("Reference"); - def transform(tpe: machine.Type.CType): Type = tpe match { - case machine.Type.CType(name) => name match { - case "ptr" => PointerType() - case "i64" => IntegerType64() - case "double" => DoubleType() - case "float" => FloatType() - case "void" => VoidType() - } + def transform(tpe: machine.CType): Type = tpe match { + case machine.CType.Ptr => PointerType() + case machine.CType.I64 => IntegerType64() + case machine.CType.Double => DoubleType() + case machine.CType.Float => FloatType() + case machine.CType.Void => VoidType() } def transform(tpe: machine.Type): Type = tpe match { @@ -485,19 +505,19 @@ object Transformer { case machine.Type.Byte() => IntegerType8() case machine.Type.Double() => DoubleType() case machine.Type.Reference(tpe) => referenceType - case CT@machine.Type.CType(name) => transform(CT) + case machine.Type.CTpe(ctpe) => transform(ctpe) } def environmentSize(environment: machine.Environment): Int = environment.map { case machine.Variable(_, typ) => typeSize(typ) }.sum - def typeSize(tpe: machine.Type.CType): Int = tpe match { - case machine.Type.CType(name) => name match { - case "ptr" => 8 - case "i64" => 8 - case "double" => 8 - case "float" => 4 - case "void" => 0 + def typeSize(tpe: machine.Type.CTpe): Int = tpe match { + case machine.Type.CTpe(tpe) => tpe match { + case machine.CType.Ptr => 8 + case machine.CType.I64 => 8 + case machine.CType.Double => 8 + case machine.CType.Float => 4 + case machine.CType.Void => 0 } } @@ -511,7 +531,7 @@ object Transformer { case machine.Type.Byte() => 1 case machine.Type.Double() => 8 // TODO Make fat? case machine.Type.Reference(_) => 16 - case CT@machine.Type.CType(name) => typeSize(CT) + case CT@machine.Type.CTpe(_) => typeSize(CT) } def defineFunction(name: String, parameters: List[Parameter])(prog: (FunctionContext, BlockContext) ?=> Terminator): ModuleContext ?=> Unit = { diff --git a/effekt/shared/src/main/scala/effekt/machine/PrettyPrinter.scala b/effekt/shared/src/main/scala/effekt/machine/PrettyPrinter.scala index 2d21baa8e..16438b07a 100644 --- a/effekt/shared/src/main/scala/effekt/machine/PrettyPrinter.scala +++ b/effekt/shared/src/main/scala/effekt/machine/PrettyPrinter.scala @@ -36,7 +36,7 @@ object PrettyPrinter extends ParenPrettyPrinter { case Type.Byte() => "Byte" case Type.Double() => "Double" case Type.Reference(tpe) => toDoc(tpe) <> "*" - case Type.CType(name) => s"CType(${name})" + case Type.CTpe(name) => s"CType(${name})" } def toDoc(defi: Definition): Doc = defi match { diff --git a/effekt/shared/src/main/scala/effekt/machine/Transformer.scala b/effekt/shared/src/main/scala/effekt/machine/Transformer.scala index f9e4bb1f2..f3083c30b 100644 --- a/effekt/shared/src/main/scala/effekt/machine/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/machine/Transformer.scala @@ -92,17 +92,27 @@ object Transformer { val validCTypes = List("ptr", "i64", "double", "float", "void") - def getValidExternC(name: Id)(using DC: DeclarationContext)(using ErrorReporter): Option[String] = - DC.findExternData(name).flatMap(ext => ext.body match { - case StringExternBody(_, contents) => contents.strings.head match { - case tpe if validCTypes.contains(tpe) => Some(tpe) - case _ => None - } + def parseCType(tpe: String)(using ErrorReporter): Option[CType] = tpe match { + case "ptr" => Some(machine.CType.Ptr) + case "i64" => Some(machine.CType.I64) + case "double" => Some(machine.CType.Double) + case "float" => Some(machine.CType.Float) + case "void" => Some(machine.CType.Void) + case o => None + } + + def parseExternCTpe(name: Id)(using DC: DeclarationContext)(using ErrorReporter): Option[machine.Type.CTpe] = + DC.findExternData(name).flatMap(value => value.body match { + case StringExternBody(_, contents) => + parseCType(contents.strings.head).map(machine.Type.CTpe.apply) case Unsupported(err) => None }) + def getExternCTpe(name: Id)(using DC: DeclarationContext)(using ErrorReporter): machine.Type.CTpe = + parseExternCTpe(name).get + def isValidExternC(name: Id)(using DC: DeclarationContext)(using ErrorReporter): Boolean = - getValidExternC(name).isDefined + parseExternCTpe(name).isDefined def handleExternC(template: Template[core.Expr])(using DC: DeclarationContext)(using ErrorReporter): Template[Variable] = template match { case Template(strings, args) => @@ -110,8 +120,8 @@ object Transformer { Template(List(cFunName), args map { case core.ValueVar(id, core.ValueType.Data(name, targs)) => - getValidExternC(name) match { - case Some(tpeName) => Variable(transform(id), machine.Type.CType(tpeName)) + parseExternCTpe(name) match { + case Some(tpe) => Variable(transform(id), tpe) case None => ErrorReporter.abort(s"In the C backend, only types '${validCTypes}' are allowed in templates") } case _ => ErrorReporter.abort(s"In the C backend, only valid extern data types are allowed") @@ -207,12 +217,10 @@ object Transformer { val variable = Variable(transform(id), transform(resultType)) transform(rest).flatMap { rest => transform(vargs, bargs).run { (values, blocks) => - perhapsUnbox(values, vparamTypes).run { unboxeds => + perhapsUnbox(values, vparamTypes map transformUnboxed).run { unboxeds => transformUnboxed(resultType) match { case Type.Positive() => Trampoline.Done(ForeignCall(variable, transform(blockName), unboxeds ++ blocks, rest)) - case Type.CType(_) => - Trampoline.Done(ForeignCall(variable, transform(blockName), unboxeds ++ blocks, rest)) case unboxedTpe => val unboxed = Variable(freshName("unboxed"), unboxedTpe) Trampoline.Done(ForeignCall(unboxed, transform(blockName), unboxeds ++ blocks, Coerce(variable, unboxed, rest))) @@ -251,7 +259,7 @@ object Transformer { // TODO better way to deal with extern async functions annotatedTpe match { case core.BlockType.Function(_, _, vparamTypes, _, _) => - perhapsUnbox(values, vparamTypes).run { unboxeds => + perhapsUnbox(values, vparamTypes map transformUnboxed).run { unboxeds => Trampoline.Done(Jump(Label(transform(id), blockParams ++ freeParams), unboxeds ++ blocks ++ freeParams)) } case _ => @@ -367,7 +375,7 @@ object Transformer { transform(body).flatMap { body => transform(init).flatMap { value => - perhapsUnbox(value, init.tpe).map { unboxed => + perhapsUnbox(value, transformUnboxed(init.tpe)).map { unboxed => Shift(temporary, Variable(transform(region), Type.Prompt()), Var(Variable(transform(ref), Type.Reference(unboxed.tpe)), unboxed, Resume(temporary, body))) @@ -382,7 +390,7 @@ object Transformer { transform(body).flatMap { body => transform(init).flatMap { value => - perhapsUnbox(value, init.tpe).map { unboxed => + perhapsUnbox(value, transformUnboxed(init.tpe)).map { unboxed => Var(Variable(transform(ref), Type.Reference(unboxed.tpe)), unboxed, body) } }.run(x => Trampoline.Done(x)) @@ -404,7 +412,7 @@ object Transformer { case core.Put(ref, capt, arg, body) => transform(body).flatMap { body => transform(arg).flatMap { value => - perhapsUnbox(value, arg.tpe).map { unboxed => + perhapsUnbox(value, transformUnboxed(arg.tpe)).map { unboxed => StoreVar(Variable(transform(ref), Type.Reference(unboxed.tpe)), unboxed, body) } }.run(x => Trampoline.Done(x)) @@ -534,13 +542,11 @@ object Transformer { case core.PureApp(core.BlockVar(blockName, core.BlockType.Function(_, _, vparamTypes, _, resultType), _), _, vargs) => transform(vargs).flatMap { values => - perhapsUnbox(values, vparamTypes).flatMap { unboxeds => + perhapsUnbox(values, vparamTypes map transformUnboxed).flatMap { unboxeds => shift { k => transformUnboxed(resultType) match { case Type.Positive() => ForeignCall(variable, transform(blockName), unboxeds, k(variable)) - case Type.CType(_) => - ForeignCall(variable, transform(blockName), unboxeds, k(variable)) case unboxedTpe => val unboxed = Variable(freshName("unboxed"), unboxedTpe) ForeignCall(unboxed, transform(blockName), unboxeds, Coerce(variable, unboxed, k(variable))) @@ -595,10 +601,8 @@ object Transformer { Variable(transform(name), transform(tpe)) } - def transform(tpe: core.ValueType)(using DeclarationContext, ErrorReporter): Type = tpe match { - case core.ValueType.Data(name, _) if isValidExternC(name) => Type.CType(getValidExternC(name).get) - case _ => Positive() - } + def transform(tpe: core.ValueType)(using DeclarationContext, ErrorReporter): Type = + Positive() def transformUnboxed(tpe: core.ValueType)(using DeclarationContext, ErrorReporter): Type = tpe match { @@ -606,7 +610,7 @@ object Transformer { case core.Type.TChar => Type.Int() case core.Type.TByte => Type.Byte() case core.Type.TDouble => Type.Double() - case core.ValueType.Data(name, _) if isValidExternC(name) => Type.CType(getValidExternC(name).get) + case core.ValueType.Data(name, _) if isValidExternC(name) => getExternCTpe(name) case _ => Positive() } @@ -627,24 +631,16 @@ object Transformer { def transform(id: Id): String = s"${id.name}_${id.id}" - def perhapsUnbox(value: Variable, tpe: core.ValueType): Binding[Variable] = + def perhapsUnbox(value: Variable, tpe: machine.Type): Binding[Variable] = tpe match { - case core.Type.TInt => - val unboxed = Variable(freshName("integer"), Type.Int()) - shift { k => Coerce(unboxed, value, k(unboxed)) } - case core.Type.TChar => - val unboxed = Variable(freshName("char"), Type.Int()) - shift { k => Coerce(unboxed, value, k(unboxed)) } - case core.Type.TByte => - val unboxed = Variable(freshName("byte"), Type.Byte()) - shift { k => Coerce(unboxed, value, k(unboxed)) } - case core.Type.TDouble => - val unboxed = Variable(freshName("double"), Type.Double()) + case Type.CTpe(CType.Void) => pure(value) + case Type.Int() | Type.Byte() | Type.Double() | Type.CTpe(_) => + val unboxed = Variable(freshName("unboxed"), tpe) shift { k => Coerce(unboxed, value, k(unboxed)) } case _ => pure(value) } - def perhapsUnbox(values: List[Variable], tpes: List[core.ValueType]): Binding[List[Variable]] = + def perhapsUnbox(values: List[Variable], tpes: List[machine.Type]): Binding[List[Variable]] = traverse(values.zip(tpes)) { case (value, tpe) => perhapsUnbox(value, tpe) } def freshName(baseName: String): String = baseName + "_" + symbols.Symbol.fresh.next() diff --git a/effekt/shared/src/main/scala/effekt/machine/Tree.scala b/effekt/shared/src/main/scala/effekt/machine/Tree.scala index a789f74ef..e39b701f3 100644 --- a/effekt/shared/src/main/scala/effekt/machine/Tree.scala +++ b/effekt/shared/src/main/scala/effekt/machine/Tree.scala @@ -209,6 +209,15 @@ enum Statement { export Statement.* +enum CType { + case Ptr + case I64 + case Double + case Float + case Void +} +export CType.* + /** * Types */ @@ -221,7 +230,7 @@ enum Type { case Byte() case Double() case Reference(tpe: Type) - case CType(name: String) + case CTpe(tpe: CType) } export Type.{ Positive, Negative } From af11366b5cdb9e56057367b8956e87b46f5100cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattis=20B=C3=B6ckle?= Date: Mon, 15 Jun 2026 15:29:50 +0200 Subject: [PATCH 12/15] Add coercion functions for c types --- libraries/llvm/rts.ll | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/libraries/llvm/rts.ll b/libraries/llvm/rts.ll index a2bc718c5..3ccd130b5 100644 --- a/libraries/llvm/rts.ll +++ b/libraries/llvm/rts.ll @@ -795,6 +795,34 @@ define ccc %Double @coercePosDouble(%Pos %input) { ret %Double %d } +define ccc %Pos @coerceFloatPos(float %input) { + %ext = fpext float %input to double + %number = bitcast double %ext to i64 + %boxed1 = insertvalue %Pos zeroinitializer, i64 %number, 0 + %boxed2 = insertvalue %Pos %boxed1, %Object null, 1 + ret %Pos %boxed2 +} + +define ccc float @coercePosFloat(%Pos %input) { + %unboxed = extractvalue %Pos %input, 0 + %d = bitcast i64 %unboxed to double + %trunc = fptrunc double %d to float + ret float %trunc +} + +define ccc %Pos @coercePtrPos(ptr %input) { + %number = ptrtoint ptr %input to i64 + %boxed1 = insertvalue %Pos zeroinitializer, i64 %number, 0 + %boxed2 = insertvalue %Pos %boxed1, %Object null, 1 + ret %Pos %boxed2 +} + +define ccc ptr @coercePosPtr(%Pos %input) { + %unboxed = extractvalue %Pos %input, 0 + %d = inttoptr i64 %unboxed to ptr + ret ptr %d +} + ; Scope domains !0 = !{!"types"} From 25d12d436872a22ac44d1bb30a9113e4b84fd3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattis=20B=C3=B6ckle?= Date: Mon, 15 Jun 2026 15:30:47 +0200 Subject: [PATCH 13/15] add effekt bool -> c int function --- libraries/common/effekt.effekt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index 5080b695f..5e8ece382 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -821,6 +821,10 @@ namespace C { %trunc = fptrunc %Double ${value} to float ret float %trunc """ + extern def Bool(value: effekt::Bool) at {}: Int = llvm """ + %bool = extractvalue %Pos ${value}, 0 + ret i64 %bool + """ extern def Nullptr() at {}: Ptr = llvm "ret ptr null" // Type conversion: C -> effekt @@ -850,3 +854,5 @@ namespace C { ret %Pos %bool """ } + +def infixEq(v1: C::Int, v2: C::Int): Bool = C::toInt(v1) == C::toInt(v2) From 3be2f4404914f55094fcf80518c612405c068072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattis=20B=C3=B6ckle?= Date: Wed, 17 Jun 2026 15:22:36 +0200 Subject: [PATCH 14/15] Update effekt/shared/src/main/scala/effekt/machine/Transformer.scala MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jonathan Immanuel Brachthäuser --- effekt/shared/src/main/scala/effekt/machine/Transformer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/effekt/shared/src/main/scala/effekt/machine/Transformer.scala b/effekt/shared/src/main/scala/effekt/machine/Transformer.scala index f3083c30b..fc09398c2 100644 --- a/effekt/shared/src/main/scala/effekt/machine/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/machine/Transformer.scala @@ -98,7 +98,7 @@ object Transformer { case "double" => Some(machine.CType.Double) case "float" => Some(machine.CType.Float) case "void" => Some(machine.CType.Void) - case o => None + case o => None } def parseExternCTpe(name: Id)(using DC: DeclarationContext)(using ErrorReporter): Option[machine.Type.CTpe] = From eeb673b1ae036200d8c337af09be5641165441e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattis=20B=C3=B6ckle?= Date: Wed, 17 Jun 2026 15:30:47 +0200 Subject: [PATCH 15/15] Change Nullptr -> Null More in line with the LLVM naming scheme --- libraries/common/effekt.effekt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index 5e8ece382..c8009e466 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -825,7 +825,7 @@ namespace C { %bool = extractvalue %Pos ${value}, 0 ret i64 %bool """ - extern def Nullptr() at {}: Ptr = llvm "ret ptr null" + extern def Null() at {}: Ptr = llvm "ret ptr null" // Type conversion: C -> effekt extern def toString(value: String): effekt::String = llvm """