2020-05-15 13:41:26 +03:00
|
|
|
|
---
|
|
|
|
|
layout: developer-doc
|
|
|
|
|
title: Function Calling Flow
|
|
|
|
|
category: runtime
|
|
|
|
|
tags: [runtime, execution, interpreter]
|
|
|
|
|
order: 3
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# Function Calling Flow
|
2020-01-07 15:39:07 +03:00
|
|
|
|
With Enso being a functional language, it is crucial for function calls to be
|
2020-05-15 13:41:26 +03:00
|
|
|
|
efficient and easily inlined. At the same time, the logic of calling functions
|
|
|
|
|
is involved – there's a plethora of features this system needs to support,
|
|
|
|
|
making the functions the most involved and somewhat convoluted part of the
|
|
|
|
|
interpreter's design.
|
|
|
|
|
|
|
|
|
|
The following is an overview of the logic, outlining the most important features
|
|
|
|
|
supported by this system and a diagram of how they fit into the Truffle
|
|
|
|
|
framework.
|
2020-01-07 15:39:07 +03:00
|
|
|
|
|
2020-01-09 16:43:44 +03:00
|
|
|
|
<!-- MarkdownTOC levels="2,3" autolink="true" -->
|
|
|
|
|
|
|
|
|
|
- [Tail Call Optimization](#tail-call-optimization)
|
|
|
|
|
- [Named Application Arguments](#named-application-arguments)
|
2020-05-15 13:41:26 +03:00
|
|
|
|
- [Definition-Site Argument Suspension](#definition-site-argument-suspension)
|
2020-01-09 16:43:44 +03:00
|
|
|
|
- [Currying and Eta-Expansion](#currying-and-eta-expansion)
|
|
|
|
|
- [Dynamic Dispatch](#dynamic-dispatch)
|
2020-04-30 17:58:45 +03:00
|
|
|
|
- [Defaulted Arguments and Application](#defaulted-arguments-and-application)
|
2020-01-09 16:43:44 +03:00
|
|
|
|
- [Flow Diagram](#flow-diagram)
|
|
|
|
|
|
|
|
|
|
<!-- /MarkdownTOC -->
|
2020-01-07 15:39:07 +03:00
|
|
|
|
|
|
|
|
|
## Tail Call Optimization
|
|
|
|
|
It is very important for Enso to perform TCO well, since there are no intrinsic
|
|
|
|
|
looping constructs in the language.
|
2020-05-15 13:41:26 +03:00
|
|
|
|
|
2020-01-07 15:39:07 +03:00
|
|
|
|
The idea behind Enso's TCO is simple, even if the implementation is confusing.
|
|
|
|
|
Whenever we call a possibly-tail-recursive function in a tail position, a tail
|
2020-05-15 13:41:26 +03:00
|
|
|
|
call exception containing the function and its arguments is thrown. This
|
|
|
|
|
exception is then caught in a loop, effectively translating recursion into a
|
|
|
|
|
loop. With the use of Truffle's `ControlFlowException`, this code is optimized
|
|
|
|
|
like a builtin language loop construct.
|
|
|
|
|
|
2020-01-07 15:39:07 +03:00
|
|
|
|
In pseudocode, a tail recursive function, like:
|
2020-05-15 13:41:26 +03:00
|
|
|
|
|
|
|
|
|
```ruby
|
2020-01-07 15:39:07 +03:00
|
|
|
|
foo x = if x == 0 then print "foo" else foo (x - 1)
|
|
|
|
|
```
|
2020-05-15 13:41:26 +03:00
|
|
|
|
|
2020-01-07 15:39:07 +03:00
|
|
|
|
becomes:
|
2020-05-15 13:41:26 +03:00
|
|
|
|
|
|
|
|
|
```ruby
|
2020-01-07 15:39:07 +03:00
|
|
|
|
foo x = if x == 0 then Y else throw (TailCallException foo [x-1])
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Then, any non-tail call site like:
|
2020-05-15 13:41:26 +03:00
|
|
|
|
|
|
|
|
|
```ruby
|
2020-01-07 15:39:07 +03:00
|
|
|
|
z = foo 1000000
|
|
|
|
|
```
|
2020-05-15 13:41:26 +03:00
|
|
|
|
|
2020-01-07 15:39:07 +03:00
|
|
|
|
is translated into
|
2020-05-15 13:41:26 +03:00
|
|
|
|
|
|
|
|
|
```ruby
|
2020-01-07 15:39:07 +03:00
|
|
|
|
z = null
|
|
|
|
|
_fun = foo
|
|
|
|
|
_args = [1000000]
|
|
|
|
|
while true
|
|
|
|
|
try
|
|
|
|
|
z = _fun.call(args)
|
|
|
|
|
break
|
|
|
|
|
catch (TailCallException e)
|
|
|
|
|
_fun = e.function
|
|
|
|
|
_args = e.args
|
|
|
|
|
```
|
|
|
|
|
|
2020-05-15 13:41:26 +03:00
|
|
|
|
This logic is encapsulated in the various subclasses of `CallOptimiserNode`.
|
2020-01-07 15:39:07 +03:00
|
|
|
|
|
2020-05-15 13:41:26 +03:00
|
|
|
|
## Named Application Arguments
|
2020-01-07 15:39:07 +03:00
|
|
|
|
Enso allows applying function arguments by name, e.g.
|
2020-05-15 13:41:26 +03:00
|
|
|
|
|
|
|
|
|
```ruby
|
2020-01-07 15:39:07 +03:00
|
|
|
|
pow base exp = x ** y
|
|
|
|
|
z = foo exp=10 base=3
|
|
|
|
|
```
|
2020-05-15 13:41:26 +03:00
|
|
|
|
|
2020-01-07 15:39:07 +03:00
|
|
|
|
While certainly useful for the user, it requires some non-trivial facilitation
|
|
|
|
|
in the interpreter. An easy solution, would be to simply pass arguments in
|
|
|
|
|
a dictionary-like structure (e.g. a HashMap), but that has unacceptable
|
|
|
|
|
performance characteristics.
|
|
|
|
|
|
|
|
|
|
Therefore, the approach of choice is to compute and _cache_ a translation
|
|
|
|
|
table – a recipe for reordering arguments for a given function at a given
|
|
|
|
|
call site.
|
|
|
|
|
|
|
|
|
|
Based on the knowledge of the call-site schema (i.e. a description like
|
|
|
|
|
"3 arguments are provided, the first is named foo, the second is named bar and
|
|
|
|
|
the third is applied positionally") and the definition-site schema ("the
|
|
|
|
|
function has 3 parameters, named [baz, foo, bar]"), a mapping ("move the first
|
|
|
|
|
argument to the second position, the second becomes third and the third becomes
|
2020-05-15 13:41:26 +03:00
|
|
|
|
the first") is computed.
|
2020-01-07 15:39:07 +03:00
|
|
|
|
|
2020-05-15 13:41:26 +03:00
|
|
|
|
This mapping is then memoized and used to efficiently reorder arguments on each
|
|
|
|
|
execution, without the need to employ any more involved data structures. A
|
|
|
|
|
standard Truffle Polymorphic Inline Cache is used to store these mappings,
|
|
|
|
|
therefore it may be subject to the standard problems – storing rarely accessed
|
|
|
|
|
paths in the cache, as well as cache misses for highly polymorphic call sites.
|
2020-01-07 15:39:07 +03:00
|
|
|
|
|
2020-05-15 13:41:26 +03:00
|
|
|
|
This logic is encapsulated in the `ArgumentSorterNode`.
|
2020-01-07 15:39:07 +03:00
|
|
|
|
|
2020-05-15 13:41:26 +03:00
|
|
|
|
## Definition-Site Argument Suspension
|
|
|
|
|
Enso allows functions to define certain arguments as `Suspended`, so that when
|
|
|
|
|
these are passed to a function, the corresponding expressions are not evaluated
|
2020-06-08 16:48:39 +03:00
|
|
|
|
at the call site, but are instead passed to the function as closures for
|
2020-05-15 13:41:26 +03:00
|
|
|
|
evaluation at the function's discretion.
|
2020-01-07 15:39:07 +03:00
|
|
|
|
|
|
|
|
|
Therefore, all application arguments are actually treated as thunks and only
|
|
|
|
|
evaluated at call-site when the function signature defines them as eager.
|
|
|
|
|
|
|
|
|
|
Argument execution is happening inside the `ArgumentSorterNode`.
|
|
|
|
|
|
|
|
|
|
## Currying and Eta-Expansion
|
|
|
|
|
Functions can also be applied partially (i.e. with less arguments than required
|
|
|
|
|
by the signature, in which case the result is a function with certain arguments
|
|
|
|
|
fixed) or over-saturated (i.e. if a function returns another function, in which
|
|
|
|
|
case arguments for 2 consecutive calls can be passed in a single application
|
|
|
|
|
expression).
|
|
|
|
|
|
|
|
|
|
This logic is handled inside the `CurryNode`.
|
|
|
|
|
|
|
|
|
|
## Dynamic Dispatch
|
|
|
|
|
Functions can be dispatched dynamically, meaning a name can denote different
|
|
|
|
|
functions, based on the (runtime) type of the first argument.
|
|
|
|
|
|
|
|
|
|
This logic is fairly straightforward. It is triggered from `InvokeCallableNode`
|
|
|
|
|
and performed (with caching) in the `MethodResolverNode`.
|
|
|
|
|
|
2020-04-30 17:58:45 +03:00
|
|
|
|
## Defaulted Arguments and Application
|
|
|
|
|
As we want to provide a consistent semantics in the language (especially with
|
|
|
|
|
the use of multi-argument lambdas internally), there is one specific situation
|
|
|
|
|
that arises when handling applications with default arguments. As we expect a
|
|
|
|
|
returned lambda to be applied to any additional arguments (in keeping with
|
|
|
|
|
currying), a returned lambda that is _otherwise_ fully saturated (by default
|
|
|
|
|
arguments) should also be executed.
|
|
|
|
|
|
|
|
|
|
To this end, we make sure that the callsite checks if the return value from a
|
|
|
|
|
function is a function, and if it is fully saturated with defaults it will call
|
|
|
|
|
it.
|
|
|
|
|
|
2020-01-07 15:39:07 +03:00
|
|
|
|
## Flow Diagram
|
|
|
|
|
The following diagram summarizes all the nodes participating in a function
|
|
|
|
|
call. The entry points to this system are `ApplicationNode` (for in-language
|
|
|
|
|
function calls) and `InteropLibrary<Function>` (for polyglot function calls).
|
|
|
|
|
|
2020-06-08 16:48:39 +03:00
|
|
|
|
![Function Call Flow](https://user-images.githubusercontent.com/5780639/84035237-5c2d9800-a993-11ea-826d-72f3ddffcb54.png)
|