EpbLanguage re-uses other TruffleContext support to run tests with assertions enabled (#7882)

This commit is contained in:
Pavel Marek 2023-12-15 13:31:32 +01:00 committed by GitHub
parent 927df167d7
commit 4b65e44ef3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 635 additions and 2181 deletions

View File

@ -1178,11 +1178,6 @@ val truffleRunOpts = Seq(
"-Dpolyglot.compiler.BackgroundCompilation=false" "-Dpolyglot.compiler.BackgroundCompilation=false"
) )
val truffleRunOptionsNoAssertSettings = Seq(
fork := true,
javaOptions ++= benchOnlyOptions
)
val truffleRunOptionsSettings = Seq( val truffleRunOptionsSettings = Seq(
fork := true, fork := true,
javaOptions ++= "-ea" +: benchOnlyOptions javaOptions ++= "-ea" +: benchOnlyOptions
@ -1378,11 +1373,20 @@ lazy val instrumentationSettings = frgaalJavaCompilerSetting ++ Seq(
lazy val `runtime-language-epb` = lazy val `runtime-language-epb` =
(project in file("engine/runtime-language-epb")) (project in file("engine/runtime-language-epb"))
.settings( .settings(
frgaalJavaCompilerSetting,
inConfig(Compile)(truffleRunOptionsSettings), inConfig(Compile)(truffleRunOptionsSettings),
truffleDslSuppressWarnsSetting, truffleDslSuppressWarnsSetting,
instrumentationSettings commands += WithDebugCommand.withDebug,
fork := true,
Test / javaOptions ++= Seq(),
instrumentationSettings,
libraryDependencies ++= Seq(
"junit" % "junit" % junitVersion % Test,
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided",
"org.graalvm.truffle" % "truffle-dsl-processor" % graalMavenPackagesVersion % "provided"
)
) )
.dependsOn(`polyglot-api`)
lazy val runtime = (project in file("engine/runtime")) lazy val runtime = (project in file("engine/runtime"))
.configs(Benchmark) .configs(Benchmark)
@ -1730,7 +1734,7 @@ lazy val `runtime-with-polyglot` =
.configs(Benchmark) .configs(Benchmark)
.settings( .settings(
frgaalJavaCompilerSetting, frgaalJavaCompilerSetting,
inConfig(Compile)(truffleRunOptionsNoAssertSettings), inConfig(Compile)(truffleRunOptionsSettings),
inConfig(Benchmark)(Defaults.testSettings), inConfig(Benchmark)(Defaults.testSettings),
commands += WithDebugCommand.withDebug, commands += WithDebugCommand.withDebug,
Benchmark / javacOptions --= Seq( Benchmark / javacOptions --= Seq(

View File

@ -30,7 +30,7 @@ impl From<bool> for Boolean {
} }
ide_ci::define_env_var! { ide_ci::define_env_var! {
ENSO_JVM_OPTS, String; JAVA_OPTS, String;
ENSO_BENCHMARK_TEST_DRY_RUN, Boolean; ENSO_BENCHMARK_TEST_DRY_RUN, Boolean;
} }
@ -88,7 +88,7 @@ impl BuiltEnso {
.arg(test_path) .arg(test_path)
// This flag enables assertions in the JVM. Some of our stdlib tests had in the past // This flag enables assertions in the JVM. Some of our stdlib tests had in the past
// failed on Graal/Truffle assertions, so we want to have them triggered. // failed on Graal/Truffle assertions, so we want to have them triggered.
.set_env(ENSO_JVM_OPTS, &ide_ci::programs::java::Option::EnableAssertions.as_ref())?; .set_env(JAVA_OPTS, &ide_ci::programs::java::Option::EnableAssertions.as_ref())?;
Ok(command) Ok(command)
} }

View File

@ -1,47 +0,0 @@
package org.enso.polyglot;
import com.oracle.truffle.api.source.Source;
import java.util.Arrays;
/** Lists all the languages supported in polyglot eval. */
public enum ForeignLanguage {
JS("js", "js"),
PY("python", "python"),
R("R", "r");
public static final String ID = "epb";
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);
}
/**
* Builds a new source instance that can later be parsed by this class.
*
* @param foreignSource the foreign source to evaluate
* @param name the name of the source
* @return a source instance, parsable by the EPB language
*/
public Source buildSource(String foreignSource, String name) {
return Source.newBuilder(ID, this + "#" + foreignSource, name).build();
}
}

View File

@ -22,7 +22,6 @@ import org.enso.compiler.pass.analyse.{
} }
import org.enso.compiler.pass.lint.UnusedBindings import org.enso.compiler.pass.lint.UnusedBindings
import org.enso.compiler.pass.optimise.LambdaConsolidate import org.enso.compiler.pass.optimise.LambdaConsolidate
import org.enso.polyglot.ForeignLanguage
import scala.annotation.tailrec import scala.annotation.tailrec
@ -161,7 +160,7 @@ case object GenerateMethodBodies extends IRPass {
case lam @ Function.Lambda(_, body, _, _, _, _) case lam @ Function.Lambda(_, body, _, _, _, _)
if findForeignDefinition( if findForeignDefinition(
body, body,
lang = Some(ForeignLanguage.JS) lang = Some("js")
).isDefined => ).isDefined =>
val thisArgs = chainedFunctionArgs.collect { val thisArgs = chainedFunctionArgs.collect {
case (arg, idx) if arg.name.name == "this" => case (arg, idx) if arg.name.name == "this" =>
@ -308,7 +307,7 @@ case object GenerateMethodBodies extends IRPass {
@tailrec @tailrec
private def findForeignDefinition( private def findForeignDefinition(
body: Expression, body: Expression,
lang: Option[ForeignLanguage] lang: Option[String]
): Option[Foreign.Definition] = { ): Option[Foreign.Definition] = {
body match { body match {
case foreignDef: Foreign.Definition => case foreignDef: Foreign.Definition =>

View File

@ -1,4 +1,4 @@
package org.enso.interpreter.epb.node; package org.enso.interpreter.epb;
import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.GenerateUncached;
@ -10,7 +10,7 @@ import com.oracle.truffle.api.strings.TruffleString;
@GenerateUncached @GenerateUncached
@ReportPolymorphism @ReportPolymorphism
public abstract class CoercePrimitiveNode extends Node { abstract class CoercePrimitiveNode extends Node {
/** /**
* Create a new node responsible for coercing primitive values to Enso primitives at the polyglot * Create a new node responsible for coercing primitive values to Enso primitives at the polyglot

View File

@ -1,19 +1,18 @@
package org.enso.interpreter.epb; package org.enso.interpreter.epb;
import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.TruffleContext; import com.oracle.truffle.api.TruffleContext;
import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.Node;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.logging.Level; import java.util.logging.Level;
import org.enso.interpreter.epb.runtime.GuardedTruffleContext;
/** /**
* A context for {@link EpbLanguage}. Provides access to both isolated Truffle contexts used in * A context for {@link EpbLanguage}. Provides access to both isolated Truffle contexts used in
* polyglot execution. * polyglot execution.
*/ */
public class EpbContext { final class EpbContext {
private static final TruffleLanguage.ContextReference<EpbContext> REFERENCE = private static final TruffleLanguage.ContextReference<EpbContext> REFERENCE =
TruffleLanguage.ContextReference.create(EpbLanguage.class); TruffleLanguage.ContextReference.create(EpbLanguage.class);
@ -21,18 +20,19 @@ public class EpbContext {
private static final String INNER_OPTION = "isEpbInner"; private static final String INNER_OPTION = "isEpbInner";
private final boolean isInner; private final boolean isInner;
private final TruffleLanguage.Env env; private final TruffleLanguage.Env env;
private @CompilerDirectives.CompilationFinal GuardedTruffleContext innerContext; private @CompilationFinal TruffleContext innerContext;
private final GuardedTruffleContext currentContext; private final ReentrantLock lock = new ReentrantLock();
private final TruffleLogger log;
/** /**
* Creates a new instance of this context. * Creates a new instance of this context.
* *
* @param env the current language environment. * @param env the current language environment.
*/ */
public EpbContext(TruffleLanguage.Env env) { EpbContext(TruffleLanguage.Env env) {
this.env = env; this.env = env;
isInner = env.getConfig().get(INNER_OPTION) != null; isInner = env.getConfig().get(INNER_OPTION) != null;
currentContext = new GuardedTruffleContext(env.getContext(), isInner); this.log = env.getLogger(EpbContext.class);
} }
/** /**
@ -45,58 +45,15 @@ public class EpbContext {
if (!isInner) { if (!isInner) {
if (innerContext == null) { if (innerContext == null) {
innerContext = innerContext =
new GuardedTruffleContext( env.newInnerContextBuilder()
env.newInnerContextBuilder() .initializeCreatorContext(true)
.initializeCreatorContext(true) .inheritAllAccess(true)
.inheritAllAccess(true) .config(INNER_OPTION, "yes")
.config(INNER_OPTION, "yes") .build();
.build(),
true);
} }
initializeLanguages(env, innerContext, preInitializeLanguages);
} }
} }
private static void initializeLanguages(
TruffleLanguage.Env environment, GuardedTruffleContext innerContext, String langs) {
if (langs == null || langs.isEmpty()) {
return;
}
var log = environment.getLogger(EpbContext.class);
log.log(Level.FINE, "Initializing languages {0}", langs);
var cdl = new CountDownLatch(1);
var run =
(Consumer<TruffleContext>)
(context) -> {
var lock = innerContext.enter(null);
try {
log.log(Level.FINEST, "Entering initialization thread");
cdl.countDown();
for (var l : langs.split(",")) {
log.log(Level.FINEST, "Initializing language {0}", l);
long then = System.currentTimeMillis();
var res = context.initializeInternal(null, l);
long took = System.currentTimeMillis() - then;
log.log(
Level.FINE,
"Done initializing language {0} with {1} in {2} ms",
new Object[] {l, res, took});
}
} finally {
innerContext.leave(null, lock);
}
};
var init = innerContext.createThread(environment, run);
log.log(Level.FINEST, "Starting initialization thread");
init.start();
try {
cdl.await();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
log.log(Level.FINEST, "Initializing on background");
}
/** /**
* @param node the location of context access. Pass {@code null} if not in a node. * @param node the location of context access. Pass {@code null} if not in a node.
* @return the proper context instance for the current {@link * @return the proper context instance for the current {@link
@ -106,30 +63,16 @@ public class EpbContext {
return REFERENCE.get(node); return REFERENCE.get(node);
} }
/**
* 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. */ /** @return the language environment associated with this context. */
public TruffleLanguage.Env getEnv() { public TruffleLanguage.Env getEnv() {
return env; return env;
} }
public TruffleContext getInnerContext() {
return innerContext;
}
public void log(Level level, String msg, Object... args) {
this.log.log(level, msg, args);
}
} }

View File

@ -3,41 +3,17 @@ package org.enso.interpreter.epb;
import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.TruffleLanguage;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.enso.interpreter.epb.node.ContextRewrapNode;
import org.enso.interpreter.epb.node.ForeignEvalNode;
import org.enso.polyglot.ForeignLanguage;
/** /** An internal language that serves as a bridge between Enso and other supported languages. */
* 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( @TruffleLanguage.Registration(
id = ForeignLanguage.ID, id = "epb",
name = "Enso Polyglot Bridge", name = "Enso Polyglot Bridge",
characterMimeTypes = {EpbLanguage.MIME}, characterMimeTypes = {EpbLanguage.MIME},
internal = true, internal = true,
defaultMimeType = EpbLanguage.MIME, defaultMimeType = EpbLanguage.MIME,
contextPolicy = TruffleLanguage.ContextPolicy.SHARED, contextPolicy = TruffleLanguage.ContextPolicy.SHARED,
services = Consumer.class) services = Consumer.class)
public class EpbLanguage extends TruffleLanguage<EpbContext> { public final class EpbLanguage extends TruffleLanguage<EpbContext> {
public static final String MIME = "application/epb"; public static final String MIME = "application/epb";
@Override @Override
@ -55,9 +31,8 @@ public class EpbLanguage extends TruffleLanguage<EpbContext> {
@Override @Override
protected CallTarget parse(ParsingRequest request) { protected CallTarget parse(ParsingRequest request) {
EpbParser.Result code = EpbParser.parse(request.getSource()); var node = ForeignEvalNode.parse(this, request.getSource(), request.getArgumentNames());
ForeignEvalNode foreignEvalNode = ForeignEvalNode.build(this, code, request.getArgumentNames()); return node.getCallTarget();
return foreignEvalNode.getCallTarget();
} }
@Override @Override

View File

@ -1,40 +0,0 @@
package org.enso.interpreter.epb;
import com.oracle.truffle.api.source.Source;
import org.enso.polyglot.ForeignLanguage;
/** A class containing helpers for creating and parsing EPB code */
public class EpbParser {
/** 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("#", 2);
return new Result(ForeignLanguage.valueOf(langAndCode[0]), langAndCode[1]);
}
}

View File

@ -0,0 +1,14 @@
package org.enso.interpreter.epb;
final class ExceptionForeignNode extends ForeignFunctionCallNode {
private final ForeignParsingException ex;
ExceptionForeignNode(ForeignParsingException ex) {
this.ex = ex;
}
@Override
public Object execute(Object[] arguments) {
throw ex;
}
}

View File

@ -0,0 +1,107 @@
package org.enso.interpreter.epb;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
final class ForeignEvalNode extends RootNode {
private final Source langAndCode;
private @Child ForeignFunctionCallNode foreign;
private final String[] argNames;
private ForeignEvalNode(EpbLanguage language, Source langAndCode, List<String> arguments) {
super(language, new FrameDescriptor());
this.langAndCode = langAndCode;
this.argNames = arguments.toArray(new String[0]);
}
static ForeignEvalNode parse(EpbLanguage epb, Source langAndCode, List<String> args) {
var node = new ForeignEvalNode(epb, langAndCode, args);
return node;
}
private String truffleId(Source langAndCode) {
var seq = langAndCode.getCharacters();
return seq.subSequence(0, splitAt(seq)).toString().toLowerCase();
}
private int splitAt(CharSequence seq) {
var at = 0;
while (at < seq.length()) {
if (seq.charAt(at) == '#') {
return at;
}
at++;
}
throw new ForeignParsingException("No # found", this);
}
private String foreignSource(Source langAndCode) {
var seq = langAndCode.getCharacters();
return seq.toString().substring(splitAt(seq) + 1);
}
@Override
public Object execute(VirtualFrame frame) {
if (foreign == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
var id = truffleId(langAndCode);
var context = EpbContext.get(this);
var installedLanguages = context.getEnv().getInternalLanguages();
var node = switch (installedLanguages.containsKey(id) ? 1 : 0) {
case 0 -> {
var ex = new ForeignParsingException(id, installedLanguages.keySet(), this);
yield new ExceptionForeignNode(ex);
}
default -> {
context.log(Level.FINE, "Parsing foreign script {1} - language {0}", id, langAndCode.getName());
yield switch (id) {
case "js" -> parseJs();
case "python" -> parseGeneric("python", PyForeignNode::new);
default -> parseGeneric(id, GenericForeignNode::new);
};
}
};
foreign = insert(node);
}
try {
var toRet = foreign.execute(frame.getArguments());
return toRet;
} catch (InteropException ex) {
throw new ForeignParsingException(ex.getMessage(), this);
}
}
private ForeignFunctionCallNode parseJs() {
var context = EpbContext.get(this);
var inner = context.getInnerContext();
var code = foreignSource(langAndCode);
var args = Arrays.stream(argNames).skip(1).collect(Collectors.joining(","));
var wrappedSrc
= "var poly_enso_eval=function("
+ args
+ "){\n"
+ code
+ "\n};poly_enso_eval";
Source source = Source.newBuilder("js", wrappedSrc, "").build();
var fn = inner.evalPublic(this, source);
return JsForeignNode.build(fn);
}
private ForeignFunctionCallNode parseGeneric(String language, Function<CallTarget,ForeignFunctionCallNode> nodeFactory) {
var ctx = EpbContext.get(this);
Source source = Source.newBuilder(language, foreignSource(langAndCode), langAndCode.getName()).build();
CallTarget ct = ctx.getEnv().parsePublic(source, argNames);
return nodeFactory.apply(ct);
}
}

View File

@ -1,10 +1,10 @@
package org.enso.interpreter.epb.node; package org.enso.interpreter.epb;
import com.oracle.truffle.api.interop.InteropException; import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.Node;
/** An interface for nodes responsible for calling into foreign languages. */ /** An interface for nodes responsible for calling into foreign languages. */
public abstract class ForeignFunctionCallNode extends Node { abstract class ForeignFunctionCallNode extends Node {
/** /**
* Executes the foreign call. * Executes the foreign call.
* *

View File

@ -1,4 +1,4 @@
package org.enso.interpreter.epb.runtime; package org.enso.interpreter.epb;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.exception.AbstractTruffleException; import com.oracle.truffle.api.exception.AbstractTruffleException;
@ -13,7 +13,7 @@ import java.util.Set;
* language is not installed, or enabled, in the Truffle engine. * language is not installed, or enabled, in the Truffle engine.
*/ */
@ExportLibrary(InteropLibrary.class) @ExportLibrary(InteropLibrary.class)
public class ForeignParsingException extends AbstractTruffleException { class ForeignParsingException extends AbstractTruffleException {
private final String message; private final String message;
/** /**

View File

@ -0,0 +1,22 @@
package org.enso.interpreter.epb;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.nodes.DirectCallNode;
class GenericForeignNode extends ForeignFunctionCallNode {
private @Child DirectCallNode callNode;
private @Child CoercePrimitiveNode coerceNode;
GenericForeignNode(CallTarget ct) {
callNode = DirectCallNode.create(ct);
coerceNode = CoercePrimitiveNode.build();
}
@Override
public Object execute(Object[] arguments) throws InteropException {
var rawResult = callNode.call(arguments);
var res = coerceNode.execute(rawResult);
return res;
}
}

View File

@ -0,0 +1,95 @@
package org.enso.interpreter.epb;
import com.oracle.truffle.api.dsl.NodeField;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import java.util.Arrays;
/** A node responsible for performing foreign JS calls. */
@NodeField(name = "foreignFunction", type = Object.class)
abstract class JsForeignNode extends ForeignFunctionCallNode {
private @Child CoercePrimitiveNode coercePrimitiveNode = CoercePrimitiveNode.build();
abstract Object getForeignFunction();
/**
* Creates a new instance of this node.
*
* @param jsFunction the parsed JS object (required to be {@link
* InteropLibrary#isExecutable(Object)})
* @return a node able to call the JS function with given arguments
*/
static JsForeignNode build(Object jsFunction) {
return JsForeignNodeGen.create(jsFunction);
}
@Specialization
Object doExecute(Object[] arguments, @CachedLibrary("foreignFunction") InteropLibrary iop)
throws InteropException {
var args = new ArgumentsArray(arguments);
var self = arguments[0];
var raw = iop.invokeMember(getForeignFunction(), "apply", self, args);
return coercePrimitiveNode.execute(raw);
}
@ExportLibrary(InteropLibrary.class)
static final class ArgumentsArray implements TruffleObject {
private static final int OFFSET = 1;
private final Object[] items;
public ArgumentsArray(Object... items) {
this.items = items;
}
@ExportMessage
public boolean hasArrayElements() {
return true;
}
@ExportMessage
public Object readArrayElement(long index) throws InvalidArrayIndexException {
if (index >= getArraySize() || index < 0) {
throw InvalidArrayIndexException.create(index);
}
return items[OFFSET + (int) index];
}
@ExportMessage
long getArraySize() {
return items.length - OFFSET;
}
@ExportMessage
boolean isArrayElementReadable(long index) {
return index < getArraySize() && index >= 0;
}
@ExportMessage
void writeArrayElement(long index, Object value) throws UnsupportedMessageException {
throw UnsupportedMessageException.create();
}
@ExportMessage
boolean isArrayElementModifiable(long index) {
return false;
}
@ExportMessage
boolean isArrayElementInsertable(long index) {
return false;
}
@Override
public String toString() {
return Arrays.toString(items);
}
}
}

View File

@ -1,14 +1,11 @@
package org.enso.interpreter.epb.node; package org.enso.interpreter.epb;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.ZoneId; import java.time.ZoneId;
import org.enso.interpreter.epb.EpbContext; import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.NodeField;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.ArityException; import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropException; import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.InteropLibrary;
@ -21,10 +18,7 @@ import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.source.Source;
@NodeField(name = "foreignFunction", type = Object.class) final class PyForeignNode extends GenericForeignNode {
public abstract class PyForeignNode extends ForeignFunctionCallNode {
@Child
private CoercePrimitiveNode coercePrimitiveNode = CoercePrimitiveNode.build();
@CompilerDirectives.CompilationFinal @CompilerDirectives.CompilationFinal
private Object fnPythonDate; private Object fnPythonDate;
@Child @Child
@ -39,10 +33,72 @@ public abstract class PyForeignNode extends ForeignFunctionCallNode {
private InteropLibrary nodePythonZone; private InteropLibrary nodePythonZone;
@CompilerDirectives.CompilationFinal @CompilerDirectives.CompilationFinal
private Object fnPythonCombine; private Object fnPythonCombine;
@CompilerDirectives.CompilationFinal
private Object none;
@Child @Child
private InteropLibrary nodePythonCombine; private InteropLibrary nodePythonCombine;
@Child
private InteropLibrary iop = InteropLibrary.getFactory().createDispatched(3);
abstract Object getForeignFunction(); PyForeignNode(CallTarget ct) {
super(ct);
}
@Override
public Object execute(Object[] arguments) throws InteropException {
// initialize venv by importing site
none();
for (int i = 0; i < arguments.length; i++) {
var javaTime = iop.isTime(arguments[i]) ? iop.asTime(arguments[i]) : null;
var time = javaTime != null ? wrapPythonTime(javaTime) : null;
var javaDate = iop.isDate(arguments[i]) ? iop.asDate(arguments[i]) : null;
var date = javaDate != null ? wrapPythonDate(javaDate) : null;
var zone = iop.isTimeZone(arguments[i]) ? wrapPythonZone(iop.asTimeZone(arguments[i]), javaTime, javaDate) : null;
if (date != null && time != null) {
arguments[i] = combinePythonDateTimeZone(date, time, zone);
} else if (date != null) {
arguments[i] = date;
} else if (time != null) {
arguments[i] = time;
} else if (zone != null) {
arguments[i] = zone;
}
// Enso's Text type should be converted to TruffleString before sent to python, because
// python does not tend to use InteropLibrary, so any Text object would be treated as
// a foreign object, rather than 'str' type.
if (iop.isString(arguments[i])) {
arguments[i] = iop.asTruffleString(arguments[i]);
}
}
var res = super.execute(arguments);
if (iop.hasMetaObject(res)) {
var meta = iop.getMetaObject(res);
var name = iop.getMetaQualifiedName(meta);
if ("module".equals(name)) {
return none();
}
}
return res;
}
private Object none() throws UnsupportedTypeException, ArityException, UnsupportedMessageException {
if (none == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
var ctx = EpbContext.get(this);
var src = Source.newBuilder("python", """
import site
def nothing():
return None
nothing
""", "nothing.py").build();
var nothingFn = ctx.getEnv().parsePublic(src).call();
assert InteropLibrary.getUncached().isExecutable(nothingFn);
none = InteropLibrary.getUncached().execute(nothingFn);
assert InteropLibrary.getUncached().isNull(none);
}
return none;
}
private Object wrapPythonDate(LocalDate date) throws UnsupportedTypeException, ArityException, UnsupportedMessageException { private Object wrapPythonDate(LocalDate date) throws UnsupportedTypeException, ArityException, UnsupportedMessageException {
if (nodePythonDate == null) { if (nodePythonDate == null) {
@ -129,37 +185,6 @@ public abstract class PyForeignNode extends ForeignFunctionCallNode {
} }
} }
@Specialization
public Object doExecute(
Object[] arguments,
@CachedLibrary("foreignFunction") InteropLibrary interopLibrary,
@CachedLibrary(limit = "3") InteropLibrary iop
) throws InteropException {
for (int i = 0; i < arguments.length; i++) {
var javaTime = iop.isTime(arguments[i]) ? iop.asTime(arguments[i]) : null;
var time = javaTime != null ? wrapPythonTime(javaTime) : null;
var javaDate = iop.isDate(arguments[i]) ? iop.asDate(arguments[i]) : null;
var date = javaDate != null ? wrapPythonDate(javaDate) : null;
var zone = iop.isTimeZone(arguments[i]) ? wrapPythonZone(iop.asTimeZone(arguments[i]), javaTime, javaDate) : null;
if (date != null && time != null) {
arguments[i] = combinePythonDateTimeZone(date, time, zone);
} else if (date != null) {
arguments[i] = date;
} else if (time != null) {
arguments[i] = time;
} else if (zone != null) {
arguments[i] = zone;
}
// Enso's Text type should be converted to TruffleString before sent to python, because
// python does not tend to use InteropLibrary, so any Text object would be treated as
// a foreign object, rather than 'str' type.
if (iop.isString(arguments[i])) {
arguments[i] = iop.asTruffleString(arguments[i]);
}
}
return coercePrimitiveNode.execute(interopLibrary.execute(getForeignFunction(), arguments));
}
@ExportLibrary(InteropLibrary.class) @ExportLibrary(InteropLibrary.class)
static final class ZoneWrapper implements TruffleObject { static final class ZoneWrapper implements TruffleObject {

View File

@ -1,55 +0,0 @@
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.exception.AbstractTruffleException;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.epb.runtime.GuardedTruffleContext;
import org.enso.interpreter.epb.runtime.PolyglotExceptionProxy;
@GenerateUncached
@ReportPolymorphism
public abstract class ContextRewrapExceptionNode extends Node {
/**
* Create a new context rewrap exception node.
*
* @return a new context rewrap exception node
*/
public static ContextRewrapExceptionNode build() {
return ContextRewrapExceptionNodeGen.create();
}
/**
* 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 AbstractTruffleException execute(
AbstractTruffleException value, GuardedTruffleContext origin, GuardedTruffleContext target);
@Specialization(guards = "proxy.getOrigin() == target")
AbstractTruffleException doUnwrapProxy(
PolyglotExceptionProxy proxy, GuardedTruffleContext origin, GuardedTruffleContext target) {
return proxy.getOriginal();
}
@Specialization(guards = "proxy.getTarget() == target")
AbstractTruffleException doAlreadyProxied(
PolyglotExceptionProxy proxy, GuardedTruffleContext origin, GuardedTruffleContext target) {
return proxy;
}
@Fallback
AbstractTruffleException doWrapProxy(
AbstractTruffleException o, GuardedTruffleContext origin, GuardedTruffleContext target) {
return new PolyglotExceptionProxy(o, origin, target);
}
}

View File

@ -1,123 +0,0 @@
package org.enso.interpreter.epb.node;
import com.oracle.truffle.api.dsl.Cached.Exclusive;
import com.oracle.truffle.api.dsl.Cached.Shared;
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.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary;
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 {
/**
* Create a new context rewrap node.
*
* @return a new context rewrap node.
*/
static ContextRewrapNode build() {
return ContextRewrapNodeGen.create();
}
/**
* 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;
}
@Specialization(guards = "bools.isBoolean(b)")
boolean doWrappedBoolean(
Object b,
GuardedTruffleContext origin,
GuardedTruffleContext target,
@Exclusive @CachedLibrary(limit = "5") InteropLibrary bools) {
try {
return bools.asBoolean(b);
} catch (UnsupportedMessageException e) {
throw new IllegalStateException("Impossible, `b` is checked to be a boolean");
}
}
@Specialization(guards = {"numbers.isNumber(l)", "numbers.fitsInLong(l)"})
long doWrappedLong(
Object l,
GuardedTruffleContext origin,
GuardedTruffleContext target,
@Shared("interop") @CachedLibrary(limit = "5") InteropLibrary numbers) {
try {
return numbers.asLong(l);
} catch (UnsupportedMessageException e) {
throw new IllegalStateException("Impossible, `l` is checked to be a long");
}
}
@Specialization(
guards = {"numbers.isNumber(d)", "!numbers.fitsInLong(d)", "numbers.fitsInDouble(d)"})
double doWrappedDouble(
Object d,
GuardedTruffleContext origin,
GuardedTruffleContext target,
@Shared("interop") @CachedLibrary(limit = "5") InteropLibrary numbers) {
try {
return numbers.asDouble(d);
} catch (UnsupportedMessageException e) {
throw new IllegalStateException("Impossible, `l` is checked to be a double");
}
}
@Fallback
Object doWrapProxy(Object o, GuardedTruffleContext origin, GuardedTruffleContext target) {
return new PolyglotProxy(o, origin, target);
}
}

View File

@ -1,191 +0,0 @@
package org.enso.interpreter.epb.node;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.enso.interpreter.epb.EpbContext;
import org.enso.interpreter.epb.EpbLanguage;
import org.enso.interpreter.epb.EpbParser;
import org.enso.interpreter.epb.runtime.ForeignParsingException;
import org.enso.interpreter.epb.runtime.GuardedTruffleContext;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
public class ForeignEvalNode extends RootNode {
private final EpbParser.Result code;
private @Child ForeignFunctionCallNode foreign;
private @Child ContextRewrapNode rewrapNode = ContextRewrapNode.build();
private @Child ContextRewrapExceptionNode rewrapExceptionNode =
ContextRewrapExceptionNode.build();
private @CompilationFinal ForeignParsingException parseException;
private final String[] argNames;
/**
* Creates a new instance of this node
*
* @param language the current language instance
* @param code the result of parsing EPB code
* @param arguments argument names allowed in the function body
* @return an instance of this node
*/
public static ForeignEvalNode build(
EpbLanguage language, EpbParser.Result code, List<String> arguments) {
return new ForeignEvalNode(language, code, arguments);
}
ForeignEvalNode(EpbLanguage language, EpbParser.Result code, List<String> arguments) {
super(language, new FrameDescriptor());
this.code = code;
argNames = arguments.toArray(new String[0]);
}
public Object execute(VirtualFrame frame) {
ensureParsed();
if (foreign != null) {
try {
return foreign.execute(frame.getArguments());
} catch (InteropException ex) {
throw new ForeignParsingException(ex.getMessage(), this);
}
} else {
CompilerDirectives.transferToInterpreter();
throw parseException;
}
}
private void ensureParsed() {
if (foreign == null && parseException == null) {
lockAndParse();
}
}
@CompilerDirectives.TruffleBoundary
private void lockAndParse() throws IllegalStateException {
getLock().lock();
try {
if (foreign == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
var foreignLang = code.getLanguage();
String truffleLangId = foreignLang.getTruffleId();
var context = EpbContext.get(this);
var installedLanguages = context.getEnv().getInternalLanguages();
if (!installedLanguages.containsKey(truffleLangId)) {
this.parseException =
new ForeignParsingException(truffleLangId, installedLanguages.keySet(), this);
} else {
switch (foreignLang) {
case JS:
parseJs();
break;
case PY:
parsePy();
break;
case R:
parseR();
break;
default:
throw new IllegalStateException("Unsupported language resulted from EPB parsing");
}
}
}
} finally {
getLock().unlock();
}
}
private void parseJs() {
EpbContext context = EpbContext.get(this);
GuardedTruffleContext outer = context.getCurrentContext();
GuardedTruffleContext inner = context.getInnerContext();
Object p = inner.enter(this);
try {
String args = Arrays.stream(argNames).skip(1).collect(Collectors.joining(","));
String wrappedSrc =
"var poly_enso_eval=function("
+ args
+ "){\n"
+ code.getForeignSource()
+ "\n};poly_enso_eval";
Source source = Source.newBuilder(code.getLanguage().getTruffleId(), wrappedSrc, "").build();
// After calling inner.enter, operating in a different, isolated truffle instance so need to
// call one with the correct semantics.
CallTarget ct = EpbContext.get(this).getEnv().parsePublic(source);
Object fn = rewrapNode.execute(ct.call(), inner, outer);
foreign = insert(JsForeignNode.build(fn, argNames.length));
} catch (Throwable e) {
if (InteropLibrary.getUncached().isException(e)) {
throw rewrapExceptionNode.execute((AbstractTruffleException) e, inner, outer);
} else {
throw e;
}
} finally {
inner.leave(this, p);
}
}
private void parsePy() {
try {
String args = Arrays.stream(argNames).collect(Collectors.joining(","));
String head = """
import site
import polyglot
@polyglot.export_value
def polyglot_enso_python_eval("""
+ args
+ "):\n";
String indentLines =
code.getForeignSource().lines().map(l -> " " + l).collect(Collectors.joining("\n"));
Source source =
Source.newBuilder(code.getLanguage().getTruffleId(), head + indentLines, "").build();
EpbContext context = EpbContext.get(this);
CallTarget ct = context.getEnv().parsePublic(source);
ct.call();
Object fn = context.getEnv().importSymbol("polyglot_enso_python_eval");
foreign = insert(PyForeignNodeGen.create(fn));
} catch (Throwable e) {
if (InteropLibrary.getUncached().isException(e)) {
throw (AbstractTruffleException) e;
} else {
throw e;
}
}
}
private void parseR() {
EpbContext context = EpbContext.get(this);
GuardedTruffleContext outer = context.getCurrentContext();
GuardedTruffleContext inner = context.getInnerContext();
Object p = inner.enter(this);
try {
String args = String.join(",", argNames);
String wrappedSrc = "function(" + args + "){\n" + code.getForeignSource() + "\n}";
Source source = Source.newBuilder(code.getLanguage().getTruffleId(), wrappedSrc, "").build();
// After calling inner.enter, operating in a different, isolated truffle instance so need to
// call one with the correct semantics.
CallTarget ct = EpbContext.get(this).getEnv().parsePublic(source);
Object fn = rewrapNode.execute(ct.call(), inner, outer);
foreign = insert(RForeignNodeGen.create(fn));
} catch (Throwable e) {
if (InteropLibrary.getUncached().isException(e)) {
throw rewrapExceptionNode.execute((AbstractTruffleException) e, inner, outer);
} else {
throw e;
}
} finally {
inner.leave(this, p);
}
}
}

View File

@ -1,43 +0,0 @@
package org.enso.interpreter.epb.node;
import com.oracle.truffle.api.dsl.NodeField;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.library.CachedLibrary;
/** A node responsible for performing foreign JS calls. */
@NodeField(name = "foreignFunction", type = Object.class)
@NodeField(name = "arity", type = int.class)
public abstract class JsForeignNode extends ForeignFunctionCallNode {
private @Child CoercePrimitiveNode coercePrimitiveNode = CoercePrimitiveNode.build();
abstract Object getForeignFunction();
abstract int getArity();
/**
* Creates a new instance of this node.
*
* @param argumentsCount the number of arguments the function expects (including {@code this})
* @param jsFunction the parsed JS object (required to be {@link
* InteropLibrary#isExecutable(Object)})
* @return a node able to call the JS function with given arguments
*/
public static JsForeignNode build(Object jsFunction, int argumentsCount) {
return JsForeignNodeGen.create(jsFunction, argumentsCount);
}
@Specialization
Object doExecute(
Object[] arguments, @CachedLibrary("foreignFunction") InteropLibrary interopLibrary)
throws InteropException {
int newLength = getArity() - 1;
Object[] positionalArgs = new Object[newLength];
System.arraycopy(arguments, 1, positionalArgs, 0, newLength);
return coercePrimitiveNode.execute(
interopLibrary.invokeMember(
getForeignFunction(), "apply", arguments[0], new ReadOnlyArray(positionalArgs)));
}
}

View File

@ -1,22 +0,0 @@
package org.enso.interpreter.epb.node;
import com.oracle.truffle.api.dsl.NodeField;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.library.CachedLibrary;
@NodeField(name = "foreignFunction", type = Object.class)
public abstract class RForeignNode extends ForeignFunctionCallNode {
private @Child CoercePrimitiveNode coercePrimitiveNode = CoercePrimitiveNode.build();
abstract Object getForeignFunction();
@Specialization
public Object doExecute(
Object[] arguments, @CachedLibrary("foreignFunction") InteropLibrary interopLibrary)
throws InteropException {
return coercePrimitiveNode.execute(interopLibrary.execute(getForeignFunction(), arguments));
}
}

View File

@ -1,96 +0,0 @@
package org.enso.interpreter.epb.node;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import java.util.Arrays;
/**
* A primitive boxed array type to be used only in EPB.
*
* <p>{@link ReadOnlyArray} is essentially a stripped-down, read-only, version of {@link
* org.enso.interpreter.runtime.data.vector.Array}, used for passing arguments. The latter cannot be
* used in EPB because EPB is a dependency of runtime.
*/
@ExportLibrary(InteropLibrary.class)
public final class ReadOnlyArray implements TruffleObject {
private final Object[] items;
/**
* Creates a new array
*
* @param items the element values
*/
public ReadOnlyArray(Object... items) {
this.items = items;
}
/**
* Marks the object as array-like for Polyglot APIs.
*
* @return {@code true}
*/
@ExportMessage
public boolean hasArrayElements() {
return true;
}
/**
* Handles reading an element by index through the polyglot API.
*
* @param index the index to read
* @return the element value at the provided index
* @throws InvalidArrayIndexException when the index is out of bounds.
*/
@ExportMessage
public Object readArrayElement(long index) throws InvalidArrayIndexException {
if (index >= items.length || index < 0) {
throw InvalidArrayIndexException.create(index);
}
return items[(int) index];
}
/**
* Exposes the size of this collection through the polyglot API.
*
* @return the size of this array
*/
@ExportMessage
long getArraySize() {
return items.length;
}
/**
* Exposes an index validity check through the polyglot API.
*
* @param index the index to check
* @return {@code true} if the index is valid, {@code false} otherwise.
*/
@ExportMessage
boolean isArrayElementReadable(long index) {
return index < getArraySize() && index >= 0;
}
@ExportMessage
void writeArrayElement(long index, Object value) throws UnsupportedMessageException {
throw UnsupportedMessageException.create();
}
@ExportMessage
boolean isArrayElementModifiable(long index) {
return false;
}
@ExportMessage
boolean isArrayElementInsertable(long index) {
return false;
}
@Override
public String toString() {
return Arrays.toString(items);
}
}

View File

@ -1,85 +0,0 @@
package org.enso.interpreter.epb.runtime;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.TruffleContext;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.nodes.Node;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
/** Wraps a {@link TruffleContext} by providing an optional GIL functionality. */
public class GuardedTruffleContext {
private final TruffleContext context;
private final Lock lock;
/**
* Creates a new instance of this wrapper.
*
* @param context the Truffle context to wrap
* @param isSingleThreaded whether or not the context should be accessed through a GIL.
*/
public GuardedTruffleContext(TruffleContext context, boolean isSingleThreaded) {
this.context = context;
if (isSingleThreaded) {
this.lock = new ReentrantLock();
} else {
this.lock = null;
}
}
/**
* Spawns new thread with associated {@code context}
*
* @param env environment to spawn the thread in
* @param run code to execute in given TruffleContext
*/
public Thread createThread(TruffleLanguage.Env env, Consumer<TruffleContext> run) {
return env.newTruffleThreadBuilder(() -> run.accept(context)).context(context).build();
}
/**
* Enters this context. If this wrapper is single threaded and the context is in use, this method
* will block indefinitely until the context becomes available.
*
* <p>Any code following a call to this method should be executed in a try/finally block, with
* {@link #leave(Node, Object)} being called in the finally block. It is crucial that this context
* is always left as soon as guest code execution finishes.
*
* <p>The token returned from this method may not be stored or used for any purpose other than
* leaving the context.
*
* @param node the node to enter this context for
* @return a context restoration token that must be passed to {@link #leave(Node, Object)}
*/
public Object enter(Node node) {
if (lock != null) {
lock();
}
return context.enter(node);
}
@CompilerDirectives.TruffleBoundary
private void lock() {
lock.lock();
}
/**
* Leaves the context and unlocks it if this wrapper is GILed.
*
* @param node the node to leave this context for (this must correspond to the same node used in
* the call that provided the {@code prev} token
* @param prev the token obtained from the call to {@link #enter(Node)}
*/
public void leave(Node node, Object prev) {
context.leave(node, prev);
if (lock != null) {
unlock();
}
}
@CompilerDirectives.TruffleBoundary
private void unlock() {
lock.unlock();
}
}

View File

@ -1,61 +0,0 @@
package org.enso.interpreter.epb.runtime;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
/**
* A wrapper for exceptions that cross the polyglot boundary.
*
* <p>It is responsible for proxying messages across the polyglot boundary specifically for the case
* of exceptions. Without this, exceptions (as non-linear control flow) would bubble up into Enso
* without their context. This would allow them to be caught, but Enso would have no means of
* executing code on the guest-language exception. Instead, we wrap the exception into this proxy,
* which holds onto the foreign exception, as well as the two contexts necessary for mediating calls
* between Enso and the foreign language.
*
* <p>This is _separate_ to the {@link PolyglotProxy} as we did not want to make that proxy into an
* {@link AbstractTruffleException}. This means that we have more control over when foreign objects
* are represented as exceptions.
*/
@ExportLibrary(value = InteropLibrary.class, delegateTo = "delegate")
public class PolyglotExceptionProxy extends AbstractTruffleException {
final PolyglotProxy delegate;
final AbstractTruffleException original;
public PolyglotExceptionProxy(
AbstractTruffleException prototype,
GuardedTruffleContext origin,
GuardedTruffleContext target) {
super(prototype);
this.original = prototype;
this.delegate = new PolyglotProxy(prototype, origin, target);
}
@ExportMessage
boolean isException() {
return true;
}
@ExportMessage
RuntimeException throwException() {
throw this;
}
public PolyglotProxy getDelegate() {
return delegate;
}
public AbstractTruffleException getOriginal() {
return original;
}
public GuardedTruffleContext getOrigin() {
return delegate.getOrigin();
}
public GuardedTruffleContext getTarget() {
return delegate.getTarget();
}
}

View File

@ -0,0 +1,49 @@
import static org.junit.Assert.fail;
import org.junit.Test;
public class EpbLanguageDependenciesTest {
public EpbLanguageDependenciesTest() {}
@Test(expected = ClassNotFoundException.class)
public void avoidFlatbuffers() throws Exception {
var c = Class.forName("com.google.flatbuffers.Constants");
fail("No class should be found: " + c);
}
@Test(expected = ClassNotFoundException.class)
public void avoidJackson() throws Exception {
var c = Class.forName("com.fasterxml.jackson.core.JsonParser");
fail("No class should be found: " + c);
}
@Test(expected = ClassNotFoundException.class)
public void avoidCirce() throws Exception {
var c = Class.forName("io.circe.Codec");
fail("No class should be found: " + c);
}
@Test(expected = ClassNotFoundException.class)
public void avoidApacheCommons() throws Exception {
var c = Class.forName("org.apache.commons.io.Charsets");
fail("No class should be found: " + c);
}
@Test(expected = ClassNotFoundException.class)
public void avoidCats() throws Exception {
var c = Class.forName("cats.Align");
fail("No class should be found: " + c);
}
@Test(expected = ClassNotFoundException.class)
public void avoidSlf4j() throws Exception {
var c = Class.forName("org.slf4j.Logger");
fail("No class should be found: " + c);
}
@Test(expected = ClassNotFoundException.class)
public void avoidSnakeyaml() throws Exception {
var c = Class.forName("org.yaml.snakeyaml.Yaml");
fail("No class should be found: " + c);
}
}

View File

@ -0,0 +1,29 @@
package org.enso.interpreter.epb;
import java.util.Collections;
import org.junit.Test;
import static org.junit.Assert.*;
import com.oracle.truffle.api.source.Source;
public class ForeignEvalNodeTest {
public ForeignEvalNodeTest() {
}
@Test
public void sourceWithoutHash() throws Exception {
var src = Source.newBuilder("epb", """
nonsensecontent
""", "simple.test").build();
var node = ForeignEvalNode.parse(null, src, Collections.emptyList());
try {
var res = node.execute(null);
fail("Unexpected result: " + res);
} catch (ForeignParsingException e) {
assertEquals("No # found", e.getMessage());
}
}
}

View File

@ -1,11 +1,14 @@
package org.enso.interpreter.test; package org.enso.interpreter.test;
import static org.junit.Assert.assertTrue; import java.util.concurrent.Executors;
import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value; import org.graalvm.polyglot.Value;
import org.junit.AfterClass; import org.junit.AfterClass;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
public class ForeignMethodInvokeTest extends TestBase { public class ForeignMethodInvokeTest extends TestBase {
@ -13,7 +16,7 @@ public class ForeignMethodInvokeTest extends TestBase {
@BeforeClass @BeforeClass
public static void prepareCtx() { public static void prepareCtx() {
ctx = defaultContextBuilder("enso").build(); ctx = defaultContextBuilder("enso", "js").build();
} }
@AfterClass @AfterClass
@ -44,4 +47,66 @@ public class ForeignMethodInvokeTest extends TestBase {
e.getMessage().matches("Cannot parse foreign python method. Only available languages are .+")); e.getMessage().matches("Cannot parse foreign python method. Only available languages are .+"));
} }
} }
@Test
public void testInteropWithJavaScript() throws Exception {
var source = """
from Standard.Base import all
foreign js js_array t = \"\"\"
return [1, 2, t]
third t = js_array t
""";
var module = ctx.eval("enso", source);
var third = module.invokeMember("eval_expression", "third");
var res = third.execute(13);
assertTrue("It is an array", res.hasArrayElements());
assertEquals(3, res.getArraySize());
assertEquals(1, res.getArrayElement(0).asInt());
assertEquals(2, res.getArrayElement(1).asInt());
assertEquals(13, res.getArrayElement(2).asInt());
var res2 = Executors.newSingleThreadExecutor().submit(() -> {
return third.execute(12);
}).get();
assertTrue("It is an array2", res2.hasArrayElements());
assertEquals(12, res2.getArrayElement(2).asInt());
}
@Ignore
@Test
public void testParallelInteropWithJavaScript() throws Exception {
var source = """
from Standard.Base import all
polyglot java import java.lang.Thread
foreign js js_array t f = \"\"\"
f(300)
return [1, 2, t]
third t = js_array t (delay-> Thread.sleep delay)
""";
var module = ctx.eval("enso", source);
var third = module.invokeMember("eval_expression", "third");
var future = Executors.newSingleThreadExecutor().submit(() -> {
return third.execute(12);
});
var res = third.execute(13);
assertTrue("It is an array", res.hasArrayElements());
assertEquals(3, res.getArraySize());
assertEquals(1, res.getArrayElement(0).asInt());
assertEquals(2, res.getArrayElement(1).asInt());
assertEquals(13, res.getArrayElement(2).asInt());
var res2 = future.get();
assertTrue("It is an array2", res2.hasArrayElements());
assertEquals(12, res2.getArrayElement(2).asInt());
}
} }

View File

@ -1,19 +1,21 @@
package org.enso.interpreter.test.instrument; package org.enso.interpreter.test.instrument;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.logging.Level; import java.util.logging.Level;
import org.enso.interpreter.test.MockLogHandler; import org.enso.interpreter.test.MockLogHandler;
import org.enso.polyglot.MethodNames;
import org.enso.polyglot.RuntimeOptions; import org.enso.polyglot.RuntimeOptions;
import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.io.IOAccess; import org.graalvm.polyglot.io.IOAccess;
import org.junit.AfterClass; import org.junit.AfterClass;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
public class VerifyJavaScriptIsAvailableTest { public class VerifyLanguageAvailabilityTest {
private static Context ctx; private static Context ctx;
private static MockLogHandler handler; private static MockLogHandler handler;
@ -44,16 +46,22 @@ public class VerifyJavaScriptIsAvailableTest {
var args = var args =
handler.assertMessage( handler.assertMessage(
"epb.org.enso.interpreter.epb.EpbContext", "Done initializing language"); "epb.org.enso.interpreter.epb.EpbContext", "Parsing foreign script");
assertEquals("js", args[0]); assertEquals("js", args[0]);
assertEquals(Boolean.TRUE, args[1]); assertEquals("mul.mul", args[1]);
} }
@Test @Test
public void javaScriptIsPresent() { public void javaScriptIsPresent() throws Exception {
var js = ctx.getEngine().getLanguages().get("js"); var js = ctx.getEngine().getLanguages().get("js");
assertNotNull("JavaScript is available", js); assertNotNull("JavaScript is available", js);
var fourtyTwo = ctx.eval("js", "6 * 7"); var src = Source.newBuilder("enso", """
foreign js mul a b = \"\"\"
return a * b
run = mul 6 7
""", "mul.enso").build();
var fourtyTwo = ctx.eval(src).invokeMember(MethodNames.Module.EVAL_EXPRESSION, "run");
assertEquals(42, fourtyTwo.asInt()); assertEquals(42, fourtyTwo.asInt());
} }

View File

@ -7,6 +7,7 @@ import java.util.Objects;
import org.enso.compiler.Compiler; import org.enso.compiler.Compiler;
import org.enso.compiler.context.InlineContext; import org.enso.compiler.context.InlineContext;
import org.enso.compiler.context.LocalScope;
import org.enso.compiler.context.ModuleContext; import org.enso.compiler.context.ModuleContext;
import org.enso.compiler.data.CompilerConfig; import org.enso.compiler.data.CompilerConfig;
import org.enso.compiler.exception.CompilationAbortedException; import org.enso.compiler.exception.CompilationAbortedException;
@ -24,7 +25,6 @@ import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.node.ProgramRootNode; import org.enso.interpreter.node.ProgramRootNode;
import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.IrToTruffle; import org.enso.interpreter.runtime.IrToTruffle;
import org.enso.compiler.context.LocalScope;
import org.enso.interpreter.runtime.state.ExecutionEnvironment; import org.enso.interpreter.runtime.state.ExecutionEnvironment;
import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag; import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag;
import org.enso.interpreter.runtime.tag.IdentifiedTag; import org.enso.interpreter.runtime.tag.IdentifiedTag;
@ -32,7 +32,6 @@ import org.enso.interpreter.runtime.tag.Patchable;
import org.enso.interpreter.util.FileDetector; import org.enso.interpreter.util.FileDetector;
import org.enso.lockmanager.client.ConnectedLockManager; import org.enso.lockmanager.client.ConnectedLockManager;
import org.enso.logger.masking.MaskingFactory; import org.enso.logger.masking.MaskingFactory;
import org.enso.polyglot.ForeignLanguage;
import org.enso.polyglot.LanguageInfo; import org.enso.polyglot.LanguageInfo;
import org.enso.polyglot.RuntimeOptions; import org.enso.polyglot.RuntimeOptions;
import org.enso.syntax2.Line; import org.enso.syntax2.Line;
@ -71,7 +70,7 @@ import com.oracle.truffle.api.nodes.RootNode;
defaultMimeType = LanguageInfo.MIME_TYPE, defaultMimeType = LanguageInfo.MIME_TYPE,
characterMimeTypes = {LanguageInfo.MIME_TYPE}, characterMimeTypes = {LanguageInfo.MIME_TYPE},
contextPolicy = TruffleLanguage.ContextPolicy.EXCLUSIVE, contextPolicy = TruffleLanguage.ContextPolicy.EXCLUSIVE,
dependentLanguages = {ForeignLanguage.ID}, dependentLanguages = {"epb"},
fileTypeDetectors = FileDetector.class, fileTypeDetectors = FileDetector.class,
services= { Timer.class, NotificationHandler.Forwarder.class, LockManager.class } services= { Timer.class, NotificationHandler.Forwarder.class, LockManager.class }
) )

View File

@ -173,8 +173,15 @@ public class FunctionCallInstrumentationNode extends Node implements Instrumenta
/** @return the source section of this node. */ /** @return the source section of this node. */
@Override @Override
public SourceSection getSourceSection() { public SourceSection getSourceSection() {
Node parent = getParent(); var parent = getParent();
return parent == null ? null : parent.getSourceSection(); while (parent != null) {
var ss = parent.getSourceSection();
if (ss != null) {
return ss;
}
parent = parent.getParent();
}
return null;
} }
/** @return the expression ID of this node. */ /** @return the expression ID of this node. */

View File

@ -1,5 +1,6 @@
package org.enso.interpreter.node.expression.builtin.meta; package org.enso.interpreter.node.expression.builtin.meta;
import org.enso.interpreter.EnsoLanguage;
import org.enso.interpreter.instrument.Timer; import org.enso.interpreter.instrument.Timer;
import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode;
import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.EnsoContext;
@ -8,8 +9,8 @@ import org.enso.interpreter.runtime.data.EnsoObject;
import org.enso.interpreter.runtime.data.vector.ArrayLikeHelpers; import org.enso.interpreter.runtime.data.vector.ArrayLikeHelpers;
import org.enso.polyglot.debugger.IdExecutionService; import org.enso.polyglot.debugger.IdExecutionService;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.instrumentation.EventBinding; import com.oracle.truffle.api.instrumentation.EventBinding;
import com.oracle.truffle.api.interop.InteropException; import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.InteropLibrary;
@ -17,7 +18,7 @@ import com.oracle.truffle.api.interop.InteropLibrary;
final class Instrumentor implements EnsoObject, IdExecutionService.Callbacks { final class Instrumentor implements EnsoObject, IdExecutionService.Callbacks {
private final IdExecutionService service; private final IdExecutionService service;
private final CallTarget target; private final RootCallTarget target;
private final Module module; private final Module module;
private final Object onEnter; private final Object onEnter;
private final Object onReturn; private final Object onReturn;
@ -25,7 +26,7 @@ final class Instrumentor implements EnsoObject, IdExecutionService.Callbacks {
private final Object onCall; private final Object onCall;
private final EventBinding<?> handle; private final EventBinding<?> handle;
Instrumentor(Module module, IdExecutionService service, CallTarget target) { Instrumentor(Module module, IdExecutionService service, RootCallTarget target) {
this.module = module; this.module = module;
this.service = service; this.service = service;
this.target = target; this.target = target;
@ -80,10 +81,12 @@ final class Instrumentor implements EnsoObject, IdExecutionService.Callbacks {
var result = onReturnExpr == null || !iop.isString(onReturnExpr) ? var result = onReturnExpr == null || !iop.isString(onReturnExpr) ?
info.getResult() info.getResult()
: :
InstrumentorEvalNode.asSuspendedEval(onReturnExpr, info); InstrumentorEvalNode.asSuspendedEval(EnsoLanguage.get(target.getRootNode()), onReturnExpr, info);
iop.execute(onReturn, info.getId().toString(), result); iop.execute(onReturn, info.getId().toString(), result);
} }
} catch (InteropException ignored) { } catch (Throwable ignored) {
CompilerDirectives.transferToInterpreter();
ignored.printStackTrace();
} }
} }

View File

@ -1,10 +1,12 @@
package org.enso.interpreter.node.expression.builtin.meta; package org.enso.interpreter.node.expression.builtin.meta;
import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.nodes.RootNode;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import org.enso.interpreter.EnsoLanguage; import org.enso.interpreter.EnsoLanguage;
import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.callable.Annotation; import org.enso.interpreter.runtime.callable.Annotation;
@ -25,14 +27,22 @@ final class InstrumentorEvalNode extends RootNode {
new boolean[] {true, true}, new boolean[] {true, true},
new CallArgumentInfo[0], new CallArgumentInfo[0],
new Annotation[0]); new Annotation[0]);
private static final RootCallTarget CALL = new InstrumentorEvalNode().getCallTarget(); private static Reference<InstrumentorEvalNode> last = new WeakReference<>(null);
private InstrumentorEvalNode() { private InstrumentorEvalNode(EnsoLanguage language) {
super(EnsoLanguage.get(null)); super(language);
} }
static Function asSuspendedEval(Object expr, IdExecutionService.Info info) { @TruffleBoundary
return new Function(CALL, null, SUSPENDED_EVAL, new Object[] {expr, info}, new Object[0]); static Function asSuspendedEval(
EnsoLanguage language, Object expr, IdExecutionService.Info info) {
var node = last.get();
if (node == null || node.getLanguage(EnsoLanguage.class) != language) {
node = new InstrumentorEvalNode(language);
last = new WeakReference<>(node);
}
var call = node.getCallTarget();
return new Function(call, null, SUSPENDED_EVAL, new Object[] {expr, info}, new Object[0]);
} }
@Override @Override

View File

@ -53,7 +53,14 @@ public abstract class PowNode extends IntegerNode {
@Specialization @Specialization
Object doBigInteger(long self, EnsoBigInteger that) { Object doBigInteger(long self, EnsoBigInteger that) {
return doBigInteger(toBigInteger(self), that); var thatValue = that.getValue();
if (thatValue.signum() > 0) {
return Math.pow((double) self, BigIntegerOps.toDouble(thatValue));
} else if (thatValue.signum() == 0) {
return 1.0D;
} else {
return 0.0D;
}
} }
@Specialization @Specialization
@ -69,10 +76,11 @@ public abstract class PowNode extends IntegerNode {
@Specialization @Specialization
Object doBigInteger(EnsoBigInteger self, EnsoBigInteger that) { Object doBigInteger(EnsoBigInteger self, EnsoBigInteger that) {
if (that.getValue().signum() > 0) { var thatValue = that.getValue();
return Math.pow( if (thatValue.signum() > 0) {
BigIntegerOps.toDouble(self.getValue()), BigIntegerOps.toDouble(that.getValue())); var selfValue = self.getValue();
} else if (that.getValue().signum() == 0) { return Math.pow(BigIntegerOps.toDouble(selfValue), BigIntegerOps.toDouble(thatValue));
} else if (thatValue.signum() == 0) {
return 1.0D; return 1.0D;
} else { } else {
return 0.0D; return 0.0D;

View File

@ -39,7 +39,6 @@ import org.enso.librarymanager.resolved.LibraryRoot;
import org.enso.pkg.Package; import org.enso.pkg.Package;
import org.enso.pkg.PackageManager; import org.enso.pkg.PackageManager;
import org.enso.pkg.QualifiedName; import org.enso.pkg.QualifiedName;
import org.enso.polyglot.ForeignLanguage;
import org.enso.polyglot.LanguageInfo; import org.enso.polyglot.LanguageInfo;
import org.enso.polyglot.RuntimeOptions; import org.enso.polyglot.RuntimeOptions;
import org.enso.polyglot.debugger.IdExecutionService; import org.enso.polyglot.debugger.IdExecutionService;
@ -50,7 +49,6 @@ import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.InstrumentInfo;
import com.oracle.truffle.api.ThreadLocalAction; import com.oracle.truffle.api.ThreadLocalAction;
import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleFile; import com.oracle.truffle.api.TruffleFile;
@ -210,7 +208,7 @@ public final class EnsoContext {
var preinit = environment.getOptions().get(RuntimeOptions.PREINITIALIZE_KEY); var preinit = environment.getOptions().get(RuntimeOptions.PREINITIALIZE_KEY);
if (preinit != null && preinit.length() > 0) { if (preinit != null && preinit.length() > 0) {
var epb = environment.getInternalLanguages().get(ForeignLanguage.ID); var epb = environment.getInternalLanguages().get("epb");
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
var run = (Consumer<String>) environment.lookup(epb, Consumer.class); var run = (Consumer<String>) environment.lookup(epb, Consumer.class);
if (run != null) { if (run != null) {

View File

@ -37,11 +37,20 @@ final class Array implements EnsoObject {
* *
* @param items the element values * @param items the element values
*/ */
Array(Object... items) { private Array(Object... items) {
assert noNulls(items);
this.items = items; this.items = items;
} }
static Array wrap(Object... items) {
assert noNulls(items);
return new Array(items);
}
static Array allocate(long size) {
var arr = new Object[Math.toIntExact(size)];
return new Array(arr);
}
private static boolean noNulls(Object[] arr) { private static boolean noNulls(Object[] arr) {
for (Object o : arr) { for (Object o : arr) {
if (o == null) { if (o == null) {

View File

@ -188,7 +188,7 @@ final class ArrayBuilder implements EnsoObject {
if (arr instanceof double[] doubles) { if (arr instanceof double[] doubles) {
yield Vector.fromDoubleArray(doubles); yield Vector.fromDoubleArray(doubles);
} }
yield Vector.fromInteropArray(new Array((Object[])arr)); yield Vector.fromInteropArray(Array.wrap((Object[])arr));
} }
default -> throw UnknownIdentifierException.create(name); default -> throw UnknownIdentifierException.create(name);
}; };

View File

@ -3,6 +3,7 @@ package org.enso.interpreter.runtime.data.vector;
import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.NeverDefault; import com.oracle.truffle.api.dsl.NeverDefault;
import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException; import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.interop.UnsupportedMessageException;
@ -33,7 +34,10 @@ public abstract class ArrayLikeAtNode extends Node {
try { try {
return self.getItems()[Math.toIntExact(index)]; return self.getItems()[Math.toIntExact(index)];
} catch (ArithmeticException | IndexOutOfBoundsException ex) { } catch (ArithmeticException | IndexOutOfBoundsException ex) {
throw InvalidArrayIndexException.create(index, ex); var cause =
new AbstractTruffleException(
ex.getMessage(), ex, AbstractTruffleException.UNLIMITED_STACK_TRACE, this) {};
throw InvalidArrayIndexException.create(index, cause);
} }
} }

View File

@ -64,8 +64,7 @@ public final class ArrayLikeHelpers {
* @return the array instance * @return the array instance
*/ */
public static EnsoObject allocate(long size) { public static EnsoObject allocate(long size) {
var arr = new Object[Math.toIntExact(size)]; return Array.allocate(size);
return new Array(arr);
} }
@Builtin.Method( @Builtin.Method(
@ -107,7 +106,7 @@ public final class ArrayLikeHelpers {
return Vector.fromDoubleArray(doubles); return Vector.fromDoubleArray(doubles);
} }
if (nonTrivialEnsoValue) { if (nonTrivialEnsoValue) {
return Vector.fromInteropArray(new Array((Object[])res)); return Vector.fromInteropArray(Array.wrap((Object[])res));
} else { } else {
return Vector.fromEnsoOnlyArray((Object[])res); return Vector.fromEnsoOnlyArray((Object[])res);
} }
@ -136,15 +135,15 @@ public final class ArrayLikeHelpers {
} }
public static EnsoObject wrapEnsoObjects(EnsoObject... arr) { public static EnsoObject wrapEnsoObjects(EnsoObject... arr) {
return new Array((Object[]) arr); return Array.wrap((Object[]) arr);
} }
public static EnsoObject wrapStrings(String... arr) { public static EnsoObject wrapStrings(String... arr) {
return new Array((Object[]) arr); return Array.wrap((Object[]) arr);
} }
public static EnsoObject wrapObjectsWithCheckAt(Object... arr) { public static EnsoObject wrapObjectsWithCheckAt(Object... arr) {
return new Array((Object[]) arr); return Array.wrap((Object[]) arr);
} }
public static EnsoObject empty() { public static EnsoObject empty() {
@ -152,7 +151,7 @@ public final class ArrayLikeHelpers {
} }
public static EnsoObject asVectorWithCheckAt(Object... arr) { public static EnsoObject asVectorWithCheckAt(Object... arr) {
return Vector.fromInteropArray(new Array((Object[]) arr)); return Vector.fromInteropArray(Array.wrap((Object[]) arr));
} }
public static EnsoObject asVectorFromArray(Object storage) { public static EnsoObject asVectorFromArray(Object storage) {

View File

@ -187,7 +187,11 @@ public class DebugLocalScope implements EnsoObject {
@ExportMessage @ExportMessage
@TruffleBoundary @TruffleBoundary
Object readMember(String member, @CachedLibrary("this") InteropLibrary interop) { Object readMember(String member, @CachedLibrary("this") InteropLibrary interop)
throws UnknownIdentifierException {
if (!allBindings.containsKey(member)) {
throw UnknownIdentifierException.create(member);
}
FramePointer framePtr = allBindings.get(member); FramePointer framePtr = allBindings.get(member);
var value = getValue(frame, framePtr); var value = getValue(frame, framePtr);
return value != null ? value : DataflowError.UNINITIALIZED; return value != null ? value : DataflowError.UNINITIALIZED;

View File

@ -62,7 +62,6 @@ import org.enso.compiler.pass.resolve.{
TypeNames, TypeNames,
TypeSignatures TypeSignatures
} }
import org.enso.polyglot.ForeignLanguage
import org.enso.interpreter.node.callable.argument.ReadArgumentNode import org.enso.interpreter.node.callable.argument.ReadArgumentNode
import org.enso.interpreter.node.callable.argument.ReadArgumentCheckNode import org.enso.interpreter.node.callable.argument.ReadArgumentCheckNode
import org.enso.interpreter.node.callable.function.{ import org.enso.interpreter.node.callable.function.{
@ -1897,7 +1896,7 @@ class IrToTruffle(
val bodyExpr = body match { val bodyExpr = body match {
case Foreign.Definition(lang, code, _, _, _) => case Foreign.Definition(lang, code, _, _, _) =>
buildForeignBody( buildForeignBody(
ForeignLanguage.getBySyntacticTag(lang), lang,
code, code,
arguments.map(_.name.name), arguments.map(_.name.name),
argSlotIdxs argSlotIdxs
@ -1958,12 +1957,13 @@ class IrToTruffle(
} }
private def buildForeignBody( private def buildForeignBody(
language: ForeignLanguage, language: String,
code: String, code: String,
argumentNames: List[String], argumentNames: List[String],
argumentSlotIdxs: List[Int] argumentSlotIdxs: List[Int]
): RuntimeExpression = { ): RuntimeExpression = {
val src = language.buildSource(code, scopeName) val src =
Source.newBuilder("epb", language + "#" + code, scopeName).build()
val foreignCt = context.parseInternal(src, argumentNames: _*) val foreignCt = context.parseInternal(src, argumentNames: _*)
val argumentReaders = argumentSlotIdxs val argumentReaders = argumentSlotIdxs
.map(slotIdx => .map(slotIdx =>

View File

@ -1,11 +1,8 @@
package org.enso.benchmarks.processor; package org.enso.benchmarks.processor;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
@ -73,13 +70,12 @@ public class BenchProcessor extends AbstractProcessor {
"import org.enso.benchmarks.Utils;"); "import org.enso.benchmarks.Utils;");
public BenchProcessor() { public BenchProcessor() {
ensoDir = Utils.findRepoRootDir(); ensoDir = Utils.findRepoRootDir();
// Note that ensoHomeOverride does not have to exist, only its parent directory // Note that ensoHomeOverride does not have to exist, only its parent directory
ensoHomeOverride = ensoDir.toPath().resolve("distribution").resolve("component").toFile(); ensoHomeOverride = ensoDir.toPath().resolve("distribution").resolve("component").toFile();
} }
@Override @Override
public SourceVersion getSupportedSourceVersion() { public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest(); return SourceVersion.latest();

View File

@ -38,7 +38,7 @@ public abstract class Builder {
case BigIntegerType x -> new BigIntegerBuilder(size, problemAggregator); case BigIntegerType x -> new BigIntegerBuilder(size, problemAggregator);
case null -> new InferredBuilder(size, problemAggregator); case null -> new InferredBuilder(size, problemAggregator);
}; };
assert builder.getType().equals(type); assert java.util.Objects.equals(builder.getType(), type);
return builder; return builder;
} }

View File

@ -27,22 +27,23 @@ type Filter
pair = Meta.atom_with_hole (h -> Filter.Item n h) pair = Meta.atom_with_hole (h -> Filter.Item n h)
newLast = pair.value newLast = pair.value
set_last newLast set_last newLast
Filter.Head head.first pair.fill [True, Filter.Head head.first pair.fill]
case self of case self of
Filter.Empty -> Filter.Empty ->
pair = Meta.atom_with_hole (h -> Filter.Item n h) pair = Meta.atom_with_hole (h -> Filter.Item n h)
Filter.Head pair.value pair.fill [True, Filter.Head pair.value pair.fill]
Filter.Head _ _ -> if iterate self.first then appendN self self.set_last else self Filter.Head _ _ -> if iterate self.first then appendN self self.set_last else [False, Nothing]
type Primes type Primes
Alg generator filter Alg generator filter
Primes.next self = case self of Primes.next self = case self of
Primes.Alg g f -> Primes.Alg g f ->
filter = f.acceptAndAdd g.n found_new = f.acceptAndAdd g.n
new_primes = Primes.Alg g.next filter if found_new.first then Primes.Alg g.next found_new.last else
if Meta.is_same_object filter f then @Tail_Call new_primes.next else new_primes new_primes = Primes.Alg g.next f
@Tail_Call new_primes.next
Primes.last_prime self = case self of Primes.last_prime self = case self of
Primes.Alg g _ -> g.n - 1 Primes.Alg g _ -> g.n - 1

View File

@ -33,7 +33,7 @@ foreign js filter_in_js = """
while (filter != null) { while (filter != null) {
if (filter.number) { if (filter.number) {
if (n % filter.number === 0) { if (n % filter.number === 0) {
return null; return [false, null];
} }
if (filter.number > sqrt) { if (filter.number > sqrt) {
break; break;
@ -44,7 +44,7 @@ foreign js filter_in_js = """
var newFilter = new Filter(n); var newFilter = new Filter(n);
this.last.next = newFilter; this.last.next = newFilter;
this.last = newFilter; this.last = newFilter;
return this; return [true, this];
}; };
return new Filter(null); return new Filter(null);
@ -65,9 +65,10 @@ type Primes
Primes.next self = case self of Primes.next self = case self of
Primes.Alg g f -> Primes.Alg g f ->
filter = f.acceptAndAdd g.n found_new = f.acceptAndAdd g.n
new_primes = Primes.Alg g.next filter if found_new.first then Primes.Alg g.next found_new.last else
if Meta.is_same_object filter f then @Tail_Call new_primes.next else new_primes new_primes = Primes.Alg g.next f
@Tail_Call new_primes.next
Primes.last_prime self = case self of Primes.last_prime self = case self of
Primes.Alg g _ -> g.n - 1 Primes.Alg g _ -> g.n - 1

View File

@ -27,22 +27,23 @@ type Filter
pair = Meta.atom_with_hole (h -> Filter.Item n h) pair = Meta.atom_with_hole (h -> Filter.Item n h)
newLast = pair.value newLast = pair.value
set_last newLast set_last newLast
Filter.Head head.first pair.fill [True, Filter.Head head.first pair.fill]
case self of case self of
Filter.Empty -> Filter.Empty ->
pair = Meta.atom_with_hole (h -> Filter.Item n h) pair = Meta.atom_with_hole (h -> Filter.Item n h)
Filter.Head pair.value pair.fill [True, Filter.Head pair.value pair.fill]
Filter.Head _ _ -> if iterate self.first then appendN self self.set_last else self Filter.Head _ _ -> if iterate self.first then appendN self self.set_last else [False, Nothing]
type Primes type Primes
Alg generator filter Alg generator filter
Primes.next self = case self of Primes.next self = case self of
Primes.Alg g f -> Primes.Alg g f ->
filter = f.acceptAndAdd g.n found_new = f.acceptAndAdd g.n
new_primes = Primes.Alg g.next filter if found_new.first then Primes.Alg g.next found_new.last else
if Meta.is_same_object filter f then @Tail_Call new_primes.next else new_primes new_primes = Primes.Alg g.next f
@Tail_Call new_primes.next
Primes.last_prime self = case self of Primes.last_prime self = case self of
Primes.Alg g _ -> g.n - 1 Primes.Alg g _ -> g.n - 1

View File

@ -24,23 +24,24 @@ type Filter
pair = Meta.atom_with_hole (h -> Filter.Item n h) pair = Meta.atom_with_hole (h -> Filter.Item n h)
newLast = pair.value newLast = pair.value
set_last newLast set_last newLast
Filter.Head head.first pair.fill [True, Filter.Head head.first pair.fill]
case self of case self of
Filter.Empty -> Filter.Empty ->
pair = Meta.atom_with_hole (h -> Filter.Item n h) pair = Meta.atom_with_hole (h -> Filter.Item n h)
Filter.Head pair.value pair.fill [True, Filter.Head pair.value pair.fill]
Filter.Head _ _ -> if iterate self.first then appendN self self.set_last else self Filter.Head _ _ -> if iterate self.first then appendN self self.set_last else
[False, Nothing]
type Primes type Primes
Alg generator filter Alg generator filter
Primes.next self = case self of Primes.next self = case self of
Primes.Alg g f -> Primes.Alg g f ->
filter = f.acceptAndAdd g.n found_new = f.acceptAndAdd g.n
nf = if filter.is_nothing then f else filter if found_new.first then Primes.Alg g.next found_new.last else
new_primes = Primes.Alg g.next nf new_primes = Primes.Alg g.next f
if filter.is_nothing || Meta.is_same_object filter f then @Tail_Call new_primes.next else new_primes @Tail_Call new_primes.next
Primes.last_prime self = case self of Primes.last_prime self = case self of
Primes.Alg g _ -> g.n - 1 Primes.Alg g _ -> g.n - 1

View File

@ -173,6 +173,14 @@ spec =
_ -> False _ -> False
num_double_match.should_be_true num_double_match.should_be_true
Test.specify "should make Python number values equal to Enso ints" <|
py_10 = make_int
py_10 . should_equal 10
Test.specify "should make Python number values equal to Enso doubles" <|
py_d = make_double
py_d . should_equal 10.5
Test.specify "should make Python None values equal to Nothing" <| Test.specify "should make Python None values equal to Nothing" <|
py_null = make_null py_null = make_null
py_null . should_equal Nothing py_null . should_equal Nothing