diff --git a/effekt/jvm/src/test/scala/effekt/JavaScriptTests.scala b/effekt/jvm/src/test/scala/effekt/JavaScriptTests.scala index 262e89afc..de90df3b4 100644 --- a/effekt/jvm/src/test/scala/effekt/JavaScriptTests.scala +++ b/effekt/jvm/src/test/scala/effekt/JavaScriptTests.scala @@ -26,14 +26,12 @@ class JavaScriptTests extends EffektTests { override lazy val withoutOptimizations: List[File] = List( // contifying under reset - //examplesDir / "pos" / "issue842.effekt", - //examplesDir / "pos" / "issue861.effekt", + examplesDir / "pos" / "issue842.effekt", + examplesDir / "pos" / "issue861.effekt", // syntax error (multiple declaration) - //examplesDir / "char" / "ascii_isalphanumeric.effekt", - //examplesDir / "char" / "ascii_iswhitespace.effekt", - //examplesDir / "pos" / "parser.effekt", - //examplesDir / "pos" / "probabilistic.effekt", + examplesDir / "pos" / "parser.effekt", + examplesDir / "pos" / "probabilistic.effekt", ) override def ignored: List[File] = List( diff --git a/effekt/jvm/src/test/scala/effekt/LLVMTests.scala b/effekt/jvm/src/test/scala/effekt/LLVMTests.scala index e0b1faca5..6a1c55b2d 100644 --- a/effekt/jvm/src/test/scala/effekt/LLVMTests.scala +++ b/effekt/jvm/src/test/scala/effekt/LLVMTests.scala @@ -55,28 +55,23 @@ class LLVMTests extends EffektTests { override lazy val withoutOptimizations: List[File] = List( // contifying under reset - //examplesDir / "pos" / "issue842.effekt", - //examplesDir / "pos" / "issue861.effekt", + examplesDir / "pos" / "issue842.effekt", + examplesDir / "pos" / "issue861.effekt", + + examplesDir / "pos" / "capture" / "regions.effekt", + examplesDir / "pos" / "capture" / "selfregion.effekt", + examplesDir / "benchmarks" / "other" / "generator.effekt", + examplesDir / "pos" / "bidirectional" / "typeparametric.effekt", + examplesDir / "benchmarks" / "are_we_fast_yet" / "permute.effekt", + examplesDir / "benchmarks" / "are_we_fast_yet" / "storage.effekt", // top-level object definition - //examplesDir / "pos" / "object" / "if_control_effect.effekt", - //examplesDir / "pos" / "lambdas" / "toplevel_objects.effekt", - //examplesDir / "pos" / "type_omission_op.effekt", - //examplesDir / "pos" / "bidirectional" / "higherorderobject.effekt", - //examplesDir / "pos" / "bidirectional" / "res_obj_boxed.effekt", - //examplesDir / "pos" / "bidirectional" / "effectfulobject.effekt", - - // no block info - //examplesDir / "pos" / "capture" / "regions.effekt", - //examplesDir / "pos" / "capture" / "selfregion.effekt", - //examplesDir / "benchmarks" / "other" / "generator.effekt", - - // hole - //examplesDir / "pos" / "bidirectional" / "typeparametric.effekt", - - // segfault - //examplesDir / "benchmarks" / "are_we_fast_yet" / "permute.effekt", - //examplesDir / "benchmarks" / "are_we_fast_yet" / "storage.effekt", + examplesDir / "pos" / "object" / "if_control_effect.effekt", + examplesDir / "pos" / "lambdas" / "toplevel_objects.effekt", + examplesDir / "pos" / "type_omission_op.effekt", + examplesDir / "pos" / "bidirectional" / "higherorderobject.effekt", + examplesDir / "pos" / "bidirectional" / "res_obj_boxed.effekt", + examplesDir / "pos" / "bidirectional" / "effectfulobject.effekt", ) override lazy val ignored: List[File] = missingFeatures ++ noValgrind(examplesDir) diff --git a/effekt/jvm/src/test/scala/effekt/StdlibTests.scala b/effekt/jvm/src/test/scala/effekt/StdlibTests.scala index 6922267a7..b053c633f 100644 --- a/effekt/jvm/src/test/scala/effekt/StdlibTests.scala +++ b/effekt/jvm/src/test/scala/effekt/StdlibTests.scala @@ -21,19 +21,16 @@ class StdlibJavaScriptTests extends StdlibTests { override def withoutOptimizations: List[File] = List( examplesDir / "stdlib" / "acme.effekt", - - //examplesDir / "stdlib" / "json.effekt", - //examplesDir / "stdlib" / "exception" / "combinators.effekt", - - // reference error (k is not defined) - //examplesDir / "stdlib" / "stream" / "fibonacci.effekt", - //examplesDir / "stdlib" / "list" / "flatmap.effekt", - //examplesDir / "stdlib" / "list" / "sortBy.effekt", - //examplesDir / "stdlib" / "stream" / "zip.effekt", - //examplesDir / "stdlib" / "stream" / "characters.effekt", - - // oom - //examplesDir / "stdlib" / "list" / "deleteat.effekt", + examplesDir / "stdlib" / "json.effekt", + examplesDir / "stdlib" / "exception" / "combinators.effekt", + examplesDir / "stdlib" / "stream" / "fibonacci.effekt", + examplesDir / "stdlib" / "list" / "flatmap.effekt", + examplesDir / "stdlib" / "list" / "sortBy.effekt", + examplesDir / "stdlib" / "stream" / "zip.effekt", + examplesDir / "stdlib" / "stream" / "characters.effekt", + examplesDir / "stdlib" / "list" / "deleteat.effekt", + examplesDir / "stdlib" / "char" / "ascii_isalphanumeric.effekt", + examplesDir / "stdlib" / "char" / "ascii_iswhitespace.effekt", ) override def ignored: List[File] = List() diff --git a/effekt/jvm/src/test/scala/effekt/core/CoreTests.scala b/effekt/jvm/src/test/scala/effekt/core/CoreTests.scala index 192652eda..f2132c35a 100644 --- a/effekt/jvm/src/test/scala/effekt/core/CoreTests.scala +++ b/effekt/jvm/src/test/scala/effekt/core/CoreTests.scala @@ -10,7 +10,7 @@ import effekt.PhaseResult.CoreTransformed */ trait CoreTests extends munit.FunSuite { - protected def defaultNames = symbols.builtins.rootTypes ++ symbols.builtins.rootTerms ++ symbols.builtins.rootCaptures + protected def defaultNames: Map[String, _root_.effekt.symbols.Symbol] = symbols.builtins.rootTypes ++ symbols.builtins.rootCaptures def shouldBeEqual(obtained: ModuleDecl, expected: ModuleDecl, clue: => Any)(using Location) = assertEquals(obtained, expected, { diff --git a/effekt/jvm/src/test/scala/effekt/core/VMTests.scala b/effekt/jvm/src/test/scala/effekt/core/VMTests.scala index 575ee288e..0b6a26a9b 100644 --- a/effekt/jvm/src/test/scala/effekt/core/VMTests.scala +++ b/effekt/jvm/src/test/scala/effekt/core/VMTests.scala @@ -357,8 +357,8 @@ class VMTests extends munit.FunSuite { poppedFrames = 8661, allocations = 0, closures = 0, - variableReads = 32437, - variableWrites = 13699, + variableReads = 23776, + variableWrites = 5039, resets = 0, shifts = 0, resumes = 0 @@ -373,8 +373,8 @@ class VMTests extends munit.FunSuite { poppedFrames = 5462, allocations = 5461, closures = 0, - variableReads = 13654, - variableWrites = 9557, + variableReads = 8192, + variableWrites = 4096, resets = 0, shifts = 0, resumes = 0 diff --git a/effekt/shared/src/main/scala/effekt/Typer.scala b/effekt/shared/src/main/scala/effekt/Typer.scala index edc45379e..5cce7eb16 100644 --- a/effekt/shared/src/main/scala/effekt/Typer.scala +++ b/effekt/shared/src/main/scala/effekt/Typer.scala @@ -54,21 +54,6 @@ object Typer extends Phase[NameResolved, Typechecked] { Context in { Context.withUnificationScope { flowingInto(builtins.toplevelCaptures) { - // bring builtins into scope - builtins.rootTerms.values.foreach { - case term: BlockParam => - Context.bind(term, term.tpe.getOrElse { - INTERNAL_ERROR("Builtins should always be annotated with their types.") - }) - Context.bind(term, CaptureSet(term.capture)) - case term: ExternResource => - Context.bind(term, term.tpe) - Context.bind(term, CaptureSet(term.capture)) - case term: Callable => - Context.bind(term, term.toType) - case term => Context.panic(s"Cannot bind builtin term: ${term}") - } - // We split the type-checking of definitions into "pre-check" and "check" // to allow mutually recursive defs tree.defs.foreach { d => precheckDef(d) } diff --git a/effekt/shared/src/main/scala/effekt/core/vm/VM.scala b/effekt/shared/src/main/scala/effekt/core/vm/VM.scala index 631e779c2..aae06c93a 100644 --- a/effekt/shared/src/main/scala/effekt/core/vm/VM.scala +++ b/effekt/shared/src/main/scala/effekt/core/vm/VM.scala @@ -331,11 +331,6 @@ class Interpreter(instrumentation: Instrumentation, runtime: Runtime) { // TODO make the type of Region more precise... case Stmt.Region(_) => ??? - case Stmt.Alloc(id, init, region, body) if region == symbols.builtins.globalRegion => - val value = eval(init, env) - val address = freshAddress() - State.Step(body, env.bind(id, Computation.Reference(address)), stack, heap.updated(address, value)) - case Stmt.Alloc(id, init, region, body) => val value = eval(init, env) diff --git a/effekt/shared/src/main/scala/effekt/generator/chez/Transformer.scala b/effekt/shared/src/main/scala/effekt/generator/chez/Transformer.scala index 01ea52559..f22c93317 100644 --- a/effekt/shared/src/main/scala/effekt/generator/chez/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/generator/chez/Transformer.scala @@ -95,9 +95,6 @@ trait Transformer { case Var(ref, init, capt, body) => state(nameDef(ref), toChez(init), toChez(body)) - case Alloc(id, init, region, body) if region == symbols.builtins.globalRegion => - chez.Let(List(Binding(nameDef(id), chez.Builtin("box", toChez(init)))), toChez(body)) - case Alloc(id, init, region, body) => chez.Let(List(Binding(nameDef(id), chez.Builtin("fresh", chez.Variable(nameRef(region)), toChez(init)))), toChez(body)) diff --git a/effekt/shared/src/main/scala/effekt/generator/js/TransformerCps.scala b/effekt/shared/src/main/scala/effekt/generator/js/TransformerCps.scala index 69bfb8dd5..db67fabf7 100644 --- a/effekt/shared/src/main/scala/effekt/generator/js/TransformerCps.scala +++ b/effekt/shared/src/main/scala/effekt/generator/js/TransformerCps.scala @@ -70,12 +70,7 @@ object TransformerCps extends Transformer { val jsDecls = module.declarations.flatMap(toJS) val stmts = module.definitions.map(toJS) - val state = js.Const( - nameDef(symbols.builtins.globalRegion), - js.Variable(JSName("global")) - ) :: Nil - - js.Module(name, Nil, exports, jsDecls ++ jsExterns ++ state ++ stmts) + js.Module(name, Nil, exports, jsDecls ++ jsExterns ++ stmts) } def compileLSP(input: cps.ModuleDecl, coreModule: core.ModuleDecl)(using C: Context): List[js.Stmt] = 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 b16c5f521..fcbb1937d 100644 --- a/effekt/shared/src/main/scala/effekt/generator/llvm/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/generator/llvm/Transformer.scala @@ -378,8 +378,6 @@ object Transformer { def transform(value: machine.Variable)(using FunctionContext): Operand = substitute(value) match { - // TODO rethink existence of global - case machine.Variable("global", machine.Type.Prompt()) => ConstantGlobal("global") case machine.Variable(name, tpe) => LocalReference(transform(tpe), name) } diff --git a/effekt/shared/src/main/scala/effekt/machine/Transformer.scala b/effekt/shared/src/main/scala/effekt/machine/Transformer.scala index 116e4acfd..8ae83326c 100644 --- a/effekt/shared/src/main/scala/effekt/machine/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/machine/Transformer.scala @@ -37,8 +37,11 @@ object Transformer { Definition(Label(transform(id), vparams.map(transform) ++ bparams.map(transform)), transform(body)) case core.Toplevel.Val(id, tpe, binding) => Definition(BC.globals(id), transform(binding)) + case core.Toplevel.Def(id, block @ core.New(impl)) => + val variable = Variable(freshName("returned"), transform(block.tpe)) + Definition(BC.globals(id), New(variable, transform(impl), Return(List(variable)))) case d => - ErrorReporter.abort(s"Toplevel object definitions not yet supported: ${d}") + ErrorReporter.abort(s"Other toplevel definitions not yet supported: ${d}") } val localDefinitions = BC.definitions @@ -89,9 +92,7 @@ object Transformer { // Regions are blocks and can be free, but do not have info. case core.Variable.Block(id, core.Type.TRegion, capt) => - if id == symbols.builtins.globalRegion - then Set.empty - else Set(Variable(transform(id), Type.Prompt())) + Set(Variable(transform(id), Type.Prompt())) case core.Variable.Block(pid, tpe, capt) if pid != id => BPC.info.get(pid) match { // For each known free block we have to add its free variables to this one (flat closure) @@ -120,13 +121,16 @@ object Transformer { noteParameter(id, block.tpe) New(Variable(transform(id), transform(impl.interface)), transform(impl), transform(rest)) - case core.Def(id, block @ core.BlockVar(alias, tpe, _), rest) => - getDefinition(alias) match { + case core.Def(id, core.BlockVar(other, tpe, capt), rest) => + getBlockInfo(other) match { case BlockInfo.Definition(free, params) => noteDefinition(id, free, params) + emitDefinition(transformLabel(id), Jump(transformLabel(other))) + transform(rest) + case BlockInfo.Parameter(_) => + noteParameter(id, tpe) + Substitute(List(Variable(transform(id), transform(tpe)) -> Variable(transform(other), transform(tpe))), transform(rest)) } - emitDefinition(transformLabel(id), Jump(transformLabel(alias))) - transform(rest) case core.Def(id, block @ core.Unbox(pure), rest) => noteParameter(id, block.tpe) @@ -190,6 +194,10 @@ object Transformer { val opTag = DeclarationContext.getPropertyTag(method) transform(vargs, bargs).run { (values, blocks) => callee match { + case Block.BlockVar(id, tpe, capt) if BPC.globals contains id => + val variable = Variable(freshName("receiver"), transform(tpe)) + PushFrame(Clause(List(variable), Invoke(variable, opTag, values ++ blocks)), Jump(BPC.globals(id))) + case Block.BlockVar(id, tpe, capt) => Invoke(Variable(transform(id), transform(tpe)), opTag, values ++ blocks) @@ -246,6 +254,8 @@ object Transformer { Resume(Variable(transform(k.id), Type.Stack()), transform(body)) case core.Region(core.BlockLit(tparams, cparams, vparams, List(region), body)) => + noteParameters(List(region)) + val variable = Variable(freshName("returned"), transform(body.tpe)) val returnClause = Clause(List(variable), Return(List(variable))) val prompt = transform(region) @@ -254,24 +264,13 @@ object Transformer { case core.Alloc(id, init, region, body) => transform(init).run { value => - val tpe = value.tpe; - val name = transform(id) - val variable = Variable(name, tpe) - val reference = Variable(transform(id), Type.Reference(tpe)) + val reference = Variable(transform(id), Type.Reference(value.tpe)) val prompt = Variable(transform(region), Type.Prompt()) val temporary = Variable(freshName("temporaryStack"), Type.Stack()) - region match { - case symbols.builtins.globalRegion => - val globalPrompt = Variable("global", Type.Prompt()) - Shift(temporary, globalPrompt, - Var(reference, value, Type.Positive(), - Resume(temporary, transform(body)))) - case _ => - Shift(temporary, prompt, - Var(reference, value, Type.Positive(), - Resume(temporary, transform(body)))) - } + Shift(temporary, prompt, + Var(reference, value, Type.Positive(), + Resume(temporary, transform(body)))) } case core.Var(ref, init, capture, body) => @@ -314,6 +313,11 @@ object Transformer { } yield (values, blocks) def transformBlockArg(block: core.Block)(using BPC: BlocksParamsContext, DC: DeclarationContext, E: ErrorReporter): Binding[Variable] = block match { + case core.BlockVar(id, tpe, _) if BPC.globals contains id => + val variable = Variable(transform(id), transform(tpe)) + Binding { k => + PushFrame(Clause(List(variable), k(variable)), Jump(BPC.globals(id))) + } case core.BlockVar(id, tpe, capt) => getBlockInfo(id) match { case BlockInfo.Definition(_, parameters) => // Passing a top-level function directly, so we need to eta-expand turning it into a closure @@ -486,8 +490,9 @@ object Transformer { case core.BlockType.Interface(symbol, targs) => Negative() } - def transformLabel(id: Id)(using BPC: BlocksParamsContext): Label = getDefinition(id) match { + def transformLabel(id: Id)(using BPC: BlocksParamsContext): Label = getBlockInfo(id) match { case BlockInfo.Definition(freeParams, boundParams) => Label(transform(id), boundParams ++ freeParams) + case BlockInfo.Parameter(_) => sys error s"Expected a function definition, but got a block parameter: ${id}" } def transform(id: Id): String = @@ -512,6 +517,9 @@ object Transformer { case Toplevel.Val(id, tpe, binding) => noteDefinition(id, Nil, Nil) noteGlobal(id) + case Toplevel.Def(id, core.New(impl)) => + noteDefinition(id, Nil, Nil) + noteGlobal(id) case other => () } @@ -554,11 +562,6 @@ object Transformer { def getBlockInfo(id: Id)(using BPC: BlocksParamsContext): BlockInfo = BPC.info.getOrElse(id, sys error s"No block info for ${util.show(id)}") - def getDefinition(id: Id)(using BPC: BlocksParamsContext): BlockInfo.Definition = getBlockInfo(id) match { - case d : BlockInfo.Definition => d - case BlockInfo.Parameter(tpe) => sys error s"Expected a function getDefinition, but got a block parameter: ${id}" - } - case class Binding[A](run: (A => Statement) => Statement) { def flatMap[B](rest: A => Binding[B]): Binding[B] = { Binding(k => run(a => rest(a).run(k))) diff --git a/effekt/shared/src/main/scala/effekt/symbols/builtins.scala b/effekt/shared/src/main/scala/effekt/symbols/builtins.scala index d3b33b553..c6e2d3813 100644 --- a/effekt/shared/src/main/scala/effekt/symbols/builtins.scala +++ b/effekt/shared/src/main/scala/effekt/symbols/builtins.scala @@ -53,6 +53,9 @@ object builtins { val AsyncSymbol = Interface(Name.local("Async"), Nil, Nil) val AsyncCapability = ExternResource(name("async"), InterfaceType(AsyncSymbol, Nil)) + val GlobalSymbol = Interface(Name.local("Global"), Nil, Nil) + val GlobalCapability = ExternResource(name("global"), InterfaceType(GlobalSymbol, Nil)) + object TState { val S: TypeParam = TypeParam(Name.local("S")) val interface: Interface = Interface(Name.local("Ref"), List(S), Nil) @@ -86,23 +89,16 @@ object builtins { "Region" -> RegionSymbol ) - lazy val globalRegion = ExternResource(name("global"), TRegion) - - val rootTerms: Map[String, TermSymbol] = Map( - "global" -> globalRegion - ) - val rootCaptures: Map[String, Capture] = Map( "io" -> IOCapability.capture, "async" -> AsyncCapability.capture, - "global" -> globalRegion.capture + "global" -> GlobalCapability.capture ) // captures which are allowed on the toplevel - val toplevelCaptures: CaptureSet = CaptureSet() // CaptureSet(IOCapability.capture, globalRegion.capture) + val toplevelCaptures: CaptureSet = CaptureSet() // CaptureSet(IOCapability.capture, GlobalCapability.capture) lazy val rootBindings: Bindings = - Bindings(rootTerms.map { case (k, v) => (k, Set(v)) }, rootTypes, rootCaptures, - Map("effekt" -> Bindings(rootTerms.map { case (k, v) => (k, Set(v)) }, rootTypes, rootCaptures, Map.empty))) + Bindings(Map.empty, rootTypes, rootCaptures, Map("effekt" -> Bindings(Map.empty, rootTypes, rootCaptures, Map.empty))) } diff --git a/examples/benchmarks/are_we_fast_yet/permute.effekt b/examples/benchmarks/are_we_fast_yet/permute.effekt index 4741322dc..04214d496 100644 --- a/examples/benchmarks/are_we_fast_yet/permute.effekt +++ b/examples/benchmarks/are_we_fast_yet/permute.effekt @@ -1,5 +1,6 @@ import examples/benchmarks/runner +import ref import array def swap(arr: Array[Int], i: Int, j: Int) = { @@ -9,10 +10,10 @@ def swap(arr: Array[Int], i: Int, j: Int) = { } def run(n: Int) = { - var count in global = 0; + val count = ref(0); def permute(arr: Array[Int], n: Int): Unit = { - count = count + 1; + count.set(count.get + 1); if (n != 0) { val n1 = n - 1; permute(arr, n1); @@ -28,7 +29,7 @@ def run(n: Int) = { array(n, 1).permute(n); - count + count.get } def main() = benchmark(6){run} diff --git a/examples/benchmarks/are_we_fast_yet/storage.effekt b/examples/benchmarks/are_we_fast_yet/storage.effekt index 17ab62fb2..315a83db7 100644 --- a/examples/benchmarks/are_we_fast_yet/storage.effekt +++ b/examples/benchmarks/are_we_fast_yet/storage.effekt @@ -1,5 +1,6 @@ import examples/benchmarks/runner +import ref import array type Tree { @@ -25,10 +26,10 @@ def withRandom[R]{ program: { Random } => R }: R = { } def run(n: Int) = { - var count in global = 0; + val count = ref(0); def buildTreeDepth(depth: Int) { rand: Random }: Tree = { - count = count + 1; + count.set(count.get + 1); if (depth == 1) { Leaf(allocate(mod(rand.next, 10) + 1)) } else { @@ -44,7 +45,7 @@ def run(n: Int) = { withRandom { { rand: Random } => buildTreeDepth(7) {rand}; () } } - count + count.get } def main() = benchmark(1){run} diff --git a/examples/pos/bidirectional/typeparametric.effekt b/examples/pos/bidirectional/typeparametric.effekt index 61b1e9ed5..f9eaf2da9 100644 --- a/examples/pos/bidirectional/typeparametric.effekt +++ b/examples/pos/bidirectional/typeparametric.effekt @@ -3,6 +3,7 @@ extern interface Cap[U, V] extern pure def cap[U, V](): Cap[U, V] at {} = js "42" chez "42" + llvm "ret %Pos undef" interface Foo[S] { def op[A]() {f: Cap[S, A]}: Cap[S, A] at {f} / { Exception[S] } diff --git a/examples/pos/capture/selfregion.effekt b/examples/pos/capture/selfregion.effekt index 165d574b5..b2e14bfb3 100644 --- a/examples/pos/capture/selfregion.effekt +++ b/examples/pos/capture/selfregion.effekt @@ -46,8 +46,8 @@ def ex5() = def ex6() = { - var x in global = 42; - println(x + x) + val x = ref(42); + println(x.get + x.get) } def main() = { diff --git a/examples/stdlib/queue.check b/examples/stdlib/queue.check new file mode 100644 index 000000000..e983f7fb2 --- /dev/null +++ b/examples/stdlib/queue.check @@ -0,0 +1,12 @@ +true +false +Some(3) +Some(1) +Some(2) +Some(4) +Some(5) +Some(6) +Some(7) +Some(8) +Some(9) +None() diff --git a/examples/stdlib/queue.effekt b/examples/stdlib/queue.effekt new file mode 100644 index 000000000..a0069a115 --- /dev/null +++ b/examples/stdlib/queue.effekt @@ -0,0 +1,3 @@ +import queue + +def main() = queue::examples::main() diff --git a/examples/tour/regions.effekt.md b/examples/tour/regions.effekt.md index a0315c4f5..90d4548da 100644 --- a/examples/tour/regions.effekt.md +++ b/examples/tour/regions.effekt.md @@ -117,19 +117,22 @@ Running it will give us the same result: example1Region() ``` -## Global +## Global Mutable State -It is also possible to allocate a variable globally by allocating it into the built-in region `global`. With this, it is possible to write a program which is normally not possible: +It is also possible to allocate a variable globally by using the reference module `ref`. With this, it is possible to write a program which is normally not possible: ``` +import ref + def example5() = { - var x in global = 1 - val closure = box { () => x } + val x = ref(1) + val closure = box { () => x.get } closure } ``` -We can return a closure that closes over a variable. This is only possible because `x` is allocated into the `global` region and therefore has a static lifetime. +We can return a closure that closes over a mutable reference. This is only possible because `x` is allocated on the heap and subject to garbage collection. Note that in most cases it does not make sense to define mutable references using `var`. + ## References diff --git a/libraries/common/buffer.effekt b/libraries/common/buffer.effekt index 2c9abab28..6002bc2c0 100644 --- a/libraries/common/buffer.effekt +++ b/libraries/common/buffer.effekt @@ -3,7 +3,6 @@ module buffer import array // TODO (in Effekt compiler) -// - [ ] fix allocating into actually global region // - [ ] fix exceptions on objects // - [X] allow omitting braces after `at` for singleton regions @@ -35,12 +34,12 @@ def emptyBuffer[T](capacity: Int): Buffer[T] at {global} = { def arrayBuffer[T](initialCapacity: Int): Buffer[T] at {global} = { // TODO allocate buffer (and array) into a region r. val contents = array::allocate[T](initialCapacity) - var head in global = 0 - var tail in global = 0 + val head = ref(0) + val tail = ref(0) def size(): Int = - if (tail >= head) { tail - head } - else { initialCapacity - head + tail } + if (tail.get >= head.get) { tail.get - head.get } + else { initialCapacity - head.get + tail.get } def capacity(): Int = initialCapacity - size() @@ -51,36 +50,36 @@ def arrayBuffer[T](initialCapacity: Int): Buffer[T] at {global} = { def read() = { if (buffer.empty?) None() else { - val result: T = contents.unsafeGet(head); - head = mod(head + 1, initialCapacity) + val result: T = contents.unsafeGet(head.get) + head.set(mod(head.get + 1, initialCapacity)) Some(result) } } def write(el: T) = { if (buffer.full?) <> // raise(BufferOverflow()) - contents.unsafeSet(tail, el) - tail = mod(tail + 1, initialCapacity) + contents.unsafeSet(tail.get, el) + tail.set(mod(tail.get + 1, initialCapacity)) } } buffer } def refBuffer[T](): Buffer[T] at {global} = { - var content: Option[T] in global = None() + val content = ref(None()) new Buffer[T] { - def capacity() = if (content.isEmpty) 1 else 0 - def full?() = content.isDefined - def empty?() = isEmpty(content) + def capacity() = if (content.get.isEmpty) 1 else 0 + def full?() = content.get.isDefined + def empty?() = isEmpty(content.get) def read() = { - val res = content - content = None() + val res = content.get + content.set(None()) res } - def write(el: T) = content match { + def write(el: T) = content.get match { case Some(v) => <> // do raise(BufferOverflow(), "Cannot read element from buffer") case None() => - content = Some(el) + content.set(Some(el)) } } } @@ -88,24 +87,24 @@ def refBuffer[T](): Buffer[T] at {global} = { namespace examples { def main() = ignore[BufferOverflow] { // Buffer with capacity 1 - def b = emptyBuffer[Int](1); - println(b.capacity); - println(b.full?); + def b = emptyBuffer[Int](1) + println(b.capacity) + println(b.full?) - b.write(17); - println(b.read()); + b.write(17) + println(b.read()) // buffer with capacity 3 - def ringbuffer = emptyBuffer[Int](3); - ringbuffer.write(1); - ringbuffer.write(2); - println(ringbuffer.read()); - ringbuffer.write(3); - println(ringbuffer.read()); - println(ringbuffer.read()); - ringbuffer.write(4); - ringbuffer.write(5); - println(ringbuffer.read()); - println(ringbuffer.read()); + def ringbuffer = emptyBuffer[Int](3) + ringbuffer.write(1) + ringbuffer.write(2) + println(ringbuffer.read()) + ringbuffer.write(3) + println(ringbuffer.read()) + println(ringbuffer.read()) + ringbuffer.write(4) + ringbuffer.write(5) + println(ringbuffer.read()) + println(ringbuffer.read()) } } diff --git a/libraries/common/queue.effekt b/libraries/common/queue.effekt index d01c28640..8fe07ee9a 100644 --- a/libraries/common/queue.effekt +++ b/libraries/common/queue.effekt @@ -25,82 +25,82 @@ def emptyQueue[T](): Queue[T] at {global} = def emptyQueue[T](initialCapacity: Int): Queue[T] at {global} = { - var contents in global = array[Option[T]](initialCapacity, None()) - var head in global = 0 - var tail in global = 0 - var size in global = 0 - var capacity in global = initialCapacity + val contents = ref(array[Option[T]](initialCapacity, None())) + val head = ref(0) + val tail = ref(0) + val size = ref(0) + val capacity = ref(initialCapacity) def remove(arr: Array[Option[T]], index: Int): Option[T] = { - with on[OutOfBounds].default { None() }; - val value = arr.get(index); - arr.set(index, None()); + with on[OutOfBounds].default { None() } + val value = arr.get(index) + arr.set(index, None()) value } def nonEmpty[T] { p: => Option[T] / Exception[OutOfBounds] }: Option[T] = - if (size <= 0) None() else on[OutOfBounds].default { None() } { p() } + if (size.get <= 0) None() else on[OutOfBounds].default { None() } { p() } // Exponential back-off def resizeTo(requiredSize: Int): Unit = - if (requiredSize <= capacity) () else { + if (requiredSize <= capacity.get) () else { with on[OutOfBounds].ignore // should not happen - val oldSize = capacity - val newSize = capacity * 2 - val oldContents = contents + val oldSize = capacity.get + val newSize = capacity.get * 2 + val oldContents = contents.get val newContents = array::allocate[Option[T]](newSize) - if (head < tail) { + if (head.get < tail.get) { // The queue does not wrap around; direct copy is possible. - copy(oldContents, head, newContents, 0, size) // changed tail to size - } else if (size > 0) { + copy(oldContents, head.get, newContents, 0, size.get) // changed tail to size + } else if (size.get > 0) { // The queue wraps around; copy in two segments. - copy(oldContents, head, newContents, 0, oldSize - head) // changed oldSize to oldSize - head - copy(oldContents, 0, newContents, oldSize - head, tail) // changed oldSize - head to oldSize - head + copy(oldContents, head.get, newContents, 0, oldSize - head.get) // changed oldSize to oldSize - head + copy(oldContents, 0, newContents, oldSize - head.get, tail.get) // changed oldSize - head to oldSize - head } - contents = newContents - capacity = newSize - head = 0 - tail = oldSize + contents.set(newContents) + capacity.set(newSize) + head.set(0) + tail.set(oldSize) } def queue = new Queue[T] { - def empty?() = size <= 0 + def empty?() = size.get <= 0 def popFront() = nonEmpty { - val result = contents.remove(head) - head = mod(head + 1, capacity) - size = size - 1 + val result = contents.get.remove(head.get) + head.set(mod(head.get + 1, capacity.get)) + size.set(size.get - 1) result } def popBack() = nonEmpty { - tail = mod(tail - 1 + capacity, capacity) - val result = contents.remove(tail) - size = size - 1 + tail.set(mod(tail.get - 1 + capacity.get, capacity.get)) + val result = contents.get.remove(tail.get) + size.set(size.get - 1) result } - def peekFront() = nonEmpty { contents.get(head) } + def peekFront() = nonEmpty { contents.get.get(head.get) } - def peekBack() = nonEmpty { contents.get(tail) } + def peekBack() = nonEmpty { contents.get.get(tail.get) } def pushFront(el: T) = { - resizeTo(size + 1); - head = mod(head - 1 + capacity, capacity); - size = size + 1; - contents.unsafeSet(head, Some(el)) + resizeTo(size.get + 1) + head.set(mod(head.get - 1 + capacity.get, capacity.get)) + size.set(size.get + 1) + contents.get.unsafeSet(head.get, Some(el)) } def pushBack(el: T) = { - resizeTo(size + 1); - contents.unsafeSet(tail, Some(el)); - size = size + 1; - tail = mod(tail + 1, capacity) + resizeTo(size.get + 1) + contents.get.unsafeSet(tail.get, Some(el)) + size.set(size.get + 1) + tail.set(mod(tail.get + 1, capacity.get)) } } queue @@ -109,30 +109,30 @@ def emptyQueue[T](initialCapacity: Int): Queue[T] at {global} = { namespace examples { def main() = { // queue with initial capacity 4 - def b = emptyQueue[Int](4); - println(b.empty?); - b.pushFront(1); - b.pushBack(2); - b.pushFront(3); - b.pushBack(4); + def b = emptyQueue[Int](4) + println(b.empty?) + b.pushFront(1) + b.pushBack(2) + b.pushFront(3) + b.pushBack(4) // this will cause resizing: - b.pushBack(5); - b.pushBack(6); - b.pushBack(7); - b.pushBack(8); + b.pushBack(5) + b.pushBack(6) + b.pushBack(7) + b.pushBack(8) // and again: - b.pushBack(9); - - println(b.empty?); - println(b.popFront()); // Some(3) - println(b.popFront()); // Some(1) - println(b.popFront()); // Some(2) - println(b.popFront()); // Some(4) - println(b.popFront()); // Some(5) - println(b.popFront()); // Some(6) - println(b.popFront()); // Some(7) - println(b.popFront()); // Some(8) - println(b.popFront()); // Some(9) - println(b.popFront()); // None() + b.pushBack(9) + + println(b.empty?) + println(b.popFront()) // Some(3) + println(b.popFront()) // Some(1) + println(b.popFront()) // Some(2) + println(b.popFront()) // Some(4) + println(b.popFront()) // Some(5) + println(b.popFront()) // Some(6) + println(b.popFront()) // Some(7) + println(b.popFront()) // Some(8) + println(b.popFront()) // Some(9) + println(b.popFront()) // None() } } diff --git a/libraries/js/effekt_runtime.js b/libraries/js/effekt_runtime.js index 57ccfc28d..bb149178d 100644 --- a/libraries/js/effekt_runtime.js +++ b/libraries/js/effekt_runtime.js @@ -40,16 +40,6 @@ function Arena() { return s } -const global = { - fresh: (v) => { - const r = { - value: v, - set: (v) => { r.value = v } - }; - return r - } -} - function snapshot(s) { const snap = { store: s, root: s.root, generation: s.generation } s.generation = s.generation + 1 diff --git a/libraries/llvm/rts.ll b/libraries/llvm/rts.ll index 52dbc9ed6..861665905 100644 --- a/libraries/llvm/rts.ll +++ b/libraries/llvm/rts.ll @@ -678,14 +678,7 @@ define private void @freeStack(%StackPointer %stackPointer) alwaysinline { ; RTS initialization define private tailcc void @topLevel(%Pos %val, %Stack %stack) { - %rest = call %Stack @underflowStack(%Stack %stack) - ; rest holds global variables - call void @resume_Pos(%Stack %rest, %Pos %val) - ret void -} - -define private tailcc void @globalsReturn(%Pos %val, %Stack %stack) { - %rest = call %Stack @underflowStack(%Stack %stack) + call %Stack @underflowStack(%Stack %stack) ret void } @@ -699,29 +692,8 @@ define private void @topLevelEraser(%Environment %environment) { ret void } -@global = private global { i64, %Stack } { i64 0, %Stack null } - define private %Stack @withEmptyStack() { - %globals = call %Stack @reset(%Stack null) - - %globalsStackPointer_pointer = getelementptr %StackValue, %Stack %globals, i64 0, i32 1 - %globalsStackPointer = load %StackPointer, ptr %globalsStackPointer_pointer, !alias.scope !11, !noalias !21 - - %returnAddressPointer.0 = getelementptr %FrameHeader, %StackPointer %globalsStackPointer, i64 0, i32 0 - %sharerPointer.0 = getelementptr %FrameHeader, %StackPointer %globalsStackPointer, i64 0, i32 1 - %eraserPointer.0 = getelementptr %FrameHeader, %StackPointer %globalsStackPointer, i64 0, i32 2 - - store ptr @globalsReturn, ptr %returnAddressPointer.0, !alias.scope !12, !noalias !22 - store ptr @topLevelSharer, ptr %sharerPointer.0, !alias.scope !12, !noalias !22 - store ptr @topLevelEraser, ptr %eraserPointer.0, !alias.scope !12, !noalias !22 - - %globalsStackPointer_2 = getelementptr %FrameHeader, %StackPointer %globalsStackPointer, i64 1 - store %StackPointer %globalsStackPointer_2, ptr %globalsStackPointer_pointer, !alias.scope !11, !noalias !21 - - %stack = call %Stack @reset(%Stack %globals) - - %globalStack = getelementptr %PromptValue, %Prompt @global, i64 0, i32 1 - store %Stack %stack, ptr %globalStack, !alias.scope !13, !noalias !23 + %stack = call %Stack @reset(%Stack null) %stackStackPointer = getelementptr %StackValue, %Stack %stack, i64 0, i32 1 %stackPointer = load %StackPointer, ptr %stackStackPointer, !alias.scope !11, !noalias !21