package com.oracle.truffle.js.lang;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import org.graalvm.options.OptionDescriptor;
import org.graalvm.options.OptionDescriptors;
import org.graalvm.options.OptionKey;
import org.graalvm.options.OptionValues;
import org.graalvm.polyglot.Context;
import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleStackTrace;
import com.oracle.truffle.api.TruffleStackTraceElement;
import com.oracle.truffle.api.debug.DebuggerTags;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.ProvidedTags;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.nodes.ExecutableNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.js.nodes.JavaScriptNode;
import com.oracle.truffle.js.nodes.ScriptNode;
import com.oracle.truffle.js.nodes.access.InitErrorObjectNodeFactory;
import com.oracle.truffle.js.nodes.control.TryCatchNode;
import com.oracle.truffle.js.nodes.function.FunctionRootNode;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.BinaryOperationTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.BuiltinRootTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ControlFlowBlockTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ControlFlowBranchTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ControlFlowRootTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.DeclareTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.EvalCallTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.FunctionCallTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.InputNodeTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.LiteralTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ObjectAllocationTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ReadElementTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ReadPropertyTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ReadVariableTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.UnaryOperationTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.WriteElementTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.WritePropertyTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.WriteVariableTag;
import com.oracle.truffle.js.nodes.interop.ExportValueNode;
import com.oracle.truffle.js.nodes.interop.ImportValueNode;
import com.oracle.truffle.js.runtime.AbstractJavaScriptLanguage;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSAgent;
import com.oracle.truffle.js.runtime.JSArguments;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSContextOptions;
import com.oracle.truffle.js.runtime.JSEngine;
import com.oracle.truffle.js.runtime.JSException;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.interop.JavaScriptLanguageView;
import com.oracle.truffle.js.runtime.objects.Undefined;
@ProvidedTags({
StandardTags.StatementTag.class,
StandardTags.RootTag.class,
StandardTags.RootBodyTag.class,
StandardTags.ExpressionTag.class,
StandardTags.CallTag.class,
StandardTags.ReadVariableTag.class,
StandardTags.WriteVariableTag.class,
StandardTags.TryBlockTag.class,
DebuggerTags.AlwaysHalt.class,
ObjectAllocationTag.class,
BinaryOperationTag.class,
UnaryOperationTag.class,
WriteVariableTag.class,
ReadElementTag.class,
WriteElementTag.class,
ReadPropertyTag.class,
WritePropertyTag.class,
ReadVariableTag.class,
LiteralTag.class,
FunctionCallTag.class,
BuiltinRootTag.class,
EvalCallTag.class,
ControlFlowRootTag.class,
ControlFlowBlockTag.class,
ControlFlowBranchTag.class,
DeclareTag.class,
InputNodeTag.class,
})
@TruffleLanguage.Registration(id = JavaScriptLanguage.ID, name = JavaScriptLanguage.NAME, implementationName = JavaScriptLanguage.IMPLEMENTATION_NAME, characterMimeTypes = {
JavaScriptLanguage.APPLICATION_MIME_TYPE,
JavaScriptLanguage.TEXT_MIME_TYPE,
JavaScriptLanguage.MODULE_MIME_TYPE}, defaultMimeType = JavaScriptLanguage.APPLICATION_MIME_TYPE, contextPolicy = TruffleLanguage.ContextPolicy.SHARED, dependentLanguages = "regex", fileTypeDetectors = JSFileTypeDetector.class)
public final class JavaScriptLanguage extends AbstractJavaScriptLanguage {
public static final String TEXT_MIME_TYPE = "text/javascript";
public static final String APPLICATION_MIME_TYPE = "application/javascript";
public static final String MODULE_MIME_TYPE = "application/javascript+module";
public static final String SCRIPT_SOURCE_NAME_SUFFIX = ".js";
public static final String MODULE_SOURCE_NAME_SUFFIX = ".mjs";
public static final String INTERNAL_SOURCE_URL_PREFIX = "internal:";
public static final String NAME = "JavaScript";
public static final String IMPLEMENTATION_NAME = "GraalVM JavaScript";
public static final String ID = "js";
@CompilationFinal private volatile JSContext languageContext;
private volatile boolean multiContext;
private final Assumption promiseJobsQueueEmptyAssumption;
public static final OptionDescriptors OPTION_DESCRIPTORS;
static {
ArrayList<OptionDescriptor> options = new ArrayList<>();
JSContextOptions.describeOptions(options);
OPTION_DESCRIPTORS = OptionDescriptors.create(options);
ensureErrorClassesInitialized();
}
public JavaScriptLanguage() {
this.promiseJobsQueueEmptyAssumption = Truffle.getRuntime().createAssumption("PromiseJobsQueueEmpty");
}
@TruffleBoundary
@Override
public CallTarget parse(ParsingRequest parsingRequest) {
Source source = parsingRequest.getSource();
List<String> argumentNames = parsingRequest.getArgumentNames();
final JSContext context = getJSContext();
final ScriptNode program = parseScript(context, source, "", "", argumentNames);
if (context.isOptionParseOnly()) {
return createEmptyScript(context).getCallTarget();
}
RootNode rootNode = new RootNode(this) {
@Child private DirectCallNode directCallNode = DirectCallNode.create(program.getCallTarget());
@Child private ExportValueNode exportValueNode = ExportValueNode.create();
@Child private ImportValueNode importValueNode = ImportValueNode.create();
@CompilationFinal private ContextReference<JSRealm> contextReference;
@Override
public Object execute(VirtualFrame frame) {
if (contextReference == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
contextReference = lookupContextReference(JavaScriptLanguage.class);
}
JSRealm realm = contextReference.get();
assert realm.getContext() == context : "unexpected JSContext";
try {
interopBoundaryEnter(realm);
Object[] arguments = frame.getArguments();
for (int i = 0; i < arguments.length; i++) {
arguments[i] = importValueNode.executeWithTarget(arguments[i]);
}
arguments = program.argumentsToRunWithArguments(realm, arguments);
Object result = directCallNode.call(arguments);
return exportValueNode.execute(result);
} finally {
interopBoundaryExit(realm);
}
}
@Override
public boolean isInternal() {
return true;
}
@Override
protected boolean isInstrumentable() {
return false;
}
};
return Truffle.getRuntime().createCallTarget(rootNode);
}
@TruffleBoundary
private static ScriptNode createEmptyScript(JSContext context) {
return ScriptNode.fromFunctionData(context, JSFunction.createEmptyFunctionData(context));
}
@Override
protected ExecutableNode parse(InlineParsingRequest request) throws Exception {
final Source source = request.getSource();
final MaterializedFrame requestFrame = request.getFrame();
final JSContext context = getJSContext();
final boolean strict = isStrictLocation(request.getLocation());
final ExecutableNode executableNode = new ExecutableNode(this) {
@Child private JavaScriptNode expression = insert(parseInlineScript(context, source, requestFrame, strict));
@Child private ExportValueNode exportValueNode = ExportValueNode.create();
@Override
public Object execute(VirtualFrame frame) {
assert JavaScriptLanguage.getCurrentJSRealm().getContext() == context : "unexpected JSContext";
Object result = expression.execute(frame);
return exportValueNode.execute(result);
}
};
return executableNode;
}
private static boolean isStrictLocation(Node location) {
if (location != null) {
RootNode rootNode = location.getRootNode();
if (rootNode instanceof FunctionRootNode) {
return ((FunctionRootNode) rootNode).getFunctionData().isStrict();
}
}
return true;
}
@TruffleBoundary
protected static ScriptNode parseScript(JSContext context, Source code, String prolog, String epilog, List<String> argumentNames) {
boolean profileTime = context.getContextOptions().isProfileTime();
long startTime = profileTime ? System.nanoTime() : 0L;
try {
String[] arguments = null;
if (!argumentNames.isEmpty()) {
arguments = argumentNames.toArray(new String[0]);
}
return context.getEvaluator().parseScript(context, code, prolog, epilog, arguments);
} finally {
if (profileTime) {
context.getTimeProfiler().printElapsed(startTime, "parsing " + code.getName());
}
}
}
@TruffleBoundary
protected static JavaScriptNode parseInlineScript(JSContext context, Source code, MaterializedFrame lexicalContextFrame, boolean strict) {
boolean profileTime = context.getContextOptions().isProfileTime();
long startTime = profileTime ? System.nanoTime() : 0L;
try {
return context.getEvaluator().parseInlineScript(context, code, lexicalContextFrame, strict);
} finally {
if (profileTime) {
context.getTimeProfiler().printElapsed(startTime, "parsing " + code.getName());
}
}
}
@Override
protected JSRealm createContext(Env env) {
CompilerAsserts.neverPartOfCompilation();
JSContext context = languageContext;
if (context == null) {
context = initLanguageContext(env);
}
JSRealm realm = context.createRealm(env);
if (env.out() != realm.getOutputStream()) {
realm.setOutputWriter(null, env.out());
}
if (env.err() != realm.getErrorStream()) {
realm.setErrorWriter(null, env.err());
}
return realm;
}
private synchronized JSContext initLanguageContext(Env env) {
CompilerAsserts.neverPartOfCompilation();
JSContext curContext = languageContext;
if (curContext != null) {
assert curContext.getContextOptions().equals(JSContextOptions.fromOptionValues(env.getOptions()));
return curContext;
}
JSContext newContext = newJSContext(env);
languageContext = newContext;
return newContext;
}
private JSContext newJSContext(Env env) {
return JSEngine.createJSContext(this, env);
}
@Override
protected void initializeContext(JSRealm realm) {
realm.initialize();
}
@Override
protected boolean patchContext(JSRealm realm, Env newEnv) {
CompilerAsserts.neverPartOfCompilation();
assert realm.getContext().getLanguage() == this;
if (optionsAllowPreInitializedContext(realm.getEnv(), newEnv) && realm.patchContext(newEnv)) {
return true;
} else {
languageContext = null;
return false;
}
}
private static final OptionKey<?>[] PREINIT_CONTEXT_PATCHABLE_OPTIONS = {
JSContextOptions.ARRAY_SORT_INHERITED,
JSContextOptions.TIMER_RESOLUTION,
JSContextOptions.SHELL,
JSContextOptions.V8_COMPATIBILITY_MODE,
JSContextOptions.GLOBAL_PROPERTY,
JSContextOptions.GLOBAL_ARGUMENTS,
JSContextOptions.SCRIPTING,
JSContextOptions.DIRECT_BYTE_BUFFER,
JSContextOptions.INTL_402,
JSContextOptions.LOAD,
JSContextOptions.PRINT,
JSContextOptions.CONSOLE,
JSContextOptions.PERFORMANCE,
JSContextOptions.CLASS_FIELDS,
JSContextOptions.REGEXP_STATIC_RESULT,
JSContextOptions.TIME_ZONE,
};
private static boolean optionsAllowPreInitializedContext(Env preinitEnv, Env env) {
OptionValues preinitOptions = preinitEnv.getOptions();
OptionValues options = env.getOptions();
if (!preinitOptions.hasSetOptions() && !options.hasSetOptions()) {
return true;
} else if (preinitOptions.equals(options)) {
return true;
} else {
assert preinitOptions.getDescriptors().equals(options.getDescriptors());
Collection<OptionKey<?>> ignoredOptions = Arrays.asList(PREINIT_CONTEXT_PATCHABLE_OPTIONS);
for (OptionDescriptor descriptor : options.getDescriptors()) {
OptionKey<?> key = descriptor.getKey();
if (preinitOptions.hasBeenSet(key) || options.hasBeenSet(key)) {
if (ignoredOptions.contains(key)) {
continue;
}
if (!preinitOptions.get(key).equals(options.get(key))) {
return false;
}
}
}
return true;
}
}
@Override
protected void disposeContext(JSRealm realm) {
CompilerAsserts.neverPartOfCompilation();
JSContext context = realm.getContext();
JSContextOptions options = context.getContextOptions();
if (options.isProfileTime() && options.isProfileTimePrintCumulative()) {
context.getTimeProfiler().printCumulative();
}
realm.setGlobalObject(Undefined.instance);
}
@Override
protected void initializeMultipleContexts() {
multiContext = true;
}
@Override
public boolean isMultiContext() {
return multiContext;
}
@Override
protected boolean areOptionsCompatible(OptionValues firstOptions, OptionValues newOptions) {
return firstOptions.equals(newOptions);
}
@Override
protected OptionDescriptors getOptionDescriptors() {
return OPTION_DESCRIPTORS;
}
@Override
protected boolean isVisible(JSRealm realm, Object value) {
return (value != Undefined.instance);
}
@Override
protected Object getLanguageView(JSRealm context, Object value) {
return JavaScriptLanguageView.create(value);
}
@Override
protected Object getScope(JSRealm context) {
return context.getTopScopeObject();
}
public static JSContext getJSContext(Context context) {
return getJSRealm(context).getContext();
}
public static JSRealm getJSRealm(Context context) {
context.enter();
try {
context.initialize(ID);
return getCurrentContext(JavaScriptLanguage.class);
} finally {
context.leave();
}
}
@SuppressWarnings("static-method")
public void interopBoundaryEnter(JSRealm realm) {
realm.getAgent().interopBoundaryEnter();
}
public void interopBoundaryExit(JSRealm realm) {
JSAgent agent = realm.getAgent();
if (agent.interopBoundaryExit()) {
if (!promiseJobsQueueEmptyAssumption.isValid()) {
agent.processAllPromises(true);
}
if (getJSContext().getContextOptions().isTestV8Mode()) {
processTimeoutCallbacks(realm);
}
}
}
@TruffleBoundary
@SuppressWarnings("unchecked")
private static void processTimeoutCallbacks(JSRealm realm) {
JSAgent agent = realm.getAgent();
List<Object> callbackList;
while ((callbackList = (List<Object>) realm.getEmbedderData()) != null && !callbackList.isEmpty()) {
realm.setEmbedderData(null);
for (Object callback : callbackList) {
JSRuntime.call(callback, Undefined.instance, JSArguments.EMPTY_ARGUMENTS_ARRAY);
}
agent.processAllPromises(true);
}
}
public Assumption getPromiseJobsQueueEmptyAssumption() {
return promiseJobsQueueEmptyAssumption;
}
public JSContext getJSContext() {
return Objects.requireNonNull(languageContext);
}
public boolean bindMemberFunctions() {
return getJSContext().getContextOptions().bindMemberFunctions();
}
public int getAsyncStackDepth() {
return super.getAsynchronousStackDepth();
}
private static void ensureErrorClassesInitialized() {
if (JSConfig.SubstrateVM) {
return;
}
try {
Class.forName(Errors.class.getName());
Class.forName(JSException.class.getName());
Class.forName(TruffleStackTrace.class.getName());
Class.forName(TruffleStackTraceElement.class.getName());
Class.forName(InitErrorObjectNodeFactory.DefineStackPropertyNodeGen.class.getName());
Class.forName(TryCatchNode.GetErrorObjectNode.class.getName());
} catch (Exception ex) {
ex.printStackTrace();
}
}
}