From 311fdd2a4a8d9101dd2539140be8fa038f0f0a19 Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Tue, 2 Feb 2021 11:22:38 +0100 Subject: [PATCH 01/12] Revert "undo changes for other languages" This reverts commit 4e1caf67ec3f4a689f982f76b2168fb39a056a79. --- .../org/enso/runner/ContextFactory.scala | 2 +- .../java/org/enso/interpreter/Language.java | 5 +- .../org/enso/interpreter/epb/EpbContext.java | 34 +++ .../org/enso/interpreter/epb/EpbLanguage.java | 45 ++++ .../org/enso/interpreter/epb/EpbParser.java | 30 +++ .../interpreter/epb/node/ContextFlipNode.java | 59 +++++ .../interpreter/epb/node/SafeEvalNode.java | 92 ++++++++ .../epb/runtime/PolyglotProxy.java | 211 ++++++++++++++++++ .../builtin/interop/generic/EvalNode.java | 4 +- .../interop/generic/ToEnsoTextNode.java | 33 +++ .../interpreter/runtime/builtin/Polyglot.java | 1 + .../runtime/callable/atom/Atom.java | 23 ++ .../callable/function/CurriedMethod.java | 32 +++ .../org/enso/compiler/codegen/AstToIr.scala | 35 ++- .../org/enso/compiler/codegen/AstView.scala | 19 ++ .../enso/compiler/codegen/IrToTruffle.scala | 57 +++-- .../compiler/pass/lint/UnusedBindings.scala | 2 + .../compiler/test/codegen/AstToIrTest.scala | 11 + .../enso/syntax/text/ast/meta/Builtin.scala | 39 ++-- project/FixInstrumentsGeneration.scala | 10 +- 20 files changed, 695 insertions(+), 49 deletions(-) create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/epb/EpbContext.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/epb/EpbLanguage.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/epb/EpbParser.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/epb/node/SafeEvalNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/ToEnsoTextNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/CurriedMethod.java 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..13a0fa91bdef 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,7 @@ class ContextFactory { strictErrors: Boolean = false ): PolyglotContext = { val context = Context - .newBuilder(LanguageInfo.ID) + .newBuilder(LanguageInfo.ID, "js", "R", "epb", "ruby", "python") .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..7afeed4879aa 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/Language.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/Language.java @@ -34,6 +34,7 @@ defaultMimeType = LanguageInfo.MIME_TYPE, characterMimeTypes = {LanguageInfo.MIME_TYPE}, contextPolicy = TruffleLanguage.ContextPolicy.SHARED, + dependentLanguages = {"epb"}, fileTypeDetectors = FileDetector.class, services = ExecutionService.class) @ProvidedTags({ @@ -57,6 +58,7 @@ public final class Language extends TruffleLanguage { */ @Override protected Context createContext(Env env) { +// System.out.println("Enso Context Create"); Context context = new Context(this, getLanguageHome(), env); InstrumentInfo idValueListenerInstrument = env.getInstruments().get(IdExecutionInstrument.INSTRUMENT_ID); @@ -71,7 +73,8 @@ protected Context createContext(Env env) { * @param context the language context */ @Override - protected void initializeContext(Context context) throws Exception { + protected void initializeContext(Context context) { +// System.out.println("Enso Context Initialize"); 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..5d620276d8d5 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbContext.java @@ -0,0 +1,34 @@ +package org.enso.interpreter.epb; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.TruffleContext; +import com.oracle.truffle.api.TruffleLanguage; + +public class EpbContext { + private final boolean isInner; + private final TruffleLanguage.Env env; + private @CompilerDirectives.CompilationFinal TruffleContext innerContext; + + public EpbContext(TruffleLanguage.Env env) { + this.env = env; + isInner = env.getConfig().get("isEpbInner") != null; + } + + public void initialize() { + if (!isInner) { + innerContext = env.newContextBuilder().config("isEpbInner", "yes").build(); + } + } + + public boolean isInner() { + return isInner; + } + + public TruffleContext getInnerContext() { + return innerContext; + } + + 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..d48777331cd8 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbLanguage.java @@ -0,0 +1,45 @@ +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.SafeEvalNode; +import org.enso.interpreter.epb.node.SafeEvalNodeGen; + +@TruffleLanguage.Registration( + id = "epb", + name = "Enso Polyglot Bridge", + characterMimeTypes = {"application/epb"}, + defaultMimeType = "application/epb", + contextPolicy = TruffleLanguage.ContextPolicy.SHARED) +public class EpbLanguage extends TruffleLanguage { + @Override + protected EpbContext createContext(Env env) { + EpbContext ctx = new EpbContext(env); +// System.out.println( +// "EPB Context Create " + (ctx.isInner() ? "Inner" : "Outer") + " (" + ctx + ")"); + return ctx; + } + + @Override + protected void initializeContext(EpbContext context) { +// System.out.println("EPB Context Initialize " + (context.isInner() ? "Inner" : "Outer")); + context.initialize(); + } + + @Override + protected CallTarget parse(ParsingRequest request) { +// System.out.println("Parsing EPB Code"); + String src = request.getSource().getCharacters().toString(); + String[] langAndCode = src.split("#", 2); + return Truffle.getRuntime() + .createCallTarget( + SafeEvalNodeGen.create( + this, langAndCode[0], langAndCode[1], 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..deeb2d53c526 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbParser.java @@ -0,0 +1,30 @@ +package org.enso.interpreter.epb; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.TruffleLanguage; + +public class EpbParser { + public static class Result { + private final String foreignSource; + private final @CompilerDirectives.CompilationFinal String[] arguments; + + public Result(String foreignSource, String[] arguments) { + this.foreignSource = foreignSource; + this.arguments = arguments; + } + + public String getForeignSource() { + return foreignSource; + } + + public String[] getArguments() { + return arguments; + } + } + + public Result run(TruffleLanguage.ParsingRequest request) { + return new Result( + request.getSource().getCharacters().toString(), + request.getArgumentNames().toArray(new String[0])); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java new file mode 100644 index 000000000000..9da62717f61b --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java @@ -0,0 +1,59 @@ +package org.enso.interpreter.epb.node; + +import com.oracle.truffle.api.TruffleContext; +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.PolyglotProxy; + +@GenerateUncached +@ReportPolymorphism +public abstract class ContextFlipNode 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 + * @param origin + * @param target + * @return + */ + public abstract Object execute(Object value, TruffleContext origin, TruffleContext target); + + @Specialization + double doDouble(double d, TruffleContext origin, TruffleContext target) { + return d; + } + + @Specialization + double doFloat(float d, TruffleContext origin, TruffleContext target) { + return d; + } + + @Specialization + long doLong(long i, TruffleContext origin, TruffleContext target) { + return i; + } + + @Specialization + long doInt(int i, TruffleContext origin, TruffleContext target) { + return i; + } + + @Specialization(guards = "proxy.getOrigin() == target") + Object doUnwrapProxy(PolyglotProxy proxy, TruffleContext origin, TruffleContext target) { + return proxy.getDelegate(); + } + + @Specialization(guards = "proxy.getTarget() == target") + Object doAlreadyProxied(PolyglotProxy proxy, TruffleContext origin, TruffleContext target) { + return proxy; + } + + @Specialization + Object doWrapProxy(Object o, TruffleContext origin, TruffleContext target) { + return new PolyglotProxy(o, origin, target); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/SafeEvalNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/SafeEvalNode.java new file mode 100644 index 000000000000..8254f717b654 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/SafeEvalNode.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.DirectCallNode; +import com.oracle.truffle.api.nodes.ExplodeLoop; +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.runtime.callable.function.Function; +import org.enso.interpreter.runtime.state.Stateful; + +import java.util.Arrays; +import java.util.List; + +public abstract class SafeEvalNode extends RootNode { + private final String source; + private @Child DirectCallNode callNode; + private @Children ContextFlipNode[] argConverters; + private @Child ContextFlipNode resultConverter = ContextFlipNodeGen.create(); + private final String[] argNames; + private final String lang; + + public SafeEvalNode(EpbLanguage language, String lang, String source, List arguments) { + super(language, new FrameDescriptor()); + this.source = source; + argNames = arguments.toArray(new String[0]); + if (argNames.length > 0 && argNames[0].equals("this")) { + argNames[0] = "here"; + } + argConverters = new ContextFlipNode[argNames.length]; + for (int i = 0; i < argNames.length; i++) { + argConverters[i] = ContextFlipNodeGen.create(); + } + this.lang = lang.equals("r") ? "R" : lang; + } + + @Specialization + Stateful doExecute( + VirtualFrame frame, + @CachedContext(EpbLanguage.class) ContextReference contextRef) { + EpbContext context = contextRef.get(); + TruffleContext outer = context.getEnv().getContext(); + TruffleContext inner = context.getInnerContext(); + ensureParsed(contextRef, inner); + Object[] args = + prepareArgs( + Function.ArgumentsHelper.getPositionalArguments(frame.getArguments()), inner, outer); + Object state = Function.ArgumentsHelper.getState(frame.getArguments()); + return doRun(args, state, inner, outer); + } + + private void ensureParsed(ContextReference ctxRef, TruffleContext inner) { + if (callNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + Object p = inner.enter(); + try { + Source source = Source.newBuilder(lang, this.source, "").build(); + CallTarget ct = ctxRef.get().getEnv().parseInternal(source, argNames); + callNode = Truffle.getRuntime().createDirectCallNode(ct); + } finally { + inner.leave(p); + } + } + } + + private Stateful doRun( + Object[] arguments, Object state, TruffleContext inner, TruffleContext outer) { + Object p = inner.enter(); + try { + Object r = callNode.call(arguments); + Object wrapped = resultConverter.execute(r, inner, outer); + return new Stateful(state, wrapped); + } finally { + inner.leave(p); + } + } + + @ExplodeLoop + private Object[] prepareArgs(Object[] args, TruffleContext inner, TruffleContext outer) { + Object[] newArgs = new Object[argConverters.length]; + for (int i = 0; i < argConverters.length; i++) { + newArgs[i] = argConverters[i].execute(args[i], outer, inner); + } + return newArgs; + } +} 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..99bc1445bf9f --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java @@ -0,0 +1,211 @@ +package org.enso.interpreter.epb.runtime; + +import com.oracle.truffle.api.TruffleContext; +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.ContextFlipNode; + +@ExportLibrary(InteropLibrary.class) +public class PolyglotProxy implements TruffleObject { + final Object delegate; + private final TruffleContext origin; + private final TruffleContext target; + + public PolyglotProxy(Object delegate, TruffleContext origin, TruffleContext target) { + this.delegate = delegate; + this.origin = origin; + this.target = target; + } + + public Object getDelegate() { + return delegate; + } + + public TruffleContext getOrigin() { + return origin; + } + + public TruffleContext 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) + throws UnsupportedMessageException { + Object p = origin.enter(); + try { + return members.getMembers(this.delegate, includeInternal); + } 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 ContextFlipNode contextFlipNode) + throws ArityException, UnknownIdentifierException, UnsupportedMessageException, + UnsupportedTypeException { + Object p = origin.enter(); + try { + // TODO Arguments + return contextFlipNode.execute( + members.invokeMember(this.delegate, member, arguments), 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 ContextFlipNode contextFlipNode) + throws UnknownIdentifierException, UnsupportedMessageException { + Object p = origin.enter(); + try { + return contextFlipNode.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 ContextFlipNode contextFlipNode) + throws UnsupportedMessageException, ArityException, UnsupportedTypeException { + Object p = origin.enter(); + try { + return contextFlipNode.execute(functions.execute(this.delegate, arguments), 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 ContextFlipNode contextFlipNode) + throws InvalidArrayIndexException, UnsupportedMessageException { + Object p = origin.enter(); + try { + return contextFlipNode.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); + } + } +} 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 index dfbad794ea19..64fd5e874bd2 100644 --- 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 @@ -64,7 +64,7 @@ CallTarget parse(Context context, Text language, Text code, ToJavaStringNode toJ String languageStr = toJavaStringNode.execute(language); String codeStr = toJavaStringNode.execute(code); - Source source = Source.newBuilder(languageStr, codeStr, "").build(); - return context.getEnvironment().parsePublic(source); + Source source = Source.newBuilder("epb", codeStr, "").build(); + return context.getEnvironment().parseInternal(source); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/ToEnsoTextNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/ToEnsoTextNode.java new file mode 100644 index 000000000000..949f1f701aae --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/ToEnsoTextNode.java @@ -0,0 +1,33 @@ +package org.enso.interpreter.node.expression.builtin.interop.generic; + +import com.oracle.truffle.api.interop.ArityException; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.interop.UnsupportedTypeException; +import com.oracle.truffle.api.nodes.Node; +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.interop.syntax.HostValueToEnsoNode; +import org.enso.interpreter.runtime.data.Array; +import org.enso.interpreter.runtime.data.text.Text; +import org.enso.interpreter.runtime.error.PanicException; + +@BuiltinMethod( + type = "Polyglot", + name = "to_enso_text", + description = "Executes a polyglot function object (e.g. a lambda).") +public class ToEnsoTextNode extends Node { + private @Child InteropLibrary library = + InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH); + private final BranchProfile err = BranchProfile.create(); + + Object execute(Object _this, Object text) { + try { + return Text.create(library.asString(text)); + } catch (UnsupportedMessageException e) { + err.enter(); + throw new PanicException(e.getMessage(), this); + } + } +} 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..bb840b3aa483 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 @@ -29,6 +29,7 @@ private void createPolyglot(Language language, ModuleScope scope) { 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, "to_enso_text", ToEnsoTextMethodGen.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..40f905bd6eb5 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 @@ -1,6 +1,8 @@ package org.enso.interpreter.runtime.callable.atom; import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; +import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.CachedContext; import com.oracle.truffle.api.dsl.Specialization; @@ -10,9 +12,13 @@ import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.nodes.UnexpectedResultException; import org.enso.interpreter.Language; +import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; +import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; +import org.enso.interpreter.runtime.callable.function.CurriedMethod; import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.callable.function.FunctionSchema; import org.enso.interpreter.runtime.data.Array; import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; @@ -121,6 +127,23 @@ public boolean isMemberInvocable(String member) { return members != null && members.containsKey(member); } + @ExportMessage + public boolean isMemberReadable(String member) { + return isMemberInvocable(member); + } + + @ExportMessage + public Object readMember(String member) { + for (int i = 0; i < constructor.getArity(); i++) { + if (member.equals(constructor.getFields()[i].getName())) { + return fields[i]; + } + } + Map members = constructor.getDefinitionScope().getMethods().get(constructor); + Function fun = members.get(member); + return new CurriedMethod(fun, this); + } + @ExportMessage static class InvokeMember { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/CurriedMethod.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/CurriedMethod.java new file mode 100644 index 000000000000..8d36767e1040 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/CurriedMethod.java @@ -0,0 +1,32 @@ +package org.enso.interpreter.runtime.callable.function; + +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; + +@ExportLibrary(InteropLibrary.class) +public class CurriedMethod implements TruffleObject { + final Function function; + private final Object self; + + public CurriedMethod(Function function, Object self) { + this.function = function; + this.self = self; + } + + @ExportMessage + public boolean isExecutable() { + return true; + } + + @ExportMessage + public Object execute( + Object[] arguments, @CachedLibrary("this.function") InteropLibrary functions) + throws UnsupportedMessageException, ArityException, UnsupportedTypeException { + Object[] args = new Object[arguments.length + 1]; + args[0] = this.self; + System.arraycopy(arguments, 0, args, 1, arguments.length); + return functions.execute(this.function, args); + } +} 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..53a1702c83fd 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 @@ -8,7 +8,7 @@ import cats.implicits._ 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.compiler.exception.{CompilerError, UnhandledEntity} import org.enso.syntax.text.AST import org.enso.syntax.text.Shape.{ SegmentEscape, @@ -144,9 +144,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 +202,33 @@ object AstToIr { translateExpression(definition), getIdentifiedLocation(inputAst) ) + case AstView.FunctionSugar( + AST.Ident.Var("foreign"), + AST.Ident.Var(lang) :: AST.Ident.Var.any(name) :: args, + body + ) => + 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 typeName = Name.Here(None) + val methodName = buildName(name) + val methodRef = + Name.MethodReference(typeName, methodName, methodName.location) + Module.Scope.Definition.Method.Binding( + methodRef, + args.map(translateArgumentDefinition(_)), + IR.Foreign.Definition(lang, code, None), + None + ) + case _ => throw new CompilerError("I don't care") + } case AstView.FunctionSugar(name, args, body) => val typeName = Name.Here(None) val methodName = buildName(name) diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstView.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstView.scala index 92281af81fe9..48bcd67e9501 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstView.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstView.scala @@ -207,6 +207,25 @@ object AstView { } } + object PolyglotDef { + def unapply( + ast: AST + ): Option[(String, AST.Ident.Var, List[AST], AST.Literal.Text)] = + ast match { + case AST.App.Infix( + SpacedList( + AST.Ident.Var("foreign") :: AST.Ident.Var + .any(lang) :: AST.Ident.Var + .any(funName) :: args + ), + AST.Opr("="), + AST.Literal.Text.any(code) + ) => + Some((lang.name, funName, args, code)) + case _ => None + } + } + object FunctionParam { /** Matches a definition-site function parameter. 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..f1b4a29541f6 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,6 @@ package org.enso.compiler.codegen -import com.oracle.truffle.api.Truffle +import com.oracle.truffle.api.{RootCallTarget, Truffle} import com.oracle.truffle.api.source.{Source, SourceSection} import org.enso.compiler.core.IR import org.enso.compiler.core.IR.Module.Scope.Import @@ -63,8 +63,10 @@ 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.{Constants, Language} - import java.math.BigInteger + +import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition.ExecutionMode + import scala.collection.mutable import scala.collection.mutable.ArrayBuffer @@ -246,23 +248,40 @@ class IrToTruffle( val function = methodDef.body match { case fn: IR.Function => - val (body, arguments) = - expressionProcessor.buildFunctionBody(fn.arguments, fn.body) - val rootNode = MethodRootNode.build( - language, - expressionProcessor.scope, - moduleScope, - body, - makeSection(methodDef.location), - cons, - methodDef.methodName.name - ) - val callTarget = Truffle.getRuntime.createCallTarget(rootNode) - new RuntimeFunction( - callTarget, - null, - new FunctionSchema(arguments: _*) - ) + fn.body match { + case foreign: IR.Foreign.Definition => + val argNames = fn.arguments.map(_.name.name) + val arguments = { + argNames.zipWithIndex.map { case (arg, pos) => + new ArgumentDefinition(pos, arg, ExecutionMode.EXECUTE) + } + } + val src = Source + .newBuilder("epb", foreign.lang + "#" + foreign.code, methodDef.methodName.name) + .build() + val ct = context.getEnvironment + .parseInternal(src, argNames: _*) + .asInstanceOf[RootCallTarget] + new RuntimeFunction(ct, null, new FunctionSchema(arguments: _*)) + case _ => + val (body, arguments) = + expressionProcessor.buildFunctionBody(fn.arguments, fn.body) + val rootNode = MethodRootNode.build( + language, + expressionProcessor.scope, + moduleScope, + body, + makeSection(methodDef.location), + cons, + methodDef.methodName.name + ) + val ct = Truffle.getRuntime.createCallTarget(rootNode) + new RuntimeFunction( + ct, + null, + new FunctionSchema(arguments: _*) + ) + } case _ => throw new CompilerError( "Method bodies must be functions at the point of codegen." 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/compiler/test/codegen/AstToIrTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/codegen/AstToIrTest.scala index 6eb576f0e29f..170cbb485931 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/codegen/AstToIrTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/codegen/AstToIrTest.scala @@ -883,6 +883,17 @@ class AstToIrTest extends CompilerTest with Inside { } } + "AST translation for polyglot definitions" should { + "work" in { + val ir = + s""" + |foreign js foo bar = ${"\"\"\""} + | console.log(foo + bar) + |""".stripMargin.toIrModule + println(ir) + } + } + "AST translation for imports and exports" should { "properly support different kinds of imports" in { val imports = List( 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..9a7763f6159e 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 @@ -199,25 +199,25 @@ object Builtin { case _ => internalError } } - - 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 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() @@ -459,7 +459,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) } } From d57666b6567df9c2dd2eec9b5c2e1e14e1b202be Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Wed, 3 Feb 2021 14:19:08 +0100 Subject: [PATCH 02/12] JS progress and thread synchronization --- .../org/enso/interpreter/epb/EpbContext.java | 17 +++- .../interpreter/epb/node/ContextFlipNode.java | 26 +++--- .../epb/node/DefaultForeignNode.java | 52 ++++++++++++ .../epb/node/ForeignFunctionCallNode.java | 8 ++ .../interpreter/epb/node/JsForeignNode.java | 40 +++++++++ .../interpreter/epb/node/SafeEvalNode.java | 82 +++++++++---------- .../epb/runtime/GuardedTruffleContext.java | 34 ++++++++ .../epb/runtime/PolyglotProxy.java | 31 +++++-- .../builtin/unsafe/CreateThreadNode.java | 30 +++++++ .../interpreter/runtime/builtin/Builtins.java | 2 + .../org/enso/compiler/codegen/AstToIr.scala | 29 ++++++- 11 files changed, 285 insertions(+), 66 deletions(-) create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/epb/node/DefaultForeignNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignFunctionCallNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/epb/node/JsForeignNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/GuardedTruffleContext.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/unsafe/CreateThreadNode.java 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 index 5d620276d8d5..27cd072ccd37 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbContext.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbContext.java @@ -1,22 +1,27 @@ package org.enso.interpreter.epb; import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.TruffleContext; import com.oracle.truffle.api.TruffleLanguage; +import org.enso.interpreter.epb.runtime.GuardedTruffleContext; public class EpbContext { private final boolean isInner; private final TruffleLanguage.Env env; - private @CompilerDirectives.CompilationFinal TruffleContext innerContext; + private @CompilerDirectives.CompilationFinal + GuardedTruffleContext innerContext; + private final GuardedTruffleContext currentContext; public EpbContext(TruffleLanguage.Env env) { this.env = env; isInner = env.getConfig().get("isEpbInner") != null; + currentContext = new GuardedTruffleContext(env.getContext(), isInner); } public void initialize() { if (!isInner) { - innerContext = env.newContextBuilder().config("isEpbInner", "yes").build(); + innerContext = + new GuardedTruffleContext( + env.newContextBuilder().config("isEpbInner", "yes").build(), true); } } @@ -24,10 +29,14 @@ public boolean isInner() { return isInner; } - public TruffleContext getInnerContext() { + public GuardedTruffleContext getInnerContext() { return innerContext; } + public GuardedTruffleContext getCurrentContext() { + return currentContext; + } + public TruffleLanguage.Env getEnv() { return env; } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java index 9da62717f61b..e624d7e434ab 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java @@ -1,10 +1,11 @@ package org.enso.interpreter.epb.node; -import com.oracle.truffle.api.TruffleContext; +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 @@ -20,40 +21,45 @@ public abstract class ContextFlipNode extends Node { * @param target * @return */ - public abstract Object execute(Object value, TruffleContext origin, TruffleContext target); + public abstract Object execute( + Object value, GuardedTruffleContext origin, GuardedTruffleContext target); @Specialization - double doDouble(double d, TruffleContext origin, TruffleContext target) { + double doDouble( + double d, GuardedTruffleContext origin, GuardedTruffleContext target) { return d; } @Specialization - double doFloat(float d, TruffleContext origin, TruffleContext target) { + double doFloat(float d, GuardedTruffleContext origin, GuardedTruffleContext target) { return d; } @Specialization - long doLong(long i, TruffleContext origin, TruffleContext target) { + long doLong(long i, GuardedTruffleContext origin, GuardedTruffleContext target) { return i; } @Specialization - long doInt(int i, TruffleContext origin, TruffleContext target) { + long doInt(int i, GuardedTruffleContext origin, GuardedTruffleContext target) { return i; } @Specialization(guards = "proxy.getOrigin() == target") - Object doUnwrapProxy(PolyglotProxy proxy, TruffleContext origin, TruffleContext target) { + Object doUnwrapProxy( + PolyglotProxy proxy, GuardedTruffleContext origin, GuardedTruffleContext target) { return proxy.getDelegate(); } @Specialization(guards = "proxy.getTarget() == target") - Object doAlreadyProxied(PolyglotProxy proxy, TruffleContext origin, TruffleContext target) { + Object doAlreadyProxied( + PolyglotProxy proxy, GuardedTruffleContext origin, GuardedTruffleContext target) { return proxy; } - @Specialization - Object doWrapProxy(Object o, TruffleContext origin, TruffleContext target) { + @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/DefaultForeignNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/DefaultForeignNode.java new file mode 100644 index 000000000000..5270e57d9d9c --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/DefaultForeignNode.java @@ -0,0 +1,52 @@ +package org.enso.interpreter.epb.node; + +import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.DirectCallNode; +import com.oracle.truffle.api.nodes.ExplodeLoop; +import org.enso.interpreter.epb.EpbContext; +import org.enso.interpreter.epb.EpbLanguage; +import org.enso.interpreter.epb.runtime.GuardedTruffleContext; + +public abstract class DefaultForeignNode extends ForeignFunctionCallNode { + + private @Children ContextFlipNode[] argConverters; + private @Child ContextFlipNode resultConverter = ContextFlipNodeGen.create(); + private @Child DirectCallNode callNode; + + DefaultForeignNode(int argsCount, CallTarget foreignCT) { + this.argConverters = new ContextFlipNode[argsCount]; + for (int i = 0; i < argsCount; i++) { + argConverters[i] = ContextFlipNodeGen.create(); + } + this.callNode = DirectCallNode.create(foreignCT); + } + + @Specialization + Object doExecute( + Object[] arguments, + @CachedContext(EpbLanguage.class) TruffleLanguage.ContextReference ctxRef) { + EpbContext context = ctxRef.get(); + GuardedTruffleContext outer = context.getCurrentContext(); + GuardedTruffleContext inner = context.getInnerContext(); + Object[] args = prepareArgs(arguments, inner, outer); + Object p = inner.enter(); + try { + Object r = callNode.call(args); + return resultConverter.execute(r, inner, outer); + } finally { + inner.leave(p); + } + } + + @ExplodeLoop + private Object[] prepareArgs(Object[] args, GuardedTruffleContext inner, GuardedTruffleContext outer) { + Object[] newArgs = new Object[argConverters.length]; + for (int i = 0; i < argConverters.length; i++) { + newArgs[i] = argConverters[i].execute(args[i], outer, inner); + } + return newArgs; + } +} 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..f0005e16a094 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignFunctionCallNode.java @@ -0,0 +1,8 @@ +package org.enso.interpreter.epb.node; + +import com.oracle.truffle.api.nodes.Node; + +public abstract class ForeignFunctionCallNode extends Node { + 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..76b0649ff159 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/JsForeignNode.java @@ -0,0 +1,40 @@ +package org.enso.interpreter.epb.node; + +import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.TruffleContext; +import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.*; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.DirectCallNode; +import com.oracle.truffle.api.nodes.ExplodeLoop; +import org.enso.interpreter.epb.EpbContext; +import org.enso.interpreter.epb.EpbLanguage; +import org.enso.interpreter.runtime.data.Array; + +public abstract class JsForeignNode extends ForeignFunctionCallNode { + + final Object jsFun; + final int argsCount; + + JsForeignNode(int argsCount, Object jsFun) { + this.argsCount = argsCount; + this.jsFun = jsFun; + } + + @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 + | UnsupportedTypeException + | UnknownIdentifierException + | ArityException e) { + e.printStackTrace(); + throw new RuntimeException("oopsie"); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/SafeEvalNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/SafeEvalNode.java index 8254f717b654..53e3128e8554 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/SafeEvalNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/SafeEvalNode.java @@ -6,23 +6,22 @@ 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.DirectCallNode; -import com.oracle.truffle.api.nodes.ExplodeLoop; 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.runtime.GuardedTruffleContext; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.state.Stateful; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; public abstract class SafeEvalNode extends RootNode { private final String source; - private @Child DirectCallNode callNode; - private @Children ContextFlipNode[] argConverters; - private @Child ContextFlipNode resultConverter = ContextFlipNodeGen.create(); + private @Child ForeignFunctionCallNode foreign; + private @Child ContextFlipNode flipNode = ContextFlipNodeGen.create(); private final String[] argNames; private final String lang; @@ -30,13 +29,6 @@ public SafeEvalNode(EpbLanguage language, String lang, String source, List 0 && argNames[0].equals("this")) { - argNames[0] = "here"; - } - argConverters = new ContextFlipNode[argNames.length]; - for (int i = 0; i < argNames.length; i++) { - argConverters[i] = ContextFlipNodeGen.create(); - } this.lang = lang.equals("r") ? "R" : lang; } @@ -44,49 +36,55 @@ public SafeEvalNode(EpbLanguage language, String lang, String source, List contextRef) { - EpbContext context = contextRef.get(); - TruffleContext outer = context.getEnv().getContext(); - TruffleContext inner = context.getInnerContext(); - ensureParsed(contextRef, inner); - Object[] args = - prepareArgs( - Function.ArgumentsHelper.getPositionalArguments(frame.getArguments()), inner, outer); + ensureParsed(contextRef); + Object result = + foreign.execute(Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())); Object state = Function.ArgumentsHelper.getState(frame.getArguments()); - return doRun(args, state, inner, outer); + return new Stateful(state, result); } - private void ensureParsed(ContextReference ctxRef, TruffleContext inner) { - if (callNode == null) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - Object p = inner.enter(); + private void ensureParsed(ContextReference ctxRef) { + if (foreign == null) { + getLock().lock(); try { - Source source = Source.newBuilder(lang, this.source, "").build(); - CallTarget ct = ctxRef.get().getEnv().parseInternal(source, argNames); - callNode = Truffle.getRuntime().createDirectCallNode(ct); + if (foreign == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + if (lang.equals("js")) { + parseJs(ctxRef); + } else { + EpbContext context = ctxRef.get(); + GuardedTruffleContext inner = context.getInnerContext(); + Object p = inner.enter(); + try { + Source source = Source.newBuilder(lang, this.source, "").build(); + CallTarget ct = ctxRef.get().getEnv().parseInternal(source, argNames); + foreign = insert(DefaultForeignNodeGen.create(argNames.length, ct)); + } finally { + inner.leave(p); + } + } + } } finally { - inner.leave(p); + getLock().unlock(); } } } - private Stateful doRun( - Object[] arguments, Object state, TruffleContext inner, TruffleContext outer) { + private void parseJs(ContextReference ctxRef) { + EpbContext context = ctxRef.get(); + GuardedTruffleContext outer = context.getCurrentContext(); + GuardedTruffleContext inner = context.getInnerContext(); Object p = inner.enter(); try { - Object r = callNode.call(arguments); - Object wrapped = resultConverter.execute(r, inner, outer); - return new Stateful(state, wrapped); + String args = Arrays.stream(argNames).skip(1).collect(Collectors.joining(",")); + String wrappedSrc = + "var poly_enso_eval=function(" + args + "){" + source + "};poly_enso_eval"; + Source source = Source.newBuilder(lang, wrappedSrc, "").build(); + CallTarget ct = ctxRef.get().getEnv().parseInternal(source); + Object fn = flipNode.execute(ct.call(), inner, outer); + foreign = insert(JsForeignNodeGen.create(argNames.length, fn)); } finally { inner.leave(p); } } - - @ExplodeLoop - private Object[] prepareArgs(Object[] args, TruffleContext inner, TruffleContext outer) { - Object[] newArgs = new Object[argConverters.length]; - for (int i = 0; i < argConverters.length; i++) { - newArgs[i] = argConverters[i].execute(args[i], outer, inner); - } - return newArgs; - } } 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..da19ed0fe4cc --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/GuardedTruffleContext.java @@ -0,0 +1,34 @@ +package org.enso.interpreter.epb.runtime; + +import com.oracle.truffle.api.TruffleContext; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class GuardedTruffleContext { + private final TruffleContext context; + private final Lock lock; + + public GuardedTruffleContext(TruffleContext context, boolean isSingleThreaded) { + this.context = context; + if (isSingleThreaded) { + this.lock = new ReentrantLock(); + } else { + this.lock = null; + } + } + + public Object enter() { + if (lock != null) { + lock.lock(); + } + return context.enter(); + } + + public void leave(Object prev) { + if (lock != null) { + lock.unlock(); + } + context.leave(prev); + } +} 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 index 99bc1445bf9f..81d07ed8c0e9 100644 --- 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 @@ -1,6 +1,5 @@ package org.enso.interpreter.epb.runtime; -import com.oracle.truffle.api.TruffleContext; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.interop.*; import com.oracle.truffle.api.library.CachedLibrary; @@ -11,10 +10,11 @@ @ExportLibrary(InteropLibrary.class) public class PolyglotProxy implements TruffleObject { final Object delegate; - private final TruffleContext origin; - private final TruffleContext target; + private final GuardedTruffleContext origin; + private final GuardedTruffleContext target; - public PolyglotProxy(Object delegate, TruffleContext origin, TruffleContext target) { + public PolyglotProxy( + Object delegate, GuardedTruffleContext origin, GuardedTruffleContext target) { this.delegate = delegate; this.origin = origin; this.target = target; @@ -24,11 +24,11 @@ public Object getDelegate() { return delegate; } - public TruffleContext getOrigin() { + public GuardedTruffleContext getOrigin() { return origin; } - public TruffleContext getTarget() { + public GuardedTruffleContext getTarget() { return target; } @@ -85,9 +85,12 @@ public Object invokeMember( UnsupportedTypeException { Object p = origin.enter(); try { - // TODO Arguments + Object[] newArgs = new Object[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + newArgs[i] = contextFlipNode.execute(arguments[i], target, origin); + } return contextFlipNode.execute( - members.invokeMember(this.delegate, member, arguments), origin, target); + members.invokeMember(this.delegate, member, newArgs), origin, target); } finally { origin.leave(p); } @@ -208,4 +211,16 @@ public String asString(@CachedLibrary("this.delegate") InteropLibrary strings) origin.leave(p); } } + + @ExportMessage + public Object toDisplayString( + boolean allowSideEffects, @CachedLibrary("this.delegate") InteropLibrary displays) { + Object p = origin.enter(); + try { + System.out.println(this.delegate.getClass()); + return displays.toDisplayString(this.delegate, allowSideEffects); + } finally { + origin.leave(p); + } + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/unsafe/CreateThreadNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/unsafe/CreateThreadNode.java new file mode 100644 index 000000000000..b4ad6f1dcbe8 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/unsafe/CreateThreadNode.java @@ -0,0 +1,30 @@ +package org.enso.interpreter.node.expression.builtin.unsafe; + +import com.oracle.truffle.api.dsl.ReportPolymorphism; +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.Language; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.dsl.MonadicState; +import org.enso.interpreter.dsl.Suspend; +import org.enso.interpreter.node.BaseNode; +import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; +import org.enso.interpreter.runtime.Context; + +@BuiltinMethod( + type = "Unsafe", + name = "create_thread", + description = + "Create a new thread evaluating the given expression. Currently only exposed for testing and should not be used for _any_ use cases.") +@ReportPolymorphism +public class CreateThreadNode extends Node { + private @Child ThunkExecutorNode thunkExecutorNode = ThunkExecutorNode.build(); + + public Object execute(@MonadicState Object state, Object _this, @Suspend Object action) { + Context context = lookupContextReference(Language.class).get(); + context + .getEnvironment() + .createThread( + () -> thunkExecutorNode.executeThunk(action, state, BaseNode.TailStatus.NOT_TAIL)).start(); + return context.getBuiltins().nothing().newInstance(); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java index e683a99292e7..4366374d9fa0 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java @@ -29,6 +29,7 @@ import org.enso.interpreter.node.expression.builtin.state.RunStateMethodGen; import org.enso.interpreter.node.expression.builtin.text.AnyToTextMethodGen; import org.enso.interpreter.node.expression.builtin.thread.WithInterruptHandlerMethodGen; +import org.enso.interpreter.node.expression.builtin.unsafe.CreateThreadMethodGen; import org.enso.interpreter.node.expression.builtin.unsafe.SetAtomFieldMethodGen; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.Module; @@ -168,6 +169,7 @@ public Builtins(Context context) { thread, "with_interrupt_handler", WithInterruptHandlerMethodGen.makeFunction(language)); scope.registerMethod(unsafe, "set_atom_field", SetAtomFieldMethodGen.makeFunction(language)); + scope.registerMethod(unsafe, "create_thread", CreateThreadMethodGen.makeFunction(language)); } /** @return {@code true} if the IR has been initialized, otherwise {@code false} */ 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 53a1702c83fd..4f31ca66ed68 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 @@ -308,8 +308,33 @@ 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"), + AST.Ident.Var(lang) :: AST.Ident.Var.any(name) :: args, + body + ) => + 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 foreign = + IR.Foreign.Definition(lang, code, getIdentifiedLocation(body)) + IR.Function.Binding( + buildName(name), + args.map(translateArgumentDefinition(_)), + foreign, + getIdentifiedLocation(inputAst) + ) + case _ => throw new CompilerError("I don't care") + } case fs @ AstView.FunctionSugar(_, _, _) => translateExpression(fs) case AST.Comment.any(inputAST) => translateComment(inputAST) case AstView.Binding(AST.App.Section.Right(opr, arg), body) => From a62ab6a8bd319033172faa31cfbfee9ff48057d1 Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Wed, 3 Feb 2021 16:35:25 +0100 Subject: [PATCH 03/12] default arguments support --- .../interpreter/epb/node/SafeEvalNode.java | 7 +- .../foreign/ForeignMethodCallNode.java | 33 +++++ .../enso/compiler/codegen/IrToTruffle.scala | 135 ++++++++++-------- 3 files changed, 111 insertions(+), 64 deletions(-) create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/foreign/ForeignMethodCallNode.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/SafeEvalNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/SafeEvalNode.java index 53e3128e8554..2975ffa3fc26 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/SafeEvalNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/SafeEvalNode.java @@ -33,14 +33,11 @@ public SafeEvalNode(EpbLanguage language, String lang, String source, List contextRef) { ensureParsed(contextRef); - Object result = - foreign.execute(Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())); - Object state = Function.ArgumentsHelper.getState(frame.getArguments()); - return new Stateful(state, result); + return foreign.execute(frame.getArguments()); } private void ensureParsed(ContextReference ctxRef) { 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..031494993681 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/foreign/ForeignMethodCallNode.java @@ -0,0 +1,33 @@ +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; + +import java.util.Arrays; + +public class ForeignMethodCallNode extends ExpressionNode { + private @Children ExpressionNode[] arguments; + private @Child DirectCallNode callNode; + + public ForeignMethodCallNode(ExpressionNode[] arguments, CallTarget foreignCt) { + this.arguments = arguments; + this.callNode = DirectCallNode.create(foreignCt); + } + + public static ForeignMethodCallNode create(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/scala/org/enso/compiler/codegen/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala index f1b4a29541f6..85dd34ab3006 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 com.oracle.truffle.api.{RootCallTarget, Truffle} +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 @@ -35,6 +38,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,11 +65,12 @@ 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 org.enso.interpreter.runtime.callable.argument.ArgumentDefinition.ExecutionMode import scala.collection.mutable import scala.collection.mutable.ArrayBuffer @@ -248,40 +253,24 @@ class IrToTruffle( val function = methodDef.body match { case fn: IR.Function => - fn.body match { - case foreign: IR.Foreign.Definition => - val argNames = fn.arguments.map(_.name.name) - val arguments = { - argNames.zipWithIndex.map { case (arg, pos) => - new ArgumentDefinition(pos, arg, ExecutionMode.EXECUTE) - } - } - val src = Source - .newBuilder("epb", foreign.lang + "#" + foreign.code, methodDef.methodName.name) - .build() - val ct = context.getEnvironment - .parseInternal(src, argNames: _*) - .asInstanceOf[RootCallTarget] - new RuntimeFunction(ct, null, new FunctionSchema(arguments: _*)) - case _ => - val (body, arguments) = - expressionProcessor.buildFunctionBody(fn.arguments, fn.body) - val rootNode = MethodRootNode.build( - language, - expressionProcessor.scope, - moduleScope, - body, - makeSection(methodDef.location), - cons, - methodDef.methodName.name - ) - val ct = Truffle.getRuntime.createCallTarget(rootNode) - new RuntimeFunction( - ct, - null, - new FunctionSchema(arguments: _*) - ) - } + val (body, arguments) = + expressionProcessor.buildFunctionBody(fn.arguments, fn.body) + val rootNode = MethodRootNode.build( + language, + expressionProcessor.scope, + moduleScope, + body, + makeSection(methodDef.location), + cons, + methodDef.methodName.name + ) + val ct = Truffle.getRuntime.createCallTarget(rootNode) + new RuntimeFunction( + ct, + null, + new FunctionSchema(arguments: _*) + ) + case _ => throw new CompilerError( "Method bodies must be functions at the point of codegen." @@ -1042,37 +1031,65 @@ 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: String, + code: String, + argumentNames: List[String], + argumentSlots: List[FrameSlot] + ): RuntimeExpression = { + val src = Source + .newBuilder("epb", language + "#" + code, scopeName) + .build() + val foreignCt = context.getEnvironment + .parseInternal(src, argumentNames: _*) + val argumentReaders = argumentSlots + .map(slot => ReadLocalVariableNode.build(new FramePointer(0, slot))) + .toArray[RuntimeExpression] + ForeignMethodCallNode.create(argumentReaders, foreignCt) + } + /** Generates code for an Enso function body. * * @param arguments the arguments to the function From 9879b1cf720b3221d7773d59bf190b21660fc6f0 Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Thu, 4 Feb 2021 15:34:01 +0100 Subject: [PATCH 04/12] tests, array fixes --- .../std-lib/Base/src/Data/Vector.enso | 24 ++++------ .../interpreter/epb/node/ContextFlipNode.java | 11 +++-- .../epb/runtime/PolyglotProxy.java | 1 - .../interop/generic/GetArraySizeNode.java | 31 ++++++++++++ .../interop/generic/ToEnsoTextNode.java | 33 ------------- .../interpreter/runtime/builtin/Polyglot.java | 2 +- test/Tests/src/Main.enso | 2 + test/Tests/src/Semantic/Js_Interop_Spec.enso | 48 +++++++++++++++++++ 8 files changed, 98 insertions(+), 54 deletions(-) create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetArraySizeNode.java delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/ToEnsoTextNode.java create mode 100644 test/Tests/src/Semantic/Js_Interop_Spec.enso diff --git a/distribution/std-lib/Base/src/Data/Vector.enso b/distribution/std-lib/Base/src/Data/Vector.enso index 6c065afa3225..36a791cf413e 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/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java index e624d7e434ab..83e2620ef4f4 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java @@ -25,8 +25,7 @@ public abstract Object execute( Object value, GuardedTruffleContext origin, GuardedTruffleContext target); @Specialization - double doDouble( - double d, GuardedTruffleContext origin, GuardedTruffleContext target) { + double doDouble(double d, GuardedTruffleContext origin, GuardedTruffleContext target) { return d; } @@ -45,6 +44,11 @@ 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) { @@ -58,8 +62,7 @@ Object doAlreadyProxied( } @Fallback - Object doWrapProxy( - Object o, GuardedTruffleContext origin, GuardedTruffleContext target) { + 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/runtime/PolyglotProxy.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java index 81d07ed8c0e9..b0a828102ba2 100644 --- 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 @@ -217,7 +217,6 @@ public Object toDisplayString( boolean allowSideEffects, @CachedLibrary("this.delegate") InteropLibrary displays) { Object p = origin.enter(); try { - System.out.println(this.delegate.getClass()); return displays.toDisplayString(this.delegate, allowSideEffects); } finally { origin.leave(p); 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/ToEnsoTextNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/ToEnsoTextNode.java deleted file mode 100644 index 949f1f701aae..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/ToEnsoTextNode.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.interop.generic; - -import com.oracle.truffle.api.interop.ArityException; -import com.oracle.truffle.api.interop.InteropLibrary; -import com.oracle.truffle.api.interop.UnsupportedMessageException; -import com.oracle.truffle.api.interop.UnsupportedTypeException; -import com.oracle.truffle.api.nodes.Node; -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.interop.syntax.HostValueToEnsoNode; -import org.enso.interpreter.runtime.data.Array; -import org.enso.interpreter.runtime.data.text.Text; -import org.enso.interpreter.runtime.error.PanicException; - -@BuiltinMethod( - type = "Polyglot", - name = "to_enso_text", - description = "Executes a polyglot function object (e.g. a lambda).") -public class ToEnsoTextNode extends Node { - private @Child InteropLibrary library = - InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH); - private final BranchProfile err = BranchProfile.create(); - - Object execute(Object _this, Object text) { - try { - return Text.create(library.asString(text)); - } catch (UnsupportedMessageException e) { - err.enter(); - throw new PanicException(e.getMessage(), this); - } - } -} 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 bb840b3aa483..1d16aa1467c1 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 @@ -29,7 +29,7 @@ private void createPolyglot(Language language, ModuleScope scope) { 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, "to_enso_text", ToEnsoTextMethodGen.makeFunction(language)); + scope.registerMethod(polyglot, "get_array_size", GetArraySizeMethodGen.makeFunction(language)); } /** @return the atom constructor for polyglot */ diff --git a/test/Tests/src/Main.enso b/test/Tests/src/Main.enso index 63b232c2c88d..2e2191d0dbcc 100644 --- a/test/Tests/src/Main.enso +++ b/test/Tests/src/Main.enso @@ -5,6 +5,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 @@ -38,6 +39,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..d35582a90118 --- /dev/null +++ b/test/Tests/src/Semantic/Js_Interop_Spec.enso @@ -0,0 +1,48 @@ +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}]; + +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] + From 49c05adce777baa1e0748ab179788753f34de154 Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Thu, 4 Feb 2021 17:15:00 +0100 Subject: [PATCH 05/12] refactors and docs inside EPB --- .../org/enso/runner/ContextFactory.scala | 3 +- .../java/org/enso/interpreter/Language.java | 5 +- .../org/enso/interpreter/epb/EpbContext.java | 32 +++++++-- .../org/enso/interpreter/epb/EpbLanguage.java | 52 +++++++++----- .../org/enso/interpreter/epb/EpbParser.java | 68 ++++++++++++++++--- .../interpreter/epb/node/ContextFlipNode.java | 8 +-- .../epb/node/DefaultForeignNode.java | 52 -------------- ...SafeEvalNode.java => ForeignEvalNode.java} | 46 +++++++------ .../epb/node/ForeignFunctionCallNode.java | 8 ++- .../interpreter/epb/node/JsForeignNode.java | 30 ++++---- .../epb/runtime/GuardedTruffleContext.java | 27 +++++++- .../epb/runtime/PolyglotProxy.java | 37 +++++++--- .../org/enso/compiler/codegen/AstToIr.scala | 9 ++- .../enso/compiler/codegen/IrToTruffle.scala | 7 +- .../scala/org/enso/compiler/core/IR.scala | 5 +- 15 files changed, 248 insertions(+), 141 deletions(-) delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/epb/node/DefaultForeignNode.java rename engine/runtime/src/main/java/org/enso/interpreter/epb/node/{SafeEvalNode.java => ForeignEvalNode.java} (66%) 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 13a0fa91bdef..bc51eace9a3b 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,8 @@ class ContextFactory { strictErrors: Boolean = false ): PolyglotContext = { val context = Context - .newBuilder(LanguageInfo.ID, "js", "R", "epb", "ruby", "python") + // TODO: Remove EPB from this list when https://github.com/oracle/graal/pull/3139 is merged + .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 7afeed4879aa..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,7 +35,7 @@ defaultMimeType = LanguageInfo.MIME_TYPE, characterMimeTypes = {LanguageInfo.MIME_TYPE}, contextPolicy = TruffleLanguage.ContextPolicy.SHARED, - dependentLanguages = {"epb"}, + dependentLanguages = {EpbLanguage.ID}, fileTypeDetectors = FileDetector.class, services = ExecutionService.class) @ProvidedTags({ @@ -58,7 +59,6 @@ public final class Language extends TruffleLanguage { */ @Override protected Context createContext(Env env) { -// System.out.println("Enso Context Create"); Context context = new Context(this, getLanguageHome(), env); InstrumentInfo idValueListenerInstrument = env.getInstruments().get(IdExecutionInstrument.INSTRUMENT_ID); @@ -74,7 +74,6 @@ protected Context createContext(Env env) { */ @Override protected void initializeContext(Context context) { -// System.out.println("Enso Context Initialize"); 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 index 27cd072ccd37..46f10fc988e7 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbContext.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbContext.java @@ -4,39 +4,63 @@ 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 @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("isEpbInner") != null; + 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("isEpbInner", "yes").build(), true); + 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 index d48777331cd8..7cbaa77c2e3c 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbLanguage.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbLanguage.java @@ -3,39 +3,59 @@ import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.TruffleLanguage; -import org.enso.interpreter.epb.node.SafeEvalNode; -import org.enso.interpreter.epb.node.SafeEvalNodeGen; +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 capacity, 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 + * org.enso.interpreter.epb.node.ContextFlipNode} for details of how and when this wrapping is being + * 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. It's only role is to manage both contexts and + * provide context-switching facilities. + */ @TruffleLanguage.Registration( - id = "epb", + id = EpbLanguage.ID, name = "Enso Polyglot Bridge", - characterMimeTypes = {"application/epb"}, - defaultMimeType = "application/epb", + 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) { - EpbContext ctx = new EpbContext(env); -// System.out.println( -// "EPB Context Create " + (ctx.isInner() ? "Inner" : "Outer") + " (" + ctx + ")"); - return ctx; + return new EpbContext(env); } @Override protected void initializeContext(EpbContext context) { -// System.out.println("EPB Context Initialize " + (context.isInner() ? "Inner" : "Outer")); context.initialize(); } @Override protected CallTarget parse(ParsingRequest request) { -// System.out.println("Parsing EPB Code"); - String src = request.getSource().getCharacters().toString(); - String[] langAndCode = src.split("#", 2); + EpbParser.Result code = EpbParser.parse(request.getSource()); return Truffle.getRuntime() - .createCallTarget( - SafeEvalNodeGen.create( - this, langAndCode[0], langAndCode[1], request.getArgumentNames())); + .createCallTarget(ForeignEvalNode.build(this, code, request.getArgumentNames())); } @Override 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 index deeb2d53c526..688f2edad87b 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbParser.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbParser.java @@ -1,30 +1,76 @@ package org.enso.interpreter.epb; -import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.source.Source; +/** 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; + + /** @return a Truffle language ID associated with this language */ + public String toTruffleLanguage() { + return "js"; + } + } + + /** A parsing result. */ public static class Result { + private final ForeignLanguage language; private final String foreignSource; - private final @CompilerDirectives.CompilationFinal String[] arguments; - public Result(String foreignSource, String[] arguments) { + private Result(ForeignLanguage language, String foreignSource) { + this.language = language; this.foreignSource = foreignSource; - this.arguments = arguments; } + /** @return the foreign language code to eval */ public String getForeignSource() { return foreignSource; } - public String[] getArguments() { - return arguments; + /** @return the foreign language in which the source is written */ + public ForeignLanguage getLanguage() { + return language; } } - public Result run(TruffleLanguage.ParsingRequest request) { - return new Result( - request.getSource().getCharacters().toString(), - request.getArgumentNames().toArray(new String[0])); + /** + * Parses an EPB source + * + * @param source the source to parse + * @return the result of parsing + */ + public static Result parse(Source source) { + String src = source.getCharacters().toString(); + String[] langAndCode = src.split("#", 2); + return new Result(ForeignLanguage.valueOf(langAndCode[0]), langAndCode[1]); + } + + /** + * 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(); + } + + /** + * 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 getLanguage(String tag) { + if (tag.equals("js")) { + return ForeignLanguage.JS; + } + return null; } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java index 83e2620ef4f4..c33ff104cb2f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java @@ -16,10 +16,10 @@ public abstract class ContextFlipNode extends Node { * is allowed to use interop library on {@code value} and therefore must be called with {@code * origin} entered. * - * @param value - * @param origin - * @param target - * @return + * @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); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/DefaultForeignNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/DefaultForeignNode.java deleted file mode 100644 index 5270e57d9d9c..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/DefaultForeignNode.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.enso.interpreter.epb.node; - -import com.oracle.truffle.api.CallTarget; -import com.oracle.truffle.api.TruffleLanguage; -import com.oracle.truffle.api.dsl.CachedContext; -import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.nodes.DirectCallNode; -import com.oracle.truffle.api.nodes.ExplodeLoop; -import org.enso.interpreter.epb.EpbContext; -import org.enso.interpreter.epb.EpbLanguage; -import org.enso.interpreter.epb.runtime.GuardedTruffleContext; - -public abstract class DefaultForeignNode extends ForeignFunctionCallNode { - - private @Children ContextFlipNode[] argConverters; - private @Child ContextFlipNode resultConverter = ContextFlipNodeGen.create(); - private @Child DirectCallNode callNode; - - DefaultForeignNode(int argsCount, CallTarget foreignCT) { - this.argConverters = new ContextFlipNode[argsCount]; - for (int i = 0; i < argsCount; i++) { - argConverters[i] = ContextFlipNodeGen.create(); - } - this.callNode = DirectCallNode.create(foreignCT); - } - - @Specialization - Object doExecute( - Object[] arguments, - @CachedContext(EpbLanguage.class) TruffleLanguage.ContextReference ctxRef) { - EpbContext context = ctxRef.get(); - GuardedTruffleContext outer = context.getCurrentContext(); - GuardedTruffleContext inner = context.getInnerContext(); - Object[] args = prepareArgs(arguments, inner, outer); - Object p = inner.enter(); - try { - Object r = callNode.call(args); - return resultConverter.execute(r, inner, outer); - } finally { - inner.leave(p); - } - } - - @ExplodeLoop - private Object[] prepareArgs(Object[] args, GuardedTruffleContext inner, GuardedTruffleContext outer) { - Object[] newArgs = new Object[argConverters.length]; - for (int i = 0; i < argConverters.length; i++) { - newArgs[i] = argConverters[i].execute(args[i], outer, inner); - } - return newArgs; - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/SafeEvalNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignEvalNode.java similarity index 66% rename from engine/runtime/src/main/java/org/enso/interpreter/epb/node/SafeEvalNode.java rename to engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignEvalNode.java index 2975ffa3fc26..27fb166b9bdc 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/SafeEvalNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignEvalNode.java @@ -10,6 +10,7 @@ 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 org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.state.Stateful; @@ -18,18 +19,29 @@ import java.util.List; import java.util.stream.Collectors; -public abstract class SafeEvalNode extends RootNode { - private final String source; +public abstract class ForeignEvalNode extends RootNode { + private final EpbParser.Result code; private @Child ForeignFunctionCallNode foreign; private @Child ContextFlipNode flipNode = ContextFlipNodeGen.create(); private final String[] argNames; - private final String lang; - public SafeEvalNode(EpbLanguage language, String lang, String source, List arguments) { + ForeignEvalNode(EpbLanguage language, EpbParser.Result code, List arguments) { super(language, new FrameDescriptor()); - this.source = source; + this.code = code; argNames = arguments.toArray(new String[0]); - this.lang = lang.equals("r") ? "R" : lang; + } + + /** + * 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 @@ -46,19 +58,10 @@ private void ensureParsed(ContextReference ctxRef) { try { if (foreign == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); - if (lang.equals("js")) { + if (code.getLanguage() == EpbParser.ForeignLanguage.JS) { parseJs(ctxRef); } else { - EpbContext context = ctxRef.get(); - GuardedTruffleContext inner = context.getInnerContext(); - Object p = inner.enter(); - try { - Source source = Source.newBuilder(lang, this.source, "").build(); - CallTarget ct = ctxRef.get().getEnv().parseInternal(source, argNames); - foreign = insert(DefaultForeignNodeGen.create(argNames.length, ct)); - } finally { - inner.leave(p); - } + throw new IllegalStateException("Unsupported language resulted from EPB parsing"); } } } finally { @@ -75,8 +78,13 @@ private void parseJs(ContextReference ctxRef) { try { String args = Arrays.stream(argNames).skip(1).collect(Collectors.joining(",")); String wrappedSrc = - "var poly_enso_eval=function(" + args + "){" + source + "};poly_enso_eval"; - Source source = Source.newBuilder(lang, wrappedSrc, "").build(); + "var poly_enso_eval=function(" + + args + + "){\n" + + code.getForeignSource() + + "\n};poly_enso_eval"; + Source source = + Source.newBuilder(code.getLanguage().toTruffleLanguage(), wrappedSrc, "").build(); CallTarget ct = ctxRef.get().getEnv().parseInternal(source); Object fn = flipNode.execute(ct.call(), inner, outer); foreign = insert(JsForeignNodeGen.create(argNames.length, fn)); 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 index f0005e16a094..207c7683b300 100644 --- 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 @@ -2,7 +2,13 @@ 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 index 76b0649ff159..42db304e02e1 100644 --- 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 @@ -1,28 +1,33 @@ package org.enso.interpreter.epb.node; -import com.oracle.truffle.api.CallTarget; -import com.oracle.truffle.api.TruffleContext; -import com.oracle.truffle.api.TruffleLanguage; -import com.oracle.truffle.api.dsl.CachedContext; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.interop.*; import com.oracle.truffle.api.library.CachedLibrary; -import com.oracle.truffle.api.nodes.DirectCallNode; -import com.oracle.truffle.api.nodes.ExplodeLoop; -import org.enso.interpreter.epb.EpbContext; -import org.enso.interpreter.epb.EpbLanguage; import org.enso.interpreter.runtime.data.Array; +/** A node responsible for performing foreign JS calls. */ public abstract class JsForeignNode extends ForeignFunctionCallNode { final Object jsFun; - final int argsCount; + 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]; @@ -30,11 +35,10 @@ Object doExecute(Object[] arguments, @CachedLibrary("jsFun") InteropLibrary inte try { return interopLibrary.invokeMember(jsFun, "apply", arguments[0], new Array(positionalArgs)); } catch (UnsupportedMessageException - | UnsupportedTypeException | UnknownIdentifierException - | ArityException e) { - e.printStackTrace(); - throw new RuntimeException("oopsie"); + | 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 index da19ed0fe4cc..3237f0ce6c3e 100644 --- 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 @@ -5,10 +5,17 @@ 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) { @@ -18,6 +25,19 @@ public GuardedTruffleContext(TruffleContext context, boolean isSingleThreaded) { } } + /** + * 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(); @@ -25,10 +45,15 @@ public Object enter() { 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(); } - context.leave(prev); } } 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 index b0a828102ba2..3cc35f1e9b71 100644 --- 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 @@ -7,12 +7,23 @@ import com.oracle.truffle.api.library.ExportMessage; import org.enso.interpreter.epb.node.ContextFlipNode; +/** + * 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; @@ -20,14 +31,17 @@ public PolyglotProxy( 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; } @@ -54,11 +68,14 @@ public boolean hasMembers(@CachedLibrary("this.delegate") InteropLibrary members @ExportMessage public Object getMembers( - boolean includeInternal, @CachedLibrary("this.delegate") InteropLibrary members) + boolean includeInternal, + @CachedLibrary("this.delegate") InteropLibrary members, + @Cached ContextFlipNode contextFlipNode) throws UnsupportedMessageException { Object p = origin.enter(); try { - return members.getMembers(this.delegate, includeInternal); + return contextFlipNode.execute( + members.getMembers(this.delegate, includeInternal), origin, target); } finally { origin.leave(p); } @@ -83,14 +100,14 @@ public Object invokeMember( @Cached ContextFlipNode contextFlipNode) throws ArityException, UnknownIdentifierException, UnsupportedMessageException, UnsupportedTypeException { + Object[] wrappedArgs = new Object[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + wrappedArgs[i] = contextFlipNode.execute(arguments[i], target, origin); + } Object p = origin.enter(); try { - Object[] newArgs = new Object[arguments.length]; - for (int i = 0; i < arguments.length; i++) { - newArgs[i] = contextFlipNode.execute(arguments[i], target, origin); - } return contextFlipNode.execute( - members.invokeMember(this.delegate, member, newArgs), origin, target); + members.invokeMember(this.delegate, member, wrappedArgs), origin, target); } finally { origin.leave(p); } @@ -137,9 +154,13 @@ public Object execute( @CachedLibrary("this.delegate") InteropLibrary functions, @Cached ContextFlipNode contextFlipNode) throws UnsupportedMessageException, ArityException, UnsupportedTypeException { + Object[] wrappedArgs = new Object[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + wrappedArgs[i] = contextFlipNode.execute(arguments[i], target, origin); + } Object p = origin.enter(); try { - return contextFlipNode.execute(functions.execute(this.delegate, arguments), origin, target); + return contextFlipNode.execute(functions.execute(this.delegate, wrappedArgs), origin, target); } finally { origin.leave(p); } 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 4f31ca66ed68..143f1d2c59d9 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.{CompilerError, UnhandledEntity} +import org.enso.interpreter.epb.EpbParser import org.enso.syntax.text.AST import org.enso.syntax.text.Shape.{ SegmentEscape, @@ -224,7 +225,7 @@ object AstToIr { Module.Scope.Definition.Method.Binding( methodRef, args.map(translateArgumentDefinition(_)), - IR.Foreign.Definition(lang, code, None), + IR.Foreign.Definition(EpbParser.getLanguage(lang), code, None), None ) case _ => throw new CompilerError("I don't care") @@ -326,7 +327,11 @@ object AstToIr { ) .mkString("\n") val foreign = - IR.Foreign.Definition(lang, code, getIdentifiedLocation(body)) + IR.Foreign.Definition( + EpbParser.getLanguage(lang), + code, + getIdentifiedLocation(body) + ) IR.Function.Binding( buildName(name), args.map(translateArgumentDefinition(_)), 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 85dd34ab3006..7e9758c6bf53 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 @@ -24,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, @@ -1074,14 +1075,12 @@ class IrToTruffle( } private def buildForeignBody( - language: String, + language: EpbParser.ForeignLanguage, code: String, argumentNames: List[String], argumentSlots: List[FrameSlot] ): RuntimeExpression = { - val src = Source - .newBuilder("epb", language + "#" + code, scopeName) - .build() + val src = EpbParser.buildSource(language, code, scopeName) val foreignCt = context.getEnvironment .parseInternal(src, argumentNames: _*) val argumentReaders = argumentSlots 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..2b569bc153a4 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, From f43342dc3c8ca6936f1239e39375deb6fb27cb7b Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Thu, 4 Feb 2021 17:46:00 +0100 Subject: [PATCH 06/12] refactors --- .../builtin/interop/generic/EvalNode.java | 70 -------------- .../builtin/unsafe/CreateThreadNode.java | 30 ------ .../foreign/ForeignMethodCallNode.java | 14 ++- .../interpreter/runtime/builtin/Builtins.java | 2 - .../interpreter/runtime/builtin/Polyglot.java | 1 - .../runtime/callable/atom/Atom.java | 22 ++--- .../callable/function/CurriedMethod.java | 32 ------- .../org/enso/compiler/codegen/AstToIr.scala | 92 +++++++++++-------- .../org/enso/compiler/codegen/AstView.scala | 19 ---- .../enso/compiler/codegen/IrToTruffle.scala | 7 +- .../scala/org/enso/compiler/core/IR.scala | 4 + .../compiler/test/codegen/AstToIrTest.scala | 11 --- 12 files changed, 80 insertions(+), 224 deletions(-) delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/EvalNode.java delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/unsafe/CreateThreadNode.java delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/CurriedMethod.java 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 64fd5e874bd2..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("epb", codeStr, "").build(); - return context.getEnvironment().parseInternal(source); - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/unsafe/CreateThreadNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/unsafe/CreateThreadNode.java deleted file mode 100644 index b4ad6f1dcbe8..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/unsafe/CreateThreadNode.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.unsafe; - -import com.oracle.truffle.api.dsl.ReportPolymorphism; -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.Language; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.dsl.MonadicState; -import org.enso.interpreter.dsl.Suspend; -import org.enso.interpreter.node.BaseNode; -import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; -import org.enso.interpreter.runtime.Context; - -@BuiltinMethod( - type = "Unsafe", - name = "create_thread", - description = - "Create a new thread evaluating the given expression. Currently only exposed for testing and should not be used for _any_ use cases.") -@ReportPolymorphism -public class CreateThreadNode extends Node { - private @Child ThunkExecutorNode thunkExecutorNode = ThunkExecutorNode.build(); - - public Object execute(@MonadicState Object state, Object _this, @Suspend Object action) { - Context context = lookupContextReference(Language.class).get(); - context - .getEnvironment() - .createThread( - () -> thunkExecutorNode.executeThunk(action, state, BaseNode.TailStatus.NOT_TAIL)).start(); - return context.getBuiltins().nothing().newInstance(); - } -} 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 index 031494993681..786bd74325b4 100644 --- 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 @@ -6,18 +6,24 @@ import com.oracle.truffle.api.nodes.ExplodeLoop; import org.enso.interpreter.node.ExpressionNode; -import java.util.Arrays; - +/** Performs a call into a given foreign call target. */ public class ForeignMethodCallNode extends ExpressionNode { private @Children ExpressionNode[] arguments; private @Child DirectCallNode callNode; - public ForeignMethodCallNode(ExpressionNode[] arguments, CallTarget foreignCt) { + ForeignMethodCallNode(ExpressionNode[] arguments, CallTarget foreignCt) { this.arguments = arguments; this.callNode = DirectCallNode.create(foreignCt); } - public static ForeignMethodCallNode create(ExpressionNode[] arguments, CallTarget 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); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java index 4366374d9fa0..e683a99292e7 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java @@ -29,7 +29,6 @@ import org.enso.interpreter.node.expression.builtin.state.RunStateMethodGen; import org.enso.interpreter.node.expression.builtin.text.AnyToTextMethodGen; import org.enso.interpreter.node.expression.builtin.thread.WithInterruptHandlerMethodGen; -import org.enso.interpreter.node.expression.builtin.unsafe.CreateThreadMethodGen; import org.enso.interpreter.node.expression.builtin.unsafe.SetAtomFieldMethodGen; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.Module; @@ -169,7 +168,6 @@ public Builtins(Context context) { thread, "with_interrupt_handler", WithInterruptHandlerMethodGen.makeFunction(language)); scope.registerMethod(unsafe, "set_atom_field", SetAtomFieldMethodGen.makeFunction(language)); - scope.registerMethod(unsafe, "create_thread", CreateThreadMethodGen.makeFunction(language)); } /** @return {@code true} if the IR has been initialized, otherwise {@code false} */ 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 1d16aa1467c1..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,7 +26,6 @@ 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)); 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 40f905bd6eb5..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 @@ -1,8 +1,6 @@ package org.enso.interpreter.runtime.callable.atom; import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; -import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.CachedContext; import com.oracle.truffle.api.dsl.Specialization; @@ -10,15 +8,12 @@ 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.node.expression.builtin.text.util.ToJavaStringNode; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; -import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; -import org.enso.interpreter.runtime.callable.function.CurriedMethod; import org.enso.interpreter.runtime.callable.function.Function; -import org.enso.interpreter.runtime.callable.function.FunctionSchema; import org.enso.interpreter.runtime.data.Array; import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; @@ -128,20 +123,25 @@ public boolean isMemberInvocable(String member) { } @ExportMessage + @ExplodeLoop public boolean isMemberReadable(String member) { - return isMemberInvocable(member); + for (int i = 0; i < constructor.getArity(); i++) { + if (member.equals(constructor.getFields()[i].getName())) { + return true; + } + } + return false; } @ExportMessage - public Object readMember(String member) { + @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]; } } - Map members = constructor.getDefinitionScope().getMethods().get(constructor); - Function fun = members.get(member); - return new CurriedMethod(fun, this); + throw UnknownIdentifierException.create(member); } @ExportMessage diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/CurriedMethod.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/CurriedMethod.java deleted file mode 100644 index 8d36767e1040..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/CurriedMethod.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.enso.interpreter.runtime.callable.function; - -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; - -@ExportLibrary(InteropLibrary.class) -public class CurriedMethod implements TruffleObject { - final Function function; - private final Object self; - - public CurriedMethod(Function function, Object self) { - this.function = function; - this.self = self; - } - - @ExportMessage - public boolean isExecutable() { - return true; - } - - @ExportMessage - public Object execute( - Object[] arguments, @CachedLibrary("this.function") InteropLibrary functions) - throws UnsupportedMessageException, ArityException, UnsupportedTypeException { - Object[] args = new Object[arguments.length + 1]; - args[0] = this.self; - System.arraycopy(arguments, 0, args, 1, arguments.length); - return functions.execute(this.function, args); - } -} 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 143f1d2c59d9..59b0e9f3d0bd 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 @@ -8,7 +8,7 @@ import cats.implicits._ 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.{CompilerError, UnhandledEntity} +import org.enso.compiler.exception.UnhandledEntity import org.enso.interpreter.epb.EpbParser import org.enso.syntax.text.AST import org.enso.syntax.text.Shape.{ @@ -205,30 +205,22 @@ object AstToIr { ) case AstView.FunctionSugar( AST.Ident.Var("foreign"), - AST.Ident.Var(lang) :: AST.Ident.Var.any(name) :: args, + header, body ) => - 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 typeName = Name.Here(None) - val methodName = buildName(name) + translateForeignDefinition(header, body) match { + case Some((name, arguments, body)) => + val typeName = Name.Here(None) val methodRef = - Name.MethodReference(typeName, methodName, methodName.location) + Name.MethodReference(typeName, name, name.location) Module.Scope.Definition.Method.Binding( methodRef, - args.map(translateArgumentDefinition(_)), - IR.Foreign.Definition(EpbParser.getLanguage(lang), code, None), - None + arguments, + body, + getIdentifiedLocation(inputAst) ) - case _ => throw new CompilerError("I don't care") + case None => + IR.Error.Syntax(inputAst, IR.Error.Syntax.InvalidForeignDefinition) } case AstView.FunctionSugar(name, args, body) => val typeName = Name.Here(None) @@ -313,32 +305,19 @@ object AstToIr { case atom @ AstView.Atom(_, _) => translateModuleSymbol(atom) case AstView.FunctionSugar( AST.Ident.Var("foreign"), - AST.Ident.Var(lang) :: AST.Ident.Var.any(name) :: args, + header, body ) => - 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 foreign = - IR.Foreign.Definition( - EpbParser.getLanguage(lang), - code, - getIdentifiedLocation(body) - ) + translateForeignDefinition(header, body) match { + case Some((name, arguments, body)) => IR.Function.Binding( - buildName(name), - args.map(translateArgumentDefinition(_)), - foreign, + name, + arguments, + body, getIdentifiedLocation(inputAst) ) - case _ => throw new CompilerError("I don't care") + case None => + IR.Error.Syntax(inputAst, IR.Error.Syntax.InvalidForeignDefinition) } case fs @ AstView.FunctionSugar(_, _, _) => translateExpression(fs) case AST.Comment.any(inputAST) => translateComment(inputAST) @@ -362,6 +341,39 @@ object AstToIr { } } + private def translateForeignDefinition(header: List[AST], body: AST): Option[ + (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.getLanguage(lang) + if (language == null) { None } + else { + val foreign = IR.Foreign.Definition( + language, + code, + getIdentifiedLocation(body) + ) + Some((methodName, arguments, foreign)) + } + case _ => None + } + case _ => None + } + } + /** 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/AstView.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstView.scala index 48bcd67e9501..92281af81fe9 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstView.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstView.scala @@ -207,25 +207,6 @@ object AstView { } } - object PolyglotDef { - def unapply( - ast: AST - ): Option[(String, AST.Ident.Var, List[AST], AST.Literal.Text)] = - ast match { - case AST.App.Infix( - SpacedList( - AST.Ident.Var("foreign") :: AST.Ident.Var - .any(lang) :: AST.Ident.Var - .any(funName) :: args - ), - AST.Opr("="), - AST.Literal.Text.any(code) - ) => - Some((lang.name, funName, args, code)) - case _ => None - } - } - object FunctionParam { /** Matches a definition-site function parameter. 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 7e9758c6bf53..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 @@ -265,13 +265,12 @@ class IrToTruffle( cons, methodDef.methodName.name ) - val ct = Truffle.getRuntime.createCallTarget(rootNode) + val callTarget = Truffle.getRuntime.createCallTarget(rootNode) new RuntimeFunction( - ct, + callTarget, null, new FunctionSchema(arguments: _*) ) - case _ => throw new CompilerError( "Method bodies must be functions at the point of codegen." @@ -1086,7 +1085,7 @@ class IrToTruffle( val argumentReaders = argumentSlots .map(slot => ReadLocalVariableNode.build(new FramePointer(0, slot))) .toArray[RuntimeExpression] - ForeignMethodCallNode.create(argumentReaders, foreignCt) + ForeignMethodCallNode.build(argumentReaders, foreignCt) } /** Generates code for an Enso function body. 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 2b569bc153a4..8a1d314050c8 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 @@ -5809,6 +5809,10 @@ object IR { case object InvalidOperatorName extends Reason { override def explanation: String = "Invalid operator name." } + + case object InvalidForeignDefinition extends Reason { + override def explanation: String = "Invalid foreign definition." + } } /** A representation of an invalid piece of IR. diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/codegen/AstToIrTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/codegen/AstToIrTest.scala index 170cbb485931..6eb576f0e29f 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/codegen/AstToIrTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/codegen/AstToIrTest.scala @@ -883,17 +883,6 @@ class AstToIrTest extends CompilerTest with Inside { } } - "AST translation for polyglot definitions" should { - "work" in { - val ir = - s""" - |foreign js foo bar = ${"\"\"\""} - | console.log(foo + bar) - |""".stripMargin.toIrModule - println(ir) - } - } - "AST translation for imports and exports" should { "properly support different kinds of imports" in { val imports = List( From f8412011eb78b61b0491efa487de2ed4fcb0ccbc Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Thu, 4 Feb 2021 17:49:07 +0100 Subject: [PATCH 07/12] syntax --- .../enso/syntax/text/ast/meta/Builtin.scala | 19 ------------------- 1 file changed, 19 deletions(-) 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 9a7763f6159e..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 @@ -199,25 +199,6 @@ object Builtin { case _ => internalError } } -// -// 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() From 7250a0a854fa9a1344a7ef7319e3fd463a441162 Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Mon, 8 Feb 2021 16:15:28 +0100 Subject: [PATCH 08/12] handle non-text texts --- .../node/callable/InvokeMethodNode.java | 36 ++++++++++++++- .../callable/resolver/HostMethodCallNode.java | 3 ++ .../controlflow/caseexpr/TextBranchNode.java | 12 ++++- .../builtin/debug/DebugEvalNode.java | 6 ++- .../interop/generic/GetMemberNode.java | 8 ++-- .../builtin/interop/generic/InvokeNode.java | 8 ++-- .../interop/java/AddToClassPathNode.java | 11 +++-- .../builtin/interop/java/LookupClassNode.java | 11 +++-- .../expression/builtin/io/GetFileNode.java | 16 +++---- .../expression/builtin/io/PrintErrNode.java | 23 +++++++--- .../expression/builtin/io/PrintlnNode.java | 30 ++++++++----- .../meta/CreateUnresolvedSymbolNode.java | 7 +-- .../builtin/system/CreateProcessNode.java | 17 +++---- .../expression/builtin/text/ConcatNode.java | 22 +++++++-- .../expression/builtin/text/OptimizeNode.java | 18 +++++++- .../builtin/text/util/ExpectStringNode.java | 44 ++++++++++++++++++ .../builtin/text/util/ExpectTextNode.java | 45 +++++++++++++++++++ .../builtin/text/util/IsTextNode.java | 35 +++++++++++++++ .../enso/interpreter/runtime/type/Types.java | 4 ++ test/Tests/src/Semantic/Js_Interop_Spec.enso | 14 ++++++ 20 files changed, 301 insertions(+), 69 deletions(-) create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/ExpectStringNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/ExpectTextNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/IsTextNode.java 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..4936097e7e7c 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 @@ -23,6 +23,7 @@ public enum PolyglotCallType { INSTANTIATE, GET_ARRAY_LENGTH, READ_ARRAY_ELEMENT, + CONVERT_TO_TEXT, NOT_SUPPORTED } @@ -53,6 +54,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..c15a1c648925 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,11 +1,15 @@ 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.node.expression.builtin.text.util.IsTextNode; import org.enso.interpreter.runtime.callable.atom.Atom; import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.data.text.Text; @@ -38,8 +42,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/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/builtin/text/util/IsTextNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/IsTextNode.java new file mode 100644 index 000000000000..02963b20ad86 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/IsTextNode.java @@ -0,0 +1,35 @@ +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 IsTextNode extends Node { + private @Child InteropLibrary library = InteropLibrary.getFactory().createDispatched(10); + + public abstract boolean execute(Object o); + + @Specialization + boolean doText(Text o) { + return true; + } + + @Specialization + boolean doString(String o) { + return true; + } + + @Fallback + boolean doFallback(Object o) { + return library.isString(o); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/Types.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/Types.java index a854661d75a1..fe3ddf60bf15 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/Types.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/Types.java @@ -1,8 +1,12 @@ package org.enso.interpreter.runtime.type; +import com.oracle.truffle.api.dsl.ImplicitCast; import com.oracle.truffle.api.dsl.TypeSystem; import com.oracle.truffle.api.interop.ArityException; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.interop.UnsupportedTypeException; +import com.oracle.truffle.api.library.CachedLibrary; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.argument.Thunk; import org.enso.interpreter.runtime.callable.atom.Atom; diff --git a/test/Tests/src/Semantic/Js_Interop_Spec.enso b/test/Tests/src/Semantic/Js_Interop_Spec.enso index d35582a90118..5e3ae6d1354f 100644 --- a/test/Tests/src/Semantic/Js_Interop_Spec.enso +++ b/test/Tests/src/Semantic/Js_Interop_Spec.enso @@ -28,6 +28,9 @@ foreign js make_object = """ 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 @@ -46,3 +49,14 @@ spec = Test.group "Polyglot JS" <| 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 + From b96c3b3ad117f356357c7c9171053a23a50d1fd1 Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Mon, 8 Feb 2021 16:40:38 +0100 Subject: [PATCH 09/12] cr feedback --- .../org/enso/runner/ContextFactory.scala | 1 + .../org/enso/interpreter/epb/EpbLanguage.java | 8 ++-- .../org/enso/interpreter/epb/EpbParser.java | 44 ++++++++++++------- ...xtFlipNode.java => ContextRewrapNode.java} | 2 +- .../interpreter/epb/node/ForeignEvalNode.java | 7 ++- .../epb/runtime/PolyglotProxy.java | 28 ++++++------ .../runtime/src/main/resources/Builtins.enso | 13 ++---- .../org/enso/compiler/codegen/AstToIr.scala | 37 ++++++++++------ .../scala/org/enso/compiler/core/IR.scala | 5 ++- 9 files changed, 82 insertions(+), 63 deletions(-) rename engine/runtime/src/main/java/org/enso/interpreter/epb/node/{ContextFlipNode.java => ContextRewrapNode.java} (97%) 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 bc51eace9a3b..89f6b148e6cd 100644 --- a/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala +++ b/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala @@ -35,6 +35,7 @@ class ContextFactory { ): PolyglotContext = { val context = Context // 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) 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 index 7cbaa77c2e3c..46e351f6a95d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbLanguage.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbLanguage.java @@ -3,6 +3,7 @@ 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; /** @@ -13,7 +14,7 @@ * {@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 capacity, ensuring that access to the single-threaded languages is + * 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 @@ -21,11 +22,10 @@ * 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 - * org.enso.interpreter.epb.node.ContextFlipNode} for details of how and when this wrapping is being - * done. + * 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. It's only role is to manage both contexts and + * and outer contexts and thus it is very minimal. Its only role is to manage both contexts and * provide context-switching facilities. */ @TruffleLanguage.Registration( 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 index 688f2edad87b..61c3fe764885 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbParser.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbParser.java @@ -2,17 +2,40 @@ 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", "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 toTruffleLanguage() { - return "js"; + 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); } } @@ -45,7 +68,7 @@ public ForeignLanguage getLanguage() { */ public static Result parse(Source source) { String src = source.getCharacters().toString(); - String[] langAndCode = src.split("#", 2); + String[] langAndCode = src.split(separator, 2); return new Result(ForeignLanguage.valueOf(langAndCode[0]), langAndCode[1]); } @@ -60,17 +83,4 @@ public static Result parse(Source source) { public static Source buildSource(ForeignLanguage language, String foreignSource, String name) { return Source.newBuilder(EpbLanguage.ID, language + separator + foreignSource, name).build(); } - - /** - * 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 getLanguage(String tag) { - if (tag.equals("js")) { - return ForeignLanguage.JS; - } - return null; - } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java similarity index 97% rename from engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java rename to engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java index c33ff104cb2f..179bc5322520 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextFlipNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java @@ -10,7 +10,7 @@ @GenerateUncached @ReportPolymorphism -public abstract class ContextFlipNode extends Node { +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 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 index 27fb166b9bdc..3430f6b84c9a 100644 --- 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 @@ -12,8 +12,6 @@ import org.enso.interpreter.epb.EpbLanguage; import org.enso.interpreter.epb.EpbParser; import org.enso.interpreter.epb.runtime.GuardedTruffleContext; -import org.enso.interpreter.runtime.callable.function.Function; -import org.enso.interpreter.runtime.state.Stateful; import java.util.Arrays; import java.util.List; @@ -22,7 +20,8 @@ public abstract class ForeignEvalNode extends RootNode { private final EpbParser.Result code; private @Child ForeignFunctionCallNode foreign; - private @Child ContextFlipNode flipNode = ContextFlipNodeGen.create(); + private @Child + ContextRewrapNode flipNode = ContextRewrapNodeGen.create(); private final String[] argNames; ForeignEvalNode(EpbLanguage language, EpbParser.Result code, List arguments) { @@ -84,7 +83,7 @@ private void parseJs(ContextReference ctxRef) { + code.getForeignSource() + "\n};poly_enso_eval"; Source source = - Source.newBuilder(code.getLanguage().toTruffleLanguage(), wrappedSrc, "").build(); + Source.newBuilder(code.getLanguage().getTruffleId(), wrappedSrc, "").build(); CallTarget ct = ctxRef.get().getEnv().parseInternal(source); Object fn = flipNode.execute(ct.call(), inner, outer); foreign = insert(JsForeignNodeGen.create(argNames.length, fn)); 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 index 3cc35f1e9b71..659a30304401 100644 --- 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 @@ -5,7 +5,7 @@ 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.ContextFlipNode; +import org.enso.interpreter.epb.node.ContextRewrapNode; /** * Wraps a polyglot value that is to be shared between Truffle contexts. See {@link @@ -70,11 +70,11 @@ public boolean hasMembers(@CachedLibrary("this.delegate") InteropLibrary members public Object getMembers( boolean includeInternal, @CachedLibrary("this.delegate") InteropLibrary members, - @Cached ContextFlipNode contextFlipNode) + @Cached @Cached.Exclusive ContextRewrapNode contextRewrapNode) throws UnsupportedMessageException { Object p = origin.enter(); try { - return contextFlipNode.execute( + return contextRewrapNode.execute( members.getMembers(this.delegate, includeInternal), origin, target); } finally { origin.leave(p); @@ -97,16 +97,16 @@ public Object invokeMember( String member, Object[] arguments, @CachedLibrary("this.delegate") InteropLibrary members, - @Cached ContextFlipNode contextFlipNode) + @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] = contextFlipNode.execute(arguments[i], target, origin); + wrappedArgs[i] = contextRewrapNode.execute(arguments[i], target, origin); } Object p = origin.enter(); try { - return contextFlipNode.execute( + return contextRewrapNode.execute( members.invokeMember(this.delegate, member, wrappedArgs), origin, target); } finally { origin.leave(p); @@ -128,11 +128,11 @@ public boolean isMemberReadable( public Object readMember( String member, @CachedLibrary("this.delegate") InteropLibrary members, - @Cached ContextFlipNode contextFlipNode) + @Cached @Cached.Exclusive ContextRewrapNode contextRewrapNode) throws UnknownIdentifierException, UnsupportedMessageException { Object p = origin.enter(); try { - return contextFlipNode.execute(members.readMember(this.delegate, member), origin, target); + return contextRewrapNode.execute(members.readMember(this.delegate, member), origin, target); } finally { origin.leave(p); } @@ -152,15 +152,16 @@ public boolean isExecutable(@CachedLibrary("this.delegate") InteropLibrary funct public Object execute( Object[] arguments, @CachedLibrary("this.delegate") InteropLibrary functions, - @Cached ContextFlipNode contextFlipNode) + @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] = contextFlipNode.execute(arguments[i], target, origin); + wrappedArgs[i] = contextRewrapNode.execute(arguments[i], target, origin); } Object p = origin.enter(); try { - return contextFlipNode.execute(functions.execute(this.delegate, wrappedArgs), origin, target); + return contextRewrapNode.execute( + functions.execute(this.delegate, wrappedArgs), origin, target); } finally { origin.leave(p); } @@ -202,11 +203,12 @@ public boolean isArrayElementReadable( public Object readArrayElement( long index, @CachedLibrary("this.delegate") InteropLibrary arrays, - @Cached ContextFlipNode contextFlipNode) + @Cached @Cached.Exclusive ContextRewrapNode contextRewrapNode) throws InvalidArrayIndexException, UnsupportedMessageException { Object p = origin.enter(); try { - return contextFlipNode.execute(arrays.readArrayElement(this.delegate, index), origin, target); + return contextRewrapNode.execute( + arrays.readArrayElement(this.delegate, index), origin, target); } finally { origin.leave(p); } 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 59b0e9f3d0bd..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 @@ -209,7 +209,7 @@ object AstToIr { body ) => translateForeignDefinition(header, body) match { - case Some((name, arguments, body)) => + case Right((name, arguments, body)) => val typeName = Name.Here(None) val methodRef = Name.MethodReference(typeName, name, name.location) @@ -219,8 +219,11 @@ object AstToIr { body, getIdentifiedLocation(inputAst) ) - case None => - IR.Error.Syntax(inputAst, IR.Error.Syntax.InvalidForeignDefinition) + case Left(reason) => + IR.Error.Syntax( + inputAst, + IR.Error.Syntax.InvalidForeignDefinition(reason) + ) } case AstView.FunctionSugar(name, args, body) => val typeName = Name.Here(None) @@ -309,15 +312,18 @@ object AstToIr { body ) => translateForeignDefinition(header, body) match { - case Some((name, arguments, body)) => + case Right((name, arguments, body)) => IR.Function.Binding( name, arguments, body, getIdentifiedLocation(inputAst) ) - case None => - IR.Error.Syntax(inputAst, IR.Error.Syntax.InvalidForeignDefinition) + 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) @@ -341,7 +347,8 @@ object AstToIr { } } - private def translateForeignDefinition(header: List[AST], body: AST): Option[ + private def translateForeignDefinition(header: List[AST], body: AST): Either[ + String, (IR.Name, List[IR.DefinitionArgument], IR.Foreign.Definition) ] = { header match { @@ -358,19 +365,23 @@ object AstToIr { .mkString("\n") val methodName = buildName(name) val arguments = args.map(translateArgumentDefinition(_)) - val language = EpbParser.getLanguage(lang) - if (language == null) { None } - else { + 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) ) - Some((methodName, arguments, foreign)) + Right((methodName, arguments, foreign)) } - case _ => None + case _ => + Left( + "The body of a foreign block must be an uninterpolated string block literal." + ) } - case _ => None + case _ => Left("The method name is not specified.") } } 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 8a1d314050c8..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 @@ -5810,8 +5810,9 @@ object IR { override def explanation: String = "Invalid operator name." } - case object InvalidForeignDefinition extends Reason { - override def explanation: String = "Invalid foreign definition." + case class InvalidForeignDefinition(details: String) extends Reason { + override def explanation: String = + s"Invalid foreign definition. $details" } } From 665a3276b216766f5a108a35ec7dda4d9983a69c Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Mon, 8 Feb 2021 16:55:45 +0100 Subject: [PATCH 10/12] more stuff --- .../interpreter/epb/node/ForeignEvalNode.java | 8 ++-- .../callable/IndirectInvokeMethodNode.java | 46 ++++++++++++++++++- .../callable/resolver/HostMethodCallNode.java | 21 +++++++++ .../controlflow/caseexpr/TextBranchNode.java | 4 +- .../builtin/text/util/IsTextNode.java | 35 -------------- 5 files changed, 70 insertions(+), 44 deletions(-) delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/IsTextNode.java 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 index 3430f6b84c9a..215221354515 100644 --- 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 @@ -20,8 +20,7 @@ public abstract class ForeignEvalNode extends RootNode { private final EpbParser.Result code; private @Child ForeignFunctionCallNode foreign; - private @Child - ContextRewrapNode flipNode = ContextRewrapNodeGen.create(); + private @Child ContextRewrapNode rewrapNode = ContextRewrapNodeGen.create(); private final String[] argNames; ForeignEvalNode(EpbLanguage language, EpbParser.Result code, List arguments) { @@ -82,10 +81,9 @@ private void parseJs(ContextReference ctxRef) { + "){\n" + code.getForeignSource() + "\n};poly_enso_eval"; - Source source = - Source.newBuilder(code.getLanguage().getTruffleId(), wrappedSrc, "").build(); + Source source = Source.newBuilder(code.getLanguage().getTruffleId(), wrappedSrc, "").build(); CallTarget ct = ctxRef.get().getEnv().parseInternal(source); - Object fn = flipNode.execute(ct.call(), inner, outer); + 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/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/resolver/HostMethodCallNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java index 4936097e7e7c..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,12 +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 } 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 c15a1c648925..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 @@ -9,10 +9,8 @@ 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.node.expression.builtin.text.util.IsTextNode; 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 { @@ -47,7 +45,7 @@ void doLiteral( VirtualFrame frame, Object state, Object target, - @CachedLibrary(limit="10") InteropLibrary strings) { + @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/text/util/IsTextNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/IsTextNode.java deleted file mode 100644 index 02963b20ad86..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/IsTextNode.java +++ /dev/null @@ -1,35 +0,0 @@ -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 IsTextNode extends Node { - private @Child InteropLibrary library = InteropLibrary.getFactory().createDispatched(10); - - public abstract boolean execute(Object o); - - @Specialization - boolean doText(Text o) { - return true; - } - - @Specialization - boolean doString(String o) { - return true; - } - - @Fallback - boolean doFallback(Object o) { - return library.isString(o); - } -} From f04c0d07a4bac742b8f2d6aa87b1279fbf984db9 Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Mon, 8 Feb 2021 16:56:13 +0100 Subject: [PATCH 11/12] remove imports --- .../main/java/org/enso/interpreter/runtime/type/Types.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/Types.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/Types.java index fe3ddf60bf15..a854661d75a1 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/Types.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/Types.java @@ -1,12 +1,8 @@ package org.enso.interpreter.runtime.type; -import com.oracle.truffle.api.dsl.ImplicitCast; import com.oracle.truffle.api.dsl.TypeSystem; import com.oracle.truffle.api.interop.ArityException; -import com.oracle.truffle.api.interop.InteropLibrary; -import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.interop.UnsupportedTypeException; -import com.oracle.truffle.api.library.CachedLibrary; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.argument.Thunk; import org.enso.interpreter.runtime.callable.atom.Atom; From 4edf19631f808badc98eb8c015932b4c702aaa24 Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Mon, 8 Feb 2021 17:05:55 +0100 Subject: [PATCH 12/12] fix test --- .../enso/interpreter/test/instrument/RuntimeServerTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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,