/*
* Copyright (c) 2017, 2020, 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
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oracle.truffle.api;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.graalvm.polyglot.PolyglotException;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleLanguage.ContextReference;
import com.oracle.truffle.api.TruffleLanguage.Env;
import com.oracle.truffle.api.nodes.Node;
A handle on a context of a set of Truffle languages. This context handle is designed to be used
by Truffle guest language implementations. The Truffle context can be used to create inner
contexts for isolated execution of guest language code.
A context
consists of a language
context
instance for each installed language
. The current language context is created
eagerly and can be accessed using a context reference
or statically with TruffleLanguage.getCurrentContext(Class<TruffleLanguage<Object>>)
after the context was entered
.
The configuration for each language context is inherited from its parent/creator context. In addition to that config
parameters can be passed to new language context instance of the current language. The configuration of other installed languages cannot be modified. To run guest language code in a context, the context needs to be first entered
and then left
. The context should be closed
when it is no longer needed. If the context is not closed explicitly, then it is automatically closed together with the parent context.
Example usage: TruffleContextSnippets.executeInContext
Since: 0.27
/**
* A handle on a context of a set of Truffle languages. This context handle is designed to be used
* by Truffle guest language implementations. The Truffle context can be used to create inner
* contexts for isolated execution of guest language code.
* <p>
* A {@link TruffleContext context} consists of a {@link TruffleLanguage#createContext(Env) language
* context} instance for each {@link Env#getInternalLanguages() installed language}. The current
* language context is {@link TruffleLanguage#createContext(Env) created} eagerly and can be
* accessed using a {@link ContextReference context reference} or statically with
* {@link TruffleLanguage#getCurrentContext(Class)} after the context was
* {@link TruffleContext#enter(Node) entered}.
* <p>
* The configuration for each language context is inherited from its parent/creator context. In
* addition to that {@link Builder#config(String, Object) config} parameters can be passed to new
* language context instance of the current language. The configuration of other installed languages
* cannot be modified. To run guest language code in a context, the context needs to be first
* {@link #enter(Node) entered} and then {@link #leave(Node, Object) left}. The context should be
* {@link #close() closed} when it is no longer needed. If the context is not closed explicitly,
* then it is automatically closed together with the parent context.
* <p>
* Example usage: {@link TruffleContextSnippets#executeInContext}
*
* @since 0.27
*/
public final class TruffleContext implements AutoCloseable {
static final TruffleContext EMPTY = new TruffleContext();
private static final ThreadLocal<List<Object>> CONTEXT_ASSERT_STACK;
static {
boolean assertions = false;
assert (assertions = true) == true;
CONTEXT_ASSERT_STACK = assertions ? new ThreadLocal<List<Object>>() {
@Override
protected List<Object> initialValue() {
return new ArrayList<>();
}
} : null;
}
final Object polyglotContext;
final boolean closeable;
TruffleContext(Object polyglotContext, boolean closeable) {
this.polyglotContext = polyglotContext;
this.closeable = closeable;
}
/*
* Constructor necessary for inner builder.
*/
private TruffleContext() {
this.polyglotContext = null;
this.closeable = false;
}
{@inheritDoc}
Since: 20.3
/**
* {@inheritDoc}
*
* @since 20.3
*/
@Override
@TruffleBoundary
public boolean equals(Object obj) {
if (!(obj instanceof TruffleContext)) {
return false;
}
TruffleContext c = (TruffleContext) obj;
return polyglotContext.equals(c.polyglotContext);
}
{@inheritDoc}
Since: 20.3
/**
* {@inheritDoc}
*
* @since 20.3
*/
@Override
public int hashCode() {
return polyglotContext.hashCode();
}
Get a parent context of this context, if any. This provides the hierarchy of inner contexts.
Returns: a parent context, or null
if there is no parent Since: 0.30
/**
* Get a parent context of this context, if any. This provides the hierarchy of inner contexts.
*
* @return a parent context, or <code>null</code> if there is no parent
* @since 0.30
*/
@TruffleBoundary
public TruffleContext getParent() {
try {
return LanguageAccessor.engineAccess().getParentContext(polyglotContext);
} catch (Throwable t) {
throw Env.engineToLanguageException(t);
}
}
Since: 0.27 Deprecated: use enter(Node)
instead and pass in the node context is possible.
/**
* @since 0.27
* @deprecated use {@link #enter(Node)} instead and pass in the node context is possible.
*/
@Deprecated
public Object enter() {
return enter(null);
}
Enters this context and returns an object representing the previous context. Calls to enter must be followed by a call to leave(Node, Object)
in a finally block and the previous context must be passed as an argument. It is allowed to enter a context multiple times from the same thread. If the context is currently not entered by any thread then it is allowed be entered by an arbitrary thread. Entering the context from two or more different threads at the same time is possible, unless one of the loaded languages denies access to the thread, in which case an IllegalStateException
is thrown. The result of the enter function is unspecified and must only be passed to leave(Node, Object)
. The result value must not be stored permanently.
An adopted node may be passed to allow perform optimizations on the fast-path. If a
null
node is passed then entering a context will result in a boundary
call in compiled code. If the provided node is not adopted an IllegalArgumentException
is thrown.
Entering a language context is designed for compilation and is most efficient if the context
instance is compilation final.
Example usage: TruffleContextSnippets.executeInContext
See Also: Since: 20.3
/**
* Enters this context and returns an object representing the previous context. Calls to enter
* must be followed by a call to {@link #leave(Node, Object)} in a finally block and the
* previous context must be passed as an argument. It is allowed to enter a context multiple
* times from the same thread. If the context is currently not entered by any thread then it is
* allowed be entered by an arbitrary thread. Entering the context from two or more different
* threads at the same time is possible, unless one of the loaded languages denies access to the
* thread, in which case an {@link IllegalStateException} is thrown. The result of the enter
* function is unspecified and must only be passed to {@link #leave(Node, Object)}. The result
* value must not be stored permanently.
* <p>
* An adopted node may be passed to allow perform optimizations on the fast-path. If a
* <code>null</code> node is passed then entering a context will result in a
* {@link TruffleBoundary boundary} call in compiled code. If the provided node is not adopted
* an {@link IllegalArgumentException} is thrown.
* <p>
* Entering a language context is designed for compilation and is most efficient if the
* {@link TruffleContext context} instance is compilation final.
*
* <p>
* Example usage: {@link TruffleContextSnippets#executeInContext}
*
* @see #leave(Node, Object)
* @since 20.3
*/
public Object enter(Node node) {
try {
CompilerAsserts.partialEvaluationConstant(node);
Object prev = LanguageAccessor.engineAccess().enterInternalContext(node, polyglotContext);
if (CONTEXT_ASSERT_STACK != null) {
verifyEnter(prev);
}
return prev;
} catch (Throwable t) {
throw Env.engineToLanguageException(t);
}
}
Checks whether the context is entered on the current thread and the context is the currently active context on this thread. There can be multiple contexts active
on a single thread, but this method only returns true
if it is the top-most context.
This method is thread-safe and may be used from multiple threads.
Since: 20.0 Returns: true
if the context is active, false
otherwise
/**
* Checks whether the context is entered on the current thread and the context is the currently
* active context on this thread. There can be multiple contexts {@link #isActive() active} on a
* single thread, but this method only returns <code>true</code> if it is the top-most context.
* This method is thread-safe and may be used from multiple threads.
*
* @since 20.0
* @return {@code true} if the context is active, {@code false} otherwise
*/
public boolean isEntered() {
try {
return LanguageAccessor.engineAccess().isContextEntered(polyglotContext);
} catch (Throwable t) {
throw Env.engineToLanguageException(t);
}
}
Returns true
if the context is currently active on the current thread, else
false
. Checks whether the context has been previously entered by this thread with enter(Node)
and hasn't been left yet with leave(Node, Object)
methods. Multiple contexts can be active on a single thread. See isEntered()
for checking whether it is the top-most entered context. This method is thread-safe and may be used from multiple threads. Since: 20.3
/**
* Returns <code>true</code> if the context is currently active on the current thread, else
* <code>false</code>. Checks whether the context has been previously entered by this thread
* with {@link #enter(Node)} and hasn't been left yet with
* {@link #leave(Node, java.lang.Object)} methods. Multiple contexts can be active on a single
* thread. See {@link #isEntered()} for checking whether it is the top-most entered context.
* This method is thread-safe and may be used from multiple threads.
*
* @since 20.3
*/
public boolean isActive() {
try {
return LanguageAccessor.engineAccess().isContextActive(polyglotContext);
} catch (Throwable t) {
throw Env.engineToLanguageException(t);
}
}
Returns true
if the context was closed else false
. A context may be closed if close()
or closeCancelled(Node, String)
was called previously. Since: 20.3
/**
* Returns <code>true</code> if the context was closed else <code>false</code>. A context may be
* closed if {@link #close()} or {@link #closeCancelled(Node, String)} was called previously.
*
* @since 20.3
*/
@TruffleBoundary
public boolean isClosed() {
try {
return LanguageAccessor.engineAccess().isContextClosed(polyglotContext);
} catch (Throwable t) {
throw Env.engineToLanguageException(t);
}
}
Since: 0.27 Deprecated: use leave(Node, Object)
instead and pass in the node context if possible.
/**
* @since 0.27
* @deprecated use {@link #leave(Node, Object)} instead and pass in the node context if
* possible.
*/
@Deprecated
public void leave(Object prev) {
leave(null, prev);
}
Leaves this context and sets the previous context as the new current context.
An adopted node may be passed to allow perform optimizations on the fast-path. If a
null
node is passed then entering a context will result in a boundary
call in compiled code. If the node is not adopted an IllegalArgumentException
is thrown.
Leaving a language context is designed for compilation and is most efficient if the context
instance is compilation final.
Params: - prev – the previous context returned by
enter(Node)
See Also: Since: 20.3
/**
* Leaves this context and sets the previous context as the new current context.
* <p>
* An adopted node may be passed to allow perform optimizations on the fast-path. If a
* <code>null</code> node is passed then entering a context will result in a
* {@link TruffleBoundary boundary} call in compiled code. If the node is not adopted an
* {@link IllegalArgumentException} is thrown.
* <p>
* Leaving a language context is designed for compilation and is most efficient if the
* {@link TruffleContext context} instance is compilation final.
*
* @param prev the previous context returned by {@link #enter(Node)}
* @see #enter(Node)
* @since 20.3
*/
public void leave(Node node, Object prev) {
try {
if (CONTEXT_ASSERT_STACK != null) {
verifyLeave(prev);
}
LanguageAccessor.engineAccess().leaveInternalContext(node, polyglotContext, prev);
} catch (Throwable t) {
throw Env.engineToLanguageException(t);
}
}
@TruffleBoundary
private static void verifyEnter(Object prev) {
assert CONTEXT_ASSERT_STACK != null;
CONTEXT_ASSERT_STACK.get().add(prev);
}
@TruffleBoundary
private static void verifyLeave(Object prev) {
assert CONTEXT_ASSERT_STACK != null;
List<Object> list = CONTEXT_ASSERT_STACK.get();
assert !list.isEmpty() : "Assert stack is empty.";
Object expectedPrev = list.get(list.size() - 1);
assert prev == expectedPrev : "Invalid prev argument provided in TruffleContext.leave(Object).";
list.remove(list.size() - 1); // pop
}
Closes this context and disposes its resources. Closes this context and disposes its resources. A context cannot be closed if it is currently entered
or active by any thread. If a closed context is attempted to be accessed or entered, then an IllegalStateException
is thrown. If the context is not closed explicitly, then it is automatically closed together with the parent context. If an attempt to close a context was successful then consecutive calls to close have no effect. Throws: - UnsupportedOperationException – if the close operation is not supported on this context
- IllegalStateException – if the context is
active
.
See Also: Since: 0.27
/**
* Closes this context and disposes its resources. Closes this context and disposes its
* resources. A context cannot be closed if it is currently {@link #enter(Node) entered} or
* active by any thread. If a closed context is attempted to be accessed or entered, then an
* {@link IllegalStateException} is thrown. If the context is not closed explicitly, then it is
* automatically closed together with the parent context. If an attempt to close a context was
* successful then consecutive calls to close have no effect.
*
* @throws UnsupportedOperationException if the close operation is not supported on this context
* @throws IllegalStateException if the context is {@link #isActive() active}.
* @since 0.27
* @see #closeCancelled(Node, String)
* @see #closeResourceExhausted(Node, String)
*/
@Override
@TruffleBoundary
public void close() {
if (!closeable) {
throw new UnsupportedOperationException("This context instance has no permission to close. " +
"Only the original creator of the truffle context or instruments can close.");
}
try {
LanguageAccessor.engineAccess().closeContext(polyglotContext, false, null, false, null);
} catch (Throwable t) {
throw Env.engineToLanguageException(t);
}
}
Force closes the context as cancelled and stops all the execution on all active threads using a ThreadDeath
exception. If this context is not currently
entered
on the current thread then this method waits until the close operation is complete and isClosed()
returns true
, else it throws the cancelled exception
upon its completion of this method. If an attempt to close a context was successful then
consecutive calls to close have no effect.
If forced and this context currently entered
on the current thread and no other context is entered on the current thread then this method directly throws a ThreadDeath
error instead of completing to indicate that the current thread should be stopped. The thrown ThreadDeath
must not be caught by the guest language and freely propagated to the guest application to cancel the execution on the current thread. If a context is active
on the current thread, but not
entered
, then an IllegalStateException
is thrown, as parent contexts that are active on the current thread cannot be cancelled.
Params: - closeLocation – the node where the close occurred. If the context is currently entered
on the thread, then this node will be used as location, for the exception thrown.
If the context is not entered, then the closeLocation parameter will be ignored.
- message – exception text for humans provided to the embedder and tools that observe the
cancellation.
Throws: - UnsupportedOperationException – if the close operation is not supported on this context
- IllegalStateException – if the context is
active
but not entered
on the current thread.
See Also: Since: 20.3
/**
* Force closes the context as cancelled and stops all the execution on all active threads using
* a {@link ThreadDeath} exception. If this context is not currently {@link #isEntered()
* entered} on the current thread then this method waits until the close operation is complete
* and {@link #isClosed()} returns <code>true</code>, else it throws the cancelled exception
* upon its completion of this method. If an attempt to close a context was successful then
* consecutive calls to close have no effect.
* <p>
* If forced and this context currently {@link #isEntered() entered} on the current thread and
* no other context is entered on the current thread then this method directly throws a
* {@link ThreadDeath} error instead of completing to indicate that the current thread should be
* stopped. The thrown {@link ThreadDeath} must not be caught by the guest language and freely
* propagated to the guest application to cancel the execution on the current thread. If a
* context is {@link #isActive() active} on the current thread, but not {@link #isEntered()
* entered}, then an {@link IllegalStateException} is thrown, as parent contexts that are active
* on the current thread cannot be cancelled.
*
* @param closeLocation the node where the close occurred. If the context is currently entered
* on the thread, then this node will be used as location, for the exception thrown.
* If the context is not entered, then the closeLocation parameter will be ignored.
* @param message exception text for humans provided to the embedder and tools that observe the
* cancellation.
* @throws UnsupportedOperationException if the close operation is not supported on this context
* @throws IllegalStateException if the context is {@link #isActive() active} but not
* {@link #isEntered() entered} on the current thread.
* @see #close()
* @see #closeResourceExhausted(Node, String)
* @since 20.3
*/
@TruffleBoundary
public void closeCancelled(Node closeLocation, String message) {
if (!closeable) {
throw new UnsupportedOperationException("This context instance has no permission to close. " +
"Only the original creator of the truffle context or instruments can close.");
}
try {
LanguageAccessor.engineAccess().closeContext(polyglotContext, true, closeLocation, false, message);
} catch (Throwable t) {
throw Env.engineToLanguageException(t);
}
}
Force closes the context due to resource exhaustion. This method is equivalent to calling closeCancelled(location, message)
except in addition the thrown PolyglotException
returns true
for PolyglotException.isResourceExhausted()
. See Also: Since: 20.3
/**
* Force closes the context due to resource exhaustion. This method is equivalent to calling
* {@link #closeCancelled(Node, String) closeCancelled(location, message)} except in addition
* the thrown {@link PolyglotException} returns <code>true</code> for
* {@link PolyglotException#isResourceExhausted()}.
*
* @see PolyglotException#isResourceExhausted()
* @see #close()
* @see #closeCancelled(Node, String)
* @since 20.3
*/
@TruffleBoundary
public void closeResourceExhausted(Node location, String message) {
if (!closeable) {
throw new UnsupportedOperationException("This context instance has no permission to cancel. " +
"Only the original creator of the truffle context or instruments can close.");
}
try {
LanguageAccessor.engineAccess().closeContext(polyglotContext, true, location, true, message);
} catch (Throwable t) {
throw Env.engineToLanguageException(t);
}
}
Builder class to create new TruffleContext
instances. Since: 0.27
/**
* Builder class to create new {@link TruffleContext} instances.
*
* @since 0.27
*/
public final class Builder {
private final Env sourceEnvironment;
private Map<String, Object> config;
Builder(Env env) {
this.sourceEnvironment = env;
}
Sets a config parameter that the child context of this language can access using Env.getConfig()
. Since: 0.27
/**
* Sets a config parameter that the child context of this language can access using
* {@link Env#getConfig()}.
*
* @since 0.27
*/
@TruffleBoundary
public Builder config(String key, Object value) {
if (config == null) {
config = new HashMap<>();
}
config.put(key, value);
return this;
}
Builds the new context instance.
Since: 0.27
/**
* Builds the new context instance.
*
* @since 0.27
*/
@TruffleBoundary
public TruffleContext build() {
try {
return LanguageAccessor.engineAccess().createInternalContext(sourceEnvironment.getPolyglotLanguageContext(), config);
} catch (Throwable t) {
throw Env.engineToLanguageException(t);
}
}
}
}
class TruffleContextSnippets {
// @formatter:off
abstract class MyContext {
}
abstract class MyLanguage extends TruffleLanguage<MyContext> {
}
static
// BEGIN: TruffleContextSnippets#executeInContext
final class MyNode extends Node {
void executeInContext(Env env) {
MyContext outerLangContext = getContext();
TruffleContext innerContext = env.newContextBuilder().build();
Object p = innerContext.enter(this);
try {
MyContext innerLangContext = getContext();
assert outerLangContext != innerLangContext;
} finally {
innerContext.leave(this, p);
}
assert outerLangContext == getContext();
innerContext.close();
}
}
private static MyContext getContext() {
return TruffleLanguage.getCurrentContext(MyLanguage.class);
}
// END: TruffleContextSnippets#executeInContext
// @formatter:on
}