diff --git a/src/de.hpi.swa.trufflesqueak.test/src/de/hpi/swa/trufflesqueak/test/tests.properties b/src/de.hpi.swa.trufflesqueak.test/src/de/hpi/swa/trufflesqueak/test/tests.properties index c075ccbba..3238663af 100644 --- a/src/de.hpi.swa.trufflesqueak.test/src/de/hpi/swa/trufflesqueak/test/tests.properties +++ b/src/de.hpi.swa.trufflesqueak.test/src/de/hpi/swa/trufflesqueak/test/tests.properties @@ -2611,6 +2611,7 @@ IntegerTest>>testSqrtFloor=passing IntegerTest>>testSqrtRem=passing IntegerTest>>testTwoComplementBitLogicWithCarry=passing IntegerTest>>testTwoComplementRightShift=passing +InteropTest>>testExceptionHandling=passing InteropTest>>testMetadataAPIs=passing InteropTest>>testSmalltalkDNU=passing diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/SqueakOptions.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/SqueakOptions.java index c742140d9..be13e9644 100644 --- a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/SqueakOptions.java +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/SqueakOptions.java @@ -27,7 +27,7 @@ public final class SqueakOptions { public static final OptionKey ImageArguments = new OptionKey<>(""); @Option(name = SqueakLanguageOptions.HEADLESS, category = OptionCategory.USER, stability = OptionStability.STABLE, help = SqueakLanguageOptions.HEADLESS_HELP)// - public static final OptionKey Headless = new OptionKey<>(false); + public static final OptionKey Headless = new OptionKey<>(true); @Option(name = SqueakLanguageOptions.INTERCEPT_MESSAGES, category = OptionCategory.INTERNAL, stability = OptionStability.EXPERIMENTAL, help = SqueakLanguageOptions.INTERCEPT_MESSAGES_HELP)// public static final OptionKey InterceptMessages = new OptionKey<>(""); diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/exceptions/SqueakExceptions.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/exceptions/SqueakExceptions.java index 769fcf54e..551e54800 100644 --- a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/exceptions/SqueakExceptions.java +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/exceptions/SqueakExceptions.java @@ -5,6 +5,7 @@ */ package de.hpi.swa.trufflesqueak.exceptions; +import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.TruffleException; import com.oracle.truffle.api.nodes.ControlFlowException; @@ -14,6 +15,7 @@ import de.hpi.swa.trufflesqueak.SqueakLanguage; import de.hpi.swa.trufflesqueak.model.NativeObject; import de.hpi.swa.trufflesqueak.model.PointersObject; +import de.hpi.swa.trufflesqueak.model.layout.ObjectLayouts.EXCEPTION; import de.hpi.swa.trufflesqueak.model.layout.ObjectLayouts.SYNTAX_ERROR_NOTIFICATION; import de.hpi.swa.trufflesqueak.nodes.AbstractNode; import de.hpi.swa.trufflesqueak.util.ArrayUtils; @@ -159,6 +161,42 @@ public boolean isExit() { } } + public static final class SqueakInteropException extends RuntimeException implements TruffleException { + private static final long serialVersionUID = 1L; + private final PointersObject squeakException; + + public SqueakInteropException(final PointersObject original) { + squeakException = original; + } + + @Override + public String getMessage() { + CompilerAsserts.neverPartOfCompilation(); + final Object messageText = squeakException.instVarAt0Slow(EXCEPTION.MESSAGE_TEXT); + if (messageText instanceof NativeObject && ((NativeObject) messageText).isString()) { + return ((NativeObject) messageText).asStringUnsafe(); + } else { + return squeakException.toString(); + } + } + + @SuppressWarnings("sync-override") + @Override + public Throwable fillInStackTrace() { + return this; + } + + @Override + public Object getExceptionObject() { + return squeakException; + } + + @Override + public Node getLocation() { + return null; + } + } + public static final class SqueakInterrupt extends RuntimeException implements TruffleException { private static final long serialVersionUID = 1L; diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/image/SqueakImageContext.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/image/SqueakImageContext.java index b48b9ccee..40614c87a 100644 --- a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/image/SqueakImageContext.java +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/image/SqueakImageContext.java @@ -138,6 +138,7 @@ public final class SqueakImageContext { public ContextObject lastSeenContext; @CompilationFinal private ClassObject compilerClass; + @CompilationFinal private ClassObject exceptionClass; @CompilationFinal private ClassObject parserClass; private PointersObject parserSharedInstance; @CompilationFinal private PointersObject scheduler; @@ -381,6 +382,14 @@ public void setCompilerClass(final ClassObject classObject) { compilerClass = classObject; } + public ClassObject getExceptionClass() { + if (exceptionClass == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + exceptionClass = (ClassObject) evaluate("Exception"); + } + return exceptionClass; + } + public ClassObject getParserClass() { return parserClass; } diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/model/PointersObject.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/model/PointersObject.java index 64263a745..80790542e 100644 --- a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/model/PointersObject.java +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/model/PointersObject.java @@ -6,7 +6,16 @@ package de.hpi.swa.trufflesqueak.model; import com.oracle.truffle.api.CompilerAsserts; - +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Cached.Shared; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; + +import de.hpi.swa.trufflesqueak.SqueakLanguage; +import de.hpi.swa.trufflesqueak.exceptions.SqueakExceptions.SqueakInteropException; import de.hpi.swa.trufflesqueak.image.SqueakImageChunk; import de.hpi.swa.trufflesqueak.image.SqueakImageContext; import de.hpi.swa.trufflesqueak.image.SqueakImageWriter; @@ -19,11 +28,13 @@ import de.hpi.swa.trufflesqueak.model.layout.ObjectLayouts.POINT; import de.hpi.swa.trufflesqueak.model.layout.ObjectLayouts.PROCESS; import de.hpi.swa.trufflesqueak.model.layout.ObjectLayouts.SPECIAL_OBJECT; +import de.hpi.swa.trufflesqueak.nodes.InheritsFromNode; import de.hpi.swa.trufflesqueak.nodes.accessing.AbstractPointersObjectNodes.AbstractPointersObjectReadNode; import de.hpi.swa.trufflesqueak.nodes.accessing.AbstractPointersObjectNodes.AbstractPointersObjectWriteNode; import de.hpi.swa.trufflesqueak.nodes.accessing.SqueakObjectIdentityNode; import de.hpi.swa.trufflesqueak.util.ObjectGraphUtils.ObjectTracer; +@ExportLibrary(InteropLibrary.class) public final class PointersObject extends AbstractPointersObject { public PointersObject(final SqueakImageContext image) { @@ -187,4 +198,26 @@ public String toString() { public void write(final SqueakImageWriter writer) { super.writeHeaderAndLayoutObjects(writer); } + + /* + * INTEROPERABILITY + */ + + @ExportMessage + protected static boolean isException(final PointersObject receiver, + @Shared("inheritsFromNode") @Cached final InheritsFromNode inheritsFromNode, + @CachedContext(SqueakLanguage.class) final SqueakImageContext image) { + return inheritsFromNode.execute(receiver, image.getExceptionClass()); + } + + @ExportMessage + protected static RuntimeException throwException(final PointersObject receiver, + @Shared("inheritsFromNode") @Cached final InheritsFromNode inheritsFromNode, + @CachedContext(SqueakLanguage.class) final SqueakImageContext image) throws UnsupportedMessageException { + if (isException(receiver, inheritsFromNode, image)) { + throw new SqueakInteropException(receiver); + } else { + throw UnsupportedMessageException.create(); + } + } } diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/InheritsFromNode.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/InheritsFromNode.java index 369eb9c53..8ff46c96a 100644 --- a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/InheritsFromNode.java +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/InheritsFromNode.java @@ -6,11 +6,13 @@ package de.hpi.swa.trufflesqueak.nodes; import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.Specialization; import de.hpi.swa.trufflesqueak.model.ClassObject; import de.hpi.swa.trufflesqueak.nodes.accessing.SqueakObjectClassNode; +@GenerateUncached public abstract class InheritsFromNode extends AbstractNode { protected static final int CACHE_SIZE = 3; diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/primitives/impl/ContextPrimitives.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/primitives/impl/ContextPrimitives.java index 714486fbf..7a53df847 100644 --- a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/primitives/impl/ContextPrimitives.java +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/primitives/impl/ContextPrimitives.java @@ -8,6 +8,7 @@ import java.util.List; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.GenerateNodeFactory; @@ -19,21 +20,27 @@ import com.oracle.truffle.api.frame.FrameInstanceVisitor; import com.oracle.truffle.api.nodes.NodeCost; import com.oracle.truffle.api.nodes.NodeInfo; +import com.oracle.truffle.api.nodes.RootNode; +import de.hpi.swa.trufflesqueak.image.SqueakImageContext; import de.hpi.swa.trufflesqueak.model.AbstractSqueakObject; +import de.hpi.swa.trufflesqueak.model.BooleanObject; import de.hpi.swa.trufflesqueak.model.CompiledCodeObject; +import de.hpi.swa.trufflesqueak.model.CompiledMethodObject; import de.hpi.swa.trufflesqueak.model.ContextObject; import de.hpi.swa.trufflesqueak.model.FrameMarker; import de.hpi.swa.trufflesqueak.model.NilObject; import de.hpi.swa.trufflesqueak.model.layout.ObjectLayouts.CONTEXT; import de.hpi.swa.trufflesqueak.nodes.accessing.ContextObjectNodes.ContextObjectReadNode; import de.hpi.swa.trufflesqueak.nodes.accessing.ContextObjectNodes.ContextObjectWriteNode; +import de.hpi.swa.trufflesqueak.nodes.bytecodes.MiscellaneousBytecodes.CallPrimitiveNode; import de.hpi.swa.trufflesqueak.nodes.primitives.AbstractPrimitiveFactoryHolder; import de.hpi.swa.trufflesqueak.nodes.primitives.AbstractPrimitiveNode; import de.hpi.swa.trufflesqueak.nodes.primitives.PrimitiveInterfaces.BinaryPrimitive; import de.hpi.swa.trufflesqueak.nodes.primitives.PrimitiveInterfaces.TernaryPrimitive; import de.hpi.swa.trufflesqueak.nodes.primitives.PrimitiveInterfaces.UnaryPrimitive; import de.hpi.swa.trufflesqueak.nodes.primitives.SqueakPrimitive; +import de.hpi.swa.trufflesqueak.shared.SqueakLanguageConfig; import de.hpi.swa.trufflesqueak.util.FrameAccess; public class ContextPrimitives extends AbstractPrimitiveFactoryHolder { @@ -192,6 +199,9 @@ private void terminateBetweenRecursively(final ContextObject start, final Contex @GenerateNodeFactory @SqueakPrimitive(indices = 197) protected abstract static class PrimNextHandlerContextNode extends AbstractPrimitiveNode implements UnaryPrimitive { + private ContextObject cachedContext; + + @TruffleBoundary @Specialization(guards = {"receiver.hasMaterializedSender()"}) protected static final AbstractSqueakObject findNext(final ContextObject receiver) { ContextObject context = receiver; @@ -210,14 +220,25 @@ protected static final AbstractSqueakObject findNext(final ContextObject receive } } + @TruffleBoundary @Specialization(guards = {"!receiver.hasMaterializedSender()"}) - protected static final AbstractSqueakObject findNextAvoidingMaterialization(final ContextObject receiver) { + protected final AbstractSqueakObject findNextAvoidingMaterialization(final ContextObject receiver) { final boolean[] foundMyself = new boolean[1]; final Object[] lastSender = new Object[1]; final ContextObject result = Truffle.getRuntime().iterateFrames(frameInstance -> { final Frame current = frameInstance.getFrame(FrameInstance.FrameAccess.READ_ONLY); if (!FrameAccess.isTruffleSqueakFrame(current)) { - return null; // Foreign frame cannot be handler. + final RootNode rootNode = ((RootCallTarget) frameInstance.getCallTarget()).getRootNode(); + if (rootNode.isInternal() || rootNode.getLanguageInfo().getId().equals(SqueakLanguageConfig.ID)) { + /* Skip internal and all other nodes that belong to TruffleSqueak. */ + return null; + } else { + /* + * Found a frame of another language. Stop here by returning the receiver + * context. This special case will be handled later on. + */ + return receiver; + } } final ContextObject context = FrameAccess.getContext(current); if (!foundMyself[0]) { @@ -237,7 +258,13 @@ protected static final AbstractSqueakObject findNextAvoidingMaterialization(fina } return null; }); - if (result == null) { + if (result == receiver) { + /* + * Foreign frame found during frame iteration. Inject a fake context which will + * throw the Smalltalk exception as polyglot exception. + */ + return getInteropExceptionThrowingContext(); + } else if (result == null) { if (!foundMyself[0]) { return findNext(receiver); // Fallback to other version. } @@ -250,6 +277,35 @@ protected static final AbstractSqueakObject findNextAvoidingMaterialization(fina return result; } } + + /** + * Returns a fake context for BlockClosure>>#on:do: that handles any exception and rethrows + * it through Interop. This allows Smalltalk exceptions to be thrown to other languages, so + * that they can catch them. The mechanism works essentially like this: + * + *
+         * [ ... ] on: Exception do: [ :e | Interop throwException: e exception ]
+         * 
+ * + * (see Context>>#handleSignal:) + */ + private ContextObject getInteropExceptionThrowingContext() { + if (cachedContext == null) { + final SqueakImageContext image = lookupContext(); + assert image.evaluate("Interop") != NilObject.SINGLETON : "Interop class must be present"; + final CompiledMethodObject onDoMethod = (CompiledMethodObject) image.evaluate("BlockClosure>>#on:do:"); + cachedContext = ContextObject.create(image, onDoMethod.getSqueakContextSize()); + cachedContext.setMethod(onDoMethod); + cachedContext.setReceiver(NilObject.SINGLETON); + cachedContext.atTempPut(0, image.evaluate("Exception")); + cachedContext.atTempPut(1, image.evaluate("[ :e | Interop throwException: e exception ]")); + cachedContext.atTempPut(2, BooleanObject.TRUE); + cachedContext.setInstructionPointer(CallPrimitiveNode.NUM_BYTECODES); + cachedContext.setStackPointer(onDoMethod.getNumTemps()); + cachedContext.removeSender(); + } + return cachedContext.shallowCopy(); + } } @NodeInfo(cost = NodeCost.NONE) diff --git a/src/image b/src/image index 54133c3b8..647f40bfd 160000 --- a/src/image +++ b/src/image @@ -1 +1 @@ -Subproject commit 54133c3b8f84256e3bf9ca47fc522199fe9010b8 +Subproject commit 647f40bfd16cb173b461586a31f53ae73f60886b