mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 17:03:32 +03:00
Automatically force all-defaulted functions (#3414)
This changes the interpreter to treat functions with all-defaulted args as thunks. Seems to have no performance impact in compiled code.
This commit is contained in:
parent
bb6a5bac02
commit
96a0c92c8b
@ -185,6 +185,7 @@
|
||||
- [Fixed compiler issue related to module cache.][3367]
|
||||
- [Fixed execution of defaulted arguments of Atom Constructors][3358]
|
||||
- [Converting Enso Date to java.time.LocalDate and back][3374]
|
||||
- [Functions with all-defaulted arguments now execute automatically][3414]
|
||||
|
||||
[3227]: https://github.com/enso-org/enso/pull/3227
|
||||
[3248]: https://github.com/enso-org/enso/pull/3248
|
||||
@ -195,6 +196,7 @@
|
||||
[3367]: https://github.com/enso-org/enso/pull/3367
|
||||
[3374]: https://github.com/enso-org/enso/pull/3374
|
||||
[3412]: https://github.com/enso-org/enso/pull/3412
|
||||
[3414]: https://github.com/enso-org/enso/pull/3414
|
||||
[3417]: https://github.com/enso-org/enso/pull/3417
|
||||
|
||||
# Enso 2.0.0-alpha.18 (2021-10-12)
|
||||
|
@ -18,7 +18,13 @@ public abstract class BaseNode extends Node {
|
||||
/** Node is in a tail position and marked as a tail call. */
|
||||
TAIL_LOOP,
|
||||
/** Node is not in a tail position. */
|
||||
NOT_TAIL
|
||||
NOT_TAIL;
|
||||
|
||||
private static final int NUMBER_OF_VALUES = values().length;
|
||||
|
||||
public static int numberOfValues() {
|
||||
return NUMBER_OF_VALUES;
|
||||
}
|
||||
}
|
||||
|
||||
private @CompilationFinal TailStatus tailStatus = TailStatus.NOT_TAIL;
|
||||
|
@ -5,7 +5,6 @@ import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import com.oracle.truffle.api.profiles.ConditionProfile;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
@ -17,7 +16,6 @@ import org.enso.interpreter.runtime.Context;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
||||
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;
|
||||
|
@ -41,10 +41,9 @@ public abstract class IndirectArgumentSorterNode extends Node {
|
||||
Object state,
|
||||
ThunkExecutorNode thunkExecutorNode) {
|
||||
for (int i = 0; i < mapping.getArgumentShouldExecute().length; i++) {
|
||||
if (TypesGen.isThunk(arguments[i]) && mapping.getArgumentShouldExecute()[i]) {
|
||||
if (mapping.getArgumentShouldExecute()[i]) {
|
||||
Stateful result =
|
||||
thunkExecutorNode.executeThunk(
|
||||
TypesGen.asThunk(arguments[i]), state, BaseNode.TailStatus.NOT_TAIL);
|
||||
thunkExecutorNode.executeThunk(arguments[i], state, BaseNode.TailStatus.NOT_TAIL);
|
||||
arguments[i] = result.getValue();
|
||||
state = result.getState();
|
||||
}
|
||||
|
@ -45,17 +45,17 @@ public class ReadArgumentNode extends ExpressionNode {
|
||||
*/
|
||||
@Override
|
||||
public Object executeGeneric(VirtualFrame frame) {
|
||||
Object argument = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())[index];
|
||||
Object arguments[] = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments());
|
||||
|
||||
if (defaultValue == null) {
|
||||
return argument;
|
||||
return arguments[index];
|
||||
}
|
||||
|
||||
// Note [Handling Argument Defaults]
|
||||
if (defaultingProfile.profile(argument == null)) {
|
||||
if (defaultingProfile.profile(arguments.length <= index || arguments[index] == null)) {
|
||||
return defaultValue.executeGeneric(frame);
|
||||
} else {
|
||||
return argument;
|
||||
return arguments[index];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import com.oracle.truffle.api.RootCallTarget;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import org.enso.interpreter.node.ExpressionNode;
|
||||
import org.enso.interpreter.runtime.callable.argument.Thunk;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
|
||||
/** This node is responsible for wrapping a call target in a {@link Thunk} at execution time. */
|
||||
@NodeInfo(shortName = "CreateThunk", description = "Wraps a call target in a thunk at runtime")
|
||||
@ -34,6 +34,6 @@ public class CreateThunkNode extends ExpressionNode {
|
||||
*/
|
||||
@Override
|
||||
public Object executeGeneric(VirtualFrame frame) {
|
||||
return new Thunk(this.callTarget, frame.materialize());
|
||||
return Function.thunk(this.callTarget, frame.materialize());
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import com.oracle.truffle.api.frame.FrameUtil;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import org.enso.interpreter.node.ExpressionNode;
|
||||
import org.enso.interpreter.runtime.callable.argument.Thunk;
|
||||
import org.enso.interpreter.runtime.state.Stateful;
|
||||
|
||||
/** Node responsible for handling user-requested thunks forcing. */
|
||||
|
@ -7,12 +7,14 @@ import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.Constants;
|
||||
import org.enso.interpreter.node.BaseNode;
|
||||
import org.enso.interpreter.node.callable.InvokeCallableNode;
|
||||
import org.enso.interpreter.node.callable.dispatch.IndirectInvokeFunctionNode;
|
||||
import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode;
|
||||
import org.enso.interpreter.node.callable.dispatch.LoopingCallOptimiserNode;
|
||||
import org.enso.interpreter.runtime.callable.argument.Thunk;
|
||||
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
|
||||
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
|
||||
@ -40,30 +42,25 @@ public abstract class ThunkExecutorNode extends Node {
|
||||
*/
|
||||
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);
|
||||
boolean sameCallTarget(DirectCallNode callNode, Function function) {
|
||||
return function.getCallTarget() == callNode.getCallTarget();
|
||||
}
|
||||
|
||||
@Specialization(
|
||||
guards = "callNode.getCallTarget() == thunk.getCallTarget()",
|
||||
guards = {"function.isThunk()", "sameCallTarget(callNode, function)"},
|
||||
limit = Constants.CacheSizes.THUNK_EXECUTOR_NODE)
|
||||
Stateful doCached(
|
||||
Thunk thunk,
|
||||
Function function,
|
||||
Object state,
|
||||
BaseNode.TailStatus isTail,
|
||||
@Cached("create(thunk.getCallTarget())") DirectCallNode callNode,
|
||||
@Cached("create(function.getCallTarget())") DirectCallNode callNode,
|
||||
@Cached LoopingCallOptimiserNode loopingCallOptimiserNode) {
|
||||
CompilerAsserts.partialEvaluationConstant(isTail);
|
||||
if (isTail != BaseNode.TailStatus.NOT_TAIL) {
|
||||
return (Stateful) callNode.call(Function.ArgumentsHelper.buildArguments(thunk, state));
|
||||
return (Stateful) callNode.call(Function.ArgumentsHelper.buildArguments(function, state));
|
||||
} else {
|
||||
try {
|
||||
return (Stateful) callNode.call(Function.ArgumentsHelper.buildArguments(thunk, state));
|
||||
return (Stateful) callNode.call(Function.ArgumentsHelper.buildArguments(function, state));
|
||||
} catch (TailCallException e) {
|
||||
return loopingCallOptimiserNode.executeDispatch(
|
||||
e.getFunction(), e.getCallerInfo(), e.getState(), e.getArguments());
|
||||
@ -71,9 +68,9 @@ public abstract class ThunkExecutorNode extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(replaces = "doCached")
|
||||
@Specialization(replaces = "doCached", guards = "function.isThunk()")
|
||||
Stateful doUncached(
|
||||
Thunk thunk,
|
||||
Function function,
|
||||
Object state,
|
||||
BaseNode.TailStatus isTail,
|
||||
@Cached IndirectCallNode callNode,
|
||||
@ -81,16 +78,66 @@ public abstract class ThunkExecutorNode extends Node {
|
||||
if (isTail != BaseNode.TailStatus.NOT_TAIL) {
|
||||
return (Stateful)
|
||||
callNode.call(
|
||||
thunk.getCallTarget(), Function.ArgumentsHelper.buildArguments(thunk, state));
|
||||
function.getCallTarget(), Function.ArgumentsHelper.buildArguments(function, state));
|
||||
} else {
|
||||
try {
|
||||
return (Stateful)
|
||||
callNode.call(
|
||||
thunk.getCallTarget(), Function.ArgumentsHelper.buildArguments(thunk, state));
|
||||
function.getCallTarget(), Function.ArgumentsHelper.buildArguments(function, state));
|
||||
} catch (TailCallException e) {
|
||||
return loopingCallOptimiserNode.executeDispatch(
|
||||
e.getFunction(), e.getCallerInfo(), e.getState(), e.getArguments());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static InvokeFunctionNode buildInvokeFunctionNode(BaseNode.TailStatus tailStatus) {
|
||||
var node =
|
||||
InvokeFunctionNode.build(
|
||||
new CallArgumentInfo[0],
|
||||
InvokeCallableNode.DefaultsExecutionMode.EXECUTE,
|
||||
InvokeCallableNode.ArgumentsExecutionMode.EXECUTE);
|
||||
node.setTailStatus(tailStatus);
|
||||
return node;
|
||||
}
|
||||
|
||||
static int numberOfTailStatuses() {
|
||||
return BaseNode.TailStatus.numberOfValues();
|
||||
}
|
||||
|
||||
@Specialization(
|
||||
guards = {"!fn.isThunk()", "fn.isFullyApplied()", "isTail == cachedIsTail"},
|
||||
limit = "numberOfTailStatuses()")
|
||||
Stateful doCachedFn(
|
||||
Function fn,
|
||||
Object state,
|
||||
BaseNode.TailStatus isTail,
|
||||
@Cached("isTail") BaseNode.TailStatus cachedIsTail,
|
||||
@Cached("buildInvokeFunctionNode(cachedIsTail)") InvokeFunctionNode invokeFunctionNode) {
|
||||
return invokeFunctionNode.execute(fn, null, state, new Object[0]);
|
||||
}
|
||||
|
||||
@Specialization(
|
||||
guards = {"!fn.isThunk()", "fn.isFullyApplied()"},
|
||||
replaces = {"doCachedFn"})
|
||||
Stateful doUncachedFn(
|
||||
Function fn,
|
||||
Object state,
|
||||
BaseNode.TailStatus isTail,
|
||||
@Cached IndirectInvokeFunctionNode invokeFunctionNode) {
|
||||
return invokeFunctionNode.execute(
|
||||
fn,
|
||||
null,
|
||||
state,
|
||||
new Object[0],
|
||||
new CallArgumentInfo[0],
|
||||
InvokeCallableNode.DefaultsExecutionMode.EXECUTE,
|
||||
InvokeCallableNode.ArgumentsExecutionMode.EXECUTE,
|
||||
isTail);
|
||||
}
|
||||
|
||||
@Fallback
|
||||
Stateful doOther(Object thunk, Object state, BaseNode.TailStatus isTail) {
|
||||
return new Stateful(state, thunk);
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ 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;
|
||||
import org.enso.interpreter.runtime.state.Stateful;
|
||||
|
||||
@BuiltinMethod(
|
||||
|
@ -8,7 +8,6 @@ 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;
|
||||
import org.enso.interpreter.runtime.callable.argument.Thunk;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.state.Stateful;
|
||||
|
||||
|
@ -1,32 +0,0 @@
|
||||
package org.enso.interpreter.node.expression.builtin.function;
|
||||
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.dsl.MonadicState;
|
||||
import org.enso.interpreter.node.BaseNode;
|
||||
import org.enso.interpreter.node.callable.InvokeCallableNode;
|
||||
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.state.Stateful;
|
||||
|
||||
@BuiltinMethod(
|
||||
type = "Function",
|
||||
name = "call",
|
||||
description = "Allows function calls to be made explicitly")
|
||||
public class ExplicitCallFunctionNode extends Node {
|
||||
private @Child InvokeCallableNode invokeCallableNode;
|
||||
|
||||
ExplicitCallFunctionNode() {
|
||||
invokeCallableNode =
|
||||
InvokeCallableNode.build(
|
||||
new CallArgumentInfo[0],
|
||||
InvokeCallableNode.DefaultsExecutionMode.EXECUTE,
|
||||
InvokeCallableNode.ArgumentsExecutionMode.PRE_EXECUTED);
|
||||
invokeCallableNode.setTailStatus(BaseNode.TailStatus.TAIL_DIRECT);
|
||||
}
|
||||
|
||||
Stateful execute(VirtualFrame frame, @MonadicState Object state, Function _this) {
|
||||
return invokeCallableNode.execute(_this, frame, state, new Object[0]);
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@ import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
|
||||
import org.enso.interpreter.runtime.data.Array;
|
||||
import org.enso.interpreter.runtime.type.TypesGen;
|
||||
|
||||
@BuiltinMethod(
|
||||
type = "Meta",
|
||||
|
@ -7,7 +7,6 @@ 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;
|
||||
import org.enso.interpreter.runtime.state.Stateful;
|
||||
|
||||
@BuiltinMethod(
|
||||
|
@ -6,7 +6,6 @@ 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;
|
||||
import org.enso.interpreter.runtime.control.ThreadInterruptedException;
|
||||
import org.enso.interpreter.runtime.state.Stateful;
|
||||
|
||||
|
@ -15,7 +15,7 @@ import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode;
|
||||
import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
|
||||
import org.enso.interpreter.runtime.Context;
|
||||
import org.enso.interpreter.runtime.callable.CallerInfo;
|
||||
import org.enso.interpreter.runtime.callable.argument.Thunk;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.data.text.Text;
|
||||
import org.enso.interpreter.runtime.scope.LocalScope;
|
||||
import org.enso.interpreter.runtime.scope.ModuleScope;
|
||||
@ -98,7 +98,7 @@ public abstract class EvalNode extends BaseNode {
|
||||
"parseExpression(callerInfo.getLocalScope(), callerInfo.getModuleScope(), expressionStr)")
|
||||
RootCallTarget cachedCallTarget,
|
||||
@Cached("build()") ThunkExecutorNode thunkExecutorNode) {
|
||||
Thunk thunk = new Thunk(cachedCallTarget, callerInfo.getFrame());
|
||||
Function thunk = Function.thunk(cachedCallTarget, callerInfo.getFrame());
|
||||
return thunkExecutorNode.executeThunk(thunk, state, getTailStatus());
|
||||
}
|
||||
|
||||
@ -114,7 +114,7 @@ public abstract class EvalNode extends BaseNode {
|
||||
callerInfo.getLocalScope(),
|
||||
callerInfo.getModuleScope(),
|
||||
toJavaStringNode.execute(expression));
|
||||
Thunk thunk = new Thunk(callTarget, callerInfo.getFrame());
|
||||
Function thunk = Function.thunk(callTarget, callerInfo.getFrame());
|
||||
return thunkExecutorNode.executeThunk(thunk, state, getTailStatus());
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import org.enso.interpreter.node.expression.builtin.error.CatchPanicMethodGen;
|
||||
import org.enso.interpreter.node.expression.builtin.error.CaughtPanicConvertToDataflowErrorMethodGen;
|
||||
import org.enso.interpreter.node.expression.builtin.error.GetAttachedStackTraceMethodGen;
|
||||
import org.enso.interpreter.node.expression.builtin.error.ThrowPanicMethodGen;
|
||||
import org.enso.interpreter.node.expression.builtin.function.ExplicitCallFunctionMethodGen;
|
||||
import org.enso.interpreter.node.expression.builtin.interop.java.AddToClassPathMethodGen;
|
||||
import org.enso.interpreter.node.expression.builtin.interop.java.LookupClassMethodGen;
|
||||
import org.enso.interpreter.node.expression.builtin.io.GetCwdMethodGen;
|
||||
@ -191,8 +190,6 @@ public class Builtins {
|
||||
scope.registerMethod(debug, MethodNames.Debug.EVAL, DebugEvalMethodGen.makeFunction(language));
|
||||
scope.registerMethod(debug, "breakpoint", DebugBreakpointMethodGen.makeFunction(language));
|
||||
|
||||
scope.registerMethod(function, "call", ExplicitCallFunctionMethodGen.makeFunction(language));
|
||||
|
||||
scope.registerMethod(any, "to_text", AnyToTextMethodGen.makeFunction(language));
|
||||
scope.registerMethod(any, "to_display_text", AnyToDisplayTextMethodGen.makeFunction(language));
|
||||
|
||||
|
@ -4,7 +4,7 @@ import java.util.Optional;
|
||||
import org.enso.interpreter.node.ExpressionNode;
|
||||
|
||||
/** Tracks the specifics about how arguments are defined at the callable definition site. */
|
||||
public class ArgumentDefinition {
|
||||
public final class ArgumentDefinition {
|
||||
|
||||
/** Represents the mode of passing this argument to the function. */
|
||||
public enum ExecutionMode {
|
||||
|
@ -3,7 +3,7 @@ package org.enso.interpreter.runtime.callable.argument;
|
||||
import org.enso.interpreter.node.ExpressionNode;
|
||||
|
||||
/** Tracks the specifics about how arguments are specified at a call site. */
|
||||
public class CallArgument {
|
||||
public final class CallArgument {
|
||||
private final String name;
|
||||
private final ExpressionNode expression;
|
||||
|
||||
|
@ -13,7 +13,7 @@ import java.util.stream.IntStream;
|
||||
* Tracks simple information about call-site arguments, used to make processing of caller argument
|
||||
* lists much more simple.
|
||||
*/
|
||||
public class CallArgumentInfo {
|
||||
public final class CallArgumentInfo {
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
@ -228,10 +228,10 @@ public class CallArgumentInfo {
|
||||
* A class that represents the partitioned mapping of the arguments applied to a given callable.
|
||||
*/
|
||||
public static class ArgumentMapping {
|
||||
private @CompilationFinal(dimensions = 1) int[] appliedArgumentMapping;
|
||||
private @CompilationFinal(dimensions = 1) int[] oversaturatedArgumentMapping;
|
||||
private @CompilationFinal(dimensions = 1) boolean[] isValidAppliedArg;
|
||||
private @CompilationFinal(dimensions = 1) boolean[] argumentShouldExecute;
|
||||
private final @CompilationFinal(dimensions = 1) int[] appliedArgumentMapping;
|
||||
private final @CompilationFinal(dimensions = 1) int[] oversaturatedArgumentMapping;
|
||||
private final @CompilationFinal(dimensions = 1) boolean[] isValidAppliedArg;
|
||||
private final @CompilationFinal(dimensions = 1) boolean[] argumentShouldExecute;
|
||||
private final FunctionSchema postApplicationSchema;
|
||||
|
||||
/**
|
||||
@ -245,7 +245,7 @@ public class CallArgumentInfo {
|
||||
* the callable
|
||||
* @param postApplicationSchema the schema resulting from applying this mapping
|
||||
*/
|
||||
public ArgumentMapping(
|
||||
private ArgumentMapping(
|
||||
int[] appliedArgumentMapping,
|
||||
int[] oversaturatedArgumentMapping,
|
||||
boolean[] isAppliedFlags,
|
||||
|
@ -1,40 +0,0 @@
|
||||
package org.enso.interpreter.runtime.callable.argument;
|
||||
|
||||
import com.oracle.truffle.api.CallTarget;
|
||||
import com.oracle.truffle.api.RootCallTarget;
|
||||
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||
|
||||
/** Runtime representation of a suspended function argument. */
|
||||
public class Thunk {
|
||||
private final RootCallTarget callTarget;
|
||||
private final MaterializedFrame scope;
|
||||
|
||||
/**
|
||||
* Creates a runtime thunk.
|
||||
*
|
||||
* @param callTarget the {@link CallTarget} representing the argument's expression
|
||||
* @param scope the caller scope used for evaluating the {@code callTarget}
|
||||
*/
|
||||
public Thunk(RootCallTarget callTarget, MaterializedFrame scope) {
|
||||
this.callTarget = callTarget;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the call target representing the argument's expression.
|
||||
*
|
||||
* @return the call target representing the argument's expression.
|
||||
*/
|
||||
public CallTarget getCallTarget() {
|
||||
return callTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the caller scope.
|
||||
*
|
||||
* @return the caller scope used for evaluating this thunk.
|
||||
*/
|
||||
public MaterializedFrame getScope() {
|
||||
return scope;
|
||||
}
|
||||
}
|
@ -24,7 +24,6 @@ import org.enso.interpreter.runtime.callable.CallerInfo;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
|
||||
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
|
||||
import org.enso.interpreter.runtime.callable.argument.Thunk;
|
||||
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
|
||||
import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary;
|
||||
import org.enso.interpreter.runtime.state.data.EmptyMap;
|
||||
@ -79,6 +78,10 @@ public final class Function implements TruffleObject {
|
||||
this(callTarget, scope, schema, null, null);
|
||||
}
|
||||
|
||||
public static Function thunk(RootCallTarget callTarget, MaterializedFrame scope) {
|
||||
return new Function(callTarget, scope, FunctionSchema.THUNK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Function object from a {@link BuiltinRootNode} and argument definitions.
|
||||
*
|
||||
@ -308,7 +311,7 @@ public final class Function implements TruffleObject {
|
||||
* @param state the state to execute the thunk with
|
||||
* @return an array containing the necessary information to call an Enso thunk
|
||||
*/
|
||||
public static Object[] buildArguments(Thunk thunk, Object state) {
|
||||
public static Object[] buildArguments(Function thunk, Object state) {
|
||||
return new Object[] {thunk.getScope(), null, state, new Object[0]};
|
||||
}
|
||||
|
||||
@ -456,4 +459,12 @@ public final class Function implements TruffleObject {
|
||||
return function;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isThunk() {
|
||||
return schema == FunctionSchema.THUNK;
|
||||
}
|
||||
|
||||
public boolean isFullyApplied() {
|
||||
return schema.isFullyApplied();
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
|
||||
* Holds the definition site argument information together with information on the partially applied
|
||||
* arguments positions.
|
||||
*/
|
||||
public class FunctionSchema {
|
||||
public final class FunctionSchema {
|
||||
/** Denotes the caller frame access functions with this schema require to run properly. */
|
||||
public enum CallerFrameAccess {
|
||||
/** Requires full access to the (materialized) caller frame. */
|
||||
@ -29,6 +29,8 @@ public class FunctionSchema {
|
||||
}
|
||||
}
|
||||
|
||||
public static final FunctionSchema THUNK = new FunctionSchema();
|
||||
|
||||
private final @CompilationFinal(dimensions = 1) ArgumentDefinition[] argumentInfos;
|
||||
private final @CompilationFinal(dimensions = 1) boolean[] hasPreApplied;
|
||||
private final @CompilationFinal(dimensions = 1) CallArgumentInfo[] oversaturatedArguments;
|
||||
@ -36,6 +38,8 @@ public class FunctionSchema {
|
||||
private final boolean hasOversaturatedArguments;
|
||||
private final CallerFrameAccess callerFrameAccess;
|
||||
|
||||
private final boolean isFullyApplied;
|
||||
|
||||
/**
|
||||
* Creates an {@link FunctionSchema} instance.
|
||||
*
|
||||
@ -66,6 +70,7 @@ public class FunctionSchema {
|
||||
|
||||
this.hasAnyPreApplied = hasAnyPreApplied;
|
||||
this.hasOversaturatedArguments = this.oversaturatedArguments.length > 0;
|
||||
this.isFullyApplied = isFullyApplied(InvokeCallableNode.DefaultsExecutionMode.EXECUTE);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -211,4 +216,8 @@ public class FunctionSchema {
|
||||
}
|
||||
return functionIsFullyApplied;
|
||||
}
|
||||
|
||||
public boolean isFullyApplied() {
|
||||
return isFullyApplied;
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,8 @@ package org.enso.interpreter.runtime.type;
|
||||
import com.oracle.truffle.api.dsl.TypeSystem;
|
||||
import com.oracle.truffle.api.interop.ArityException;
|
||||
import com.oracle.truffle.api.interop.UnsupportedTypeException;
|
||||
import org.enso.interpreter.runtime.Context;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
|
||||
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;
|
||||
@ -40,7 +38,6 @@ import org.enso.polyglot.data.TypeGraph;
|
||||
Function.class,
|
||||
Atom.class,
|
||||
AtomConstructor.class,
|
||||
Thunk.class,
|
||||
DataflowError.class,
|
||||
UnresolvedConversion.class,
|
||||
UnresolvedSymbol.class,
|
||||
@ -124,8 +121,6 @@ public class Types {
|
||||
return TypesGen.asAtom(value).getConstructor().getQualifiedName().toString();
|
||||
} else if (TypesGen.isAtomConstructor(value)) {
|
||||
return TypesGen.asAtomConstructor(value).getQualifiedName().toString();
|
||||
} else if (TypesGen.isThunk(value)) {
|
||||
return Constants.THUNK;
|
||||
} else if (TypesGen.isDataflowError(value)) {
|
||||
return Constants.ERROR;
|
||||
} else if (TypesGen.isUnresolvedSymbol(value) || TypesGen.isUnresolvedConversion(value)) {
|
||||
|
@ -470,23 +470,6 @@ type Function
|
||||
@Builtin_Type
|
||||
type Function
|
||||
|
||||
## Forces evaluation of a fully-applied but not evaluated function.
|
||||
|
||||
This is particularly useful when you want to call a function that is
|
||||
fully applied with default arguments.
|
||||
|
||||
This is usually _not_ the correct solution to a problem, and so should be
|
||||
used sparingly.
|
||||
|
||||
> Example
|
||||
Evaluate a fully applied function to get 4.
|
||||
|
||||
example_call =
|
||||
f (a = 2) = a * a
|
||||
f.call
|
||||
call : Any
|
||||
call = @Builtin_Method "Function.call"
|
||||
|
||||
## Generic utilities for interacting with other languages.
|
||||
type Polyglot
|
||||
|
||||
|
@ -1316,6 +1316,8 @@ class IrToTruffle(
|
||||
*/
|
||||
def processApplication(application: IR.Application): RuntimeExpression =
|
||||
application match {
|
||||
case IR.Application.Prefix(fn, Nil, true, _, _, _) =>
|
||||
run(fn)
|
||||
case IR.Application.Prefix(fn, args, hasDefaultsSuspended, loc, _, _) =>
|
||||
val callArgFactory = new CallArgumentProcessor(scope, scopeName)
|
||||
|
||||
@ -1407,7 +1409,6 @@ class IrToTruffle(
|
||||
name,
|
||||
value,
|
||||
_,
|
||||
shouldBeSuspended,
|
||||
_,
|
||||
_
|
||||
) =>
|
||||
@ -1422,12 +1423,7 @@ class IrToTruffle(
|
||||
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."
|
||||
)
|
||||
)
|
||||
case _ => true
|
||||
}
|
||||
|
||||
val childScope = if (shouldSuspend) {
|
||||
|
@ -5310,15 +5310,6 @@ object IR {
|
||||
/** The expression of the argument, if present. */
|
||||
val value: Expression
|
||||
|
||||
/** Whether or not the argument should be suspended at code generation time.
|
||||
*
|
||||
* A value of `Some(true)` implies that code generation should suspend the
|
||||
* argument. A value of `Some(false)` implies that code generation should
|
||||
* _not_ suspend the argument. A value of [[None]] implies that the
|
||||
* information is missing.
|
||||
*/
|
||||
val shouldBeSuspended: Option[Boolean]
|
||||
|
||||
/** @inheritdoc */
|
||||
override def mapExpressions(fn: Expression => Expression): CallArgument
|
||||
|
||||
@ -5340,21 +5331,18 @@ object IR {
|
||||
* A [[CallArgument]] where the `value` is an [[IR.Name.Blank]] is a
|
||||
* representation of a lambda shorthand argument.
|
||||
*
|
||||
* @param name the name of the argument being called, if present
|
||||
* @param value the expression being passed as the argument's value
|
||||
* @param location the source location that the node corresponds to
|
||||
* @param shouldBeSuspended whether or not the argument should be passed
|
||||
* suspended
|
||||
* @param passData the pass metadata associated with this node
|
||||
* @param name the name of the argument being called, if present
|
||||
* @param value the expression being passed as the argument's value
|
||||
* @param location the source location that the node corresponds to
|
||||
* @param passData the pass metadata associated with this node
|
||||
* @param diagnostics compiler diagnostics for this node
|
||||
*/
|
||||
sealed case class Specified(
|
||||
override val name: Option[IR.Name],
|
||||
override val value: Expression,
|
||||
override val location: Option[IdentifiedLocation],
|
||||
override val shouldBeSuspended: Option[Boolean] = None,
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
) extends CallArgument
|
||||
with IRKind.Primitive {
|
||||
override protected var id: Identifier = randomId
|
||||
@ -5375,7 +5363,6 @@ object IR {
|
||||
name: Option[IR.Name] = name,
|
||||
value: Expression = value,
|
||||
location: Option[IdentifiedLocation] = location,
|
||||
shouldBeSuspended: Option[Boolean] = shouldBeSuspended,
|
||||
passData: MetadataStorage = passData,
|
||||
diagnostics: DiagnosticStorage = diagnostics,
|
||||
id: Identifier = id
|
||||
@ -5384,7 +5371,6 @@ object IR {
|
||||
name,
|
||||
value,
|
||||
location,
|
||||
shouldBeSuspended,
|
||||
passData,
|
||||
diagnostics
|
||||
)
|
||||
@ -5439,7 +5425,6 @@ object IR {
|
||||
|name = $name,
|
||||
|value = $value,
|
||||
|location = $location,
|
||||
|shouldBeSuspended = $shouldBeSuspended,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|
@ -493,7 +493,7 @@ case object AliasAnalysis extends IRPass {
|
||||
graph: AliasAnalysis.Graph,
|
||||
parentScope: AliasAnalysis.Graph.Scope
|
||||
): List[IR.CallArgument] = {
|
||||
args.map { case arg @ IR.CallArgument.Specified(_, expr, _, _, _, _) =>
|
||||
args.map { case arg @ IR.CallArgument.Specified(_, expr, _, _, _) =>
|
||||
val currentScope = expr match {
|
||||
case _: IR.Literal => parentScope
|
||||
case _ => parentScope.addChild()
|
||||
|
@ -692,7 +692,7 @@ case object DataflowAnalysis extends IRPass {
|
||||
info: DependencyInfo
|
||||
): IR.CallArgument = {
|
||||
argument match {
|
||||
case spec @ IR.CallArgument.Specified(name, value, _, _, _, _) =>
|
||||
case spec @ IR.CallArgument.Specified(name, value, _, _, _) =>
|
||||
val specDep = asStatic(spec)
|
||||
val valueDep = asStatic(value)
|
||||
info.dependents.updateAt(valueDep, Set(specDep))
|
||||
|
@ -169,51 +169,31 @@ case object DemandAnalysis extends IRPass {
|
||||
name: IR.Name,
|
||||
isInsideCallArgument: Boolean
|
||||
): IR.Expression = {
|
||||
val usesLazyTerm = isUsageOfSuspendedTerm(name)
|
||||
|
||||
if (isInsideCallArgument) {
|
||||
name
|
||||
} else {
|
||||
if (usesLazyTerm) {
|
||||
val forceLocation = name.location
|
||||
val newNameLocation = name.location.map(l => l.copy(id = None))
|
||||
|
||||
val newName = name match {
|
||||
case lit: IR.Name.Literal => lit.copy(location = newNameLocation)
|
||||
case ths: IR.Name.This => ths.copy(location = newNameLocation)
|
||||
case here: IR.Name.Here => here.copy(location = newNameLocation)
|
||||
case special: IR.Name.Special =>
|
||||
special.copy(location = newNameLocation)
|
||||
case _: IR.Name.Annotation =>
|
||||
throw new CompilerError(
|
||||
"Annotations should not be present by the time demand analysis" +
|
||||
" runs."
|
||||
)
|
||||
case _: IR.Name.MethodReference =>
|
||||
throw new CompilerError(
|
||||
"Method references should not be present by the time demand " +
|
||||
"analysis runs."
|
||||
)
|
||||
case _: IR.Name.Qualified =>
|
||||
throw new CompilerError(
|
||||
"Qualified names should not be present by the time demand " +
|
||||
"analysis runs."
|
||||
)
|
||||
case err: IR.Error.Resolution => err
|
||||
case err: IR.Error.Conversion => err
|
||||
case _: IR.Name.Blank =>
|
||||
throw new CompilerError(
|
||||
"Blanks should not be present by the time demand analysis runs."
|
||||
)
|
||||
}
|
||||
|
||||
IR.Application.Force(newName, forceLocation)
|
||||
} else {
|
||||
name
|
||||
name match {
|
||||
case lit: IR.Name.Literal if isDefined(lit) =>
|
||||
val forceLocation = name.location
|
||||
val newNameLocation = name.location.map(l => l.copy(id = None))
|
||||
val newName = lit.copy(location = newNameLocation)
|
||||
IR.Application.Force(newName, forceLocation)
|
||||
case _ => name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def isDefined(name: IR.Name): Boolean = {
|
||||
val aliasInfo = name
|
||||
.unsafeGetMetadata(
|
||||
AliasAnalysis,
|
||||
"Missing alias occurrence information for a name usage"
|
||||
)
|
||||
.unsafeAs[AliasAnalysis.Info.Occurrence]
|
||||
|
||||
aliasInfo.graph.defLinkFor(aliasInfo.id).isDefined
|
||||
}
|
||||
|
||||
/** Performs demand analysis on an application.
|
||||
*
|
||||
* @param application the function application to perform demand analysis on
|
||||
@ -227,11 +207,12 @@ case object DemandAnalysis extends IRPass {
|
||||
): IR.Application =
|
||||
application match {
|
||||
case pref @ IR.Application.Prefix(fn, args, _, _, _, _) =>
|
||||
val newFun = fn match {
|
||||
case n: IR.Name => n
|
||||
case e => analyseExpression(e, isInsideCallArgument = false)
|
||||
}
|
||||
pref.copy(
|
||||
function = analyseExpression(
|
||||
fn,
|
||||
isInsideCallArgument = false
|
||||
),
|
||||
function = newFun,
|
||||
arguments = args.map(analyseCallArgument)
|
||||
)
|
||||
case force @ IR.Application.Force(target, _, _, _) =>
|
||||
@ -261,43 +242,6 @@ case object DemandAnalysis extends IRPass {
|
||||
)
|
||||
}
|
||||
|
||||
/** Determines whether a particular piece of IR represents the usage of a
|
||||
* suspended term (and hence requires forcing).
|
||||
*
|
||||
* @param expr the expression to check
|
||||
* @return `true` if `expr` represents the usage of a suspended term, `false`
|
||||
* otherwise
|
||||
*/
|
||||
def isUsageOfSuspendedTerm(expr: IR.Expression): Boolean = {
|
||||
expr match {
|
||||
case name: IR.Name =>
|
||||
val aliasInfo = name
|
||||
.unsafeGetMetadata(
|
||||
AliasAnalysis,
|
||||
"Missing alias occurrence information for a name usage"
|
||||
)
|
||||
.unsafeAs[AliasAnalysis.Info.Occurrence]
|
||||
|
||||
aliasInfo.graph
|
||||
.defLinkFor(aliasInfo.id)
|
||||
.flatMap(link => {
|
||||
aliasInfo.graph
|
||||
.getOccurrence(link.target)
|
||||
.getOrElse(
|
||||
throw new CompilerError(
|
||||
s"Malformed aliasing link with target ${link.target}"
|
||||
)
|
||||
) match {
|
||||
case AliasAnalysis.Graph.Occurrence.Def(_, _, _, _, isLazy) =>
|
||||
if (isLazy) Some(true) else None
|
||||
case _ => None
|
||||
}
|
||||
})
|
||||
.isDefined
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
/** Performs demand analysis on a function call argument.
|
||||
*
|
||||
* In keeping with the requirement by the runtime to pass all function
|
||||
@ -309,13 +253,12 @@ case object DemandAnalysis extends IRPass {
|
||||
*/
|
||||
def analyseCallArgument(arg: IR.CallArgument): IR.CallArgument = {
|
||||
arg match {
|
||||
case spec @ IR.CallArgument.Specified(_, expr, _, _, _, _) =>
|
||||
case spec @ IR.CallArgument.Specified(_, expr, _, _, _) =>
|
||||
spec.copy(
|
||||
value = analyseExpression(
|
||||
expr,
|
||||
isInsideCallArgument = true
|
||||
),
|
||||
shouldBeSuspended = Some(!isUsageOfSuspendedTerm(expr))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -271,7 +271,7 @@ case object TailCall extends IRPass {
|
||||
*/
|
||||
def analyseCallArg(argument: IR.CallArgument): IR.CallArgument = {
|
||||
argument match {
|
||||
case arg @ IR.CallArgument.Specified(_, expr, _, _, _, _) =>
|
||||
case arg @ IR.CallArgument.Specified(_, expr, _, _, _) =>
|
||||
arg
|
||||
.copy(
|
||||
// Note [Call Argument Tail Position]
|
||||
|
@ -186,7 +186,7 @@ case object LambdaShorthandToLambda extends IRPass {
|
||||
args
|
||||
.zip(argIsUnderscore)
|
||||
.map(updateShorthandArg(_, freshNameSupply))
|
||||
.map { case s @ IR.CallArgument.Specified(_, value, _, _, _, _) =>
|
||||
.map { case s @ IR.CallArgument.Specified(_, value, _, _, _) =>
|
||||
s.copy(value = desugarExpression(value, freshNameSupply))
|
||||
}
|
||||
|
||||
@ -301,7 +301,7 @@ case object LambdaShorthandToLambda extends IRPass {
|
||||
* position is lambda shorthand, otherwise `false`
|
||||
*/
|
||||
def determineLambdaShorthand(args: List[IR.CallArgument]): List[Boolean] = {
|
||||
args.map { case IR.CallArgument.Specified(_, value, _, _, _, _) =>
|
||||
args.map { case IR.CallArgument.Specified(_, value, _, _, _) =>
|
||||
value match {
|
||||
case _: IR.Name.Blank => true
|
||||
case _ => false
|
||||
@ -325,7 +325,7 @@ case object LambdaShorthandToLambda extends IRPass {
|
||||
val isShorthand = argAndIsShorthand._2
|
||||
|
||||
arg match {
|
||||
case s @ IR.CallArgument.Specified(_, value, _, _, _, _) =>
|
||||
case s @ IR.CallArgument.Specified(_, value, _, _, _) =>
|
||||
if (isShorthand) {
|
||||
val newName = freshNameSupply
|
||||
.newName()
|
||||
@ -354,7 +354,7 @@ case object LambdaShorthandToLambda extends IRPass {
|
||||
): Option[IR.DefinitionArgument] = {
|
||||
if (isShorthand) {
|
||||
arg match {
|
||||
case IR.CallArgument.Specified(_, value, _, _, passData, diagnostics) =>
|
||||
case IR.CallArgument.Specified(_, value, _, passData, diagnostics) =>
|
||||
// Note [Safe Casting to IR.Name.Literal]
|
||||
val defArgName =
|
||||
IR.Name.Literal(
|
||||
|
@ -105,7 +105,7 @@ case object SectionsToBinOp extends IRPass {
|
||||
case Section.Left(arg, op, loc, passData, diagnostics) =>
|
||||
val rightArgName = freshNameSupply.newName()
|
||||
val rightCallArg =
|
||||
IR.CallArgument.Specified(None, rightArgName, None, None)
|
||||
IR.CallArgument.Specified(None, rightArgName, None)
|
||||
val rightDefArg = IR.DefinitionArgument.Specified(
|
||||
rightArgName.duplicate(),
|
||||
None,
|
||||
@ -117,7 +117,7 @@ case object SectionsToBinOp extends IRPass {
|
||||
if (arg.value.isInstanceOf[IR.Name.Blank]) {
|
||||
val leftArgName = freshNameSupply.newName()
|
||||
val leftCallArg =
|
||||
IR.CallArgument.Specified(None, leftArgName, None, None)
|
||||
IR.CallArgument.Specified(None, leftArgName, None)
|
||||
val leftDefArg = IR.DefinitionArgument.Specified(
|
||||
leftArgName.duplicate(),
|
||||
None,
|
||||
@ -165,7 +165,7 @@ case object SectionsToBinOp extends IRPass {
|
||||
case Section.Sides(op, loc, passData, diagnostics) =>
|
||||
val leftArgName = freshNameSupply.newName()
|
||||
val leftCallArg =
|
||||
IR.CallArgument.Specified(None, leftArgName, None, None)
|
||||
IR.CallArgument.Specified(None, leftArgName, None)
|
||||
val leftDefArg = IR.DefinitionArgument.Specified(
|
||||
leftArgName.duplicate(),
|
||||
None,
|
||||
@ -176,7 +176,7 @@ case object SectionsToBinOp extends IRPass {
|
||||
|
||||
val rightArgName = freshNameSupply.newName()
|
||||
val rightCallArg =
|
||||
IR.CallArgument.Specified(None, rightArgName, None, None)
|
||||
IR.CallArgument.Specified(None, rightArgName, None)
|
||||
val rightDefArg = IR.DefinitionArgument.Specified(
|
||||
rightArgName.duplicate(),
|
||||
None,
|
||||
@ -228,7 +228,7 @@ case object SectionsToBinOp extends IRPass {
|
||||
case Section.Right(op, arg, loc, passData, diagnostics) =>
|
||||
val leftArgName = freshNameSupply.newName()
|
||||
val leftCallArg =
|
||||
IR.CallArgument.Specified(None, leftArgName, None, None)
|
||||
IR.CallArgument.Specified(None, leftArgName, None)
|
||||
val leftDefArg =
|
||||
IR.DefinitionArgument.Specified(
|
||||
leftArgName.duplicate(),
|
||||
@ -242,7 +242,7 @@ case object SectionsToBinOp extends IRPass {
|
||||
// Note [Blanks in Sections]
|
||||
val rightArgName = freshNameSupply.newName()
|
||||
val rightCallArg =
|
||||
IR.CallArgument.Specified(None, rightArgName, None, None)
|
||||
IR.CallArgument.Specified(None, rightArgName, None)
|
||||
val rightDefArg = IR.DefinitionArgument.Specified(
|
||||
rightArgName.duplicate(),
|
||||
None,
|
||||
|
@ -196,7 +196,7 @@ case object TypeFunctions extends IRPass {
|
||||
*/
|
||||
def resolveCallArgument(arg: IR.CallArgument): IR.CallArgument = {
|
||||
arg match {
|
||||
case spec @ IR.CallArgument.Specified(_, value, _, _, _, _) =>
|
||||
case spec @ IR.CallArgument.Specified(_, value, _, _, _) =>
|
||||
spec.copy(
|
||||
value = resolveExpression(value)
|
||||
)
|
||||
@ -218,8 +218,8 @@ case object TypeFunctions extends IRPass {
|
||||
*/
|
||||
def isValidCallArg(arg: IR.CallArgument): Boolean = {
|
||||
arg match {
|
||||
case IR.CallArgument.Specified(name, _, _, susp, _, _) =>
|
||||
name.isEmpty && (susp.isEmpty || susp.get)
|
||||
case IR.CallArgument.Specified(name, _, _, _, _) =>
|
||||
name.isEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ case object VectorLiterals extends IRPass {
|
||||
vec.duplicate(),
|
||||
List(
|
||||
IR.CallArgument
|
||||
.Specified(None, trans.copy(location = None), None, None)
|
||||
.Specified(None, trans.copy(location = None), None)
|
||||
),
|
||||
false,
|
||||
trans.location
|
||||
|
@ -936,9 +936,8 @@ class DataflowAnalysisTest extends CompilerTest {
|
||||
fn.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified]
|
||||
val fnArgY =
|
||||
fn.arguments(1).asInstanceOf[IR.DefinitionArgument.Specified]
|
||||
val fnArgYDefault = fnArgY.defaultValue.get.asInstanceOf[IR.Name.Literal]
|
||||
val fnBody = fn.body.asInstanceOf[IR.Application.Prefix]
|
||||
val plusFn = fnBody.function.asInstanceOf[IR.Name.Literal]
|
||||
val fnBody = fn.body.asInstanceOf[IR.Application.Prefix]
|
||||
val plusFn = fnBody.function.asInstanceOf[IR.Name.Literal]
|
||||
val plusArgX =
|
||||
fnBody.arguments.head.asInstanceOf[IR.CallArgument.Specified]
|
||||
val plusArgXExpr = plusArgX.value.asInstanceOf[IR.Name.Literal]
|
||||
@ -947,16 +946,15 @@ class DataflowAnalysisTest extends CompilerTest {
|
||||
val plusArgYExpr = plusArgY.value.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
// Identifiers
|
||||
val fnId = mkStaticDep(fn.getId)
|
||||
val fnArgXId = mkStaticDep(fnArgX.getId)
|
||||
val fnArgYId = mkStaticDep(fnArgY.getId)
|
||||
val fnArgYDefaultId = mkStaticDep(fnArgYDefault.getId)
|
||||
val fnBodyId = mkStaticDep(fnBody.getId)
|
||||
val plusFnId = mkStaticDep(plusFn.getId)
|
||||
val plusArgXId = mkStaticDep(plusArgX.getId)
|
||||
val plusArgXExprId = mkStaticDep(plusArgXExpr.getId)
|
||||
val plusArgYId = mkStaticDep(plusArgY.getId)
|
||||
val plusArgYExprId = mkStaticDep(plusArgYExpr.getId)
|
||||
val fnId = mkStaticDep(fn.getId)
|
||||
val fnArgXId = mkStaticDep(fnArgX.getId)
|
||||
val fnArgYId = mkStaticDep(fnArgY.getId)
|
||||
val fnBodyId = mkStaticDep(fnBody.getId)
|
||||
val plusFnId = mkStaticDep(plusFn.getId)
|
||||
val plusArgXId = mkStaticDep(plusArgX.getId)
|
||||
val plusArgXExprId = mkStaticDep(plusArgXExpr.getId)
|
||||
val plusArgYId = mkStaticDep(plusArgY.getId)
|
||||
val plusArgYExprId = mkStaticDep(plusArgYExpr.getId)
|
||||
|
||||
// Dynamic Symbols
|
||||
val plusSym = mkDynamicDep("+")
|
||||
@ -967,11 +965,7 @@ class DataflowAnalysisTest extends CompilerTest {
|
||||
|
||||
// The Tests for dependents
|
||||
dependents.getDirect(fnId) should not be defined
|
||||
dependents.getDirect(fnArgXId) shouldEqual Some(
|
||||
Set(plusArgXExprId, fnArgYDefaultId)
|
||||
)
|
||||
dependents.getDirect(fnArgYId) shouldEqual Some(Set(plusArgYExprId))
|
||||
dependents.getDirect(fnArgYDefaultId) shouldEqual Some(Set(fnArgYId))
|
||||
dependents.getDirect(fnBodyId) shouldEqual Some(Set(fnId))
|
||||
dependents.getDirect(plusSym) shouldEqual Some(Set(plusFnId))
|
||||
dependents.getDirect(plusArgXId) shouldEqual Some(Set(fnBodyId))
|
||||
@ -982,8 +976,6 @@ class DataflowAnalysisTest extends CompilerTest {
|
||||
// The Tests for dependencies
|
||||
dependencies.getDirect(fnId) shouldEqual Some(Set(fnBodyId))
|
||||
dependencies.getDirect(fnArgXId) shouldEqual None
|
||||
dependencies.getDirect(fnArgYId) shouldEqual Some(Set(fnArgYDefaultId))
|
||||
dependencies.getDirect(fnArgYDefaultId) shouldEqual Some(Set(fnArgXId))
|
||||
dependencies.getDirect(fnBodyId) shouldEqual Some(
|
||||
Set(plusFnId, plusArgXId, plusArgYId)
|
||||
)
|
||||
@ -1100,25 +1092,15 @@ class DataflowAnalysisTest extends CompilerTest {
|
||||
val lam = ir.asInstanceOf[IR.Function.Lambda]
|
||||
val argX =
|
||||
lam.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified]
|
||||
val xUse = lam.body.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
// The IDs
|
||||
val lamId = mkStaticDep(lam.getId)
|
||||
val argXId = mkStaticDep(argX.getId)
|
||||
val xUseId = mkStaticDep(xUse.getId)
|
||||
|
||||
// The info
|
||||
val dependents = depInfo.dependents
|
||||
val dependencies = depInfo.dependencies
|
||||
|
||||
// The test for dependents
|
||||
dependents.getDirect(argXId) shouldEqual Some(Set(xUseId))
|
||||
dependents.getDirect(xUseId) shouldEqual Some(Set(lamId))
|
||||
|
||||
// The test for dependencies
|
||||
dependencies.getDirect(argXId) shouldEqual None
|
||||
dependencies.getDirect(xUseId) shouldEqual Some(Set(argXId))
|
||||
dependencies.getDirect(lamId) shouldEqual Some(Set(xUseId))
|
||||
}
|
||||
|
||||
"work properly for blocks" in {
|
||||
@ -1136,14 +1118,12 @@ class DataflowAnalysisTest extends CompilerTest {
|
||||
val xBind = block.expressions.head.asInstanceOf[IR.Expression.Binding]
|
||||
val xBindName = xBind.name.asInstanceOf[IR.Name.Literal]
|
||||
val xBindExpr = xBind.expression.asInstanceOf[IR.Literal.Number]
|
||||
val xUse = block.returnValue.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
// The IDs
|
||||
val blockId = mkStaticDep(block.getId)
|
||||
val xBindId = mkStaticDep(xBind.getId)
|
||||
val xBindNameId = mkStaticDep(xBindName.getId)
|
||||
val xBindExprId = mkStaticDep(xBindExpr.getId)
|
||||
val xUseId = mkStaticDep(xUse.getId)
|
||||
|
||||
// The info
|
||||
val dependents = depInfo.dependents
|
||||
@ -1151,14 +1131,10 @@ class DataflowAnalysisTest extends CompilerTest {
|
||||
|
||||
// The test for dependents
|
||||
dependents.getDirect(blockId) should not be defined
|
||||
dependents.getDirect(xBindId) shouldEqual Some(Set(xUseId))
|
||||
dependents.getDirect(xBindNameId) shouldEqual Some(Set(xBindId))
|
||||
dependents.getDirect(xBindExprId) shouldEqual Some(Set(xBindId))
|
||||
dependents.getDirect(xUseId) shouldEqual Some(Set(blockId))
|
||||
|
||||
// The test for dependencies
|
||||
dependencies.getDirect(blockId) shouldEqual Some(Set(xUseId))
|
||||
dependencies.getDirect(xUseId) shouldEqual Some(Set(xBindId))
|
||||
dependencies.getDirect(xBindId) shouldEqual Some(
|
||||
Set(xBindNameId, xBindExprId)
|
||||
)
|
||||
@ -1202,47 +1178,6 @@ class DataflowAnalysisTest extends CompilerTest {
|
||||
dependencies.getDirect(bindingExprId) shouldEqual None
|
||||
}
|
||||
|
||||
"work properly for undefined variables" in {
|
||||
implicit val inlineContext: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|x = undefined
|
||||
|""".stripMargin.preprocessExpression.get.analyse
|
||||
|
||||
val depInfo = ir.getMetadata(DataflowAnalysis).get
|
||||
|
||||
val binding = ir.asInstanceOf[IR.Expression.Binding]
|
||||
val bindingName = binding.name.asInstanceOf[IR.Name.Literal]
|
||||
val bindingExpr = binding.expression.asInstanceOf[IR.Error.Resolution]
|
||||
val undefinedName = bindingExpr.originalName
|
||||
|
||||
// The IDs
|
||||
val bindingId = mkStaticDep(binding.getId)
|
||||
val bindingNameId = mkStaticDep(bindingName.getId)
|
||||
val bindingExprId = mkStaticDep(bindingExpr.getId)
|
||||
val undefinedNameId = mkDynamicDep(undefinedName.name)
|
||||
|
||||
// The info
|
||||
val dependents = depInfo.dependents
|
||||
val dependencies = depInfo.dependencies
|
||||
|
||||
// The tests for dependents
|
||||
dependents.getDirect(bindingId) should not be defined
|
||||
dependents.getDirect(bindingNameId) shouldEqual Some(Set(bindingId))
|
||||
dependents.getDirect(bindingExprId) shouldEqual Some(Set(bindingId))
|
||||
dependents.getDirect(undefinedNameId) shouldEqual Some(Set(bindingExprId))
|
||||
|
||||
// The tests for dependencies
|
||||
dependencies.getDirect(bindingId) shouldEqual Some(
|
||||
Set(bindingNameId, bindingExprId)
|
||||
)
|
||||
dependencies.getDirect(bindingNameId) shouldEqual None
|
||||
dependencies.getDirect(bindingExprId) shouldEqual Some(
|
||||
Set(undefinedNameId)
|
||||
)
|
||||
}
|
||||
|
||||
"work properly for undefined variables in expressions" in {
|
||||
implicit val inlineContext: InlineContext = mkInlineContext
|
||||
|
||||
@ -1331,7 +1266,6 @@ class DataflowAnalysisTest extends CompilerTest {
|
||||
val vector = callArg.value
|
||||
.asInstanceOf[IR.Application.Literal.Sequence]
|
||||
|
||||
val xDefId = mkStaticDep(ir.arguments(0).getId)
|
||||
val xUseId = mkStaticDep(vector.items(0).getId)
|
||||
val yId = mkStaticDep(vector.items(1).getId)
|
||||
val litId = mkStaticDep(vector.items(2).getId)
|
||||
@ -1345,7 +1279,6 @@ class DataflowAnalysisTest extends CompilerTest {
|
||||
val dependencies = depInfo.dependencies
|
||||
|
||||
// Tests for dependents
|
||||
dependents.getDirect(xDefId) shouldEqual Some(Set(xUseId))
|
||||
dependents.getDirect(xUseId) shouldEqual Some(Set(vecId))
|
||||
dependents.getDirect(yId) shouldEqual Some(Set(vecId))
|
||||
dependents.getDirect(litId) shouldEqual Some(Set(vecId))
|
||||
@ -1397,9 +1330,7 @@ class DataflowAnalysisTest extends CompilerTest {
|
||||
caseBinding.expression.asInstanceOf[IR.Application.Prefix]
|
||||
val caseBindingName = caseBinding.name.asInstanceOf[IR.Name.Literal]
|
||||
val caseExpr = caseBlock.returnValue.asInstanceOf[IR.Case.Expr]
|
||||
val scrutinee = caseExpr.scrutinee.asInstanceOf[IR.Name.Literal]
|
||||
val consBranch = caseExpr.branches.head
|
||||
val catchAllbranch = caseExpr.branches(1)
|
||||
|
||||
val consBranchPattern =
|
||||
consBranch.pattern.asInstanceOf[Pattern.Constructor]
|
||||
@ -1432,9 +1363,7 @@ class DataflowAnalysisTest extends CompilerTest {
|
||||
val caseBindingExprId = mkStaticDep(caseBindingExpr.getId)
|
||||
val caseBindingNameId = mkStaticDep(caseBindingName.getId)
|
||||
val caseExprId = mkStaticDep(caseExpr.getId)
|
||||
val scrutineeId = mkStaticDep(scrutinee.getId)
|
||||
val consBranchId = mkStaticDep(consBranch.getId)
|
||||
val catchAllBranchId = mkStaticDep(catchAllbranch.getId)
|
||||
|
||||
val consBranchPatternId = mkStaticDep(consBranchPattern.getId)
|
||||
val consBranchPatternConsId = mkStaticDep(consBranchPatternCons.getId)
|
||||
@ -1457,8 +1386,6 @@ class DataflowAnalysisTest extends CompilerTest {
|
||||
// Tests for dependents
|
||||
dependents.getDirect(caseBlockId) should not be defined
|
||||
dependents.getDirect(caseExprId) shouldEqual Some(Set(caseBlockId))
|
||||
dependents.getDirect(scrutineeId) shouldEqual Some(Set(caseExprId))
|
||||
dependents.getDirect(caseBindingId) shouldEqual Some(Set(scrutineeId))
|
||||
dependents.getDirect(caseBindingExprId) shouldEqual Some(
|
||||
Set(caseBindingId)
|
||||
)
|
||||
@ -1499,10 +1426,6 @@ class DataflowAnalysisTest extends CompilerTest {
|
||||
dependencies.getDirect(caseBindingId) shouldEqual Some(
|
||||
Set(caseBindingNameId, caseBindingExprId)
|
||||
)
|
||||
dependencies.getDirect(caseExprId) shouldEqual Some(
|
||||
Set(scrutineeId, consBranchId, catchAllBranchId)
|
||||
)
|
||||
dependencies.getDirect(scrutineeId) shouldEqual Some(Set(caseBindingId))
|
||||
dependencies.getDirect(consBranchId) shouldEqual Some(
|
||||
Set(consBranchPatternId, consBranchExpressionId)
|
||||
)
|
||||
|
@ -157,79 +157,9 @@ class DemandAnalysisTest extends CompilerTest {
|
||||
|
||||
vec.items(0) shouldBe an[IR.Application.Force]
|
||||
vec.items(1) shouldBe an[IR.Application.Force]
|
||||
vec.items(2) shouldBe an[IR.Name]
|
||||
vec.items(2) shouldBe an[IR.Application.Force]
|
||||
|
||||
}
|
||||
|
||||
"be marked as not to suspend during codegen when passed to a function" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|~x -> ~y -> z -> foo x y z
|
||||
|""".stripMargin.preprocessExpression.get.analyse
|
||||
|
||||
val app = ir
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
.body
|
||||
.asInstanceOf[IR.Application.Prefix]
|
||||
|
||||
app.arguments.head
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.shouldBeSuspended shouldEqual Some(false)
|
||||
|
||||
app
|
||||
.arguments(1)
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.shouldBeSuspended shouldEqual Some(false)
|
||||
}
|
||||
}
|
||||
|
||||
"Non-suspended arguments" should {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""x -> y ->
|
||||
| a = x
|
||||
| foo x a
|
||||
|""".stripMargin.preprocessExpression.get.analyse
|
||||
|
||||
val body = ir
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
.body
|
||||
.asInstanceOf[IR.Expression.Block]
|
||||
|
||||
"be left alone by demand analysis" in {
|
||||
body.expressions.head
|
||||
.asInstanceOf[IR.Expression.Binding]
|
||||
.expression shouldBe an[IR.Name]
|
||||
|
||||
body.returnValue
|
||||
.asInstanceOf[IR.Application.Prefix]
|
||||
.arguments
|
||||
.head
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value shouldBe an[IR.Name]
|
||||
}
|
||||
|
||||
"be marked for suspension during codegen when passed to a function" in {
|
||||
val xArg = body.returnValue
|
||||
.asInstanceOf[IR.Application.Prefix]
|
||||
.arguments
|
||||
.head
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
|
||||
val aArg = body.returnValue
|
||||
.asInstanceOf[IR.Application.Prefix]
|
||||
.arguments(1)
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
|
||||
xArg.value shouldBe an[IR.Name]
|
||||
xArg.shouldBeSuspended shouldEqual Some(true)
|
||||
|
||||
aArg.value shouldBe an[IR.Name]
|
||||
aArg.shouldBeSuspended shouldEqual Some(true)
|
||||
}
|
||||
}
|
||||
|
||||
"Suspended blocks" should {
|
||||
@ -280,28 +210,6 @@ class DemandAnalysisTest extends CompilerTest {
|
||||
.value shouldBe an[IR.Name]
|
||||
}
|
||||
|
||||
"be marked as not to suspend during codegen when passed to a function" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|x ->
|
||||
| blck =
|
||||
| foo a b
|
||||
| bar blck
|
||||
|""".stripMargin.preprocessExpression.get.analyse
|
||||
|
||||
ir.asInstanceOf[IR.Function.Lambda]
|
||||
.body
|
||||
.asInstanceOf[IR.Expression.Block]
|
||||
.returnValue
|
||||
.asInstanceOf[IR.Application.Prefix]
|
||||
.arguments
|
||||
.head
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.shouldBeSuspended shouldEqual Some(false)
|
||||
}
|
||||
|
||||
"force terms in blocks passed directly as arguments" in {
|
||||
implicit val ctx: ModuleContext = mkModuleContext
|
||||
|
||||
|
@ -32,8 +32,7 @@ class CurryingTest extends InterpreterTest {
|
||||
| fn1 = fn ...
|
||||
| fn2 = fn1 1 2 ...
|
||||
| fn3 = fn2 3 ...
|
||||
|
|
||||
| fn3.call
|
||||
| fn3
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual 26
|
||||
@ -45,7 +44,7 @@ class CurryingTest extends InterpreterTest {
|
||||
|main =
|
||||
| fn = w -> x -> (y = 10) -> (z = 20) -> w + x + y + z
|
||||
|
|
||||
| fn.call 1 2 (z = 10)
|
||||
| fn 1 2 (z = 10)
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual 23
|
||||
@ -74,11 +73,63 @@ class CurryingTest extends InterpreterTest {
|
||||
| fn1 = Nothing.fn ...
|
||||
| fn2 = fn1 1 2 ...
|
||||
| fn3 = fn2 3 ...
|
||||
|
|
||||
| fn3.call
|
||||
| fn3
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual 26
|
||||
}
|
||||
|
||||
"automatically force functions with all-defaulted arguments" in {
|
||||
val code =
|
||||
"""main =
|
||||
| foo (x=1) = x
|
||||
| foo + 1
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual 2
|
||||
}
|
||||
|
||||
"allow to pass suspended functions in arguments with `...`" in {
|
||||
val code =
|
||||
"""main =
|
||||
| foo f = f 2
|
||||
| bar x=1 = x + 1
|
||||
| foo (bar ...)
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual 3
|
||||
}
|
||||
|
||||
"allow to pass suspended functions in arguments with `...` but still auto-execute them" in {
|
||||
val code =
|
||||
"""main =
|
||||
| foo f = f
|
||||
| bar x=1 = x + 1
|
||||
| foo (bar ...)
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual 2
|
||||
}
|
||||
|
||||
"should handle a defaulted-suspended combo" in {
|
||||
val code =
|
||||
"""main =
|
||||
| foo ~f = f
|
||||
| bar x=1 = x + 1
|
||||
| foo (bar ...)
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual 2
|
||||
}
|
||||
|
||||
"should make `...` an identity on Atom Constructors" in {
|
||||
val code =
|
||||
"""
|
||||
|type My_Atom x=1
|
||||
|
|
||||
|My_Atom.my_static = "hello"
|
||||
|
|
||||
|main =
|
||||
| (My_Atom ...).my_static
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual "hello"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ class LambdaShorthandArgsTest extends InterpreterTest {
|
||||
"""
|
||||
|main =
|
||||
| f = (x = _) -> x
|
||||
| g = f.call
|
||||
| g = f
|
||||
| h = _
|
||||
| res1 = g 10
|
||||
| res2 = h 10
|
||||
|
Loading…
Reference in New Issue
Block a user