Opt-in TCO (#1219)

This commit is contained in:
Marcin Kostrzewa 2020-10-15 16:52:26 +02:00 committed by GitHub
parent 1feec8388d
commit 8668079337
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 850 additions and 435 deletions

View File

@ -415,6 +415,8 @@ lazy val syntax = crossProject(JVMPlatform, JSPlatform)
.configs(Test)
.configs(Benchmark)
.settings(
commands += WithDebugCommand.withDebug,
Test / fork := true,
testFrameworks := Nil,
scalacOptions ++= Seq("-Ypatmat-exhaust-depth", "off"),
mainClass in (Compile, run) := Some("org.enso.syntax.text.Main"),

View File

@ -2,27 +2,31 @@ from Base import all
reverse_list = list ->
go = list -> acc -> case list of
Cons h t -> go t (Cons h acc)
Cons h t -> @Tail_Call go t (Cons h acc)
Nil -> acc
go list Nil
res = go list Nil
res
sum_list = list ->
go = list -> acc -> case list of
Cons a b -> go b (acc + a)
Cons a b -> @Tail_Call go b (acc + a)
Nil -> acc
go list 0
res = go list 0
res
avg_list = list -> here.sum_list list / here.len_list list
len_list = list ->
go = list -> acc -> case list of
Cons _ b -> go b (acc + 1)
Cons _ b -> @Tail_Call go b (acc + 1)
Nil -> acc
go list 0
res = go list 0
res
Number.times = act ->
go = results -> number -> if number == 0 then results else go (Cons (act number) results) number-1
go = results -> number -> if number == 0 then results else
@Tail_Call go (Cons (act number) results) number-1
res = here.reverse_list (go Nil this)
res

View File

@ -12,7 +12,7 @@ map_helper list cons f = case list of
Cons h t ->
res = Cons (f h) Nil
Unsafe.set_atom_field cons 1 res
here.map_helper t res f
@Tail_Call here.map_helper t res f
Nil -> Unsafe.set_atom_field cons 1 Nil
## The basic cons-list type.
@ -53,11 +53,14 @@ type List
to the standard output:
(Cons 0 <| Cons 1 <| Cons 2 <| Nil) . each IO.println
each : (Any -> Any) -> Unit
each f = case this of
Nil -> Unit
Cons h t ->
f h
t.each f
each f =
go list = case list of
Nil -> Unit
Cons h t ->
f h
@Tail_Call go t
res = go this
res
## Combines all the elements of the list, by iteratively applying the
passed function with next elements of the list.
@ -75,7 +78,7 @@ type List
fold init f =
go acc list = case list of
Nil -> acc
Cons h t -> go (f acc h) t
Cons h t -> @Tail_Call go (f acc h) t
res = go init this
res
@ -98,7 +101,10 @@ type List
larger than `1`:
(Cons 0 <| Cons 1 <| Cons 2 <| Nil) . any (> 5)
any : (Any -> Boolean) -> Boolean
any predicate = case this of
Nil -> False
Cons h t -> if predicate h then True else t.any predicate
any predicate =
go list = case list of
Nil -> False
Cons h t -> if predicate h then True else
@Tail_Call go t
res = go this
res

View File

@ -33,7 +33,7 @@ type Range
each function =
it start end = if start == end then Unit else
function start
it start+1 end
@Tail_Call it start+1 end
it this.start this.end
Unit
@ -52,7 +52,7 @@ type Range
fold initial function =
it acc start end = if start == end then acc else
new_acc = function acc start
it new_acc start+1 end
@Tail_Call it new_acc start+1 end
res = it initial this.start this.end
res
@ -60,7 +60,7 @@ type Range
every predicate =
it start end = if start==end then True else
r = predicate start
if r then (it start+1 end) else False
if r then (@Tail_Call it start+1 end) else False
res = it this.start this.end
res

View File

@ -167,8 +167,7 @@ type File
read_bytes : Vector ! File_Error
read_bytes =
opts = [Option.Read]
bytes = this.with_input_stream opts (_.read_all_bytes)
bytes
this.with_input_stream opts (_.read_all_bytes)
## Reads the whole file into a `Text`, assuming UTF-8 content encoding.
read : Text ! File_Error

View File

@ -24,7 +24,7 @@ Text.each function =
iterate prev nxt = if nxt == -1 then Unit else
function (Text_Utils.substring [this, prev, nxt])
next_nxt = iterator.next []
iterate nxt next_nxt
@Tail_Call iterate nxt next_nxt
iterate fst nxt
Unit
@ -39,8 +39,7 @@ Text.characters : Vector
Text.characters =
bldr = Vector.new_builder
this.each bldr.append
r = bldr.to_vector
r
bldr.to_vector
## Takes a separator string and returns a vector resulting from splitting
`this` on each occurence of `separator`.

View File

@ -53,11 +53,7 @@ type Vector
## Converts a polyglot value representing an array into a vector. This is
useful when wrapping polyglot APIs for further use in Enso.
from_polyglot_array : Any -> Vector
from_polyglot_array arr =
a = Array.new arr.length
0.upto arr.length . each i->
a.set_at i (arr.at i)
Vector a
from_polyglot_array arr = Vector.new arr.length arr.at
## Returns the number of elements stored in this vector.
length : Number
@ -79,8 +75,7 @@ type Vector
fold initial function =
arr = this.to_array
f = acc -> ix -> function acc (arr.at ix)
res = 0.upto this.length . fold initial f
res
0.upto this.length . fold initial f
## Creates a new vector of the given length, initializing elements using
the provided constructor function.
@ -146,8 +141,7 @@ type Vector
arr1 = this.to_array
arr2 = that.to_array
eq_at i = arr1.at i == arr2.at i
r = if arr1.length == arr2.length then 0.upto arr1.length . every eq_at else False
r
if arr1.length == arr2.length then 0.upto arr1.length . every eq_at else False
## Concatenates two vectors, resulting in a new vector, containing all the
elements of `this`, followed by all the elements of `that`.

View File

@ -42,11 +42,6 @@ should be treated as a builtin method. It takes the following arguments:
2. `name` **String** the method name.
3. `description` **String** a summary of the method's behavior, for
documentation purposes.
4. `alwaysDirect` **Boolean = `true`** describes whether the function can always
be safely called directly. It should be set to `false` for methods that
evaluate user code in a tail position (e.g. `if_then_else`). If the method
does not take functions or thunks as parameters, or it only evaluates these
in a non-tail position, this parameter should be left defaulted.
The annotation will generate a `RootNode` subclass in the same package as the
declaring node, with a name basing on the declaring node's name. The name of the

View File

@ -46,7 +46,7 @@ like a builtin language loop construct.
In pseudocode, a tail recursive function, like:
```ruby
foo x = if x == 0 then print "foo" else foo (x - 1)
foo x = if x == 0 then print "foo" else @Tail_Call foo (x - 1)
```
becomes:

View File

@ -0,0 +1,79 @@
---
layout: developer-doc
title: Managed Resources
category: semantics
tags: [resources, finalization, cleanup]
order: 10
---
# Tail Call Optimization
Tail call optimization is a powerful technique for optimizing functional
programs. It allows transforming recursive functions of certain shapes into
loops, removing unnecessary intermediate function calls and saving stack space.
This document outlines the usage and semantics of tail call optimization in
Enso.
<!-- MarkdownTOC levels="2,3" autolink="true" -->
- [Tail Calls](#tail-calls)
- [Usage](#usage)
<!-- /MarkdownTOC -->
## Tail Calls
A Tail Call is a function call occurring as the last statement in a function
body, i.e. an expression whose value is guaranteed not to be depended upon by
the function itself. For example,
```hs
sum_1 n = if n == 0 then 0 else 1 + sum_1 n-1
sum_2 n acc = if n == 0 then acc else @Tail_Call sum_2 n-1 acc+n
```
In the code snippet above, only the `sum_2` function is tail recursive. The
result of calling `sum_2` recursively is not depended upon by `sum_2` itself or
the definition of `if_then_else` method on booleans. On the other hand, `sum_1`
needs to know the value of its recursive call in order to perform the addition
operation. It is advised that functions that can be expressed with tail-calls
are implemented that way. Using tail call optimization, will lead to `sum_2`
being orders of magnitude faster than `sum_1`. Moreover, for `n = 100000000`,
`sum_1` will allocate a hundred million stack frames (over a gigabyte, likely
resulting in a stack overflow error), while `sum_2` is an allocation-free loop.
## Usage
Enso does not currently perform automatic tail call detection and defers the
optimization decisions to the user. To mark a function call as a tail call, the
`@Tail_Call` annotation must be used. Note that if the annotation is placed
incorrectly, it may either be reported as a warning by the compiler, or silently
ignored if such analysis is impossible to perform due to the compiler's limited
static analysis capabilities. However, it is _guaranteed_ that a wrongly placed
`@Tail_Call` annotation will not lead to incorrect runtime behavior.
If the `@Tail_Call` annotation is not placed, the call will be treated as a
standard on-stack function call.
For example, the following code reverses a list in a tail recursive fashion:
```
reverse list =
go list result = case list of
Nil -> result
Cons head tail -> @Tail_Call go tail (Cons head result)
result = go list Nil
result
```
Note the placement of `@Tail_Call` in the recursive branch of `go`. It is placed
correctly, marking the last operation in a function, and therefore `go` will be
interpreted as a loop rather than a chain of function calls.
> #### Debugging Tail Calls
>
> The way `go` is wrapped in the example above is recommended for most uses.
> Using the assignment and return of a variable, rather than a direct call,
> guarantees that calls to `reverse` won't themselves be removed from the call
> stack and therefore greatly aids debugging.

View File

@ -23,7 +23,7 @@ class AtomFixtures extends DefaultInterpreterRunner {
"""from Builtins import all
|
|main = length ->
| generator = acc -> i -> if i == 0 then acc else generator (Cons i acc) (i - 1)
| generator = acc -> i -> if i == 0 then acc else @Tail_Call generator (Cons i acc) (i - 1)
|
| res = generator Nil length
| res
@ -34,7 +34,7 @@ class AtomFixtures extends DefaultInterpreterRunner {
"""from Builtins import all
|
|main = length ->
| generator = acc -> i -> if i == 0 then acc else generator (Builtins.cons i acc) (i - 1)
| generator = acc -> i -> if i == 0 then acc else @Tail_Call generator (Builtins.cons i acc) (i - 1)
|
| res = generator Builtins.nil length
| res
@ -46,7 +46,7 @@ class AtomFixtures extends DefaultInterpreterRunner {
|
|main = list ->
| reverser = acc -> list -> case list of
| Cons h t -> reverser (Cons h acc) t
| Cons h t -> @Tail_Call reverser (Cons h acc) t
| Nil -> acc
|
| res = reverser Nil list
@ -58,7 +58,7 @@ class AtomFixtures extends DefaultInterpreterRunner {
"""from Builtins import all
|
|Cons.reverse = acc -> case this of
| Cons h t -> reverse t (Cons h acc)
| Cons h t -> @Tail_Call reverse t (Cons h acc)
|
|Nil.reverse = acc -> acc
|
@ -73,7 +73,7 @@ class AtomFixtures extends DefaultInterpreterRunner {
|
|main = list ->
| summator = acc -> list -> case list of
| Cons h t -> summator acc+h t
| Cons h t -> @Tail_Call summator acc+h t
| Nil -> acc
|
| res = summator 0 list
@ -86,7 +86,7 @@ class AtomFixtures extends DefaultInterpreterRunner {
|
|main = list ->
| fold = f -> acc -> list -> case list of
| Cons h t -> fold f (f acc h) t
| Cons h t -> @Tail_Call fold f (f acc h) t
| _ -> acc
|
| res = fold (x -> y -> x + y) 0 list
@ -99,7 +99,7 @@ class AtomFixtures extends DefaultInterpreterRunner {
|
|main = list ->
| summator = acc -> list -> case list of
| Cons h t -> summator acc+h t
| Cons h t -> @Tail_Call summator acc+h t
| _ -> acc
|
| res = summator 0 list
@ -112,7 +112,7 @@ class AtomFixtures extends DefaultInterpreterRunner {
|
|Nil.sum = acc -> acc
|Cons.sum = acc -> case this of
| Cons h t -> sum t h+acc
| Cons h t -> @Tail_Call sum t h+acc
|
|main = list ->
| res = sum list 0
@ -125,7 +125,7 @@ class AtomFixtures extends DefaultInterpreterRunner {
|
|Nil.mapReverse = f -> acc -> acc
|Cons.mapReverse = f -> acc -> case this of
| Cons h t -> mapReverse t f (Cons (f h) acc)
| Cons h t -> @Tail_Call mapReverse t f (Cons (f h) acc)
|
|main = list ->
| res = mapReverse list (x -> x + 1) Nil
@ -138,7 +138,7 @@ class AtomFixtures extends DefaultInterpreterRunner {
|
|Nil.mapReverse = f -> acc -> acc
|Cons.mapReverse = f -> acc -> case this of
| Cons h t -> mapReverse t f (Cons (f h) acc)
| Cons h t -> @Tail_Call mapReverse t f (Cons (f h) acc)
|
|main = list ->
| adder = x -> y -> x + y

View File

@ -9,7 +9,7 @@ class NamedDefaultedArgumentFixtures extends DefaultInterpreterRunner {
"""
|main = sumTo ->
| summator = acc -> current ->
| if current == 0 then acc else summator (current = current - 1) (acc = acc + current)
| if current == 0 then acc else @Tail_Call summator (current = current - 1) (acc = acc + current)
|
| res = summator current=sumTo acc=0
| res
@ -20,7 +20,7 @@ class NamedDefaultedArgumentFixtures extends DefaultInterpreterRunner {
"""
|main = sumTo ->
| summator = (acc = 0) -> current ->
| if current == 0 then acc else summator (current = current - 1) (acc = acc + current)
| if current == 0 then acc else @Tail_Call summator (current = current - 1) (acc = acc + current)
|
| res = summator (current = sumTo)
| res

View File

@ -12,7 +12,7 @@ class RecursionFixtures extends DefaultInterpreterRunner {
"""
|main = sumTo ->
| summator = acc -> current ->
| if current == 0 then acc else summator acc+current current-1
| if current == 0 then acc else @Tail_Call summator acc+current current-1
|
| res = summator 0 sumTo
| res
@ -23,7 +23,7 @@ class RecursionFixtures extends DefaultInterpreterRunner {
"""
|main = sumTo ->
| summator = acc -> i -> f ->
| if i == 0 then acc else summator (f acc i) (i - 1) f
| if i == 0 then acc else @Tail_Call summator (f acc i) (i - 1) f
| res = summator 0 sumTo (x -> y -> x + y)
| res
|""".stripMargin
@ -42,7 +42,7 @@ class RecursionFixtures extends DefaultInterpreterRunner {
"""
|main = sumTo ->
| summator = acc -> i -> f ->
| if i == 0 then acc else summator (f acc i) (i - 1) f
| if i == 0 then acc else @Tail_Call summator (f acc i) (i - 1) f
| res = summator 0 sumTo (x -> y -> x + y)
| res
|""".stripMargin
@ -54,7 +54,7 @@ class RecursionFixtures extends DefaultInterpreterRunner {
|stateSum = n ->
| acc = State.get Number
| State.put Number (acc + n)
| if n == 0 then State.get Number else here.stateSum (n - 1)
| if n == 0 then State.get Number else @Tail_Call here.stateSum (n - 1)
|
|main = sumTo ->
| res = State.run Number 0 (here.stateSum sumTo)
@ -67,7 +67,7 @@ class RecursionFixtures extends DefaultInterpreterRunner {
|
|main = sumTo ->
| summator = acc -> current ->
| if current == 0 then acc else Debug.eval "summator (acc + current) (current - 1)"
| if current == 0 then acc else Debug.eval "@Tail_Call summator (acc + current) (current - 1)"
|
| res = summator 0 sumTo
| res
@ -79,7 +79,7 @@ class RecursionFixtures extends DefaultInterpreterRunner {
|
|doNTimes = n -> ~block ->
| block
| if n == 1 then Unit else here.doNTimes n-1 block
| if n == 1 then Unit else @Tail_Call here.doNTimes n-1 block
|
|main = n ->
| block =

View File

@ -11,7 +11,17 @@ import com.oracle.truffle.api.nodes.NodeInfo;
@NodeInfo(shortName = "Base", description = "A base node for the Enso AST")
@ReportPolymorphism
public abstract class BaseNode extends Node {
private @CompilationFinal boolean isTail = false;
/** Represents the tail-position status of the node. */
public enum TailStatus {
/** Node is in a tail position, but not marked as a tail call. */
TAIL_DIRECT,
/** Node is in a tail position and marked as a tail call. */
TAIL_LOOP,
/** Node is not in a tail position. */
NOT_TAIL
}
private @CompilationFinal TailStatus tailStatus = TailStatus.NOT_TAIL;
private @CompilerDirectives.CompilationFinal FrameSlot stateFrameSlot;
/**
@ -28,30 +38,16 @@ public abstract class BaseNode extends Node {
}
/**
* Sets whether the node is tail-recursive.
* Sets the new tail position status for this node.
*
* @param isTail whether or not the node is tail-recursive.
* @param tailStatus the new tail status.
*/
public void setTail(boolean isTail) {
this.isTail = isTail;
public void setTailStatus(TailStatus tailStatus) {
this.tailStatus = tailStatus;
}
/** Marks the node as tail-recursive. */
public final void markTail() {
setTail(true);
}
/** Marks the node as not tail-recursive. */
public final void markNotTail() {
setTail(false);
}
/**
* Checks if the node is tail-recursive.
*
* @return {@code true} if the node is tail-recursive, otherwise {@code false}
*/
public boolean isTail() {
return isTail;
/** @return the tail position status of this node. */
public TailStatus getTailStatus() {
return tailStatus;
}
}

View File

@ -65,9 +65,9 @@ public class ApplicationNode extends ExpressionNode {
* @param isTail whether or not the node is tail-recursive.
*/
@Override
public void setTail(boolean isTail) {
super.setTail(isTail);
invokeCallableNode.setTail(isTail);
public void setTailStatus(TailStatus isTail) {
super.setTailStatus(isTail);
invokeCallableNode.setTailStatus(isTail);
}
/**

View File

@ -9,6 +9,7 @@ import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
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.thunk.ThunkExecutorNode;
@ -51,7 +52,7 @@ public abstract class IndirectInvokeCallableNode extends Node {
CallArgumentInfo[] schema,
InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode,
InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode,
boolean isTail);
BaseNode.TailStatus isTail);
@Specialization
Stateful invokeFunction(
@ -62,7 +63,7 @@ public abstract class IndirectInvokeCallableNode extends Node {
CallArgumentInfo[] schema,
InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode,
InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode,
boolean isTail,
BaseNode.TailStatus isTail,
@Cached IndirectInvokeFunctionNode invokeFunctionNode) {
return invokeFunctionNode.execute(
function,
@ -84,7 +85,7 @@ public abstract class IndirectInvokeCallableNode extends Node {
CallArgumentInfo[] schema,
InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode,
InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode,
boolean isTail,
BaseNode.TailStatus isTail,
@Cached IndirectInvokeFunctionNode invokeFunctionNode) {
return invokeFunction(
constructor.getConstructorFunction(),
@ -107,7 +108,7 @@ public abstract class IndirectInvokeCallableNode extends Node {
CallArgumentInfo[] schema,
InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode,
InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode,
boolean isTail,
BaseNode.TailStatus isTail,
@Cached IndirectInvokeFunctionNode invokeFunctionNode,
@Cached ThunkExecutorNode thisExecutor,
@Cached MethodResolverNode methodResolverNode) {
@ -117,7 +118,8 @@ public abstract class IndirectInvokeCallableNode extends Node {
if (canApplyThis) {
Object selfArgument = arguments[thisArgumentPosition];
if (argumentsExecutionMode.shouldExecute()) {
Stateful selfResult = thisExecutor.executeThunk((Thunk) selfArgument, state, false);
Stateful selfResult =
thisExecutor.executeThunk((Thunk) selfArgument, state, BaseNode.TailStatus.NOT_TAIL);
selfArgument = selfResult.getValue();
state = selfResult.getState();
arguments[thisArgumentPosition] = selfArgument;
@ -146,7 +148,7 @@ public abstract class IndirectInvokeCallableNode extends Node {
CallArgumentInfo[] schema,
InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode,
InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode,
boolean isTail) {
BaseNode.TailStatus isTail) {
throw new NotInvokableException(callable, this);
}
}

View File

@ -7,6 +7,7 @@ import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.Constants;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.dispatch.IndirectInvokeFunctionNode;
import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode;
import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode;
@ -96,7 +97,7 @@ public abstract class InteropApplicationNode extends Node {
buildSchema(arguments.length),
InvokeCallableNode.DefaultsExecutionMode.EXECUTE,
InvokeCallableNode.ArgumentsExecutionMode.PRE_EXECUTED,
false)
BaseNode.TailStatus.NOT_TAIL)
.getValue();
}
}

View File

@ -185,7 +185,8 @@ public abstract class InvokeCallableNode extends BaseNode {
lock.unlock();
}
}
Stateful selfResult = thisExecutor.executeThunk((Thunk) selfArgument, state, false);
Stateful selfResult =
thisExecutor.executeThunk((Thunk) selfArgument, state, TailStatus.NOT_TAIL);
selfArgument = selfResult.getValue();
state = selfResult.getState();
arguments[thisArgumentPosition] = selfArgument;
@ -233,9 +234,9 @@ public abstract class InvokeCallableNode extends BaseNode {
* @param isTail whether or not the node is tail-recursive.
*/
@Override
public void setTail(boolean isTail) {
super.setTail(isTail);
invokeFunctionNode.setTail(isTail);
public void setTailStatus(TailStatus isTail) {
super.setTailStatus(isTail);
invokeFunctionNode.setTailStatus(isTail);
}
/** @return the source section for this node. */

View File

@ -82,7 +82,8 @@ public class ArgumentSorterNode extends BaseNode {
}
for (int i = 0; i < mapping.getArgumentShouldExecute().length; i++) {
if (executors[i] != null) {
Stateful result = executors[i].executeThunk(TypesGen.asThunk(arguments[i]), state, false);
Stateful result =
executors[i].executeThunk(TypesGen.asThunk(arguments[i]), state, TailStatus.NOT_TAIL);
arguments[i] = result.getValue();
state = result.getState();
}

View File

@ -6,6 +6,7 @@ import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.InvokeCallableNode;
import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo.ArgumentMapping;
@ -42,7 +43,8 @@ public abstract class IndirectArgumentSorterNode extends Node {
for (int i = 0; i < mapping.getArgumentShouldExecute().length; i++) {
if (TypesGen.isThunk(arguments[i]) && mapping.getArgumentShouldExecute()[i]) {
Stateful result =
thunkExecutorNode.executeThunk(TypesGen.asThunk(arguments[i]), state, false);
thunkExecutorNode.executeThunk(
TypesGen.asThunk(arguments[i]), state, BaseNode.TailStatus.NOT_TAIL);
arguments[i] = result.getValue();
state = result.getState();
}

View File

@ -21,7 +21,6 @@ import java.util.concurrent.locks.Lock;
/** Handles runtime function currying and oversaturated (eta-expanded) calls. */
@NodeInfo(description = "Handles runtime currying and eta-expansion")
public class CurryNode extends BaseNode {
private final FunctionSchema preApplicationSchema;
private final FunctionSchema postApplicationSchema;
private final boolean appliesFully;
private @Child InvokeCallableNode oversaturatedCallableNode;
@ -31,14 +30,12 @@ public class CurryNode extends BaseNode {
private final InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode;
private CurryNode(
FunctionSchema originalSchema,
FunctionSchema postApplicationSchema,
InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode,
InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode,
boolean isTail) {
setTail(isTail);
BaseNode.TailStatus isTail) {
setTailStatus(isTail);
this.defaultsExecutionMode = defaultsExecutionMode;
this.preApplicationSchema = originalSchema;
this.postApplicationSchema = postApplicationSchema;
appliesFully = postApplicationSchema.isFullyApplied(defaultsExecutionMode);
initializeCallNodes();
@ -48,32 +45,27 @@ public class CurryNode extends BaseNode {
/**
* Creates a new instance of this node.
*
* @param preApplicationSchema the schema of all functions being used in the {@link
* #execute(VirtualFrame, Function, CallerInfo, Object, Object[], Object[])} method.
* @param argumentMapping the argument mapping for moving from the original schema to the argument
* schema expected by the function.
* @param defaultsExecutionMode the mode of handling defaulted arguments for this call.
* @param argumentsExecutionMode the mode of executing lazy arguments for this call.
* @param isTail is this a tail call position?
* @param tailStatus is this a tail call position?
* @return an instance of this node.
*/
public static CurryNode build(
FunctionSchema preApplicationSchema,
CallArgumentInfo.ArgumentMapping argumentMapping,
InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode,
InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode,
boolean isTail) {
BaseNode.TailStatus tailStatus) {
return new CurryNode(
preApplicationSchema,
argumentMapping.getPostApplicationSchema(),
defaultsExecutionMode,
argumentsExecutionMode,
isTail);
tailStatus);
}
private void initializeCallNodes() {
if (postApplicationSchema.hasOversaturatedArgs()
|| !preApplicationSchema.getCallStrategy().shouldCallDirect(isTail())) {
if (postApplicationSchema.hasOversaturatedArgs() || getTailStatus() == TailStatus.NOT_TAIL) {
this.loopingCall = CallOptimiserNode.build();
} else {
this.directCall = ExecuteCallNode.build();
@ -88,7 +80,7 @@ public class CurryNode extends BaseNode {
postApplicationSchema.getOversaturatedArguments(),
defaultsExecutionMode,
argumentsExecutionMode);
oversaturatedCallableNode.setTail(isTail());
oversaturatedCallableNode.setTailStatus(getTailStatus());
}
}
@ -159,12 +151,13 @@ public class CurryNode extends BaseNode {
private Stateful doCall(
Function function, CallerInfo callerInfo, Object state, Object[] arguments) {
if (preApplicationSchema.getCallStrategy().shouldCallDirect(isTail())) {
return directCall.executeCall(function, callerInfo, state, arguments);
} else if (isTail()) {
throw new TailCallException(function, callerInfo, state, arguments);
} else {
return loopingCall.executeDispatch(function, callerInfo, state, arguments);
switch (getTailStatus()) {
case TAIL_DIRECT:
return directCall.executeCall(function, callerInfo, state, arguments);
case TAIL_LOOP:
throw new TailCallException(function, callerInfo, state, arguments);
default:
return loopingCall.executeDispatch(function, callerInfo, state, arguments);
}
}
}

View File

@ -6,6 +6,7 @@ import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.ExecuteCallNode;
import org.enso.interpreter.node.callable.IndirectInvokeCallableNode;
import org.enso.interpreter.node.callable.InvokeCallableNode;
@ -36,7 +37,6 @@ public abstract class IndirectCurryNode extends Node {
* @param arguments the properly ordered arguments to pass to the function.
* @param oversaturatedArguments any arguments that should be treated as candidates for an
* eta-expanded call.
* @param originalSchema function schema before the call.
* @param postApplicationSchema function schema after the call.
* @param defaultsExecutionMode should default arguments be used for this call.
* @param argumentsExecutionMode are arguments pre-executed or suspended.
@ -50,11 +50,10 @@ public abstract class IndirectCurryNode extends Node {
Object state,
Object[] arguments,
Object[] oversaturatedArguments,
FunctionSchema originalSchema,
FunctionSchema postApplicationSchema,
InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode,
InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode,
boolean isTail);
BaseNode.TailStatus isTail);
@Specialization
Stateful doCurry(
@ -64,11 +63,10 @@ public abstract class IndirectCurryNode extends Node {
Object state,
Object[] arguments,
Object[] oversaturatedArguments,
FunctionSchema preApplicationSchema,
FunctionSchema postApplicationSchema,
InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode,
InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode,
boolean isTail,
BaseNode.TailStatus isTail,
@Cached ExecuteCallNode directCall,
@Cached LoopingCallOptimiserNode loopingCall,
@Cached IndirectInvokeCallableNode oversaturatedCallableNode) {
@ -81,7 +79,6 @@ public abstract class IndirectCurryNode extends Node {
callerInfo,
state,
arguments,
preApplicationSchema,
isTail,
directCall,
loopingCall);
@ -128,16 +125,16 @@ public abstract class IndirectCurryNode extends Node {
CallerInfo callerInfo,
Object state,
Object[] arguments,
FunctionSchema preApplicationSchema,
boolean isTail,
BaseNode.TailStatus isTail,
ExecuteCallNode directCall,
CallOptimiserNode loopingCall) {
if (preApplicationSchema.getCallStrategy().shouldCallDirect(isTail)) {
return directCall.executeCall(function, callerInfo, state, arguments);
} else if (isTail) {
throw new TailCallException(function, callerInfo, state, arguments);
} else {
return loopingCall.executeDispatch(function, callerInfo, state, arguments);
switch (isTail) {
case TAIL_DIRECT:
return directCall.executeCall(function, callerInfo, state, arguments);
case TAIL_LOOP:
throw new TailCallException(function, callerInfo, state, arguments);
default:
return loopingCall.executeDispatch(function, callerInfo, state, arguments);
}
}
}

View File

@ -6,6 +6,7 @@ import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.CaptureCallerInfoNode;
import org.enso.interpreter.node.callable.InvokeCallableNode;
import org.enso.interpreter.node.callable.argument.ArgumentSorterNode;
@ -45,7 +46,7 @@ public abstract class IndirectInvokeFunctionNode extends Node {
CallArgumentInfo[] schema,
InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode,
InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode,
boolean isTail);
BaseNode.TailStatus isTail);
@Specialization
Stateful invokeUncached(
@ -56,7 +57,7 @@ public abstract class IndirectInvokeFunctionNode extends Node {
CallArgumentInfo[] schema,
InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode,
InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode,
boolean isTail,
BaseNode.TailStatus isTail,
@Cached IndirectArgumentSorterNode mappingNode,
@Cached IndirectCurryNode curryNode,
@Cached CaptureCallerInfoNode captureCallerInfoNode) {
@ -85,7 +86,6 @@ public abstract class IndirectInvokeFunctionNode extends Node {
mappedArguments.getState(),
mappedArguments.getSortedArguments(),
mappedArguments.getOversaturatedArguments(),
function.getSchema(),
argumentMapping.getPostApplicationSchema(),
defaultsExecutionMode,
argumentsExecutionMode,

View File

@ -77,7 +77,7 @@ public abstract class InvokeFunctionNode extends BaseNode {
@Cached("build(cachedSchema, argumentMapping, getArgumentsExecutionMode())")
ArgumentSorterNode mappingNode,
@Cached(
"build(cachedSchema, argumentMapping, getDefaultsExecutionMode(), getArgumentsExecutionMode(), isTail())")
"build(argumentMapping, getDefaultsExecutionMode(), getArgumentsExecutionMode(), getTailStatus())")
CurryNode curryNode) {
ArgumentSorterNode.MappedArguments mappedArguments =
mappingNode.execute(function, state, arguments);
@ -142,11 +142,10 @@ public abstract class InvokeFunctionNode extends BaseNode {
mappedArguments.getState(),
mappedArguments.getSortedArguments(),
mappedArguments.getOversaturatedArguments(),
function.getSchema(),
argumentMapping.getPostApplicationSchema(),
defaultsExecutionMode,
argumentsExecutionMode,
isTail());
getTailStatus());
}
/**

View File

@ -4,7 +4,6 @@ import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.ClosureRootNode;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.Function;
@ -23,7 +22,7 @@ public class CreateFunctionNode extends ExpressionNode {
private CreateFunctionNode(RootCallTarget callTarget, ArgumentDefinition[] args) {
this.callTarget = callTarget;
this.schema = new FunctionSchema(FunctionSchema.CallStrategy.CALL_LOOP, args);
this.schema = new FunctionSchema(args);
}
/**

View File

@ -33,7 +33,7 @@ public abstract class ForceNode extends ExpressionNode {
Thunk thunk,
@Cached("build()") ThunkExecutorNode thunkExecutorNode) {
Object state = FrameUtil.getObjectSafe(frame, getStateFrameSlot());
Stateful result = thunkExecutorNode.executeThunk(thunk, state, isTail());
Stateful result = thunkExecutorNode.executeThunk(thunk, state, getTailStatus());
frame.setObject(getStateFrameSlot(), result.getState());
return result.getValue();
}

View File

@ -6,6 +6,7 @@ import com.oracle.truffle.api.nodes.DirectCallNode;
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.dispatch.LoopingCallOptimiserNode;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.callable.function.Function;
@ -36,7 +37,7 @@ public abstract class ThunkExecutorNode extends Node {
* @param isTail is the execution happening in a tail-call position
* @return the return value of this thunk
*/
public abstract Stateful executeThunk(Thunk thunk, Object state, boolean isTail);
public abstract Stateful executeThunk(Thunk thunk, Object state, BaseNode.TailStatus isTail);
@Specialization(
guards = "callNode.getCallTarget() == thunk.getCallTarget()",
@ -44,11 +45,11 @@ public abstract class ThunkExecutorNode extends Node {
Stateful doCached(
Thunk thunk,
Object state,
boolean isTail,
BaseNode.TailStatus isTail,
@Cached("create(thunk.getCallTarget())") DirectCallNode callNode,
@Cached LoopingCallOptimiserNode loopingCallOptimiserNode) {
CompilerAsserts.partialEvaluationConstant(isTail);
if (isTail) {
if (isTail != BaseNode.TailStatus.NOT_TAIL) {
return (Stateful) callNode.call(Function.ArgumentsHelper.buildArguments(thunk, state));
} else {
try {
@ -64,10 +65,10 @@ public abstract class ThunkExecutorNode extends Node {
Stateful doUncached(
Thunk thunk,
Object state,
boolean isTail,
BaseNode.TailStatus isTail,
@Cached IndirectCallNode callNode,
@Cached LoopingCallOptimiserNode loopingCallOptimiserNode) {
if (isTail) {
if (isTail != BaseNode.TailStatus.NOT_TAIL) {
return (Stateful)
callNode.call(
thunk.getCallTarget(), Function.ArgumentsHelper.buildArguments(thunk, state));

View File

@ -4,6 +4,7 @@ import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.ConditionProfile;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.dsl.MonadicState;
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;
@ -11,7 +12,6 @@ import org.enso.interpreter.runtime.state.Stateful;
@BuiltinMethod(
type = "Boolean",
name = "if_then_else",
alwaysDirect = false,
description = "Performs the standard if-then-else control flow operation.")
public class IfThenElseNode extends Node {
private @Child ThunkExecutorNode leftThunkExecutorNode = ThunkExecutorNode.build();
@ -20,9 +20,9 @@ public class IfThenElseNode extends Node {
Stateful execute(@MonadicState Object state, boolean _this, Thunk if_true, Thunk if_false) {
if (condProfile.profile(_this)) {
return leftThunkExecutorNode.executeThunk(if_true, state, true);
return leftThunkExecutorNode.executeThunk(if_true, state, BaseNode.TailStatus.TAIL_DIRECT);
} else {
return rightThunkExecutorNode.executeThunk(if_false, state, true);
return rightThunkExecutorNode.executeThunk(if_false, state, BaseNode.TailStatus.TAIL_DIRECT);
}
}
}

View File

@ -7,6 +7,7 @@ import com.oracle.truffle.api.profiles.ConditionProfile;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.dsl.MonadicState;
import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.argument.Thunk;
@ -15,7 +16,6 @@ import org.enso.interpreter.runtime.state.Stateful;
@BuiltinMethod(
type = "Boolean",
name = "if_then",
alwaysDirect = false,
description = "Performs the standard if-then control flow operation.")
public abstract class IfThenNode extends Node {
private @Child ThunkExecutorNode leftThunkExecutorNode = ThunkExecutorNode.build();
@ -31,7 +31,7 @@ public abstract class IfThenNode extends Node {
Stateful doExecute(
Object state, boolean _this, Thunk if_true, @CachedContext(Language.class) Context context) {
if (condProfile.profile(_this)) {
return leftThunkExecutorNode.executeThunk(if_true, state, true);
return leftThunkExecutorNode.executeThunk(if_true, state, BaseNode.TailStatus.TAIL_DIRECT);
} else {
return new Stateful(state, context.getUnit().newInstance());
}

View File

@ -3,6 +3,7 @@ package org.enso.interpreter.node.expression.builtin.debug;
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.expression.debug.EvalNode;
import org.enso.interpreter.runtime.callable.CallerInfo;
import org.enso.interpreter.runtime.data.text.Text;
@ -12,13 +13,12 @@ import org.enso.interpreter.runtime.state.Stateful;
@BuiltinMethod(
type = "Debug",
name = "eval",
description = "Evaluates an expression passed as a Text argument, in the caller frame.",
alwaysDirect = false)
description = "Evaluates an expression passed as a Text argument, in the caller frame.")
public class DebugEvalNode extends Node {
private @Child EvalNode evalNode = EvalNode.build();
DebugEvalNode() {
evalNode.markTail();
evalNode.setTailStatus(BaseNode.TailStatus.TAIL_DIRECT);
}
Stateful execute(

View File

@ -5,6 +5,7 @@ import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.ConditionProfile;
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.state.Stateful;
@ -14,8 +15,7 @@ import org.enso.interpreter.runtime.type.TypesGen;
type = "Any",
name = "catch",
description =
"If called on an error, executes the provided handler on the error's payload. Otherwise acts as identity.",
alwaysDirect = false)
"If called on an error, executes the provided handler on the error's payload. Otherwise acts as identity.")
public class CatchErrorNode extends Node {
private @Child InvokeCallableNode invokeCallableNode;
private final ConditionProfile executionProfile = ConditionProfile.createCountingProfile();
@ -26,7 +26,7 @@ public class CatchErrorNode extends Node {
new CallArgumentInfo[] {new CallArgumentInfo()},
InvokeCallableNode.DefaultsExecutionMode.EXECUTE,
InvokeCallableNode.ArgumentsExecutionMode.PRE_EXECUTED);
this.invokeCallableNode.markTail();
this.invokeCallableNode.setTailStatus(BaseNode.TailStatus.TAIL_DIRECT);
}
Stateful execute(VirtualFrame frame, @MonadicState Object state, Object _this, Object handler) {

View File

@ -7,6 +7,7 @@ import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.dsl.MonadicState;
import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.argument.Thunk;
@ -34,7 +35,7 @@ public abstract class CatchPanicNode extends Node {
Thunk action,
@CachedContext(Language.class) Context ctx) {
try {
return thunkExecutorNode.executeThunk(action, state, false);
return thunkExecutorNode.executeThunk(action, state, BaseNode.TailStatus.NOT_TAIL);
} catch (PanicException e) {
return new Stateful(state, new RuntimeError(e.getExceptionObject()));
} catch (Throwable e) {

View File

@ -4,6 +4,7 @@ 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.argument.Thunk;
@ -13,8 +14,7 @@ import org.enso.interpreter.runtime.state.Stateful;
@BuiltinMethod(
type = "Function",
name = "<|",
description = "Takes a function and an argument and applies the function to the argument.",
alwaysDirect = false)
description = "Takes a function and an argument and applies the function to the argument.")
public class ApplicationOperator extends Node {
private @Child InvokeCallableNode invokeCallableNode;
@ -24,7 +24,7 @@ public class ApplicationOperator extends Node {
new CallArgumentInfo[] {new CallArgumentInfo()},
InvokeCallableNode.DefaultsExecutionMode.EXECUTE,
InvokeCallableNode.ArgumentsExecutionMode.EXECUTE);
invokeCallableNode.markTail();
invokeCallableNode.setTailStatus(BaseNode.TailStatus.TAIL_DIRECT);
}
Stateful execute(VirtualFrame frame, @MonadicState Object state, Function _this, Thunk argument) {

View File

@ -4,6 +4,7 @@ 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;
@ -12,8 +13,7 @@ import org.enso.interpreter.runtime.state.Stateful;
@BuiltinMethod(
type = "Function",
name = "call",
description = "Allows function calls to be made explicitly",
alwaysDirect = false)
description = "Allows function calls to be made explicitly")
public class ExplicitCallFunctionNode extends Node {
private @Child InvokeCallableNode invokeCallableNode;
@ -23,7 +23,7 @@ public class ExplicitCallFunctionNode extends Node {
new CallArgumentInfo[0],
InvokeCallableNode.DefaultsExecutionMode.EXECUTE,
InvokeCallableNode.ArgumentsExecutionMode.PRE_EXECUTED);
invokeCallableNode.markTail();
invokeCallableNode.setTailStatus(BaseNode.TailStatus.TAIL_DIRECT);
}
Stateful execute(VirtualFrame frame, @MonadicState Object state, Function _this) {

View File

@ -1,25 +1,12 @@
package org.enso.interpreter.node.expression.builtin.interop.syntax;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.profiles.BranchProfile;
import org.enso.interpreter.Constants;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.node.expression.builtin.BuiltinRootNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.callable.function.FunctionSchema.CallStrategy;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.state.Stateful;
import org.enso.interpreter.runtime.type.TypesGen;
@BuiltinMethod(
type = "Any",

View File

@ -13,7 +13,6 @@ import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.BuiltinRootNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.callable.function.FunctionSchema.CallStrategy;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.state.Stateful;
import org.enso.interpreter.runtime.type.TypesGen;
@ -37,7 +36,6 @@ public class ConstructorDispatchNode extends BuiltinRootNode {
public static Function makeFunction(Language language) {
return Function.fromBuiltinRootNode(
new ConstructorDispatchNode(language),
CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "arguments", ArgumentDefinition.ExecutionMode.EXECUTE));
}

View File

@ -5,6 +5,7 @@ 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.node.callable.thunk.ThunkExecutorNode;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
@ -61,7 +62,8 @@ public abstract class BracketNode extends Node {
Thunk constructor,
Object destructor,
Object action) {
Stateful resourceStateful = invokeConstructorNode.executeThunk(constructor, state, false);
Stateful resourceStateful =
invokeConstructorNode.executeThunk(constructor, state, BaseNode.TailStatus.NOT_TAIL);
Object resource = resourceStateful.getValue();
state = resourceStateful.getState();
try {

View File

@ -4,6 +4,7 @@ import com.oracle.truffle.api.CompilerDirectives;
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.thunk.ThunkExecutorNode;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.state.Stateful;
@ -17,6 +18,6 @@ public class NoInlineNode extends Node {
@CompilerDirectives.TruffleBoundary
Stateful execute(@MonadicState Object state, Object _this, Thunk action) {
return thunkExecutorNode.executeThunk(action, state, false);
return thunkExecutorNode.executeThunk(action, state, BaseNode.TailStatus.NOT_TAIL);
}
}

View File

@ -7,6 +7,7 @@ import com.oracle.truffle.api.dsl.Specialization;
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.thunk.ThunkExecutorNode;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.state.data.EmptyMap;
@ -34,7 +35,10 @@ public abstract class RunStateNode extends Node {
Stateful doEmpty(
EmptyMap state, Object _this, Object key, Object local_state, Thunk computation) {
SingletonMap localStateMap = new SingletonMap(key, local_state);
Object result = thunkExecutorNode.executeThunk(computation, localStateMap, false).getValue();
Object result =
thunkExecutorNode
.executeThunk(computation, localStateMap, BaseNode.TailStatus.NOT_TAIL)
.getValue();
return new Stateful(state, result);
}
@ -42,7 +46,9 @@ public abstract class RunStateNode extends Node {
Stateful doSingletonSameKey(
SingletonMap state, Object _this, Object key, Object local_state, Thunk computation) {
SingletonMap localStateContainer = new SingletonMap(state.getKey(), local_state);
Stateful res = thunkExecutorNode.executeThunk(computation, localStateContainer, false);
Stateful res =
thunkExecutorNode.executeThunk(
computation, localStateContainer, BaseNode.TailStatus.NOT_TAIL);
return new Stateful(state, res.getValue());
}
@ -63,7 +69,8 @@ public abstract class RunStateNode extends Node {
@Cached(value = "buildSmallKeys(cachedNewKey, cachedOldKey)", dimensions = 1)
Object[] newKeys) {
SmallMap localStateMap = new SmallMap(newKeys, new Object[] {local_state, state.getValue()});
Stateful res = thunkExecutorNode.executeThunk(computation, localStateMap, false);
Stateful res =
thunkExecutorNode.executeThunk(computation, localStateMap, BaseNode.TailStatus.NOT_TAIL);
Object newStateVal = ((SmallMap) res.getState()).getValues()[1];
return new Stateful(new SingletonMap(cachedOldKey, newStateVal), res.getValue());
}
@ -103,7 +110,8 @@ public abstract class RunStateNode extends Node {
System.arraycopy(state.getValues(), 0, newValues, 1, cachedOldKeys.length);
newValues[0] = local_state;
SmallMap localStateMap = new SmallMap(newKeys, newValues);
Stateful res = thunkExecutorNode.executeThunk(computation, localStateMap, false);
Stateful res =
thunkExecutorNode.executeThunk(computation, localStateMap, BaseNode.TailStatus.NOT_TAIL);
SmallMap resultStateMap = (SmallMap) res.getState();
Object[] resultValues = new Object[cachedOldKeys.length];
System.arraycopy(resultStateMap.getValues(), 1, resultValues, 0, cachedOldKeys.length);
@ -125,7 +133,8 @@ public abstract class RunStateNode extends Node {
System.arraycopy(state.getValues(), 0, newValues, 0, cachedOldKeys.length);
newValues[index] = local_state;
SmallMap localStateMap = new SmallMap(cachedOldKeys, newValues);
Stateful res = thunkExecutorNode.executeThunk(computation, localStateMap, false);
Stateful res =
thunkExecutorNode.executeThunk(computation, localStateMap, BaseNode.TailStatus.NOT_TAIL);
SmallMap resultStateMap = (SmallMap) res.getState();
Object[] resultValues = new Object[cachedOldKeys.length];
System.arraycopy(resultStateMap.getValues(), 0, resultValues, 0, cachedOldKeys.length);

View File

@ -3,6 +3,7 @@ package org.enso.interpreter.node.expression.builtin.thread;
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.thunk.ThunkExecutorNode;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.control.ThreadInterruptedException;
@ -16,11 +17,12 @@ public class WithInterruptHandlerNode extends Node {
private @Child ThunkExecutorNode actExecutorNode = ThunkExecutorNode.build();
private @Child ThunkExecutorNode handlerExecutorNode = ThunkExecutorNode.build();
Stateful execute(@MonadicState Object state, Object _this, Thunk action, Thunk interrupt_handler) {
Stateful execute(
@MonadicState Object state, Object _this, Thunk action, Thunk interrupt_handler) {
try {
return actExecutorNode.executeThunk(action, state, false);
return actExecutorNode.executeThunk(action, state, BaseNode.TailStatus.NOT_TAIL);
} catch (ThreadInterruptedException e) {
handlerExecutorNode.executeThunk(interrupt_handler, state, false);
handlerExecutorNode.executeThunk(interrupt_handler, state, BaseNode.TailStatus.NOT_TAIL);
throw e;
}
}

View File

@ -62,7 +62,7 @@ public abstract class EvalNode extends BaseNode {
RootCallTarget parseExpression(LocalScope scope, ModuleScope moduleScope, String expression) {
LocalScope localScope = scope.createChild();
InlineContext inlineContext = InlineContext.fromJava(localScope, moduleScope, isTail());
InlineContext inlineContext = InlineContext.fromJava(localScope, moduleScope, getTailStatus());
ExpressionNode expr =
lookupContextReference(Language.class)
.get()
@ -107,7 +107,7 @@ public abstract class EvalNode extends BaseNode {
RootCallTarget cachedCallTarget,
@Cached("build()") ThunkExecutorNode thunkExecutorNode) {
Thunk thunk = new Thunk(cachedCallTarget, callerInfo.getFrame());
return thunkExecutorNode.executeThunk(thunk, state, isTail());
return thunkExecutorNode.executeThunk(thunk, state, getTailStatus());
}
@Specialization
@ -123,6 +123,6 @@ public abstract class EvalNode extends BaseNode {
callerInfo.getModuleScope(),
toJavaStringNode.execute(expression));
Thunk thunk = new Thunk(callTarget, callerInfo.getFrame());
return thunkExecutorNode.executeThunk(thunk, state, isTail());
return thunkExecutorNode.executeThunk(thunk, state, getTailStatus());
}
}

View File

@ -5,7 +5,6 @@ import com.oracle.truffle.api.Truffle;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.interop.generic.*;
import org.enso.interpreter.node.expression.builtin.interop.syntax.*;
import org.enso.interpreter.node.expression.builtin.interop.syntax.GetArrayElementMethodGen;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
@ -36,7 +35,6 @@ public class Polyglot {
interopDispatchRoot = Truffle.getRuntime().createCallTarget(MethodDispatchNode.build(language));
interopDispatchSchema =
new FunctionSchema(
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
FunctionSchema.CallerFrameAccess.NONE,
new ArgumentDefinition[] {
new ArgumentDefinition(1, "this", ArgumentDefinition.ExecutionMode.EXECUTE),

View File

@ -3,10 +3,11 @@ package org.enso.interpreter.runtime.callable.argument;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import java.util.OptionalInt;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
/**
* Tracks simple information about call-site arguments, used to make processing of caller argument
@ -219,7 +220,6 @@ public class CallArgumentInfo {
newOversaturatedArgInfo.length);
return new FunctionSchema(
originalSchema.getCallStrategy(),
originalSchema.getCallerFrameAccess(),
definitions,
argumentUsed,

View File

@ -75,8 +75,7 @@ public class AtomConstructor implements TruffleObject {
ExpressionNode instantiateNode = InstantiateNode.build(this, argumentReaders);
RootNode rootNode = InstantiateAtomNode.build(null, name, instantiateNode);
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(rootNode);
return new Function(
callTarget, null, new FunctionSchema(FunctionSchema.CallStrategy.ALWAYS_DIRECT, args));
return new Function(callTarget, null, new FunctionSchema(args));
}
private void generateMethods(ArgumentDefinition[] args) {
@ -94,7 +93,6 @@ public class AtomConstructor implements TruffleObject {
callTarget,
null,
new FunctionSchema(
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE)));
definitionScope.registerMethod(
definitionScope.getAssociatedType(), this.name.toLowerCase(), function);
@ -107,7 +105,6 @@ public class AtomConstructor implements TruffleObject {
callTarget,
null,
new FunctionSchema(
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE)));
}

View File

@ -80,14 +80,13 @@ public final class Function implements TruffleObject {
* Creates a Function object from a {@link BuiltinRootNode} and argument definitions.
*
* @param node the {@link RootNode} for the function logic
* @param callStrategy the {@link FunctionSchema.CallStrategy} to use for this function
* @param args argument definitons
* @return a Function object with specified behavior and arguments
*/
public static Function fromBuiltinRootNode(
BuiltinRootNode node, FunctionSchema.CallStrategy callStrategy, ArgumentDefinition... args) {
BuiltinRootNode node, ArgumentDefinition... args) {
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(node);
FunctionSchema schema = new FunctionSchema(callStrategy, args);
FunctionSchema schema = new FunctionSchema(args);
return new Function(callTarget, null, schema);
}
@ -98,15 +97,14 @@ public final class Function implements TruffleObject {
* will be non-null.
*
* @param node the {@link RootNode} for the function logic
* @param callStrategy the {@link FunctionSchema.CallStrategy} to use for this function
* @param args argument definitons
* @return a Function object with specified behavior and arguments
*/
public static Function fromBuiltinRootNodeWithCallerFrameAccess(
BuiltinRootNode node, FunctionSchema.CallStrategy callStrategy, ArgumentDefinition... args) {
BuiltinRootNode node, ArgumentDefinition... args) {
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(node);
FunctionSchema schema =
new FunctionSchema(callStrategy, FunctionSchema.CallerFrameAccess.FULL, args);
new FunctionSchema(FunctionSchema.CallerFrameAccess.FULL, args);
return new Function(callTarget, null, schema);
}
@ -129,15 +127,6 @@ public final class Function implements TruffleObject {
return getCallTarget().getRootNode().getSourceSection();
}
/**
* Gets the call strategy that should be used for this function.
*
* @return this function's call strategy
*/
public FunctionSchema.CallStrategy getCallStrategy() {
return getSchema().getCallStrategy();
}
/**
* Gets the function's scope.
*

View File

@ -11,36 +11,6 @@ import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
* arguments positions.
*/
public class FunctionSchema {
/**
* Denotes the call strategy that should be used whenever a function with this schema is called.
*
* <p>For builtin functions, the algorithm for choosing the proper {@link CallStrategy} is as
* follows: if the node executes user-provided code ({@link
* org.enso.interpreter.runtime.callable.argument.Thunk} or a {@link Function}) using the Tail
* Call Optimization machinery (i.e. marking the execution node as tail position), the right
* choice is {@code DIRECT_WHEN_TAIL}. Otherwise {@code ALWAYS_DIRECT} should be chosen.
*/
public enum CallStrategy {
/** Always call the function directly. */
ALWAYS_DIRECT,
/** Call the function directly when said function is in tail call position. */
DIRECT_WHEN_TAIL,
/** Always call the function using standard tail call machinery. */
CALL_LOOP;
/**
* Should this function be called directly in the given position?
*
* @param isTail is this a tail position?
* @return {@code true} if the function should be called directly, {@code false} otherwise
*/
public boolean shouldCallDirect(boolean isTail) {
return this == ALWAYS_DIRECT || (this == DIRECT_WHEN_TAIL && isTail);
}
}
/** Denotes the caller frame access functions with this schema require to run properly. */
public enum CallerFrameAccess {
/** Requires full access to the (materialized) caller frame. */
@ -62,7 +32,6 @@ public class FunctionSchema {
private final @CompilationFinal(dimensions = 1) ArgumentDefinition[] argumentInfos;
private final @CompilationFinal(dimensions = 1) boolean[] hasPreApplied;
private final @CompilationFinal(dimensions = 1) CallArgumentInfo[] oversaturatedArguments;
private final CallStrategy callStrategy;
private final boolean hasAnyPreApplied;
private final boolean hasOversaturatedArguments;
private final CallerFrameAccess callerFrameAccess;
@ -70,7 +39,6 @@ public class FunctionSchema {
/**
* Creates an {@link FunctionSchema} instance.
*
* @param callStrategy the call strategy to use for functions having this schema
* @param callerFrameAccess the declaration of whether access to caller frame is required for this
* function
* @param argumentInfos Definition site arguments information
@ -80,12 +48,10 @@ public class FunctionSchema {
* this function so far
*/
public FunctionSchema(
CallStrategy callStrategy,
CallerFrameAccess callerFrameAccess,
ArgumentDefinition[] argumentInfos,
boolean[] hasPreApplied,
CallArgumentInfo[] oversaturatedArguments) {
this.callStrategy = callStrategy;
this.argumentInfos = argumentInfos;
this.oversaturatedArguments = oversaturatedArguments;
this.hasPreApplied = hasPreApplied;
@ -106,16 +72,13 @@ public class FunctionSchema {
* Creates an {@link FunctionSchema} instance assuming the function has no partially applied
* arguments.
*
* @param callStrategy the call strategy to use for this function
* @param callerFrameAccess the declaration of need to access the caller frame from the function
* @param argumentInfos Definition site arguments information
*/
public FunctionSchema(
CallStrategy callStrategy,
CallerFrameAccess callerFrameAccess,
ArgumentDefinition... argumentInfos) {
this(
callStrategy,
callerFrameAccess,
argumentInfos,
new boolean[argumentInfos.length],
@ -128,11 +91,10 @@ public class FunctionSchema {
*
* <p>Caller frame access is assumed to be {@link CallerFrameAccess#NONE}.
*
* @param callStrategy the call strategy to use for this function
* @param argumentInfos Definition site arguments information
*/
public FunctionSchema(CallStrategy callStrategy, ArgumentDefinition... argumentInfos) {
this(callStrategy, CallerFrameAccess.NONE, argumentInfos);
public FunctionSchema(ArgumentDefinition... argumentInfos) {
this(CallerFrameAccess.NONE, argumentInfos);
}
/**
@ -222,15 +184,6 @@ public class FunctionSchema {
return oversaturatedArguments;
}
/**
* Returns the call strategy to use for functions with this schema.
*
* @return the call strategy to use
*/
public CallStrategy getCallStrategy() {
return callStrategy;
}
/**
* Returns the caller frame access declaration for this function.
*

View File

@ -43,6 +43,7 @@ class Passes(passes: Option[List[PassGroup]] = None) {
IgnoredBindings,
TypeFunctions,
TypeSignatures,
Annotations,
AliasAnalysis,
UppercaseNames,
VectorLiterals,

View File

@ -834,6 +834,8 @@ object AstToIr {
} else {
buildName(identifier)
}
case AST.Ident.Annotation(name) =>
Name.Annotation(name, getIdentifiedLocation(identifier))
case AST.Ident.Cons(_) =>
buildName(identifier)
case AST.Ident.Blank(_) =>

View File

@ -19,6 +19,7 @@ import org.enso.compiler.pass.analyse.{
}
import org.enso.compiler.pass.optimise.ApplicationSaturation
import org.enso.compiler.pass.resolve.{
Annotations,
MethodDefinitions,
Patterns,
UppercaseNames
@ -51,6 +52,7 @@ import org.enso.interpreter.node.expression.literal.{
}
import org.enso.interpreter.node.scope.{AssignmentNode, ReadLocalVariableNode}
import org.enso.interpreter.node.{
BaseNode,
ClosureRootNode,
MethodRootNode,
ExpressionNode => RuntimeExpression
@ -270,10 +272,7 @@ class IrToTruffle(
new RuntimeFunction(
callTarget,
null,
new FunctionSchema(
FunctionSchema.CallStrategy.CALL_LOOP,
arguments: _*
)
new FunctionSchema(arguments: _*)
)
case _ =>
throw new CompilerError(
@ -303,6 +302,23 @@ class IrToTruffle(
.getOrElse(source.createUnavailableSection())
}
private def getTailStatus(
expression: IR.Expression
): BaseNode.TailStatus = {
val isTailPosition =
expression.getMetadata(TailCall).contains(TailCall.TailPosition.Tail)
val isTailAnnotated = expression.getMetadata(Annotations).isDefined
if (isTailPosition) {
if (isTailAnnotated) {
BaseNode.TailStatus.TAIL_LOOP
} else {
BaseNode.TailStatus.TAIL_DIRECT
}
} else {
BaseNode.TailStatus.NOT_TAIL
}
}
/** Sets the source section for a given expression node to the provided
* location.
*
@ -333,7 +349,6 @@ class IrToTruffle(
new EnsoProjectNode(language, context, pkg)
)
val schema = new FunctionSchema(
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(
0,
"this",
@ -352,7 +367,6 @@ class IrToTruffle(
),
null,
new FunctionSchema(
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(
0,
"this",
@ -460,11 +474,6 @@ class IrToTruffle(
* @return a truffle expression that represents the same program as `ir`
*/
def run(ir: IR.Expression): RuntimeExpression = {
val tailMeta = ir.unsafeGetMetadata(
TailCall,
"Missing tail call information on method."
)
val runtimeExpression = ir match {
case block: IR.Expression.Block => processBlock(block)
case literal: IR.Literal => processLiteral(literal)
@ -489,7 +498,7 @@ class IrToTruffle(
)
}
runtimeExpression.setTail(tailMeta)
runtimeExpression.setTailStatus(getTailStatus(ir))
runtimeExpression
}
@ -623,11 +632,6 @@ class IrToTruffle(
)
.unsafeAs[AliasAnalysis.Info.Scope.Child]
val branchIsTail = branch.unsafeGetMetadata(
TailCall,
"Case branch is missing tail position information."
)
val childProcessor = this.createChild("case_branch", scopeInfo.scope)
branch.pattern match {
@ -641,7 +645,6 @@ class IrToTruffle(
)
val branchNode = CatchAllBranchNode.build(branchCodeNode)
branchNode.setTail(branchIsTail)
Right(branchNode)
case cons @ Pattern.Constructor(constructor, _, _, _, _) =>
@ -707,7 +710,6 @@ class IrToTruffle(
} else {
ConstructorBranchNode.build(atomCons, branchCodeNode)
}
branchNode.setTail(branchIsTail)
branchNode
}
@ -861,6 +863,10 @@ class IrToTruffle(
passData
)
)
case _: IR.Name.Annotation =>
throw new CompilerError(
"Annotation should not be present at codegen time."
)
case _: IR.Name.Blank =>
throw new CompilerError(
"Blanks should not be present at codegen time."
@ -997,15 +1003,9 @@ class IrToTruffle(
} else seenArgNames.add(argName)
}
val bodyIsTail = body.unsafeGetMetadata(
TailCall,
"Function body missing tail call information."
)
val bodyExpr = this.run(body)
val fnBodyNode = BlockNode.build(argExpressions.toArray, bodyExpr)
fnBodyNode.setTail(bodyIsTail)
(fnBodyNode, argDefinitions)
}
@ -1188,12 +1188,7 @@ class IrToTruffle(
val result = if (!shouldSuspend) {
argumentExpression
} else {
val argExpressionIsTail = value.unsafeGetMetadata(
TailCall,
"Argument with missing tail call information."
)
argumentExpression.setTail(argExpressionIsTail)
argumentExpression.setTailStatus(getTailStatus(value))
val displayName =
s"argument<${name.map(_.name).getOrElse(String.valueOf(position))}>"

View File

@ -1,6 +1,7 @@
package org.enso.compiler.context
import org.enso.compiler.pass.PassConfiguration
import org.enso.interpreter.node.BaseNode.TailStatus
import org.enso.interpreter.runtime.scope.{LocalScope, ModuleScope}
import org.enso.interpreter.runtime.Module
@ -35,12 +36,12 @@ object InlineContext {
def fromJava(
localScope: LocalScope,
moduleScope: ModuleScope,
isInTailPosition: Boolean
isInTailPosition: TailStatus
): InlineContext = {
InlineContext(
localScope = Option(localScope),
module = moduleScope.getModule,
isInTailPosition = Option(isInTailPosition)
isInTailPosition = Option(isInTailPosition != TailStatus.NOT_TAIL)
)
}
}

View File

@ -2084,6 +2084,82 @@ object IR {
override def showCode(indent: Int): String = name
}
/** The representation of an annotation name.
*
* @param name the annotation text of the name
* @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 Annotation(
override val name: String,
override val location: Option[IdentifiedLocation],
override val passData: MetadataStorage = MetadataStorage(),
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
) extends Name {
override protected var id: Identifier = randomId
/** Creates a copy of `this`.
*
* @param name the annotation text of the name
* @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
* @param id the identifier for the new node
* @return a copy of `this`, updated with the specified values
*/
def copy(
name: String = name,
location: Option[IdentifiedLocation] = location,
passData: MetadataStorage = passData,
diagnostics: DiagnosticStorage = diagnostics,
id: Identifier = id
): Annotation = {
val res = Annotation(name, location, passData, diagnostics)
res.id = id
res
}
override def duplicate(
keepLocations: Boolean = true,
keepMetadata: Boolean = true,
keepDiagnostics: Boolean = true
): Annotation =
copy(
location = if (keepLocations) location else None,
passData =
if (keepMetadata) passData.duplicate else MetadataStorage(),
diagnostics =
if (keepDiagnostics) diagnostics.copy else DiagnosticStorage(),
id = randomId
)
override def setLocation(
location: Option[IdentifiedLocation]
): Annotation =
copy(location = location)
override def mapExpressions(fn: Expression => Expression): Annotation =
this
override def toString: String =
s"""
|IR.Name.Annotation(
|name = $name,
|location = $location,
|passData = ${this.showPassData},
|diagnostics = $diagnostics,
|id = $id
|)
|""".toSingleLine
override def children: List[IR] = List()
override def isReferent: Boolean = false
override def showCode(indent: Int): String = name
}
/** A representation of the name `this`, used to refer to the current type.
*
* @param location the source location that the node corresponds to
@ -5196,6 +5272,17 @@ object IR {
}
}
/**
* A warning about a `@Tail_Call` annotation placed in a non-tail
* position.
* @param location the location of the annotated application
*/
case class WrongTco(override val location: Option[IdentifiedLocation])
extends Warning {
override def message: String =
"A @Tail_Call annotation was placed in a non-tail-call position."
}
/** Warnings about shadowing names. */
sealed trait Shadowed extends Warning {
@ -5343,6 +5430,24 @@ object IR {
" importing the default definition from the Base.Vector module."
}
/**
* An error coming from an unknown annotation name.
*/
case object UnknownAnnotation extends Reason {
override def explain(originalName: Name): String =
s"The annotation ${originalName.name} is not defined."
}
/**
* An error coming from a tail call annotation placed in a syntactically
* incorrect position.
*/
case object UnexpectedTailCallAnnotation extends Reason {
override def explain(originalName: Name): String =
s"Unexpected @TailCall annotation. This annotation can only be " +
s"used with function applications."
}
/**
* An error coming from an unexpected occurence of a polyglot symbol.
*

View File

@ -166,6 +166,11 @@ case object DemandAnalysis extends IRPass {
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 _: 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 " +

View File

@ -7,6 +7,7 @@ import org.enso.compiler.core.ir.MetadataStorage._
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.desugar._
import org.enso.compiler.pass.resolve.Annotations
/** This pass performs tail call analysis on the Enso IR.
*
@ -128,14 +129,22 @@ case object TailCall extends IRPass {
expression: IR.Expression,
isInTailPosition: Boolean
): IR.Expression = {
expression match {
val expressionWithWarning =
if (
expression
.getMetadata(Annotations)
.contains(Annotations.TailCallAnnotated) && !isInTailPosition
) expression.addDiagnostic(IR.Warning.WrongTco(expression.location))
else expression
expressionWithWarning match {
case empty: IR.Empty =>
empty.updateMetadata(this -->> TailPosition.NotTail)
case function: IR.Function => analyseFunction(function, isInTailPosition)
case caseExpr: IR.Case => analyseCase(caseExpr, isInTailPosition)
case typ: IR.Type => analyseType(typ, isInTailPosition)
case app: IR.Application => analyseApplication(app, isInTailPosition)
case name: IR.Name => analyseName(name, isInTailPosition)
case function: IR.Function =>
analyseFunction(function, isInTailPosition)
case caseExpr: IR.Case => analyseCase(caseExpr, isInTailPosition)
case typ: IR.Type => analyseType(typ, isInTailPosition)
case app: IR.Application => analyseApplication(app, isInTailPosition)
case name: IR.Name => analyseName(name, isInTailPosition)
case foreign: IR.Foreign =>
foreign.updateMetadata(this -->> TailPosition.NotTail)
case literal: IR.Literal => analyseLiteral(literal, isInTailPosition)
@ -143,11 +152,19 @@ case object TailCall extends IRPass {
throw new CompilerError(
"Comments should not be present during tail call analysis."
)
case block @ IR.Expression.Block(expressions, returnValue, _, _, _, _) =>
case block @ IR.Expression.Block(
expressions,
returnValue,
_,
_,
_,
_
) =>
block
.copy(
expressions =
expressions.map(analyseExpression(_, isInTailPosition = false)),
expressions = expressions.map(
analyseExpression(_, isInTailPosition = false)
),
returnValue = analyseExpression(returnValue, isInTailPosition)
)
.updateMetadata(this -->> TailPosition.fromBool(isInTailPosition))
@ -158,7 +175,9 @@ case object TailCall extends IRPass {
)
.updateMetadata(this -->> TailPosition.fromBool(isInTailPosition))
case err: IR.Diagnostic =>
err.updateMetadata(this -->> TailPosition.fromBool(isInTailPosition))
err.updateMetadata(
this -->> TailPosition.fromBool(isInTailPosition)
)
}
}

View File

@ -312,12 +312,13 @@ case object LambdaConsolidate extends IRPass {
case defSpec: IR.DefinitionArgument.Specified => defSpec.name.name
}
)
case ths: IR.Name.This => ths
case here: IR.Name.Here => here
case blank: IR.Name.Blank => blank
case ref: IR.Name.MethodReference => ref
case qual: IR.Name.Qualified => qual
case err: IR.Error.Resolution => err
case ths: IR.Name.This => ths
case here: IR.Name.Here => here
case blank: IR.Name.Blank => blank
case ref: IR.Name.MethodReference => ref
case qual: IR.Name.Qualified => qual
case err: IR.Error.Resolution => err
case annotation: IR.Name.Annotation => annotation
}
} else {
name

View File

@ -0,0 +1,105 @@
package org.enso.compiler.pass.resolve
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.core.ir.MetadataStorage.ToPair
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.IRPass
case object Annotations extends IRPass {
case object TailCallAnnotated extends IRPass.Metadata {
override val metadataName: String = "TailCallAnnotated"
override def duplicate(): Option[IRPass.Metadata] = Some(this)
}
val tailCallName = "@Tail_Call"
/** The type of the metadata object that the pass writes to the IR. */
override type Metadata = TailCallAnnotated.type
/** The type of configuration for the pass. */
override type Config = IRPass.Configuration.Default
/** The passes that this pass depends _directly_ on to run. */
override val precursorPasses: Seq[IRPass] = Seq()
/** The passes that are invalidated by running this pass. */
override val invalidatedPasses: Seq[IRPass] = Seq()
/** Executes the pass on the provided `ir`, and returns a possibly transformed
* or annotated version of `ir`.
*
* @param ir the Enso IR to process
* @param moduleContext a context object that contains the information needed
* to process a module
* @return `ir`, possibly having made transformations or annotations to that
* IR.
*/
override def runModule(
ir: IR.Module,
moduleContext: ModuleContext
): IR.Module = {
ir.mapExpressions(doExpression)
}
/** Executes the pass on the provided `ir`, and returns a possibly transformed
* or annotated version of `ir` in an inline context.
*
* @param ir the Enso IR to process
* @param inlineContext a context object that contains the information needed
* for inline evaluation
* @return `ir`, possibly having made transformations or annotations to that
* IR.
*/
override def runExpression(
ir: IR.Expression,
inlineContext: InlineContext
): IR.Expression = {
doExpression(ir)
}
private def doExpression(
ir: IR.Expression
): IR.Expression =
ir.transformExpressions {
case app @ IR.Application.Prefix(
ann: IR.Name.Annotation,
arguments,
_,
_,
_,
_
) =>
if (ann.name == tailCallName) {
arguments match {
case List() =>
throw new CompilerError(
"Impossible, application with no arguments."
)
case List(arg) =>
doExpression(arg.value)
.updateMetadata(this -->> TailCallAnnotated)
case realFun :: args =>
val recurFun = doExpression(realFun.value)
val recurArgs = args.map(_.mapExpressions(doExpression))
app
.copy(function = recurFun, arguments = recurArgs)
.updateMetadata(this -->> TailCallAnnotated)
}
} else {
val err =
IR.Error.Resolution(ann, IR.Error.Resolution.UnknownAnnotation)
app.copy(function = err)
}
case ann: IR.Name.Annotation =>
if (ann.name == tailCallName) {
IR.Error.Resolution(
ann,
IR.Error.Resolution.UnexpectedTailCallAnnotation
)
} else {
IR.Error.Resolution(ann, IR.Error.Resolution.UnknownAnnotation)
}
}
}

View File

@ -8,7 +8,15 @@ import org.enso.compiler.pass.analyse.{AliasAnalysis, BindingAnalysis}
import org.enso.compiler.pass.desugar._
import org.enso.compiler.pass.lint.ShadowedPatternFields
import org.enso.compiler.pass.optimise.UnreachableMatchBranches
import org.enso.compiler.pass.resolve.{DocumentationComments, IgnoredBindings, MethodDefinitions, ModuleThisToHere, TypeFunctions, TypeSignatures}
import org.enso.compiler.pass.resolve.{
Annotations,
DocumentationComments,
IgnoredBindings,
MethodDefinitions,
ModuleThisToHere,
TypeFunctions,
TypeSignatures
}
class PassesTest extends CompilerTest {
@ -56,7 +64,8 @@ class PassesTest extends CompilerTest {
NestedPatternMatch,
IgnoredBindings,
TypeFunctions,
TypeSignatures
TypeSignatures,
Annotations
)
)
}

View File

@ -149,9 +149,9 @@ class TailCallTest extends CompilerTest {
val ir =
"""
|a -> b -> c ->
| d = a + b
| d = @Tail_Call (a + b)
| e = a * c
| d + e
| @Tail_Call (d + e)
|""".stripMargin.preprocessExpression.get.analyse
.asInstanceOf[IR.Function.Lambda]
@ -170,6 +170,22 @@ class TailCallTest extends CompilerTest {
)
)
}
"warn about misplaced @TailCall annotations" in {
fnBody
.expressions(0)
.asInstanceOf[IR.Expression.Binding]
.expression
.diagnostics
.filter(_.isInstanceOf[IR.Warning.WrongTco])
.toList
.length shouldEqual 1
fnBody.returnValue.diagnostics
.filter(_.isInstanceOf[IR.Warning.WrongTco])
.toList
.length shouldEqual 0
}
}
"Tail call analysis on case expressions" should {

View File

@ -0,0 +1,100 @@
package org.enso.compiler.test.pass.resolve
import org.enso.compiler.Passes
import org.enso.compiler.context.{FreshNameSupply, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.pass.resolve.Annotations
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.test.CompilerTest
class AnnotationsTest extends CompilerTest {
// === Test Setup ===========================================================
def mkModuleContext: ModuleContext =
buildModuleContext(
freshNameSupply = Some(new FreshNameSupply)
)
val passes = new Passes
val precursorPasses: PassGroup =
passes.getPrecursors(Annotations).get
val passConfiguration: PassConfiguration = PassConfiguration()
implicit val passManager: PassManager =
new PassManager(List(precursorPasses), passConfiguration)
/** Adds an extension method to analyse an Enso module.
*
* @param ir the ir to analyse
*/
implicit class AnalyseModule(ir: IR.Module) {
/** Performs tail call analysis on [[ir]].
*
* @param context the module context in which analysis takes place
* @return [[ir]], with tail call analysis metadata attached
*/
def analyse(implicit context: ModuleContext): IR.Module = {
Annotations.runModule(ir, context)
}
}
// === The Tests ============================================================
"Annotations resolution" should {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
"""
|foo x =
| @Tail_Call
| @Unknown_Annotation foo bar baz
| foo @Tail_Call
| foo (@Tail_Call bar baz)
|""".stripMargin.preprocessModule.analyse
"resolve and mark annotations" in {
val items = ir.bindings.head
.asInstanceOf[IR.Module.Scope.Definition.Method.Explicit]
.body
.asInstanceOf[IR.Function.Lambda]
.body
.asInstanceOf[IR.Expression.Block]
items.expressions(0) shouldBe an[IR.Error.Resolution]
items
.expressions(0)
.asInstanceOf[IR.Error.Resolution]
.reason shouldEqual IR.Error.Resolution.UnexpectedTailCallAnnotation
val unknown =
items.expressions(1).asInstanceOf[IR.Application.Prefix].function
unknown shouldBe an[IR.Error.Resolution]
unknown
.asInstanceOf[IR.Error.Resolution]
.reason shouldEqual IR.Error.Resolution.UnknownAnnotation
val misplaced = items
.expressions(2)
.asInstanceOf[IR.Application.Prefix]
.arguments(0)
.value
misplaced shouldBe an[IR.Error.Resolution]
misplaced
.asInstanceOf[IR.Error.Resolution]
.reason shouldEqual IR.Error.Resolution.UnexpectedTailCallAnnotation
val correct = items
.returnValue
.asInstanceOf[IR.Application.Prefix]
.arguments(0)
.value
.asInstanceOf[IR.Application.Prefix]
correct.function.asInstanceOf[IR.Name].name shouldEqual "bar"
correct.arguments.length shouldEqual 1
correct.getMetadata(Annotations) should contain(Annotations.TailCallAnnotated)
}
}
}

View File

@ -2756,6 +2756,11 @@ class RuntimeServerTest
Some(
model.Range(model.Position(2, 14), model.Position(2, 23))
)
),
Api.StackTraceElement(
"Main.main",
Some(mainFile),
Some(model.Range(model.Position(0, 7), model.Position(0, 19)))
)
)
)
@ -2823,6 +2828,11 @@ class RuntimeServerTest
Some(
model.Range(model.Position(2, 10), model.Position(2, 15))
)
),
Api.StackTraceElement(
"Main.main",
Some(mainFile),
Some(model.Range(model.Position(0, 7), model.Position(0, 23)))
)
)
)
@ -2976,6 +2986,11 @@ class RuntimeServerTest
"Main.foo",
Some(mainFile),
Some(model.Range(model.Position(5, 8), model.Position(5, 16)))
),
Api.StackTraceElement(
"Main.main",
Some(mainFile),
Some(model.Range(model.Position(2, 7), model.Position(2, 15)))
)
)
)

View File

@ -74,12 +74,12 @@ class ExpressionIdTest extends InterpreterTest {
val id1 = meta.addItem(106, 5)
val id2 = meta.addItem(124, 1)
val id3 = meta.addItem(120, 7)
val id4 = meta.addItem(133, 9)
val id4 = meta.addItem(132, 9)
instrumenter.assertNodeExists(id1, "30")
instrumenter.assertNodeExists(id2, "10")
instrumenter.assertNodeExistsTail(id3)
instrumenter.assertNodeExistsTail(id4)
instrumenter.assertNodeExists(id3, "30")
instrumenter.assertNodeExists(id4, "30")
eval(meta.appendToCode(code))
}

View File

@ -34,7 +34,7 @@ class GroupingTest extends InterpreterTest {
"""
|main =
| ifTest = c -> (~ifT) -> ~ifF -> if c == 0 then ifT else ifF
| sum = c -> acc -> ifTest c acc (sum c-1 acc+c)
| sum = c -> acc -> ifTest c acc (@Tail_Call sum c-1 acc+c)
| sum 10000 0
|""".stripMargin

View File

@ -25,7 +25,7 @@ class RuntimeManagementTest extends InterpreterTest {
|
|foo x =
| if x == 0 then IO.println "Start." else Unit
| here.foo x+1
| @Tail_Call here.foo x+1
|
|main =
| Thread.with_interrupt_handler (here.foo 0) (IO.println "Interrupted.")

View File

@ -38,7 +38,7 @@ class SuspendedArgumentsTest extends InterpreterTest {
"""
|main =
| ifTest = c -> ~ifT -> ~ifF -> if c == 0 then ifT else ifF
| sum = c -> acc -> ifTest c acc (sum c-1 acc+c)
| sum = c -> acc -> ifTest c acc (@Tail_Call sum c-1 acc+c)
| sum 10000 0
|""".stripMargin
eval(code) shouldEqual 50005000

View File

@ -17,7 +17,4 @@ public @interface BuiltinMethod {
/** @return a short description of this method. */
String description();
/** @return whether it is safe to always call this function directly. */
boolean alwaysDirect() default true;
}

View File

@ -122,11 +122,6 @@ public class MethodProcessor extends AbstractProcessor {
out.println(" public static Function makeFunction(Language language) {");
out.println(" return Function." + functionBuilderMethod + "(");
out.println(" new " + methodDefinition.getClassName() + "(language),");
if (methodDefinition.isAlwaysDirect()) {
out.println(" FunctionSchema.CallStrategy.ALWAYS_DIRECT,");
} else {
out.println(" FunctionSchema.CallStrategy.DIRECT_WHEN_TAIL,");
}
List<String> argumentDefs = new ArrayList<>();
for (MethodDefinition.ArgumentDefinition arg : methodDefinition.getArguments()) {
if (arg.isPositional()) {

View File

@ -172,11 +172,6 @@ public class MethodDefinition {
return needsCallerInfo;
}
/** @return whether this method can be always safely called directly. */
public boolean isAlwaysDirect() {
return annotation.alwaysDirect();
}
public String getConstructorExpression() {
return constructorExpression;
}

View File

@ -197,6 +197,7 @@ object Shape extends ShapeImplicit {
final case class Opr[T](name: String) extends Ident[T] {
val (prec, assoc) = opr.Info.of(name)
}
final case class Annotation[T](name: String) extends Ident[T]
final case class InvalidSuffix[T](elem: AST.Ident, suffix: String)
extends Invalid[T]
with Phantom
@ -450,6 +451,13 @@ object Shape extends ShapeImplicit {
implicit def ozip[T]: OffsetZip[Opr, T] = t => t.coerce
implicit def span[T]: HasSpan[Opr[T]] = t => t.name.length
}
object Annotation {
implicit def ftor: Functor[Annotation] = semi.functor
implicit def fold: Foldable[Annotation] = semi.foldable
implicit def repr[T]: Repr[Annotation[T]] = _.name
implicit def ozip[T]: OffsetZip[Annotation, T] = t => t.coerce
implicit def span[T]: HasSpan[Annotation[T]] = t => t.name.length
}
object InvalidSuffix {
implicit def ftor: Functor[InvalidSuffix] = semi.functor
implicit def fold: Foldable[InvalidSuffix] = semi.foldable
@ -1140,6 +1148,7 @@ sealed trait ShapeImplicit {
case s: Var[T] => s.repr
case s: Cons[T] => s.repr
case s: Opr[T] => s.repr
case s: Annotation[T] => s.repr
case s: Mod[T] => s.repr
case s: InvalidSuffix[T] => s.repr
case s: Number[T] => s.repr
@ -1182,6 +1191,7 @@ sealed trait ShapeImplicit {
case s: Var[T] => OffsetZip[Var, T].zipWithOffset(s)
case s: Cons[T] => OffsetZip[Cons, T].zipWithOffset(s)
case s: Opr[T] => OffsetZip[Opr, T].zipWithOffset(s)
case s: Annotation[T] => OffsetZip[Annotation, T].zipWithOffset(s)
case s: Mod[T] => OffsetZip[Mod, T].zipWithOffset(s)
case s: InvalidSuffix[T] => OffsetZip[InvalidSuffix, T].zipWithOffset(s)
case s: Number[T] => OffsetZip[Number, T].zipWithOffset(s)
@ -1225,6 +1235,7 @@ sealed trait ShapeImplicit {
case s: Var[T] => s.span()
case s: Cons[T] => s.span()
case s: Opr[T] => s.span()
case s: Annotation[T] => s.span()
case s: Mod[T] => s.span()
case s: InvalidSuffix[T] => s.span()
case s: Number[T] => s.span()
@ -1685,28 +1696,31 @@ object AST {
//// Reexports ////
type Blank = Ident.Blank
type Var = Ident.Var
type Cons = Ident.Cons
type Opr = Ident.Opr
type Mod = Ident.Mod
type Blank = Ident.Blank
type Var = Ident.Var
type Cons = Ident.Cons
type Opr = Ident.Opr
type Mod = Ident.Mod
type Annotation = Ident.Annotation
val Blank = Ident.Blank
val Var = Ident.Var
val Cons = Ident.Cons
val Opr = Ident.Opr
val Mod = Ident.Mod
val Blank = Ident.Blank
val Var = Ident.Var
val Cons = Ident.Cons
val Opr = Ident.Opr
val Mod = Ident.Mod
val Annotation = Ident.Annotation
//// Definition ////
type Ident = ASTOf[Shape.Ident]
object Ident {
type Blank = ASTOf[Shape.Blank]
type Var = ASTOf[Shape.Var]
type Cons = ASTOf[Shape.Cons]
type Mod = ASTOf[Shape.Mod]
type Opr = ASTOf[Shape.Opr]
type Blank = ASTOf[Shape.Blank]
type Var = ASTOf[Shape.Var]
type Cons = ASTOf[Shape.Cons]
type Mod = ASTOf[Shape.Mod]
type Opr = ASTOf[Shape.Opr]
type Annotation = ASTOf[Shape.Annotation]
type InvalidSuffix = ASTOf[Shape.InvalidSuffix]
@ -1768,6 +1782,11 @@ object AST {
def unapply(t: AST) = Unapply[Opr].run(_.name)(t)
def apply(name: String): Opr = Shape.Opr[AST](name)
}
object Annotation {
val any = UnapplyByType[Annotation]
def unapply(t: AST) = Unapply[Annotation].run(_.name)(t)
def apply(name: String): Annotation = Shape.Annotation[AST](name)
}
}
//////////////////////////////////////////////////////////////////////////////

View File

@ -64,17 +64,18 @@ object Pattern {
final case class Cls (cls : Class , pat : P) extends P
/** Token Patterns */
final case class Tok (spaced : Spaced, ast : AST) extends P
final case class Blank (spaced : Spaced) extends P
final case class Var (spaced : Spaced) extends P
final case class Cons (spaced : Spaced) extends P
final case class Opr (spaced : Spaced, maxPrec: Option[Int]) extends P
final case class Mod (spaced : Spaced) extends P
final case class Num (spaced : Spaced) extends P
final case class Text (spaced : Spaced) extends P
final case class Block (spaced : Spaced) extends P
final case class Macro (spaced : Spaced) extends P
final case class Invalid (spaced : Spaced) extends P
final case class Tok (spaced : Spaced, ast : AST) extends P
final case class Blank (spaced : Spaced) extends P
final case class Var (spaced : Spaced) extends P
final case class Cons (spaced : Spaced) extends P
final case class Opr (spaced : Spaced, maxPrec: Option[Int]) extends P
final case class Annotation(spaced : Spaced) extends P
final case class Mod (spaced : Spaced) extends P
final case class Num (spaced : Spaced) extends P
final case class Text (spaced : Spaced) extends P
final case class Block (spaced : Spaced) extends P
final case class Macro (spaced : Spaced) extends P
final case class Invalid (spaced : Spaced) extends P
// format: on
//// Smart Constructors ////
@ -95,6 +96,10 @@ object Pattern {
def apply(spaced: Spaced): Opr = Opr(spaced, None)
def apply(spaced: Boolean): Opr = Opr(Some(spaced))
}
object Annotation {
def apply(): Annotation = Annotation(None)
def apply(spaced: Boolean): Annotation = Annotation(Some(spaced))
}
object Num {
def apply(): Num = Num(None)
def apply(spaced: Boolean): Num = Num(Some(spaced))
@ -113,6 +118,7 @@ object Pattern {
Var(spaced) |
Cons(spaced) |
Opr(spaced) |
Annotation(spaced) |
Mod(spaced) |
Num(spaced) |
Text(spaced) |
@ -183,17 +189,18 @@ object Pattern {
final case class Cls [T](pat:P.Cls , elem:M[T]) extends M[T]
/** Token Matches */
final case class Tok [T](pat:P.Tok , elem:T) extends M[T]
final case class Blank [T](pat:P.Blank , elem:T) extends M[T]
final case class Var [T](pat:P.Var , elem:T) extends M[T]
final case class Cons [T](pat:P.Cons , elem:T) extends M[T]
final case class Opr [T](pat:P.Opr , elem:T) extends M[T]
final case class Mod [T](pat:P.Mod , elem:T) extends M[T]
final case class Num [T](pat:P.Num , elem:T) extends M[T]
final case class Text [T](pat:P.Text , elem:T) extends M[T]
final case class Block [T](pat:P.Block , elem:T) extends M[T]
final case class Macro [T](pat:P.Macro , elem:T) extends M[T]
final case class Invalid [T](pat:P.Invalid , elem:T) extends M[T]
final case class Tok [T](pat:P.Tok , elem:T) extends M[T]
final case class Blank [T](pat:P.Blank , elem:T) extends M[T]
final case class Var [T](pat:P.Var , elem:T) extends M[T]
final case class Cons [T](pat:P.Cons , elem:T) extends M[T]
final case class Opr [T](pat:P.Opr , elem:T) extends M[T]
final case class Annotation[T](pat:P.Annotation , elem:T) extends M[T]
final case class Mod [T](pat:P.Mod , elem:T) extends M[T]
final case class Num [T](pat:P.Num , elem:T) extends M[T]
final case class Text [T](pat:P.Text , elem:T) extends M[T]
final case class Block [T](pat:P.Block , elem:T) extends M[T]
final case class Macro [T](pat:P.Macro , elem:T) extends M[T]
final case class Invalid [T](pat:P.Invalid , elem:T) extends M[T]
// format: on
//// Smart Constructors ////
@ -228,28 +235,29 @@ object Pattern {
@nowarn("cat=unchecked")
def mapStructShallow(f: MatchOf[T] => MatchOf[T]): MatchOf[T] =
this match {
case m: M.Begin[T] => m
case m: M.End[T] => m
case m: M.Nothing[T] => m
case m: M.Seq[T] => m.copy(elem = m.elem.bimap(f, f))
case m: M.Or[T] => m.copy(elem = m.elem.bimap(f, f))
case m: M.Many[T] => m.copy(elem = m.elem.map(f))
case m: M.Except[T] => m.copy(elem = f(m.elem))
case m: M.Build[T] => m
case m: M.Err[T] => m
case m: M.Tag[T] => m.copy(elem = f(m.elem))
case m: M.Cls[T] => m.copy(elem = f(m.elem))
case m: M.Tok[T] => m
case m: M.Blank[T] => m
case m: M.Var[T] => m
case m: M.Cons[T] => m
case m: M.Opr[T] => m
case m: M.Mod[T] => m
case m: M.Num[T] => m
case m: M.Text[T] => m
case m: M.Block[T] => m
case m: M.Macro[T] => m
case m: M.Invalid[T] => m
case m: M.Begin[T] => m
case m: M.End[T] => m
case m: M.Nothing[T] => m
case m: M.Seq[T] => m.copy(elem = m.elem.bimap(f, f))
case m: M.Or[T] => m.copy(elem = m.elem.bimap(f, f))
case m: M.Many[T] => m.copy(elem = m.elem.map(f))
case m: M.Except[T] => m.copy(elem = f(m.elem))
case m: M.Build[T] => m
case m: M.Err[T] => m
case m: M.Tag[T] => m.copy(elem = f(m.elem))
case m: M.Cls[T] => m.copy(elem = f(m.elem))
case m: M.Tok[T] => m
case m: M.Blank[T] => m
case m: M.Var[T] => m
case m: M.Cons[T] => m
case m: M.Opr[T] => m
case m: M.Annotation[T] => m
case m: M.Mod[T] => m
case m: M.Num[T] => m
case m: M.Text[T] => m
case m: M.Block[T] => m
case m: M.Macro[T] => m
case m: M.Invalid[T] => m
}
@nowarn("cat=unchecked")
@ -304,6 +312,8 @@ object Pattern {
case m: M.Cons[T] =>
(m.copy(elem = f(off, m.elem)), off + m.elem.span())
case m: M.Opr[T] => (m.copy(elem = f(off, m.elem)), off + m.elem.span())
case m: M.Annotation[T] =>
(m.copy(elem = f(off, m.elem)), off + m.elem.span())
case m: M.Mod[T] => (m.copy(elem = f(off, m.elem)), off + m.elem.span())
case m: M.Num[T] => (m.copy(elem = f(off, m.elem)), off + m.elem.span())
case m: M.Text[T] =>
@ -534,6 +544,8 @@ sealed trait Pattern {
matchByCls[AST.Opr](spaced) { sast =>
Option.when(maxPrec.forall(_ >= sast.wrapped.prec))(M.Opr(p, sast))
}
case p @ P.Annotation(spaced) =>
matchByCls_[AST.Annotation](spaced, M.Annotation(p, _))
case p @ P.Mod(spaced) => matchByCls_[AST.Mod](spaced, M.Mod(p, _))
case p @ P.Macro(spaced) =>

View File

@ -180,21 +180,23 @@ case class ParserDef() extends flexer.Parser[AST.Module] {
if (current.isDefined) submit()
}
val char: Pattern = alphaNum | '_'
val body: Pattern = char.many >> '\''.many
val _var: Pattern = lowerLetter >> body
val cons: Pattern = upperLetter >> body
val breaker: String = "^`!@#$%^&*()-=+[]{}|;:<>,./ \t\r\n\\"
val errSfx: Pattern = noneOf(breaker).many1
val char: Pattern = alphaNum | '_'
val body: Pattern = char.many >> '\''.many
val _var: Pattern = lowerLetter >> body
val cons: Pattern = upperLetter >> body
val annotation: Pattern = "@" >> cons
val breaker: String = "^`!@#$%^&*()-=+[]{}|;:<>,./ \t\r\n\\"
val errSfx: Pattern = noneOf(breaker).many1
val SFX_CHECK = state.define("Identifier Suffix Check")
}
ROOT || ident._var || ident.on(AST.Var(_))
ROOT || ident.cons || ident.on(AST.Cons(_))
ROOT || "_" || ident.on(AST.Blank())
ident.SFX_CHECK || ident.errSfx || ident.onErrSfx()
ident.SFX_CHECK || always || ident.onNoErrSfx()
ROOT || ident._var || ident.on(AST.Var(_))
ROOT || ident.cons || ident.on(AST.Cons(_))
ROOT || "_" || ident.on(AST.Blank())
ROOT || ident.annotation || ident.on(AST.Annotation(_))
ident.SFX_CHECK || ident.errSfx || ident.onErrSfx()
ident.SFX_CHECK || always || ident.onNoErrSfx()
//////////////////
//// Operator ////

View File

@ -77,16 +77,17 @@ class ParserTest extends AnyFlatSpec with Matchers {
//// Identifiers /////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
"_" ?= "_"
"Name" ?= "Name"
"name" ?= "name"
"name'" ?= "name'"
"name''" ?= "name''"
"name'a" ?= Ident.InvalidSuffix("name'", "a")
"name_" ?= "name_"
"name_'" ?= "name_'"
"name'_" ?= Ident.InvalidSuffix("name'", "_")
"name`" ?= "name" $ Invalid.Unrecognized("`")
"_" ?= "_"
"Name" ?= "Name"
"name" ?= "name"
"name'" ?= "name'"
"name''" ?= "name''"
"name'a" ?= Ident.InvalidSuffix("name'", "a")
"name_" ?= "name_"
"name_'" ?= "name_'"
"name'_" ?= Ident.InvalidSuffix("name'", "_")
"name`" ?= "name" $ Invalid.Unrecognized("`")
"@Annotation" ?= Ident.Annotation("@Annotation")
//////////////////////////////////////////////////////////////////////////////
//// Operators ///////////////////////////////////////////////////////////////

View File

@ -4,21 +4,13 @@ import Base.Bench_Utils
gen_list len = 0.upto len . fold Nil (l -> i -> Cons i+1 l)
sum_vec vec =
arr = vec.to_array
len = vec.length
sumator = acc -> idx ->
if idx == len then acc else sumator (acc + arr.at idx) idx+1
res = sumator 0 0
res
sum_list_meta list =
nil_cons = Meta.meta Nil . constructor
folder acc list =
meta_list = Meta.meta list
if meta_list.constructor == nil_cons then acc else
fs = meta_list.fields
folder (acc + fs.at 0) (fs.at 1)
@Tail_Call folder (acc + fs.at 0) (fs.at 1)
res = folder 0 list
res

View File

@ -8,27 +8,31 @@ type Sum
sum_tco = sum_to ->
summator = acc -> current ->
if current == 0 then acc else summator acc+current current-1
if current == 0 then acc else
@Tail_Call summator acc+current current-1
res = summator 0 sum_to
res
sum_tco_decimal = sum_to ->
s = sum_to.to_decimal
summator = acc -> current ->
if current >= s then acc else summator acc+current current+1.0
if current >= s then acc else
@Tail_Call summator acc+current current+1.0
res = summator 0.0 0.0
res
sum_tco_eval = sumTo ->
summator = acc -> current ->
if current == 0 then acc else Debug.eval "summator (acc + current) (current - 1)"
if current == 0 then acc else
Debug.eval "@Tail_Call summator (acc + current) (current - 1)"
res = summator 0 sumTo
res
sum_tco_java = sum_to ->
summator = acc -> current ->
if current == 0 then acc else summator (Long.sum [acc, current]) (current - 1)
if current == 0 then acc else
@Tail_Call summator (Long.sum [acc, current]) (current - 1)
res = summator 0 sum_to
res
@ -37,7 +41,7 @@ sum_co_state_body =
acc = State.get Sum
State.put Counter n-1
State.put Sum acc+n
if n == 0 then acc else here.sum_co_state_body
if n == 0 then acc else @Tail_Call here.sum_co_state_body
sum_co_state n =
res = State.run Counter n (State.run Sum 0 here.sum_co_state_body)
@ -46,14 +50,26 @@ sum_co_state n =
sum_state_body n =
acc = State.get Number
State.put Number (acc + n)
if n == 0 then State.get Number else here.sum_state_body (n - 1)
if n == 0 then State.get Number else
@Tail_Call here.sum_state_body (n - 1)
sum_state = sum_to ->
res = State.run Number 0 (here.sum_state_body sum_to)
res
sum_co_1 n acc = if n == 0 then acc else @Tail_Call here.sum_co_2 n-1 acc+n
sum_co_2 n acc = if n == 0 then acc else here.sum_co_1 n-1 acc+n
sum_co n =
res = here.sum_co_2 n 0
res
main =
hundred_mil = 100000000
IO.println (here.sum_co 1000)
IO.println "Measuring Sum TCO Corecursive"
Bench_Utils.measure (here.sum_co hundred_mil) "sum_tco_corecursive" 100 10
IO.println "Measuring Sum TCO Decimal"
Bench_Utils.measure (here.sum_tco_decimal hundred_mil) "sum_tco_float" 100 10
IO.println "Measuring SumTCO"

View File

@ -15,6 +15,7 @@ library:
dependencies:
- base
- deepseq
- containers
benchmarks:
haskell-benchmark:

View File

@ -2,8 +2,10 @@ module Fixtures where
import Prelude
import Data.Int (Int64)
import qualified Data.Map.Strict as Map
import Data.Int (Int64)
import Data.List (foldl')
------------------
@ -12,8 +14,6 @@ import Data.Int (Int64)
data List a = Cons a (List a) | Nil deriving (Show)
--------------------------
-- === Input Values === --
--------------------------
@ -32,7 +32,8 @@ millionElementList = genList 1000000
hundredMillion :: Int64
hundredMillion = 100000000
tenThousand :: Integer
tenThousand = 10000
----------------------
-- === Fixtures === --
@ -67,3 +68,5 @@ myFoldl _ z Nil = z
myFoldl f z (Cons x xs) = let z' = z `f` x
in seq z' $ myFoldl f z' xs
buildMap :: Integer -> Map.Map Integer Integer
buildMap i = foldl' (\m i -> Map.insert i i m) Map.empty [0..i]

View File

@ -9,6 +9,7 @@ import Criterion.Main
main :: IO ()
main = defaultMain
[
bench "buildMap" $ whnf Fixtures.buildMap Fixtures.tenThousand,
bench "sumTCO" $ whnf Fixtures.sumTCO Fixtures.hundredMillion,
bench "sumList" $ whnf Fixtures.sumList Fixtures.millionElementList,
bench "reverseList" $ whnf Fixtures.reverseList Fixtures.millionElementList,