Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[Truffle] Initial implementation of Module#autoload.
  • Loading branch information
nirvdrum committed Feb 18, 2015
1 parent a64434d commit 2d0e094
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 28 deletions.
22 changes: 0 additions & 22 deletions spec/truffle/tags/core/module/autoload_tags.txt
@@ -1,40 +1,18 @@
fails:Module#autoload? returns the name of the file that will be autoloaded
fails:Module#autoload? returns nil if no file has been registered for a constant
fails:Module#autoload registers a file to load the first time the named constant is accessed
fails:Module#autoload sets the autoload constant in the constants table
fails:Module#autoload loads the registered constant when it is accessed
fails:Module#autoload loads the registered constant into a dynamically created class
fails:Module#autoload loads the registered constant into a dynamically created module
fails:Module#autoload loads the registered constant when it is opened as a class
fails:Module#autoload loads the registered constant when it is opened as a module
fails:Module#autoload loads the registered constant when it is inherited from
fails:Module#autoload loads the registered constant when it is included
fails:Module#autoload does not load the file when the constant is already set
fails:Module#autoload loads a file with .rb extension when passed the name without the extension
fails:Module#autoload does not load the file if the file is manually required
fails:Module#autoload ignores the autoload request if the file is already loaded
fails:Module#autoload retains the autoload even if the request to require fails
fails:Module#autoload allows multiple autoload constants for a single file
fails:Module#autoload runs for an exception condition class and doesn't trample the exception
fails:Module#autoload does not load the file when refering to the constant in defined?
fails:Module#autoload does not remove the constant from the constant table if load fails
fails:Module#autoload does not remove the constant from the constant table if the loaded files does not define it
fails:Module#autoload returns 'constant' on refering the constant with defined?()
fails:Module#autoload does not load the file when removing an autoload constant
fails:Module#autoload does not load the file when accessing the constants table of the module
fails:Module#autoload loads the file when opening a module that is the autoloaded constant
fails:Module#autoload loads the file that defines subclass XX::YY < YY and YY is a top level constant
fails:Module#autoload looks up the constant in the scope where it is referred
fails:Module#autoload looks up the constant when in a meta class scope
fails:Module#autoload does NOT raise a NameError when the autoload file did not define the constant and a module is opened with the same name
fails:Module#autoload calls #to_path on non-string filenames
fails:Module#autoload raises an ArgumentError when an empty filename is given
fails:Module#autoload shares the autoload request across dup'ed copies of modules
fails:Module#autoload raises a TypeError if opening a class with a different superclass than the class defined in the autoload file
fails:Module#autoload raises a TypeError if not passed a String or object respodning to #to_path for the filename
fails:Module#autoload calls #to_path on non-String filename arguments
fails:Module#autoload on a frozen module raises a RuntimeError before setting the name
fails:Module#autoload (concurrently) blocks a second thread while a first is doing the autoload
fails:Module#autoload when changing $LOAD_PATH does not reload a file due to a different load path
fails:Module#autoload (concurrently) blocks others threads while doing an autoload
fails:Module#autoload does not call Kernel#require or Kernel#load dynamically
Expand Up @@ -12,12 +12,16 @@
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.dsl.CreateCast;
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.nodes.Node.Child;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;

import com.oracle.truffle.api.utilities.ConditionProfile;
import org.jcodings.Encoding;
import org.jruby.runtime.Visibility;
import org.jruby.truffle.nodes.RubyNode;
Expand Down Expand Up @@ -384,6 +388,59 @@ public static void attrAccessor(RubyNode currentNode, RubyContext context, Sourc

}

@CoreMethod(names = "autoload", required = 2)
@NodeChildren({
@NodeChild(value = "module"),
@NodeChild(value = "name"),
@NodeChild(value = "filename")
})
public abstract static class AutoloadNode extends RubyNode {

@Child private StringNodes.EmptyNode emptyNode;
private final ConditionProfile invalidConstantName = ConditionProfile.createBinaryProfile();
private final ConditionProfile emptyFilename = ConditionProfile.createBinaryProfile();

public AutoloadNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
emptyNode = StringNodesFactory.EmptyNodeFactory.create(context, sourceSection, new RubyNode[]{});
}

public AutoloadNode(AutoloadNode prev) {
super(prev);
emptyNode = prev.emptyNode;
}

@CreateCast("filename") public RubyNode coerceFilenameToString(RubyNode filename) {
return ToStrNodeFactory.create(getContext(), getSourceSection(), filename);
}

@Specialization
public RubyNilClass autoload(RubyModule module, RubySymbol name, RubyString filename) {
return autoload(module, name.toString(), filename);
}

@Specialization
public RubyNilClass autoload(RubyModule module, RubyString name, RubyString filename) {
return autoload(module, name.toString(), filename);
}

private RubyNilClass autoload(RubyModule module, String name, RubyString filename) {
if (invalidConstantName.profile(!IdUtil.isConstant(name))) {
CompilerDirectives.transferToInterpreter();
throw new RaiseException(getContext().getCoreLibrary().nameError(String.format("autoload must be constant name: %s", name), this));
}

if (emptyFilename.profile(emptyNode.empty(filename))) {
CompilerDirectives.transferToInterpreter();
throw new RaiseException(getContext().getCoreLibrary().argumentError("empty file name", this));
}

module.setAutoloadConstant(this, name.toString(), filename);

return getContext().getCoreLibrary().getNilObject();
}
}

@CoreMethod(names = {"class_eval","module_eval"}, optional = 3, needsBlock = true)
public abstract static class ClassEvalNode extends CoreMethodNode {

Expand Down
Expand Up @@ -12,13 +12,17 @@
import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.VirtualFrame;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.core.KernelNodes;
import org.jruby.truffle.nodes.core.KernelNodesFactory;
import org.jruby.truffle.runtime.RubyArguments;
import org.jruby.truffle.runtime.RubyConstant;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.core.RubyBasicObject;
import org.jruby.truffle.runtime.core.RubyClass;
import org.jruby.truffle.runtime.core.RubyModule;
import org.jruby.truffle.runtime.core.RubyString;
import org.jruby.truffle.runtime.core.RubySymbol;
import org.jruby.truffle.runtime.methods.InternalMethod;
import org.jruby.util.cli.Options;
Expand All @@ -31,6 +35,8 @@ public final class UnresolvedDispatchNode extends DispatchNode {
private final boolean indirect;
private final MissingBehavior missingBehavior;

@Child private KernelNodes.RequireNode requireNode;

public UnresolvedDispatchNode(
RubyContext context,
boolean ignoreVisibility,
Expand All @@ -41,6 +47,7 @@ public UnresolvedDispatchNode(
this.ignoreVisibility = ignoreVisibility;
this.indirect = indirect;
this.missingBehavior = missingBehavior;
requireNode = KernelNodesFactory.RequireNodeFactory.create(context, getSourceSection(), new RubyNode[]{});
}

@Override
Expand Down Expand Up @@ -195,6 +202,14 @@ private Object doRubyBasicObject(
methodName, blockObject, argumentsObjects);
}

if (constant.isAutoload()) {
module.removeConstant(this, (String) methodName);

requireNode.require((RubyString) constant.getValue());

return doRubyBasicObject(frame, first, receiverObject, methodName, blockObject, argumentsObjects);
}

// The module, the "receiver" is an instance of its singleton class.
// But we want to check the module assumption, not its singleton class assumption.
final DispatchNode newDispatch = new CachedBoxedDispatchNode(getContext(), methodName, first,
Expand Down
Expand Up @@ -18,11 +18,13 @@ public class RubyConstant {
private final RubyModule declaringModule;
private final Object value;
private boolean isPrivate;
private boolean autoload;

public RubyConstant(RubyModule declaringModule, Object value, boolean isPrivate) {
public RubyConstant(RubyModule declaringModule, Object value, boolean isPrivate, boolean autoload) {
this.declaringModule = declaringModule;
this.value = value;
this.isPrivate = isPrivate;
this.autoload = autoload;
}

public Object getValue() {
Expand Down Expand Up @@ -72,4 +74,8 @@ public boolean isVisibleTo(RubyContext context, LexicalScope lexicalScope, RubyM
return false;
}

public boolean isAutoload() {
return autoload;
}

}
Expand Up @@ -124,7 +124,7 @@ protected RubyModule(RubyContext context, RubyClass selfClass, RubyModule lexica
}

public void getAdoptedByLexicalParent(RubyModule lexicalParent, String name, RubyNode currentNode) {
lexicalParent.setConstantInternal(currentNode, name, this);
lexicalParent.setConstantInternal(currentNode, name, this, false);
lexicalParent.addLexicalDependent(this);

if (this.name == null) {
Expand Down Expand Up @@ -192,21 +192,26 @@ public void setConstant(RubyNode currentNode, String name, Object value) {
if (value instanceof RubyModule) {
((RubyModule) value).getAdoptedByLexicalParent(this, name, currentNode);
} else {
setConstantInternal(currentNode, name, value);
setConstantInternal(currentNode, name, value, false);
}
}

private void setConstantInternal(RubyNode currentNode, String name, Object value) {
@TruffleBoundary
public void setAutoloadConstant(RubyNode currentNode, String name, RubyString filename) {
setConstantInternal(currentNode, name, filename, true);
}

private void setConstantInternal(RubyNode currentNode, String name, Object value, boolean autoload) {
RubyNode.notDesignedForCompilation();

checkFrozen(currentNode);

RubyConstant previous = getConstants().get(name);
if (previous == null) {
getConstants().put(name, new RubyConstant(this, value, false));
getConstants().put(name, new RubyConstant(this, value, false, autoload));
} else {
// TODO(CS): warn when redefining a constant
getConstants().put(name, new RubyConstant(this, value, previous.isPrivate()));
getConstants().put(name, new RubyConstant(this, value, previous.isPrivate(), autoload));
}

newLexicalVersion();
Expand Down
5 changes: 5 additions & 0 deletions truffle/src/main/ruby/core/rubinius/common/kernel.rb
Expand Up @@ -92,6 +92,11 @@ def StringValue(obj)
end
module_function :StringValue

def autoload(name, file)
Object.autoload(name, file)
end
private :autoload

def define_singleton_method(*args, &block)
singleton_class.send(:define_method, *args, &block)
end
Expand Down

0 comments on commit 2d0e094

Please sign in to comment.