From c9999b1149e8cf87784ac886bcae340d93c5e713 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Fri, 2 Jun 2023 18:47:48 +0300 Subject: [PATCH] fix: method calls of partially applied constructors --- .../instrument/IdExecutionInstrument.java | 51 +++++++++++++---- .../test/instrument/RuntimeServerTest.scala | 56 ++++++++++++++++++- .../instrument/IdExecutionService.java | 32 ++++++++++- .../callable/atom/AtomConstructor.java | 5 +- 4 files changed, 127 insertions(+), 17 deletions(-) diff --git a/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/IdExecutionInstrument.java b/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/IdExecutionInstrument.java index 6635661346f7..2b7ca97d6816 100644 --- a/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/IdExecutionInstrument.java +++ b/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/IdExecutionInstrument.java @@ -12,14 +12,21 @@ import com.oracle.truffle.api.interop.InteropException; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.nodes.Node; + import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.UUID; +import java.util.Set; +import java.util.function.Consumer; + import org.enso.interpreter.instrument.execution.Timer; import org.enso.interpreter.instrument.profiling.ExecutionTime; import org.enso.interpreter.instrument.profiling.ProfilingInfo; +import org.enso.interpreter.node.ClosureRootNode; import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; +import org.enso.interpreter.node.expression.atom.QualifiedAccessorNode; import org.enso.interpreter.node.expression.builtin.meta.TypeOfNode; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.function.Function; @@ -28,14 +35,11 @@ import org.enso.interpreter.runtime.error.PanicException; import org.enso.interpreter.runtime.error.PanicSentinel; import org.enso.interpreter.runtime.state.State; +import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag; import org.enso.interpreter.runtime.tag.IdentifiedTag; import org.enso.interpreter.runtime.type.Constants; import org.enso.interpreter.runtime.Module; -import java.util.function.Consumer; -import org.enso.interpreter.node.ClosureRootNode; -import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag; - /** An instrument for getting values from AST-identified expressions. */ @TruffleInstrument.Registration( id = IdExecutionService.INSTRUMENT_ID, @@ -68,6 +72,7 @@ private static class IdEventNodeFactory implements ExecutionEventNodeFactory { private final UpdatesSynchronizationState syncState; private final UUID nextExecutionItem; private final Map calls = new HashMap<>(); + private final Set constructorCalls = new HashSet<>(); private final Timer timer; /** @@ -109,8 +114,20 @@ public IdEventNodeFactory( @Override public ExecutionEventNode create(EventContext context) { - return new IdExecutionEventNode(context, entryCallTarget, cache, methodCallsCache, syncState, - nextExecutionItem, calls, functionCallCallback, onComputedCallback, onCachedCallback, onExceptionalCallback, timer); + return new IdExecutionEventNode( + context, + entryCallTarget, + cache, + methodCallsCache, + syncState, + nextExecutionItem, + calls, + constructorCalls, + functionCallCallback, + onComputedCallback, + onCachedCallback, + onExceptionalCallback, + timer); } } @@ -127,6 +144,7 @@ private static class IdExecutionEventNode extends ExecutionEventNode { private final UpdatesSynchronizationState syncState; private final UUID nextExecutionItem; private final Map calls; + private final Set constructorCalls; private final Timer timer; private long nanoTimeElapsed = 0; private @Child TypeOfNode typeOfNode = TypeOfNode.build(); @@ -139,6 +157,8 @@ private static class IdExecutionEventNode extends ExecutionEventNode { * @param methodCallsCache the storage tracking the executed method calls. * @param syncState the synchronization state of runtime updates. * @param nextExecutionItem the next item scheduled for execution. + * @param calls the list of instrumented method calls. + * @param constructorCalls the list of instrumented constructor calls. * @param functionCallCallback the consumer of function call events. * @param onComputedCallback the consumer of the computed value events. * @param onCachedCallback the consumer of the cached value events. @@ -153,6 +173,7 @@ public IdExecutionEventNode( UpdatesSynchronizationState syncState, UUID nextExecutionItem, // The expression ID Map calls, + Set constructorCalls, Consumer functionCallCallback, Consumer onComputedCallback, Consumer onCachedCallback, @@ -162,6 +183,7 @@ public IdExecutionEventNode( this.entryCallTarget = entryCallTarget; this.cache = cache; this.calls = calls; + this.constructorCalls = constructorCalls; this.callsCache = methodCallsCache; this.syncState = syncState; this.nextExecutionItem = nextExecutionItem; @@ -256,7 +278,7 @@ private void onExpressionReturn(Object result, Node node, EventContext context) String resultType = typeOf(result); String cachedType = cache.getType(nodeId); - FunctionCallInfo call = functionCallInfoById(nodeId); + FunctionCallInfo call = functionCallInfoById(nodeId, result); FunctionCallInfo cachedCall = cache.getCall(nodeId); ProfilingInfo[] profilingInfo = new ProfilingInfo[] {new ExecutionTime(nanoTimeElapsed)}; @@ -302,14 +324,21 @@ private void passExpressionValueToCallback(ExpressionValue expressionValue) { } @CompilerDirectives.TruffleBoundary - private FunctionCallInfo functionCallInfoById(UUID nodeId) { + private FunctionCallInfo functionCallInfoById(UUID nodeId, Object result) { + if (constructorCalls.contains(nodeId) && result instanceof Function function) { + FunctionCallInfo call = calls.get(nodeId); + return new FunctionCallInfo(call, function); + } return calls.get(nodeId); } @CompilerDirectives.TruffleBoundary - private void onFunctionReturn(UUID nodeId, FunctionCallInstrumentationNode.FunctionCall result, EventContext context) throws ThreadDeath { - calls.put(nodeId, new FunctionCallInfo(result)); - functionCallCallback.accept(new ExpressionCall(nodeId, result)); + private void onFunctionReturn(UUID nodeId, FunctionCallInstrumentationNode.FunctionCall functionCall, EventContext context) throws ThreadDeath { + if (functionCall.getFunction().getCallTarget().getRootNode() instanceof QualifiedAccessorNode) { + constructorCalls.add(nodeId); + } + calls.put(nodeId, new FunctionCallInfo(functionCall)); + functionCallCallback.accept(new ExpressionCall(nodeId, functionCall)); // Return cached value after capturing the enterable function call in `functionCallCallback` Object cachedResult = cache.get(nodeId); if (cachedResult != null) { diff --git a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala index 7b78ad0d6c8f..a31576e7e76c 100644 --- a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala +++ b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala @@ -807,7 +807,6 @@ class RuntimeServerTest } it should "send method pointer updates of partially applied constructors" in { - pending val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" @@ -867,9 +866,62 @@ class RuntimeServerTest Vector(1) ) ), + TestMessages.update(contextId, id_x_2, "Enso_Test.Test.Main.T"), + context.executionComplete(contextId) + ) + } + + it should "send method pointer updates of partially applied constructors with default arguments" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + val metadata = new Metadata + val id_x_1 = metadata.addItem(39, 3, "aa") + + val code = + """type T + | A x=1 y=2 + | + |main = + | x_1 = T.A + | x_1 + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // open file + context.send( + Api.Request(Api.OpenFileNotification(mainFile, contents)) + ) + context.receiveNone shouldEqual None + + // push main + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) + ) + context.receiveN(4) should contain theSameElementsAs Seq( + Api.Response(Api.BackgroundJobsStartedNotification()), + Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - id_x_2, + id_x_1, "Enso_Test.Test.Main.T", Api.MethodCall( Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "A") diff --git a/engine/runtime/src/main/java/org/enso/interpreter/instrument/IdExecutionService.java b/engine/runtime/src/main/java/org/enso/interpreter/instrument/IdExecutionService.java index 8c6c2be623e8..ecea2b6e693f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/instrument/IdExecutionService.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/instrument/IdExecutionService.java @@ -4,7 +4,6 @@ import com.oracle.truffle.api.instrumentation.EventBinding; import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory; import com.oracle.truffle.api.nodes.RootNode; -import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; import java.util.UUID; @@ -16,9 +15,8 @@ import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; import org.enso.interpreter.node.expression.atom.QualifiedAccessorNode; import org.enso.interpreter.runtime.Module; -import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.atom.AtomConstructor; -import org.enso.interpreter.runtime.callable.function.FunctionSchema; +import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.data.Type; import org.enso.logger.masking.MaskedString; import org.enso.pkg.QualifiedName; @@ -243,6 +241,19 @@ public FunctionCallInfo(FunctionCallInstrumentationNode.FunctionCall call) { notAppliedArguments = collectNotAppliedArguments(call); } + /** + * Create a new function call info for partially applied constructors. + * + * @param call the function call returning the constructor function. + * @param function the constructor function creating the type instance. + */ + public FunctionCallInfo(FunctionCallInfo call, Function function) { + this.moduleName = call.moduleName; + this.typeName = call.typeName; + this.functionName = call.functionName; + this.notAppliedArguments = collectNotAppliedArguments(function); + } + @Override public boolean equals(Object o) { if (this == o) { @@ -303,5 +314,20 @@ private static int[] collectNotAppliedArguments(FunctionCallInstrumentationNode. return Arrays.copyOf(notAppliedArgs, notAppliedArgsSize); } + + private static int[] collectNotAppliedArguments(Function function) { + Object[] preAppliedArguments = function.getPreAppliedArguments(); + int[] notAppliedArgs = new int[preAppliedArguments.length]; + int notAppliedArgsSize = 0; + + for (int i = 0; i < preAppliedArguments.length; i++) { + if (preAppliedArguments[i] == null) { + notAppliedArgs[notAppliedArgsSize] = i; + notAppliedArgsSize += 1; + } + } + + return Arrays.copyOf(notAppliedArgs, notAppliedArgsSize); + } } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java index 87c29656dda8..c02da1f4a937 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java @@ -13,6 +13,8 @@ import com.oracle.truffle.api.source.SourceSection; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; + +import org.enso.interpreter.Constants; import org.enso.interpreter.EnsoLanguage; import org.enso.interpreter.node.ClosureRootNode; import org.enso.interpreter.node.ExpressionNode; @@ -181,7 +183,8 @@ private void generateQualifiedAccessor() { callTarget, null, new FunctionSchema( - new ArgumentDefinition(0, "self", ArgumentDefinition.ExecutionMode.EXECUTE))); + new ArgumentDefinition( + 0, Constants.Names.SELF_ARGUMENT, ArgumentDefinition.ExecutionMode.EXECUTE))); definitionScope.registerMethod(type.getEigentype(), this.name, function); }