From 339c2753383ef149748993c7039037f2bb08c191 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 29 Aug 2024 17:20:53 +0200 Subject: [PATCH] Benchmark and speed processing of polyglot java imports up (#10899) --- .../org/enso/compiler/ExecCompilerTest.java | 9 +- .../enso/interpreter/test/WarningsTest.java | 21 +++-- .../enso/interpreter/node/MethodRootNode.java | 3 +- .../argument/ReadArgumentCheckNode.java | 20 +++-- .../caseexpr/ObjectEqualityBranchNode.java | 12 ++- .../constant/ConstantObjectNode.java | 2 +- .../expression/constant/LazyObjectNode.java | 44 +++++++++ .../enso/interpreter/runtime/EnsoContext.java | 14 +-- .../interpreter/runtime/builtin/Builtins.java | 8 +- .../interpreter/runtime/builtin/Error.java | 25 ++++-- .../runtime/error/DataflowError.java | 24 +++++ .../runtime/error/PanicException.java | 9 ++ .../runtime/scope/ModuleScope.java | 36 +++++--- .../runtime/util/CachingSupplier.java | 56 ++++++++++-- .../interpreter/runtime/IrToTruffle.scala | 89 ++++++++----------- test/Benchmarks/src/Startup/Import_World.enso | 9 ++ test/Benchmarks/src/Startup/Startup.enso | 8 +- 17 files changed, 275 insertions(+), 114 deletions(-) create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/constant/LazyObjectNode.java create mode 100644 test/Benchmarks/src/Startup/Import_World.enso diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ExecCompilerTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ExecCompilerTest.java index bc9f0cf7fbf0..592ec81905d1 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ExecCompilerTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ExecCompilerTest.java @@ -408,16 +408,15 @@ public void testDoubledRandom() throws Exception { polyglot java import java.util.Random run seed = - operator1 = Random.new_generator seed + Random.new_generator seed """); var run = module.invokeMember("eval_expression", "run"); try { var err = run.execute(1L); - fail("Not expecting any result: " + err); + assertTrue("Returned value represents exception: " + err, err.isException()); + throw err.throwException(); } catch (PolyglotException ex) { - assertEquals( - "Compile error: Compiler Internal Error: No polyglot symbol for Random.", - ex.getMessage()); + assertEquals("Compile error: No polyglot symbol for Random.", ex.getMessage()); } } diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/WarningsTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/WarningsTest.java index 3dd2def91dfd..61aead7772ee 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/WarningsTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/WarningsTest.java @@ -148,16 +148,27 @@ private void assertWarningsForAType(Value v) { assertEquals("Types without and with warnings are the same", type, warningType); assertTrue("It is an exception. Type: " + type, warning2.isException()); try { - warning2.throwException(); + throw warning2.throwException(); } catch (PolyglotException ex) { if (ex.getMessage() == null) { assertEquals(generator.typeError(), type); assertEquals(generator.typeError(), warningType); } else { - assertThat( - "Warning found for " + type, - ex.getMessage(), - AllOf.allOf(containsString("warn:once"), containsString("warn:twice"))); + try { + assertThat( + "Warning found for " + type, + ex.getMessage(), + AllOf.allOf(containsString("warn:once"), containsString("warn:twice"))); + } catch (AssertionError err) { + if (type != null && v.equals(warning1) && v.equals(warning2)) { + assertEquals( + "Cannot attach warnings to Error - check it is an error", + "Standard.Base.Error.Error", + type.getMetaQualifiedName()); + return; + } + throw err; + } } } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/MethodRootNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/MethodRootNode.java index 7477056e24ce..90674085e6c7 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/MethodRootNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/MethodRootNode.java @@ -14,7 +14,6 @@ import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.data.atom.AtomConstructor; -import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.error.PanicException; import org.enso.interpreter.runtime.scope.ModuleScope; @@ -214,7 +213,7 @@ final ExpressionNode replaceItself() { return newNode; } catch (CompilerError abnormalException) { var ctx = EnsoContext.get(this); - var msg = Text.create(abnormalException.getMessage()); + var msg = abnormalException.getMessage(); var load = ctx.getBuiltins().error().makeCompileError(msg); throw new PanicException(load, this); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentCheckNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentCheckNode.java index d61186db70f6..b353b105e5bb 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentCheckNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/ReadArgumentCheckNode.java @@ -17,6 +17,7 @@ import com.oracle.truffle.api.nodes.RootNode; import java.util.Arrays; import java.util.List; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; import org.enso.interpreter.EnsoLanguage; @@ -45,6 +46,7 @@ import org.enso.interpreter.runtime.error.PanicSentinel; import org.enso.interpreter.runtime.library.dispatch.TypeOfNode; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; +import org.enso.interpreter.runtime.util.CachingSupplier; import org.graalvm.collections.Pair; public abstract class ReadArgumentCheckNode extends Node { @@ -162,8 +164,10 @@ public static ReadArgumentCheckNode build(EnsoContext ctx, String comment, Type return ReadArgumentCheckNodeFactory.TypeCheckNodeGen.create(comment, expectedType); } - public static ReadArgumentCheckNode meta(String comment, Object metaObject) { - return ReadArgumentCheckNodeFactory.MetaCheckNodeGen.create(comment, metaObject); + public static ReadArgumentCheckNode meta( + String comment, Supplier metaObjectSupplier) { + var cachingSupplier = CachingSupplier.wrap(metaObjectSupplier); + return ReadArgumentCheckNodeFactory.MetaCheckNodeGen.create(comment, cachingSupplier); } public static boolean isWrappedThunk(Function fn) { @@ -475,12 +479,12 @@ String expectedTypeMessage() { } abstract static class MetaCheckNode extends ReadArgumentCheckNode { - private final Object expectedMeta; + private final CachingSupplier expectedSupplier; @CompilerDirectives.CompilationFinal private String expectedTypeMessage; - MetaCheckNode(String name, Object expectedMeta) { + MetaCheckNode(String name, CachingSupplier expectedMetaSupplier) { super(name); - this.expectedMeta = expectedMeta; + this.expectedSupplier = expectedMetaSupplier; } @Override @@ -493,7 +497,7 @@ Object verifyMetaObject(VirtualFrame frame, Object v, @Cached IsValueOfTypeNode if (isAllFitValue(v)) { return v; } - if (isA.execute(expectedMeta, v)) { + if (isA.execute(expectedSupplier.get(), v)) { return v; } else { return null; @@ -508,9 +512,9 @@ String expectedTypeMessage() { CompilerDirectives.transferToInterpreterAndInvalidate(); var iop = InteropLibrary.getUncached(); try { - expectedTypeMessage = iop.asString(iop.getMetaQualifiedName(expectedMeta)); + expectedTypeMessage = iop.asString(iop.getMetaQualifiedName(expectedSupplier.get())); } catch (UnsupportedMessageException ex) { - expectedTypeMessage = expectedMeta.toString(); + expectedTypeMessage = expectedSupplier.get().toString(); } return expectedTypeMessage; } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/ObjectEqualityBranchNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/ObjectEqualityBranchNode.java index b1d7cf9c1445..37b4d4f50df3 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/ObjectEqualityBranchNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/ObjectEqualityBranchNode.java @@ -3,25 +3,29 @@ import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.profiles.CountingConditionProfile; +import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.node.expression.builtin.meta.IsSameObjectNode; public class ObjectEqualityBranchNode extends BranchNode { - private final Object expected; + private @Child ExpressionNode expected; private @Child IsSameObjectNode isSameObject = IsSameObjectNode.build(); private final CountingConditionProfile profile = CountingConditionProfile.create(); - private ObjectEqualityBranchNode(RootCallTarget branch, Object expected, boolean terminalBranch) { + private ObjectEqualityBranchNode( + RootCallTarget branch, ExpressionNode expected, boolean terminalBranch) { super(branch, terminalBranch); this.expected = expected; } - public static BranchNode build(RootCallTarget branch, Object expected, boolean terminalBranch) { + public static BranchNode build( + RootCallTarget branch, ExpressionNode expected, boolean terminalBranch) { return new ObjectEqualityBranchNode(branch, expected, terminalBranch); } @Override public void execute(VirtualFrame frame, Object state, Object target) { - if (profile.profile(isSameObject.execute(target, expected))) { + var exp = expected.executeGeneric(frame); + if (profile.profile(isSameObject.execute(target, exp))) { accept(frame, state, new Object[0]); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/constant/ConstantObjectNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/constant/ConstantObjectNode.java index bbf9c8441e74..bf0e9ed7d84f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/constant/ConstantObjectNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/constant/ConstantObjectNode.java @@ -7,7 +7,7 @@ /** Represents a compile-time constant. */ @NodeInfo(shortName = "const", description = "Represents an arbitrary compile-time constant.") -public class ConstantObjectNode extends ExpressionNode { +public final class ConstantObjectNode extends ExpressionNode { private final Object object; private ConstantObjectNode(Object object) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/constant/LazyObjectNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/constant/LazyObjectNode.java new file mode 100644 index 000000000000..0c7dda52705d --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/constant/LazyObjectNode.java @@ -0,0 +1,44 @@ +package org.enso.interpreter.node.expression.constant; + +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.nodes.NodeInfo; +import java.util.function.Supplier; +import org.enso.interpreter.node.ExpressionNode; +import org.enso.interpreter.runtime.data.text.Text; +import org.enso.interpreter.runtime.error.DataflowError; +import org.enso.interpreter.runtime.util.CachingSupplier; + +@NodeInfo( + shortName = "lazy", + description = "Represents an arbitrary compile-time constant computed lazily.") +public final class LazyObjectNode extends ExpressionNode { + + private final String error; + private final CachingSupplier supply; + + private LazyObjectNode(String error, Supplier supply) { + this.error = error; + this.supply = CachingSupplier.wrap(supply); + } + + /** + * Creates a node that returns lazily computed value. + * + * @param errorMessage the error message to show when the value is {@code null} + * @param supplier computes the value lazily. Can return {@code null} and then the {@code + * errorMessage} error is created + */ + public static ExpressionNode build(String errorMessage, Supplier supplier) { + return new LazyObjectNode(errorMessage, supplier); + } + + @Override + public Object executeGeneric(VirtualFrame frame) { + var result = supply.get(); + if (result instanceof TruffleObject) { + return result; + } + return DataflowError.withDefaultTrace(Text.create(error), this); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java index be434fb65b59..72704cc4513c 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java @@ -13,6 +13,7 @@ import com.oracle.truffle.api.TruffleLogger; import com.oracle.truffle.api.interop.InteropException; import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.interop.UnknownIdentifierException; import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.io.TruffleProcessBuilder; @@ -51,7 +52,7 @@ import org.enso.interpreter.instrument.NotificationHandler; import org.enso.interpreter.runtime.builtin.Builtins; import org.enso.interpreter.runtime.data.Type; -import org.enso.interpreter.runtime.data.text.Text; +import org.enso.interpreter.runtime.error.DataflowError; import org.enso.interpreter.runtime.error.PanicException; import org.enso.interpreter.runtime.scope.TopLevelScope; import org.enso.interpreter.runtime.state.ExecutionEnvironment; @@ -553,10 +554,10 @@ public boolean isColorTerminalOutput() { * is looked up by iterating the members of the outer class via Truffle's interop protocol. * * @param className Fully qualified class name, can also be nested static inner class. - * @return If the java class is found, return it, otherwise return null. + * @return If the java class is found, return it, otherwise return {@link DataflowError}. */ @TruffleBoundary - public Object lookupJavaClass(String className) { + public TruffleObject lookupJavaClass(String className) { var binaryName = new StringBuilder(className); var collectedExceptions = new ArrayList(); for (; ; ) { @@ -564,7 +565,7 @@ public Object lookupJavaClass(String className) { try { var hostSymbol = lookupHostSymbol(fqn); if (hostSymbol != null) { - return hostSymbol; + return (TruffleObject) hostSymbol; } } catch (ClassNotFoundException | RuntimeException | InteropException ex) { collectedExceptions.add(ex); @@ -581,7 +582,7 @@ public Object lookupJavaClass(String className) { level = Level.FINE; logger.log(Level.FINE, null, ex); } - return null; + return getBuiltins().error().makeMissingPolyglotImportError(className); } private Object lookupHostSymbol(String fqn) @@ -971,8 +972,7 @@ public PanicException raiseAssertionPanic(Node node, String message, Throwable e if (message != null) { msg = msg + sep + message; } - var txt = Text.create(msg); - var err = getBuiltins().error().makeAssertionError(txt); + var err = getBuiltins().error().makeAssertionError(msg); throw new PanicException(err, e, node); } 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 7224e103e624..746908d52d3a 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 @@ -244,7 +244,7 @@ private Map>> registerBuiltinM constr -> { Map> atomNodes = getOrUpdate(builtinMethodNodes, constr.getName()); - atomNodes.put(builtinMethodName, new CachingSupplier<>(() -> meta.toMethod())); + atomNodes.put(builtinMethodName, CachingSupplier.wrap(() -> meta.toMethod())); Map atomNodesMeta = getOrUpdate(builtinMetaMethods, constr.getName()); @@ -253,7 +253,7 @@ private Map>> registerBuiltinM () -> { Map> atomNodes = getOrUpdate(builtinMethodNodes, builtinMethodOwner); - atomNodes.put(builtinMethodName, new CachingSupplier<>(() -> meta.toMethod())); + atomNodes.put(builtinMethodName, CachingSupplier.wrap(() -> meta.toMethod())); Map atomNodesMeta = getOrUpdate(builtinMetaMethods, builtinMethodOwner); @@ -420,12 +420,12 @@ private Map>> readBuiltinMetho constr -> { Map> atomNodes = getOrUpdate(methodNodes, constr.getName()); - atomNodes.put(builtinMethodName, new CachingSupplier<>(builtin)); + atomNodes.put(builtinMethodName, CachingSupplier.forValue(builtin)); }, () -> { Map> atomNodes = getOrUpdate(methodNodes, builtinMethodOwner); - atomNodes.put(builtinMethodName, new CachingSupplier<>(builtin)); + atomNodes.put(builtinMethodName, CachingSupplier.forValue(builtin)); }); }); return methodNodes; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java index 75419e8904cd..ace957b9e26d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java @@ -38,6 +38,7 @@ import org.enso.interpreter.runtime.data.atom.Atom; import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.data.vector.ArrayLikeHelpers; +import org.enso.interpreter.runtime.error.DataflowError; /** Container for builtin Error types */ public final class Error { @@ -110,16 +111,16 @@ public Error(Builtins builtins, EnsoContext context) { mapError = builtins.getBuiltinType(MapError.class); } - public Atom makeSyntaxError(Object message) { - return syntaxError.newInstance(message); + public Atom makeSyntaxError(String message) { + return syntaxError.newInstance(Text.create(message)); } - public Atom makeCompileError(Object message) { - return compileError.newInstance(message); + public Atom makeCompileError(String message) { + return compileError.newInstance(Text.create(message)); } - public Atom makeAssertionError(Text text) { - return assertionError.newInstance(text); + public Atom makeAssertionError(String text) { + return assertionError.newInstance(Text.create(text)); } public Atom makeIndexOutOfBounds(long index, long length) { @@ -350,4 +351,16 @@ public Atom makeNumberParseError(String message) { public Atom makeMapError(long index, Object innerError) { return mapError.newInstance(index, innerError); } + + /** + * Creates error on missing polyglot java import class. + * + * @param className the name of the class that is missing + * @return data flow error representing the missing value + */ + public DataflowError makeMissingPolyglotImportError(String className) { + var msg = "No polyglot symbol for " + className; + var err = makeCompileError(msg); + return DataflowError.withDefaultTrace(err, null); + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java index 181f4449ed38..c87e494e00b0 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java @@ -1,17 +1,25 @@ package org.enso.interpreter.runtime.error; +import static org.enso.interpreter.runtime.error.PanicException.handleExceptionMessage; + import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.TruffleStackTrace; import com.oracle.truffle.api.TruffleStackTraceElement; import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.exception.AbstractTruffleException; 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.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.nodes.Node; import java.util.Objects; +import org.enso.interpreter.node.callable.IndirectInvokeMethodNode; +import org.enso.interpreter.node.expression.builtin.text.util.TypeToDisplayTextNode; import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.data.EnsoObject; import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.data.vector.ArrayLikeHelpers; @@ -26,6 +34,7 @@ */ @ExportLibrary(InteropLibrary.class) @ExportLibrary(TypesLibrary.class) +@ImportStatic(PanicException.class) public final class DataflowError extends AbstractTruffleException implements EnsoObject { /** Signals (local) values that haven't yet been initialized */ public static final DataflowError UNINITIALIZED = new DataflowError(null, (Node) null); @@ -141,6 +150,21 @@ boolean isException() { return true; } + @ExportMessage + boolean hasExceptionMessage() { + return true; + } + + @ExportMessage + Object getExceptionMessage( + @Cached IndirectInvokeMethodNode payloads, + @Cached(value = "toDisplayText(payloads)", allowUncached = true) + UnresolvedSymbol toDisplayText, + @CachedLibrary(limit = "3") InteropLibrary strings, + @Cached TypeToDisplayTextNode typeToDisplayTextNode) { + return handleExceptionMessage(payload, payloads, toDisplayText, strings, typeToDisplayTextNode); + } + @ExportMessage boolean hasExceptionStackTrace() { return ownTrace; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicException.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicException.java index 2493789d0066..39f80e13bc27 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicException.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicException.java @@ -128,6 +128,15 @@ Object getExceptionMessage( UnresolvedSymbol toDisplayText, @CachedLibrary(limit = "3") InteropLibrary strings, @Cached TypeToDisplayTextNode typeToDisplayTextNode) { + return handleExceptionMessage(payload, payloads, toDisplayText, strings, typeToDisplayTextNode); + } + + static Object handleExceptionMessage( + Object payload, + IndirectInvokeMethodNode payloads, + UnresolvedSymbol toDisplayText, + InteropLibrary strings, + TypeToDisplayTextNode typeToDisplayTextNode) { var ctx = EnsoContext.get(payloads); var text = payloads.execute( diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java index f5de1e783971..7e6f85284688 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java @@ -1,12 +1,14 @@ package org.enso.interpreter.runtime.scope; import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; import java.util.*; import java.util.function.Supplier; import java.util.stream.Collectors; import org.enso.compiler.context.CompilerContext; +import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.Module; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.data.EnsoObject; @@ -21,7 +23,7 @@ public final class ModuleScope implements EnsoObject { private final Type associatedType; private final Module module; - private final Map polyglotSymbols; + private final Map> polyglotSymbols; private final Map types; private final Map>> methods; @@ -43,7 +45,7 @@ public final class ModuleScope implements EnsoObject { public ModuleScope( Module module, Type associatedType, - Map polyglotSymbols, + Map> polyglotSymbols, Map types, Map>> methods, Map> conversions, @@ -257,10 +259,20 @@ public List getConversions() { } /** - * @return the polyglot symbol imported into this scope. + * Finds a polyglot symbol supplier. The supplier will then load the provided {@code symbolName} + * when its {@link Supplier#get()} method is called. + * + * @param symbolName name of the symbol to search for + * @return non-{@code null} supplier of a polyglot symbol imported into this scope */ - public Object getPolyglotSymbol(String symbolName) { - return polyglotSymbols.get(symbolName); + public Supplier getPolyglotSymbolSupplier(String symbolName) { + var supplier = polyglotSymbols.get(symbolName); + if (supplier != null) { + return supplier; + } + var ctx = EnsoContext.get(null); + var err = ctx.getBuiltins().error().makeMissingPolyglotImportError(symbolName); + return CachingSupplier.forValue(err); } @ExportMessage @@ -283,7 +295,7 @@ public static class Builder { @CompilerDirectives.CompilationFinal private ModuleScope moduleScope = null; private final Module module; private final Type associatedType; - private final Map polyglotSymbols; + private final Map> polyglotSymbols; private final Map types; private final Map>> methods; private final Map> conversions; @@ -315,7 +327,7 @@ public Builder(Module module, Map types) { public Builder( Module module, Type associatedType, - Map polyglotSymbols, + Map> polyglotSymbols, Map types, Map>> methods, Map> conversions, @@ -364,7 +376,7 @@ public void registerMethod(Type type, String method, Function function) { if (methodMap.containsKey(method) && !type.isBuiltin()) { throw new RedefinedMethodException(type.getName(), method); } else { - methodMap.put(method, new CachingSupplier<>(function)); + methodMap.put(method, CachingSupplier.forValue(function)); } } @@ -384,7 +396,7 @@ public void registerMethod(Type type, String method, Supplier supply) if (methodMap.containsKey(method) && !type.isBuiltin()) { throw new RedefinedMethodException(type.getName(), method); } else { - methodMap.put(method, new CachingSupplier<>(supply)); + methodMap.put(method, CachingSupplier.wrap(supply)); } } @@ -409,11 +421,11 @@ public void registerConversionMethod(Type toType, Type fromType, Function functi * Registers a new symbol in the polyglot namespace. * * @param name the name of the symbol - * @param sym the value being exposed + * @param symbolFactory the value being exposed */ - public void registerPolyglotSymbol(String name, Object sym) { + public void registerPolyglotSymbol(String name, Supplier symbolFactory) { assert moduleScope == null; - polyglotSymbols.put(name, sym); + polyglotSymbols.put(name, CachingSupplier.wrap(symbolFactory)); } /** diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/util/CachingSupplier.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/util/CachingSupplier.java index 8526c3b75fe1..4f5f17d287c6 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/util/CachingSupplier.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/util/CachingSupplier.java @@ -1,25 +1,69 @@ package org.enso.interpreter.runtime.util; +import com.oracle.truffle.api.CompilerDirectives; import java.util.function.Supplier; public final class CachingSupplier implements Supplier { + @SuppressWarnings("unchecked") + private static final Supplier EMPTY = new CachingSupplier(null); + private final Supplier supply; - private T memo; + @CompilerDirectives.CompilationFinal private boolean memoComputed; + @CompilerDirectives.CompilationFinal private T memo; - public CachingSupplier(Supplier supply) { + private CachingSupplier(Supplier supply) { this.supply = supply; } - public CachingSupplier(T memo) { + private CachingSupplier(T memo) { this.supply = null; this.memo = memo; + this.memoComputed = true; + } + + public static CachingSupplier wrap(Supplier supply) { + if (supply instanceof CachingSupplier cs) { + return cs; + } else { + return new CachingSupplier<>(supply); + } + } + + public static CachingSupplier forValue(V value) { + return new CachingSupplier<>(value); } @Override public T get() { - if (memo == null) { - memo = supply.get(); + synchronized (this) { + if (memoComputed) { + return memo; + } + CompilerDirectives.transferToInterpreterAndInvalidate(); + if (supply == null) { + memoComputed = true; + return memo; + } + } + var v = supply.get(); + synchronized (this) { + if (!memoComputed) { + memo = v; + memoComputed = true; + } + return memo; } - return memo; + } + + /** + * Returns a supplier that always returns {@code null} when its {@link Supplier#get()} method is + * called. + * + * @param type of value returned by the supplier + * @return non-{@code null} instance of supplier + */ + @SuppressWarnings("unchecked") + public static Supplier nullSupplier() { + return EMPTY; } } diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala index 808e8cc47f7e..b7103a4e70e7 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala @@ -102,7 +102,6 @@ import org.enso.interpreter.runtime.callable.{ Annotation => RuntimeAnnotation } import org.enso.interpreter.runtime.data.Type -import org.enso.interpreter.runtime.data.text.Text import org.enso.interpreter.runtime.scope.{ImportExportScope, ModuleScope} import org.enso.interpreter.{Constants, EnsoLanguage} @@ -112,7 +111,6 @@ import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.jdk.CollectionConverters._ import scala.jdk.OptionConverters._ -import org.enso.interpreter.runtime.error.DataflowError /** This is an implementation of a codegeneration pass that lowers the Enso * [[IR]] into the truffle structures that are actually executed. @@ -233,16 +231,12 @@ class IrToTruffle( private def registerPolyglotImports(module: Module): Unit = module.imports.foreach { case poly @ imports.Polyglot(i: imports.Polyglot.Java, _, _, _) => - var hostSymbol = context.lookupJavaClass(i.getJavaName) - if (hostSymbol == null) { - val err = Text.create( - s"Incorrect polyglot java import: ${i.getJavaName}" - ) - hostSymbol = DataflowError.withDefaultTrace(err, null) - } this.scopeBuilder.registerPolyglotSymbol( poly.getVisibleName, - hostSymbol + () => { + val hostSymbol = context.lookupJavaClass(i.getJavaName) + hostSymbol + } ) case _: Import.Module => case _: Error => @@ -902,7 +896,7 @@ class IrToTruffle( comment, asScope( mod.unsafeAsModule().asInstanceOf[TruffleCompilerContext.Module] - ).getPolyglotSymbol(symbol.name) + ).getPolyglotSymbolSupplier(symbol.name) ) case _ => null } @@ -1391,9 +1385,7 @@ class IrToTruffle( context.getBuiltins .error() .makeSyntaxError( - Text.create( - "Type operators are not currently supported at runtime" - ) + "Type operators are not currently supported at runtime" ) ), value.location @@ -1439,7 +1431,7 @@ class IrToTruffle( val error = context.getBuiltins .error() - .makeCompileError(Text.create(message)) + .makeCompileError(message) setLocation(ErrorNode.build(error), caseExpr.location) } @@ -1516,7 +1508,7 @@ class IrToTruffle( Right( ObjectEqualityBranchNode.build( branchCodeNode.getCallTarget, - asAssociatedType(mod.unsafeAsModule()), + LiteralNode.build(asAssociatedType(mod.unsafeAsModule())), branch.terminalBranch ) ) @@ -1564,7 +1556,7 @@ class IrToTruffle( } else { ObjectEqualityBranchNode.build( branchCodeNode.getCallTarget, - tpe, + LiteralNode.build(tpe), branch.terminalBranch ) } @@ -1575,13 +1567,14 @@ class IrToTruffle( ) ) => val polyglotSymbol = - asScope(mod.unsafeAsModule()).getPolyglotSymbol(symbol.name) + asScope(mod.unsafeAsModule()) + .getPolyglotSymbolSupplier(symbol.name) Either.cond( polyglotSymbol != null, ObjectEqualityBranchNode .build( branchCodeNode.getCallTarget, - polyglotSymbol, + LazyObjectNode.build(symbol.name, polyglotSymbol), branch.terminalBranch ), BadPatternMatch.NonVisiblePolyglotSymbol(symbol.name) @@ -1593,7 +1586,8 @@ class IrToTruffle( ) => val mod = typ.module val polyClass = asScope(mod.unsafeAsModule()) - .getPolyglotSymbol(typ.symbol.name) + .getPolyglotSymbolSupplier(typ.symbol.name) + .get() val polyValueOrError = if (polyClass == null) @@ -1627,7 +1621,7 @@ class IrToTruffle( ObjectEqualityBranchNode .build( branchCodeNode.getCallTarget, - polyValue, + ConstantObjectNode.build(polyValue), branch.terminalBranch ) }) @@ -1751,7 +1745,9 @@ class IrToTruffle( ) ) => val polySymbol = - asScope(mod.unsafeAsModule()).getPolyglotSymbol(symbol.name) + asScope(mod.unsafeAsModule()) + .getPolyglotSymbolSupplier(symbol.name) + .get() if (polySymbol != null) { val argOfType = List( new DefinitionArgument.Specified( @@ -2019,23 +2015,14 @@ class IrToTruffle( ) case BindingsMap.ResolvedPolyglotSymbol(module, symbol) => val s = - asScope(module.unsafeAsModule()).getPolyglotSymbol(symbol.name) - if (s == null) { - throw new CompilerError( - s"No polyglot symbol for ${symbol.name}" - ) - } - ConstantObjectNode.build(s) + asScope(module.unsafeAsModule()) + .getPolyglotSymbolSupplier(symbol.name) + LazyObjectNode.build(symbol.name, s) case BindingsMap.ResolvedPolyglotField(symbol, name) => val s = - asScope(symbol.module.unsafeAsModule()).getPolyglotSymbol(name) - if (s == null) { - throw new CompilerError( - s"No polyglot field for ${name}" - ) - } - - ConstantObjectNode.build(s) + asScope(symbol.module.unsafeAsModule()) + .getPolyglotSymbolSupplier(name) + LazyObjectNode.build(name, s) case BindingsMap.ResolvedModuleMethod(_, method) => throw new CompilerError( s"Impossible here, module method ${method.name} should be caught when translating application" @@ -2091,47 +2078,47 @@ class IrToTruffle( case err: errors.Syntax => context.getBuiltins .error() - .makeSyntaxError(Text.create(err.message(fileLocationFromSection))) + .makeSyntaxError(err.message(fileLocationFromSection)) case err: errors.Redefined.Binding => context.getBuiltins .error() - .makeCompileError(Text.create(err.message(fileLocationFromSection))) + .makeCompileError(err.message(fileLocationFromSection)) case err: errors.Redefined.Method => context.getBuiltins .error() - .makeCompileError(Text.create(err.message(fileLocationFromSection))) + .makeCompileError(err.message(fileLocationFromSection)) case err: errors.Redefined.MethodClashWithAtom => context.getBuiltins .error() - .makeCompileError(Text.create(err.message(fileLocationFromSection))) + .makeCompileError(err.message(fileLocationFromSection)) case err: errors.Redefined.Conversion => context.getBuiltins .error() - .makeCompileError(Text.create(err.message(fileLocationFromSection))) + .makeCompileError(err.message(fileLocationFromSection)) case err: errors.Redefined.Type => context.getBuiltins .error() - .makeCompileError(Text.create(err.message(fileLocationFromSection))) + .makeCompileError(err.message(fileLocationFromSection)) case err: errors.Redefined.SelfArg => context.getBuiltins .error() - .makeCompileError(Text.create(err.message(fileLocationFromSection))) + .makeCompileError(err.message(fileLocationFromSection)) case err: errors.Redefined.Arg => context.getBuiltins .error() - .makeCompileError(Text.create(err.message(fileLocationFromSection))) + .makeCompileError(err.message(fileLocationFromSection)) case err: errors.Unexpected.TypeSignature => context.getBuiltins .error() - .makeCompileError(Text.create(err.message(fileLocationFromSection))) + .makeCompileError(err.message(fileLocationFromSection)) case err: errors.Resolution => context.getBuiltins .error() - .makeCompileError(Text.create(err.message(fileLocationFromSection))) + .makeCompileError(err.message(fileLocationFromSection)) case err: errors.Conversion => context.getBuiltins .error() - .makeCompileError(Text.create(err.message(fileLocationFromSection))) + .makeCompileError(err.message(fileLocationFromSection)) case _: errors.Pattern => throw new CompilerError( "Impossible here, should be handled in the pattern match." @@ -2149,7 +2136,7 @@ class IrToTruffle( * @return the Nothing builtin */ private def processEmpty(): RuntimeExpression = { - ConstantObjectNode.build(context.getBuiltins.nothing()) + LiteralNode.build(context.getBuiltins.nothing()) } /** Processes function arguments, generates arguments reads and creates @@ -2362,9 +2349,7 @@ class IrToTruffle( context.getBuiltins .error() .makeSyntaxError( - Text.create( - "Typeset literals are not yet supported at runtime" - ) + "Typeset literals are not yet supported at runtime" ) ), application.location diff --git a/test/Benchmarks/src/Startup/Import_World.enso b/test/Benchmarks/src/Startup/Import_World.enso new file mode 100644 index 000000000000..4fd46858d42e --- /dev/null +++ b/test/Benchmarks/src/Startup/Import_World.enso @@ -0,0 +1,9 @@ +from Standard.Base import all +from Standard.Table import all +from Standard.Database import all +from Standard.AWS import all +from Standard.Google_Api import all +from Standard.Snowflake import all +import Standard.Visualization + +main = IO.println "Hello World" diff --git a/test/Benchmarks/src/Startup/Startup.enso b/test/Benchmarks/src/Startup/Startup.enso index 36947a0587ea..6d2ab0d5fdc9 100644 --- a/test/Benchmarks/src/Startup/Startup.enso +++ b/test/Benchmarks/src/Startup/Startup.enso @@ -6,7 +6,7 @@ polyglot java import java.lang.System as Java_System polyglot java import java.io.File as Java_File type Data - Value ~enso_bin:File ~empty_world:File ~hello_world:File + Value ~enso_bin:File ~empty_world:File ~hello_world:File ~import_world:File bench_empty self = self.startup [ "--run", self.empty_world.to_text ] @@ -14,6 +14,9 @@ type Data bench_hello self = self.startup [ "--run", self.hello_world.to_text ] + bench_import self = + self.startup [ "--run", self.import_world.to_text ] + startup self args = exe = self.enso_bin result = Process.run exe.path args @@ -34,11 +37,12 @@ collect_benches = Bench.build builder-> data = - Data.Value enso_bin (find_sibling "Empty_World.enso") (find_sibling "Hello_World.enso") + Data.Value enso_bin (find_sibling "Empty_World.enso") (find_sibling "Hello_World.enso") (find_sibling "Import_World.enso") builder.group "Startup" options group_builder-> group_builder.specify "empty_startup" data.bench_empty group_builder.specify "hello_world_startup" data.bench_hello + group_builder.specify "import_world_startup" data.bench_import find_sibling name = f = enso_project.root / "src" / "Startup" / name