diff --git a/distribution/std-lib/Base/src/Data/Vector.enso b/distribution/std-lib/Base/src/Data/Vector.enso index 14430c5e9ee5..7a955aafb2bd 100644 --- a/distribution/std-lib/Base/src/Data/Vector.enso +++ b/distribution/std-lib/Base/src/Data/Vector.enso @@ -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). @@ -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. @@ -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. @@ -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`. diff --git a/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala b/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala index 668fd3405d98..89f6b148e6cd 100644 --- a/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala +++ b/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala @@ -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) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/Language.java b/engine/runtime/src/main/java/org/enso/interpreter/Language.java index f561e848e56f..d779a43db332 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/Language.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/Language.java @@ -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; @@ -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({ @@ -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(); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbContext.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbContext.java new file mode 100644 index 000000000000..46f10fc988e7 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbContext.java @@ -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; + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbLanguage.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbLanguage.java new file mode 100644 index 000000000000..46e351f6a95d --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbLanguage.java @@ -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. + * + *

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. + * + *

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. + * + *

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 { + 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; + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbParser.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbParser.java new file mode 100644 index 000000000000..61c3fe764885 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbParser.java @@ -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(); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java new file mode 100644 index 000000000000..179bc5322520 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java @@ -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); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignEvalNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignEvalNode.java new file mode 100644 index 000000000000..215221354515 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignEvalNode.java @@ -0,0 +1,92 @@ +package org.enso.interpreter.epb.node; + +import com.oracle.truffle.api.*; +import com.oracle.truffle.api.TruffleLanguage.ContextReference; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.FrameDescriptor; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.RootNode; +import com.oracle.truffle.api.source.Source; +import org.enso.interpreter.epb.EpbContext; +import org.enso.interpreter.epb.EpbLanguage; +import org.enso.interpreter.epb.EpbParser; +import org.enso.interpreter.epb.runtime.GuardedTruffleContext; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public abstract class ForeignEvalNode extends RootNode { + private final EpbParser.Result code; + private @Child ForeignFunctionCallNode foreign; + private @Child ContextRewrapNode rewrapNode = ContextRewrapNodeGen.create(); + private final String[] argNames; + + ForeignEvalNode(EpbLanguage language, EpbParser.Result code, List arguments) { + super(language, new FrameDescriptor()); + this.code = code; + argNames = arguments.toArray(new String[0]); + } + + /** + * 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 arguments) { + return ForeignEvalNodeGen.create(language, code, arguments); + } + + @Specialization + Object doExecute( + VirtualFrame frame, + @CachedContext(EpbLanguage.class) ContextReference contextRef) { + ensureParsed(contextRef); + return foreign.execute(frame.getArguments()); + } + + private void ensureParsed(ContextReference ctxRef) { + if (foreign == null) { + getLock().lock(); + try { + if (foreign == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + if (code.getLanguage() == EpbParser.ForeignLanguage.JS) { + parseJs(ctxRef); + } else { + throw new IllegalStateException("Unsupported language resulted from EPB parsing"); + } + } + } finally { + getLock().unlock(); + } + } + } + + private void parseJs(ContextReference ctxRef) { + EpbContext context = ctxRef.get(); + GuardedTruffleContext outer = context.getCurrentContext(); + GuardedTruffleContext inner = context.getInnerContext(); + Object p = inner.enter(); + 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(); + CallTarget ct = ctxRef.get().getEnv().parseInternal(source); + Object fn = rewrapNode.execute(ct.call(), inner, outer); + foreign = insert(JsForeignNodeGen.create(argNames.length, fn)); + } finally { + inner.leave(p); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignFunctionCallNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignFunctionCallNode.java new file mode 100644 index 000000000000..207c7683b300 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignFunctionCallNode.java @@ -0,0 +1,14 @@ +package org.enso.interpreter.epb.node; + +import com.oracle.truffle.api.nodes.Node; + +/** An interface for nodes responsible for calling into foreign languages. */ +public abstract class ForeignFunctionCallNode extends Node { + /** + * Executes the foreign call. + * + * @param arguments the arguments to pass to the foreign function + * @return the result of executing the foreign function + */ + public abstract Object execute(Object[] arguments); +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/JsForeignNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/JsForeignNode.java new file mode 100644 index 000000000000..42db304e02e1 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/JsForeignNode.java @@ -0,0 +1,44 @@ +package org.enso.interpreter.epb.node; + +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.*; +import com.oracle.truffle.api.library.CachedLibrary; +import org.enso.interpreter.runtime.data.Array; + +/** A node responsible for performing foreign JS calls. */ +public abstract class JsForeignNode extends ForeignFunctionCallNode { + + final Object jsFun; + private final int argsCount; + + JsForeignNode(int argsCount, Object jsFun) { + this.argsCount = argsCount; + this.jsFun = jsFun; + } + + /** + * 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(int argumentsCount, Object jsFunction) { + return JsForeignNodeGen.create(argumentsCount, jsFunction); + } + + @Specialization + Object doExecute(Object[] arguments, @CachedLibrary("jsFun") InteropLibrary interopLibrary) { + Object[] positionalArgs = new Object[argsCount - 1]; + if (argsCount - 1 >= 0) System.arraycopy(arguments, 1, positionalArgs, 0, argsCount - 1); + try { + return interopLibrary.invokeMember(jsFun, "apply", arguments[0], new Array(positionalArgs)); + } catch (UnsupportedMessageException + | UnknownIdentifierException + | ArityException + | UnsupportedTypeException e) { + throw new IllegalStateException("Invalid JS function resulted from parsing."); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/GuardedTruffleContext.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/GuardedTruffleContext.java new file mode 100644 index 000000000000..3237f0ce6c3e --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/GuardedTruffleContext.java @@ -0,0 +1,59 @@ +package org.enso.interpreter.epb.runtime; + +import com.oracle.truffle.api.TruffleContext; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** 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; + } + } + + /** + * 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. + * + *

Any code following a call to this method should be executed in a try/finally block, with + * {@link #leave(Object)} being called in the finally block. It is crucial that this context is + * always left as soon as guest code execution finishes. + * + *

The token returned from this method may not be stored or used for any purpose other than + * leaving the context. + * + * @return a context restoration token that must be passed to {@link #leave(Object)} + */ + public Object enter() { + if (lock != null) { + lock.lock(); + } + return context.enter(); + } + + /** + * Leaves the context and unlocks it if this wrapper is GILed. + * + * @param prev the token obtained from the call to {@link #enter()} + */ + public void leave(Object prev) { + context.leave(prev); + if (lock != null) { + lock.unlock(); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java new file mode 100644 index 000000000000..659a30304401 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java @@ -0,0 +1,248 @@ +package org.enso.interpreter.epb.runtime; + +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.interop.*; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; +import org.enso.interpreter.epb.node.ContextRewrapNode; + +/** + * Wraps a polyglot value that is to be shared between Truffle contexts. See {@link + * org.enso.interpreter.epb.EpbLanguage} for the explanation of this system. + */ +@ExportLibrary(InteropLibrary.class) +public class PolyglotProxy implements TruffleObject { + final Object delegate; + private final GuardedTruffleContext origin; + private final GuardedTruffleContext target; + + /** + * Wraps a value for inter-context use. + * + * @param delegate the wrapped value + * @param origin the context in which {@code delegate} is a valid value + * @param target the context in which {@code delegate} will be accessed through this wrapper + */ + public PolyglotProxy( + Object delegate, GuardedTruffleContext origin, GuardedTruffleContext target) { + this.delegate = delegate; + this.origin = origin; + this.target = target; + } + + /** @return the wrapped value */ + public Object getDelegate() { + return delegate; + } + + /** @return the context in which the wrapped value originates */ + public GuardedTruffleContext getOrigin() { + return origin; + } + + /** @return the context in which this wrapper is supposed to be used */ + public GuardedTruffleContext getTarget() { + return target; + } + + @ExportMessage + public boolean isNull(@CachedLibrary("this.delegate") InteropLibrary nulls) { + Object p = origin.enter(); + try { + return nulls.isNull(this.delegate); + } finally { + origin.leave(p); + } + } + + @ExportMessage + public boolean hasMembers(@CachedLibrary("this.delegate") InteropLibrary members) { + Object p = origin.enter(); + try { + return members.hasMembers(this.delegate); + } finally { + origin.leave(p); + } + } + + @ExportMessage + public Object getMembers( + boolean includeInternal, + @CachedLibrary("this.delegate") InteropLibrary members, + @Cached @Cached.Exclusive ContextRewrapNode contextRewrapNode) + throws UnsupportedMessageException { + Object p = origin.enter(); + try { + return contextRewrapNode.execute( + members.getMembers(this.delegate, includeInternal), origin, target); + } finally { + origin.leave(p); + } + } + + @ExportMessage + public boolean isMemberInvocable( + String member, @CachedLibrary("this.delegate") InteropLibrary members) { + Object p = origin.enter(); + try { + return members.isMemberInvocable(this.delegate, member); + } finally { + origin.leave(p); + } + } + + @ExportMessage + public Object invokeMember( + String member, + Object[] arguments, + @CachedLibrary("this.delegate") InteropLibrary members, + @Cached @Cached.Exclusive ContextRewrapNode contextRewrapNode) + throws ArityException, UnknownIdentifierException, UnsupportedMessageException, + UnsupportedTypeException { + Object[] wrappedArgs = new Object[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + wrappedArgs[i] = contextRewrapNode.execute(arguments[i], target, origin); + } + Object p = origin.enter(); + try { + return contextRewrapNode.execute( + members.invokeMember(this.delegate, member, wrappedArgs), origin, target); + } finally { + origin.leave(p); + } + } + + @ExportMessage + public boolean isMemberReadable( + String member, @CachedLibrary("this.delegate") InteropLibrary members) { + Object p = origin.enter(); + try { + return members.isMemberReadable(this.delegate, member); + } finally { + origin.leave(p); + } + } + + @ExportMessage + public Object readMember( + String member, + @CachedLibrary("this.delegate") InteropLibrary members, + @Cached @Cached.Exclusive ContextRewrapNode contextRewrapNode) + throws UnknownIdentifierException, UnsupportedMessageException { + Object p = origin.enter(); + try { + return contextRewrapNode.execute(members.readMember(this.delegate, member), origin, target); + } finally { + origin.leave(p); + } + } + + @ExportMessage + public boolean isExecutable(@CachedLibrary("this.delegate") InteropLibrary functions) { + Object p = origin.enter(); + try { + return functions.isExecutable(this.delegate); + } finally { + origin.leave(p); + } + } + + @ExportMessage + public Object execute( + Object[] arguments, + @CachedLibrary("this.delegate") InteropLibrary functions, + @Cached @Cached.Exclusive ContextRewrapNode contextRewrapNode) + throws UnsupportedMessageException, ArityException, UnsupportedTypeException { + Object[] wrappedArgs = new Object[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + wrappedArgs[i] = contextRewrapNode.execute(arguments[i], target, origin); + } + Object p = origin.enter(); + try { + return contextRewrapNode.execute( + functions.execute(this.delegate, wrappedArgs), origin, target); + } finally { + origin.leave(p); + } + } + + @ExportMessage + public boolean hasArrayElements(@CachedLibrary("this.delegate") InteropLibrary arrays) { + Object p = origin.enter(); + try { + return arrays.hasArrayElements(this.delegate); + } finally { + origin.leave(p); + } + } + + @ExportMessage + public long getArraySize(@CachedLibrary("this.delegate") InteropLibrary arrays) + throws UnsupportedMessageException { + Object p = origin.enter(); + try { + return arrays.getArraySize(this.delegate); + } finally { + origin.leave(p); + } + } + + @ExportMessage + public boolean isArrayElementReadable( + long idx, @CachedLibrary("this.delegate") InteropLibrary arrays) { + Object p = origin.enter(); + try { + return arrays.isArrayElementReadable(this.delegate, idx); + } finally { + origin.leave(p); + } + } + + @ExportMessage + public Object readArrayElement( + long index, + @CachedLibrary("this.delegate") InteropLibrary arrays, + @Cached @Cached.Exclusive ContextRewrapNode contextRewrapNode) + throws InvalidArrayIndexException, UnsupportedMessageException { + Object p = origin.enter(); + try { + return contextRewrapNode.execute( + arrays.readArrayElement(this.delegate, index), origin, target); + } finally { + origin.leave(p); + } + } + + @ExportMessage + public boolean isString(@CachedLibrary("this.delegate") InteropLibrary strings) { + Object p = origin.enter(); + try { + return strings.isString(this.delegate); + } finally { + origin.leave(p); + } + } + + @ExportMessage + public String asString(@CachedLibrary("this.delegate") InteropLibrary strings) + throws UnsupportedMessageException { + Object p = origin.enter(); + try { + return strings.asString(this.delegate); + } finally { + origin.leave(p); + } + } + + @ExportMessage + public Object toDisplayString( + boolean allowSideEffects, @CachedLibrary("this.delegate") InteropLibrary displays) { + Object p = origin.enter(); + try { + return displays.toDisplayString(this.delegate, allowSideEffects); + } finally { + origin.leave(p); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeMethodNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeMethodNode.java index c831b8f6f1ea..23c109c5e27a 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeMethodNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeMethodNode.java @@ -135,7 +135,8 @@ Stateful doPanicSentinel( guards = { "!methods.hasFunctionalDispatch(_this)", "!methods.hasSpecialDispatch(_this)", - "polyglotCallType != NOT_SUPPORTED" + "polyglotCallType != NOT_SUPPORTED", + "polyglotCallType != CONVERT_TO_TEXT" }) Stateful doPolyglot( MaterializedFrame frame, @@ -166,6 +167,49 @@ Stateful doPolyglot( state, hostMethodCallNode.execute(polyglotCallType, symbol.getName(), _this, args)); } + @Specialization( + guards = { + "!methods.hasFunctionalDispatch(_this)", + "!methods.hasSpecialDispatch(_this)", + "getPolyglotCallType(_this, symbol.getName(), interop) == CONVERT_TO_TEXT" + }) + Stateful doConvertText( + MaterializedFrame frame, + Object state, + UnresolvedSymbol symbol, + Object _this, + Object[] arguments, + CallArgumentInfo[] schema, + InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, + InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, + BaseNode.TailStatus isTail, + @CachedLibrary(limit = "10") MethodDispatchLibrary methods, + @CachedLibrary(limit = "1") MethodDispatchLibrary textDispatch, + @CachedLibrary(limit = "10") InteropLibrary interop, + @CachedContext(Language.class) TruffleLanguage.ContextReference ctx, + @Cached IndirectInvokeFunctionNode invokeFunctionNode) { + try { + String str = interop.asString(_this); + Text txt = Text.create(str); + Function function = textDispatch.getFunctionalDispatch(txt, symbol); + arguments[0] = txt; + return invokeFunctionNode.execute( + function, + frame, + state, + arguments, + schema, + defaultsExecutionMode, + argumentsExecutionMode, + isTail); + } catch (UnsupportedMessageException e) { + throw new IllegalStateException("Impossible, _this is guaranteed to be a string."); + } catch (MethodDispatchLibrary.NoSuchMethodException e) { + throw new PanicException( + ctx.get().getBuiltins().error().makeNoSuchMethodError(_this, symbol), this); + } + } + @ExplodeLoop @Specialization( guards = { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java index 9b822a523094..617b045226ea 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java @@ -5,6 +5,7 @@ import com.oracle.truffle.api.dsl.*; import com.oracle.truffle.api.frame.VirtualFrame; 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.ExplodeLoop; import com.oracle.truffle.api.nodes.Node; @@ -20,6 +21,7 @@ import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.error.DataflowError; import org.enso.interpreter.runtime.error.PanicSentinel; import org.enso.interpreter.runtime.error.PanicException; @@ -114,7 +116,8 @@ Stateful doPanicSentinel( guards = { "!methods.hasFunctionalDispatch(_this)", "!methods.hasSpecialDispatch(_this)", - "polyglotCallType != NOT_SUPPORTED" + "polyglotCallType != NOT_SUPPORTED", + "polyglotCallType != CONVERT_TO_TEXT" }) Stateful doPolyglot( VirtualFrame frame, @@ -139,7 +142,36 @@ Stateful doPolyglot( state, hostMethodCallNode.execute(polyglotCallType, symbol.getName(), _this, args)); } - @ExplodeLoop + @Specialization( + guards = { + "!methods.hasFunctionalDispatch(_this)", + "!methods.hasSpecialDispatch(_this)", + "getPolyglotCallType(_this, symbol.getName(), interop) == CONVERT_TO_TEXT" + }) + Stateful doConvertText( + VirtualFrame frame, + Object state, + UnresolvedSymbol symbol, + Object _this, + Object[] arguments, + @CachedLibrary(limit = "10") MethodDispatchLibrary methods, + @CachedLibrary(limit = "1") MethodDispatchLibrary textDispatch, + @CachedLibrary(limit = "10") InteropLibrary interop, + @CachedContext(Language.class) TruffleLanguage.ContextReference ctx) { + try { + String str = interop.asString(_this); + Text txt = Text.create(str); + Function function = textDispatch.getFunctionalDispatch(txt, symbol); + arguments[0] = txt; + return invokeFunctionNode.execute(function, frame, state, arguments); + } catch (UnsupportedMessageException e) { + throw new IllegalStateException("Impossible, _this is guaranteed to be a string."); + } catch (MethodDispatchLibrary.NoSuchMethodException e) { + throw new PanicException( + ctx.get().getBuiltins().error().makeNoSuchMethodError(_this, symbol), this); + } + } + @Specialization( guards = { "!methods.hasFunctionalDispatch(_this)", diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java index 6b392f665dba..00d7944ea030 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java @@ -18,11 +18,33 @@ public abstract class HostMethodCallNode extends Node { /** Represents a mode of calling a method on a polyglot value. */ public enum PolyglotCallType { + /** + * The method call should be handled through {@link InteropLibrary#invokeMember(Object, String, + * Object...)}. + */ CALL_METHOD, + /** + * The method call should be handled through {@link InteropLibrary#readMember(Object, String)}. + */ GET_MEMBER, + /** + * The method call should be handled through {@link InteropLibrary#instantiate(Object, + * Object...)}. + */ INSTANTIATE, + /** The method call should be handled through {@link InteropLibrary#getArraySize(Object)}. */ GET_ARRAY_LENGTH, + /** + * The method call should be handled through {@link InteropLibrary#readArrayElement(Object, + * long)}. + */ READ_ARRAY_ELEMENT, + /** + * The method call should be handled by converting {@code _this} to a {@link + * org.enso.interpreter.runtime.data.text.Text} and dispatching natively. + */ + CONVERT_TO_TEXT, + /** The method call should be handled by dispatching through the {@code Any} type. */ NOT_SUPPORTED } @@ -53,6 +75,8 @@ public static PolyglotCallType getPolyglotCallType( return PolyglotCallType.GET_ARRAY_LENGTH; } else if (library.hasArrayElements(_this) && methodName.equals(ARRAY_READ_NAME)) { return PolyglotCallType.READ_ARRAY_ELEMENT; + } else if (library.isString(_this)) { + return PolyglotCallType.CONVERT_TO_TEXT; } else { return PolyglotCallType.NOT_SUPPORTED; } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TextBranchNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TextBranchNode.java index d229dcde1261..2b2cbbe2ee2e 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TextBranchNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TextBranchNode.java @@ -1,14 +1,16 @@ package org.enso.interpreter.node.controlflow.caseexpr; import com.oracle.truffle.api.RootCallTarget; +import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.profiles.ConditionProfile; import org.enso.interpreter.runtime.callable.atom.Atom; import org.enso.interpreter.runtime.callable.atom.AtomConstructor; -import org.enso.interpreter.runtime.data.text.Text; @NodeInfo(shortName = "TextMatch", description = "Allows matching on the Text type.") public abstract class TextBranchNode extends BranchNode { @@ -38,8 +40,12 @@ void doConstructor(VirtualFrame frame, Object state, Atom target) { } } - @Specialization - void doLiteral(VirtualFrame frame, Object state, Text target) { + @Specialization(guards = "strings.isString(target)") + void doLiteral( + VirtualFrame frame, + Object state, + Object target, + @CachedLibrary(limit = "10") InteropLibrary strings) { accept(frame, state, new Object[0]); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/debug/DebugEvalNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/debug/DebugEvalNode.java index 38ef7a9e6fcc..abc1af76c44a 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/debug/DebugEvalNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/debug/DebugEvalNode.java @@ -4,6 +4,7 @@ import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.dsl.MonadicState; import org.enso.interpreter.node.BaseNode; +import org.enso.interpreter.node.expression.builtin.text.util.ExpectTextNode; import org.enso.interpreter.node.expression.debug.EvalNode; import org.enso.interpreter.runtime.callable.CallerInfo; import org.enso.interpreter.runtime.data.text.Text; @@ -16,13 +17,14 @@ description = "Evaluates an expression passed as a Text argument, in the caller frame.") public class DebugEvalNode extends Node { private @Child EvalNode evalNode = EvalNode.build(); + private @Child ExpectTextNode expectTextNode = ExpectTextNode.build(); DebugEvalNode() { evalNode.setTailStatus(BaseNode.TailStatus.TAIL_DIRECT); } Stateful execute( - CallerInfo callerInfo, @MonadicState Object state, Object _this, Text expression) { - return evalNode.execute(callerInfo, state, expression); + CallerInfo callerInfo, @MonadicState Object state, Object _this, Object expression) { + return evalNode.execute(callerInfo, state, expectTextNode.execute(expression)); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/EvalNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/EvalNode.java deleted file mode 100644 index dfbad794ea19..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/EvalNode.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.interop.generic; - -import com.oracle.truffle.api.CallTarget; -import com.oracle.truffle.api.dsl.Cached; -import com.oracle.truffle.api.dsl.CachedContext; -import com.oracle.truffle.api.dsl.ReportPolymorphism; -import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.nodes.DirectCallNode; -import com.oracle.truffle.api.nodes.IndirectCallNode; -import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.source.Source; -import org.enso.interpreter.Language; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode; -import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; -import org.enso.interpreter.runtime.Context; -import org.enso.interpreter.runtime.data.text.Text; - -@BuiltinMethod( - type = "Polyglot", - name = "eval", - description = "Evaluates a foreign language string.") -@ReportPolymorphism -public abstract class EvalNode extends Node { - static final int LIMIT = 10; - - static EvalNode build() { - return EvalNodeGen.create(); - } - - abstract Object execute(Object _this, Text language, Text code); - - @Specialization( - guards = {"cachedLanguage == language", "cachedCode == code"}, - limit = "LIMIT") - Object doCached( - Object _this, - Text language, - Text code, - @CachedContext(Language.class) Context context, - @Cached("language") Text cachedLanguage, - @Cached("code") Text cachedCode, - @Cached("build()") ToJavaStringNode toJavaStringNode, - @Cached("parse(context, cachedLanguage, cachedCode, toJavaStringNode)") CallTarget callTarget, - @Cached("create(callTarget)") DirectCallNode callNode, - @Cached("build()") HostValueToEnsoNode hostValueToEnsoNode) { - return hostValueToEnsoNode.execute(callNode.call()); - } - - @Specialization(replaces = "doCached") - Object doUncached( - Object _this, - Text language, - Text code, - @CachedContext(Language.class) Context context, - @Cached IndirectCallNode callNode, - @Cached("build()") ToJavaStringNode toJavaStringNode, - @Cached("build()") HostValueToEnsoNode hostValueToEnsoNode) { - CallTarget ct = parse(context, language, code, toJavaStringNode); - return hostValueToEnsoNode.execute(callNode.call(ct)); - } - - CallTarget parse(Context context, Text language, Text code, ToJavaStringNode toJavaStringNode) { - String languageStr = toJavaStringNode.execute(language); - String codeStr = toJavaStringNode.execute(code); - - Source source = Source.newBuilder(languageStr, codeStr, "").build(); - return context.getEnvironment().parsePublic(source); - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetArraySizeNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetArraySizeNode.java new file mode 100644 index 000000000000..032cd3b39742 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetArraySizeNode.java @@ -0,0 +1,31 @@ +package org.enso.interpreter.node.expression.builtin.interop.generic; + +import com.oracle.truffle.api.interop.*; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.profiles.BranchProfile; +import org.enso.interpreter.Constants; +import org.enso.interpreter.Language; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.builtin.Builtins; +import org.enso.interpreter.runtime.error.PanicException; + +@BuiltinMethod( + type = "Polyglot", + name = "get_array_size", + description = "Returns the size of a polyglot array.") +public class GetArraySizeNode extends Node { + private @Child InteropLibrary library = + InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH); + private final BranchProfile err = BranchProfile.create(); + + long execute(Object _this, Object array) { + try { + return library.getArraySize(array); + } catch (UnsupportedMessageException e) { + err.enter(); + Builtins builtins = lookupContextReference(Language.class).get().getBuiltins(); + throw new PanicException( + builtins.error().makeTypeError(builtins.mutable().array(), array), this); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetMemberNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetMemberNode.java index 31e04ea81aad..1bd964c5d9e3 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetMemberNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetMemberNode.java @@ -7,6 +7,8 @@ import com.oracle.truffle.api.profiles.BranchProfile; import org.enso.interpreter.Constants; import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode; +import org.enso.interpreter.node.expression.builtin.text.util.ExpectTextNode; import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.error.PanicException; @@ -18,12 +20,12 @@ public class GetMemberNode extends Node { private @Child InteropLibrary library = InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH); - private @Child ToJavaStringNode toJavaStringNode = ToJavaStringNode.build(); + private @Child ExpectStringNode expectStringNode = ExpectStringNode.build(); private final BranchProfile err = BranchProfile.create(); - Object execute(Object _this, Object object, Text member_name) { + Object execute(Object _this, Object object, Object member_name) { try { - return library.readMember(object, toJavaStringNode.execute(member_name)); + return library.readMember(object, expectStringNode.execute(member_name)); } catch (UnsupportedMessageException | UnknownIdentifierException e) { err.enter(); throw new PanicException(e.getMessage(), this); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/InvokeNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/InvokeNode.java index 5b37f891ebc7..4909e60ccb1e 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/InvokeNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/InvokeNode.java @@ -5,6 +5,8 @@ import com.oracle.truffle.api.profiles.BranchProfile; import org.enso.interpreter.Constants; import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode; +import org.enso.interpreter.node.expression.builtin.text.util.ExpectTextNode; import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; import org.enso.interpreter.runtime.data.Array; import org.enso.interpreter.runtime.data.text.Text; @@ -17,12 +19,12 @@ public class InvokeNode extends Node { private @Child InteropLibrary library = InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH); - private @Child ToJavaStringNode toJavaStringNode = ToJavaStringNode.build(); + private @Child ExpectStringNode expectStringNode = ExpectStringNode.build(); private final BranchProfile err = BranchProfile.create(); - Object execute(Object _this, Object target, Text name, Array arguments) { + Object execute(Object _this, Object target, Object name, Array arguments) { try { - return library.invokeMember(target, toJavaStringNode.execute(name), arguments.getItems()); + return library.invokeMember(target, expectStringNode.execute(name), arguments.getItems()); } catch (UnsupportedMessageException | ArityException | UnsupportedTypeException diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/java/AddToClassPathNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/java/AddToClassPathNode.java index 33b8cd64c556..d8e298341aca 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/java/AddToClassPathNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/java/AddToClassPathNode.java @@ -6,9 +6,8 @@ import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.Language; import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; +import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode; import org.enso.interpreter.runtime.Context; -import org.enso.interpreter.runtime.data.text.Text; import java.io.File; @@ -25,14 +24,14 @@ static AddToClassPathNode build() { @Specialization Object doExecute( Object _this, - Text path, + Object path, @CachedContext(Language.class) Context context, - @Cached("build()") ToJavaStringNode toJavaStringNode) { + @Cached ExpectStringNode expectStringNode) { context .getEnvironment() - .addToHostClassPath(context.getTruffleFile(new File(toJavaStringNode.execute(path)))); + .addToHostClassPath(context.getTruffleFile(new File(expectStringNode.execute(path)))); return context.getBuiltins().nothing(); } - abstract Object execute(Object _this, Text path); + abstract Object execute(Object _this, Object path); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/java/LookupClassNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/java/LookupClassNode.java index df9a9679d7e9..38fa7eddfed6 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/java/LookupClassNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/java/LookupClassNode.java @@ -6,9 +6,8 @@ import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.Language; import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; +import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode; import org.enso.interpreter.runtime.Context; -import org.enso.interpreter.runtime.data.text.Text; @BuiltinMethod(type = "Java", name = "lookup_class", description = "Looks up a Java symbol.") public abstract class LookupClassNode extends Node { @@ -19,11 +18,11 @@ static LookupClassNode build() { @Specialization Object doExecute( Object _this, - Text name, + Object name, @CachedContext(Language.class) Context ctx, - @Cached("build()") ToJavaStringNode toJavaStringNode) { - return ctx.getEnvironment().lookupHostSymbol(toJavaStringNode.execute(name)); + @Cached("build()") ExpectStringNode expectStringNode) { + return ctx.getEnvironment().lookupHostSymbol(expectStringNode.execute(name)); } - abstract Object execute(Object _this, Text name); + abstract Object execute(Object _this, Object name); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/GetFileNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/GetFileNode.java index aff304d66024..f550c4d983f0 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/GetFileNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/GetFileNode.java @@ -1,23 +1,17 @@ package org.enso.interpreter.node.expression.builtin.io; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.TruffleFile; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.CachedContext; 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.nodes.Node; import org.enso.interpreter.Language; import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; +import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.data.EnsoFile; import org.enso.interpreter.runtime.data.text.Text; -import java.io.IOException; -import java.util.stream.Collectors; - @BuiltinMethod( type = "Prim_Io", name = "get_file", @@ -28,15 +22,15 @@ static GetFileNode build() { return GetFileNodeGen.create(); } - abstract Object execute(Object _this, Text path); + abstract Object execute(Object _this, Object path); @Specialization Object doGetFile( Object _this, - Text path, + Object path, @CachedContext(Language.class) Context ctx, - @Cached("build()") ToJavaStringNode toJavaStringNode) { - String pathStr = toJavaStringNode.execute(path); + @Cached("build()") ExpectStringNode expectStringNode) { + String pathStr = expectStringNode.execute(path); TruffleFile file = ctx.getEnvironment().getPublicTruffleFile(pathStr); EnsoFile ensoFile = new EnsoFile(file); return ctx.getEnvironment().asGuestValue(ensoFile); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintErrNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintErrNode.java index f1fa9175e9c4..e1a256796d63 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintErrNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintErrNode.java @@ -5,6 +5,9 @@ import com.oracle.truffle.api.dsl.CachedContext; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; +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 java.io.PrintStream; import org.enso.interpreter.Language; @@ -12,6 +15,7 @@ import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.dsl.MonadicState; import org.enso.interpreter.node.callable.InvokeCallableNode; +import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode; import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; @@ -32,30 +36,35 @@ static PrintErrNode build() { abstract Stateful execute( VirtualFrame frame, @MonadicState Object state, Object _this, @AcceptsError Object message); - @Specialization + @Specialization(guards = "strings.isString(message)") Stateful doPrintText( VirtualFrame frame, Object state, Object self, - Text message, + Object message, @CachedContext(Language.class) Context ctx, - @Cached("build()") ToJavaStringNode toJavaStringNode) { - print(ctx.getErr(), toJavaStringNode.execute(message)); + @CachedLibrary(limit = "10") InteropLibrary strings) { + try { + print(ctx.getErr(), strings.asString(message)); + } catch (UnsupportedMessageException e) { + throw new IllegalStateException("Impossible. self is guaranteed to be a string"); + } return new Stateful(state, ctx.getUnit().newInstance()); } - @Specialization(guards = "!isText(message)") + @Specialization(guards = "!strings.isString(message)") Stateful doPrint( VirtualFrame frame, Object state, Object self, Object message, @CachedContext(Language.class) Context ctx, + @CachedLibrary(limit = "10") InteropLibrary strings, @Cached("buildSymbol(ctx)") UnresolvedSymbol symbol, @Cached("buildInvokeCallableNode()") InvokeCallableNode invokeCallableNode, - @Cached("build()") ToJavaStringNode toJavaStringNode) { + @Cached ExpectStringNode expectStringNode) { Stateful str = invokeCallableNode.execute(symbol, frame, state, new Object[] {message}); - print(ctx.getErr(), toJavaStringNode.execute((Text) str.getValue())); + print(ctx.getErr(), expectStringNode.execute(str.getValue())); return new Stateful(str.getState(), ctx.getUnit().newInstance()); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintlnNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintlnNode.java index b549b4ebc240..268c64f93491 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintlnNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintlnNode.java @@ -5,6 +5,9 @@ import com.oracle.truffle.api.dsl.CachedContext; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; +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 java.io.PrintStream; import org.enso.interpreter.Language; @@ -12,6 +15,7 @@ import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.dsl.MonadicState; import org.enso.interpreter.node.callable.InvokeCallableNode; +import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode; import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; @@ -31,31 +35,35 @@ public abstract class PrintlnNode extends Node { abstract Stateful execute( VirtualFrame frame, @MonadicState Object state, Object _this, @AcceptsError Object message); - @Specialization + @Specialization(guards = "strings.isString(message)") Stateful doPrintText( VirtualFrame frame, Object state, - Object _this, - Text message, + Object self, + Object message, @CachedContext(Language.class) Context ctx, - @Cached("build()") ToJavaStringNode toJavaStringNode) { - print(ctx.getOut(), toJavaStringNode.execute(message)); + @CachedLibrary(limit = "10") InteropLibrary strings) { + try { + print(ctx.getOut(), strings.asString(message)); + } catch (UnsupportedMessageException e) { + throw new IllegalStateException("Impossible. self is guaranteed to be a string"); + } return new Stateful(state, ctx.getUnit().newInstance()); } - @Specialization(guards = "!isText(message)") + @Specialization(guards = "!strings.isString(message)") Stateful doPrint( VirtualFrame frame, Object state, - Object _this, + Object self, Object message, @CachedContext(Language.class) Context ctx, + @CachedLibrary(limit = "10") InteropLibrary strings, @Cached("buildSymbol(ctx)") UnresolvedSymbol symbol, - @Cached("build()") ToJavaStringNode toJavaStringNode, - @Cached("buildInvokeCallableNode()") InvokeCallableNode invokeCallableNode) { + @Cached("buildInvokeCallableNode()") InvokeCallableNode invokeCallableNode, + @Cached ExpectStringNode expectStringNode) { Stateful str = invokeCallableNode.execute(symbol, frame, state, new Object[] {message}); - String strr = toJavaStringNode.execute((Text) str.getValue()); - print(ctx.getOut(), strr); + print(ctx.getOut(), expectStringNode.execute(str.getValue())); return new Stateful(str.getState(), ctx.getUnit().newInstance()); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/CreateUnresolvedSymbolNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/CreateUnresolvedSymbolNode.java index 2d5fb6fc8f39..b7314af27586 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/CreateUnresolvedSymbolNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/CreateUnresolvedSymbolNode.java @@ -2,6 +2,7 @@ import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode; import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.data.text.Text; @@ -12,9 +13,9 @@ name = "create_unresolved_symbol", description = "Creates a new unresolved symbol node") public class CreateUnresolvedSymbolNode extends Node { - private @Child ToJavaStringNode toJavaStringNode = ToJavaStringNode.build(); + private @Child ExpectStringNode expectStringNode = ExpectStringNode.build(); - UnresolvedSymbol execute(Object _this, Text name, ModuleScope scope) { - return UnresolvedSymbol.build(toJavaStringNode.execute(name), scope); + UnresolvedSymbol execute(Object _this, Object name, ModuleScope scope) { + return UnresolvedSymbol.build(expectStringNode.execute(name), scope); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/system/CreateProcessNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/system/CreateProcessNode.java index 714fc70025c8..9e6a47c5f20d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/system/CreateProcessNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/system/CreateProcessNode.java @@ -8,6 +8,7 @@ import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.Language; import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode; import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.data.Array; @@ -28,9 +29,9 @@ static CreateProcessNode build() { abstract Object execute( Object _this, - Text command, + Object command, Array arguments, - Text input, + Object input, boolean redirectIn, boolean redirectOut, boolean redirectErr); @@ -39,25 +40,25 @@ abstract Object execute( @CompilerDirectives.TruffleBoundary Object doCreate( Object _this, - Text command, + Object command, Array arguments, - Text input, + Object input, boolean redirectIn, boolean redirectOut, boolean redirectErr, @CachedContext(Language.class) Context ctx, - @Cached("build()") ToJavaStringNode toJavaStringNode) { + @Cached ExpectStringNode expectStringNode) { String[] cmd = new String[arguments.getItems().length + 1]; - cmd[0] = toJavaStringNode.execute(command); + cmd[0] = expectStringNode.execute(command); for (int i = 1; i <= arguments.getItems().length; i++) { - cmd[i] = toJavaStringNode.execute((Text) arguments.getItems()[i - 1]); + cmd[i] = expectStringNode.execute(arguments.getItems()[i - 1]); } TruffleProcessBuilder pb = ctx.getEnvironment().newProcessBuilder(cmd); try { Process p = pb.start(); ByteArrayInputStream in = - new ByteArrayInputStream(toJavaStringNode.execute(input).getBytes()); + new ByteArrayInputStream(expectStringNode.execute(input).getBytes()); ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream err = new ByteArrayOutputStream(); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/ConcatNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/ConcatNode.java index 3a144fa83bd6..bd04291a46c1 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/ConcatNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/ConcatNode.java @@ -1,12 +1,28 @@ package org.enso.interpreter.node.expression.builtin.text; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.expression.builtin.text.util.ExpectTextNode; import org.enso.interpreter.runtime.data.text.Text; @BuiltinMethod(type = "Text", name = "+", description = "Text concatenation.") -public class ConcatNode extends Node { - Text execute(Text _this, Text that) { - return Text.create(_this, that); +public abstract class ConcatNode extends Node { + abstract Text execute(Object _this, Object that); + + static ConcatNode build() { + return ConcatNodeGen.create(); + } + + @Specialization + Text doExecute( + Object _this, + Object that, + @Cached ExpectTextNode leftCast, + @Cached ExpectTextNode rightCast) { + Text l = leftCast.execute(_this); + Text r = rightCast.execute(that); + return Text.create(l, r); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/OptimizeNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/OptimizeNode.java index 34f40da18db4..bc88b946fd3b 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/OptimizeNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/OptimizeNode.java @@ -1,6 +1,8 @@ package org.enso.interpreter.node.expression.builtin.text; import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; @@ -10,11 +12,23 @@ type = "Prim_Text_Helper", name = "optimize", description = "Forces flattening of a text value, for testing or purposes.") -public class OptimizeNode extends Node { +public abstract class OptimizeNode extends Node { private @Child ToJavaStringNode toJavaStringNode = ToJavaStringNode.build(); - Text execute(Object _this, Text text) { + static OptimizeNode build() { + return OptimizeNodeGen.create(); + } + + abstract Object execute(Object _this, Object text); + + @Specialization + Text doText(Object _this, Text text) { toJavaStringNode.execute(text); return text; } + + @Fallback + Object doOther(Object _this, Object that) { + return that; + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/ExpectStringNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/ExpectStringNode.java new file mode 100644 index 000000000000..6467630a96ee --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/ExpectStringNode.java @@ -0,0 +1,44 @@ +package org.enso.interpreter.node.expression.builtin.text.util; + +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Fallback; +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.nodes.Node; +import org.enso.interpreter.Language; +import org.enso.interpreter.runtime.builtin.Builtins; +import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.data.text.Text; +import org.enso.interpreter.runtime.error.PanicException; + +public abstract class ExpectStringNode extends Node { + private @Child InteropLibrary library = InteropLibrary.getFactory().createDispatched(10); + + public abstract String execute(Object o); + + public static ExpectStringNode build() { + return ExpectStringNodeGen.create(); + } + + @Specialization + String doText(Text o, @Cached("build()") ToJavaStringNode toJavaStringNode) { + return toJavaStringNode.execute(o); + } + + @Specialization + String doString(String o) { + return o; + } + + @Fallback + String doFallback(Object o) { + try { + return library.asString(o); + } catch (UnsupportedMessageException e) { + Builtins builtins = lookupContextReference(Language.class).get().getBuiltins(); + Atom err = builtins.error().makeTypeError(builtins.text().getText(), o); + throw new PanicException(err, this); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/ExpectTextNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/ExpectTextNode.java new file mode 100644 index 000000000000..8442653c8121 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/ExpectTextNode.java @@ -0,0 +1,45 @@ +package org.enso.interpreter.node.expression.builtin.text.util; + +import com.oracle.truffle.api.dsl.Fallback; +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.nodes.Node; +import org.enso.interpreter.Language; +import org.enso.interpreter.runtime.builtin.Builtins; +import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.data.text.Text; +import org.enso.interpreter.runtime.error.PanicException; + +@ReportPolymorphism +public abstract class ExpectTextNode extends Node { + private @Child InteropLibrary library = InteropLibrary.getFactory().createDispatched(10); + + public abstract Text execute(Object o); + + public static ExpectTextNode build() { + return ExpectTextNodeGen.create(); + } + + @Specialization + Text doText(Text o) { + return o; + } + + @Specialization + Text doString(String o) { + return Text.create(o); + } + + @Fallback + Text doFallback(Object o) { + try { + return Text.create(library.asString(o)); + } catch (UnsupportedMessageException e) { + Builtins builtins = lookupContextReference(Language.class).get().getBuiltins(); + Atom err = builtins.error().makeTypeError(builtins.text().getText(), o); + throw new PanicException(err, this); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/foreign/ForeignMethodCallNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/foreign/ForeignMethodCallNode.java new file mode 100644 index 000000000000..786bd74325b4 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/foreign/ForeignMethodCallNode.java @@ -0,0 +1,39 @@ +package org.enso.interpreter.node.expression.foreign; + +import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.DirectCallNode; +import com.oracle.truffle.api.nodes.ExplodeLoop; +import org.enso.interpreter.node.ExpressionNode; + +/** Performs a call into a given foreign call target. */ +public class ForeignMethodCallNode extends ExpressionNode { + private @Children ExpressionNode[] arguments; + private @Child DirectCallNode callNode; + + ForeignMethodCallNode(ExpressionNode[] arguments, CallTarget foreignCt) { + this.arguments = arguments; + this.callNode = DirectCallNode.create(foreignCt); + } + + /** + * Creates a new instance of this node + * + * @param arguments expressions resulting in the computation of function arguments + * @param foreignCt the foreign call target to call + * @return the result of calling the foreign call target with the executed arguments + */ + public static ForeignMethodCallNode build(ExpressionNode[] arguments, CallTarget foreignCt) { + return new ForeignMethodCallNode(arguments, foreignCt); + } + + @Override + @ExplodeLoop + public Object executeGeneric(VirtualFrame frame) { + Object[] args = new Object[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + args[i] = arguments[i].executeGeneric(frame); + } + return callNode.call(args); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Polyglot.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Polyglot.java index 136d4635e87f..de2ef9775a7d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Polyglot.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Polyglot.java @@ -26,9 +26,9 @@ private void createPolyglot(Language language, ModuleScope scope) { scope.registerMethod(polyglot, "execute", ExecuteMethodGen.makeFunction(language)); scope.registerMethod(polyglot, "invoke", InvokeMethodGen.makeFunction(language)); scope.registerMethod(polyglot, "new", InstantiateMethodGen.makeFunction(language)); - scope.registerMethod(polyglot, "eval", EvalMethodGen.makeFunction(language)); scope.registerMethod(polyglot, "get_member", GetMemberMethodGen.makeFunction(language)); scope.registerMethod(polyglot, "get_members", GetMembersMethodGen.makeFunction(language)); + scope.registerMethod(polyglot, "get_array_size", GetArraySizeMethodGen.makeFunction(language)); } /** @return the atom constructor for polyglot */ diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java index 98b2a0dec841..089c2b10d108 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java @@ -8,6 +8,7 @@ import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; +import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.UnexpectedResultException; import org.enso.interpreter.Language; import org.enso.interpreter.runtime.Context; @@ -121,6 +122,28 @@ public boolean isMemberInvocable(String member) { return members != null && members.containsKey(member); } + @ExportMessage + @ExplodeLoop + public boolean isMemberReadable(String member) { + for (int i = 0; i < constructor.getArity(); i++) { + if (member.equals(constructor.getFields()[i].getName())) { + return true; + } + } + return false; + } + + @ExportMessage + @ExplodeLoop + public Object readMember(String member) throws UnknownIdentifierException { + for (int i = 0; i < constructor.getArity(); i++) { + if (member.equals(constructor.getFields()[i].getName())) { + return fields[i]; + } + } + throw UnknownIdentifierException.create(member); + } + @ExportMessage static class InvokeMember { diff --git a/engine/runtime/src/main/resources/Builtins.enso b/engine/runtime/src/main/resources/Builtins.enso index 37329b84c045..252a01631701 100644 --- a/engine/runtime/src/main/resources/Builtins.enso +++ b/engine/runtime/src/main/resources/Builtins.enso @@ -380,17 +380,12 @@ type Polyglot @Builtin_Type type Polyglot - ## PRIVATE - - Evaluates the provided code in the provided language to produce a - value. + ## Reads the number of elements in a given polyglot array object. Arguments: - - language: The polyglot language in which to evaluate the code. The - available languages depend on what is installed on your platform. - - code: The code in language to evaluate. - eval : Text -> Text -> Any - eval language code = @Builtin_Method "Polyglot.eval" + - array: a polyglot array object, originating in any supported language. + get_array_size : Any -> Integer + get_array_size array = @Builtin_Method "Polyglot.get_array_size" ## Executes a polyglot function object (e.g. a lambda). diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala index c6b81dc00afe..1d0771c5fb8b 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala @@ -9,6 +9,7 @@ import org.enso.compiler.core.IR import org.enso.compiler.core.IR.Name.MethodReference import org.enso.compiler.core.IR._ import org.enso.compiler.exception.UnhandledEntity +import org.enso.interpreter.epb.EpbParser import org.enso.syntax.text.AST import org.enso.syntax.text.Shape.{ SegmentEscape, @@ -144,9 +145,9 @@ object AstToIr { case AstView.TypeDef(typeName, args, body) => val translatedBody = translateTypeBody(body) val containsAtomDefOrInclude = translatedBody.exists { - case _: IR.Module.Scope.Definition.Atom => true - case _: IR.Name.Literal => true - case _ => false + case _: IR.Module.Scope.Definition.Atom => true + case _: IR.Name.Literal => true + case _ => false } val hasArgs = args.nonEmpty @@ -202,6 +203,28 @@ object AstToIr { translateExpression(definition), getIdentifiedLocation(inputAst) ) + case AstView.FunctionSugar( + AST.Ident.Var("foreign"), + header, + body + ) => + translateForeignDefinition(header, body) match { + case Right((name, arguments, body)) => + val typeName = Name.Here(None) + val methodRef = + Name.MethodReference(typeName, name, name.location) + Module.Scope.Definition.Method.Binding( + methodRef, + arguments, + body, + getIdentifiedLocation(inputAst) + ) + case Left(reason) => + IR.Error.Syntax( + inputAst, + IR.Error.Syntax.InvalidForeignDefinition(reason) + ) + } case AstView.FunctionSugar(name, args, body) => val typeName = Name.Here(None) val methodName = buildName(name) @@ -281,8 +304,27 @@ object AstToIr { inputAst match { case AST.Ident.Annotation.any(ann) => IR.Name.Annotation(ann.name, getIdentifiedLocation(ann)) - case AST.Ident.Cons.any(include) => translateIdent(include) - case atom @ AstView.Atom(_, _) => translateModuleSymbol(atom) + case AST.Ident.Cons.any(include) => translateIdent(include) + case atom @ AstView.Atom(_, _) => translateModuleSymbol(atom) + case AstView.FunctionSugar( + AST.Ident.Var("foreign"), + header, + body + ) => + translateForeignDefinition(header, body) match { + case Right((name, arguments, body)) => + IR.Function.Binding( + name, + arguments, + body, + getIdentifiedLocation(inputAst) + ) + case Left(reason) => + IR.Error.Syntax( + inputAst, + IR.Error.Syntax.InvalidForeignDefinition(reason) + ) + } case fs @ AstView.FunctionSugar(_, _, _) => translateExpression(fs) case AST.Comment.any(inputAST) => translateComment(inputAST) case AstView.Binding(AST.App.Section.Right(opr, arg), body) => @@ -305,6 +347,44 @@ object AstToIr { } } + private def translateForeignDefinition(header: List[AST], body: AST): Either[ + String, + (IR.Name, List[IR.DefinitionArgument], IR.Foreign.Definition) + ] = { + header match { + case AST.Ident.Var(lang) :: AST.Ident.Var.any(name) :: args => + body.shape match { + case AST.Literal.Text.Block.Raw(lines, _, _) => + val code = lines + .map(t => + t.text.collect { + case AST.Literal.Text.Segment.Plain(str) => str + case AST.Literal.Text.Segment.RawEsc(code) => code.repr + }.mkString + ) + .mkString("\n") + val methodName = buildName(name) + val arguments = args.map(translateArgumentDefinition(_)) + val language = EpbParser.ForeignLanguage.getBySyntacticTag(lang) + if (language == null) { + Left(s"Language $lang is not a supported polyglot language.") + } else { + val foreign = IR.Foreign.Definition( + language, + code, + getIdentifiedLocation(body) + ) + Right((methodName, arguments, foreign)) + } + case _ => + Left( + "The body of a foreign block must be an uninterpolated string block literal." + ) + } + case _ => Left("The method name is not specified.") + } + } + /** Translates a method reference from [[AST]] into [[IR]]. * * @param inputAst the method reference to translate diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala index 1da7192c8243..038678fbcadc 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala @@ -1,6 +1,9 @@ package org.enso.compiler.codegen +import java.math.BigInteger + import com.oracle.truffle.api.Truffle +import com.oracle.truffle.api.frame.FrameSlot import com.oracle.truffle.api.source.{Source, SourceSection} import org.enso.compiler.core.IR import org.enso.compiler.core.IR.Module.Scope.Import @@ -21,6 +24,7 @@ import org.enso.compiler.pass.resolve.{ Patterns, UppercaseNames } +import org.enso.interpreter.epb.EpbParser import org.enso.interpreter.node.callable.argument.ReadArgumentNode import org.enso.interpreter.node.callable.function.{ BlockNode, @@ -35,6 +39,7 @@ import org.enso.interpreter.node.callable.{ import org.enso.interpreter.node.controlflow.caseexpr._ import org.enso.interpreter.node.expression.atom.QualifiedAccessorNode import org.enso.interpreter.node.expression.constant._ +import org.enso.interpreter.node.expression.foreign.ForeignMethodCallNode import org.enso.interpreter.node.expression.literal.{ BigIntegerLiteralNode, DecimalLiteralNode, @@ -61,10 +66,13 @@ import org.enso.interpreter.runtime.callable.function.{ } import org.enso.interpreter.runtime.data.text.Text import org.enso.interpreter.runtime.error.DuplicateArgumentNameException -import org.enso.interpreter.runtime.scope.{LocalScope, ModuleScope} +import org.enso.interpreter.runtime.scope.{ + FramePointer, + LocalScope, + ModuleScope +} import org.enso.interpreter.{Constants, Language} -import java.math.BigInteger import scala.collection.mutable import scala.collection.mutable.ArrayBuffer @@ -1023,37 +1031,63 @@ class IrToTruffle( val seenArgNames = mutable.Set[String]() // Note [Rewriting Arguments] - for ((unprocessedArg, idx) <- arguments.view.zipWithIndex) { - val arg = argFactory.run(unprocessedArg, idx) - argDefinitions(idx) = arg + val argSlots = + arguments.zipWithIndex.map { case (unprocessedArg, idx) => + val arg = argFactory.run(unprocessedArg, idx) + argDefinitions(idx) = arg - val occInfo = unprocessedArg - .unsafeGetMetadata( - AliasAnalysis, - "No occurrence on an argument definition." - ) - .unsafeAs[AliasAnalysis.Info.Occurrence] + val occInfo = unprocessedArg + .unsafeGetMetadata( + AliasAnalysis, + "No occurrence on an argument definition." + ) + .unsafeAs[AliasAnalysis.Info.Occurrence] - val slot = scope.createVarSlot(occInfo.id) - val readArg = - ReadArgumentNode.build(idx, arg.getDefaultValue.orElse(null)) - val assignArg = AssignmentNode.build(readArg, slot) + val slot = scope.createVarSlot(occInfo.id) + val readArg = + ReadArgumentNode.build(idx, arg.getDefaultValue.orElse(null)) + val assignArg = AssignmentNode.build(readArg, slot) - argExpressions.append(assignArg) + argExpressions.append(assignArg) - val argName = arg.getName + val argName = arg.getName - if (seenArgNames contains argName) { - throw new DuplicateArgumentNameException(argName) - } else seenArgNames.add(argName) - } + if (seenArgNames contains argName) { + throw new DuplicateArgumentNameException(argName) + } else seenArgNames.add(argName) + slot + } - val bodyExpr = this.run(body) + val bodyExpr = body match { + case IR.Foreign.Definition(lang, code, _, _, _) => + buildForeignBody( + lang, + code, + arguments.map(_.name.name), + argSlots + ) + case _ => this.run(body) + } val fnBodyNode = BlockNode.build(argExpressions.toArray, bodyExpr) (fnBodyNode, argDefinitions) } + private def buildForeignBody( + language: EpbParser.ForeignLanguage, + code: String, + argumentNames: List[String], + argumentSlots: List[FrameSlot] + ): RuntimeExpression = { + val src = EpbParser.buildSource(language, code, scopeName) + val foreignCt = context.getEnvironment + .parseInternal(src, argumentNames: _*) + val argumentReaders = argumentSlots + .map(slot => ReadLocalVariableNode.build(new FramePointer(0, slot))) + .toArray[RuntimeExpression] + ForeignMethodCallNode.build(argumentReaders, foreignCt) + } + /** Generates code for an Enso function body. * * @param arguments the arguments to the function diff --git a/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala index 2ff316496a42..b60319a27d96 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala @@ -8,6 +8,7 @@ import org.enso.compiler.core.ir.MetadataStorage.MetadataPair import org.enso.compiler.data.BindingsMap import org.enso.compiler.exception.CompilerError import org.enso.compiler.pass.IRPass +import org.enso.interpreter.epb.EpbParser import org.enso.syntax.text.{AST, Debug, Location} import scala.annotation.unused @@ -5113,7 +5114,7 @@ object IR { * @param diagnostics compiler diagnostics for this node */ sealed case class Definition( - lang: String, + lang: EpbParser.ForeignLanguage, code: String, override val location: Option[IdentifiedLocation], override val passData: MetadataStorage = MetadataStorage(), @@ -5133,7 +5134,7 @@ object IR { * @return a copy of `this`, updated with the specified values */ def copy( - lang: String = lang, + lang: EpbParser.ForeignLanguage = lang, code: String = code, location: Option[IdentifiedLocation] = location, passData: MetadataStorage = passData, @@ -5808,6 +5809,11 @@ object IR { case object InvalidOperatorName extends Reason { override def explanation: String = "Invalid operator name." } + + case class InvalidForeignDefinition(details: String) extends Reason { + override def explanation: String = + s"Invalid foreign definition. $details" + } } /** A representation of an invalid piece of IR. diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/lint/UnusedBindings.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/lint/UnusedBindings.scala index 2e45eaec7994..60a33d6b04db 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/lint/UnusedBindings.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/lint/UnusedBindings.scala @@ -118,6 +118,8 @@ case object UnusedBindings extends IRPass { @unused context: InlineContext ): IR.Function = { function match { + case IR.Function.Lambda(_, _: IR.Foreign.Definition, _, _, _, _) => + function case lam @ IR.Function.Lambda(args, body, _, _, _, _) => lam.copy( arguments = args.map(lintFunctionArgument(_, context)), diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala index 7cffd53a0412..e406867c1ee4 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala @@ -3145,7 +3145,7 @@ class RuntimeServerTest contextId, Seq( Api.ExecutionResult.Diagnostic.error( - "Unexpected type provided for argument `that` in Text.+", + "Type_Error Text 2", None, None, None, diff --git a/lib/scala/syntax/definition/src/main/scala/org/enso/syntax/text/ast/meta/Builtin.scala b/lib/scala/syntax/definition/src/main/scala/org/enso/syntax/text/ast/meta/Builtin.scala index e04d97783f8b..318a3dc1f3c3 100644 --- a/lib/scala/syntax/definition/src/main/scala/org/enso/syntax/text/ast/meta/Builtin.scala +++ b/lib/scala/syntax/definition/src/main/scala/org/enso/syntax/text/ast/meta/Builtin.scala @@ -200,25 +200,6 @@ object Builtin { } } - val foreign = Definition( - Var("foreign") -> (Pattern.Cons() :: Pattern.Block()) - ) { ctx => - ctx.body match { - case List(s1) => - s1.body.toStream match { - case List(langAST, Shifted(_, AST.Block.any(bodyAST))) => - val indent = bodyAST.indent - val lang = langAST.wrapped.show() - val body = bodyAST.show() - val bodyLines = body.split("\\r?\\n").toList.drop(1) - val bodyLines2 = bodyLines.map(_.drop(indent)) - AST.Foreign(indent, lang, bodyLines2) - case _ => internalError - } - case _ => internalError - } - } - val skip = Definition( Var("skip") -> Pattern.Expr() ) { ctx => @@ -459,7 +440,6 @@ object Builtin { qualifiedExport, defn, arrow, - foreign, docComment, disableComment, skip, diff --git a/project/FixInstrumentsGeneration.scala b/project/FixInstrumentsGeneration.scala index a28c42c08756..789e6a342caf 100644 --- a/project/FixInstrumentsGeneration.scala +++ b/project/FixInstrumentsGeneration.scala @@ -98,10 +98,16 @@ object FixInstrumentsGeneration { ): FragileFiles = { val fragileSources = (file(s"$root/src/main/java/") ** "*Instrument.java").get ++ - Seq(file(s"$root/src/main/java/org/enso/interpreter/Language.java")) + Seq( + file(s"$root/src/main/java/org/enso/interpreter/Language.java"), + file(s"$root/src/main/java/org/enso/interpreter/epb/EpbLanguage.java") + ) val fragileClassFiles = (classFilesDirectory ** "*Instrument.class").get ++ - Seq(file(s"$classFilesDirectory/org/enso/interpreter/Language.class")) + Seq( + file(s"$classFilesDirectory/org/enso/interpreter/Language.class"), + file(s"$classFilesDirectory/org/enso/interpreter/epb/EpbLanguage.class") + ) FragileFiles(fragileSources, fragileClassFiles) } } diff --git a/test/Tests/src/Main.enso b/test/Tests/src/Main.enso index 4ba8efc5a511..e8ee2b875a29 100644 --- a/test/Tests/src/Main.enso +++ b/test/Tests/src/Main.enso @@ -8,6 +8,7 @@ import Tests.Semantic.Deep_Export.Spec as Deep_Export_Spec import Tests.Semantic.Error_Spec import Tests.Semantic.Import_Loop.Spec as Import_Loop_Spec import Tests.Semantic.Java_Interop_Spec +import Tests.Semantic.Js_Interop_Spec import Tests.Semantic.Meta_Spec import Tests.Semantic.Names_Spec @@ -45,6 +46,7 @@ main = Test.Suite.runMain <| Import_Loop_Spec.spec Interval_Spec.spec Java_Interop_Spec.spec + Js_Interop_Spec.spec Json_Spec.spec List_Spec.spec Map_Spec.spec diff --git a/test/Tests/src/Semantic/Js_Interop_Spec.enso b/test/Tests/src/Semantic/Js_Interop_Spec.enso new file mode 100644 index 000000000000..5e3ae6d1354f --- /dev/null +++ b/test/Tests/src/Semantic/Js_Interop_Spec.enso @@ -0,0 +1,62 @@ +from Base import all +import Test + +foreign js my_method a b = """ + return a + b; + +type My_Type + type My_Type a b + + foreign js my_method = """ + return this.a + this.b; + + my_method_2 x = this.my_method * x + + foreign js my_method_3 y = """ + var r = this.my_method_2(y) + return r + 1; + +foreign js make_object = """ + return { + x: 10, + y: false, + compare: function (guess) { + return this.x < guess; + } + }; + +foreign js make_array = """ + return [{ x: 10}, {x: 20}, {x: 30}]; + +foreign js make_str str = """ + return "foo " + str + " bar" + +spec = Test.group "Polyglot JS" <| + Test.specify "should allow declaring module-level methods in JS" <| + here.my_method 1 2 . should_equal 3 + + Test.specify "should allow mutual calling of instance-level methods" <| + My_Type 3 4 . my_method_3 5 . should_equal 36 + + Test.specify "should expose methods and fields of JS objects" <| + obj = here.make_object + obj.x . should_equal 10 + obj.y . should_be_false + obj.compare 5 . should_be_false + obj.compare 11 . should_be_true + + Test.specify "should expose array interfaces for JS arrays" <| + vec = Vector.Vector here.make_array + vec.map .x . should_equal [10, 20, 30] + + Test.specify "should correctly marshall strings" <| + str = here.make_str "x" + " baz" + str.should_equal "foo x bar baz" + + Test.specify "should make JS strings type pattern-matchable" <| + str = here.make_str "x" + t = case str of + Text -> True + _ -> False + t.should_be_true +