Skip to content

Commit

Permalink
Polyglot JS Definitions (#1451)
Browse files Browse the repository at this point in the history
  • Loading branch information
kustosz authored Feb 8, 2021
1 parent 9ce5999 commit c4a0772
Show file tree
Hide file tree
Showing 45 changed files with 1,393 additions and 220 deletions.
24 changes: 9 additions & 15 deletions distribution/std-lib/Base/src/Data/Vector.enso
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ type Vector
Checking the length of a vector.
[1, 2, 3, 4].length == 4
length : Number
length = this.to_array.length
length = Polyglot.get_array_size this.to_array

## Gets an element from the vector at a specified index (0-based).

Expand Down Expand Up @@ -208,10 +208,7 @@ type Vector
[2, 3, 4]
map : (Any -> Any) -> Vector
map function =
arr = this.to_array
new_arr = Array.new arr.length
0.up_to arr.length . each ix-> new_arr.set_at ix (function (arr.at ix))
Vector new_arr
here.new this.length i-> function (this.at i)

## Applies a function to each element of the vector, returning the vector
of results.
Expand Down Expand Up @@ -247,12 +244,11 @@ type Vector
[1, 2, 3].to_text == "[1, 2, 3]"
to_text : Text
to_text =
arr = this.to_array
if arr.length == 0 then "[]" else
if arr.length == 1 then "[" + (arr.at 0 . to_text) + "]" else
folder = str -> ix -> str + ", " + (arr.at ix).to_text
tail_elems = 1.up_to arr.length . fold "" folder
"[" + (arr.at 0 . to_text) + tail_elems + "]"
if this.length == 0 then "[]" else
if this.length == 1 then "[" + (this.at 0 . to_text) + "]" else
folder = str -> ix -> str + ", " + (this.at ix).to_text
tail_elems = 1.up_to this.length . fold "" folder
"[" + (this.at 0 . to_text) + tail_elems + "]"

## Checks whether this vector is equal to `that`. Two vectors are considered
equal, when they have the same length and their items are pairwise equal.
Expand All @@ -262,10 +258,8 @@ type Vector
[1, 2, 3] == [2, 3, 4
== : Vector -> Boolean
== that =
arr1 = this.to_array
arr2 = that.to_array
eq_at i = arr1.at i == arr2.at i
if arr1.length == arr2.length then 0.up_to arr1.length . all eq_at else False
eq_at i = this.at i == that.at i
if this.length == that.length then 0.up_to this.length . all 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`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ class ContextFactory {
strictErrors: Boolean = false
): PolyglotContext = {
val context = Context
.newBuilder(LanguageInfo.ID)
// TODO: Remove EPB from this list when https://github.com/oracle/graal/pull/3139 is merged
// and available in our Graal release.
.newBuilder(LanguageInfo.ID, "js", "epb")
.allowExperimentalOptions(true)
.allowAllAccess(true)
.option(RuntimeOptions.PACKAGES_PATH, packagesPath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.oracle.truffle.api.nodes.RootNode;
import java.util.Collections;

import org.enso.interpreter.epb.EpbLanguage;
import org.enso.interpreter.instrument.IdExecutionInstrument;
import org.enso.interpreter.node.ProgramRootNode;
import org.enso.interpreter.runtime.Context;
Expand Down Expand Up @@ -34,6 +35,7 @@
defaultMimeType = LanguageInfo.MIME_TYPE,
characterMimeTypes = {LanguageInfo.MIME_TYPE},
contextPolicy = TruffleLanguage.ContextPolicy.SHARED,
dependentLanguages = {EpbLanguage.ID},
fileTypeDetectors = FileDetector.class,
services = ExecutionService.class)
@ProvidedTags({
Expand Down Expand Up @@ -71,7 +73,7 @@ protected Context createContext(Env env) {
* @param context the language context
*/
@Override
protected void initializeContext(Context context) throws Exception {
protected void initializeContext(Context context) {
context.initialize();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.enso.interpreter.epb;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.TruffleLanguage;
import org.enso.interpreter.epb.runtime.GuardedTruffleContext;

/**
* A context for {@link EpbLanguage}. Provides access to both isolated Truffle contexts used in
* polyglot execution.
*/
public class EpbContext {
private static final String INNER_OPTION = "isEpbInner";
private final boolean isInner;
private final TruffleLanguage.Env env;
private @CompilerDirectives.CompilationFinal GuardedTruffleContext innerContext;
private final GuardedTruffleContext currentContext;

/**
* Creates a new instance of this context.
*
* @param env the current language environment.
*/
public EpbContext(TruffleLanguage.Env env) {
this.env = env;
isInner = env.getConfig().get(INNER_OPTION) != null;
currentContext = new GuardedTruffleContext(env.getContext(), isInner);
}

/**
* Initializes the context. No-op in the inner context. Spawns the inner context if called from
* the outer context.
*/
public void initialize() {
if (!isInner) {
innerContext =
new GuardedTruffleContext(
env.newContextBuilder().config(INNER_OPTION, "yes").build(), true);
}
}

/**
* Checks if this context corresponds to the inner Truffle context.
*
* @return true if run in the inner Truffle context, false otherwise.
*/
public boolean isInner() {
return isInner;
}

/**
* @return the inner Truffle context handle if called from the outer context, or null if called in
* the inner context.
*/
public GuardedTruffleContext getInnerContext() {
return innerContext;
}

/** @return returns the currently entered Truffle context handle. */
public GuardedTruffleContext getCurrentContext() {
return currentContext;
}

/** @return the language environment associated with this context. */
public TruffleLanguage.Env getEnv() {
return env;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.enso.interpreter.epb;

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage;
import org.enso.interpreter.epb.node.ContextRewrapNode;
import org.enso.interpreter.epb.node.ForeignEvalNode;

/**
* An internal language that serves as a bridge between Enso and other supported languages.
*
* <p>Truffle places a lot of emphasis on safety guarantees, which also means that single-threaded
* languages cannot easily be called from multiple threads. We circumvent this by using two separate
* {@link com.oracle.truffle.api.TruffleContext}s, one (often referred to as "outer") is allowed to
* run Enso, Host Java, and possibly other thread-ready languages. Languages that cannot safely run
* in a multithreaded environment are relegated to the other context (referred to as "inner"). The
* inner context provides a GIL capability, ensuring that access to the single-threaded languages is
* serialized.
*
* <p>This imposes certain limitations on data interchange between the contexts. In particular, it
* is impossible to execute origin language's code when executing in the other context. Therefore
* outer context values need to be specially wrapped before being passed (e.g. as arguments) to the
* inner context, and inner context values need rewrapping for use in the outer context. See {@link
* org.enso.interpreter.epb.runtime.PolyglotProxy} and {@link
* ContextRewrapNode} for details of how and when this wrapping is done.
*
* <p>With the structure outlined above, EPB is the only language that is initialized in both inner
* and outer contexts and thus it is very minimal. Its only role is to manage both contexts and
* provide context-switching facilities.
*/
@TruffleLanguage.Registration(
id = EpbLanguage.ID,
name = "Enso Polyglot Bridge",
characterMimeTypes = {EpbLanguage.MIME},
// TODO mark this language as internal when https://github.com/oracle/graal/pull/3139 is
// released
internal = false,
defaultMimeType = EpbLanguage.MIME,
contextPolicy = TruffleLanguage.ContextPolicy.SHARED)
public class EpbLanguage extends TruffleLanguage<EpbContext> {
public static final String ID = "epb";
public static final String MIME = "application/epb";

@Override
protected EpbContext createContext(Env env) {
return new EpbContext(env);
}

@Override
protected void initializeContext(EpbContext context) {
context.initialize();
}

@Override
protected CallTarget parse(ParsingRequest request) {
EpbParser.Result code = EpbParser.parse(request.getSource());
return Truffle.getRuntime()
.createCallTarget(ForeignEvalNode.build(this, code, request.getArgumentNames()));
}

@Override
protected boolean isThreadAccessAllowed(Thread thread, boolean singleThreaded) {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.enso.interpreter.epb;

import com.oracle.truffle.api.source.Source;

import java.util.Arrays;

/** A class containing helpers for creating and parsing EPB code */
public class EpbParser {
private static final String separator = "#";

/** Lists all the languages supported in polyglot eval. */
public enum ForeignLanguage {
JS("js", "js");

private final String truffleId;
private final String syntacticTag;

ForeignLanguage(String truffleId, String syntacticTag) {
this.truffleId = truffleId;
this.syntacticTag = syntacticTag;
}

/** @return a Truffle language ID associated with this language */
public String getTruffleId() {
return truffleId;
}

/**
* Transforms an Enso-side syntactic language tag into a recognized language object.
*
* @param tag the tag to parse
* @return a corresponding language value, or null if the language is not recognized
*/
public static ForeignLanguage getBySyntacticTag(String tag) {
return Arrays.stream(values())
.filter(l -> l.syntacticTag.equals(tag))
.findFirst()
.orElse(null);
}
}

/** A parsing result. */
public static class Result {
private final ForeignLanguage language;
private final String foreignSource;

private Result(ForeignLanguage language, String foreignSource) {
this.language = language;
this.foreignSource = foreignSource;
}

/** @return the foreign language code to eval */
public String getForeignSource() {
return foreignSource;
}

/** @return the foreign language in which the source is written */
public ForeignLanguage getLanguage() {
return language;
}
}

/**
* Parses an EPB source
*
* @param source the source to parse
* @return the result of parsing
*/
public static Result parse(Source source) {
String src = source.getCharacters().toString();
String[] langAndCode = src.split(separator, 2);
return new Result(ForeignLanguage.valueOf(langAndCode[0]), langAndCode[1]);
}

/**
* Builds a new source instance that can later be parsed by this class.
*
* @param language the foreign language to use
* @param foreignSource the foreign source to evaluate
* @param name the name of the source
* @return a source instance, parsable by the EPB language
*/
public static Source buildSource(ForeignLanguage language, String foreignSource, String name) {
return Source.newBuilder(EpbLanguage.ID, language + separator + foreignSource, name).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.enso.interpreter.epb.node;

import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.ReportPolymorphism;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.epb.runtime.GuardedTruffleContext;
import org.enso.interpreter.epb.runtime.PolyglotProxy;

@GenerateUncached
@ReportPolymorphism
public abstract class ContextRewrapNode extends Node {
/**
* Wraps a value originating from {@code origin} into a value valid in {@code target}. This method
* is allowed to use interop library on {@code value} and therefore must be called with {@code
* origin} entered.
*
* @param value the value to wrap
* @param origin the context the value originates in (and is currently entered)
* @param target the context in which the value will be accessed in the future
* @return a context-switch-safe wrapper for the value
*/
public abstract Object execute(
Object value, GuardedTruffleContext origin, GuardedTruffleContext target);

@Specialization
double doDouble(double d, GuardedTruffleContext origin, GuardedTruffleContext target) {
return d;
}

@Specialization
double doFloat(float d, GuardedTruffleContext origin, GuardedTruffleContext target) {
return d;
}

@Specialization
long doLong(long i, GuardedTruffleContext origin, GuardedTruffleContext target) {
return i;
}

@Specialization
long doInt(int i, GuardedTruffleContext origin, GuardedTruffleContext target) {
return i;
}

@Specialization
boolean doBoolean(boolean b, GuardedTruffleContext origin, GuardedTruffleContext target) {
return b;
}

@Specialization(guards = "proxy.getOrigin() == target")
Object doUnwrapProxy(
PolyglotProxy proxy, GuardedTruffleContext origin, GuardedTruffleContext target) {
return proxy.getDelegate();
}

@Specialization(guards = "proxy.getTarget() == target")
Object doAlreadyProxied(
PolyglotProxy proxy, GuardedTruffleContext origin, GuardedTruffleContext target) {
return proxy;
}

@Fallback
Object doWrapProxy(Object o, GuardedTruffleContext origin, GuardedTruffleContext target) {
return new PolyglotProxy(o, origin, target);
}
}
Loading

0 comments on commit c4a0772

Please sign in to comment.