From 1c47bd2786db14c06d95329e40e75764ce1a20b2 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Sat, 4 Dec 2021 06:06:17 +0100 Subject: [PATCH 01/13] Node.replace should not initialize call target. --- .../src/com/oracle/truffle/api/nodes/Node.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java index a36dc01c4ed3..5c95a74c0346 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java @@ -419,7 +419,7 @@ private void reportReplace(Node oldNode, Node newNode, CharSequence reason) { } else if (node instanceof BytecodeOSRNode) { NodeAccessor.RUNTIME.onOSRNodeReplaced((BytecodeOSRNode) node, oldNode, newNode, reason); } else if (node instanceof RootNode) { - CallTarget target = ((RootNode) node).getCallTarget(); + CallTarget target = ((RootNode) node).getCallTargetWithoutInitialization(); if (target instanceof ReplaceObserver) { consumed = ((ReplaceObserver) target).nodeReplaced(oldNode, newNode, reason); } From 1d6675eca54b124747e0e1a803debbde3bd40a9c Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Sat, 4 Dec 2021 18:22:55 +0100 Subject: [PATCH 02/13] Assign call target before invoking onLoad hook to protect against recursive getCallTarget(). --- .../truffle/runtime/GraalRuntimeSupport.java | 15 +++++++++++-- .../truffle/runtime/OptimizedCallTarget.java | 10 +++++++++ .../com/oracle/truffle/api/impl/Accessor.java | 4 ++++ .../truffle/api/impl/DefaultCallTarget.java | 9 ++++++++ .../api/impl/DefaultRuntimeAccessor.java | 22 ++++++++++++++----- .../oracle/truffle/api/nodes/RootNode.java | 4 +++- 6 files changed, 55 insertions(+), 9 deletions(-) diff --git a/compiler/src/org.graalvm.compiler.truffle.runtime/src/org/graalvm/compiler/truffle/runtime/GraalRuntimeSupport.java b/compiler/src/org.graalvm.compiler.truffle.runtime/src/org/graalvm/compiler/truffle/runtime/GraalRuntimeSupport.java index 8cb3ace8cd48..f6ebab56eb95 100644 --- a/compiler/src/org.graalvm.compiler.truffle.runtime/src/org/graalvm/compiler/truffle/runtime/GraalRuntimeSupport.java +++ b/compiler/src/org.graalvm.compiler.truffle.runtime/src/org/graalvm/compiler/truffle/runtime/GraalRuntimeSupport.java @@ -62,7 +62,18 @@ public RootCallTarget newCallTarget(CallTarget source, RootNode rootNode) { assert GraalRuntimeAccessor.NODES.getCallTargetWithoutInitialization(rootNode) == null : "CallTarget for root node already initialized."; CompilerAsserts.neverPartOfCompilation(); - OptimizedCallTarget target = GraalTruffleRuntime.getRuntime().createOptimizedCallTarget((OptimizedCallTarget) source, rootNode); + return GraalTruffleRuntime.getRuntime().createOptimizedCallTarget((OptimizedCallTarget) source, rootNode); + } + + @Override + public boolean isLoaded(CallTarget callTarget) { + return ((OptimizedCallTarget) callTarget).isLoaded(); + } + + @Override + public void notifyOnLoad(CallTarget callTarget) { + CompilerAsserts.neverPartOfCompilation(); + OptimizedCallTarget target = (OptimizedCallTarget) callTarget; GraalRuntimeAccessor.INSTRUMENT.onLoad(target.getRootNode()); if (target.engine.compileAOTOnCreate) { if (target.prepareForAOT()) { @@ -70,7 +81,7 @@ public RootCallTarget newCallTarget(CallTarget source, RootNode rootNode) { } } TruffleSplittingStrategy.newTargetCreated(target); - return target; + target.setLoaded(); } @ExplodeLoop diff --git a/compiler/src/org.graalvm.compiler.truffle.runtime/src/org/graalvm/compiler/truffle/runtime/OptimizedCallTarget.java b/compiler/src/org.graalvm.compiler.truffle.runtime/src/org/graalvm/compiler/truffle/runtime/OptimizedCallTarget.java index 6e0925c73cb8..bbf6c435a441 100644 --- a/compiler/src/org.graalvm.compiler.truffle.runtime/src/org/graalvm/compiler/truffle/runtime/OptimizedCallTarget.java +++ b/compiler/src/org.graalvm.compiler.truffle.runtime/src/org/graalvm/compiler/truffle/runtime/OptimizedCallTarget.java @@ -134,6 +134,7 @@ public abstract class OptimizedCallTarget implements CompilableTruffleAST, RootC /** Whether this call target was cloned, compiled or called. */ @CompilationFinal protected volatile boolean initialized; + @CompilationFinal private volatile boolean loaded; /** * The call threshold is counted up for each real call until it reaches a @@ -1630,6 +1631,15 @@ final void setNonTrivialNodeCount(int nonTrivialNodeCount) { this.cachedNonTrivialNodeCount = nonTrivialNodeCount; } + final boolean isLoaded() { + return loaded; + } + + final void setLoaded() { + CompilerAsserts.neverPartOfCompilation(); + this.loaded = true; + } + public final boolean prepareForAOT() { if (wasExecuted()) { throw new IllegalStateException("Cannot prepare for AOT if call target was already executed."); diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java index 4eff8ca253b3..d008ae8e0171 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java @@ -989,6 +989,10 @@ protected RuntimeSupport(Object permission) { public abstract RootCallTarget newCallTarget(CallTarget source, RootNode rootNode); + public abstract boolean isLoaded(CallTarget callTarget); + + public abstract void notifyOnLoad(CallTarget callTarget); + public ThreadLocalHandshake getThreadLocalHandshake() { return DefaultThreadLocalHandshake.SINGLETON; } diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultCallTarget.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultCallTarget.java index 27531c6b8cfe..0bc8dcf28e43 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultCallTarget.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultCallTarget.java @@ -59,6 +59,7 @@ public final class DefaultCallTarget implements RootCallTarget { public static final String CALL_BOUNDARY_METHOD = "callDirectOrIndirect"; private final RootNode rootNode; private volatile boolean initialized; + private volatile boolean loaded; DefaultCallTarget(RootNode function) { this.rootNode = function; @@ -112,4 +113,12 @@ private void initialize() { } } } + + boolean isLoaded() { + return loaded; + } + + void setLoaded() { + this.loaded = true; + } } diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultRuntimeAccessor.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultRuntimeAccessor.java index 9627a6618d91..0daf13b5b6ff 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultRuntimeAccessor.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultRuntimeAccessor.java @@ -42,17 +42,17 @@ import java.util.function.Function; -import com.oracle.truffle.api.frame.Frame; -import com.oracle.truffle.api.frame.VirtualFrame; -import com.oracle.truffle.api.nodes.BytecodeOSRNode; import org.graalvm.options.OptionDescriptors; import org.graalvm.options.OptionValues; import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.TruffleLogger; +import com.oracle.truffle.api.frame.Frame; +import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.BlockNode; import com.oracle.truffle.api.nodes.BlockNode.ElementExecutor; +import com.oracle.truffle.api.nodes.BytecodeOSRNode; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; @@ -79,9 +79,19 @@ static final class DefaultRuntimeSupport extends RuntimeSupport { @Override public RootCallTarget newCallTarget(CallTarget sourceCallTarget, RootNode rootNode) { - DefaultCallTarget target = new DefaultCallTarget(rootNode); - DefaultRuntimeAccessor.INSTRUMENT.onLoad(rootNode); - return target; + return new DefaultCallTarget(rootNode); + } + + @Override + public boolean isLoaded(CallTarget callTarget) { + return ((DefaultCallTarget) callTarget).isLoaded(); + } + + @Override + public void notifyOnLoad(CallTarget callTarget) { + DefaultCallTarget target = (DefaultCallTarget) callTarget; + DefaultRuntimeAccessor.INSTRUMENT.onLoad(target.getRootNode()); + target.setLoaded(); } @Override diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/RootNode.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/RootNode.java index 48e724d8f799..1c3cc30fc2ac 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/RootNode.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/RootNode.java @@ -355,6 +355,7 @@ final RootNode cloneUninitializedImpl(CallTarget sourceCallTarget, RootNode unin } RootCallTarget clonedTarget = NodeAccessor.RUNTIME.newCallTarget(sourceCallTarget, clonedRoot); + NodeAccessor.RUNTIME.notifyOnLoad(clonedTarget); ReentrantLock l = clonedRoot.getLazyLock(); l.lock(); @@ -383,7 +384,7 @@ final RootNode cloneUninitializedImpl(CallTarget sourceCallTarget, RootNode unin /** @since 0.8 or earlier */ public final RootCallTarget getCallTarget() { RootCallTarget target = this.callTarget; - if (target == null) { + if (target == null || !NodeAccessor.RUNTIME.isLoaded(target)) { CompilerDirectives.transferToInterpreterAndInvalidate(); ReentrantLock l = getLazyLock(); l.lock(); @@ -391,6 +392,7 @@ public final RootCallTarget getCallTarget() { target = this.callTarget; if (target == null) { target = NodeAccessor.RUNTIME.newCallTarget(null, this); + NodeAccessor.RUNTIME.notifyOnLoad(target); if (callTarget != null) { throw CompilerDirectives.shouldNotReachHere("callTarget was set by newCallTarget but should not"); } From d847574d63e058f047fdb1eb75c32c9962852f2b Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Sat, 4 Dec 2021 16:29:05 +0100 Subject: [PATCH 03/13] Change default NodeLibrary exports to only provide a scope if the node is instrumentable. --- .../api/interop/DefaultNodeExports.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java b/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java index 5ce7de8206e5..27c223e96ae4 100644 --- a/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java +++ b/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java @@ -62,23 +62,31 @@ @SuppressWarnings("static-method") final class DefaultNodeExports { + @TruffleBoundary @ExportMessage @SuppressWarnings("unused") static boolean hasScope(Node node, Frame frame) { - RootNode root = node.getRootNode(); - TruffleLanguage language = InteropAccessor.NODES.getLanguage(root); - return language != null; + if (InteropAccessor.ACCESSOR.instrumentSupport().isInstrumentable(node)) { + RootNode root = node.getRootNode(); + TruffleLanguage language = InteropAccessor.NODES.getLanguage(root); + return language != null; + } else { + return false; + } } + @TruffleBoundary @ExportMessage @SuppressWarnings({"unchecked", "unused"}) static Object getScope(Node node, Frame frame, boolean nodeEnter) throws UnsupportedMessageException { - RootNode root = node.getRootNode(); - TruffleLanguage language = InteropAccessor.NODES.getLanguage(root); - if (language == null) { - throw UnsupportedMessageException.create(); + if (InteropAccessor.ACCESSOR.instrumentSupport().isInstrumentable(node)) { + RootNode root = node.getRootNode(); + TruffleLanguage language = InteropAccessor.NODES.getLanguage(root); + if (language != null) { + return createDefaultScope(root, frame, (Class>) language.getClass()); + } } - return createDefaultScope(root, frame, (Class>) language.getClass()); + throw UnsupportedMessageException.create(); } private static boolean isInternal(Object identifier) { From 550a3736c022cf75db43d88e0417ef05a5a0c8ef Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Sat, 4 Dec 2021 18:37:16 +0100 Subject: [PATCH 04/13] Provide DefaultScope also for RootNodes. --- .../api/interop/DefaultNodeExports.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java b/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java index 27c223e96ae4..3e2533fc17e4 100644 --- a/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java +++ b/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java @@ -66,25 +66,19 @@ final class DefaultNodeExports { @ExportMessage @SuppressWarnings("unused") static boolean hasScope(Node node, Frame frame) { - if (InteropAccessor.ACCESSOR.instrumentSupport().isInstrumentable(node)) { - RootNode root = node.getRootNode(); - TruffleLanguage language = InteropAccessor.NODES.getLanguage(root); - return language != null; - } else { - return false; - } + RootNode root = node.getRootNode(); + TruffleLanguage language = InteropAccessor.NODES.getLanguage(root); + return language != null && (node == root || InteropAccessor.ACCESSOR.instrumentSupport().isInstrumentable(node)); } @TruffleBoundary @ExportMessage @SuppressWarnings({"unchecked", "unused"}) static Object getScope(Node node, Frame frame, boolean nodeEnter) throws UnsupportedMessageException { - if (InteropAccessor.ACCESSOR.instrumentSupport().isInstrumentable(node)) { - RootNode root = node.getRootNode(); - TruffleLanguage language = InteropAccessor.NODES.getLanguage(root); - if (language != null) { - return createDefaultScope(root, frame, (Class>) language.getClass()); - } + RootNode root = node.getRootNode(); + TruffleLanguage language = InteropAccessor.NODES.getLanguage(root); + if (language != null && (node == root || InteropAccessor.ACCESSOR.instrumentSupport().isInstrumentable(node))) { + return createDefaultScope(root, frame, (Class>) language.getClass()); } throw UnsupportedMessageException.create(); } From 66aa632808abc307725ab3d5fecf770eb978242b Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Sat, 4 Dec 2021 16:11:52 +0100 Subject: [PATCH 05/13] Add indexed slots to DefaultScope. --- .../api/interop/DefaultNodeExports.java | 57 ++++++++++++++----- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java b/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java index 3e2533fc17e4..902a836d0ddc 100644 --- a/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java +++ b/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java @@ -96,30 +96,55 @@ private static boolean isInternal(Object identifier) { @SuppressWarnings("deprecation") @TruffleBoundary private static Object createDefaultScope(RootNode root, Frame frame, Class> language) { - LinkedHashMap slotsMap = new LinkedHashMap<>(); + LinkedHashMap slotsMap = new LinkedHashMap<>(); FrameDescriptor descriptor = frame == null ? root.getFrameDescriptor() : frame.getFrameDescriptor(); for (com.oracle.truffle.api.frame.FrameSlot slot : descriptor.getSlots()) { if (!isInternal(slot.getIdentifier()) && (frame == null || InteropLibrary.isValidValue(frame.getValue(slot)))) { - slotsMap.put(Objects.toString(slot.getIdentifier()), slot); + slotsMap.put(Objects.toString(slot.getIdentifier()), new Slot(slot)); + } + } + for (int slot = 0; slot < descriptor.getNumberOfSlots(); slot++) { + Object identifier = descriptor.getSlotName(slot); + if (!isInternal(identifier) && (frame == null || InteropLibrary.isValidValue(frame.getValue(slot)))) { + slotsMap.put(Objects.toString(identifier), new Slot(slot, false)); } } for (Map.Entry entry : descriptor.getAuxiliarySlots().entrySet()) { if (!isInternal(entry.getKey()) && (frame == null || InteropLibrary.isValidValue(frame.getAuxiliarySlot(entry.getValue())))) { - slotsMap.put(Objects.toString(entry.getKey()), entry.getValue()); + slotsMap.put(Objects.toString(entry.getKey()), new Slot(entry.getValue(), true)); } } return new DefaultScope(slotsMap, root, frame, language); } + @SuppressWarnings("deprecation") + private static final class Slot { + final int index; + final boolean auxiliary; + final com.oracle.truffle.api.frame.FrameSlot frameSlot; + + Slot(int index, boolean auxiliary) { + this.index = index; + this.auxiliary = auxiliary; + this.frameSlot = null; + } + + Slot(com.oracle.truffle.api.frame.FrameSlot frameSlot) { + this.index = -1; + this.auxiliary = false; + this.frameSlot = frameSlot; + } + } + @ExportLibrary(InteropLibrary.class) static final class DefaultScope implements TruffleObject { - private final Map slots; + private final Map slots; private final RootNode root; private final Frame frame; private final Class> language; - private DefaultScope(Map slots, RootNode root, Frame frame, Class> language) { + private DefaultScope(Map slots, RootNode root, Frame frame, Class> language) { this.slots = slots; this.root = root; this.frame = frame; @@ -160,14 +185,16 @@ Object readMember(String member) throws UnknownIdentifierException { if (frame == null) { return DefaultScopeNull.INSTANCE; } - Object slot = slots.get(member); + Slot slot = slots.get(member); if (slot == null) { throw UnknownIdentifierException.create(member); } else { - if (slot instanceof com.oracle.truffle.api.frame.FrameSlot) { - return frame.getValue((com.oracle.truffle.api.frame.FrameSlot) slot); + if (slot.frameSlot != null) { + return frame.getValue(slot.frameSlot); + } else if (slot.auxiliary) { + return frame.getAuxiliarySlot(slot.index); } else { - return frame.getAuxiliarySlot((int) slot); + return frame.getValue(slot.index); } } } @@ -187,7 +214,7 @@ boolean isMemberReadable(String member) { @ExportMessage @TruffleBoundary boolean isMemberModifiable(String member) { - return slots.containsKey(member) && frame != null; + return frame != null && slots.containsKey(member); } @SuppressWarnings("deprecation") @@ -197,14 +224,16 @@ void writeMember(String member, Object value) throws UnknownIdentifierException, if (frame == null) { throw UnsupportedMessageException.create(); } - Object slot = slots.get(member); + Slot slot = slots.get(member); if (slot == null) { throw UnknownIdentifierException.create(member); } else { - if (slot instanceof com.oracle.truffle.api.frame.FrameSlot) { - frame.setObject((com.oracle.truffle.api.frame.FrameSlot) slot, value); + if (slot.frameSlot != null) { + frame.setObject(slot.frameSlot, value); + } else if (slot.auxiliary) { + frame.setAuxiliarySlot(slot.index, value); } else { - frame.setAuxiliarySlot((int) slot, value); + frame.setObject(slot.index, value); } } } From 12e5cab751ebd7e3191a9385436d8e31aa10b53b Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Sat, 4 Dec 2021 17:21:01 +0100 Subject: [PATCH 06/13] Revert "Add indexed slots to DefaultScope." --- .../api/interop/DefaultNodeExports.java | 57 +++++-------------- 1 file changed, 14 insertions(+), 43 deletions(-) diff --git a/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java b/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java index 902a836d0ddc..3e2533fc17e4 100644 --- a/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java +++ b/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java @@ -96,55 +96,30 @@ private static boolean isInternal(Object identifier) { @SuppressWarnings("deprecation") @TruffleBoundary private static Object createDefaultScope(RootNode root, Frame frame, Class> language) { - LinkedHashMap slotsMap = new LinkedHashMap<>(); + LinkedHashMap slotsMap = new LinkedHashMap<>(); FrameDescriptor descriptor = frame == null ? root.getFrameDescriptor() : frame.getFrameDescriptor(); for (com.oracle.truffle.api.frame.FrameSlot slot : descriptor.getSlots()) { if (!isInternal(slot.getIdentifier()) && (frame == null || InteropLibrary.isValidValue(frame.getValue(slot)))) { - slotsMap.put(Objects.toString(slot.getIdentifier()), new Slot(slot)); - } - } - for (int slot = 0; slot < descriptor.getNumberOfSlots(); slot++) { - Object identifier = descriptor.getSlotName(slot); - if (!isInternal(identifier) && (frame == null || InteropLibrary.isValidValue(frame.getValue(slot)))) { - slotsMap.put(Objects.toString(identifier), new Slot(slot, false)); + slotsMap.put(Objects.toString(slot.getIdentifier()), slot); } } for (Map.Entry entry : descriptor.getAuxiliarySlots().entrySet()) { if (!isInternal(entry.getKey()) && (frame == null || InteropLibrary.isValidValue(frame.getAuxiliarySlot(entry.getValue())))) { - slotsMap.put(Objects.toString(entry.getKey()), new Slot(entry.getValue(), true)); + slotsMap.put(Objects.toString(entry.getKey()), entry.getValue()); } } return new DefaultScope(slotsMap, root, frame, language); } - @SuppressWarnings("deprecation") - private static final class Slot { - final int index; - final boolean auxiliary; - final com.oracle.truffle.api.frame.FrameSlot frameSlot; - - Slot(int index, boolean auxiliary) { - this.index = index; - this.auxiliary = auxiliary; - this.frameSlot = null; - } - - Slot(com.oracle.truffle.api.frame.FrameSlot frameSlot) { - this.index = -1; - this.auxiliary = false; - this.frameSlot = frameSlot; - } - } - @ExportLibrary(InteropLibrary.class) static final class DefaultScope implements TruffleObject { - private final Map slots; + private final Map slots; private final RootNode root; private final Frame frame; private final Class> language; - private DefaultScope(Map slots, RootNode root, Frame frame, Class> language) { + private DefaultScope(Map slots, RootNode root, Frame frame, Class> language) { this.slots = slots; this.root = root; this.frame = frame; @@ -185,16 +160,14 @@ Object readMember(String member) throws UnknownIdentifierException { if (frame == null) { return DefaultScopeNull.INSTANCE; } - Slot slot = slots.get(member); + Object slot = slots.get(member); if (slot == null) { throw UnknownIdentifierException.create(member); } else { - if (slot.frameSlot != null) { - return frame.getValue(slot.frameSlot); - } else if (slot.auxiliary) { - return frame.getAuxiliarySlot(slot.index); + if (slot instanceof com.oracle.truffle.api.frame.FrameSlot) { + return frame.getValue((com.oracle.truffle.api.frame.FrameSlot) slot); } else { - return frame.getValue(slot.index); + return frame.getAuxiliarySlot((int) slot); } } } @@ -214,7 +187,7 @@ boolean isMemberReadable(String member) { @ExportMessage @TruffleBoundary boolean isMemberModifiable(String member) { - return frame != null && slots.containsKey(member); + return slots.containsKey(member) && frame != null; } @SuppressWarnings("deprecation") @@ -224,16 +197,14 @@ void writeMember(String member, Object value) throws UnknownIdentifierException, if (frame == null) { throw UnsupportedMessageException.create(); } - Slot slot = slots.get(member); + Object slot = slots.get(member); if (slot == null) { throw UnknownIdentifierException.create(member); } else { - if (slot.frameSlot != null) { - frame.setObject(slot.frameSlot, value); - } else if (slot.auxiliary) { - frame.setAuxiliarySlot(slot.index, value); + if (slot instanceof com.oracle.truffle.api.frame.FrameSlot) { + frame.setObject((com.oracle.truffle.api.frame.FrameSlot) slot, value); } else { - frame.setObject(slot.index, value); + frame.setAuxiliarySlot((int) slot, value); } } } From ac08e01c7db1f467c59aa7789bf72a91a49988ae Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Sat, 4 Dec 2021 16:38:03 +0100 Subject: [PATCH 07/13] Insight: iterateFrames should get the scope from the instrumentable parent of the call node. --- .../truffle/tools/agentscript/impl/EventContextObject.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java index b66e0f70978c..1d46c7ef7202 100644 --- a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java +++ b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java @@ -29,6 +29,7 @@ import com.oracle.truffle.api.frame.Frame; import com.oracle.truffle.api.frame.FrameInstance; import com.oracle.truffle.api.instrumentation.EventContext; +import com.oracle.truffle.api.instrumentation.InstrumentableNode; import com.oracle.truffle.api.interop.ArityException; import com.oracle.truffle.api.interop.InteropException; import com.oracle.truffle.api.interop.InteropLibrary; @@ -128,9 +129,10 @@ private static Object iterateFrames(Object[] args, EventContextObject obj) throw return null; } final Frame frame = frameInstance.getFrame(FrameInstance.FrameAccess.READ_WRITE); - if (lib.hasScope(n, frame)) { + Node instrumentableNode = InstrumentableNode.findInstrumentableParent(n); + if (instrumentableNode != null && lib.hasScope(instrumentableNode, frame)) { try { - Object frameVars = lib.getScope(n, frame, false); + Object frameVars = lib.getScope(instrumentableNode, frame, false); Object ret = iop.execute(callback, location, frameVars); return iop.isNull(ret) ? null : ret; } catch (UnsupportedMessageException | UnsupportedTypeException | ArityException ex) { From 417258d343659e98ccf6178f3a79198f69ea7277 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Sat, 4 Dec 2021 16:55:26 +0100 Subject: [PATCH 08/13] Insight: Fix expected output of iterateFrames test. --- vm/tests/all/agentscript/iterateFrames.test | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/vm/tests/all/agentscript/iterateFrames.test b/vm/tests/all/agentscript/iterateFrames.test index 2e1c4ff0b089..04b16126ce0a 100644 --- a/vm/tests/all/agentscript/iterateFrames.test +++ b/vm/tests/all/agentscript/iterateFrames.test @@ -6,28 +6,41 @@ >[0] js --insight=iterateFrames.js fib.js dumping locals at fib (fib.js:8:3) n has value 1 + at fib (fib.js:8:3) this has value [object global] at fib (fib.js:10:15) n has value 2 + at fib (fib.js:10:15) this has value [object global] at fib (fib.js:10:15) n has value 3 + at fib (fib.js:10:15) this has value [object global] at fib (fib.js:10:15) n has value 4 + at fib (fib.js:10:15) this has value [object global] .*at.*main.*fib.js.*prefix has value Three is the result .*at.*main.*fib.js.*fib4 has value undefined -.*at.*program.*fib.js:.*return.*has value undefined +.*at.*main.*fib.js.*this has value .* +.*at.*program.*fib.js:.*this has value .* end of locals dumping locals at fib (fib.js:8:3) n has value 1 + at fib (fib.js:8:3) this has value [object global] at fib (fib.js:10:34) n has value 3 + at fib (fib.js:10:34) this has value [object global] at fib (fib.js:10:15) n has value 4 + at fib (fib.js:10:15) this has value [object global] .*at.*main.*fib.js.*prefix has value The result is three .*at.*main.*fib.js.*fib4 has value undefined -.*at.*program.*fib.js:.*return.*has value undefined +.*at.*main.*fib.js.*this has value .* +.*at.*program.*fib.js:.*this has value .* end of locals dumping locals at fib (fib.js:8:3) n has value 1 + at fib (fib.js:8:3) this has value [object global] at fib (fib.js:10:15) n has value 2 + at fib (fib.js:10:15) this has value [object global] at fib (fib.js:10:34) n has value 4 + at fib (fib.js:10:34) this has value [object global] .*at.*main.*fib.js.*prefix has value The result is three .*at.*main.*fib.js.*fib4 has value undefined -.*at.*program.*fib.js:.*return.*has value undefined +.*at.*main.*fib.js.*this has value .* +.*at.*program.*fib.js:.*this has value .* end of locals The result is three 3 > cat >err0.js From 6ab9ad83e9879e71d207d3edccf1df5e78958a93 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sat, 4 Dec 2021 19:24:32 +0100 Subject: [PATCH 09/13] Carefully set the RootNode#callTarget field before notifyOnLoad() * To ensure the 1 RootNode - 1 CallTarget relation is already there before running non-trivial code in notifyOnLoad(). --- .../oracle/truffle/api/nodes/RootNode.java | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/RootNode.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/RootNode.java index 1c3cc30fc2ac..b5499d672515 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/RootNode.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/RootNode.java @@ -355,15 +355,11 @@ final RootNode cloneUninitializedImpl(CallTarget sourceCallTarget, RootNode unin } RootCallTarget clonedTarget = NodeAccessor.RUNTIME.newCallTarget(sourceCallTarget, clonedRoot); - NodeAccessor.RUNTIME.notifyOnLoad(clonedTarget); ReentrantLock l = clonedRoot.getLazyLock(); l.lock(); try { - if (clonedRoot.callTarget != null) { - throw CompilerDirectives.shouldNotReachHere("callTarget not null. Was getCallTarget on the result of RootNode.cloneUninitialized called?"); - } - clonedRoot.callTarget = clonedTarget; + clonedRoot.setupCallTarget(clonedTarget, "callTarget not null. Was getCallTarget on the result of RootNode.cloneUninitialized called?"); } finally { l.unlock(); } @@ -384,6 +380,7 @@ final RootNode cloneUninitializedImpl(CallTarget sourceCallTarget, RootNode unin /** @since 0.8 or earlier */ public final RootCallTarget getCallTarget() { RootCallTarget target = this.callTarget; + // Check isLoaded to avoid returning a CallTarget before notifyOnLoad() is done if (target == null || !NodeAccessor.RUNTIME.isLoaded(target)) { CompilerDirectives.transferToInterpreterAndInvalidate(); ReentrantLock l = getLazyLock(); @@ -392,11 +389,7 @@ public final RootCallTarget getCallTarget() { target = this.callTarget; if (target == null) { target = NodeAccessor.RUNTIME.newCallTarget(null, this); - NodeAccessor.RUNTIME.notifyOnLoad(target); - if (callTarget != null) { - throw CompilerDirectives.shouldNotReachHere("callTarget was set by newCallTarget but should not"); - } - this.callTarget = target; + this.setupCallTarget(target, "callTarget was set by newCallTarget but should not"); } } finally { l.unlock(); @@ -405,6 +398,21 @@ public final RootCallTarget getCallTarget() { return target; } + private void setupCallTarget(RootCallTarget callTarget, String message) { + assert getLazyLock().isHeldByCurrentThread(); + + if (this.callTarget != null) { + throw CompilerDirectives.shouldNotReachHere(message); + } + this.callTarget = callTarget; + + // Call notifyOnLoad() after the callTarget field is set, so the invariant that if a + // CallTarget exists for a RootNode then that rootNode.callTarget points to the CallTarget + // always holds, and no matter what notifyOnLoad() does the 1-1 relation between the + // RootNode and CallTarget is already there. + NodeAccessor.RUNTIME.notifyOnLoad(callTarget); + } + final RootCallTarget getCallTargetWithoutInitialization() { return callTarget; } From 9b769f7bc876d2cd237b548d479e95ed30259850 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sat, 4 Dec 2021 19:35:19 +0100 Subject: [PATCH 10/13] Document the reason we skip the ReplaceObserver if there is no CallTarget yet --- .../src/com/oracle/truffle/api/ReplaceObserver.java | 3 ++- .../src/com/oracle/truffle/api/nodes/Node.java | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/ReplaceObserver.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/ReplaceObserver.java index 033374106cc5..d347af825b4b 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/ReplaceObserver.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/ReplaceObserver.java @@ -43,7 +43,8 @@ import com.oracle.truffle.api.nodes.Node; /** - * An observer that is notified whenever a child node is replaced. + * An observer that is notified whenever a child node is replaced. Note this is not called if + * the RootNode does not have a CallTarget yet. * * @since 0.8 or earlier */ diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java index 5c95a74c0346..473af443b44d 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java @@ -419,6 +419,8 @@ private void reportReplace(Node oldNode, Node newNode, CharSequence reason) { } else if (node instanceof BytecodeOSRNode) { NodeAccessor.RUNTIME.onOSRNodeReplaced((BytecodeOSRNode) node, oldNode, newNode, reason); } else if (node instanceof RootNode) { + // Avoid creating a CallTarget here if replace() is called before this RootNode has + // a CallTarget CallTarget target = ((RootNode) node).getCallTargetWithoutInitialization(); if (target instanceof ReplaceObserver) { consumed = ((ReplaceObserver) target).nodeReplaced(oldNode, newNode, reason); From e27bd000137a200794ba20f9ec840bd55e4ba62d Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Sun, 5 Dec 2021 02:36:34 +0100 Subject: [PATCH 11/13] Insight: iterateFrames only shows variables of the current frame. --- .../agentscript/impl/CurrentScopeView.java | 110 ++++++++++++++++++ .../agentscript/impl/EventContextObject.java | 4 +- 2 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/CurrentScopeView.java diff --git a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/CurrentScopeView.java b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/CurrentScopeView.java new file mode 100644 index 000000000000..69967ec94151 --- /dev/null +++ b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/CurrentScopeView.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.tools.agentscript.impl; + +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.InvalidArrayIndexException; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; + +/** + * Creates a view of the current execution scope, hiding members of the parent scope. + */ +@ExportLibrary(value = InteropLibrary.class, delegateTo = "scope") +final class CurrentScopeView implements TruffleObject { + + final Object scope; + + CurrentScopeView(Object scope) { + this.scope = scope; + } + + @ExportMessage + @SuppressWarnings("static-method") + boolean hasMembers() { + return true; + } + + @ExportMessage + Object getMembers(@SuppressWarnings("unused") boolean includeInternal, + @CachedLibrary("this.scope") InteropLibrary scopeLib, + @CachedLibrary(limit = "5") InteropLibrary parentScopeLib) throws UnsupportedMessageException { + Object allKeys = scopeLib.getMembers(scope); + if (scopeLib.hasScopeParent(scope)) { + Object parentScope = scopeLib.getScopeParent(scope); + Object parentKeys = parentScopeLib.getMembers(parentScope); + return new SubtractedKeys(allKeys, parentKeys); + } + return allKeys; + } + + @ExportLibrary(InteropLibrary.class) + static final class SubtractedKeys implements TruffleObject { + + final Object allKeys; + private final long allSize; + private final long removedSize; + + SubtractedKeys(Object allKeys, Object removedKeys) throws UnsupportedMessageException { + this.allKeys = allKeys; + this.allSize = InteropLibrary.getUncached().getArraySize(allKeys); + this.removedSize = InteropLibrary.getUncached().getArraySize(removedKeys); + } + + @ExportMessage + @SuppressWarnings("static-method") + boolean hasArrayElements() { + return true; + } + + @ExportMessage + long getArraySize() { + return allSize - removedSize; + } + + @ExportMessage + Object readArrayElement(long index, + @CachedLibrary("this.allKeys") InteropLibrary interop) throws InvalidArrayIndexException, UnsupportedMessageException { + if (0 <= index && index < getArraySize()) { + return interop.readArrayElement(allKeys, index); + } else { + throw InvalidArrayIndexException.create(index); + } + } + + @ExportMessage + boolean isArrayElementReadable(long index, + @CachedLibrary("this.allKeys") InteropLibrary interop) { + if (0 <= index && index < getArraySize()) { + return interop.isArrayElementReadable(allKeys, index); + } else { + return false; + } + } + } +} diff --git a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java index 1d46c7ef7202..aecafed8b0fa 100644 --- a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java +++ b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -132,7 +132,7 @@ private static Object iterateFrames(Object[] args, EventContextObject obj) throw Node instrumentableNode = InstrumentableNode.findInstrumentableParent(n); if (instrumentableNode != null && lib.hasScope(instrumentableNode, frame)) { try { - Object frameVars = lib.getScope(instrumentableNode, frame, false); + Object frameVars = new CurrentScopeView(lib.getScope(instrumentableNode, frame, false)); Object ret = iop.execute(callback, location, frameVars); return iop.isNull(ret) ? null : ret; } catch (UnsupportedMessageException | UnsupportedTypeException | ArityException ex) { From 8c52937bb5bb0dff08d154a8ac354115f2b0b33b Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Sun, 5 Dec 2021 02:45:48 +0100 Subject: [PATCH 12/13] Update copyright year. --- .../src/com/oracle/truffle/api/interop/DefaultNodeExports.java | 2 +- .../src/com/oracle/truffle/api/ReplaceObserver.java | 2 +- .../src/com/oracle/truffle/api/nodes/Node.java | 2 +- .../src/com/oracle/truffle/api/nodes/RootNode.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java b/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java index 3e2533fc17e4..d59d4619707b 100644 --- a/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java +++ b/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/DefaultNodeExports.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/ReplaceObserver.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/ReplaceObserver.java index d347af825b4b..03a1c01ddd70 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/ReplaceObserver.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/ReplaceObserver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java index 473af443b44d..99c0e9833301 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/RootNode.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/RootNode.java index b5499d672515..cf215b5c5da7 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/RootNode.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/RootNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 From 18c48fd5e4cfdcc80424ddae64167f3e2f084875 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Mon, 6 Dec 2021 03:43:16 +0100 Subject: [PATCH 13/13] Update CI overlay. --- graal-common.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graal-common.json b/graal-common.json index 6906de2bce94..23a7596c99e1 100644 --- a/graal-common.json +++ b/graal-common.json @@ -1,7 +1,7 @@ { "README": "This file contains definitions that are useful for the hocon and jsonnet CI files of the graal and graal-enterprise repositories.", "ci": { - "overlay": "031ab3f1c9d415abd5d889227a632d210221b3a5" + "overlay": "30806c62fe3527181448edf5716b4a151634262a" }, "mx_version" : "HEAD" }