org.truffleruby.RubyLanguage Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ruby-language Show documentation
Show all versions of ruby-language Show documentation
Core module of Ruby on Truffle
The newest version!
/*
* Copyright (c) 2015, 2024 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 2.0, or
* GNU General Public License version 2, or
* GNU Lesser General Public License version 2.1.
*/
package org.truffleruby;
import java.io.File;
import java.io.IOException;
import java.lang.ref.Cleaner;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.ContextThreadLocal;
import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.AllocationReporter;
import com.oracle.truffle.api.instrumentation.EventContext;
import com.oracle.truffle.api.instrumentation.ExecutionEventListener;
import com.oracle.truffle.api.instrumentation.Instrumenter;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.Shape;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.strings.AbstractTruffleString;
import com.oracle.truffle.api.strings.InternalByteArray;
import com.oracle.truffle.api.strings.TruffleString;
import org.graalvm.nativeimage.ImageInfo;
import org.graalvm.nativeimage.ProcessProperties;
import org.graalvm.options.OptionDescriptors;
import org.prism.Parser;
import org.truffleruby.annotations.SuppressFBWarnings;
import org.truffleruby.builtins.PrimitiveManager;
import org.truffleruby.cext.ValueWrapperManager;
import org.truffleruby.collections.SharedIndicesMap;
import org.truffleruby.collections.SharedIndicesMap.LanguageArray;
import org.truffleruby.core.RubyHandle;
import org.truffleruby.core.array.RubyArray;
import org.truffleruby.core.basicobject.RubyBasicObject;
import org.truffleruby.core.binding.RubyBinding;
import org.truffleruby.core.encoding.Encodings;
import org.truffleruby.core.encoding.RubyEncoding;
import org.truffleruby.core.encoding.RubyEncodingConverter;
import org.truffleruby.core.exception.RubyException;
import org.truffleruby.core.exception.RubyFrozenError;
import org.truffleruby.core.exception.RubyNameError;
import org.truffleruby.core.exception.RubyNoMethodError;
import org.truffleruby.core.exception.RubySyntaxError;
import org.truffleruby.core.exception.RubySystemCallError;
import org.truffleruby.core.exception.RubySystemExit;
import org.truffleruby.core.fiber.RubyFiber;
import org.truffleruby.core.hash.RubyHash;
import org.graalvm.options.OptionValues;
import org.truffleruby.core.inlined.CoreMethodAssumptions;
import org.truffleruby.core.kernel.TraceManager;
import org.truffleruby.core.klass.RubyClass;
import org.truffleruby.core.method.RubyMethod;
import org.truffleruby.core.method.RubyUnboundMethod;
import org.truffleruby.core.module.RubyModule;
import org.truffleruby.core.mutex.RubyConditionVariable;
import org.truffleruby.core.mutex.RubyMutex;
import org.truffleruby.core.objectspace.ObjectSpaceManager;
import org.truffleruby.core.objectspace.RubyWeakMap;
import org.truffleruby.core.proc.RubyProc;
import org.truffleruby.core.queue.RubyQueue;
import org.truffleruby.core.queue.RubySizedQueue;
import org.truffleruby.core.range.RubyObjectRange;
import org.truffleruby.core.regexp.RegexpCacheKey;
import org.truffleruby.core.regexp.RegexpTable;
import org.truffleruby.core.regexp.RubyMatchData;
import org.truffleruby.core.regexp.RubyRegexp;
import org.truffleruby.core.string.PathToTStringCache;
import org.truffleruby.core.string.TStringCache;
import org.truffleruby.core.string.CoreStrings;
import org.truffleruby.core.string.FrozenStringLiterals;
import org.truffleruby.core.string.RubyString;
import org.truffleruby.core.string.StringUtils;
import org.truffleruby.core.support.RubyByteArray;
import org.truffleruby.core.support.RubyCustomRandomizer;
import org.truffleruby.core.support.RubyIO;
import org.truffleruby.core.support.RubyPRNGRandomizer;
import org.truffleruby.core.support.RubySecureRandomizer;
import org.truffleruby.core.symbol.CoreSymbols;
import org.truffleruby.core.symbol.RubySymbol;
import org.truffleruby.core.symbol.SymbolTable;
import org.truffleruby.core.thread.RubyBacktraceLocation;
import org.truffleruby.core.thread.RubyThread;
import org.truffleruby.core.time.RubyTime;
import org.truffleruby.core.tracepoint.RubyTracePoint;
import org.truffleruby.extra.RubyAtomicReference;
import org.truffleruby.extra.RubyConcurrentMap;
import org.truffleruby.extra.ffi.RubyPointer;
import org.truffleruby.core.string.ImmutableRubyString;
import org.truffleruby.interop.RubyInnerContext;
import org.truffleruby.interop.RubySourceLocation;
import org.truffleruby.language.LexicalScope;
import org.truffleruby.language.RubyDynamicObject;
import org.truffleruby.language.RubyEvalInteractiveRootNode;
import org.truffleruby.language.RubyInlineParsingRequestNode;
import org.truffleruby.language.RubyParsingRequestNode;
import org.truffleruby.language.arguments.KeywordArgumentsDescriptorManager;
import org.truffleruby.language.backtrace.BacktraceFormatter;
import org.truffleruby.language.objects.RubyObjectType;
import org.truffleruby.language.objects.classvariables.ClassVariableStorage;
import org.truffleruby.language.threadlocal.SpecialVariableStorage;
import org.truffleruby.options.LanguageOptions;
import org.truffleruby.parser.BlockDescriptorInfo;
import org.truffleruby.parser.ParserContext;
import org.truffleruby.parser.ParsingParameters;
import org.truffleruby.parser.RubySource;
import org.truffleruby.parser.TranslatorEnvironment;
import org.truffleruby.shared.Platform;
import org.truffleruby.shared.Metrics;
import org.truffleruby.shared.TruffleRuby;
import org.truffleruby.shared.options.OptionsCatalog;
import org.truffleruby.signal.LibRubySignal;
import org.truffleruby.stdlib.CoverageManager;
import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLanguage.ContextPolicy;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.instrumentation.ProvidedTags;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.nodes.ExecutableNode;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.utilities.CyclicAssumption;
import org.truffleruby.stdlib.digest.RubyDigest;
import static org.truffleruby.language.RubyBaseNode.createArray;
import static org.truffleruby.language.RubyBaseNode.createString;
import static org.truffleruby.language.RubyBaseNode.nil;
@TruffleLanguage.Registration(
name = "Ruby",
website = "https://www.graalvm.org/ruby/",
contextPolicy = ContextPolicy.SHARED,
id = TruffleRuby.LANGUAGE_ID,
implementationName = TruffleRuby.FORMAL_NAME,
version = TruffleRuby.LANGUAGE_VERSION,
characterMimeTypes = {
RubyLanguage.MIME_TYPE,
RubyLanguage.MIME_TYPE_COVERAGE,
RubyLanguage.MIME_TYPE_MAIN_SCRIPT },
defaultMimeType = RubyLanguage.MIME_TYPE,
dependentLanguages = { "nfi", "llvm", "regex" },
fileTypeDetectors = RubyFileTypeDetector.class)
@ProvidedTags({
CoverageManager.LineTag.class,
TraceManager.CallTag.class,
TraceManager.ClassTag.class,
TraceManager.LineTag.class,
TraceManager.NeverTag.class,
StandardTags.RootTag.class,
StandardTags.StatementTag.class,
StandardTags.ReadVariableTag.class,
StandardTags.WriteVariableTag.class,
})
public final class RubyLanguage extends TruffleLanguage {
/** Do not access directly, instead use {@link #getMimeType(boolean)} */
static final String MIME_TYPE = "application/x-ruby";
public static final String MIME_TYPE_COVERAGE = "application/x-ruby;coverage=true";
public static final String MIME_TYPE_MAIN_SCRIPT = "application/x-ruby;main-script=true";
public static final String[] MIME_TYPES = { MIME_TYPE, MIME_TYPE_COVERAGE, MIME_TYPE_MAIN_SCRIPT };
public static final String LLVM_BITCODE_MIME_TYPE = "application/x-llvm-ir-bitcode";
public static final String CEXT_EXTENSION = Platform.CEXT_SUFFIX;
public static final String RESOURCE_SCHEME = "resource:";
public static final TruffleLogger LOGGER = TruffleLogger.getLogger(TruffleRuby.LANGUAGE_ID);
/** This is a truly empty frame descriptor and should only by dummy root nodes which require no variables. Any other
* root nodes should should use either {@link TranslatorEnvironment#newFrameDescriptorBuilderForMethod()} or
* {@link TranslatorEnvironment#newFrameDescriptorBuilderForBlock(BlockDescriptorInfo)}. */
public static final FrameDescriptor EMPTY_FRAME_DESCRIPTOR = new FrameDescriptor(nil);
private RubyThread getOrCreateForeignThread(RubyContext context, Thread thread) {
RubyThread foreignThread = rubyThreadInitMap.remove(thread);
if (foreignThread == null) {
foreignThread = context.getThreadManager().createForeignThread();
rubyThreadInitMap.put(thread, foreignThread);
}
return foreignThread;
}
public final Map rubyThreadInitMap = new ConcurrentHashMap<>();
private final ContextThreadLocal rubyThread = locals.createContextThreadLocal(
(context, thread) -> {
if (thread == context.getThreadManager().getOrInitializeRootJavaThread()) {
// Already initialized when creating the context
return context.getThreadManager().getRootThread();
}
if (context.getThreadManager().isRubyManagedThread(thread)) {
return Objects.requireNonNull(rubyThreadInitMap.remove(thread));
}
return getOrCreateForeignThread(context, thread);
});
public final Map rubyFiberInitMap = new ConcurrentHashMap<>();
private final ContextThreadLocal rubyFiber = locals.createContextThreadLocal(
(context, thread) -> {
if (thread == context.getThreadManager().getOrInitializeRootJavaThread()) {
// Already initialized when creating the context
return context.getThreadManager().getRootThread().getRootFiber();
}
if (context.getThreadManager().isRubyManagedThread(thread)) {
return Objects.requireNonNull(rubyFiberInitMap.remove(thread));
}
return getOrCreateForeignThread(context, thread).getRootFiber();
});
private final CyclicAssumption tracingCyclicAssumption = new CyclicAssumption("object-space-tracing");
@CompilationFinal private volatile Assumption tracingAssumption = tracingCyclicAssumption.getAssumption();
@CompilationFinal public boolean singleContext = true;
@CompilationFinal public Optional contextIfSingleContext;
private int numberOfContexts = 0;
public final CyclicAssumption traceFuncUnusedAssumption = new CyclicAssumption("set_trace_func is not used");
@CompilationFinal public String coreLoadPath;
@CompilationFinal public String corePath;
public final CoreMethodAssumptions coreMethodAssumptions;
public final CoreStrings coreStrings;
public final CoreSymbols coreSymbols;
public final PrimitiveManager primitiveManager;
public final TStringCache tstringCache;
public final RegexpTable regexpTable;
public final SymbolTable symbolTable;
public final KeywordArgumentsDescriptorManager keywordArgumentsDescriptorManager = new KeywordArgumentsDescriptorManager();
public final FrozenStringLiterals frozenStringLiterals;
// GR-44025: We store the cleanerThread explicitly here to make it a clear image building failure if it would still be set.
public Thread cleanerThread = null;
@CompilationFinal public Cleaner cleaner = null;
@SuppressFBWarnings("VO_VOLATILE_REFERENCE_TO_ARRAY") public volatile ValueWrapperManager.HandleBlockWeakReference[] handleBlockSharedMap = new ValueWrapperManager.HandleBlockWeakReference[0];
public final ValueWrapperManager.HandleBlockAllocator handleBlockAllocator = new ValueWrapperManager.HandleBlockAllocator();
@CompilationFinal public LanguageOptions options;
@CompilationFinal private String rubyHome;
@CompilationFinal public String cextPath;
private TruffleFile rubyHomeTruffleFile;
@CompilationFinal private AllocationReporter allocationReporter;
@CompilationFinal public CoverageManager coverageManager;
private final AtomicLong nextObjectID = new AtomicLong(ObjectSpaceManager.INITIAL_LANGUAGE_OBJECT_ID);
private final PathToTStringCache pathToTStringCache = new PathToTStringCache(this);
public final SharedIndicesMap globalVariablesMap = new SharedIndicesMap();
private final LanguageArray globalVariableNeverAliasedAssumptions = new LanguageArray<>(
globalVariablesMap,
Assumption[]::new,
() -> Assumption.create("global variable was never aliased: "));
private static final RubyObjectType objectType = new RubyObjectType();
public final Shape basicObjectShape = createShape(RubyBasicObject.class);
public final Shape moduleShape = createShape(RubyModule.class);
public final Shape classShape = createShape(RubyClass.class);
public final Shape arrayShape = createShape(RubyArray.class);
public final Shape atomicReferenceShape = createShape(RubyAtomicReference.class);
public final Shape bindingShape = createShape(RubyBinding.class);
public final Shape byteArrayShape = createShape(RubyByteArray.class);
public final Shape concurrentMapShape = createShape(RubyConcurrentMap.class);
public final Shape conditionVariableShape = createShape(RubyConditionVariable.class);
public final Shape customRandomizerShape = createShape(RubyCustomRandomizer.class);
public final Shape digestShape = createShape(RubyDigest.class);
public final Shape encodingConverterShape = createShape(RubyEncodingConverter.class);
public final Shape exceptionShape = createShape(RubyException.class);
public final Shape fiberShape = createShape(RubyFiber.class);
public final Shape frozenErrorShape = createShape(RubyFrozenError.class);
public final Shape handleShape = createShape(RubyHandle.class);
public final Shape hashShape = createShape(RubyHash.class);
public final Shape innerContextShape = createShape(RubyInnerContext.class);
public final Shape ioShape = createShape(RubyIO.class);
public final Shape matchDataShape = createShape(RubyMatchData.class);
public final Shape methodShape = createShape(RubyMethod.class);
public final Shape mutexShape = createShape(RubyMutex.class);
public final Shape nameErrorShape = createShape(RubyNameError.class);
public final Shape noMethodErrorShape = createShape(RubyNoMethodError.class);
public final Shape objectRangeShape = createShape(RubyObjectRange.class);
public final Shape procShape = createShape(RubyProc.class);
public final Shape queueShape = createShape(RubyQueue.class);
public final Shape prngRandomizerShape = createShape(RubyPRNGRandomizer.class);
public final Shape secureRandomizerShape = createShape(RubySecureRandomizer.class);
public final Shape sizedQueueShape = createShape(RubySizedQueue.class);
public final Shape sourceLocationShape = createShape(RubySourceLocation.class);
public final Shape stringShape = createShape(RubyString.class);
public final Shape syntaxErrorShape = createShape(RubySyntaxError.class);
public final Shape systemCallErrorShape = createShape(RubySystemCallError.class);
public final Shape systemExitShape = createShape(RubySystemExit.class);
public final Shape threadBacktraceLocationShape = createShape(RubyBacktraceLocation.class);
public final Shape threadShape = createShape(RubyThread.class);
public final Shape timeShape = createShape(RubyTime.class);
public final Shape tracePointShape = createShape(RubyTracePoint.class);
public final Shape truffleFFIPointerShape = createShape(RubyPointer.class);
public final Shape unboundMethodShape = createShape(RubyUnboundMethod.class);
public final Shape weakMapShape = createShape(RubyWeakMap.class);
public final Shape classVariableShape = Shape
.newBuilder()
.allowImplicitCastIntToLong(true)
.layout(ClassVariableStorage.class)
.build();
public final ThreadLocal parsingRequestParams = new ThreadLocal<>();
/* Some things (such as procs created from symbols) require a declaration frame, and this should include a slot for
* special variable storage. This frame descriptor should be used for those frames to provide a constant frame
* descriptor in those cases. */
public final FrameDescriptor emptyDeclarationDescriptor = TranslatorEnvironment
.newFrameDescriptorBuilderForMethod().build();
public MaterializedFrame createEmptyDeclarationFrame(Object[] packedArgs, SpecialVariableStorage variables) {
// createVirtualFrame().materialize() compiles better if this is in PE code
final MaterializedFrame declarationFrame = Truffle
.getRuntime()
.createVirtualFrame(packedArgs, emptyDeclarationDescriptor)
.materialize();
SpecialVariableStorage.set(declarationFrame, variables);
return declarationFrame;
}
private static final LanguageReference REFERENCE = LanguageReference.create(RubyLanguage.class);
public static RubyLanguage get(Node node) {
return REFERENCE.get(node);
}
public static String getMimeType(boolean coverageEnabled) {
return coverageEnabled ? MIME_TYPE_COVERAGE : MIME_TYPE;
}
public RubyLanguage() {
coreMethodAssumptions = new CoreMethodAssumptions(this);
coreStrings = new CoreStrings(this);
coreSymbols = new CoreSymbols();
primitiveManager = new PrimitiveManager();
tstringCache = new TStringCache(coreSymbols);
symbolTable = new SymbolTable(tstringCache, coreSymbols);
regexpTable = new RegexpTable();
frozenStringLiterals = new FrozenStringLiterals(tstringCache);
}
public RubyThread getCurrentThread() {
return rubyThread.get();
}
public RubyFiber getCurrentFiber() {
return rubyFiber.get();
}
@TruffleBoundary
public RubyRegexp getRegexp(RegexpCacheKey regexp) {
return regexpTable.getRegexpIfExists(regexp);
}
@TruffleBoundary
public void addRegexp(RegexpCacheKey key, RubyRegexp regexp) {
regexpTable.addRegexp(key, regexp);
}
@TruffleBoundary
public RubySymbol getSymbol(String string) {
return symbolTable.getSymbol(string);
}
@TruffleBoundary
public RubySymbol getSymbol(AbstractTruffleString name, RubyEncoding encoding) {
return symbolTable.getSymbol(name, encoding, false);
}
@TruffleBoundary
public RubySymbol getSymbol(AbstractTruffleString name, RubyEncoding encoding, boolean preserveSymbol) {
return symbolTable.getSymbol(name, encoding, preserveSymbol);
}
public Assumption getTracingAssumption() {
return tracingAssumption;
}
public void invalidateTracingAssumption() {
tracingCyclicAssumption.invalidate();
tracingAssumption = tracingCyclicAssumption.getAssumption();
}
private boolean multiThreading = false;
public boolean isMultiThreaded() {
return multiThreading;
}
@Override
protected void initializeMultiThreading(RubyContext context) {
this.multiThreading = true;
}
@Override
protected void initializeMultipleContexts() {
LOGGER.fine("initializeMultipleContexts()");
// TODO Make Symbol.all_symbols per context, by having a SymbolTable per context and creating new symbols with
// the per-language SymbolTable.
if (contextIfSingleContext == null) { // before first context created
this.singleContext = false;
} else {
throw CompilerDirectives
.shouldNotReachHere("#initializeMultipleContexts() called after a context was created");
}
}
@Override
public RubyContext createContext(Env env) {
// We need to initialize the Metrics class of the language classloader
Metrics.initializeOption();
boolean firstContext;
synchronized (this) {
numberOfContexts++;
setupCleaner();
firstContext = this.options == null;
if (firstContext) { // First context
this.allocationReporter = env.lookup(AllocationReporter.class);
this.options = new LanguageOptions(env, env.getOptions(), singleContext);
setRubyHome(findRubyHome(env));
setupLocale(env, rubyHome);
loadLibYARPBindings();
this.coreLoadPath = buildCoreLoadPath(this.options.CORE_LOAD_PATH);
this.corePath = coreLoadPath + File.separator + "core" + File.separator;
Instrumenter instrumenter = Objects.requireNonNull(env.lookup(Instrumenter.class));
this.coverageManager = new CoverageManager(options, instrumenter);
if (options.INSTRUMENT_ALL_NODES) {
instrumentAllNodes(instrumenter);
}
primitiveManager.loadCoreMethodNodes(this.options);
}
}
// Set rubyHomeTruffleFile every time, as pre-initialized contexts use a different FileSystem
if (!firstContext) {
final String oldHome = this.rubyHome;
final TruffleFile newHome = findRubyHome(env);
if (!Objects.equals(newHome.getPath(), oldHome)) {
throw CompilerDirectives.shouldNotReachHere(
"home changed for the same RubyLanguage instance: " + oldHome + " vs " + newHome);
}
rubyHomeTruffleFile = newHome;
}
LOGGER.fine("createContext() on " + Thread.currentThread());
Metrics.printTime("before-create-context");
final RubyContext context = new RubyContext(this, env);
Metrics.printTime("after-create-context");
if (singleContext) {
contextIfSingleContext = Optional.of(context);
} else {
contextIfSingleContext = Optional.empty();
}
return context;
}
@Override
protected void initializeContext(RubyContext context) {
LOGGER.fine("initializeContext() on " + Thread.currentThread());
try {
Metrics.printTime("before-initialize-context");
context.initialize();
if (context.isPreInitializing()) {
synchronized (this) {
resetRubyHome();
resetCleaner();
}
}
Metrics.printTime("after-initialize-context");
} catch (Throwable e) {
if (context.getOptions().EXCEPTIONS_PRINT_JAVA || context.getOptions().EXCEPTIONS_PRINT_UNCAUGHT_JAVA) {
e.printStackTrace();
}
throw e;
}
applicationStarts();
}
private void applicationStarts() {
// Set breakpoints on this line to break when user code is about to be loaded
return;
}
@Override
protected boolean patchContext(RubyContext context, Env newEnv) {
// We need to initialize the Metrics class of the language classloader
Metrics.initializeOption();
LOGGER.fine("patchContext() on " + Thread.currentThread() + ")");
Metrics.printTime("before-patch-context");
final LanguageOptions oldOptions = Objects.requireNonNull(this.options);
final LanguageOptions newOptions = new LanguageOptions(newEnv, newEnv.getOptions(), singleContext);
if (!LanguageOptions.areOptionsCompatibleOrLog(LOGGER, oldOptions, newOptions)) {
return false;
}
synchronized (this) {
setRubyHome(findRubyHome(newEnv));
setupLocale(newEnv, rubyHome);
loadLibYARPBindings();
setupCleaner();
}
boolean patched = context.patchContext(newEnv);
Metrics.printTime("after-patch-context");
return patched;
}
@Override
protected void finalizeContext(RubyContext context) {
LOGGER.fine("finalizeContext() on " + Thread.currentThread());
context.finalizeContext();
}
@Override
protected void disposeContext(RubyContext context) {
LOGGER.fine("disposeContext() on " + Thread.currentThread());
context.disposeContext();
if (options.COVERAGE_GLOBAL) {
coverageManager.print(this, System.out);
}
synchronized (this) {
// GR-28354: Workaround for no "before CacheStore hook"
numberOfContexts--;
if (numberOfContexts == 0 && !singleContext) {
resetCleaner();
}
}
}
public static RubyContext getCurrentContext() {
CompilerAsserts.neverPartOfCompilation("Use getContext() or RubyContext.get(Node) instead in PE code");
return RubyContext.get(null);
}
public static RubyLanguage getCurrentLanguage() {
CompilerAsserts.neverPartOfCompilation("Use getLanguage() or RubyLanguage.get(Node) instead in PE code");
return RubyLanguage.get(null);
}
@Override
protected RootCallTarget parse(ParsingRequest request) {
final Source source = request.getSource();
final ParsingParameters parsingParameters = parsingRequestParams.get();
if (parsingParameters != null) { // from #require or core library
assert parsingParameters.rubySource.getSource().equals(source);
final ParserContext parserContext = MIME_TYPE_MAIN_SCRIPT.equals(source.getMimeType())
? ParserContext.TOP_LEVEL_FIRST
: ParserContext.TOP_LEVEL;
final LexicalScope lexicalScope = contextIfSingleContext.map(RubyContext::getRootLexicalScope).orElse(null);
return getCurrentContext().getCodeLoader().parse(
parsingParameters.rubySource,
parserContext,
null,
lexicalScope,
parsingParameters.currentNode);
}
RootNode root;
if (source.isInteractive()) {
root = new RubyEvalInteractiveRootNode(this, source);
} else {
final RubyContext context = Objects.requireNonNull(getCurrentContext());
root = new RubyParsingRequestNode(
this,
context,
source,
request.getArgumentNames().toArray(StringUtils.EMPTY_STRING_ARRAY));
}
return root.getCallTarget();
}
@Override
protected ExecutableNode parse(InlineParsingRequest request) {
final RubyContext context = Objects.requireNonNull(getCurrentContext());
return new RubyInlineParsingRequestNode(this, context, request.getSource(), request.getFrame());
}
@Override
protected OptionDescriptors getOptionDescriptors() {
return OptionDescriptors.create(Arrays.asList(OptionsCatalog.allDescriptors()));
}
@Override
protected boolean isThreadAccessAllowed(Thread thread, boolean singleThreaded) {
return true;
}
@Override
public void initializeThread(RubyContext context, Thread thread) {
LOGGER.fine(() -> "initializeThread(" + showThread(thread) + ") on " + Thread.currentThread());
if (thread == context.getThreadManager().getOrInitializeRootJavaThread()) {
// Already initialized when creating the context
return;
}
if (context.getThreadManager().isRubyManagedThread(thread)) {
final RubyThread rubyThread = this.rubyThread.get(thread);
if (rubyThread.thread == thread) { // new Ruby Thread
if (thread != Thread.currentThread()) {
throw CompilerDirectives
.shouldNotReachHere("Ruby threads should be initialized on their Java thread");
}
context.getThreadManager().start(rubyThread, thread);
} else { // (non-root) Fiber
var fiber = this.rubyFiber.get(thread);
rubyThread.setCurrentFiber(fiber);
}
return;
}
final RubyThread foreignThread = this.rubyThread.get(thread);
context.getThreadManager().startForeignThread(foreignThread, thread);
}
@Override
public void disposeThread(RubyContext context, Thread thread) {
LOGGER.fine(() -> "disposeThread(" + showThread(thread) + ") on " + Thread.currentThread());
if (thread == context.getThreadManager().getRootJavaThread()) {
if (context.getEnv().isPreInitialization()) {
// Cannot save the root Java Thread instance in the image
context.getThreadManager().resetMainThread();
context.getThreadManager().dispose();
return;
} else if (!context.isInitialized()) {
// Context patching failed, we cannot cleanup the main thread as it was not initialized
return;
} else {
// Cleanup the main thread, this is done between finalizeContext() and disposeContext()
context.getThreadManager().cleanupThreadState(context.getThreadManager().getRootThread(), thread);
return;
}
}
if (context.getThreadManager().isRubyManagedThread(thread)) {
final RubyThread rubyThread = this.rubyThread.get(thread);
if (rubyThread.thread == thread) { // Thread
if (thread != Thread.currentThread()) {
throw CompilerDirectives.shouldNotReachHere("Ruby threads should be disposed on their Java thread");
}
context.getThreadManager().cleanupThreadState(rubyThread, thread);
} else { // (non-root) Fiber
var fiber = this.rubyFiber.get(thread);
context.fiberManager.cleanup(fiber, thread);
}
return;
}
// A foreign Thread, its Fibers are considered isRubyManagedThread()
final RubyThread foreignThread = this.rubyThread.get(thread);
context.getThreadManager().cleanup(foreignThread, thread);
}
private String showThread(Thread thread) {
return "#" + getThreadId(thread) + " " + thread + " = " + this.rubyThread.get(thread);
}
@Override
protected Object getScope(RubyContext context) {
return context.getTopScopeObject();
}
private static void instrumentAllNodes(Instrumenter instrumenter) {
instrumenter.attachExecutionEventListener(SourceSectionFilter.ANY, new ExecutionEventListener() {
@Override
public void onEnter(EventContext context, VirtualFrame frame) {
}
@Override
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
}
@Override
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
}
});
}
private void setupCleaner() {
assert Thread.holdsLock(this);
if (cleaner == null) {
cleaner = Cleaner.create(runnable -> this.cleanerThread = new Thread(runnable, "Ruby-Cleaner"));
}
}
private void resetCleaner() {
assert Thread.holdsLock(this);
cleanerThread = null;
cleaner = null;
}
public String getRubyHome() {
return rubyHome;
}
public TruffleFile getRubyHomeTruffleFile() {
return rubyHomeTruffleFile;
}
public String getPathRelativeToHome(String path) {
if (path.startsWith(rubyHome) && path.length() > rubyHome.length()) {
return path.substring(rubyHome.length() + 1);
} else {
return path;
}
}
private void setRubyHome(TruffleFile home) {
assert Thread.holdsLock(this);
rubyHomeTruffleFile = home;
rubyHome = home.getPath();
cextPath = rubyHome + "/lib/truffle/truffle/cext_ruby.rb";
}
private void resetRubyHome() {
assert Thread.holdsLock(this);
rubyHomeTruffleFile = null;
rubyHome = null;
cextPath = null;
}
private TruffleFile findRubyHome(Env env) {
final TruffleFile home = searchRubyHome(env);
if (LOGGER.isLoggable(Level.CONFIG)) {
LOGGER.config("home: " + home);
}
return home;
}
// Returns a canonical path to the home
private TruffleFile searchRubyHome(Env env) {
final String truffleReported = getLanguageHome();
if (truffleReported != null) {
var truffleReportedFile = env.getInternalTruffleFile(truffleReported);
try {
if (truffleReportedFile.exists()) {
truffleReportedFile = truffleReportedFile.getCanonicalFile();
}
} catch (IOException e) {
throw CompilerDirectives.shouldNotReachHere(e);
}
if (isRubyHome(truffleReportedFile)) {
LOGGER.config(() -> String.format("Using Truffle-reported home %s as the Ruby home", truffleReported));
return truffleReportedFile;
}
}
TruffleFile homeResource;
try {
var homeResourceRelative = env.getInternalResource("ruby-home");
homeResource = homeResourceRelative == null ? null : homeResourceRelative.getCanonicalFile();
} catch (IOException e) {
throw CompilerDirectives.shouldNotReachHere(e);
}
if (homeResource != null) {
if (isRubyHome(homeResource)) {
LOGGER.config(() -> String.format("Using the internal resource %s as the Ruby home", homeResource));
return homeResource;
}
}
throw new Error("Could not find TruffleRuby's home - not possible to parse Ruby code" + String.format(
" (Truffle-reported home %s and internal resource %s do not look like TruffleRuby's home).",
truffleReported, homeResource));
}
private boolean isRubyHome(TruffleFile path) {
var lib = path.resolve("lib");
return lib.resolve("truffle").isDirectory() &&
(options.BUILDING_CORE_CEXTS || lib.resolve("gems").isDirectory()) &&
lib.resolve("patches").isDirectory();
}
private void setupLocale(Env env, String rubyHome) {
// CRuby does setlocale(LC_CTYPE, "") because this is needed to get the locale encoding with nl_langinfo(CODESET).
// This means every locale category except LC_CTYPE remains the initial "C".
// LC_CTYPE is set according to environment variables (LC_ALL, LC_CTYPE, LANG).
// HotSpot does setlocale(LC_ALL, "") and Native Image does nothing.
// We match CRuby by doing setlocale(LC_ALL, "C") and setlocale(LC_CTYPE, "").
// This also affects C functions that depend on the locale in C extensions, so best to follow CRuby here.
// Change the strict minimum if embedded because setlocale() is process-wide.
if (env.getOptions().get(OptionsCatalog.EMBEDDED_KEY)) {
if (ImageInfo.inImageRuntimeCode()) {
ProcessProperties.setLocale("LC_CTYPE", "");
}
} else {
LibRubySignal.loadLibrary(rubyHome, Platform.LIB_SUFFIX);
LibRubySignal.setupLocale();
}
}
private void loadLibYARPBindings() {
String libyarpbindings = getRubyHome() + "/lib/libyarpbindings" + Platform.LIB_SUFFIX;
Parser.loadLibrary(libyarpbindings);
}
@SuppressFBWarnings("IS2_INCONSISTENT_SYNC")
public AllocationReporter getAllocationReporter() {
return allocationReporter;
}
public ImmutableRubyString getFrozenStringLiteral(TruffleString tstring, RubyEncoding encoding) {
return frozenStringLiterals.getFrozenStringLiteral(tstring, encoding);
}
public ImmutableRubyString getFrozenStringLiteral(InternalByteArray byteArray, boolean isImmutable,
RubyEncoding encoding) {
return frozenStringLiterals.getFrozenStringLiteral(byteArray, isImmutable, encoding);
}
public long getNextObjectID() {
final long id = nextObjectID.getAndAdd(ObjectSpaceManager.OBJECT_ID_INCREMENT_BY);
if (id == ObjectSpaceManager.INITIAL_LANGUAGE_OBJECT_ID - ObjectSpaceManager.OBJECT_ID_INCREMENT_BY) {
throw CompilerDirectives.shouldNotReachHere("Language Object IDs exhausted");
}
return id;
}
public PathToTStringCache getPathToTStringCache() {
return pathToTStringCache;
}
private static Shape createShape(Class extends RubyDynamicObject> layoutClass) {
return Shape
.newBuilder()
.allowImplicitCastIntToLong(true)
.layout(layoutClass)
.dynamicType(RubyLanguage.objectType)
.build();
}
@Override
protected boolean areOptionsCompatible(OptionValues firstOptions, OptionValues newOptions) {
final boolean compatible = checkAreOptionsCompatible(firstOptions, newOptions);
LOGGER.fine(compatible ? "areOptionsCompatible() -> true" : "areOptionsCompatible() -> false");
return compatible;
}
private boolean checkAreOptionsCompatible(OptionValues firstOptions, OptionValues newOptions) {
if (firstOptions.get(OptionsCatalog.RUN_TWICE_KEY) ||
firstOptions.get(OptionsCatalog.EXPERIMENTAL_ENGINE_CACHING_KEY)) {
return LanguageOptions.areOptionsCompatible(firstOptions, newOptions);
} else {
return false;
}
}
/** {@link RubyLanguage#getSourcePath(Source)} should be used instead whenever possible (i.e., when we can access
* the language).
*
* Returns the path of a Source. Returns the short, potentially relative, path for the main script. Note however
* that the path of {@code eval(code, nil, filename)} is just {@code filename} and might not be absolute. */
public static String getPath(Source source) {
final String path = source.getPath();
if (path != null) {
return path;
} else {
// non-file sources: eval(), main_boot_source, etc
final String name = source.getName();
assert name != null;
return name;
}
}
/** {@link RubyLanguage#getPath(Source)} but also handles core library sources. Ideally this method would be static
* but for now the core load path is an option and it also depends on the current working directory. Once we have
* Source metadata in Truffle we could use that to identify core library sources without needing the language. */
@TruffleBoundary
public String getSourcePath(Source source) {
final String path = getPath(source);
if (path.startsWith(coreLoadPath)) {
return " " + path.substring(coreLoadPath.length() + 1);
} else {
return path;
}
}
@TruffleBoundary
public static String getCorePath(Source source) {
final String path = getPath(source);
String coreLoadPath = OptionsCatalog.CORE_LOAD_PATH_KEY.getDefaultValue();
if (path.startsWith(coreLoadPath)) {
return " " + path.substring(coreLoadPath.length() + 1);
} else {
throw CompilerDirectives.shouldNotReachHere(path + " is not a core path starting with " + coreLoadPath);
}
}
/** Only use when no language/context is available (e.g. Node#toString). Prefer
* {@link RubyContext#fileLine(SourceSection)} as it accounts for coreLoadPath and line offsets. */
@TruffleBoundary
public static String fileLineRange(SourceSection section) {
if (section == null) {
return "no source section";
} else {
final String path = getPath(section.getSource());
if (section.isAvailable()) {
if (section.getStartLine() != section.getEndLine()) {
return path + ":" + section.getStartLine() + "-" + section.getEndLine();
} else {
return path + ":" + section.getStartLine();
}
} else {
return path;
}
}
}
/** Prefer {@link RubyContext#fileLine(SourceSection)} as it is more concise. */
@TruffleBoundary
String fileLine(RubyContext context, SourceSection section) {
if (section == null) {
return "no source section";
} else {
final String path = getSourcePath(section.getSource());
if (section.isAvailable()) {
return path + ":" + RubySource.getStartLineAdjusted(context, section);
} else {
return path;
}
}
}
/** Only use when no language/context is available (e.g. Node#toString). Prefer
* {@link RubyContext#fileLine(SourceSection)} as it accounts for coreLoadPath and line offsets. */
@TruffleBoundary
public static String filenameLine(SourceSection section) {
if (section == null) {
return "no source section";
} else {
final String path = getPath(section.getSource());
final String filename = new File(path).getName();
if (section.isAvailable()) {
return filename + ":" + section.getStartLine();
} else {
return filename;
}
}
}
public Object rubySourceLocation(RubyContext context, SourceSection section,
TruffleString.FromJavaStringNode fromJavaStringNode,
Node node) {
if (!BacktraceFormatter.isAvailable(section)) {
return nil;
} else {
var file = createString(node, fromJavaStringNode, getSourcePath(section.getSource()), Encodings.UTF_8);
Object[] objects = new Object[]{ file, RubySource.getStartLineAdjusted(context, section) };
return createArray(node, objects);
}
}
public int getGlobalVariableIndex(String name) {
return globalVariablesMap.lookup(name);
}
@TruffleBoundary
public Assumption getGlobalVariableNeverAliasedAssumption(int index) {
return globalVariableNeverAliasedAssumptions.get(index);
}
private static String buildCoreLoadPath(String coreLoadPath) {
while (coreLoadPath.endsWith("/")) {
coreLoadPath = coreLoadPath.substring(0, coreLoadPath.length() - 1);
}
if (coreLoadPath.startsWith(RubyLanguage.RESOURCE_SCHEME)) {
return coreLoadPath;
}
try {
return new File(coreLoadPath).getCanonicalPath();
} catch (IOException e) {
throw CompilerDirectives.shouldNotReachHere(e);
}
}
@SuppressWarnings("deprecation") // deprecated on JDK19 by Thread#threadId, but that's added in JDK19
public static long getThreadId(Thread thread) {
return thread.getId();
}
}