Skip to content

Commit

Permalink
[Truffle] Make require ready for compilation and add fine-grained bou…
Browse files Browse the repository at this point in the history
…ndaries.

* Keep direct references to storage of well-known global variables.
* Extract lookupForExistingModule() in its own node since it's shared.
* Support some global variable aliases.
eregon committed Jun 5, 2016
1 parent 045140f commit 70b51aa
Showing 13 changed files with 384 additions and 298 deletions.
21 changes: 14 additions & 7 deletions truffle/src/main/java/org/jruby/truffle/core/CoreLibrary.java
Original file line number Diff line number Diff line change
@@ -93,6 +93,7 @@
import org.jruby.truffle.language.backtrace.BacktraceFormatter;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.truffle.language.control.TruffleFatalException;
import org.jruby.truffle.language.globals.GlobalVariableStorage;
import org.jruby.truffle.language.globals.GlobalVariables;
import org.jruby.truffle.language.loader.CodeLoader;
import org.jruby.truffle.language.loader.SourceLoader;
@@ -114,7 +115,6 @@
import org.jruby.truffle.stdlib.psych.PsychParserNodesFactory;
import org.jruby.truffle.stdlib.psych.YAMLEncoding;
import org.jruby.util.cli.OutputStrings;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@@ -245,6 +245,9 @@ public class CoreLibrary {

@CompilationFinal private InternalMethod basicObjectSendMethod;

@CompilationFinal private GlobalVariableStorage loadPathStorage;
@CompilationFinal private GlobalVariableStorage loadedFeaturesStorage;

private static final Object systemObject = TruffleOptions.AOT ? null : JavaInterop.asTruffleObject(System.class);

public String getCoreLoadPath() {
@@ -773,10 +776,14 @@ public void addCoreMethods() {
private void initializeGlobalVariables() {
GlobalVariables globals = globalVariables;

globals.put("$LOAD_PATH", Layouts.ARRAY.createArray(Layouts.CLASS.getInstanceFactory(arrayClass), null, 0));
globals.put("$LOADED_FEATURES", Layouts.ARRAY.createArray(Layouts.CLASS.getInstanceFactory(arrayClass), new Object[0], 0));
globals.put("$:", globals.getOrDefault("$LOAD_PATH", nilObject));
globals.put("$\"", globals.getOrDefault("$LOADED_FEATURES", nilObject));
loadPathStorage = globals.put("$LOAD_PATH",
Layouts.ARRAY.createArray(Layouts.CLASS.getInstanceFactory(arrayClass), null, 0));
globals.alias("$:", loadPathStorage);

loadedFeaturesStorage = globals.put("$LOADED_FEATURES",
Layouts.ARRAY.createArray(Layouts.CLASS.getInstanceFactory(arrayClass), null, 0));
globals.alias("$\"", loadedFeaturesStorage);

globals.put("$,", nilObject);
globals.put("$*", argv);
globals.put("$0", StringOperations.createString(context, StringOperations.encodeRope(context.getJRubyRuntime().getInstanceConfig().displayedFileName(), UTF8Encoding.INSTANCE)));
@@ -1204,11 +1211,11 @@ public GlobalVariables getGlobalVariables() {
}

public DynamicObject getLoadPath() {
return (DynamicObject) globalVariables.getOrDefault("$LOAD_PATH", context.getCoreLibrary().getNilObject());
return (DynamicObject) loadPathStorage.value;
}

public DynamicObject getLoadedFeatures() {
return (DynamicObject) globalVariables.getOrDefault("$LOADED_FEATURES", context.getCoreLibrary().getNilObject());
return (DynamicObject) loadedFeaturesStorage.value;
}

public DynamicObject getMainObject() {
Original file line number Diff line number Diff line change
@@ -100,6 +100,7 @@
import org.jruby.truffle.language.dispatch.MissingBehavior;
import org.jruby.truffle.language.loader.CodeLoader;
import org.jruby.truffle.language.loader.FeatureLoader;
import org.jruby.truffle.language.loader.RequireNode;
import org.jruby.truffle.language.loader.SourceLoader;
import org.jruby.truffle.language.methods.DeclarationContext;
import org.jruby.truffle.language.methods.InternalMethod;
@@ -1379,36 +1380,34 @@ public Object send(VirtualFrame frame, Object self, Object name, Object[] args,
}

@CoreMethod(names = "require", isModuleFunction = true, required = 1, unsafe = UnsafeGroup.LOAD)
@NodeChildren({
@NodeChild(type = RubyNode.class, value = "feature")
})
public abstract static class RequireNode extends CoreMethodNode {
@NodeChild(type = RubyNode.class, value = "feature")
public abstract static class KernelRequireNode extends CoreMethodNode {

@CreateCast("feature")
public RubyNode coerceFeatureToPath(RubyNode feature) {
return ToPathNodeGen.create(null, null, feature);
}

@Specialization(guards = "isRubyString(featureString)")
public boolean require(VirtualFrame frame, DynamicObject featureString, @Cached("create()") IndirectCallNode callNode) {
CompilerDirectives.bailout("require cannot be compiled but needs the frame");
public boolean require(VirtualFrame frame, DynamicObject featureString,
@Cached("create()") RequireNode requireNode) {

final String feature = featureString.toString();
String feature = StringOperations.getString(getContext(), featureString);

// Pysch loads either the jar or the so - we need to intercept
if (feature.equals("psych.so") && callerIs("stdlib/psych.rb")) {
getContext().getFeatureLoader().require(frame, "truffle/psych.rb", callNode);
return true;
feature = "truffle/psych.rb";
}

// TODO CS 1-Mar-15 ERB will use strscan if it's there, but strscan is not yet complete, so we need to hide it
if (feature.equals("strscan") && callerIs("stdlib/erb.rb")) {
throw new RaiseException(coreExceptions().loadErrorCannotLoad(feature, this));
}

return getContext().getFeatureLoader().require(frame, feature, callNode);
return requireNode.executeRequire(frame, feature);
}

@TruffleBoundary
private boolean callerIs(String caller) {
for (Activation activation : getContext().getCallStack().getBacktrace(this).getActivations()) {

@@ -1427,12 +1426,16 @@ private boolean callerIs(String caller) {
public abstract static class RequireRelativeNode extends CoreMethodArrayArgumentsNode {

@Specialization(guards = "isRubyString(feature)")
public boolean requireRelative(VirtualFrame frame, DynamicObject feature, @Cached("create()") IndirectCallNode callNode) {
CompilerDirectives.bailout("require cannot be compiled but needs the frame");
public boolean requireRelative(VirtualFrame frame, DynamicObject feature,
@Cached("create()") RequireNode requireNode) {
final String featureString = StringOperations.getString(getContext(), feature);
final String featurePath = getFullPath(featureString);

final FeatureLoader featureLoader = getContext().getFeatureLoader();
return requireNode.executeRequire(frame, featurePath);
}

final String featureString = feature.toString();
@TruffleBoundary
private String getFullPath(final String featureString) {
final String featurePath;

if (featureString.startsWith(SourceLoader.TRUFFLE_SCHEME) || featureString.startsWith(SourceLoader.JRUBY_SCHEME) || new File(featureString).isAbsolute()) {
@@ -1454,10 +1457,7 @@ public boolean requireRelative(VirtualFrame frame, DynamicObject feature, @Cache

featurePath = dirname(sourcePath) + "/" + featureString;
}

featureLoader.require(frame, featurePath, callNode);

return true;
return featurePath;
}

private String dirname(String path) {
Original file line number Diff line number Diff line change
@@ -51,8 +51,6 @@
import org.jruby.truffle.core.cast.ToPathNodeGen;
import org.jruby.truffle.core.cast.ToStrNode;
import org.jruby.truffle.core.cast.ToStrNodeGen;
import org.jruby.truffle.core.kernel.KernelNodes;
import org.jruby.truffle.core.kernel.KernelNodesFactory;
import org.jruby.truffle.core.method.MethodFilter;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.string.StringNodes;
@@ -74,6 +72,7 @@
import org.jruby.truffle.language.dispatch.CallDispatchHeadNode;
import org.jruby.truffle.language.dispatch.DispatchHeadNodeFactory;
import org.jruby.truffle.language.loader.CodeLoader;
import org.jruby.truffle.language.loader.RequireNode;
import org.jruby.truffle.language.methods.AddMethodNode;
import org.jruby.truffle.language.methods.AddMethodNodeGen;
import org.jruby.truffle.language.methods.Arity;
@@ -837,8 +836,7 @@ public boolean isConstDefined(DynamicObject module, String fullName, boolean inh
})
public abstract static class ConstGetNode extends CoreMethodNode {

@Child private KernelNodes.RequireNode requireNode;
@Child private IndirectCallNode indirectCallNode;
@Child private RequireNode requireNode;

@Child LookupConstantNode lookupConstantNode = LookupConstantNode.create(true, true);
@Child GetConstantNode getConstantNode = GetConstantNode.create();
@@ -923,15 +921,11 @@ boolean isScoped(DynamicObject name) {
private void loadAutoloadedConstant(VirtualFrame frame, RubyConstant constant) {
if (requireNode == null) {
CompilerDirectives.transferToInterpreter();
requireNode = insert(KernelNodesFactory.RequireNodeFactory.create(null));
requireNode = insert(RequireNode.create());
}

if (indirectCallNode == null) {
CompilerDirectives.transferToInterpreter();
indirectCallNode = insert(IndirectCallNode.create());
}

requireNode.require(frame, (DynamicObject) constant.getValue(), indirectCallNode);
final String feature = StringOperations.getString(getContext(), (DynamicObject) constant.getValue());
requireNode.executeRequire(frame, feature);
}

}
Original file line number Diff line number Diff line change
@@ -92,6 +92,7 @@ private void assumptionInvalidated(Node currentNode, boolean fromBlockingCall) {

if (!interruptible) {
Thread.currentThread().interrupt(); // keep the interrupt flag
System.err.println("re-interrupting " + thread);
return; // interrupt me later
}

Original file line number Diff line number Diff line change
@@ -15,17 +15,16 @@
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.profiles.ConditionProfile;
import org.jruby.truffle.Layouts;
import org.jruby.truffle.core.kernel.KernelNodes.RequireNode;
import org.jruby.truffle.core.kernel.KernelNodesFactory;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.RubyConstant;
import org.jruby.truffle.language.RubyNode;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.truffle.language.dispatch.CallDispatchHeadNode;
import org.jruby.truffle.language.dispatch.DispatchHeadNodeFactory;
import org.jruby.truffle.language.loader.RequireNode;
import org.jruby.util.IdUtil;

@NodeChildren({ @NodeChild("module"), @NodeChild("name"), @NodeChild("constant"), @NodeChild("lookupConstantNode") })
@@ -47,16 +46,15 @@ protected Object getConstant(DynamicObject module, String name, RubyConstant con

@Specialization(guards = { "constant != null", "constant.isAutoload()" })
protected Object autoloadConstant(VirtualFrame frame, DynamicObject module, String name, RubyConstant constant, LookupConstantInterface lookupConstantNode,
@Cached("createRequireNode()") RequireNode requireNode,
@Cached("create()") IndirectCallNode callNode) {
@Cached("create()") RequireNode requireNode) {

final DynamicObject path = (DynamicObject) constant.getValue();

// The autoload constant must only be removed if everything succeeds.
// We remove it first to allow lookup to ignore it and add it back if there was a failure.
Layouts.MODULE.getFields(constant.getDeclaringModule()).removeConstant(getContext(), this, name);
try {
requireNode.require(frame, path, callNode);
requireNode.executeRequire(frame, StringOperations.getString(getContext(), path));
final RubyConstant resolvedConstant = lookupConstantNode.lookupConstant(frame, module, name);
return executeGetConstant(frame, module, name, resolvedConstant, lookupConstantNode);
} catch (RaiseException e) {
@@ -110,10 +108,6 @@ private String formatError(String name) {
return String.format("wrong constant name %s", name);
}

protected RequireNode createRequireNode() {
return KernelNodesFactory.RequireNodeFactory.create(null);
}

protected boolean isValidConstantName(String name) {
return IdUtil.isValidConstantName19(name);
}
Original file line number Diff line number Diff line change
@@ -50,8 +50,14 @@ public GlobalVariableStorage getStorage(String key) {
}
}

public void put(String key, Object value) {
getStorage(key).value = value;
public GlobalVariableStorage put(String key, Object value) {
GlobalVariableStorage storage = getStorage(key);
storage.value = value;
return storage;
}

public void alias(String name, GlobalVariableStorage storage) {
variables.put(name, storage);
}

public Collection<DynamicObject> dynamicObjectValues() {
Original file line number Diff line number Diff line change
@@ -10,32 +10,15 @@
package org.jruby.truffle.language.loader;

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.ForeignAccess;
import com.oracle.truffle.api.interop.Message;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.source.Source;
import org.jcodings.specific.UTF8Encoding;
import org.jruby.truffle.Layouts;
import java.io.File;
import java.io.IOException;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.RubyLanguage;
import org.jruby.truffle.core.array.ArrayOperations;
import org.jruby.truffle.core.array.ArrayUtils;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.RubyRootNode;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.truffle.language.methods.DeclarationContext;
import org.jruby.truffle.language.parser.ParserContext;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.locks.ReentrantLock;

public class FeatureLoader {

@@ -50,19 +33,12 @@ public FeatureLoader(RubyContext context) {
this.context = context;
}

public boolean require(VirtualFrame frame, String feature, IndirectCallNode callNode) {
final String featurePath = findFeature(feature);

if (featurePath == null) {
throw new RaiseException(context.getCoreExceptions().loadErrorCannotLoad(
feature,
callNode));
}

return doRequire(frame, featurePath, callNode);
public ReentrantLockFreeingMap<String> getFileLocks() {
return fileLocks;
}

private String findFeature(String feature) {
@TruffleBoundary
public String findFeature(String feature) {
final String currentDirectory = context.getNativePlatform().getPosix().getcwd();

if (feature.startsWith("./")) {
@@ -137,192 +113,44 @@ private String findFeatureWithExactPath(String path) {
}
}

private boolean doRequire(
final VirtualFrame frame,
final String expandedPath,
final IndirectCallNode callNode) {

if (isFeatureLoaded(expandedPath)) {
return false;
}

while (true) {
final ReentrantLock lock = fileLocks.get(expandedPath);

if (lock.isHeldByCurrentThread()) {
// circular require
// TODO (pitr-ch 20-Mar-2016): warn user
return false;
}

if (!fileLocks.lock(callNode, context.getThreadManager(), expandedPath, lock)) {
continue;
}

try {
if (isFeatureLoaded(expandedPath)) {
return false;
}

final Source source;

try {
source = context.getSourceCache().getSource(expandedPath);
} catch (IOException e) {
return false;
}

final String mimeType = source.getMimeType();

switch (mimeType) {
case RubyLanguage.MIME_TYPE: {
final RubyRootNode rootNode = context.getCodeLoader().parse(
source,
UTF8Encoding.INSTANCE,
ParserContext.TOP_LEVEL,
null,
true,
callNode);

final CodeLoader.DeferredCall deferredCall = context.getCodeLoader().prepareExecute(
ParserContext.TOP_LEVEL,
DeclarationContext.TOP_LEVEL,
rootNode,
null,
context.getCoreLibrary().getMainObject());

deferredCall.call(frame, callNode);
}
break;

case RubyLanguage.CEXT_MIME_TYPE: {
ensureCExtImplementationLoaded(frame, callNode);

final CallTarget callTarget;

try {
callTarget = context.getEnv().parse(source);
} catch (IOException e) {
throw new RuntimeException(e);
}

callNode.call(frame, callTarget, new Object[]{});

final Object initFunction = context.getEnv().importSymbol("@Init_" + getBaseName(
expandedPath));

if (!(initFunction instanceof TruffleObject)) {
throw new UnsupportedOperationException();
}

final TruffleObject initFunctionObject = (TruffleObject) initFunction;

final Node isExecutableNode = Message.IS_EXECUTABLE.createNode();

if (!ForeignAccess.sendIsExecutable(
isExecutableNode,
frame,
initFunctionObject)) {
throw new UnsupportedOperationException();
}

final Node executeNode = Message.createExecute(0).createNode();

try {
ForeignAccess.sendExecute(executeNode, frame, initFunctionObject);
} catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
throw new RuntimeException(e);
}
}
break;

default:
throw new RaiseException(context.getCoreExceptions().internalError(
"unknown language " + mimeType + " for " + expandedPath,
callNode));
}

final DynamicObject pathString = StringOperations.createString(
context,
StringOperations.encodeRope(expandedPath, UTF8Encoding.INSTANCE));

addToLoadedFeatures(pathString);

return true;
} finally {
fileLocks.unlock(expandedPath, lock);
}
}
}

private void ensureCExtImplementationLoaded(VirtualFrame frame, IndirectCallNode callNode) {
public void ensureCExtImplementationLoaded(VirtualFrame frame, IndirectCallNode callNode) {
synchronized (cextImplementationLock) {
if (cextImplementationLoaded) {
return;
}

final CallTarget callTarget;

try {
callTarget = context.getEnv().parse(Source.fromFileName(context.getJRubyRuntime().getJRubyHome() + "/lib/ruby/truffle/cext/ruby.su"));
} catch (IOException e) {
throw new RuntimeException(e);
}

callNode.call(frame, callTarget, new Object[]{});
final CallTarget callTarget = getCExtLibRuby();
callNode.call(frame, callTarget, new Object[] {});

cextImplementationLoaded = true;
}
}

private String getBaseName(String path) {
final String name = new File(path).getName();

final int firstDot = name.indexOf('.');
@TruffleBoundary
private CallTarget getCExtLibRuby() {
final String path = context.getJRubyRuntime().getJRubyHome() + "/lib/ruby/truffle/cext/ruby.su";
try {
return parseSource(Source.fromFileName(path));
} catch (IOException e) {
throw new RuntimeException(e);
}
}

if (firstDot == -1) {
return name;
} else {
return name.substring(0, firstDot);
@TruffleBoundary
public CallTarget parseSource(Source source) {
try {
return context.getEnv().parse(source);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

// TODO (pitr-ch 16-Mar-2016): this protects the $LOADED_FEATURES only in this class,
// it can still be accessed and modified (rare) by Ruby code which may cause issues
private final Object loadedFeaturesLock = new Object();

private boolean isFeatureLoaded(String feature) {
synchronized (loadedFeaturesLock) {
final DynamicObject loadedFeatures = context.getCoreLibrary().getLoadedFeatures();

for (Object loaded : ArrayOperations.toIterable(loadedFeatures)) {
if (loaded.toString().equals(feature)) {
return true;
}
}

return false;
}
}

private void addToLoadedFeatures(DynamicObject feature) {
synchronized (loadedFeaturesLock) {

final DynamicObject loadedFeatures = context.getCoreLibrary().getLoadedFeatures();
final int size = Layouts.ARRAY.getSize(loadedFeatures);
final Object[] store = (Object[]) Layouts.ARRAY.getStore(loadedFeatures);

if (size < store.length) {
store[size] = feature;
} else {
final Object[] newStore = ArrayUtils.grow(
store,
ArrayUtils.capacityForOneMore(context, store.length));
newStore[size] = feature;
Layouts.ARRAY.setStore(loadedFeatures, newStore);
}
Layouts.ARRAY.setSize(loadedFeatures, size + 1);
}
public Object getLoadedFeaturesLock() {
return loadedFeaturesLock;
}

}
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@
* while (true) {
* final ReentrantLock lock = fileLocks.getLock(key);
*
* if (!fileLocks.lock(callNode, context, lock);) {
* if (!fileLocks.lock(callNode, context, lock)) {
* continue;
* }
*
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package org.jruby.truffle.language.loader;

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.ForeignAccess;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.Message;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.api.profiles.ConditionProfile;
import com.oracle.truffle.api.source.Source;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.locks.ReentrantLock;
import org.jcodings.specific.UTF8Encoding;
import org.jruby.truffle.RubyLanguage;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.RubyNode;
import org.jruby.truffle.language.RubyRootNode;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.truffle.language.dispatch.CallDispatchHeadNode;
import org.jruby.truffle.language.methods.DeclarationContext;
import org.jruby.truffle.language.parser.ParserContext;

@NodeChild("feature")
public abstract class RequireNode extends RubyNode {

@Child IndirectCallNode callNode = IndirectCallNode.create();
@Child CallDispatchHeadNode isInLoadedFeatures = CallDispatchHeadNode.createMethodCall();
@Child CallDispatchHeadNode addToLoadedFeatures = CallDispatchHeadNode.createMethodCall();

@Child Node isExecutableNode = Message.IS_EXECUTABLE.createNode();
@Child Node executeNode = Message.createExecute(0).createNode();

public static RequireNode create() {
return RequireNodeGen.create(null);
}

public abstract boolean executeRequire(VirtualFrame frame, String feature);

@Specialization
protected boolean require(VirtualFrame frame, String feature,
@Cached("create()") BranchProfile errorProfile,
@Cached("createBinaryProfile()") ConditionProfile isLoadedProfile) {
final FeatureLoader featureLoader = getContext().getFeatureLoader();

final String expandedPath = featureLoader.findFeature(feature);

if (expandedPath == null) {
errorProfile.enter();
throw new RaiseException(getContext().getCoreExceptions().loadErrorCannotLoad(feature, this));
}

final DynamicObject pathString = StringOperations.createString(getContext(),
StringOperations.encodeRope(expandedPath, UTF8Encoding.INSTANCE));

if (isLoadedProfile.profile(isFeatureLoaded(frame, pathString))) {
return false;
}

final ReentrantLockFreeingMap<String> fileLocks = featureLoader.getFileLocks();

while (true) {
final ReentrantLock lock = fileLocks.get(expandedPath);

if (lock.isHeldByCurrentThread()) {
// circular require
// TODO (pitr-ch 20-Mar-2016): warn user
return false;
}

if (!fileLocks.lock(this, getContext().getThreadManager(), expandedPath, lock)) {
continue;
}

try {
if (isFeatureLoaded(frame, pathString)) {
return false;
}

final Source source;
try {
source = getContext().getSourceCache().getSource(expandedPath);
} catch (IOException e) {
return false;
}

final String mimeType = source.getMimeType();
switch (mimeType) {
case RubyLanguage.MIME_TYPE: {
final RubyRootNode rootNode = getContext().getCodeLoader().parse(
source,
UTF8Encoding.INSTANCE,
ParserContext.TOP_LEVEL,
null,
true,
this);

final CodeLoader.DeferredCall deferredCall = getContext().getCodeLoader().prepareExecute(
ParserContext.TOP_LEVEL,
DeclarationContext.TOP_LEVEL,
rootNode,
null,
coreLibrary().getMainObject());

deferredCall.call(frame, callNode);
break;
}

case RubyLanguage.CEXT_MIME_TYPE: {
featureLoader.ensureCExtImplementationLoaded(frame, callNode);

final CallTarget callTarget = featureLoader.parseSource(source);
callNode.call(frame, callTarget, new Object[] {});

final TruffleObject initFunction = getInitFunction(expandedPath);

if (!ForeignAccess.sendIsExecutable(isExecutableNode, frame, initFunction)) {
CompilerDirectives.transferToInterpreter();
throw new UnsupportedOperationException();
}

try {
ForeignAccess.sendExecute(executeNode, frame, initFunction);
} catch (InteropException e) {
CompilerDirectives.transferToInterpreter();
throw new RuntimeException(e);
}
break;
}

default:
errorProfile.enter();
throw new RaiseException(unknownLanguage(expandedPath, mimeType));
}

addToLoadedFeatures(frame, pathString);

return true;
} finally {
fileLocks.unlock(expandedPath, lock);
}
}
}

@TruffleBoundary
private DynamicObject unknownLanguage(String expandedPath, final String mimeType) {
return getContext().getCoreExceptions().internalError(
"unknown language " + mimeType + " for " + expandedPath,
callNode);
}

@TruffleBoundary
private TruffleObject getInitFunction(final String expandedPath) {
final Object initFunction = getContext().getEnv().importSymbol("@Init_" + getBaseName(expandedPath));

if (!(initFunction instanceof TruffleObject)) {
throw new UnsupportedOperationException();
}

return (TruffleObject) initFunction;
}

@TruffleBoundary
private String getBaseName(String path) {
final String name = new File(path).getName();
final int firstDot = name.indexOf('.');
if (firstDot == -1) {
return name;
} else {
return name.substring(0, firstDot);
}
}

public boolean isFeatureLoaded(VirtualFrame frame, DynamicObject feature) {
final DynamicObject loadedFeatures = getContext().getCoreLibrary().getLoadedFeatures();
synchronized (getContext().getFeatureLoader().getLoadedFeaturesLock()) {
return isInLoadedFeatures.callBoolean(frame, loadedFeatures, "include?", null, feature);
}
}

private void addToLoadedFeatures(VirtualFrame frame, DynamicObject feature) {
final DynamicObject loadedFeatures = coreLibrary().getLoadedFeatures();
synchronized (getContext().getFeatureLoader().getLoadedFeaturesLock()) {
addToLoadedFeatures.call(frame, loadedFeatures, "<<", null, feature);
}
}

}
Original file line number Diff line number Diff line change
@@ -10,8 +10,8 @@

package org.jruby.truffle.language.loader;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.source.Source;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@@ -30,6 +30,7 @@ public SourceCache(SourceLoader loader) {
this.loader = loader;
}

@TruffleBoundary
public synchronized Source getSource(String canonicalPath) throws IOException {
Source source = sources.get(canonicalPath);

Original file line number Diff line number Diff line change
@@ -11,7 +11,6 @@

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.api.profiles.ConditionProfile;
@@ -31,9 +30,10 @@ public class DefineClassNode extends RubyNode {
protected final String name;

@Child private RubyNode superClassNode;
@Child private CallDispatchHeadNode inheritedNode;
@Child private RubyNode lexicalParentModule;
@Child private IndirectCallNode indirectCallNode;

@Child LookupForExistingModuleNode lookupForExistingModuleNode;
@Child CallDispatchHeadNode inheritedNode;

private final ConditionProfile needToDefineProfile = ConditionProfile.createBinaryProfile();
private final BranchProfile errorProfile = BranchProfile.create();
@@ -44,7 +44,6 @@ public DefineClassNode(RubyContext context, SourceSection sourceSection, String
this.name = name;
this.lexicalParentModule = lexicalParent;
this.superClassNode = superClass;
indirectCallNode = IndirectCallNode.create();
}

@Override
@@ -60,8 +59,7 @@ public Object execute(VirtualFrame frame) {

final DynamicObject superClass = executeSuperClass(frame);

final RubyConstant constant = DefineModuleNode.lookupForExistingModule(
frame, getContext(), name, lexicalParentModule, indirectCallNode);
final RubyConstant constant = lookupForExistingModule(frame, name, lexicalParentModule);

final DynamicObject definedClass;

@@ -113,8 +111,15 @@ private void callInherited(VirtualFrame frame, DynamicObject superClass, Dynamic
CompilerDirectives.transferToInterpreter();
inheritedNode = insert(DispatchHeadNodeFactory.createMethodCallOnSelf(getContext()));
}

inheritedNode.call(frame, superClass, "inherited", null, childClass);
}

private RubyConstant lookupForExistingModule(VirtualFrame frame, String name, DynamicObject lexicalParent) {
if (lookupForExistingModuleNode == null) {
CompilerDirectives.transferToInterpreter();
lookupForExistingModuleNode = insert(LookupForExistingModuleNodeGen.create(null, null));
}
return lookupForExistingModuleNode.executeLookupForExistingModule(frame, name, lexicalParent);
}

}
Original file line number Diff line number Diff line change
@@ -13,15 +13,12 @@
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.api.profiles.ConditionProfile;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.Layouts;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.module.ModuleNodes;
import org.jruby.truffle.language.LexicalScope;
import org.jruby.truffle.language.RubyConstant;
import org.jruby.truffle.language.RubyGuards;
import org.jruby.truffle.language.RubyNode;
@@ -32,20 +29,19 @@ public abstract class DefineModuleNode extends RubyNode {

private final String name;

@Child private IndirectCallNode indirectCallNode;
@Child LookupForExistingModuleNode lookupForExistingModuleNode;

private final ConditionProfile needToDefineProfile = ConditionProfile.createBinaryProfile();
private final BranchProfile errorProfile = BranchProfile.create();

public DefineModuleNode(RubyContext context, SourceSection sourceSection, String name) {
super(context, sourceSection);
this.name = name;
indirectCallNode = IndirectCallNode.create();
}

@Specialization(guards = "isRubyModule(lexicalParentModule)")
public Object defineModule(VirtualFrame frame, DynamicObject lexicalParentModule) {
final RubyConstant constant = lookupForExistingModule(frame, getContext(), name, lexicalParentModule, indirectCallNode);
final RubyConstant constant = lookupForExistingModule(frame, name, lexicalParentModule);

final DynamicObject definingModule;

@@ -71,43 +67,12 @@ public Object defineModuleWrongParent(VirtualFrame frame, Object lexicalParentOb
throw new RaiseException(coreExceptions().typeErrorIsNotA(lexicalParentObject, "module", this));
}

public static RubyConstant lookupForExistingModule(VirtualFrame frame, RubyContext context, String name,
DynamicObject lexicalParent, IndirectCallNode callNode) {
CompilerDirectives.bailout("require cannot be compiled but needs the frame");

RubyConstant constant = Layouts.MODULE.getFields(lexicalParent).getConstant(name);

final DynamicObject objectClass = context.getCoreLibrary().getObjectClass();

if (constant == null && lexicalParent == objectClass) {
for (DynamicObject included : Layouts.MODULE.getFields(objectClass).prependedAndIncludedModules()) {
constant = Layouts.MODULE.getFields(included).getConstant(name);

if (constant != null) {
break;
}
}
}

if (constant != null && !constant.isVisibleTo(context, LexicalScope.NONE, lexicalParent)) {
throw new RaiseException(context.getCoreExceptions().nameErrorPrivateConstant(lexicalParent, name, callNode));
private RubyConstant lookupForExistingModule(VirtualFrame frame, String name, DynamicObject lexicalParent) {
if (lookupForExistingModuleNode == null) {
CompilerDirectives.transferToInterpreter();
lookupForExistingModuleNode = insert(LookupForExistingModuleNodeGen.create(null, null));
}

// If a constant already exists with this class/module name and it's an autoload module, we have to trigger
// the autoload behavior before proceeding.

if ((constant != null) && constant.isAutoload()) {

// We know that we're redefining this constant as we're defining a class/module with that name. We remove
// the constant here rather than just overwrite it in order to prevent autoload loops in either the require
// call or the recursive execute call.

Layouts.MODULE.getFields(lexicalParent).removeConstant(context, callNode, name);
context.getFeatureLoader().require(frame, constant.getValue().toString(), callNode);
return lookupForExistingModule(frame, context, name, lexicalParent, callNode);
}

return constant;
return lookupForExistingModuleNode.executeLookupForExistingModule(frame, name, lexicalParent);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright (c) 2013, 2016 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 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
package org.jruby.truffle.language.objects;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.profiles.ConditionProfile;
import org.jruby.truffle.Layouts;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.LexicalScope;
import org.jruby.truffle.language.RubyConstant;
import org.jruby.truffle.language.RubyNode;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.truffle.language.loader.RequireNode;

@NodeChildren({ @NodeChild("name"), @NodeChild("lexicalParent") })
public abstract class LookupForExistingModuleNode extends RubyNode {

@Child private RequireNode requireNode;

public abstract RubyConstant executeLookupForExistingModule(VirtualFrame frame, String name, DynamicObject lexicalParent);

@Specialization(guards = "isRubyModule(lexicalParent)")
public RubyConstant lookupForExistingModule(VirtualFrame frame, String name, DynamicObject lexicalParent,
@Cached("createBinaryProfile()") ConditionProfile autoloadProfile) {
RubyConstant constant = deepConstantSearch(name, lexicalParent);

// If a constant already exists with this class/module name and it's an autoload module, we have to trigger
// the autoload behavior before proceeding.

if (autoloadProfile.profile(constant != null && constant.isAutoload())) {

// We know that we're redefining this constant as we're defining a class/module with that name. We remove
// the constant here rather than just overwrite it in order to prevent autoload loops in either the require
// call or the recursive execute call.

Layouts.MODULE.getFields(lexicalParent).removeConstant(getContext(), this, name);
getRequireNode().executeRequire(frame, StringOperations.getString(getContext(), (DynamicObject) constant.getValue()));
return deepConstantSearch(name, lexicalParent);
}

return constant;
}

@TruffleBoundary
private RubyConstant deepConstantSearch(String name, DynamicObject lexicalParent) {
RubyConstant constant = Layouts.MODULE.getFields(lexicalParent).getConstant(name);

final DynamicObject objectClass = getContext().getCoreLibrary().getObjectClass();

if (constant == null && lexicalParent == objectClass) {
for (DynamicObject included : Layouts.MODULE.getFields(objectClass).prependedAndIncludedModules()) {
constant = Layouts.MODULE.getFields(included).getConstant(name);

if (constant != null) {
break;
}
}
}

if (constant != null && !constant.isVisibleTo(getContext(), LexicalScope.NONE, lexicalParent)) {
throw new RaiseException(getContext().getCoreExceptions().nameErrorPrivateConstant(lexicalParent, name, this));
}
return constant;
}

public RequireNode getRequireNode() {
if (requireNode == null) {
CompilerDirectives.transferToInterpreter();
requireNode = insert(RequireNode.create());
}
return requireNode;
}

}

0 comments on commit 70b51aa

Please sign in to comment.