From 6cc81c6edbff6fac3d87e4fc0ee1c538f51b7b78 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 4 Jan 2023 23:09:42 +0100 Subject: [PATCH 01/24] Removing test that relies on Unsafe --- .../interpreter/test/DebuggingEnsoTest.java | 63 ------------------- 1 file changed, 63 deletions(-) diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java b/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java index b9c5c7569e3d..529179bf814d 100644 --- a/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java @@ -613,69 +613,6 @@ public String toString() { } } - // TODO[PM]: Re-enable (https://www.pivotaltracker.com/story/show/183854585) - @Test - @Ignore - public void unsafeRecursiveAtom() throws Exception { - Engine eng = Engine.newBuilder() - .allowExperimentalOptions(true) - .option( - RuntimeOptions.LANGUAGE_HOME_OVERRIDE, - Paths.get("../../test/micro-distribution/component").toFile().getAbsolutePath() - ).build(); - Context ctx = Context.newBuilder() - .engine(eng) - .allowIO(true) - .allowHostClassLoading(true) - .allowHostClassLookup((c) -> true) - .build(); - final Map langs = ctx.getEngine().getLanguages(); - org.junit.Assert.assertNotNull("Enso found: " + langs, langs.get("enso")); - - final URI onceUri = new URI("memory://once.enso"); - final Source onesSrc = Source.newBuilder("enso", """ - import Standard.Base.Runtime.Unsafe - - type Gen - Empty - Generator a:Int tail:Gen - - ones : Gen - ones = - g = Gen.Generator 1 Gen.Empty - Unsafe.set_atom_field g 1 g - g - - next g = case g of - Gen.Generator a tail -> a - Gen.Empty -> -1 - """, "ones.enso") - .uri(onceUri) - .buildLiteral(); - - var module = ctx.eval(onesSrc); - var ones = module.invokeMember("eval_expression", "ones"); - var next = module.invokeMember("eval_expression", "next"); - - - final var dbg = Debugger.find(eng); - final var values = new HashSet(); - try (var session = dbg.startSession((event) -> { - final DebugValue gVariable = findDebugValue(event, "g"); - if (gVariable != null) { - final String str = gVariable.toDisplayString(false); - assertNotNull("The string shall always be computed for " + gVariable, str); - values.add(str); - } - event.getSession().suspendNextExecution(); - })) { - session.suspendNextExecution(); - var one = next.execute(ones); - Assert.assertEquals("First element from list of ones", 1, one.asInt()); - } - Assert.assertEquals("Some values of g variable found: " + values, 1, values.size()); - } - private static DebugValue findDebugValue(SuspendedEvent event, final String n) throws DebugException { for (var v : event.getTopStackFrame().getScope().getDeclaredValues()) { if (v.getName().contains(n)) { From 983c37ed3ee8efa0df4bd17c76a08517b3bddd85 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 4 Jan 2023 23:17:25 +0100 Subject: [PATCH 02/24] Replacing set_atom_field with Meta.lazy_atom --- .../Base/0.0.0-dev/src/Data/List.enso | 33 ++---- .../lib/Standard/Base/0.0.0-dev/src/Meta.enso | 3 + .../Base/0.0.0-dev/src/Runtime/Unsafe.enso | 18 --- .../builtin/meta/LazyAtomInstanceNode.java | 112 ++++++++++++++++++ .../builtin/unsafe/SetAtomFieldNode.java | 17 --- 5 files changed, 127 insertions(+), 56 deletions(-) delete mode 100644 distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Unsafe.enso create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/unsafe/SetAtomFieldNode.java diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso index 8b0b1b3b31d0..986f08e27eb4 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso @@ -7,7 +7,7 @@ import project.Data.Vector.Vector import project.Error.Error import project.Function.Function import project.Nothing.Nothing -import project.Runtime.Unsafe +import project.Meta from project.Data.Boolean import Boolean, True, False @@ -209,8 +209,17 @@ type List map self f = case self of Nil -> Nil Cons h t -> - res = Cons (f h) Nil - map_helper t res f + go : List -> Any -> (Any -> Any) -> Nothing + go list fill = case list of + Nil -> fill Nil + Cons h t -> + v = f h + res = Meta.lazy_atom (e -> Cons v e) + fill res + @Tail_Call go t (res.at 1) + v = f h + res = Meta.lazy_atom (e -> Cons v e) + go t (res.at 1) res ## Applies a function to each element of the list. @@ -379,21 +388,3 @@ type Empty_Error to_display_text : Text to_display_text self = "The List is empty." -## PRIVATE - A helper for the `map` function. - - Arguments: - - list: The list to map over. - - cons: The current field to set. - - f: The function to apply to the value. - - Uses unsafe field mutation under the hood, to rewrite `map` in - a tail-recursive manner. The mutation is purely internal and does not leak - to the user-facing API. -map_helper : List -> Any -> (Any -> Any) -> Nothing -map_helper list cons f = case list of - Nil -> Unsafe.set_atom_field cons 1 Nil - Cons h t -> - res = Cons (f h) Nil - Unsafe.set_atom_field cons 1 res - @Tail_Call map_helper t res f diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso index 439ebab8de1f..d4c5fc26b1bf 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso @@ -273,6 +273,9 @@ get_constructor_name atom_constructor = @Builtin_Method "Meta.get_constructor_na new_atom : Any -> Array -> Atom new_atom constructor fields = @Builtin_Method "Meta.new_atom" +lazy_atom : (Nothing -> Atom) -> Atom +lazy_atom constructor fields = @Builtin_Method "Meta.lazy_atom" + ## UNSTABLE ADVANCED diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Unsafe.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Unsafe.enso deleted file mode 100644 index 6dbee4406a78..000000000000 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Unsafe.enso +++ /dev/null @@ -1,18 +0,0 @@ -## Unsafe operations. - A container for unsafe operations that operate based on implementation - details of the language. - -import project.Any.Any -import project.Data.Numbers.Integer -import project.Meta.Atom - -## PRIVATE - - Sets the atom field at the provided index to have the provided value. - - Arguments: - - atom: The atom to set the field in. - - index: The index of the field to set (zero-based). - - value: The value to set the field at index to. -set_atom_field : Atom -> Integer -> Any -> Atom -set_atom_field atom index value = @Builtin_Method "Unsafe.set_atom_field" diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java new file mode 100644 index 000000000000..0cbe62bac121 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java @@ -0,0 +1,112 @@ +package org.enso.interpreter.node.expression.builtin.meta; + +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.ArityException; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.interop.UnsupportedTypeException; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.expression.builtin.mutable.CoerceArrayNode; +import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.data.Array; +import org.enso.interpreter.runtime.data.Vector; +import org.enso.interpreter.runtime.error.PanicException; + +@BuiltinMethod( + type = "Meta", + name = "lazy_atom", + description = "Creates a new atom with given constructor and fields.", + autoRegister = false) +public abstract class LazyAtomInstanceNode extends Node { + + static LazyAtomInstanceNode build() { + return LazyAtomInstanceNodeGen.create(); + } + + abstract Vector execute(Object factory); + + @Specialization(guards = "iop.isExecutable(factory)") + Vector doExecute(Object factory, @CachedLibrary(limit="3") InteropLibrary iop, @Cached CoerceArrayNode coerce) { + var ctx = EnsoContext.get(this); + var lazy = new HoleInAtom(null, -1, null); + try { + var r = iop.execute(factory, lazy); + if (r instanceof Atom a) { + for (int i = 0; i < a.getFields().length; i++) { + if (a.getFields()[i] == lazy) { + return Vector.fromArray(new Array(a, new HoleInAtom(a, i, lazy))); + } + } + } + throw new PanicException(ctx.getBuiltins().error().getInvalidArrayIndex(), this); + } catch (UnsupportedTypeException ex) { + throw raise(RuntimeException.class, ex); + } catch (ArityException ex) { + throw raise(RuntimeException.class, ex); + } catch (UnsupportedMessageException ex) { + throw raise(RuntimeException.class, ex); + } + } + + @SuppressWarnings("unchecked") + private static E raise(Class type, Throwable t) throws E { + throw (E)t; + } + + @ExportLibrary(InteropLibrary.class) + static final class HoleInAtom implements TruffleObject { + private final Atom atom; + private final int index; + private final Object empty; + + HoleInAtom(Atom atom, int index, Object empty) { + this.atom = atom; + this.index = index; + this.empty = empty; + } + + @ExportMessage + Object invokeMember(String name, Object[] args, @CachedLibrary("this") InteropLibrary iop) throws UnsupportedMessageException { + if ("set".equals(name)) { + return execute(args, iop); + } else { + throw UnsupportedMessageException.create(); + } + } + + @ExportMessage + boolean isMemberInvocable(String name) { + return "set".equals(name); + } + + @ExportMessage boolean hasMembers() { + return true; + } + + @ExportMessage + Object getMembers(boolean includeInternal) throws UnsupportedMessageException { + return new Array("set"); + } + + @ExportMessage + Object execute(Object[] args, @CachedLibrary("this") InteropLibrary iop) { + var prev = atom.getFields()[index]; + if (prev == empty) { + atom.getFields()[index] = args[0]; + } + return EnsoContext.get(iop).getBuiltins().nothing(); + } + + @ExportMessage + boolean isExecutable() { + return true; + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/unsafe/SetAtomFieldNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/unsafe/SetAtomFieldNode.java deleted file mode 100644 index 22d72bc8748a..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/unsafe/SetAtomFieldNode.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.unsafe; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.callable.atom.Atom; - -@BuiltinMethod( - type = "Unsafe", - name = "set_atom_field", - description = "Unsafely, in place, sets the value of an atom field by index.", - autoRegister = false) -public class SetAtomFieldNode extends Node { - Atom execute(Atom atom, long index, Object value) { - atom.getFields()[(int) index] = value; - return atom; - } -} From ff7a47429b53f837d788ad56b5d2631c5bea75c3 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 4 Jan 2023 23:21:52 +0100 Subject: [PATCH 03/24] invokeMember instead of execute --- distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso index 986f08e27eb4..a46beb7dc5d0 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso @@ -211,16 +211,16 @@ type List Cons h t -> go : List -> Any -> (Any -> Any) -> Nothing go list fill = case list of - Nil -> fill Nil + Nil -> fill.set Nil Cons h t -> v = f h res = Meta.lazy_atom (e -> Cons v e) - fill res + fill.set (res.at 0) @Tail_Call go t (res.at 1) v = f h res = Meta.lazy_atom (e -> Cons v e) go t (res.at 1) - res + res.at 0 ## Applies a function to each element of the list. From f3449b8e3674be8fd408941be5761146ce0e46a9 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 5 Jan 2023 07:22:50 +0100 Subject: [PATCH 04/24] With incremental compilation there may not be any input files --- project/FrgaalJavaCompiler.scala | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/project/FrgaalJavaCompiler.scala b/project/FrgaalJavaCompiler.scala index 2dca3850c0cb..7e3188799c10 100644 --- a/project/FrgaalJavaCompiler.scala +++ b/project/FrgaalJavaCompiler.scala @@ -118,7 +118,11 @@ object FrgaalJavaCompiler { } val (withTarget, noTarget) = sources0.partition(checkTarget) - val in = findUnder(3, noTarget.tail.fold(asPath(noTarget.head))(asCommon).asInstanceOf[Path]) + val in = if (noTarget.isEmpty) { + None + } else { + Some(findUnder(3, noTarget.tail.fold(asPath(noTarget.head))(asCommon).asInstanceOf[Path])) + } val generated = if (withTarget.isEmpty) { None } else { @@ -133,8 +137,9 @@ object FrgaalJavaCompiler { def storeArray(name: String, values : Seq[String]) = { values.zipWithIndex.foreach { case (value, idx) => ensoProperties.setProperty(s"$name.$idx", value) } } - - ensoProperties.setProperty("input", in.toString()) + if (in.isDefined) { + ensoProperties.setProperty("input", in.get.toString()) + } if (generated.isDefined) { ensoProperties.setProperty("generated", generated.get.toString()) } From e3840fb6dfb55fb3ebe82945bf22f666db113f19 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 5 Jan 2023 09:10:07 +0100 Subject: [PATCH 05/24] Removing Unsafe from micro-distribution --- .../lib/Standard/Base/0.0.0-dev/src/Runtime/Unsafe.enso | 1 - 1 file changed, 1 deletion(-) delete mode 100644 test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Unsafe.enso diff --git a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Unsafe.enso b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Unsafe.enso deleted file mode 100644 index 74beb65048f3..000000000000 --- a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Unsafe.enso +++ /dev/null @@ -1 +0,0 @@ -set_atom_field atom index value = @Builtin_Method "Unsafe.set_atom_field" From 3df23b3acdb2a9986d6694f0cffea2a8f9927c76 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 5 Jan 2023 09:32:16 +0100 Subject: [PATCH 06/24] Make fill function really a function that can be called --- .../Base/0.0.0-dev/src/Data/List.enso | 4 +- .../lib/Standard/Base/0.0.0-dev/src/Meta.enso | 2 +- .../builtin/meta/LazyAtomInstanceNode.java | 89 +++++++++++-------- 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso index a46beb7dc5d0..839be80f2eb5 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso @@ -211,11 +211,11 @@ type List Cons h t -> go : List -> Any -> (Any -> Any) -> Nothing go list fill = case list of - Nil -> fill.set Nil + Nil -> fill Nil Cons h t -> v = f h res = Meta.lazy_atom (e -> Cons v e) - fill.set (res.at 0) + fill (res.at 0) @Tail_Call go t (res.at 1) v = f h res = Meta.lazy_atom (e -> Cons v e) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso index d4c5fc26b1bf..1962400dad08 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso @@ -273,7 +273,7 @@ get_constructor_name atom_constructor = @Builtin_Method "Meta.get_constructor_na new_atom : Any -> Array -> Atom new_atom constructor fields = @Builtin_Method "Meta.new_atom" -lazy_atom : (Nothing -> Atom) -> Atom +lazy_atom : (Any -> Atom) -> Atom lazy_atom constructor fields = @Builtin_Method "Meta.lazy_atom" ## UNSTABLE diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java index 0cbe62bac121..028c7c620121 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java @@ -2,6 +2,7 @@ import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.interop.ArityException; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.TruffleObject; @@ -11,10 +12,15 @@ import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.nodes.RootNode; import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.node.expression.builtin.mutable.CoerceArrayNode; import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; +import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.callable.function.FunctionSchema; import org.enso.interpreter.runtime.data.Array; import org.enso.interpreter.runtime.data.Vector; import org.enso.interpreter.runtime.error.PanicException; @@ -33,15 +39,21 @@ static LazyAtomInstanceNode build() { abstract Vector execute(Object factory); @Specialization(guards = "iop.isExecutable(factory)") - Vector doExecute(Object factory, @CachedLibrary(limit="3") InteropLibrary iop, @Cached CoerceArrayNode coerce) { + Vector doExecute( + Object factory, + @CachedLibrary(limit="3") InteropLibrary iop, + @Cached CoerceArrayNode coerce, + @Cached SwapAtomFieldNode swapNode + ) { var ctx = EnsoContext.get(this); - var lazy = new HoleInAtom(null, -1, null); + var lazy = new HoleInAtom(); try { var r = iop.execute(factory, lazy); if (r instanceof Atom a) { for (int i = 0; i < a.getFields().length; i++) { if (a.getFields()[i] == lazy) { - return Vector.fromArray(new Array(a, new HoleInAtom(a, i, lazy))); + var function = swapNode.createFn(a, i, lazy); + return Vector.fromArray(new Array(a, function)); } } } @@ -62,51 +74,56 @@ private static E raise(Class type, Throwable t) throws @ExportLibrary(InteropLibrary.class) static final class HoleInAtom implements TruffleObject { - private final Atom atom; - private final int index; - private final Object empty; - - HoleInAtom(Atom atom, int index, Object empty) { - this.atom = atom; - this.index = index; - this.empty = empty; + HoleInAtom() { } @ExportMessage - Object invokeMember(String name, Object[] args, @CachedLibrary("this") InteropLibrary iop) throws UnsupportedMessageException { - if ("set".equals(name)) { - return execute(args, iop); - } else { - throw UnsupportedMessageException.create(); - } + String toDisplayString(boolean pure) { + return "Meta.lazy_atom"; } + } + static final class SwapAtomFieldNode extends RootNode { + private final FunctionSchema schema; - @ExportMessage - boolean isMemberInvocable(String name) { - return "set".equals(name); + private SwapAtomFieldNode() { + super(null); + this.schema = new FunctionSchema(FunctionSchema.CallerFrameAccess.NONE, new ArgumentDefinition[]{ + new ArgumentDefinition(0, "instance", ArgumentDefinition.ExecutionMode.EXECUTE), + new ArgumentDefinition(1, "lazy_index", ArgumentDefinition.ExecutionMode.EXECUTE), + new ArgumentDefinition(2, "lazy", ArgumentDefinition.ExecutionMode.EXECUTE), + new ArgumentDefinition(3, "value", ArgumentDefinition.ExecutionMode.EXECUTE) + }, new boolean[]{ + true, true, true, false + }, new CallArgumentInfo[0]); } - @ExportMessage boolean hasMembers() { - return true; + static SwapAtomFieldNode create() { + return new SwapAtomFieldNode(); } - @ExportMessage - Object getMembers(boolean includeInternal) throws UnsupportedMessageException { - return new Array("set"); + Function createFn(Atom atom, int index, HoleInAtom lazy) { + var preArgs = new Object[]{atom, index, lazy, null}; + return new Function( + getCallTarget(), + null, + schema, + preArgs, + new Object[]{} + ); } - @ExportMessage - Object execute(Object[] args, @CachedLibrary("this") InteropLibrary iop) { - var prev = atom.getFields()[index]; - if (prev == empty) { - atom.getFields()[index] = args[0]; + @Override + public Object execute(VirtualFrame frame) { + var args = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments()); + if (args[0] instanceof Atom replace) { + if (args[1] instanceof Number n) { + var fields = replace.getFields(); + if (fields[n.intValue()] == args[2]) { + fields[n.intValue()] = args[3]; + } + } } - return EnsoContext.get(iop).getBuiltins().nothing(); - } - - @ExportMessage - boolean isExecutable() { - return true; + return EnsoContext.get(this).getBuiltins().nothing(); } } } From ba603afd28900bd287d502dab70c1e2c6e9ac5c6 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 5 Jan 2023 09:58:41 +0100 Subject: [PATCH 07/24] Unit test to check basic functionality --- test/Tests/src/Semantic/Meta_Spec.enso | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/Tests/src/Semantic/Meta_Spec.enso b/test/Tests/src/Semantic/Meta_Spec.enso index bbf83e5b8fb5..4b5d828c0add 100644 --- a/test/Tests/src/Semantic/Meta_Spec.enso +++ b/test/Tests/src/Semantic/Meta_Spec.enso @@ -170,4 +170,29 @@ spec = Test.group "Meta-Value Manipulation" <| (Test_Type.Value a)==(Test_Type.Value b) . should_be_true (Test_Type.Value a)==(Test_Type.Value c) . should_be_false + Test.group "Lazy atom with holes" <| + Test.specify "construct and fill" <| + vec = Meta.lazy_atom (e -> My_Type.Value 1 e 3) + case vec of + v : Vector -> v.length . should_equal 2 + _ -> Test.fail "Should return an array" + + atom = vec.at 0 + fill = vec.at 1 + + Meta.is_atom atom . should_be_true + + atom.foo . should_equal 1 + atom.baz . should_equal 3 + case atom.bar of + n : Number -> Test.fail "Shouldn't be number yet: "+n + _ -> Nothing + + fill 2 + atom.bar . should_equal 2 + + fill 10 # no change + atom.bar . should_equal 2 + + main = Test_Suite.run_main spec From c185161db8d4dc81319a010186aae8f2d374591d Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 5 Jan 2023 10:23:22 +0100 Subject: [PATCH 08/24] Generate Uninitialized_State.Error if lazy_atom isn't used --- .../expression/builtin/meta/LazyAtomInstanceNode.java | 2 +- test/Tests/src/Semantic/Meta_Spec.enso | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java index 028c7c620121..ef8ff8140af2 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java @@ -57,7 +57,7 @@ Vector doExecute( } } } - throw new PanicException(ctx.getBuiltins().error().getInvalidArrayIndex(), this); + throw new PanicException(ctx.getBuiltins().error().makeUninitializedStateError(r), this); } catch (UnsupportedTypeException ex) { throw raise(RuntimeException.class, ex); } catch (ArityException ex) { diff --git a/test/Tests/src/Semantic/Meta_Spec.enso b/test/Tests/src/Semantic/Meta_Spec.enso index 4b5d828c0add..69164fe2c1bd 100644 --- a/test/Tests/src/Semantic/Meta_Spec.enso +++ b/test/Tests/src/Semantic/Meta_Spec.enso @@ -11,6 +11,7 @@ polyglot java import java.util.Locale as JavaLocale from Standard.Test import Test, Test_Suite import Standard.Test.Extensions +from Standard.Base.Error.Common import Uninitialized_State type My_Type Value foo bar baz @@ -194,5 +195,13 @@ spec = Test.group "Meta-Value Manipulation" <| fill 10 # no change atom.bar . should_equal 2 + Test.specify "fail if lazy_atom isn't used" <| + key = Panic.catch Uninitialized_State.Error handler=(caught_panic-> caught_panic.payload.key) <| + Meta.lazy_atom (_ -> My_Type.Value 1 2 3) + case key of + t : My_Type -> + t.foo . should_equal 1 + t.bar . should_equal 2 + t.baz . should_equal 3 main = Test_Suite.run_main spec From f58455599a566b0b182b5fb195b3d352e97e8b18 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 5 Jan 2023 10:24:21 +0100 Subject: [PATCH 09/24] Fail if non-atom is created --- test/Tests/src/Semantic/Meta_Spec.enso | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/Tests/src/Semantic/Meta_Spec.enso b/test/Tests/src/Semantic/Meta_Spec.enso index 69164fe2c1bd..03f37421816e 100644 --- a/test/Tests/src/Semantic/Meta_Spec.enso +++ b/test/Tests/src/Semantic/Meta_Spec.enso @@ -204,4 +204,11 @@ spec = Test.group "Meta-Value Manipulation" <| t.bar . should_equal 2 t.baz . should_equal 3 + Test.specify "fail if non-atom is created" <| + key = Panic.catch Uninitialized_State.Error handler=(caught_panic-> caught_panic.payload.key) <| + Meta.lazy_atom (_ -> 2) + case key of + t : Number -> + t . should_equal 2 + main = Test_Suite.run_main spec From 85c8cde9496823b5ad2e14dbaa85d088038525e8 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 5 Jan 2023 10:29:07 +0100 Subject: [PATCH 10/24] Allow only one lazy_atom in an Atom --- test/Tests/src/Semantic/Meta_Spec.enso | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/Tests/src/Semantic/Meta_Spec.enso b/test/Tests/src/Semantic/Meta_Spec.enso index 03f37421816e..14443fcbbb3d 100644 --- a/test/Tests/src/Semantic/Meta_Spec.enso +++ b/test/Tests/src/Semantic/Meta_Spec.enso @@ -211,4 +211,38 @@ spec = Test.group "Meta-Value Manipulation" <| t : Number -> t . should_equal 2 + Test.specify "only one lazy_atom is used" <| + vec = Meta.lazy_atom (e -> My_Type.Value e e e) + case vec of + v : Vector -> v.length . should_equal 2 + _ -> Test.fail "Should return an array" + + atom = vec.at 0 + fill = vec.at 1 + + Meta.is_atom atom . should_be_true + + case atom.foo of + n : Number -> Test.fail "Shouldn't be number yet: "+n + _ -> Nothing + case atom.baz of + n : Number -> Test.fail "Shouldn't be number yet: "+n + _ -> Nothing + case atom.bar of + n : Number -> Test.fail "Shouldn't be number yet: "+n + _ -> Nothing + + fill 2 + atom.foo . should_equal 2 + + fill 10 # no change + atom.foo . should_equal 2 + + case atom.baz of + n : Number -> Test.fail "Not changed to number: "+n + _ -> Nothing + case atom.bar of + n : Number -> Test.fail "Not changed to number: "+n + _ -> Nothing + main = Test_Suite.run_main spec From c51714b018d5969d6a95b030b0fbdfb35dce21c3 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 5 Jan 2023 10:46:10 +0100 Subject: [PATCH 11/24] Speculate the hole index remains the same --- .../builtin/meta/LazyAtomInstanceNode.java | 48 ++++++++++++++++--- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java index ef8ff8140af2..8d6a248f6615 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java @@ -1,5 +1,6 @@ package org.enso.interpreter.node.expression.builtin.meta; +import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; @@ -14,7 +15,6 @@ import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.node.expression.builtin.mutable.CoerceArrayNode; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; @@ -42,7 +42,6 @@ static LazyAtomInstanceNode build() { Vector doExecute( Object factory, @CachedLibrary(limit="3") InteropLibrary iop, - @Cached CoerceArrayNode coerce, @Cached SwapAtomFieldNode swapNode ) { var ctx = EnsoContext.get(this); @@ -50,11 +49,10 @@ Vector doExecute( try { var r = iop.execute(factory, lazy); if (r instanceof Atom a) { - for (int i = 0; i < a.getFields().length; i++) { - if (a.getFields()[i] == lazy) { - var function = swapNode.createFn(a, i, lazy); - return Vector.fromArray(new Array(a, function)); - } + var i = swapNode.findHoleIndex(a, lazy); + if (i >= 0) { + var function = swapNode.createFn(a, i, lazy); + return Vector.fromArray(new Array(a, function)); } } throw new PanicException(ctx.getBuiltins().error().makeUninitializedStateError(r), this); @@ -84,6 +82,8 @@ String toDisplayString(boolean pure) { } static final class SwapAtomFieldNode extends RootNode { private final FunctionSchema schema; + @CompilerDirectives.CompilationFinal + private int lastIndex = -1; private SwapAtomFieldNode() { super(null); @@ -101,6 +101,40 @@ static SwapAtomFieldNode create() { return new SwapAtomFieldNode(); } + int findHoleIndex(Atom atom, HoleInAtom lazy) { + var arr = atom.getFields(); + if (lastIndex >= 0 && lastIndex < arr.length) { + if (arr[lastIndex] == lazy) { + return lastIndex; + } + } + int index = findHoleIndexLoop(arr, lazy); + if (index == -1) { + return -1; + } + if (lastIndex == -1) { + lastIndex = index; + CompilerDirectives.transferToInterpreterAndInvalidate(); + return index; + } else { + if (lastIndex != -2) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + lastIndex = -2; + } + } + return index; + } + + @CompilerDirectives.TruffleBoundary + private int findHoleIndexLoop(Object[] arr, HoleInAtom lazy) { + for (int i = 0; i < arr.length; i++) { + if (arr[i] == lazy) { + return i; + } + } + return -1; + } + Function createFn(Atom atom, int index, HoleInAtom lazy) { var preArgs = new Object[]{atom, index, lazy, null}; return new Function( From 28ca763543951e04509831367644fef410629b13 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 5 Jan 2023 11:16:29 +0100 Subject: [PATCH 12/24] Specify the exact type for instanceof --- .../node/expression/builtin/meta/LazyAtomInstanceNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java index 8d6a248f6615..ecf2ad1d4c10 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java @@ -150,7 +150,7 @@ Function createFn(Atom atom, int index, HoleInAtom lazy) { public Object execute(VirtualFrame frame) { var args = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments()); if (args[0] instanceof Atom replace) { - if (args[1] instanceof Number n) { + if (args[1] instanceof Integer n) { var fields = replace.getFields(); if (fields[n.intValue()] == args[2]) { fields[n.intValue()] = args[3]; From eaeadf2820bdc38b58ab030f114dbb51cb114451 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 6 Jan 2023 07:02:16 +0100 Subject: [PATCH 13/24] ListTest to verify behavior of operations that need @Tail_Call annotation --- .../org/enso/interpreter/test/ListTest.java | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 engine/runtime/src/test/java/org/enso/interpreter/test/ListTest.java diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/ListTest.java b/engine/runtime/src/test/java/org/enso/interpreter/test/ListTest.java new file mode 100644 index 000000000000..ed62d342facf --- /dev/null +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/ListTest.java @@ -0,0 +1,153 @@ +package org.enso.interpreter.test; + +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Map; +import org.enso.polyglot.RuntimeOptions; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Language; +import org.graalvm.polyglot.Source; +import org.graalvm.polyglot.Value; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + +public class ListTest { + private Context ctx; + private final int size = 10; + private Value generator; + private Value plusOne; + private Value evenOnes; + private Value taken; + private Value init; + private Value asVector; + + @Before + public void prepareCtx() throws Exception { + this.ctx = Context.newBuilder() + .allowExperimentalOptions(true) + .allowIO(true) + .allowAllAccess(true) + .logHandler(new ByteArrayOutputStream()) + .option( + RuntimeOptions.LANGUAGE_HOME_OVERRIDE, + Paths.get("../../distribution/component").toFile().getAbsolutePath() + ).build(); + + final Map langs = ctx.getEngine().getLanguages(); + assertNotNull("Enso found: " + langs, langs.get("enso")); + + final String code = """ + from Standard.Base.Data.List.List import Cons, Nil + + init list = list.init + + taken list n = list.take_start n + + even_ones list = list.filter (x -> x % 2 == 0) + + plus_one list = list.map (x -> x + 1) + + as_vector list = list.to_vector + + generator n = + go x v l = if x > n then l else + @Tail_Call go x+1 v+1 (Cons v l) + go 1 1 Nil + """; + + generator = evalCode(code, "generator"); + plusOne = evalCode(code, "plus_one"); + evenOnes = evalCode(code, "even_ones"); + taken = evalCode(code, "taken"); + init = evalCode(code, "init"); + asVector = evalCode(code, "as_vector"); + } + + @Test + public void mapPlusOneAndIterate() throws Exception { + var list = generator.execute(size); + assertEquals("Size is OK", size, list.invokeMember("length").asInt()); + + var values = new ArrayList(); + { + var it = plusOne.execute(list); + + for (long i = 0; i < size; i++) { + var e = it.getMember("x"); + assertTrue("All numbers are numbers, but " + e + " is not", e.isNumber()); + var n = e.as(Number.class); + assertNotNull("All numbers can be seen as java.lang.Number", n); + var b = new BigInteger(n.toString()); + assertNotNull("Each Enso number can be parsed as big integer", b); + assertEquals("Textual values are the same", n.toString(), b.toString()); + values.add(b); + it = it.getMember("xs"); + } + } + assertEquals("Two hundred numbers collected", size, values.size()); + for (int i = 1; i < values.size(); i++) { + var prev = values.get(i - 1); + var next = values.get(i); + + assertEquals("Each value is accurate", next.add(BigInteger.ONE), prev); + } + } + + @Test + public void filterEvenOnes() throws Exception { + var list = generator.execute(size); + assertLength("Generated", size, list); + var even = evenOnes.execute(list); + assertLength("Half the size", size / 2, even); + } + + @Test + public void takeHalf() throws Exception { + var list = generator.execute(size); + assertLength("Generated", size, list); + var even = taken.execute(list, size / 2); + assertLength("Half the size", size / 2, even); + } + + @Test + public void initAllButLast() throws Exception { + var list = generator.execute(size); + assertLength("Generated all", size, list); + var shorterByOne = init.execute(list); + assertLength("Last element is gone", size - 1, shorterByOne); + } + + @Test + public void toVector() throws Exception { + var list = generator.execute(size); + assertLength("Generated all", size, list); + var vec = asVector.execute(list); + assertEquals("It's vector", "Vector", vec.getMetaObject().getMetaSimpleName()); + assertTrue("And an array like object", vec.hasArrayElements()); + assertEquals("The same size remains", size, vec.getArraySize()); + } + + private Value evalCode(final String code, final String methodName) throws URISyntaxException { + final var testName = "test.enso"; + final URI testUri = new URI("memory://" + testName); + final Source src = Source.newBuilder("enso", code, testName) + .uri(testUri) + .buildLiteral(); + var module = ctx.eval(src); + var powers = module.invokeMember("eval_expression", methodName); + return powers; + } + + private void assertLength(String msg, long expected, Value list) { + var actual = list.invokeMember("length"); + assertTrue("Size fits into number", actual.fitsInLong()); + assertEquals(msg, expected, actual.asLong()); + } +} From 9222ff54c94035c7857eaba0fc8a3e5713cb0572 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 6 Jan 2023 08:22:14 +0100 Subject: [PATCH 14/24] Renaming to Meta.atom_with_hole and returning Pair --- .../Base/0.0.0-dev/src/Data/List.enso | 12 +- .../lib/Standard/Base/0.0.0-dev/src/Meta.enso | 16 +- ...stanceNode.java => AtomWithAHoleNode.java} | 31 +- test/Tests/src/Semantic/Meta_Spec.enso | 333 +++++++++--------- 4 files changed, 201 insertions(+), 191 deletions(-) rename engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/{LazyAtomInstanceNode.java => AtomWithAHoleNode.java} (96%) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso index 839be80f2eb5..6130f14b4f70 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso @@ -214,13 +214,13 @@ type List Nil -> fill Nil Cons h t -> v = f h - res = Meta.lazy_atom (e -> Cons v e) - fill (res.at 0) - @Tail_Call go t (res.at 1) + res = Meta.atom_with_hole (e -> Cons v e) + fill res.first + @Tail_Call go t res.second v = f h - res = Meta.lazy_atom (e -> Cons v e) - go t (res.at 1) - res.at 0 + res = Meta.atom_with_hole (e -> Cons v e) + go t res.second + res.first ## Applies a function to each element of the list. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso index 1962400dad08..d84d24b3d445 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso @@ -3,6 +3,7 @@ import project.Data.Array.Array import project.Data.Numbers.Decimal import project.Data.Numbers.Integer import project.Data.Numbers.Number +import project.Data.Pair.Pair import project.Data.Text.Text import project.Data.Time.Date_Time.Date_Time import project.Data.Time.Date.Date @@ -273,8 +274,19 @@ get_constructor_name atom_constructor = @Builtin_Method "Meta.get_constructor_na new_atom : Any -> Array -> Atom new_atom constructor fields = @Builtin_Method "Meta.new_atom" -lazy_atom : (Any -> Atom) -> Atom -lazy_atom constructor fields = @Builtin_Method "Meta.lazy_atom" +## PRIVATE + + Constructs a new atom with a "hole". Returns a `Pair` of the newly created + atom and a function to "fill the hole" later. + + Arguments: + - factory: a function that takes the "hole" element and returns newly created atom +atom_with_hole : (Any -> Atom) -> Pair Atom (Any -> Nothing) +atom_with_hole factory = + vec = atom_with_hole_builtin factory + Pair.new (vec.at 0) (vec.at 1) + +atom_with_hole_builtin factory = @Builtin_Method "Meta.atom_with_hole_builtin" ## UNSTABLE ADVANCED diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java similarity index 96% rename from engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java rename to engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java index ecf2ad1d4c10..a0ff5628e179 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/LazyAtomInstanceNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java @@ -1,5 +1,16 @@ package org.enso.interpreter.node.expression.builtin.meta; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; +import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; +import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.callable.function.FunctionSchema; +import org.enso.interpreter.runtime.data.Array; +import org.enso.interpreter.runtime.data.Vector; +import org.enso.interpreter.runtime.error.PanicException; + import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Specialization; @@ -14,26 +25,16 @@ import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.EnsoContext; -import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; -import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; -import org.enso.interpreter.runtime.callable.atom.Atom; -import org.enso.interpreter.runtime.callable.function.Function; -import org.enso.interpreter.runtime.callable.function.FunctionSchema; -import org.enso.interpreter.runtime.data.Array; -import org.enso.interpreter.runtime.data.Vector; -import org.enso.interpreter.runtime.error.PanicException; @BuiltinMethod( type = "Meta", - name = "lazy_atom", + name = "atom_with_hole_builtin", description = "Creates a new atom with given constructor and fields.", autoRegister = false) -public abstract class LazyAtomInstanceNode extends Node { +public abstract class AtomWithAHoleNode extends Node { - static LazyAtomInstanceNode build() { - return LazyAtomInstanceNodeGen.create(); + static AtomWithAHoleNode build() { + return AtomWithAHoleNodeGen.create(); } abstract Vector execute(Object factory); @@ -77,7 +78,7 @@ static final class HoleInAtom implements TruffleObject { @ExportMessage String toDisplayString(boolean pure) { - return "Meta.lazy_atom"; + return "Meta.atom_with_hole"; } } static final class SwapAtomFieldNode extends RootNode { diff --git a/test/Tests/src/Semantic/Meta_Spec.enso b/test/Tests/src/Semantic/Meta_Spec.enso index 14443fcbbb3d..dd998f5e47ee 100644 --- a/test/Tests/src/Semantic/Meta_Spec.enso +++ b/test/Tests/src/Semantic/Meta_Spec.enso @@ -21,165 +21,166 @@ My_Type.my_method self = self.foo + self.bar + self.baz type Test_Type Value x -spec = Test.group "Meta-Value Manipulation" <| - Test.specify "should allow manipulating unresolved symbols" <| - sym = .does_not_exist - meta_sym = Meta.meta sym - meta_sym.name.should_equal "does_not_exist" - new_sym = meta_sym . rename "my_method" - object = My_Type.Value 1 2 3 - new_sym object . should_equal 6 - Test.specify "should allow manipulating atoms" <| - atom = My_Type.Value 1 "foo" Nothing - meta_atom = Meta.meta atom - meta_atom.constructor.value.should_equal My_Type.Value - meta_atom.fields.should_equal [1, "foo", Nothing] - Meta.meta (meta_atom.constructor.value) . new [1, "foo", Nothing] . should_equal atom - Test.specify "should allow getting a value's constructor's name" <| - Meta.meta List.Nil . constructor . name . should_equal "Nil" - Meta.meta (List.Cons 1 List.Nil) . constructor . name . should_equal "Cons" - Test.specify "should allow getting a value's constructor's fields" <| - Meta.meta List.Nil . constructor . fields . should_equal [] - Meta.meta (List.Cons 1 List.Nil) . constructor . fields . should_equal ["x", "xs"] - Test.specify "should allow creating atoms from atom constructors" <| - atom_1 = Meta.new_atom My_Type.Value [1,"foo", Nothing] - (Meta.meta atom_1).constructor.value . should_equal My_Type.Value - atom_2 = Meta.new_atom My_Type.Value [1,"foo", Nothing].to_array - (Meta.meta atom_2).constructor.value . should_equal My_Type.Value - Test.specify "should correctly return representations of different classes of objects" <| - Meta.meta 1 . should_equal (Meta.Primitive.Value 1) - Meta.meta "foo" . should_equal (Meta.Primitive.Value "foo") - Test.specify "should allow manipulation of error values" <| - err = Error.throw "My Error" - meta_err = Meta.meta err - meta_err.is_a Meta.Error.Value . should_be_true - meta_err.value . should_equal "My Error" - Test.specify "should allow checking if a value is of a certain type" <| - 1.is_a Any . should_be_true - 1.2.is_a Any . should_be_true - (My_Type.Value 1 "foo" Nothing).is_a Any . should_be_true - - Array.is_a Array . should_be_false - [].to_array.is_a Array . should_be_true - [].to_array.is_a Decimal . should_be_false - [1,2,3].is_a Vector . should_be_true - [1,2,3].is_a Array . should_be_false - - Boolean.is_a Boolean . should_be_false - True.is_a Boolean . should_be_true - False.is_a Boolean . should_be_true - True.is_a Integer . should_be_false - - "".is_a Text . should_be_true - "".is_a Decimal . should_be_false - - 1.is_a Array . should_be_false - 1.is_a Integer . should_be_true - 1.is_a Number . should_be_true - 1.is_a Decimal . should_be_false - 1.is_a Date . should_be_false - - 1.0.is_a Number . should_be_true - 1.0.is_a Decimal . should_be_true - 1.0.is_a Integer . should_be_false - 1.0.is_a Text . should_be_false - - random_gen = Random.new - Meta.is_a random_gen Random . should_be_true - Meta.is_a random_gen Integer . should_be_false - - (My_Type.Value 1 "foo" Nothing).is_a My_Type.Value . should_be_true - (My_Type.Value 1 "foo" Nothing).is_a Test_Type.Value . should_be_false - (My_Type.Value 1 "foo" Nothing).is_a Number . should_be_false - - err = Error.throw "Error Value" - 1.is_a Error . should_be_false - err.is_a Error . should_be_true - err.is_a Text . should_be_false - Meta.is_a err Error . should_be_true - Meta.is_a err Text . should_be_false - - Meta.is_a Date.now Date . should_be_true - Meta.is_a Date_Time.now Date_Time . should_be_true - Meta.is_a Date_Time.now Date . should_be_false - Meta.is_a Time_Of_Day.now Time_Of_Day . should_be_true - Meta.is_a Time_Of_Day.now Date . should_be_false - Meta.is_a Date_Time.now.zone Time_Zone . should_be_true - Meta.is_a Date_Time.now.zone Date . should_be_false - - Test.specify "should allow for returning the type of a value" <| - n_1 = Meta.type_of 42 - n_1 . should_equal_type Integer - n_1 . should_not_equal_type Decimal - - n_2 = Meta.type_of 2.81 - n_2 . should_equal_type Decimal - n_2 . should_not_equal_type Integer - - n_3 = Meta.type_of (JLong.MAX_VALUE * 2) - n_3 . should_equal_type Integer - n_3 . should_not_equal_type Decimal - - v_tpe = Meta.type_of [1,2,3] - v_tpe . should_equal_type Vector - v_tpe . should_not_equal_type Array - Meta.type_of [1,2,3].to_array . should_equal_type Array - - Meta.type_of "foo" . should_equal_type Text - Meta.type_of "0" . should_not_equal_type Integer - - Meta.type_of True . should_equal_type Boolean - Meta.type_of False . should_not_equal_type Any - - (Meta.type_of Date.now) . should_equal_type Date - (Meta.type_of Date.now) . should_not_equal_type Date_Time - (Meta.type_of Date_Time.now) . should_equal_type Date_Time - (Meta.type_of Date_Time.now) . should_not_equal_type Date - (Meta.type_of Time_Of_Day.now) . should_equal_type Time_Of_Day - (Meta.type_of Time_Of_Day.now) . should_not_equal_type Date - (Meta.type_of Date_Time.now.zone) . should_equal_type Time_Zone - (Meta.type_of Date_Time.now.zone) . should_not_equal_type Date - (Meta.type_of Time_Zone.local) . should_equal_type Time_Zone - (Meta.type_of Time_Zone.system) . should_equal_type Time_Zone - - list = ArrayList.new - list.add 123 - list_tpe = Meta.type_of list - list_tpe . should_not_equal_type JObject - list_tpe . should_equal_type ArrayList - - e = IOException.new "meh" - e_tpe = Meta.type_of e - e_tpe . should_equal_type IOException - e_tpe . should_not_equal_type JException - - Test.specify "should correctly handle Java values" <| - java_meta = Meta.meta Random.new - java_meta . should_be_a Meta.Polyglot.Value - java_meta . get_language . should_equal Meta.Language.Java - - Test.specify "should correctly handle equality of Java values" <| - a = JavaLocale.new "en" - b = JavaLocale.new "en" - c = JavaLocale.new "pl" - - a==a . should_be_true - a==b . should_be_true - a==c . should_be_false - - (Test_Type.Value a)==(Test_Type.Value a) . should_be_true - (Test_Type.Value a)==(Test_Type.Value b) . should_be_true - (Test_Type.Value a)==(Test_Type.Value c) . should_be_false - - Test.group "Lazy atom with holes" <| +spec = + Test.group "Meta-Value Manipulation" <| + Test.specify "should allow manipulating unresolved symbols" <| + sym = .does_not_exist + meta_sym = Meta.meta sym + meta_sym.name.should_equal "does_not_exist" + new_sym = meta_sym . rename "my_method" + object = My_Type.Value 1 2 3 + new_sym object . should_equal 6 + Test.specify "should allow manipulating atoms" <| + atom = My_Type.Value 1 "foo" Nothing + meta_atom = Meta.meta atom + meta_atom.constructor.value.should_equal My_Type.Value + meta_atom.fields.should_equal [1, "foo", Nothing] + Meta.meta (meta_atom.constructor.value) . new [1, "foo", Nothing] . should_equal atom + Test.specify "should allow getting a value's constructor's name" <| + Meta.meta List.Nil . constructor . name . should_equal "Nil" + Meta.meta (List.Cons 1 List.Nil) . constructor . name . should_equal "Cons" + Test.specify "should allow getting a value's constructor's fields" <| + Meta.meta List.Nil . constructor . fields . should_equal [] + Meta.meta (List.Cons 1 List.Nil) . constructor . fields . should_equal ["x", "xs"] + Test.specify "should allow creating atoms from atom constructors" <| + atom_1 = Meta.new_atom My_Type.Value [1,"foo", Nothing] + (Meta.meta atom_1).constructor.value . should_equal My_Type.Value + atom_2 = Meta.new_atom My_Type.Value [1,"foo", Nothing].to_array + (Meta.meta atom_2).constructor.value . should_equal My_Type.Value + Test.specify "should correctly return representations of different classes of objects" <| + Meta.meta 1 . should_equal (Meta.Primitive.Value 1) + Meta.meta "foo" . should_equal (Meta.Primitive.Value "foo") + Test.specify "should allow manipulation of error values" <| + err = Error.throw "My Error" + meta_err = Meta.meta err + meta_err.is_a Meta.Error.Value . should_be_true + meta_err.value . should_equal "My Error" + Test.specify "should allow checking if a value is of a certain type" <| + 1.is_a Any . should_be_true + 1.2.is_a Any . should_be_true + (My_Type.Value 1 "foo" Nothing).is_a Any . should_be_true + + Array.is_a Array . should_be_false + [].to_array.is_a Array . should_be_true + [].to_array.is_a Decimal . should_be_false + [1,2,3].is_a Vector . should_be_true + [1,2,3].is_a Array . should_be_false + + Boolean.is_a Boolean . should_be_false + True.is_a Boolean . should_be_true + False.is_a Boolean . should_be_true + True.is_a Integer . should_be_false + + "".is_a Text . should_be_true + "".is_a Decimal . should_be_false + + 1.is_a Array . should_be_false + 1.is_a Integer . should_be_true + 1.is_a Number . should_be_true + 1.is_a Decimal . should_be_false + 1.is_a Date . should_be_false + + 1.0.is_a Number . should_be_true + 1.0.is_a Decimal . should_be_true + 1.0.is_a Integer . should_be_false + 1.0.is_a Text . should_be_false + + random_gen = Random.new + Meta.is_a random_gen Random . should_be_true + Meta.is_a random_gen Integer . should_be_false + + (My_Type.Value 1 "foo" Nothing).is_a My_Type.Value . should_be_true + (My_Type.Value 1 "foo" Nothing).is_a Test_Type.Value . should_be_false + (My_Type.Value 1 "foo" Nothing).is_a Number . should_be_false + + err = Error.throw "Error Value" + 1.is_a Error . should_be_false + err.is_a Error . should_be_true + err.is_a Text . should_be_false + Meta.is_a err Error . should_be_true + Meta.is_a err Text . should_be_false + + Meta.is_a Date.now Date . should_be_true + Meta.is_a Date_Time.now Date_Time . should_be_true + Meta.is_a Date_Time.now Date . should_be_false + Meta.is_a Time_Of_Day.now Time_Of_Day . should_be_true + Meta.is_a Time_Of_Day.now Date . should_be_false + Meta.is_a Date_Time.now.zone Time_Zone . should_be_true + Meta.is_a Date_Time.now.zone Date . should_be_false + + Test.specify "should allow for returning the type of a value" <| + n_1 = Meta.type_of 42 + n_1 . should_equal_type Integer + n_1 . should_not_equal_type Decimal + + n_2 = Meta.type_of 2.81 + n_2 . should_equal_type Decimal + n_2 . should_not_equal_type Integer + + n_3 = Meta.type_of (JLong.MAX_VALUE * 2) + n_3 . should_equal_type Integer + n_3 . should_not_equal_type Decimal + + v_tpe = Meta.type_of [1,2,3] + v_tpe . should_equal_type Vector + v_tpe . should_not_equal_type Array + Meta.type_of [1,2,3].to_array . should_equal_type Array + + Meta.type_of "foo" . should_equal_type Text + Meta.type_of "0" . should_not_equal_type Integer + + Meta.type_of True . should_equal_type Boolean + Meta.type_of False . should_not_equal_type Any + + (Meta.type_of Date.now) . should_equal_type Date + (Meta.type_of Date.now) . should_not_equal_type Date_Time + (Meta.type_of Date_Time.now) . should_equal_type Date_Time + (Meta.type_of Date_Time.now) . should_not_equal_type Date + (Meta.type_of Time_Of_Day.now) . should_equal_type Time_Of_Day + (Meta.type_of Time_Of_Day.now) . should_not_equal_type Date + (Meta.type_of Date_Time.now.zone) . should_equal_type Time_Zone + (Meta.type_of Date_Time.now.zone) . should_not_equal_type Date + (Meta.type_of Time_Zone.local) . should_equal_type Time_Zone + (Meta.type_of Time_Zone.system) . should_equal_type Time_Zone + + list = ArrayList.new + list.add 123 + list_tpe = Meta.type_of list + list_tpe . should_not_equal_type JObject + list_tpe . should_equal_type ArrayList + + e = IOException.new "meh" + e_tpe = Meta.type_of e + e_tpe . should_equal_type IOException + e_tpe . should_not_equal_type JException + + Test.specify "should correctly handle Java values" <| + java_meta = Meta.meta Random.new + java_meta . should_be_a Meta.Polyglot.Value + java_meta . get_language . should_equal Meta.Language.Java + + Test.specify "should correctly handle equality of Java values" <| + a = JavaLocale.new "en" + b = JavaLocale.new "en" + c = JavaLocale.new "pl" + + a==a . should_be_true + a==b . should_be_true + a==c . should_be_false + + (Test_Type.Value a)==(Test_Type.Value a) . should_be_true + (Test_Type.Value a)==(Test_Type.Value b) . should_be_true + (Test_Type.Value a)==(Test_Type.Value c) . should_be_false + + Test.group "Atom with holes" <| Test.specify "construct and fill" <| - vec = Meta.lazy_atom (e -> My_Type.Value 1 e 3) - case vec of - v : Vector -> v.length . should_equal 2 - _ -> Test.fail "Should return an array" + pair = Meta.atom_with_hole (e -> My_Type.Value 1 e 3) + case pair of + _ : Pair -> Nothing + _ -> Test.fail "Should return a Pair" - atom = vec.at 0 - fill = vec.at 1 + atom = pair.first + fill = pair.second Meta.is_atom atom . should_be_true @@ -195,9 +196,9 @@ spec = Test.group "Meta-Value Manipulation" <| fill 10 # no change atom.bar . should_equal 2 - Test.specify "fail if lazy_atom isn't used" <| + Test.specify "fail if atom_with_hole isn't used" <| key = Panic.catch Uninitialized_State.Error handler=(caught_panic-> caught_panic.payload.key) <| - Meta.lazy_atom (_ -> My_Type.Value 1 2 3) + Meta.atom_with_hole (_ -> My_Type.Value 1 2 3) case key of t : My_Type -> t.foo . should_equal 1 @@ -206,19 +207,15 @@ spec = Test.group "Meta-Value Manipulation" <| Test.specify "fail if non-atom is created" <| key = Panic.catch Uninitialized_State.Error handler=(caught_panic-> caught_panic.payload.key) <| - Meta.lazy_atom (_ -> 2) + Meta.atom_with_hole (_ -> 2) case key of t : Number -> t . should_equal 2 - Test.specify "only one lazy_atom is used" <| - vec = Meta.lazy_atom (e -> My_Type.Value e e e) - case vec of - v : Vector -> v.length . should_equal 2 - _ -> Test.fail "Should return an array" - - atom = vec.at 0 - fill = vec.at 1 + Test.specify "only one atom_with_hole is used" <| + pair = Meta.atom_with_hole (e -> My_Type.Value e e e) + atom = pair.first + fill = pair.second Meta.is_atom atom . should_be_true From dc9e60f190afdc0dfa8ebc14841a7adcf445dc7c Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 6 Jan 2023 09:07:28 +0100 Subject: [PATCH 15/24] Formatting --- .../interpreter/bench/benchmarks/semantic/VectorBenchmarks.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/VectorBenchmarks.java b/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/VectorBenchmarks.java index 7597439b02a8..2213245e1857 100644 --- a/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/VectorBenchmarks.java +++ b/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/VectorBenchmarks.java @@ -1,4 +1,4 @@ - package org.enso.interpreter.bench.benchmarks.semantic; +package org.enso.interpreter.bench.benchmarks.semantic; import java.io.ByteArrayOutputStream; import java.nio.file.Paths; From 11d71437313154df4366ce4dd9cfa7b7dcf60355 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 6 Jan 2023 09:07:59 +0100 Subject: [PATCH 16/24] Adding also List.to_text test --- .../java/org/enso/interpreter/test/ListTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/ListTest.java b/engine/runtime/src/test/java/org/enso/interpreter/test/ListTest.java index ed62d342facf..205bc6c1d706 100644 --- a/engine/runtime/src/test/java/org/enso/interpreter/test/ListTest.java +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/ListTest.java @@ -27,6 +27,7 @@ public class ListTest { private Value taken; private Value init; private Value asVector; + private Value asText; @Before public void prepareCtx() throws Exception { @@ -56,6 +57,8 @@ public void prepareCtx() throws Exception { as_vector list = list.to_vector + as_text list = list.to_text + generator n = go x v l = if x > n then l else @Tail_Call go x+1 v+1 (Cons v l) @@ -68,6 +71,7 @@ public void prepareCtx() throws Exception { taken = evalCode(code, "taken"); init = evalCode(code, "init"); asVector = evalCode(code, "as_vector"); + asText = evalCode(code, "as_text"); } @Test @@ -134,6 +138,14 @@ public void toVector() throws Exception { assertEquals("The same size remains", size, vec.getArraySize()); } + @Test + public void toText() throws Exception { + var list = generator.execute(size); + assertLength("Generated all", size, list); + var str = asText.execute(list); + assertTrue("It is a string", str.isString()); + } + private Value evalCode(final String code, final String methodName) throws URISyntaxException { final var testName = "test.enso"; final URI testUri = new URI("memory://" + testName); From 695775b250e0c92bfe76dfa116fc78725bb388fa Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 6 Jan 2023 09:56:54 +0100 Subject: [PATCH 17/24] Benchmark to measure performance of List.map --- .../benchmarks/semantic/ListBenchmarks.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/ListBenchmarks.java diff --git a/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/ListBenchmarks.java b/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/ListBenchmarks.java new file mode 100644 index 000000000000..0ac40ed24f1f --- /dev/null +++ b/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/ListBenchmarks.java @@ -0,0 +1,107 @@ +package org.enso.interpreter.bench.benchmarks.semantic; + +import java.io.ByteArrayOutputStream; +import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.BenchmarkParams; +import org.openjdk.jmh.infra.Blackhole; + + +@BenchmarkMode(Mode.AverageTime) +@Fork(1) +@Warmup(iterations = 3) +@Measurement(iterations = 5) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +public class ListBenchmarks { + private final int LENGTH_OF_EXPERIMENT = 1_000_000; + private Value list; + private Value plusOne; + private Value self; + private Value sum; + private Value oldSum; + + @Setup + public void initializeBenchmark(BenchmarkParams params) throws Exception { + var ctx = Context.newBuilder() + .allowExperimentalOptions(true) + .allowIO(true) + .allowAllAccess(true) + .logHandler(new ByteArrayOutputStream()) + .option( + "enso.languageHomeOverride", + Paths.get("../../distribution/component").toFile().getAbsolutePath() + ).build(); + + var benchmarkName = params.getBenchmark().replaceFirst(".*\\.", ""); + var code = """ + from Standard.Base.Data.List.List import Cons, Nil + import Standard.Base.IO + + plus_one list = list.map (x -> x + 1) + + sum list acc = + case list of + Nil -> acc + Cons x xs -> @Tail_Call sum xs acc+x + + generator n = + go x v l = if x > n then l else + @Tail_Call go x+1 v+1 (Cons v l) + go 1 1 Nil + """; + + var module = ctx.eval(SrcUtil.source(benchmarkName, code)); + + this.self = module.invokeMember("get_associated_type"); + Function getMethod = (name) -> module.invokeMember("get_method", self, name); + + Value longList = getMethod.apply("generator").execute(self, LENGTH_OF_EXPERIMENT); + + this.plusOne = getMethod.apply("plus_one"); + this.sum = getMethod.apply("sum"); + + switch (benchmarkName) { + case "mapOverList": { + this.list = longList; + this.oldSum = sum.execute(self, longList, 0); + if (!this.oldSum.fitsInLong()) { + throw new AssertionError("Expecting a number " + this.oldSum); + } + break; + } + default: + throw new IllegalStateException("Unexpected benchmark: " + params.getBenchmark()); + } + } + + @Benchmark + public void mapOverList(Blackhole matter) { + performBenchmark(matter); + } + + private void performBenchmark(Blackhole hole) throws AssertionError { + var newList = plusOne.execute(self, list); + var newSum = sum.execute(self, newList, 0); + + var result = newSum.asLong() - oldSum.asLong(); + if (result != LENGTH_OF_EXPERIMENT) { + throw new AssertionError("Unexpected result " + result); + } + hole.consume(result); + } +} + From b383acebe92a9ab7f2476ccc45ed9af64c134f0d Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 6 Jan 2023 11:02:30 +0100 Subject: [PATCH 18/24] Using InvokeCallableNode instead of InteropLibrary --- .../builtin/meta/AtomWithAHoleNode.java | 48 ++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java index a0ff5628e179..c20c1c2f6288 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java @@ -15,16 +15,14 @@ import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; -import com.oracle.truffle.api.interop.ArityException; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.TruffleObject; -import com.oracle.truffle.api.interop.UnsupportedMessageException; -import com.oracle.truffle.api.interop.UnsupportedTypeException; -import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; +import org.enso.interpreter.node.callable.InvokeCallableNode; +import org.enso.interpreter.runtime.state.State; @BuiltinMethod( type = "Meta", @@ -37,38 +35,34 @@ static AtomWithAHoleNode build() { return AtomWithAHoleNodeGen.create(); } - abstract Vector execute(Object factory); - @Specialization(guards = "iop.isExecutable(factory)") + abstract Vector execute(VirtualFrame frame, Object factory); + + static InvokeCallableNode callWithHole() { + return InvokeCallableNode.build( + new CallArgumentInfo[] {new CallArgumentInfo()}, + InvokeCallableNode.DefaultsExecutionMode.EXECUTE, + InvokeCallableNode.ArgumentsExecutionMode.PRE_EXECUTED); + } + + @Specialization Vector doExecute( + VirtualFrame frame, Object factory, - @CachedLibrary(limit="3") InteropLibrary iop, + @Cached("callWithHole()") InvokeCallableNode iop, @Cached SwapAtomFieldNode swapNode ) { var ctx = EnsoContext.get(this); var lazy = new HoleInAtom(); - try { - var r = iop.execute(factory, lazy); - if (r instanceof Atom a) { - var i = swapNode.findHoleIndex(a, lazy); - if (i >= 0) { - var function = swapNode.createFn(a, i, lazy); - return Vector.fromArray(new Array(a, function)); - } + var r = iop.execute(factory, frame, State.create(ctx), new Object[] { lazy }); + if (r instanceof Atom a) { + var i = swapNode.findHoleIndex(a, lazy); + if (i >= 0) { + var function = swapNode.createFn(a, i, lazy); + return Vector.fromArray(new Array(a, function)); } - throw new PanicException(ctx.getBuiltins().error().makeUninitializedStateError(r), this); - } catch (UnsupportedTypeException ex) { - throw raise(RuntimeException.class, ex); - } catch (ArityException ex) { - throw raise(RuntimeException.class, ex); - } catch (UnsupportedMessageException ex) { - throw raise(RuntimeException.class, ex); } - } - - @SuppressWarnings("unchecked") - private static E raise(Class type, Throwable t) throws E { - throw (E)t; + throw new PanicException(ctx.getBuiltins().error().makeUninitializedStateError(r), this); } @ExportLibrary(InteropLibrary.class) From 72255fa049e9525a1c13dfbedccba885234dd76d Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 6 Jan 2023 11:17:38 +0100 Subject: [PATCH 19/24] Note about Meta.atom_with_hole in the changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b030ccb5f12d..f016a33d8e1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -497,6 +497,7 @@ - [Simplify compilation of nested patterns][4005] - [IGV can jump to JMH sources & more][4008] - [Sync language server with file system after VCS restore][4020] +- [Introducing Meta.atom_with_hole][4023] [3227]: https://github.com/enso-org/enso/pull/3227 [3248]: https://github.com/enso-org/enso/pull/3248 @@ -577,6 +578,7 @@ [4005]: https://github.com/enso-org/enso/pull/4005 [4008]: https://github.com/enso-org/enso/pull/4008 [4020]: https://github.com/enso-org/enso/pull/4020 +[4023]: https://github.com/enso-org/enso/pull/4023 # Enso 2.0.0-alpha.18 (2021-10-12) From 9484ae026c85b6975ea1b9610ca067870ea96b10 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 6 Jan 2023 14:35:20 +0100 Subject: [PATCH 20/24] Avoid allocating Vector at all. Expose object with value and fill properties. --- .../Base/0.0.0-dev/src/Data/List.enso | 8 +- .../lib/Standard/Base/0.0.0-dev/src/Meta.enso | 13 +-- .../builtin/meta/AtomWithAHoleNode.java | 92 +++++++++++++------ test/Tests/src/Semantic/Meta_Spec.enso | 11 +-- 4 files changed, 78 insertions(+), 46 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso index 6130f14b4f70..6069d72cdda8 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso @@ -215,12 +215,12 @@ type List Cons h t -> v = f h res = Meta.atom_with_hole (e -> Cons v e) - fill res.first - @Tail_Call go t res.second + fill res.value + @Tail_Call go t res.fill v = f h res = Meta.atom_with_hole (e -> Cons v e) - go t res.second - res.first + go t res.fill + res.value ## Applies a function to each element of the list. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso index d84d24b3d445..16d621741a9b 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso @@ -276,17 +276,14 @@ new_atom constructor fields = @Builtin_Method "Meta.new_atom" ## PRIVATE - Constructs a new atom with a "hole". Returns a `Pair` of the newly created - atom and a function to "fill the hole" later. + Constructs a new atom with a "hole". Returns an object with `value` and + `fill` properties. Value contains the created atom and `fill` holds a + function to "fill the hole" later. Arguments: - factory: a function that takes the "hole" element and returns newly created atom -atom_with_hole : (Any -> Atom) -> Pair Atom (Any -> Nothing) -atom_with_hole factory = - vec = atom_with_hole_builtin factory - Pair.new (vec.at 0) (vec.at 1) - -atom_with_hole_builtin factory = @Builtin_Method "Meta.atom_with_hole_builtin" +atom_with_hole : (Any -> Atom) -> Any +atom_with_hole factory = @Builtin_Method "Meta.atom_with_hole_builtin" ## UNSTABLE ADVANCED diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java index c20c1c2f6288..82409941cde8 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java @@ -17,10 +17,13 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.interop.UnknownIdentifierException; +import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; +import com.oracle.truffle.api.profiles.ValueProfile; import org.enso.interpreter.node.callable.InvokeCallableNode; import org.enso.interpreter.runtime.state.State; @@ -35,18 +38,17 @@ static AtomWithAHoleNode build() { return AtomWithAHoleNodeGen.create(); } - - abstract Vector execute(VirtualFrame frame, Object factory); + abstract Object execute(VirtualFrame frame, Object factory); static InvokeCallableNode callWithHole() { - return InvokeCallableNode.build( - new CallArgumentInfo[] {new CallArgumentInfo()}, - InvokeCallableNode.DefaultsExecutionMode.EXECUTE, - InvokeCallableNode.ArgumentsExecutionMode.PRE_EXECUTED); + return InvokeCallableNode.build( + new CallArgumentInfo[] {new CallArgumentInfo()}, + InvokeCallableNode.DefaultsExecutionMode.EXECUTE, + InvokeCallableNode.ArgumentsExecutionMode.PRE_EXECUTED); } @Specialization - Vector doExecute( + Object doExecute( VirtualFrame frame, Object factory, @Cached("callWithHole()") InvokeCallableNode iop, @@ -54,22 +56,59 @@ Vector doExecute( ) { var ctx = EnsoContext.get(this); var lazy = new HoleInAtom(); - var r = iop.execute(factory, frame, State.create(ctx), new Object[] { lazy }); - if (r instanceof Atom a) { - var i = swapNode.findHoleIndex(a, lazy); - if (i >= 0) { - var function = swapNode.createFn(a, i, lazy); - return Vector.fromArray(new Array(a, function)); + var result = iop.execute(factory, frame, State.create(ctx), new Object[] { lazy }); + if (result instanceof Atom atom) { + var index = swapNode.findHoleIndex(atom, lazy); + if (index >= 0) { + var function = swapNode.createFn(lazy); + lazy.init(atom, index, function); + return lazy; } } - throw new PanicException(ctx.getBuiltins().error().makeUninitializedStateError(r), this); + throw new PanicException(ctx.getBuiltins().error().makeUninitializedStateError(result), this); } @ExportLibrary(InteropLibrary.class) static final class HoleInAtom implements TruffleObject { + Atom result; + int index; + Function function; + HoleInAtom() { } + void init(Atom result, int index, Function function) { + this.result = result; + this.index = index; + this.function = function; + } + + @ExportMessage boolean hasMembers() { + return true; + } + + @ExportMessage boolean isMemberReadable(String member) { + return switch (member) { + case "value", "fill" -> true; + default -> false; + }; + } + + @ExportMessage Object getMembers(boolean includeInternal) throws UnsupportedMessageException { + return new Array("value", "fill"); + } + + @ExportMessage + Object readMember(String name) throws UnknownIdentifierException { + if ("value".equals(name)) { + return result; + } + if ("fill".equals(name)) { + return function; + } + throw UnknownIdentifierException.create(name); + } + @ExportMessage String toDisplayString(boolean pure) { return "Meta.atom_with_hole"; @@ -77,18 +116,17 @@ String toDisplayString(boolean pure) { } static final class SwapAtomFieldNode extends RootNode { private final FunctionSchema schema; + private final ValueProfile sameAtom = ValueProfile.createClassProfile(); @CompilerDirectives.CompilationFinal private int lastIndex = -1; private SwapAtomFieldNode() { super(null); this.schema = new FunctionSchema(FunctionSchema.CallerFrameAccess.NONE, new ArgumentDefinition[]{ - new ArgumentDefinition(0, "instance", ArgumentDefinition.ExecutionMode.EXECUTE), - new ArgumentDefinition(1, "lazy_index", ArgumentDefinition.ExecutionMode.EXECUTE), - new ArgumentDefinition(2, "lazy", ArgumentDefinition.ExecutionMode.EXECUTE), - new ArgumentDefinition(3, "value", ArgumentDefinition.ExecutionMode.EXECUTE) + new ArgumentDefinition(0, "lazy", ArgumentDefinition.ExecutionMode.EXECUTE), + new ArgumentDefinition(1, "value", ArgumentDefinition.ExecutionMode.EXECUTE) }, new boolean[]{ - true, true, true, false + true, false }, new CallArgumentInfo[0]); } @@ -130,8 +168,8 @@ private int findHoleIndexLoop(Object[] arr, HoleInAtom lazy) { return -1; } - Function createFn(Atom atom, int index, HoleInAtom lazy) { - var preArgs = new Object[]{atom, index, lazy, null}; + Function createFn(HoleInAtom lazy) { + var preArgs = new Object[]{lazy, null}; return new Function( getCallTarget(), null, @@ -144,13 +182,13 @@ Function createFn(Atom atom, int index, HoleInAtom lazy) { @Override public Object execute(VirtualFrame frame) { var args = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments()); - if (args[0] instanceof Atom replace) { - if (args[1] instanceof Integer n) { - var fields = replace.getFields(); - if (fields[n.intValue()] == args[2]) { - fields[n.intValue()] = args[3]; - } + if (args[0] instanceof HoleInAtom lazy) { + var fields = lazy.result.getFields(); + var newValue = args[1]; + if (fields[lazy.index] == lazy) { + fields[lazy.index] = newValue; } + return newValue; } return EnsoContext.get(this).getBuiltins().nothing(); } diff --git a/test/Tests/src/Semantic/Meta_Spec.enso b/test/Tests/src/Semantic/Meta_Spec.enso index dd998f5e47ee..eb512bab7f77 100644 --- a/test/Tests/src/Semantic/Meta_Spec.enso +++ b/test/Tests/src/Semantic/Meta_Spec.enso @@ -175,12 +175,9 @@ spec = Test.group "Atom with holes" <| Test.specify "construct and fill" <| pair = Meta.atom_with_hole (e -> My_Type.Value 1 e 3) - case pair of - _ : Pair -> Nothing - _ -> Test.fail "Should return a Pair" - atom = pair.first - fill = pair.second + atom = pair.value + fill = pair.fill Meta.is_atom atom . should_be_true @@ -214,8 +211,8 @@ spec = Test.specify "only one atom_with_hole is used" <| pair = Meta.atom_with_hole (e -> My_Type.Value e e e) - atom = pair.first - fill = pair.second + atom = pair.value + fill = pair.fill Meta.is_atom atom . should_be_true From 8ccbecbadd8eedcc0d226a1be4a32bc43c88bcc4 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 6 Jan 2023 15:38:49 +0100 Subject: [PATCH 21/24] Make sure all List operation handle "infinite" lists --- .../Base/0.0.0-dev/src/Data/List.enso | 55 +++++++++++++++---- .../org/enso/interpreter/test/ListTest.java | 2 +- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso index 6069d72cdda8..e92c491f7640 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso @@ -185,13 +185,19 @@ type List filter : (Filter_Condition | (Any -> Boolean)) -> Vector Any filter self filter = case filter of _ : Filter_Condition -> self.filter filter.to_predicate - predicate : Function -> - go_filter list = case list of - Nil -> Nil - Cons h t -> - rest = go_filter t - if predicate h then Cons h rest else rest - go_filter self + predicate : Function -> case self of + Nil -> Nil + Cons x xs -> if predicate x . not then @Tail_Call xs.filter filter else + go list fill = case list of + Nil -> fill Nil + Cons h t -> if predicate h . not then @Tail_Call go t fill else + res = Meta.atom_with_hole (e -> Cons h e) + fill res.value + @Tail_Call go t res.fill + + res = Meta.atom_with_hole (e -> Cons x e) + go xs res.fill + res.value ## Applies a function to each element of the list, returning the list of results. @@ -290,7 +296,16 @@ type List take_start : Integer -> List take_start self count = if count <= 0 then Nil else case self of Nil -> Nil - Cons a b -> Cons a (b.take_start count-1) + Cons a b -> + go c l fill = if c <= 0 then fill Nil else case l of + Nil -> fill Nil + Cons a b -> + res = Meta.atom_with_hole (e -> Cons a e) + fill res.value + @Tail_Call go c-1 b res.fill + res = Meta.atom_with_hole (e -> Cons a e) + go count-1 b res.fill + res.value ## Get the first element from the list. @@ -328,12 +343,21 @@ type List example_init = Examples.list.init init : List ! Empty_Error init self = - init_fn x y = case y of - Nil -> Nil - Cons a b -> Cons x (init_fn a b) case self of Nil -> Error.throw Empty_Error - Cons a b -> init_fn a b + Cons a Nil -> Nil + Cons a b -> + go l fill = case l of + Cons x xs -> case xs of + Nil -> fill Nil + Cons y z -> + res = Meta.atom_with_hole (e -> Cons y e) + fill res.value + @Tail_Call go xs res.fill + + res = Meta.atom_with_hole (e -> Cons a e) + go b res.fill + res.value ## Get the last element of the list. @@ -378,6 +402,13 @@ type List builder.append elem builder.to_vector + to_text : Text + to_text self = + go l t = case l of + Nil -> t + "Nil" + Cons x xs -> @Tail_Call go xs ("Cons " + x.to_text + " ") + go self "" + ## UNSTABLE An error representing that the list is empty. diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/ListTest.java b/engine/runtime/src/test/java/org/enso/interpreter/test/ListTest.java index 205bc6c1d706..bce689576a5f 100644 --- a/engine/runtime/src/test/java/org/enso/interpreter/test/ListTest.java +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/ListTest.java @@ -20,7 +20,7 @@ public class ListTest { private Context ctx; - private final int size = 10; + private final int size = 100_000; private Value generator; private Value plusOne; private Value evenOnes; From bfe53389a415818c448bd25e3cfb2e8c87b79427 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 6 Jan 2023 17:23:52 +0100 Subject: [PATCH 22/24] Pair import is no longer needed --- distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso | 1 - 1 file changed, 1 deletion(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso index 16d621741a9b..4a6652e27d68 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso @@ -3,7 +3,6 @@ import project.Data.Array.Array import project.Data.Numbers.Decimal import project.Data.Numbers.Integer import project.Data.Numbers.Number -import project.Data.Pair.Pair import project.Data.Text.Text import project.Data.Time.Date_Time.Date_Time import project.Data.Time.Date.Date From a0906d0db5ac2805bbd9842e74fce6c8483588ed Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 6 Jan 2023 18:25:16 +0100 Subject: [PATCH 23/24] Make sure List.init works for one, two and three elements --- .../lib/Standard/Base/0.0.0-dev/src/Data/List.enso | 8 ++++---- test/Tests/src/Data/List_Spec.enso | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso index e92c491f7640..e94c75644d60 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso @@ -345,13 +345,13 @@ type List init self = case self of Nil -> Error.throw Empty_Error - Cons a Nil -> Nil + Cons _ Nil -> Nil Cons a b -> go l fill = case l of Cons x xs -> case xs of Nil -> fill Nil - Cons y z -> - res = Meta.atom_with_hole (e -> Cons y e) + Cons _ _ -> + res = Meta.atom_with_hole (e -> Cons x e) fill res.value @Tail_Call go xs res.fill @@ -406,7 +406,7 @@ type List to_text self = go l t = case l of Nil -> t + "Nil" - Cons x xs -> @Tail_Call go xs ("Cons " + x.to_text + " ") + Cons x xs -> @Tail_Call go xs (t + "Cons " + x.to_text + " ") go self "" ## UNSTABLE diff --git a/test/Tests/src/Data/List_Spec.enso b/test/Tests/src/Data/List_Spec.enso index 5e129aef979e..00e825394c93 100644 --- a/test/Tests/src/Data/List_Spec.enso +++ b/test/Tests/src/Data/List_Spec.enso @@ -115,6 +115,10 @@ spec = Test.group "List" <| Test.specify "should allow getting the tail of the list with `.tail`" <| l.tail . should_equal (List.Cons 2 (List.Cons 3 List.Nil)) empty.tail.should_fail_with Empty_Error + Test.specify "single element list.init yields Nil" <| + (List.Cons 1 List.Nil).init . should_equal List.Nil + Test.specify "two element list.init yields one element" <| + (List.Cons 1 (List.Cons 2 List.Nil)).init . should_equal (List.Cons 1 List.Nil) Test.specify "should allow getting the init of the list with `.init`" <| l.init . should_equal (List.Cons 1 (List.Cons 2 List.Nil)) empty.init.should_fail_with Empty_Error From a8cd2bf6bed3a60cc9d94863663558943e72e0eb Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 6 Jan 2023 18:28:04 +0100 Subject: [PATCH 24/24] Using (Cons x _) per James suggestion --- .../Standard/Base/0.0.0-dev/src/Data/List.enso | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso index e94c75644d60..237ae78c51ff 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso @@ -191,11 +191,11 @@ type List go list fill = case list of Nil -> fill Nil Cons h t -> if predicate h . not then @Tail_Call go t fill else - res = Meta.atom_with_hole (e -> Cons h e) + res = Meta.atom_with_hole (Cons h _) fill res.value @Tail_Call go t res.fill - res = Meta.atom_with_hole (e -> Cons x e) + res = Meta.atom_with_hole (Cons x _) go xs res.fill res.value @@ -220,11 +220,11 @@ type List Nil -> fill Nil Cons h t -> v = f h - res = Meta.atom_with_hole (e -> Cons v e) + res = Meta.atom_with_hole (Cons v _) fill res.value @Tail_Call go t res.fill v = f h - res = Meta.atom_with_hole (e -> Cons v e) + res = Meta.atom_with_hole (Cons v _) go t res.fill res.value @@ -300,10 +300,10 @@ type List go c l fill = if c <= 0 then fill Nil else case l of Nil -> fill Nil Cons a b -> - res = Meta.atom_with_hole (e -> Cons a e) + res = Meta.atom_with_hole (Cons a _) fill res.value @Tail_Call go c-1 b res.fill - res = Meta.atom_with_hole (e -> Cons a e) + res = Meta.atom_with_hole (Cons a _) go count-1 b res.fill res.value @@ -351,11 +351,11 @@ type List Cons x xs -> case xs of Nil -> fill Nil Cons _ _ -> - res = Meta.atom_with_hole (e -> Cons x e) + res = Meta.atom_with_hole (Cons x _) fill res.value @Tail_Call go xs res.fill - res = Meta.atom_with_hole (e -> Cons a e) + res = Meta.atom_with_hole (Cons a _) go b res.fill res.value