diff --git a/distribution/bin/enso b/distribution/bin/enso index fe7f14afc3ea..2c34c242061b 100755 --- a/distribution/bin/enso +++ b/distribution/bin/enso @@ -1,3 +1,3 @@ COMP_PATH=$(dirname "$0")/../component -exec java -jar -Dtruffle.class.path.append="$COMP_PATH/runtime.jar" -Dpolyglot.engine.IterativePartialEscape=true $JAVA_OPTS $COMP_PATH/runner.jar "$@" +exec java -jar -Dtruffle.class.path.append="$COMP_PATH/runtime.jar" $JAVA_OPTS $COMP_PATH/runner.jar "$@" exit diff --git a/distribution/std-lib/Base/src/Data/Map.enso b/distribution/std-lib/Base/src/Data/Map.enso new file mode 100644 index 000000000000..d9b76f1b438d --- /dev/null +++ b/distribution/std-lib/Base/src/Data/Map.enso @@ -0,0 +1,79 @@ +from Base import all +import Base.Data.Map.Internal + +## An error for getting a missing value from a map. +type No_Value_For_Key key + +## A key-value store. This type assumes all keys are pairwise comparable, + using the `<`, `>` and `==` operators. +type Map + type Tip + type Bin s key value left right + + ## Checks if the map is empty. + is_empty : Boolean + is_empty = case this of + Bin _ _ _ _ _ -> False + Tip -> True + + ## Returns the number of entries in this map. + size : Integer + size = case this of + Bin s _ _ _ _ -> s + Tip -> 0 + + ## Converts the map into a vector of `[key, value]` pairs. + The returned vector is sorted in the increasing order of keys. + to_vector : Vector + to_vector = + builder = Vector.new_builder + to_vector_with_builder m = case m of + Bin _ k v l r -> + to_vector_with_builder l + builder.append [k, v] + to_vector_with_builder r + Unit + Tip -> Unit + to_vector_with_builder this + result = builder.to_vector + result + + ## Returns a text representation of this map. + to_text : Text + to_text = this.to_vector.to_text + + ## Checks if this map is equal to another map. + + Maps are equal when they contained the same keys and the values + associated with each key are pairwise equal. + == : Map -> Boolean + == that = this.to_vector == that.to_vector + + ## Maps a function over each value in this map. + map : (Any -> Any) -> Map + map function = case this of + Bin s k v l r -> + Bin s k (function v) (l.map function) (r.map function) + Tip -> Tip + + ## Gets the value associated with `key` in this map, or returns a + `No_Value_For_Key` error, if `key` is not present. + get : Any -> Any ! No_Value_For_Key + get key = + go map = case map of + Tip -> Error.throw (No_Value_For_Key key) + Bin _ k v l r -> + if k == key then v else + if k > key then @Tail_Call go l else @Tail_Call go r + result = go this + result + + ## Inserts a key-value mapping into this map. If `key` is already present, + it will be overriden with the new `value`. + insert : Any -> Any -> Map + insert key value = Internal.insert this key value + +## Returns an empty map. +empty : Map +empty = Tip + diff --git a/distribution/std-lib/Base/src/Data/Map/Internal.enso b/distribution/std-lib/Base/src/Data/Map/Internal.enso new file mode 100644 index 000000000000..141f8f20ae1e --- /dev/null +++ b/distribution/std-lib/Base/src/Data/Map/Internal.enso @@ -0,0 +1,113 @@ +from Base import all +from Base.Data.Map import all + +## PRIVATE + + Helper used in the insert operation. +insert_l key value k v l r = + new_left = here.insert l key value + here.balance_left k v new_left r + +## PRIVATE + + Helper used in the insert operation. +insert_r key value k v l r = + new_right = here.insert r key value + here.balance_right k v l new_right + +## PRIVATE + + Helper for inserting a new key-value pair into a map. + + The algorithm used here is based on the paper "Implementing Sets Efficiently + in a Functional Language" by Stephen Adams. + Implementation is based on Haskell's `Data.Map.Strict` implemented in the + `containers` package. +insert map key value = case map of + Bin s k v l r -> + if key > k then @Tail_Call here.insert_r key value k v l r else + if key == k then @Tail_Call Bin s k value l r else + @Tail_Call here.insert_l key value k v l r + _ -> Bin 1 key value Tip Tip + +## PRIVATE + + Rebalances the map after the left subtree grows. +balance_left k x l r = case r of + Bin rs _ _ _ _ -> case l of + Bin ls lk lx ll lr -> + if ls <= Delta*rs then Bin 1+ls+rs k x l r else + lls = here.size ll + case lr of + Bin lrs lrk lrx lrl lrr -> + if lrs < Ratio*lls then Bin 1+ls+rs lk lx ll (Bin 1+rs+lrs k x lr r) else + lrls = here.size lrl + lrrs = here.size lrr + Bin 1+ls+rs lrk lrx (Bin 1+lls+lrls lk lx ll lrl) (Bin 1+rs+lrrs k x lrr r) + _ -> Bin 1+rs k x Tip r + _ -> case l of + Tip -> Bin 1 k x Tip Tip + Bin _ _ _ Tip Tip -> Bin 2 k x l Tip + Bin _ lk lx Tip (Bin _ lrk lrx _ _) -> Bin 3 lrk lrx (Bin 1 lk lx Tip Tip) (Bin 1 k x Tip Tip) + Bin _ lk lx ll Tip -> Bin 3 lk lx ll (Bin 1 k x Tip Tip) + Bin ls lk lx ll lr -> case lr of + Bin lrs lrk lrx lrl lrr -> + lls = here.size ll + if lrs < Ratio*lls then Bin 1+ls lk lx ll (Bin 1+lrs k x lr Tip) else + lrls = here.size lrl + lrrs = here.size lrr + Bin 1+ls lrk lrx (Bin 1+lls+lrls lk lx ll lrl) (Bin 1+lrrs k x lrr Tip) + +## PRIVATE + + Rebalances the map after the right subtree grows. +balance_right k x l r = case l of + Bin ls _ _ _ _ -> case r of + Bin rs rk rx rl rr -> + if rs <= Delta*ls then Bin 1+ls+rs k x l r else + case rl of + Bin rls rlk rlx rll rlr -> + rrs = here.size rr + if rls < Ratio*rrs then Bin 1+ls+rs rk rx (Bin 1+ls+rls k x l rl) rr else + rlls = here.size rll + rlrs = here.size rlr + Bin 1+ls+rs rlk rlx (Bin 1+ls+rlls k x l rll) (Bin 1+rrs+rlrs rk rx rlr rr) + _ -> Bin 1+ls k x l Tip + _ -> case r of + Tip -> Bin 1 k x Tip Tip + Bin _ _ _ Tip Tip -> Bin 2 k x Tip r + Bin _ rk rx Tip rr -> Bin 3 rk rx (Bin 1 k x Tip Tip) rr + Bin _ rk rx (Bin _ rlk rlx _ _) Tip -> Bin 3 rlk rlx (Bin 1 k x Tip Tip) (Bin 1 rk rx Tip Tip) + Bin rs rk rx (Bin rls rlk rlx rll rlr) rr -> case rr of + Bin rrs _ _ _ _ -> + if rls < Ratio*rrs then Bin 1+rs rk rx (Bin 1+rls k x Tip rl) rr else + srll = here.size rll + srlr = here.size rlr + Bin 1+rs rlk rlx (Bin 1+srll k x Tip rll) (Bin 1+rrs+srlr rk rx rlr rr) + +## PRIVATE + + Controls the difference between inner and outer siblings of a heavy subtree. + Used to decide between a double and a single rotation. + + The choice of values for `ratio` and `delta` is taken from the Haskell + implementation. +ratio : Integer +ratio = 2 + +## PRIVATE + + Controls the maximum size difference between subtrees. + + The choice of values for `ratio` and `delta` is taken from the Haskell + implementation. +delta : Integer +delta = 3 + +## PRIVATE + + Gets the size of a map. +size m = case m of + Bin s _ _ _ _ -> s + _ -> 0 + diff --git a/distribution/std-lib/Base/src/Main.enso b/distribution/std-lib/Base/src/Main.enso index e1998bb3d04e..82c37e1304ad 100644 --- a/distribution/std-lib/Base/src/Main.enso +++ b/distribution/std-lib/Base/src/Main.enso @@ -7,11 +7,13 @@ import Base.Meta.Enso_Project import Base.Meta.Meta import Base.Error.Extensions import Base.Polyglot.Java +import Base.Data.Map from Builtins import Unit, Number, Integer, Any, True, False, Cons export Base.Meta.Meta from Builtins export all hiding Meta +export Base.Data.Map from Base.Meta.Enso_Project export all from Base.List export Nil, Cons from Base.Vector export Vector diff --git a/distribution/std-lib/Base/src/Test.enso b/distribution/std-lib/Base/src/Test.enso index 17c1cc74e0e4..2fa0b59530e1 100644 --- a/distribution/std-lib/Base/src/Test.enso +++ b/distribution/std-lib/Base/src/Test.enso @@ -38,7 +38,7 @@ type Verbs equal subject argument = if subject == argument then Success else - msg = this.to_text + " did not equal " + that.to_text + "." + msg = this.to_text + " did not equal " + argument.to_text + "." here.fail msg be subject argument = this.equal subject argument diff --git a/distribution/std-lib/Base/src/Text/Extensions.enso b/distribution/std-lib/Base/src/Text/Extensions.enso index 8497274a6fc3..481bdca9f1b8 100644 --- a/distribution/std-lib/Base/src/Text/Extensions.enso +++ b/distribution/std-lib/Base/src/Text/Extensions.enso @@ -69,6 +69,14 @@ Text.split_at separator = Text.== : Text -> Boolean Text.== that = Text_Utils.equals [this, that] +## Checks if `this` is lexicographically before `that`. +Text.< : Text -> Boolean +Text.< that = Text_Utils.lt [this, that] + +## Checks if `this` is lexicographically after `that`. +Text.> : Text -> Boolean +Text.> that = Text_Utils.lt [that, this] + ## Returns a vector containing bytes representing the UTF-8 encoding of the input text. @@ -103,4 +111,5 @@ Text.from_codepoints : Vector -> Text Text.from_codepoints codepoints = Text_Utils.from_codepoints [codepoints.to_array] ## Checks whether `this` starts with `prefix`. +Text.starts_with : Text -> Boolean Text.starts_with prefix = Text_Utils.starts_with [this, prefix] diff --git a/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/RecursionBenchmarks.java b/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/RecursionBenchmarks.java index bd0c777f2a3b..c302efa68dbe 100644 --- a/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/RecursionBenchmarks.java +++ b/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/RecursionBenchmarks.java @@ -1,17 +1,10 @@ package org.enso.interpreter.bench.benchmarks.semantic; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.concurrent.TimeUnit; import org.enso.interpreter.bench.fixtures.semantic.RecursionFixtures; import org.enso.interpreter.test.DefaultInterpreterRunner; -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.Warmup; +import org.openjdk.jmh.annotations.*; + +import java.util.concurrent.TimeUnit; @BenchmarkMode(Mode.AverageTime) @Fork(1) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeCallableNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeCallableNode.java index 52477b10966d..2f0793e09478 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeCallableNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeCallableNode.java @@ -119,7 +119,7 @@ public Stateful invokeDynamicSymbol( Object selfArgument = arguments[thisArgumentPosition]; if (argumentsExecutionMode.shouldExecute()) { Stateful selfResult = - thisExecutor.executeThunk((Thunk) selfArgument, state, BaseNode.TailStatus.NOT_TAIL); + thisExecutor.executeThunk(selfArgument, state, BaseNode.TailStatus.NOT_TAIL); selfArgument = selfResult.getValue(); state = selfResult.getState(); arguments[thisArgumentPosition] = selfArgument; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java index cd4e448ea931..e1a6d06b4aae 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java @@ -185,8 +185,7 @@ public Stateful invokeDynamicSymbol( lock.unlock(); } } - Stateful selfResult = - thisExecutor.executeThunk((Thunk) selfArgument, state, TailStatus.NOT_TAIL); + Stateful selfResult = thisExecutor.executeThunk(selfArgument, state, TailStatus.NOT_TAIL); selfArgument = selfResult.getValue(); state = selfResult.getState(); arguments[thisArgumentPosition] = selfArgument; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ArgumentSorterNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ArgumentSorterNode.java index 68bfa762946a..2e21ac896266 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ArgumentSorterNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ArgumentSorterNode.java @@ -55,11 +55,11 @@ public static ArgumentSorterNode build( preApplicationSchema, mapping.getPostApplicationSchema(), mapping, argumentsExecutionMode); } - private void initArgumentExecutors(Object[] arguments) { + private void initArgumentExecutors() { ThunkExecutorNode[] executors = new ThunkExecutorNode[mapping.getArgumentShouldExecute().length]; for (int i = 0; i < mapping.getArgumentShouldExecute().length; i++) { - if (mapping.getArgumentShouldExecute()[i] && TypesGen.isThunk(arguments[i])) { + if (mapping.getArgumentShouldExecute()[i]) { executors[i] = insert(ThunkExecutorNode.build()); } } @@ -74,7 +74,7 @@ private Object executeArguments(Object[] arguments, Object state) { lock.lock(); try { if (executors == null) { - initArgumentExecutors(arguments); + initArgumentExecutors(); } } finally { lock.unlock(); @@ -82,8 +82,7 @@ private Object executeArguments(Object[] arguments, Object state) { } for (int i = 0; i < mapping.getArgumentShouldExecute().length; i++) { if (executors[i] != null) { - Stateful result = - executors[i].executeThunk(TypesGen.asThunk(arguments[i]), state, TailStatus.NOT_TAIL); + Stateful result = executors[i].executeThunk(arguments[i], state, TailStatus.NOT_TAIL); arguments[i] = result.getValue(); state = result.getState(); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/thunk/ForceNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/thunk/ForceNode.java index 5bc32c322a87..90bff0d36301 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/thunk/ForceNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/thunk/ForceNode.java @@ -30,7 +30,7 @@ public static ForceNode build(ExpressionNode target) { @Specialization Object passToExecutorNode( VirtualFrame frame, - Thunk thunk, + Object thunk, @Cached("build()") ThunkExecutorNode thunkExecutorNode) { Object state = FrameUtil.getObjectSafe(frame, getStateFrameSlot()); Stateful result = thunkExecutorNode.executeThunk(thunk, state, getTailStatus()); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/thunk/ThunkExecutorNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/thunk/ThunkExecutorNode.java index fa812a4d8de4..8669e5dd1441 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/thunk/ThunkExecutorNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/thunk/ThunkExecutorNode.java @@ -12,6 +12,7 @@ import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.control.TailCallException; import org.enso.interpreter.runtime.state.Stateful; +import org.enso.interpreter.runtime.type.TypesGen; /** Node responsible for executing (forcing) thunks passed to it as runtime values. */ @GenerateUncached @@ -37,7 +38,16 @@ public static ThunkExecutorNode build() { * @param isTail is the execution happening in a tail-call position * @return the return value of this thunk */ - public abstract Stateful executeThunk(Thunk thunk, Object state, BaseNode.TailStatus isTail); + public abstract Stateful executeThunk(Object thunk, Object state, BaseNode.TailStatus isTail); + + static boolean isThunk(Object th) { + return TypesGen.isThunk(th); + } + + @Specialization(guards = "!isThunk(thunk)") + Stateful doOther(Object thunk, Object state, BaseNode.TailStatus isTail) { + return new Stateful(state, thunk); + } @Specialization( guards = "callNode.getCallTarget() == thunk.getCallTarget()", diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/BooleanBranchNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/BooleanBranchNode.java index 409421c21e27..4dcb6388c30d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/BooleanBranchNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/BooleanBranchNode.java @@ -1,9 +1,11 @@ package org.enso.interpreter.node.controlflow; +import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.FrameUtil; import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.DirectCallNode; import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.profiles.ConditionProfile; import org.enso.interpreter.node.ExpressionNode; @@ -13,19 +15,19 @@ import org.enso.interpreter.runtime.callable.atom.Atom; import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.state.Stateful; import org.enso.interpreter.runtime.type.TypesGen; /** An implementation of the case expression specialised to working on booleans. */ @NodeInfo(shortName = "BooleanMatch") public abstract class BooleanBranchNode extends BranchNode { private final boolean matched; - private @Child ExpressionNode branch; - private @Child ExecuteCallNode executeCallNode = ExecuteCallNodeGen.create(); private final ConditionProfile profile = ConditionProfile.createCountingProfile(); + private @Child DirectCallNode callNode; - BooleanBranchNode(boolean matched, CreateFunctionNode branch) { + BooleanBranchNode(boolean matched, RootCallTarget branch) { this.matched = matched; - this.branch = branch; + this.callNode = DirectCallNode.create(branch); } /** @@ -35,7 +37,7 @@ public abstract class BooleanBranchNode extends BranchNode { * @param branch the expression to be executed if (@code matcher} matches * @return a node for matching in a case expression */ - public static BooleanBranchNode build(boolean matched, CreateFunctionNode branch) { + public static BooleanBranchNode build(boolean matched, RootCallTarget branch) { return BooleanBranchNodeGen.create(matched, branch); } @@ -43,17 +45,19 @@ public static BooleanBranchNode build(boolean matched, CreateFunctionNode branch * Handles the boolean scrutinee case. * * @param frame the stack frame in which to execute + * @param state current monadic state * @param target the atom to destructure */ @Specialization - public void doAtom(VirtualFrame frame, boolean target) { - Object state = FrameUtil.getObjectSafe(frame, getStateFrameSlot()); + public void doAtom(VirtualFrame frame, Object state, boolean target) { if (profile.profile(matched == target)) { - Function function = TypesGen.asFunction(branch.executeGeneric(frame)); - + Stateful result = + (Stateful) + callNode.call( + Function.ArgumentsHelper.buildArguments( + frame.materialize(), state, new Object[0])); // Note [Caller Info For Case Branches] - throw new BranchSelectedException( - executeCallNode.executeCall(function, null, state, new Object[0])); + throw new BranchSelectedException(result); } } @@ -64,7 +68,7 @@ public void doAtom(VirtualFrame frame, boolean target) { * @param target the object to execute on */ @Fallback - public void doFallback(VirtualFrame frame, Object target) {} + public void doFallback(VirtualFrame frame, Object state, Object target) {} /* Note [Caller Info For Case Branches] * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/BranchNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/BranchNode.java index 60bf80a31e6f..1f4d3c9c1472 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/BranchNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/BranchNode.java @@ -15,7 +15,8 @@ public abstract class BranchNode extends BaseNode { * Executes the case branch. * * @param frame the stack frame in which to execute + * @param state current monadic state * @param target the object to match against */ - public abstract void execute(VirtualFrame frame, Object target); + public abstract void execute(VirtualFrame frame, Object state, Object target); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/CaseNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/CaseNode.java index d8ef3d0a906b..1b74701e737f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/CaseNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/CaseNode.java @@ -3,13 +3,12 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.dsl.CachedContext; -import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.dsl.NodeChild; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.FrameUtil; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.NodeInfo; -import com.oracle.truffle.api.profiles.BranchProfile; import org.enso.interpreter.Language; import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.runtime.Context; @@ -29,7 +28,6 @@ public abstract class CaseNode extends ExpressionNode { @Children private final BranchNode[] cases; - private final BranchProfile typeErrorProfile = BranchProfile.create(); CaseNode(BranchNode[] cases) { this.cases = cases; @@ -74,9 +72,10 @@ public Object doMatch( VirtualFrame frame, Object object, @CachedContext(Language.class) TruffleLanguage.ContextReference ctx) { + Object state = FrameUtil.getObjectSafe(frame, getStateFrameSlot()); try { for (BranchNode branchNode : cases) { - branchNode.execute(frame, object); + branchNode.execute(frame, state, object); } CompilerDirectives.transferToInterpreter(); throw new PanicException( diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/CatchAllBranchNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/CatchAllBranchNode.java index 73e29d1a8518..c99863b3650a 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/CatchAllBranchNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/CatchAllBranchNode.java @@ -1,15 +1,19 @@ package org.enso.interpreter.node.controlflow; +import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.frame.FrameUtil; import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.DirectCallNode; import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.nodes.UnexpectedResultException; +import com.oracle.truffle.api.profiles.ConditionProfile; import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.node.callable.ExecuteCallNode; import org.enso.interpreter.node.callable.ExecuteCallNodeGen; import org.enso.interpreter.node.callable.function.CreateFunctionNode; import org.enso.interpreter.runtime.callable.atom.Atom; import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.state.Stateful; import org.enso.interpreter.runtime.type.TypesGen; /** @@ -20,11 +24,10 @@ shortName = "Catch_All", description = "An explicit catch-all branch in a case expression") public class CatchAllBranchNode extends BranchNode { - @Child private ExpressionNode functionNode; - @Child private ExecuteCallNode executeCallNode = ExecuteCallNodeGen.create(); + private @Child DirectCallNode callNode; - private CatchAllBranchNode(CreateFunctionNode functionNode) { - this.functionNode = functionNode; + private CatchAllBranchNode(RootCallTarget functionNode) { + this.callNode = DirectCallNode.create(functionNode); } /** @@ -33,7 +36,7 @@ private CatchAllBranchNode(CreateFunctionNode functionNode) { * @param functionNode the function to execute in this case * @return a catch-all node */ - public static CatchAllBranchNode build(CreateFunctionNode functionNode) { + public static CatchAllBranchNode build(RootCallTarget functionNode) { return new CatchAllBranchNode(functionNode); } @@ -41,15 +44,17 @@ public static CatchAllBranchNode build(CreateFunctionNode functionNode) { * Executes the case branch on an arbitrary target. * * @param frame the stack frame in which to execute + * @param state current monadic state * @param target the object to match against */ - public void execute(VirtualFrame frame, Object target) { + public void execute(VirtualFrame frame, Object state, Object target) { // Note [Safe Casting to Function in Catch All Branches] - Function function = TypesGen.asFunction(functionNode.executeGeneric(frame)); - Object state = FrameUtil.getObjectSafe(frame, getStateFrameSlot()); - throw new BranchSelectedException( - // Note [Caller Info For Case Branches] - executeCallNode.executeCall(function, null, state, new Object[] {target})); + Stateful result = + (Stateful) + callNode.call( + Function.ArgumentsHelper.buildArguments( + frame.materialize(), state, new Object[] {target})); + throw new BranchSelectedException(result); } /* Note [Safe Casting to Function in Catch All Branches] diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/ConstructorBranchNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/ConstructorBranchNode.java index d3779c3cad9b..585dc292a749 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/ConstructorBranchNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/ConstructorBranchNode.java @@ -1,11 +1,16 @@ package org.enso.interpreter.node.controlflow; +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.FrameUtil; import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.DirectCallNode; +import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.profiles.ConditionProfile; +import org.enso.compiler.Compiler; import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.node.callable.ExecuteCallNode; import org.enso.interpreter.node.callable.ExecuteCallNodeGen; @@ -13,19 +18,19 @@ import org.enso.interpreter.runtime.callable.atom.Atom; import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.state.Stateful; import org.enso.interpreter.runtime.type.TypesGen; /** An implementation of the case expression specialised to working on constructors. */ @NodeInfo(shortName = "ConstructorMatch") public abstract class ConstructorBranchNode extends BranchNode { private final AtomConstructor matcher; - private @Child ExpressionNode branch; - private @Child ExecuteCallNode executeCallNode = ExecuteCallNodeGen.create(); + private @Child DirectCallNode callNode; private final ConditionProfile profile = ConditionProfile.createCountingProfile(); - ConstructorBranchNode(AtomConstructor matcher, CreateFunctionNode branch) { + ConstructorBranchNode(AtomConstructor matcher, RootCallTarget branch) { this.matcher = matcher; - this.branch = branch; + this.callNode = DirectCallNode.create(branch); } /** @@ -35,7 +40,7 @@ public abstract class ConstructorBranchNode extends BranchNode { * @param branch the expression to be executed if (@code matcher} matches * @return a node for matching in a case expression */ - public static ConstructorBranchNode build(AtomConstructor matcher, CreateFunctionNode branch) { + public static ConstructorBranchNode build(AtomConstructor matcher, RootCallTarget branch) { return ConstructorBranchNodeGen.create(matcher, branch); } @@ -46,17 +51,19 @@ public static ConstructorBranchNode build(AtomConstructor matcher, CreateFunctio * all the atom's fields as arguments. * * @param frame the stack frame in which to execute + * @param state current monadic state * @param target the atom to destructure */ @Specialization - public void doAtom(VirtualFrame frame, Atom target) { - Object state = FrameUtil.getObjectSafe(frame, getStateFrameSlot()); + public void doAtom(VirtualFrame frame, Object state, Atom target) { if (profile.profile(matcher == target.getConstructor())) { - Function function = TypesGen.asFunction(branch.executeGeneric(frame)); - // Note [Caller Info For Case Branches] - throw new BranchSelectedException( - executeCallNode.executeCall(function, null, state, target.getFields())); + Stateful result = + (Stateful) + callNode.call( + Function.ArgumentsHelper.buildArguments( + frame.materialize(), state, target.getFields())); + throw new BranchSelectedException(result); } } @@ -67,7 +74,7 @@ public void doAtom(VirtualFrame frame, Atom target) { * @param target the object to execute on */ @Fallback - public void doFallback(VirtualFrame frame, Object target) {} + public void doFallback(VirtualFrame frame, Object state, Object target) {} /* Note [Caller Info For Case Branches] * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/InstantiateAtomNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/InstantiateAtomNode.java index ab3382e85e95..8a8fa58c73f1 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/InstantiateAtomNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/InstantiateAtomNode.java @@ -1,19 +1,17 @@ package org.enso.interpreter.node.expression.builtin; import com.oracle.truffle.api.frame.VirtualFrame; -import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.nodes.RootNode; import org.enso.interpreter.Language; import org.enso.interpreter.node.ExpressionNode; -import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.state.Stateful; /** This node represents the process of instantiating an atom at runtime. */ @NodeInfo(shortName = "constructor::", description = "An atom instantiation at runtime.") public class InstantiateAtomNode extends RootNode { - private @Node.Child ExpressionNode instantiator; + private @Child ExpressionNode instantiator; private final String name; private InstantiateAtomNode(Language language, String name, ExpressionNode instantiator) { @@ -22,7 +20,8 @@ private InstantiateAtomNode(Language language, String name, ExpressionNode insta this.instantiator = instantiator; } - /** Executes this node. + /** + * Executes this node. * * @param frame the language frame being executed * @return the result of executing this node @@ -34,7 +33,8 @@ public Stateful execute(VirtualFrame frame) { instantiator.executeGeneric(frame)); } - /** Returns a string representation of this node. + /** + * Returns a string representation of this node. * * @return a string representation of this node */ @@ -43,7 +43,8 @@ public String getName() { return "constructor::" + name; } - /** Creates an instance of this node. + /** + * Creates an instance of this node. * * @param language the language for which the node is created * @param name the name of the atom being instantated diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/bool/IfThenElseNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/bool/IfThenElseNode.java index d5c9286f88fd..f4d860a8a095 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/bool/IfThenElseNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/bool/IfThenElseNode.java @@ -4,6 +4,7 @@ import com.oracle.truffle.api.profiles.ConditionProfile; import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.dsl.MonadicState; +import org.enso.interpreter.dsl.Suspend; import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; import org.enso.interpreter.runtime.callable.argument.Thunk; @@ -18,7 +19,11 @@ public class IfThenElseNode extends Node { private @Child ThunkExecutorNode rightThunkExecutorNode = ThunkExecutorNode.build(); private final ConditionProfile condProfile = ConditionProfile.createCountingProfile(); - Stateful execute(@MonadicState Object state, boolean _this, Thunk if_true, Thunk if_false) { + Stateful execute( + @MonadicState Object state, + boolean _this, + @Suspend Object if_true, + @Suspend Object if_false) { if (condProfile.profile(_this)) { return leftThunkExecutorNode.executeThunk(if_true, state, BaseNode.TailStatus.TAIL_DIRECT); } else { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/bool/IfThenNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/bool/IfThenNode.java index dbf77b92956b..e7d608165c42 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/bool/IfThenNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/bool/IfThenNode.java @@ -7,6 +7,7 @@ import org.enso.interpreter.Language; import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.dsl.MonadicState; +import org.enso.interpreter.dsl.Suspend; import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; import org.enso.interpreter.runtime.Context; @@ -25,11 +26,11 @@ static IfThenNode build() { return IfThenNodeGen.create(); } - abstract Stateful execute(@MonadicState Object state, boolean _this, Thunk if_true); + abstract Stateful execute(@MonadicState Object state, boolean _this, @Suspend Object if_true); @Specialization Stateful doExecute( - Object state, boolean _this, Thunk if_true, @CachedContext(Language.class) Context context) { + Object state, boolean _this, Object if_true, @CachedContext(Language.class) Context context) { if (condProfile.profile(_this)) { return leftThunkExecutorNode.executeThunk(if_true, state, BaseNode.TailStatus.TAIL_DIRECT); } else { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNode.java index 90f5844fd300..fcec5eb33a25 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNode.java @@ -7,6 +7,7 @@ import org.enso.interpreter.Language; import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.dsl.MonadicState; +import org.enso.interpreter.dsl.Suspend; import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; import org.enso.interpreter.runtime.Context; @@ -26,13 +27,13 @@ static CatchPanicNode build() { return CatchPanicNodeGen.create(); } - abstract Stateful execute(@MonadicState Object state, Object _this, Thunk action); + abstract Stateful execute(@MonadicState Object state, Object _this, @Suspend Object action); @Specialization Stateful doExecute( @MonadicState Object state, Object _this, - Thunk action, + Object action, @CachedContext(Language.class) Context ctx) { try { return thunkExecutorNode.executeThunk(action, state, BaseNode.TailStatus.NOT_TAIL); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/function/ApplicationOperator.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/function/ApplicationOperator.java index 4d86eb0b0413..12d2a5bc711b 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/function/ApplicationOperator.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/function/ApplicationOperator.java @@ -4,6 +4,7 @@ import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.dsl.MonadicState; +import org.enso.interpreter.dsl.Suspend; import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.callable.InvokeCallableNode; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; @@ -27,7 +28,8 @@ public class ApplicationOperator extends Node { invokeCallableNode.setTailStatus(BaseNode.TailStatus.TAIL_DIRECT); } - Stateful execute(VirtualFrame frame, @MonadicState Object state, Function _this, Thunk argument) { + Stateful execute( + VirtualFrame frame, @MonadicState Object state, Function _this, @Suspend Object argument) { return invokeCallableNode.execute(_this, frame, state, new Object[] {argument}); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/EvalNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/EvalNode.java new file mode 100644 index 000000000000..dfbad794ea19 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/EvalNode.java @@ -0,0 +1,70 @@ +package org.enso.interpreter.node.expression.builtin.interop.generic; + +import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.dsl.ReportPolymorphism; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.DirectCallNode; +import com.oracle.truffle.api.nodes.IndirectCallNode; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.source.Source; +import org.enso.interpreter.Language; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode; +import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.data.text.Text; + +@BuiltinMethod( + type = "Polyglot", + name = "eval", + description = "Evaluates a foreign language string.") +@ReportPolymorphism +public abstract class EvalNode extends Node { + static final int LIMIT = 10; + + static EvalNode build() { + return EvalNodeGen.create(); + } + + abstract Object execute(Object _this, Text language, Text code); + + @Specialization( + guards = {"cachedLanguage == language", "cachedCode == code"}, + limit = "LIMIT") + Object doCached( + Object _this, + Text language, + Text code, + @CachedContext(Language.class) Context context, + @Cached("language") Text cachedLanguage, + @Cached("code") Text cachedCode, + @Cached("build()") ToJavaStringNode toJavaStringNode, + @Cached("parse(context, cachedLanguage, cachedCode, toJavaStringNode)") CallTarget callTarget, + @Cached("create(callTarget)") DirectCallNode callNode, + @Cached("build()") HostValueToEnsoNode hostValueToEnsoNode) { + return hostValueToEnsoNode.execute(callNode.call()); + } + + @Specialization(replaces = "doCached") + Object doUncached( + Object _this, + Text language, + Text code, + @CachedContext(Language.class) Context context, + @Cached IndirectCallNode callNode, + @Cached("build()") ToJavaStringNode toJavaStringNode, + @Cached("build()") HostValueToEnsoNode hostValueToEnsoNode) { + CallTarget ct = parse(context, language, code, toJavaStringNode); + return hostValueToEnsoNode.execute(callNode.call(ct)); + } + + CallTarget parse(Context context, Text language, Text code, ToJavaStringNode toJavaStringNode) { + String languageStr = toJavaStringNode.execute(language); + String codeStr = toJavaStringNode.execute(code); + + Source source = Source.newBuilder(languageStr, codeStr, "").build(); + return context.getEnvironment().parsePublic(source); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/ExecuteNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/ExecuteNode.java index 70685cbd06a1..4510bd3046ce 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/ExecuteNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/ExecuteNode.java @@ -8,6 +8,7 @@ import com.oracle.truffle.api.profiles.BranchProfile; import org.enso.interpreter.Constants; import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode; import org.enso.interpreter.runtime.data.Array; import org.enso.interpreter.runtime.error.PanicException; @@ -18,11 +19,12 @@ public class ExecuteNode extends Node { private @Child InteropLibrary library = InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH); + private @Child HostValueToEnsoNode hostValueToEnsoNode = HostValueToEnsoNode.build(); private final BranchProfile err = BranchProfile.create(); Object execute(Object _this, Object callable, Array arguments) { try { - return library.execute(callable, arguments.getItems()); + return hostValueToEnsoNode.execute(library.execute(callable, arguments.getItems())); } catch (UnsupportedMessageException | ArityException | UnsupportedTypeException e) { err.enter(); throw new PanicException(e.getMessage(), this); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/BracketNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/BracketNode.java index 93c7eae5fe57..e01941bd7906 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/BracketNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/BracketNode.java @@ -5,6 +5,7 @@ import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.dsl.MonadicState; +import org.enso.interpreter.dsl.Suspend; import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.callable.InvokeCallableNode; import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; @@ -50,7 +51,7 @@ abstract Stateful execute( @MonadicState Object state, VirtualFrame frame, Object _this, - Thunk constructor, + @Suspend Object constructor, Object destructor, Object action); @@ -59,7 +60,7 @@ Stateful doBracket( Object state, VirtualFrame frame, Object _this, - Thunk constructor, + Object constructor, Object destructor, Object action) { Stateful resourceStateful = diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/NoInlineNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/NoInlineNode.java index 70009b63ba91..07d8f384a762 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/NoInlineNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/NoInlineNode.java @@ -4,6 +4,7 @@ import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.dsl.MonadicState; +import org.enso.interpreter.dsl.Suspend; import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; import org.enso.interpreter.runtime.callable.argument.Thunk; @@ -17,7 +18,7 @@ public class NoInlineNode extends Node { private @Child ThunkExecutorNode thunkExecutorNode = ThunkExecutorNode.build(); @CompilerDirectives.TruffleBoundary - Stateful execute(@MonadicState Object state, Object _this, Thunk action) { + Stateful execute(@MonadicState Object state, Object _this, @Suspend Object action) { return thunkExecutorNode.executeThunk(action, state, BaseNode.TailStatus.NOT_TAIL); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/state/RunStateNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/state/RunStateNode.java index f0ab25f50d92..ea5ebd1a6a37 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/state/RunStateNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/state/RunStateNode.java @@ -7,6 +7,7 @@ import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.dsl.MonadicState; +import org.enso.interpreter.dsl.Suspend; import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; import org.enso.interpreter.runtime.callable.argument.Thunk; @@ -29,11 +30,15 @@ static RunStateNode build() { private @Child ThunkExecutorNode thunkExecutorNode = ThunkExecutorNode.build(); abstract Stateful execute( - @MonadicState Object state, Object _this, Object key, Object local_state, Thunk computation); + @MonadicState Object state, + Object _this, + Object key, + Object local_state, + @Suspend Object computation); @Specialization Stateful doEmpty( - EmptyMap state, Object _this, Object key, Object local_state, Thunk computation) { + EmptyMap state, Object _this, Object key, Object local_state, Object computation) { SingletonMap localStateMap = new SingletonMap(key, local_state); Object result = thunkExecutorNode @@ -44,7 +49,7 @@ Stateful doEmpty( @Specialization(guards = {"state.getKey() == key"}) Stateful doSingletonSameKey( - SingletonMap state, Object _this, Object key, Object local_state, Thunk computation) { + SingletonMap state, Object _this, Object key, Object local_state, Object computation) { SingletonMap localStateContainer = new SingletonMap(state.getKey(), local_state); Stateful res = thunkExecutorNode.executeThunk( @@ -63,7 +68,7 @@ Stateful doSingletonNewKeyCached( Object _this, Object key, Object local_state, - Thunk computation, + Object computation, @Cached("key") Object cachedNewKey, @Cached("state.getKey()") Object cachedOldKey, @Cached(value = "buildSmallKeys(cachedNewKey, cachedOldKey)", dimensions = 1) @@ -77,7 +82,7 @@ Stateful doSingletonNewKeyCached( @Specialization Stateful doSingletonNewKeyUncached( - SingletonMap state, Object _this, Object key, Object local_state, Thunk computation) { + SingletonMap state, Object _this, Object key, Object local_state, Object computation) { return doSingletonNewKeyCached( state, _this, @@ -100,7 +105,7 @@ Stateful doMultiNewKeyCached( Object _this, Object key, Object local_state, - Thunk computation, + Object computation, @Cached("key") Object cachedNewKey, @Cached(value = "state.getKeys()", dimensions = 1) Object[] cachedOldKeys, @Cached("state.indexOf(key)") int index, @@ -125,7 +130,7 @@ Stateful doMultiExistingKeyCached( Object _this, Object key, Object local_state, - Thunk computation, + Object computation, @Cached("key") Object cachedNewKey, @Cached(value = "state.getKeys()", dimensions = 1) Object[] cachedOldKeys, @Cached("state.indexOf(key)") int index) { @@ -144,7 +149,7 @@ Stateful doMultiExistingKeyCached( @Specialization Stateful doMultiUncached( - SmallMap state, Object _this, Object key, Object local_state, Thunk computation) { + SmallMap state, Object _this, Object key, Object local_state, Object computation) { int idx = state.indexOf(key); if (idx == SmallMap.NOT_FOUND) { return doMultiNewKeyCached( diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/thread/WithInterruptHandlerNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/thread/WithInterruptHandlerNode.java index c8489573bc5d..07966ef6d4ac 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/thread/WithInterruptHandlerNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/thread/WithInterruptHandlerNode.java @@ -3,6 +3,7 @@ import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.dsl.MonadicState; +import org.enso.interpreter.dsl.Suspend; import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; import org.enso.interpreter.runtime.callable.argument.Thunk; @@ -18,7 +19,7 @@ public class WithInterruptHandlerNode extends Node { private @Child ThunkExecutorNode handlerExecutorNode = ThunkExecutorNode.build(); Stateful execute( - @MonadicState Object state, Object _this, Thunk action, Thunk interrupt_handler) { + @MonadicState Object state, Object _this, @Suspend Object action, @Suspend Object interrupt_handler) { try { return actExecutorNode.executeThunk(action, state, BaseNode.TailStatus.NOT_TAIL); } catch (ThreadInterruptedException e) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Polyglot.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Polyglot.java index bc8ccc0ba785..db325667f620 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Polyglot.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Polyglot.java @@ -65,6 +65,7 @@ private void createPolyglot(Language language, ModuleScope scope) { scope.registerMethod(polyglot, "execute", ExecuteMethodGen.makeFunction(language)); scope.registerMethod(polyglot, "invoke", InvokeMethodGen.makeFunction(language)); scope.registerMethod(polyglot, "new", InstantiateMethodGen.makeFunction(language)); + scope.registerMethod(polyglot, "eval", EvalMethodGen.makeFunction(language)); scope.registerMethod(polyglot, "get_member", GetMemberMethodGen.makeFunction(language)); scope.registerMethod(polyglot, "get_members", GetMembersMethodGen.makeFunction(language)); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java index 20bf7e1a1bb6..d7d33713d525 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java @@ -8,6 +8,7 @@ import com.oracle.truffle.api.dsl.CachedContext; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.MaterializedFrame; +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; @@ -83,8 +84,7 @@ public Function(RootCallTarget callTarget, MaterializedFrame scope, FunctionSche * @param args argument definitons * @return a Function object with specified behavior and arguments */ - public static Function fromBuiltinRootNode( - BuiltinRootNode node, ArgumentDefinition... args) { + public static Function fromBuiltinRootNode(BuiltinRootNode node, ArgumentDefinition... args) { RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(node); FunctionSchema schema = new FunctionSchema(args); return new Function(callTarget, null, schema); @@ -103,8 +103,7 @@ public static Function fromBuiltinRootNode( public static Function fromBuiltinRootNodeWithCallerFrameAccess( BuiltinRootNode node, ArgumentDefinition... args) { RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(node); - FunctionSchema schema = - new FunctionSchema(FunctionSchema.CallerFrameAccess.FULL, args); + FunctionSchema schema = new FunctionSchema(FunctionSchema.CallerFrameAccess.FULL, args); return new Function(callTarget, null, schema); } @@ -288,6 +287,19 @@ public static Object[] buildArguments( return new Object[] {function.getScope(), callerInfo, state, positionalArguments}; } + /** + * Generates an array of arguments using the schema to be passed to a call target. + * + * @param frame the frame becoming the lexical scope + * @param state the state to execute the thunk with + * @param positionalArguments the positional arguments to the call target + * @return an array containing the necessary information to call an Enso function + */ + public static Object[] buildArguments( + MaterializedFrame frame, Object state, Object[] positionalArguments) { + return new Object[] {frame, null, state, positionalArguments}; + } + /** * Generates an array of arguments using the schema to be passed to a call target. * diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala index 70c3e1495e91..bdb7db16e872 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala @@ -596,7 +596,10 @@ class IrToTruffle( .toArray[BranchNode] // Note [Pattern Match Fallbacks] - val matchExpr = CaseNode.build(scrutineeNode, cases) + val matchExpr = CaseNode.build( + scrutineeNode, + cases + ) setLocation(matchExpr, location) } else { val invalidBranches = maybeCases.collect { @@ -644,7 +647,8 @@ class IrToTruffle( branch.location ) - val branchNode = CatchAllBranchNode.build(branchCodeNode) + val branchNode = + CatchAllBranchNode.build(branchCodeNode.getCallTarget) Right(branchNode) case cons @ Pattern.Constructor(constructor, _, _, _, _) => @@ -704,11 +708,14 @@ class IrToTruffle( val bool = context.getBuiltins.bool() val branchNode: BranchNode = if (atomCons == bool.getTrue) { - BooleanBranchNode.build(true, branchCodeNode) + BooleanBranchNode.build(true, branchCodeNode.getCallTarget) } else if (atomCons == bool.getFalse) { - BooleanBranchNode.build(false, branchCodeNode) + BooleanBranchNode.build(false, branchCodeNode.getCallTarget) } else { - ConstructorBranchNode.build(atomCons, branchCodeNode) + ConstructorBranchNode.build( + atomCons, + branchCodeNode.getCallTarget + ) } branchNode @@ -1170,11 +1177,17 @@ class IrToTruffle( ) .unsafeAs[AliasAnalysis.Info.Scope.Child] - val shouldSuspend = shouldBeSuspended.getOrElse( - throw new CompilerError( - "Demand analysis information missing from call argument." - ) - ) + val shouldSuspend = value match { + case _: IR.Name => false + case _: IR.Literal.Text => false + case _: IR.Literal.Number => false + case _ => + shouldBeSuspended.getOrElse( + throw new CompilerError( + "Demand analysis information missing from call argument." + ) + ) + } val childScope = if (shouldSuspend) { scope.createChild(scopeInfo.scope) diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/CodeLocationsTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/CodeLocationsTest.scala index 96b7fb761c05..4d33bde9b724 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/CodeLocationsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/CodeLocationsTest.scala @@ -279,7 +279,6 @@ class CodeLocationsTest extends InterpreterTest { instrumenter.assertNodeExists(55, 67, classOf[CaseNode]) instrumenter.assertNodeExists(60, 1, classOf[ReadLocalVariableNode]) instrumenter.assertNodeExists(103, 3, classOf[IntegerLiteralNode]) - instrumenter.assertNodeExists(73, 33, classOf[CreateFunctionNode]) eval(code) shouldEqual 100 } diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Suspend.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Suspend.java new file mode 100644 index 000000000000..a2f4c52b7015 --- /dev/null +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Suspend.java @@ -0,0 +1,11 @@ +package org.enso.interpreter.dsl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** An interface marking an argument as suspended. */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.SOURCE) +public @interface Suspend {} diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/model/MethodDefinition.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/model/MethodDefinition.java index f62991dd4b3e..58a5ec3e3eac 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/model/MethodDefinition.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/model/MethodDefinition.java @@ -2,6 +2,7 @@ import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.dsl.MonadicState; +import org.enso.interpreter.dsl.Suspend; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.*; @@ -119,7 +120,10 @@ public boolean validate(ProcessingEnvironment processingEnvironment) { "The execute method does not take `this` argument. At least one positional argument must be named `_this`.", element); } - return definesThis; + + boolean argsValid = arguments.stream().allMatch(arg -> arg.validate(processingEnvironment)); + + return definesThis && argsValid; } /** @return the package name this method was declared in. */ @@ -188,7 +192,9 @@ public static class ArgumentDefinition { private final boolean isState; private final boolean isFrame; private final boolean isCallerInfo; + private final boolean isSuspended; private final int position; + private final VariableElement element; /** * Creates a new instance of this class. @@ -197,17 +203,32 @@ public static class ArgumentDefinition { * @param position the position (0-indexed) of this argument in the arguments list. */ public ArgumentDefinition(VariableElement element, int position) { + this.element = element; type = element.asType(); String[] typeNameSegments = type.toString().split("\\."); typeName = typeNameSegments[typeNameSegments.length - 1]; String originalName = element.getSimpleName().toString(); name = originalName.equals("_this") ? "this" : originalName; isState = element.getAnnotation(MonadicState.class) != null && type.toString().equals(OBJECT); + isSuspended = element.getAnnotation(Suspend.class) != null; isFrame = type.toString().equals(VIRTUAL_FRAME); isCallerInfo = type.toString().equals(CALLER_INFO); this.position = position; } + public boolean validate(ProcessingEnvironment processingEnvironment) { + if (type.toString().equals(THUNK)) { + processingEnvironment + .getMessager() + .printMessage( + Diagnostic.Kind.ERROR, + "Argument must not be typed as Thunk. Use @Suspend Object instead.", + element); + return false; + } + return true; + } + /** @return whether this argument should be passed the monadic state. */ public boolean isState() { return isState; @@ -265,7 +286,7 @@ public String getName() { /** @return whether this argument is expected to be passed suspended. */ public boolean isSuspended() { - return type.toString().equals(THUNK); + return isSuspended; } } } diff --git a/std-bits/src/main/java/org/enso/base/Text_Utils.java b/std-bits/src/main/java/org/enso/base/Text_Utils.java index c9377dd265f2..9d1c0db006ff 100644 --- a/std-bits/src/main/java/org/enso/base/Text_Utils.java +++ b/std-bits/src/main/java/org/enso/base/Text_Utils.java @@ -95,4 +95,15 @@ public static String from_utf_8(byte[] bytes) { public static boolean starts_with(String str, String prefix) { return str.startsWith(prefix); } + + /** + * Checks whether {@code a} is lexicographically before {@code b}. + * + * @param a the left operand + * @param b the right operand + * @return whether {@code a} is before {@code b}. + */ + public static boolean lt(String a, String b) { + return a.compareTo(b) < 0; + } } diff --git a/test/Benchmarks/src/Collections.enso b/test/Benchmarks/src/Collections.enso index f4ef3b806887..9ad203654fe9 100644 --- a/test/Benchmarks/src/Collections.enso +++ b/test/Benchmarks/src/Collections.enso @@ -2,6 +2,8 @@ from Base import all import Builtins import Base.Bench_Utils +polyglot java import java.util.Random + gen_list len = 0.upto len . fold Nil (l -> i -> Cons i+1 l) sum_list_meta list = @@ -14,6 +16,12 @@ sum_list_meta list = res = folder 0 list res +sum_recur n = if n == 0 then 0 else 1 + here.sum_recur n-1 + +build_map size = + rand = Random.new [].to_array + 0.upto size . fold Map.empty (m -> i -> m.insert (rand.nextInt [10000]) i) + main = mil = 1000000 list = here.gen_list mil @@ -23,3 +31,4 @@ main = Bench_Utils.measure (list.fold 0 (+)) "list fold" 1000 10 Bench_Utils.measure (vec.fold 0 (+)) "vector fold" 1000 10 Bench_Utils.measure (vec_decimal.fold 0 (+)) "vector decimal fold" 1000 10 + Bench_Utils.measure (here.build_map 10000) "build a map" 100 10 diff --git a/test/Benchmarks/src/Main.enso b/test/Benchmarks/src/Main.enso index d33d58ded152..2c458a667bb8 100644 --- a/test/Benchmarks/src/Main.enso +++ b/test/Benchmarks/src/Main.enso @@ -41,7 +41,8 @@ sum_co_state_body = acc = State.get Sum State.put Counter n-1 State.put Sum acc+n - if n == 0 then acc else @Tail_Call here.sum_co_state_body + if n == 0 then acc else + @Tail_Call here.sum_co_state_body sum_co_state n = res = State.run Counter n (State.run Sum 0 here.sum_co_state_body) @@ -67,7 +68,6 @@ sum_co n = main = hundred_mil = 100000000 - IO.println (here.sum_co 1000) IO.println "Measuring Sum TCO Corecursive" Bench_Utils.measure (here.sum_co hundred_mil) "sum_tco_corecursive" 100 10 IO.println "Measuring Sum TCO Decimal" diff --git a/test/Test/src/Data/Map_Spec.enso b/test/Test/src/Data/Map_Spec.enso new file mode 100644 index 000000000000..17d152846a7c --- /dev/null +++ b/test/Test/src/Data/Map_Spec.enso @@ -0,0 +1,14 @@ +from Base import all + +import Base.Test + +spec = describe "Maps" <| + it "should allow inserting and looking up values" <| + m = Map.empty . insert "foo" 134 . insert "bar" 654 . insert "baz" "spam" + m.get "foo" . should equal 134 + m.get "bar" . should equal 654 + m.get "baz" . should equal "spam" + m.get "nope" . catch e->e . should_equal (Map.No_Value_For_Key "nope") + it "should convert the whole map to a vector" <| + m = Map.empty . insert 0 0 . insert 3 -5 . insert 1 2 + m.to_vector.should equal [[0, 0], [1, 2], [3, -5]] diff --git a/test/Test/src/Main.enso b/test/Test/src/Main.enso index 0293722b6cfd..a5e8e22773c7 100644 --- a/test/Test/src/Main.enso +++ b/test/Test/src/Main.enso @@ -8,6 +8,7 @@ import Test.Semantic.Names_Spec import Test.Semantic.Meta_Spec import Test.List_Spec +import Test.Data.Map_Spec import Test.Number_Spec import Test.Process_Spec import Test.Vector.Spec as Vector_Spec @@ -31,3 +32,4 @@ main = Test.Suite.runMain <| Time_Spec.spec File_Spec.spec Meta_Spec.spec + Map_Spec.spec diff --git a/tools/performance/comparative-benchmark/benchmarks/haskell/package.yaml b/tools/performance/comparative-benchmark/benchmarks/haskell/package.yaml index 8f1478452289..252078831a88 100644 --- a/tools/performance/comparative-benchmark/benchmarks/haskell/package.yaml +++ b/tools/performance/comparative-benchmark/benchmarks/haskell/package.yaml @@ -16,6 +16,7 @@ library: - base - deepseq - containers + - random benchmarks: haskell-benchmark: diff --git a/tools/performance/comparative-benchmark/benchmarks/haskell/src/Fixtures.hs b/tools/performance/comparative-benchmark/benchmarks/haskell/src/Fixtures.hs index cae5c7ad09b5..da1f19354259 100644 --- a/tools/performance/comparative-benchmark/benchmarks/haskell/src/Fixtures.hs +++ b/tools/performance/comparative-benchmark/benchmarks/haskell/src/Fixtures.hs @@ -4,8 +4,10 @@ import Prelude import qualified Data.Map.Strict as Map +import Control.Monad (foldM) import Data.Int (Int64) import Data.List (foldl') +import System.Random (randomRIO) ------------------ @@ -69,4 +71,7 @@ myFoldl f z (Cons x xs) = let z' = z `f` x in seq z' $ myFoldl f z' xs buildMap :: Integer -> Map.Map Integer Integer -buildMap i = foldl' (\m i -> Map.insert i i m) Map.empty [0..i] \ No newline at end of file +buildMap n = foldl' (\m i -> Map.insert i i m) Map.empty [0..n] + +buildRandomMap :: Integer -> IO (Map.Map Integer Integer) +buildRandomMap n = foldM (\m i -> fmap (\key -> Map.insert key i m) $ randomRIO (0, 10000)) Map.empty [0..n] diff --git a/tools/performance/comparative-benchmark/benchmarks/haskell/test/bench/Main.hs b/tools/performance/comparative-benchmark/benchmarks/haskell/test/bench/Main.hs index a4d4262e63fd..a18b4688919a 100644 --- a/tools/performance/comparative-benchmark/benchmarks/haskell/test/bench/Main.hs +++ b/tools/performance/comparative-benchmark/benchmarks/haskell/test/bench/Main.hs @@ -10,6 +10,7 @@ main :: IO () main = defaultMain [ bench "buildMap" $ whnf Fixtures.buildMap Fixtures.tenThousand, + bench "buildMapRandom" $ whnfIO $ Fixtures.buildRandomMap Fixtures.tenThousand, bench "sumTCO" $ whnf Fixtures.sumTCO Fixtures.hundredMillion, bench "sumList" $ whnf Fixtures.sumList Fixtures.millionElementList, bench "reverseList" $ whnf Fixtures.reverseList Fixtures.millionElementList,