diff --git a/Interpreter/src/main/java/org/enso/interpreter/Constants.java b/Interpreter/src/main/java/org/enso/interpreter/Constants.java index 7a3ff1bcd3f..1320b805389 100644 --- a/Interpreter/src/main/java/org/enso/interpreter/Constants.java +++ b/Interpreter/src/main/java/org/enso/interpreter/Constants.java @@ -11,6 +11,7 @@ public class Constants { public static final String THIS_ARGUMENT_NAME = "this"; public static final String SCOPE_SEPARATOR = "."; + public static final String ANY_TYPE_NAME = "Any"; /** Cache sizes for different AST nodes. */ public static class CacheSizes { diff --git a/Interpreter/src/main/java/org/enso/interpreter/builder/ExpressionFactory.java b/Interpreter/src/main/java/org/enso/interpreter/builder/ExpressionFactory.java index 15f2acb3b99..983ebd84820 100644 --- a/Interpreter/src/main/java/org/enso/interpreter/builder/ExpressionFactory.java +++ b/Interpreter/src/main/java/org/enso/interpreter/builder/ExpressionFactory.java @@ -26,12 +26,7 @@ import org.enso.interpreter.node.callable.ForceNodeGen; import org.enso.interpreter.node.callable.argument.ReadArgumentNode; import org.enso.interpreter.node.callable.function.CreateFunctionNode; import org.enso.interpreter.node.callable.function.FunctionBodyNode; -import org.enso.interpreter.node.controlflow.CaseNode; -import org.enso.interpreter.node.controlflow.ConstructorCaseNode; -import org.enso.interpreter.node.controlflow.DefaultFallbackNode; -import org.enso.interpreter.node.controlflow.FallbackNode; -import org.enso.interpreter.node.controlflow.IfZeroNode; -import org.enso.interpreter.node.controlflow.MatchNode; +import org.enso.interpreter.node.controlflow.*; import org.enso.interpreter.node.expression.constant.ConstructorNode; import org.enso.interpreter.node.expression.constant.DynamicSymbolNode; import org.enso.interpreter.node.expression.literal.IntegerLiteralNode; @@ -395,7 +390,7 @@ public class ExpressionFactory implements AstExpressionVisitor { .map(fb -> (CaseNode) new FallbackNode(fb.visit(this))) .orElseGet(DefaultFallbackNode::new); - return new MatchNode(targetNode, cases, fallbackNode); + return MatchNodeGen.create(cases, fallbackNode, targetNode); } /* Note [Pattern Match Fallbacks] diff --git a/Interpreter/src/main/java/org/enso/interpreter/builder/ModuleScopeExpressionFactory.java b/Interpreter/src/main/java/org/enso/interpreter/builder/ModuleScopeExpressionFactory.java index 277d8a6cb0e..966dc0c1d60 100644 --- a/Interpreter/src/main/java/org/enso/interpreter/builder/ModuleScopeExpressionFactory.java +++ b/Interpreter/src/main/java/org/enso/interpreter/builder/ModuleScopeExpressionFactory.java @@ -90,10 +90,6 @@ public class ModuleScopeExpressionFactory implements AstGlobalScopeVisitor new VariableDoesNotExistException(method.typeName())); ExpressionFactory expressionFactory = new ExpressionFactory( language, @@ -105,7 +101,16 @@ public class ModuleScopeExpressionFactory implements AstGlobalScopeVisitor new VariableDoesNotExistException(method.typeName())); + moduleScope.registerMethod(constructor, method.methodName(), function); + } } ExpressionFactory factory = new ExpressionFactory(this.language, moduleScope); diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/callable/ApplicationNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/callable/ApplicationNode.java index 7f76437843d..391f92992e4 100644 --- a/Interpreter/src/main/java/org/enso/interpreter/node/callable/ApplicationNode.java +++ b/Interpreter/src/main/java/org/enso/interpreter/node/callable/ApplicationNode.java @@ -44,8 +44,9 @@ public class ApplicationNode extends ExpressionNode { Arrays.stream(callArguments).map(CallArgumentInfo::new).toArray(CallArgumentInfo[]::new); this.callable = callable; + boolean argumentsArePreExecuted = false; this.invokeCallableNode = - InvokeCallableNodeGen.create(argSchema, hasDefaultsSuspended); + InvokeCallableNodeGen.create(argSchema, hasDefaultsSuspended, argumentsArePreExecuted); } /** diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java index ef795d475f8..9d510965156 100644 --- a/Interpreter/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java +++ b/Interpreter/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java @@ -13,12 +13,9 @@ import org.enso.interpreter.node.callable.argument.sorter.ArgumentSorterNodeGen; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.callable.argument.Thunk; -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.error.MethodDoesNotExistException; import org.enso.interpreter.runtime.error.NotInvokableException; -import org.enso.interpreter.runtime.type.TypesGen; /** * This class is responsible for performing the actual invocation of a given callable with its @@ -36,6 +33,8 @@ public abstract class InvokeCallableNode extends BaseNode { private final boolean canApplyThis; private final int thisArgumentPosition; + private final boolean argumentsArePreExecuted; + private final ConditionProfile methodCalledOnNonAtom = ConditionProfile.createCountingProfile(); /** @@ -44,8 +43,11 @@ public abstract class InvokeCallableNode extends BaseNode { * @param schema a description of the arguments being applied to the callable * @param hasDefaultsSuspended whether or not the invocation has the callable's default arguments * (if any) suspended + * @param argumentsArePreExecuted whether the arguments will be provided as thunks or fully + * computed values */ - public InvokeCallableNode(CallArgumentInfo[] schema, boolean hasDefaultsSuspended) { + public InvokeCallableNode( + CallArgumentInfo[] schema, boolean hasDefaultsSuspended, boolean argumentsArePreExecuted) { boolean appliesThis = false; int idx = 0; for (; idx < schema.length; idx++) { @@ -61,7 +63,8 @@ public abstract class InvokeCallableNode extends BaseNode { this.canApplyThis = appliesThis; this.thisArgumentPosition = idx; - boolean argumentsArePreExecuted = false; + this.argumentsArePreExecuted = argumentsArePreExecuted; + this.argumentSorter = ArgumentSorterNodeGen.create(schema, hasDefaultsSuspended, argumentsArePreExecuted); this.methodResolverNode = MethodResolverNodeGen.create(); @@ -102,21 +105,16 @@ public abstract class InvokeCallableNode extends BaseNode { @Specialization public Object invokeDynamicSymbol(UnresolvedSymbol symbol, Object[] arguments) { if (canApplyThis) { - if (thisExecutor == null) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - thisExecutor = ThunkExecutorNodeGen.create(false); - } - - Object selfArgument = - thisExecutor.executeThunk(((Thunk) arguments[thisArgumentPosition])); - - if (methodCalledOnNonAtom.profile(TypesGen.isAtom(selfArgument))) { - Atom self = (Atom) selfArgument; - Function function = methodResolverNode.execute(symbol, self); - return this.argumentSorter.execute(function, arguments); - } else { - throw new MethodDoesNotExistException(selfArgument, symbol.getName(), this); + Object selfArgument = arguments[thisArgumentPosition]; + if (!argumentsArePreExecuted) { + if (thisExecutor == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + thisExecutor = ThunkExecutorNodeGen.create(false); + } + selfArgument = thisExecutor.executeThunk((Thunk) selfArgument); } + Function function = methodResolverNode.execute(symbol, selfArgument); + return this.argumentSorter.execute(function, arguments); } else { throw new RuntimeException("Currying without `this` argument is not yet supported."); } diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/callable/MethodResolverNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/callable/MethodResolverNode.java index 3be3308a025..6cd18450693 100644 --- a/Interpreter/src/main/java/org/enso/interpreter/node/callable/MethodResolverNode.java +++ b/Interpreter/src/main/java/org/enso/interpreter/node/callable/MethodResolverNode.java @@ -14,45 +14,56 @@ import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.error.MethodDoesNotExistException; /** - * A node performing lookups of method definitions. Uses a polymorphic inline cache to ensure the - * best performance. + * A node performing lookups of method definitions. + * + *

Uses a polymorphic inline cache to ensure the best performance. + * + *

The dispatch algorithm works by matching the kind of value the method is requested for and + * delegating to the proper lookup method of {@link UnresolvedSymbol}. */ public abstract class MethodResolverNode extends Node { - /** - * DSL method to generate the actual cached code. Uses Cached arguments and performs all the logic - * through the DSL. Not for manual use. - */ - @Specialization(guards = "isValidCache(symbol, cachedSymbol, atom, cachedConstructor)") - public Function resolveCached( - UnresolvedSymbol symbol, - Atom atom, - @CachedContext(Language.class) TruffleLanguage.ContextReference contextRef, - @Cached("symbol") UnresolvedSymbol cachedSymbol, - @Cached("atom.getConstructor()") AtomConstructor cachedConstructor, - @Cached("resolveMethod(cachedConstructor, cachedSymbol)") Function function) { - return function; - } - /** * Entry point for this node. * * @param symbol Method name to resolve. - * @param atom Object for which to resolve the method. + * @param self Object for which to resolve the method. * @return Resolved method. */ - public abstract Function execute(UnresolvedSymbol symbol, Atom atom); + public abstract Function execute(UnresolvedSymbol symbol, Object self); - /** - * Handles the actual method lookup. Not for manual use. - * - * @param cons Type for which to resolve the method. - * @param symbol symbol representing the method to resolve - * @return Resolved method definition. - */ - public Function resolveMethod( - AtomConstructor cons, - UnresolvedSymbol symbol) { + @Specialization(guards = "isValidAtomCache(symbol, cachedSymbol, atom, cachedConstructor)") + Function resolveAtomCached( + UnresolvedSymbol symbol, + Atom atom, + @CachedContext(Language.class) TruffleLanguage.ContextReference contextRef, + @Cached("symbol") UnresolvedSymbol cachedSymbol, + @Cached("atom.getConstructor()") AtomConstructor cachedConstructor, + @Cached("resolveAtomMethod(cachedConstructor, cachedSymbol)") Function function) { + return function; + } + + @Specialization(guards = "cachedSymbol == symbol") + Function resolveNumberCached( + UnresolvedSymbol symbol, + long self, + @CachedContext(Language.class) TruffleLanguage.ContextReference contextReference, + @Cached("symbol") UnresolvedSymbol cachedSymbol, + @Cached("resolveNumberMethod(cachedSymbol)") Function function) { + return function; + } + + @Specialization(guards = "cachedSymbol == symbol") + Function resolveFunctionCached( + UnresolvedSymbol symbol, + Function self, + @CachedContext(Language.class) TruffleLanguage.ContextReference contextReference, + @Cached("symbol") UnresolvedSymbol cachedSymbol, + @Cached("resolveFunctionMethod(cachedSymbol)") Function function) { + return function; + } + + Function resolveAtomMethod(AtomConstructor cons, UnresolvedSymbol symbol) { Function result = symbol.resolveFor(cons); if (result == null) { throw new MethodDoesNotExistException(cons, symbol.getName(), this); @@ -60,12 +71,27 @@ public abstract class MethodResolverNode extends Node { return result; } - /** - * Checks the cache validity. For use by the DSL. The cache entry is valid if it's resolved for - * the same method name and this argument type. Not for manual use. - */ - public boolean isValidCache( - UnresolvedSymbol symbol, UnresolvedSymbol cachedSymbol, Atom atom, AtomConstructor cachedConstructor) { + Function resolveNumberMethod(UnresolvedSymbol symbol) { + Function result = symbol.resolveForNumber(); + if (result == null) { + throw new MethodDoesNotExistException("Number", symbol.getName(), this); + } + return result; + } + + Function resolveFunctionMethod(UnresolvedSymbol symbol) { + Function result = symbol.resolveForFunction(); + if (result == null) { + throw new MethodDoesNotExistException("Function", symbol.getName(), this); + } + return result; + } + + boolean isValidAtomCache( + UnresolvedSymbol symbol, + UnresolvedSymbol cachedSymbol, + Atom atom, + AtomConstructor cachedConstructor) { return (symbol == cachedSymbol) && (atom.getConstructor() == cachedConstructor); } } diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/callable/argument/sorter/CachedArgumentSorterNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/callable/argument/sorter/CachedArgumentSorterNode.java index 89f07631610..ea9f142a808 100644 --- a/Interpreter/src/main/java/org/enso/interpreter/node/callable/argument/sorter/CachedArgumentSorterNode.java +++ b/Interpreter/src/main/java/org/enso/interpreter/node/callable/argument/sorter/CachedArgumentSorterNode.java @@ -40,8 +40,8 @@ public class CachedArgumentSorterNode extends BaseNode { * @param schema information on the calling argument * @param hasDefaultsSuspended whether or not the function to which these arguments are applied * has its defaults suspended. - * @param ignoresArgumentExecution whether this node assumes all arguments are pre-executed and not - * passed in a {@link Thunk}. + * @param ignoresArgumentExecution whether this node assumes all arguments are pre-executed and + * not passed in a {@link Thunk}. * @param isTail whether this node is called from a tail call position. */ public CachedArgumentSorterNode( @@ -72,7 +72,9 @@ public class CachedArgumentSorterNode extends BaseNode { if (postApplicationSchema.hasOversaturatedArgs()) { oversaturatedCallableNode = InvokeCallableNodeGen.create( - postApplicationSchema.getOversaturatedArguments(), hasDefaultsSuspended); + postApplicationSchema.getOversaturatedArguments(), + hasDefaultsSuspended, + ignoresArgumentExecution); oversaturatedCallableNode.setTail(isTail); } @@ -149,8 +151,7 @@ public class CachedArgumentSorterNode extends BaseNode { return optimiser.executeDispatch(function, mappedAppliedArguments); } } else { - Object evaluatedVal = - optimiser.executeDispatch(function, mappedAppliedArguments); + Object evaluatedVal = optimiser.executeDispatch(function, mappedAppliedArguments); return this.oversaturatedCallableNode.execute( evaluatedVal, generateOversaturatedArguments(function, arguments)); diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/CaseNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/CaseNode.java index 610cad76b61..ec7f3736fc9 100644 --- a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/CaseNode.java +++ b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/CaseNode.java @@ -4,16 +4,40 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.UnexpectedResultException; import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.callable.function.Function; /** An abstract representation of a case expression. */ public abstract class CaseNode extends BaseNode { /** - * Executes the case expression. + * Executes the case expression with an atom scrutinee. * * @param frame the stack frame in which to execute - * @param target the constructor to destructure + * @param target the atom to match and destructure * @throws UnexpectedResultException when the result of desctructuring {@code target} can't be * represented as a value of the expected return type */ - public abstract void execute(VirtualFrame frame, Atom target) throws UnexpectedResultException; + public abstract void executeAtom(VirtualFrame frame, Atom target) + throws UnexpectedResultException; + + /** + * Executes the case expression with a function scrutinee. + * + * @param frame the stack frame in which to execute + * @param target the function to match + * @throws UnexpectedResultException when the result of desctructuring {@code target} can't be + * represented as a value of the expected return type + */ + public abstract void executeFunction(VirtualFrame frame, Function target) + throws UnexpectedResultException; + + /** + * * Executes the case expression with a number scrutinee. + * + * @param frame the stack frame in which to execute + * @param target the number to match + * @throws UnexpectedResultException when the result of desctructuring {@code target} can't be + * represented as a value of the expected return type + */ + public abstract void executeNumber(VirtualFrame frame, long target) + throws UnexpectedResultException; } diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/ConstructorCaseNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/ConstructorCaseNode.java index 9c74dc86813..88d294b14d9 100644 --- a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/ConstructorCaseNode.java +++ b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/ConstructorCaseNode.java @@ -9,8 +9,9 @@ import org.enso.interpreter.node.callable.ExecuteCallNodeGen; 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.type.TypesGen; -/** An implementation of the case expression specialised to working on explicit constructors. */ +/** An implementation of the case expression specialised to working on constructors. */ public class ConstructorCaseNode extends CaseNode { @Child private ExpressionNode matcher; @Child private ExpressionNode branch; @@ -39,21 +40,39 @@ public class ConstructorCaseNode extends CaseNode { } /** - * Executes the case expression. + * Handles the atom scrutinee case. * - *

It has no direct return value and instead uses a {@link BranchSelectedException} to signal - * the correct result back to the parent of the case expression. + *

The atom's constructor is checked and if it matches the conditional branch is executed with + * all the atom's fields as arguments. * * @param frame the stack frame in which to execute - * @param target the constructor to destructure - * @throws UnexpectedResultException when the result of desctructuring {@code target} can't be - * represented as a value of the expected return type + * @param target the atom to destructure + * @throws UnexpectedResultException */ - public void execute(VirtualFrame frame, Atom target) throws UnexpectedResultException { + @Override + public void executeAtom(VirtualFrame frame, Atom target) throws UnexpectedResultException { AtomConstructor matcherVal = matcher.executeAtomConstructor(frame); if (profile.profile(matcherVal == target.getConstructor())) { Function function = branch.executeFunction(frame); throw new BranchSelectedException(executeCallNode.executeCall(function, target.getFields())); } } + + /** + * Handles the function scrutinee case, by not matching it at all. + * + * @param frame the stack frame in which to execute + * @param target the function to match + */ + @Override + public void executeFunction(VirtualFrame frame, Function target) {} + + /** + * Handles the number scrutinee case, by not matching it at all. + * + * @param frame the stack frame in which to execute + * @param target the function to match + */ + @Override + public void executeNumber(VirtualFrame frame, long target) {} } diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/DefaultFallbackNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/DefaultFallbackNode.java index 2a2dc5be692..a25ed4c5878 100644 --- a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/DefaultFallbackNode.java +++ b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/DefaultFallbackNode.java @@ -1,7 +1,9 @@ package org.enso.interpreter.node.controlflow; import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.UnexpectedResultException; import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.error.InexhaustivePatternMatchException; /** @@ -11,13 +13,49 @@ import org.enso.interpreter.runtime.error.InexhaustivePatternMatchException; public class DefaultFallbackNode extends CaseNode { /** - * Executes the case expression's error case. + * Executes the case expression's error case, by throwing a {@link + * InexhaustivePatternMatchException}. * * @param frame the stack frame in which to execute * @param target the constructor to destructure */ - @Override - public void execute(VirtualFrame frame, Atom target) { + private void execute(VirtualFrame frame, Object target) { throw new InexhaustivePatternMatchException(this.getParent()); } + + /** + * Executes the case expression's error case, by throwing a {@link + * InexhaustivePatternMatchException}. + * + * @param frame the stack frame in which to execute + * @param target the atom to match and destructure + */ + @Override + public void executeAtom(VirtualFrame frame, Atom target) { + execute(frame, target); + } + + /** + * Executes the case expression's error case, by throwing a {@link + * InexhaustivePatternMatchException}. + * + * @param frame the stack frame in which to execute + * @param target the function to match + */ + @Override + public void executeFunction(VirtualFrame frame, Function target) { + execute(frame, target); + } + + /** + * Executes the case expression's error case, by throwing a {@link + * InexhaustivePatternMatchException}. + * + * @param frame the stack frame in which to execute + * @param target the number to match + */ + @Override + public void executeNumber(VirtualFrame frame, long target) { + execute(frame, target); + } } diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/FallbackNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/FallbackNode.java index feb349bf8b3..82c46c53dbe 100644 --- a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/FallbackNode.java +++ b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/FallbackNode.java @@ -25,6 +25,11 @@ public class FallbackNode extends CaseNode { this.functionNode = functionNode; } + private void execute(VirtualFrame frame, Object target) throws UnexpectedResultException { + Function function = functionNode.executeFunction(frame); + throw new BranchSelectedException(executeCallNode.executeCall(function, new Object[0])); + } + /** * Executes the case expression catch-all case. * @@ -37,8 +42,40 @@ public class FallbackNode extends CaseNode { * represented as a value of the expected return type */ @Override - public void execute(VirtualFrame frame, Atom target) throws UnexpectedResultException { - Function function = functionNode.executeFunction(frame); - throw new BranchSelectedException(executeCallNode.executeCall(function, new Object[0])); + public void executeAtom(VirtualFrame frame, Atom target) throws UnexpectedResultException { + execute(frame, target); + } + + /** + * Executes the case expression catch-all case. + * + *

It has no direct return value and instead uses a {@link BranchSelectedException} to signal + * the correct result back to the parent of the case expression. + * + * @param frame the stack frame in which to execute + * @param target the constructor to destructure + * @throws UnexpectedResultException when the result of desctructuring {@code target} can't be + * represented as a value of the expected return type + */ + @Override + public void executeFunction(VirtualFrame frame, Function target) + throws UnexpectedResultException { + execute(frame, target); + } + + /** + * Executes the case expression catch-all case. + * + *

It has no direct return value and instead uses a {@link BranchSelectedException} to signal + * the correct result back to the parent of the case expression. + * + * @param frame the stack frame in which to execute + * @param target the constructor to destructure + * @throws UnexpectedResultException when the result of desctructuring {@code target} can't be + * represented as a value of the expected return type + */ + @Override + public void executeNumber(VirtualFrame frame, long target) throws UnexpectedResultException { + execute(frame, target); } } diff --git a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/MatchNode.java b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/MatchNode.java index ec3d21b2838..5bdf898b121 100644 --- a/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/MatchNode.java +++ b/Interpreter/src/main/java/org/enso/interpreter/node/controlflow/MatchNode.java @@ -1,30 +1,31 @@ package org.enso.interpreter.node.controlflow; import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.dsl.NodeChild; +import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.UnexpectedResultException; import com.oracle.truffle.api.profiles.BranchProfile; import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.error.TypeError; -/** A node representing a pattern match on an Atom. */ -public class MatchNode extends ExpressionNode { - @Child private ExpressionNode target; +/** + * A node representing a pattern match on an arbitrary runtime value. + * + *

Has a scrutinee node and a collection of {@link CaseNode}s. The case nodes get executed one by + * one, until one throws an {@link BranchSelectedException}, the value of which becomes the result + * of this pattern match. + */ +@NodeChild(value = "scrutinee", type = ExpressionNode.class) +public abstract class MatchNode extends ExpressionNode { @Children private final CaseNode[] cases; @Child private CaseNode fallback; private final BranchProfile typeErrorProfile = BranchProfile.create(); - /** - * Creates a node that pattern matches on an Atom. - * - * @param target the atom to pattern match on - * @param cases the branches of the pattern match - * @param fallback the fallback case of the pattern match - */ - public MatchNode(ExpressionNode target, CaseNode[] cases, CaseNode fallback) { - this.target = target; + MatchNode(CaseNode[] cases, CaseNode fallback) { this.cases = cases; this.fallback = fallback; } @@ -43,21 +44,62 @@ public class MatchNode extends ExpressionNode { fallback.setTail(isTail); } - /** - * Executes the pattern match. - * - * @param frame the stack frame for execution - * @return the result of the pattern match - */ + // TODO[MK]: The atom, number and function cases are very repetitive and should be refactored. + // It poses some engineering challenge – the approaches tried so far included passing the only + // changing line as a lambda and introducing a separate node between this and the CaseNodes. + // Both attempts resulted in a performance drop. + @ExplodeLoop - @Override - public Object executeGeneric(VirtualFrame frame) { + @Specialization + Object doAtom(VirtualFrame frame, Atom atom) { try { - Atom atom = target.executeAtom(frame); for (CaseNode caseNode : cases) { - caseNode.execute(frame, atom); + caseNode.executeAtom(frame, atom); } - fallback.execute(frame, atom); + fallback.executeAtom(frame, atom); + CompilerDirectives.transferToInterpreter(); + throw new RuntimeException("Impossible behavior."); + + } catch (BranchSelectedException e) { + // Note [Branch Selection Control Flow] + return e.getResult(); + + } catch (UnexpectedResultException e) { + typeErrorProfile.enter(); + throw new TypeError("Expected an Atom.", this); + } + } + + @ExplodeLoop + @Specialization + Object doFunction(VirtualFrame frame, Function function) { + try { + for (CaseNode caseNode : cases) { + caseNode.executeFunction(frame, function); + } + fallback.executeFunction(frame, function); + CompilerDirectives.transferToInterpreter(); + throw new RuntimeException("Impossible behavior."); + + } catch (BranchSelectedException e) { + // Note [Branch Selection Control Flow] + return e.getResult(); + + } catch (UnexpectedResultException e) { + typeErrorProfile.enter(); + throw new TypeError("Expected an Atom.", this); + } + } + + @ExplodeLoop + @Specialization + Object doNumber(VirtualFrame frame, long number) { + try { + + for (CaseNode caseNode : cases) { + caseNode.executeNumber(frame, number); + } + fallback.executeNumber(frame, number); CompilerDirectives.transferToInterpreter(); throw new RuntimeException("Impossible behavior."); diff --git a/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java b/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java index 77d7367b1c9..93120e97a55 100644 --- a/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java +++ b/Interpreter/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java @@ -1,5 +1,6 @@ package org.enso.interpreter.runtime.callable; +import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.interop.TruffleObject; import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.function.Function; @@ -27,7 +28,7 @@ public class UnresolvedSymbol implements TruffleObject { *

All names for dynamic symbols are interned, making it safe to compare symbol names using the * standard {@code ==} equality operator. * - * @return the name of this symbol. + * @return the name of this symbol */ public String getName() { return name; @@ -40,6 +41,26 @@ public class UnresolvedSymbol implements TruffleObject { * @return the resolved function definition, or null if not found */ public Function resolveFor(AtomConstructor cons) { - return scope.lookupMethodDefinition(cons, name); + return scope.lookupMethodDefinitionForAtom(cons, name); + } + + /** + * Resolves the symbol for a number. + * + * @return the resolved function definition, or null if not found + */ + @CompilerDirectives.TruffleBoundary + public Function resolveForNumber() { + return scope.lookupMethodDefinitionForAny(name).orElse(null); + } + + /** + * Resolves the symbol for a function. + * + * @return the resolved function definition, or null if not found + */ + @CompilerDirectives.TruffleBoundary + public Function resolveForFunction() { + return scope.lookupMethodDefinitionForAny(name).orElse(null); } } diff --git a/Interpreter/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java b/Interpreter/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java index ee4f74666ab..2b8369c8479 100644 --- a/Interpreter/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java +++ b/Interpreter/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java @@ -1,7 +1,6 @@ package org.enso.interpreter.runtime.scope; -import org.enso.interpreter.runtime.Builtins; -import org.enso.interpreter.runtime.Module; +import com.oracle.truffle.api.CompilerDirectives; import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.function.Function; @@ -12,6 +11,7 @@ public class ModuleScope { private final Map constructors = new HashMap<>(); private final Map> methods = new HashMap<>(); + private final Map anyMethods = new HashMap<>(); private final Set imports = new HashSet<>(); private final Set transitiveImports = new HashSet<>(); @@ -57,38 +57,80 @@ public class ModuleScope { /** * Registers a method defined for a given type. * - * @param atom type the method was defined for. - * @param method method name. - * @param function the {@link Function} associated with this definition. + * @param atom type the method was defined for + * @param method method name + * @param function the {@link Function} associated with this definition */ public void registerMethod(AtomConstructor atom, String method, Function function) { getMethodMapFor(atom).put(method, function); } /** - * Looks up the definition for a given type and method name. The resolution algorithm is first - * looking for methods defined at the constructor definition site (i.e. non-overloads), then looks - * for methods defined in this scope and finally tries to resolve the method in all transitive - * dependencies of this module. + * Registers a method for the {@code Any} type. + * + * @param methodName the name of the method to register + * @param function the {@link Function} associated with this definition + */ + public void registerMethodForAny(String methodName, Function function) { + anyMethods.put(methodName, function); + } + + /** + * Looks up the definition for a given type and method name. + * + *

The resolution algorithm is first looking for methods defined at the constructor definition + * site (i.e. non-overloads), then looks for methods defined in this scope and finally tries to + * resolve the method in all transitive dependencies of this module. + * + *

If the specific search fails, methods defined for any type are searched, first looking at + * locally defined methods and then all the transitive imports. * * @param atom type to lookup the method for. * @param name the method name. * @return the matching method definition or null if not found. */ - public Function lookupMethodDefinition(AtomConstructor atom, String name) { + @CompilerDirectives.TruffleBoundary + public Function lookupMethodDefinitionForAtom(AtomConstructor atom, String name) { + return lookupSpecificMethodDefinitionForAtom(atom, name) + .orElseGet(() -> lookupMethodDefinitionForAny(name).orElse(null)); + } + + /** + * Looks up a method definition by-name, for methods defined on the type Any. + * + *

The resolution algorithm prefers methods defined locally over any other method. The + * definitions are imported into scope transitively. + * + * @param name the name of the method to look up + * @return {@code Optional.of(resultMethod)} if the method existed, {@code Optional.empty()} + * otherwise + */ + @CompilerDirectives.TruffleBoundary + public Optional lookupMethodDefinitionForAny(String name) { + Function definedHere = anyMethods.get(name); + if (definedHere != null) { + return Optional.of(definedHere); + } + return transitiveImports.stream() + .map(scope -> scope.getMethodsOfAny().get(name)) + .filter(Objects::nonNull) + .findFirst(); + } + + private Optional lookupSpecificMethodDefinitionForAtom( + AtomConstructor atom, String name) { Function definedWithAtom = atom.getDefinitionScope().getMethodMapFor(atom).get(name); if (definedWithAtom != null) { - return definedWithAtom; + return Optional.of(definedWithAtom); } Function definedHere = getMethodMapFor(atom).get(name); if (definedHere != null) { - return definedHere; + return Optional.of(definedHere); } return transitiveImports.stream() .map(scope -> scope.getMethodMapFor(atom).get(name)) .filter(Objects::nonNull) - .findFirst() - .orElse(null); + .findFirst(); } /** @@ -96,10 +138,14 @@ public class ModuleScope { * * @return a set of all the transitive dependencies of this module */ - protected Set getTransitiveImports() { + private Set getTransitiveImports() { return transitiveImports; } + private Map getMethodsOfAny() { + return anyMethods; + } + /** * Adds a dependency for this module. * diff --git a/Interpreter/src/test/scala/org/enso/interpreter/test/semantic/InteropTest.scala b/Interpreter/src/test/scala/org/enso/interpreter/test/semantic/InteropTest.scala index 0bf1a813ae6..402277c6ecd 100644 --- a/Interpreter/src/test/scala/org/enso/interpreter/test/semantic/InteropTest.scala +++ b/Interpreter/src/test/scala/org/enso/interpreter/test/semantic/InteropTest.scala @@ -33,4 +33,15 @@ class InteropTest extends LanguageTest { val fun = eval(code) fun.call(1).call(2).call(3) shouldEqual 6 } + + "Interop library" should "work with oversaturated calls on unresolved methods returned from functions" in { + val code = + """ + |Any.method = this + | + |{ |x| method } + |""".stripMargin + val fun = eval(code) + fun.call(1, 2) shouldEqual 2 + } } diff --git a/Interpreter/src/test/scala/org/enso/interpreter/test/semantic/MethodsTest.scala b/Interpreter/src/test/scala/org/enso/interpreter/test/semantic/MethodsTest.scala index 1980ea8289c..55409001d5f 100644 --- a/Interpreter/src/test/scala/org/enso/interpreter/test/semantic/MethodsTest.scala +++ b/Interpreter/src/test/scala/org/enso/interpreter/test/semantic/MethodsTest.scala @@ -42,6 +42,34 @@ class MethodsTest extends LanguageTest { """ |@foo [7] |""".stripMargin - the[PolyglotException] thrownBy eval(code) should have message "Object 7 does not define method foo." + the[PolyglotException] thrownBy eval(code) should have message "Object Number does not define method foo." + } + + "Methods defined on Any type" should "be callable for any type" in { + val code = + """ + |type Foo; + |type Bar; + |type Baz; + | + |Any.method = { match this < + | Foo ~ { 1 }; + | Bar ~ { 2 }; + | Baz ~ { 3 }; + | { 0 }; + |>} + | + |@{ + | @println [@IO, @method [@Foo]]; + | @println [@IO, @method [@Bar]]; + | @println [@IO, @method [@Baz]]; + | @println [@IO, @method [@Unit]]; + | @println [@IO, @method [123]]; + | @println [@IO, @method [{|x| x }]]; + | 0 + |} + |""".stripMargin + eval(code) + consumeOut shouldEqual List("1", "2", "3", "0", "0", "0") } }