Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report type of expressions returning polyglot values #4111

Merged
merged 10 commits into from
Feb 9, 2023
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,7 @@
- [Make instance methods callable like statics for builtin types][4077]
- [Convert large longs to doubles, safely, for host calls][4099]
- [Profile engine startup][4110]
- [Report type of polyglot values][4111]

[3227]: https://github.com/enso-org/enso/pull/3227
[3248]: https://github.com/enso-org/enso/pull/3248
Expand Down Expand Up @@ -646,6 +647,7 @@
[4077]: https://github.com/enso-org/enso/pull/4077
[4099]: https://github.com/enso-org/enso/pull/4099
[4110]: https://github.com/enso-org/enso/pull/4110
[4111]: https://github.com/enso-org/enso/pull/4111

# Enso 2.0.0-alpha.18 (2021-10-12)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@
import org.enso.interpreter.instrument.profiling.ProfilingInfo;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode;
import org.enso.interpreter.node.expression.builtin.meta.TypeOfNode;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.control.TailCallException;
import org.enso.interpreter.runtime.data.Type;
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.IdentifiedTag;
import org.enso.interpreter.runtime.type.Constants;
import org.enso.interpreter.runtime.type.Types;
import org.enso.interpreter.runtime.Module;

Expand All @@ -51,8 +55,68 @@ protected void onCreate(Env env) {
this.env = env;
}

/** The listener class used by this instrument. */
private static class IdExecutionEventListener implements ExecutionEventListener {
/** Factory for creating new id event nodes **/
private static class IdEventNodeFactory implements ExecutionEventNodeFactory {

private final CallTarget entryCallTarget;
private final Consumer<ExpressionCall> functionCallCallback;
private final Consumer<ExpressionValue> onComputedCallback;
private final Consumer<ExpressionValue> onCachedCallback;
private final Consumer<Exception> onExceptionalCallback;
private final RuntimeCache cache;
private final MethodCallsCache methodCallsCache;
private final UpdatesSynchronizationState syncState;
private final UUID nextExecutionItem;
private final Map<UUID, FunctionCallInfo> calls = new HashMap<>();
private final Timer timer;

/**
* Creates a new event node factory.
*
* @param entryCallTarget the call target being observed.
* @param cache the precomputed expression values.
* @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 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.
* @param onExceptionalCallback the consumer of the exceptional events.
* @param timer the timer for timing execution
*/
public IdEventNodeFactory(
CallTarget entryCallTarget,
RuntimeCache cache,
MethodCallsCache methodCallsCache,
UpdatesSynchronizationState syncState,
UUID nextExecutionItem, // The expression ID
Consumer<ExpressionCall> functionCallCallback,
Consumer<ExpressionValue> onComputedCallback,
Consumer<ExpressionValue> onCachedCallback,
Consumer<Exception> onExceptionalCallback,
Timer timer) {
this.entryCallTarget = entryCallTarget;
this.cache = cache;
this.methodCallsCache = methodCallsCache;
this.syncState = syncState;
this.nextExecutionItem = nextExecutionItem;
this.functionCallCallback = functionCallCallback;
this.onComputedCallback = onComputedCallback;
this.onCachedCallback = onCachedCallback;
this.onExceptionalCallback = onExceptionalCallback;
this.timer = timer;
}

@Override
public ExecutionEventNode create(EventContext context) {
return new IdExecutionEventNode(context, entryCallTarget, cache, methodCallsCache, syncState,
nextExecutionItem, calls, functionCallCallback, onComputedCallback, onCachedCallback, onExceptionalCallback, timer);
}
}

/** The execution event node class used by this instrument. */
private static class IdExecutionEventNode extends ExecutionEventNode {
private final EventContext context;
private final CallTarget entryCallTarget;
private final Consumer<ExpressionCall> functionCallCallback;
private final Consumer<ExpressionValue> onComputedCallback;
Expand All @@ -62,12 +126,13 @@ private static class IdExecutionEventListener implements ExecutionEventListener
private final MethodCallsCache callsCache;
private final UpdatesSynchronizationState syncState;
private final UUID nextExecutionItem;
private final Map<UUID, FunctionCallInfo> calls = new HashMap<>();
private final Map<UUID, FunctionCallInfo> calls;
private final Timer timer;
private long nanoTimeElapsed = 0;
private @Child TypeOfNode typeOfNode = TypeOfNode.build();

/**
* Creates a new listener.
* Creates a new event node.
*
* @param entryCallTarget the call target being observed.
* @param cache the precomputed expression values.
Expand All @@ -80,19 +145,23 @@ private static class IdExecutionEventListener implements ExecutionEventListener
* @param onExceptionalCallback the consumer of the exceptional events.
* @param timer the timer for timing execution
*/
public IdExecutionEventListener(
public IdExecutionEventNode(
EventContext context,
CallTarget entryCallTarget,
RuntimeCache cache,
MethodCallsCache methodCallsCache,
UpdatesSynchronizationState syncState,
UUID nextExecutionItem, // The expression ID
Map<UUID, FunctionCallInfo> calls,
Consumer<ExpressionCall> functionCallCallback,
Consumer<ExpressionValue> onComputedCallback,
Consumer<ExpressionValue> onCachedCallback,
Consumer<Exception> onExceptionalCallback,
Timer timer) {
this.context = context;
this.entryCallTarget = entryCallTarget;
this.cache = cache;
this.calls = calls;
this.callsCache = methodCallsCache;
this.syncState = syncState;
this.nextExecutionItem = nextExecutionItem;
Expand All @@ -104,20 +173,20 @@ public IdExecutionEventListener(
}

@Override
public Object onUnwind(EventContext context, VirtualFrame frame, Object info) {
public Object onUnwind(VirtualFrame frame, Object info) {
return info;
}

@Override
public void onEnter(EventContext context, VirtualFrame frame) {
public void onEnter(VirtualFrame frame) {
if (!isTopFrame(entryCallTarget)) {
return;
}
onEnterImpl(context);
onEnterImpl();
}

@CompilerDirectives.TruffleBoundary
private void onEnterImpl(EventContext context) {
private void onEnterImpl() {
UUID nodeId = getNodeId(context.getInstrumentedNode());

// Add a flag to say it was cached.
Expand Down Expand Up @@ -148,12 +217,11 @@ private void onEnterImpl(EventContext context) {
* Triggered when a node (either a function call sentry or an identified expression) finishes
* execution.
*
* @param context the event context.
* @param frame the current execution frame.
* @param result the result of executing the node this method was triggered for.
*/
@Override
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
public void onReturnValue(VirtualFrame frame, Object result) {
nanoTimeElapsed = timer.getTime() - nanoTimeElapsed;
if (!isTopFrame(entryCallTarget)) {
return;
Expand All @@ -170,26 +238,35 @@ public void onReturnValue(EventContext context, VirtualFrame frame, Object resul
}

@Override
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
public void onReturnExceptional(VirtualFrame frame, Throwable exception) {
if (exception instanceof TailCallException) {
onTailCallReturn(exception, Function.ArgumentsHelper.getState(frame.getArguments()), context);
onTailCallReturn(exception, Function.ArgumentsHelper.getState(frame.getArguments()));
} else if (exception instanceof PanicException) {
PanicException panicException = (PanicException) exception;
onReturnValue(
context, frame, new PanicSentinel(panicException, context.getInstrumentedNode()));
onReturnValue(frame, new PanicSentinel(panicException, context.getInstrumentedNode()));
} else if (exception instanceof PanicSentinel) {
onReturnValue(context, frame, exception);
onReturnValue(frame, exception);
}
}

@CompilerDirectives.TruffleBoundary
private void onExpressionReturn(Object result, Node node, EventContext context) throws ThreadDeath {
boolean isPanic = result instanceof PanicSentinel;
UUID nodeId = ((ExpressionNode) node).getId();
String resultType = Types.getName(result);

String resultType;
if (result instanceof UnresolvedSymbol) {
resultType = Constants.UNRESOLVED_SYMBOL;
} else {
Object typeResult = typeOfNode.execute(result);
hubertp marked this conversation as resolved.
Show resolved Hide resolved
if (typeResult instanceof Type t) {
resultType = t.getQualifiedName().toString();
} else {
resultType = null;
}
}

String cachedType = cache.getType(nodeId);
FunctionCallInfo call = calls.get(nodeId);
FunctionCallInfo call = functionCallInfoById(nodeId);
FunctionCallInfo cachedCall = cache.getCall(nodeId);
ProfilingInfo[] profilingInfo = new ProfilingInfo[] {new ExecutionTime(nanoTimeElapsed)};

Expand All @@ -208,12 +285,21 @@ private void onExpressionReturn(Object result, Node node, EventContext context)
cache.putType(nodeId, resultType);
cache.putCall(nodeId, call);

onComputedCallback.accept(expressionValue);
passExpressionValueToCallback(expressionValue);
if (isPanic) {
throw context.createUnwind(result);
}
}

@CompilerDirectives.TruffleBoundary
private void passExpressionValueToCallback(ExpressionValue expressionValue) {
onComputedCallback.accept(expressionValue);
}

@CompilerDirectives.TruffleBoundary
private FunctionCallInfo functionCallInfoById(UUID nodeId) {
return calls.get(nodeId);
}

@CompilerDirectives.TruffleBoundary
private void onFunctionReturn(UUID nodeId, Object result, EventContext context) throws ThreadDeath {
Expand All @@ -230,7 +316,7 @@ private void onFunctionReturn(UUID nodeId, Object result, EventContext context)
}

@CompilerDirectives.TruffleBoundary
private void onTailCallReturn(Throwable exception, State state, EventContext context) {
private void onTailCallReturn(Throwable exception, State state) {
try {
TailCallException tailCallException = (TailCallException) exception;
FunctionCallInstrumentationNode.FunctionCall functionCall =
Expand All @@ -239,7 +325,7 @@ private void onTailCallReturn(Throwable exception, State state, EventContext con
state,
tailCallException.getArguments());
Object result = InteropLibrary.getFactory().getUncached().execute(functionCall);
onReturnValue(context, null, result);
onReturnValue(null, result);
} catch (InteropException e) {
onExceptionalCallback.accept(e);
}
Expand Down Expand Up @@ -287,7 +373,7 @@ private UUID getNodeId(Node node) {
}

/**
* Attach a new listener to observe identified nodes within given function.
* Attach a new event node factory to observe identified nodes within given function.
*
* @param module module that contains the code
* @param entryCallTarget the call target being observed.
Expand All @@ -300,10 +386,10 @@ private UUID getNodeId(Node node) {
* @param onComputedCallback the consumer of the computed value events.
* @param onCachedCallback the consumer of the cached value events.
* @param onExceptionalCallback the consumer of the exceptional events.
* @return a reference to the attached event listener.
* @return a reference to the attached event node factory.
*/
@Override
public EventBinding<ExecutionEventListener> bind(
public EventBinding<ExecutionEventNodeFactory> bind(
Module module,
CallTarget entryCallTarget,
RuntimeCache cache,
Expand All @@ -329,9 +415,9 @@ public EventBinding<ExecutionEventListener> bind(
SourceSectionFilter filter = builder.build();

return env.getInstrumenter()
.attachExecutionEventListener(
.attachExecutionEventFactory(
filter,
new IdExecutionEventListener(
new IdEventNodeFactory(
entryCallTarget,
cache,
methodCallsCache,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ class BuiltinTypesTest
3
) should contain theSameElementsAs Seq(
Api.Response(requestId, Api.PushContextResponse(contextId)),
TestMessages.update(contextId, idMain, ConstantsGen.DECIMAL),
TestMessages.update(contextId, idMain, ConstantsGen.DECIMAL_BUILTIN),
context.executionComplete(contextId)
)
}
Expand Down Expand Up @@ -258,7 +258,7 @@ class BuiltinTypesTest
4
) should contain theSameElementsAs Seq(
Api.Response(requestId, Api.PushContextResponse(contextId)),
TestMessages.update(contextId, idY, ConstantsGen.FUNCTION),
TestMessages.update(contextId, idY, ConstantsGen.FUNCTION_BUILTIN),
TestMessages.update(contextId, idMain, ConstantsGen.INTEGER),
context.executionComplete(contextId)
)
Expand Down Expand Up @@ -319,7 +319,7 @@ class BuiltinTypesTest
3
) should contain theSameElementsAs Seq(
Api.Response(requestId, Api.PushContextResponse(contextId)),
TestMessages.update(contextId, idMain, "Enso_Test.Test.Main.Foo.Bar"),
TestMessages.update(contextId, idMain, ConstantsGen.FUNCTION_BUILTIN),
Copy link
Contributor Author

@hubertp hubertp Feb 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might seem like a weird change at first sight but consider that Bar is a constructor taking an argument. So the type of it is a Function. Types.getName had a special case for AtomConstructor and I think now we are more consistent.

context.executionComplete(contextId)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,23 +176,26 @@ class RuntimeErrorsTest
Api.ExpressionUpdate.Payload.Panic(
"Compile error: The name `undefined` could not be found.",
Seq(xId)
)
),
builtin = true
),
TestMessages.panic(
contextId,
yId,
Api.ExpressionUpdate.Payload.Panic(
"Compile error: The name `undefined` could not be found.",
Seq(xId)
)
),
builtin = true
),
TestMessages.panic(
contextId,
mainResId,
Api.ExpressionUpdate.Payload.Panic(
"Compile error: The name `undefined` could not be found.",
Seq(xId)
)
),
builtin = true
),
context.executionComplete(contextId)
)
Expand Down Expand Up @@ -263,7 +266,8 @@ class RuntimeErrorsTest
Api.ExpressionUpdate.Payload.Panic(
"Compile error: The name `x` could not be found.",
Seq(mainBodyId)
)
),
builtin = true
),
context.executionComplete(contextId)
)
Expand Down Expand Up @@ -1255,7 +1259,8 @@ class RuntimeErrorsTest
Api.ExpressionUpdate.Payload.Panic(
"9 (Integer)",
Seq(xId)
)
),
builtin = false
),
TestMessages.panic(
contextId,
Expand Down Expand Up @@ -1482,15 +1487,17 @@ class RuntimeErrorsTest
Api.ExpressionUpdate.Payload.Panic(
"Compile error: The name `IO` could not be found.",
Seq(xId)
)
),
builtin = true
),
TestMessages.panic(
contextId,
mainResId,
Api.ExpressionUpdate.Payload.Panic(
"Compile error: The name `IO` could not be found.",
Seq(xId)
)
),
builtin = true
),
context.executionComplete(contextId)
)
Expand Down
Loading