mirror of
https://github.com/enso-org/enso.git
synced 2024-09-11 13:15:52 +03:00
Opt-in TCO (#1219)
This commit is contained in:
parent
1feec8388d
commit
8668079337
@ -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"),
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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`.
|
||||
|
@ -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`.
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
79
docs/semantics/tail-call-optimization.md
Normal file
79
docs/semantics/tail-call-optimization.md
Normal 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.
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 =
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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. */
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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",
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
|
@ -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)));
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -43,6 +43,7 @@ class Passes(passes: Option[List[PassGroup]] = None) {
|
||||
IgnoredBindings,
|
||||
TypeFunctions,
|
||||
TypeSignatures,
|
||||
Annotations,
|
||||
AliasAnalysis,
|
||||
UppercaseNames,
|
||||
VectorLiterals,
|
||||
|
@ -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(_) =>
|
||||
|
@ -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))}>"
|
||||
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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 " +
|
||||
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.")
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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()) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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) =>
|
||||
|
@ -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 ////
|
||||
|
@ -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 ///////////////////////////////////////////////////////////////
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"
|
||||
|
@ -15,6 +15,7 @@ library:
|
||||
dependencies:
|
||||
- base
|
||||
- deepseq
|
||||
- containers
|
||||
|
||||
benchmarks:
|
||||
haskell-benchmark:
|
||||
|
@ -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]
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user