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:
Marcin Kostrzewa 2022-04-27 19:57:00 +02:00 committed by GitHub
parent bb6a5bac02
commit 96a0c92c8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 236 additions and 461 deletions

View File

@ -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)

View File

@ -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;

View File

@ -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;

View File

@ -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();
}

View File

@ -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];
}
}

View File

@ -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());
}
}

View File

@ -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. */

View File

@ -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);
}
}

View File

@ -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(

View File

@ -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;

View File

@ -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]);
}
}

View File

@ -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",

View File

@ -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(

View File

@ -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;

View File

@ -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());
}
}

View File

@ -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));

View File

@ -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 {

View File

@ -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;

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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)) {

View File

@ -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

View File

@ -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) {

View File

@ -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
@ -5343,8 +5334,6 @@ object IR {
* @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 diagnostics compiler diagnostics for this node
*/
@ -5352,7 +5341,6 @@ object IR {
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()
) extends CallArgument
@ -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

View File

@ -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()

View File

@ -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))

View File

@ -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) {
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 = 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."
)
}
val newName = lit.copy(location = newNameLocation)
IR.Application.Force(newName, forceLocation)
} else {
name
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))
)
)
}
}

View File

@ -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]

View File

@ -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(

View File

@ -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,

View File

@ -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
}
}
}

View File

@ -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

View File

@ -936,7 +936,6 @@ 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 plusArgX =
@ -950,7 +949,6 @@ class DataflowAnalysisTest extends CompilerTest {
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)
@ -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)
)

View File

@ -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

View File

@ -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"
}
}
}

View File

@ -85,7 +85,7 @@ class LambdaShorthandArgsTest extends InterpreterTest {
"""
|main =
| f = (x = _) -> x
| g = f.call
| g = f
| h = _
| res1 = g 10
| res2 = h 10