Skip to content

Commit

Permalink
[Truffle] Implement Module#prepend.
Browse files Browse the repository at this point in the history
* Introduce RubyModule.start as start of the ModuleChain.
  self by default and a marker if we have prepended modules.
  This is necessery to make future prepended modules also included in the subclasses.
eregon committed Jul 7, 2015
1 parent be5e7f1 commit e409258
Showing 11 changed files with 155 additions and 80 deletions.
6 changes: 0 additions & 6 deletions spec/truffle/tags/core/module/prepend_features_tags.txt

This file was deleted.

26 changes: 0 additions & 26 deletions spec/truffle/tags/core/module/prepend_tags.txt

This file was deleted.

2 changes: 0 additions & 2 deletions spec/truffle/tags/core/module/prepended_tags.txt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1381,6 +1381,28 @@ public RubyModule doPrivate(VirtualFrame frame, RubyModule module, Object[] name

}

@CoreMethod(names = "prepend_features", required = 1, visibility = Visibility.PRIVATE)
public abstract static class PrependFeaturesNode extends CoreMethodArrayArgumentsNode {

@Child TaintResultNode taintResultNode;

public PrependFeaturesNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
taintResultNode = new TaintResultNode(context, sourceSection);
}

@Specialization
public RubyBasicObject prependFeatures(RubyModule features, RubyModule target) {
if (features instanceof RubyClass) {
CompilerDirectives.transferToInterpreter();
throw new RaiseException(getContext().getCoreLibrary().typeError("prepend_features must be called only on modules", this));
}
target.prepend(this, features);
taintResultNode.maybeTaint(features, target);
return nil();
}
}

@CoreMethod(names = "private_class_method", argumentsAsArray = true)
public abstract static class PrivateClassMethodNode extends CoreMethodArrayArgumentsNode {

Original file line number Diff line number Diff line change
@@ -83,7 +83,7 @@ protected RubyConstant lookupForExistingModule(RubyModule lexicalParent) {
final RubyClass objectClass = getContext().getCoreLibrary().getObjectClass();

if (constant == null && lexicalParent == objectClass) {
for (RubyModule included : objectClass.includedModules()) {
for (RubyModule included : objectClass.prependedAndIncludedModules()) {
constant = included.getConstants().get(name);
if (constant != null) {
break;
Original file line number Diff line number Diff line change
@@ -59,7 +59,7 @@ public static Map<String, RubyConstant> getAllConstants(RubyModule module) {
constants.putAll(module.getConstants());

// Look in ancestors
for (RubyModule ancestor : module.includedModules()) {
for (RubyModule ancestor : module.prependedAndIncludedModules()) {
for (Map.Entry<String, RubyConstant> constant : ancestor.getConstants().entrySet()) {
if (!constants.containsKey(constant.getKey())) {
constants.put(constant.getKey(), constant.getValue());
@@ -126,7 +126,7 @@ public static RubyConstant lookupConstant(RubyContext context, LexicalScope lexi
return constant;
}

for (RubyModule ancestor : objectClass.includedModules()) {
for (RubyModule ancestor : objectClass.prependedAndIncludedModules()) {
constant = ancestor.getConstants().get(name);

if (constant != null) {
@@ -249,26 +249,16 @@ public static Map<String, InternalMethod> withoutUndefinedMethods(Map<String, In
public static InternalMethod lookupMethod(RubyModule module, String name) {
CompilerAsserts.neverPartOfCompilation();

InternalMethod method;

// Look in the current module
method = module.getMethods().get(name);

if (method != null) {
return method;
}

// Look in ancestors
for (RubyModule ancestor : module.parentAncestors()) {
method = ancestor.getMethods().get(name);
for (RubyModule ancestor : module.ancestors()) {
InternalMethod method = ancestor.getMethods().get(name);

if (method != null) {
return method;
}
}

// Nothing found

return null;
}

Original file line number Diff line number Diff line change
@@ -160,7 +160,7 @@ private enum State {

private State state = State.INITIALIZING;

private final Allocator NO_ALLOCATOR = new Allocator() {
public final Allocator NO_ALLOCATOR = new Allocator() {
@Override
public RubyBasicObject allocate(RubyContext context, RubyClass rubyClass, Node currentNode) {
CompilerDirectives.transferToInterpreter();
Original file line number Diff line number Diff line change
@@ -81,7 +81,7 @@ public void initialize(RubyClass superclass) {
protected void unsafeSetSuperclass(RubyClass superClass) {
assert parentModule == null;

parentModule = superClass;
parentModule = superClass.start;
superClass.addDependent(this);

newVersion();
134 changes: 105 additions & 29 deletions truffle/src/main/java/org/jruby/truffle/runtime/core/RubyModule.java
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.utilities.CyclicAssumption;

import org.jruby.runtime.Visibility;
import org.jruby.truffle.nodes.objects.Allocator;
import org.jruby.truffle.runtime.*;
@@ -64,17 +65,28 @@ public void insertAfter(RubyModule module) {
}
}

public static void debugModuleChain(RubyModule module) {
ModuleChain chain = module;
while (chain != null) {
System.err.print(chain.getClass());
private static class PrependMarker implements ModuleChain {
@CompilationFinal private ModuleChain parentModule;

RubyModule real = chain.getActualModule();
System.err.print(" " + real.getName());
public PrependMarker(ModuleChain parentModule) {
this.parentModule = parentModule;
}

@Override
public ModuleChain getParentModule() {
return parentModule;
}

@Override
public RubyModule getActualModule() {
throw new UnsupportedOperationException();
}

System.err.println();
chain = chain.getParentModule();
@Override
public void insertAfter(RubyModule module) {
parentModule = new IncludedModule(module, parentModule);
}

}

/**
@@ -86,6 +98,8 @@ public static void debugModuleChain(RubyModule module) {
// The context is stored here - objects can obtain it via their class (which is a module)
private final RubyContext context;

/** Either {@code this} or a {@link PrependMarker} */
@CompilationFinal protected ModuleChain start = this;
@CompilationFinal protected ModuleChain parentModule;

private final RubyModule lexicalParent;
@@ -160,12 +174,19 @@ private void updateAnonymousChildrenModules() {
@TruffleBoundary
public void initCopy(RubyModule from) {
// Do not copy name, the copy is an anonymous module
this.parentModule = from.parentModule;
if (parentModule != null)
parentModule.getActualModule().addDependent(this);
this.methods.putAll(from.methods);
this.constants.putAll(from.constants);
this.classVariables.putAll(from.classVariables);

if (from.start instanceof PrependMarker) {
this.parentModule = from.start;
} else {
this.parentModule = from.parentModule;
}

for (RubyModule ancestor : from.ancestors()) {
ancestor.addDependent(this);
}
}

/** If this instance is a module and not a class. */
@@ -232,14 +253,43 @@ private void performIncludes(ModuleChain inclusionPoint, Stack<RubyModule> modul
}

private boolean isIncludedModuleBeforeSuperClass(RubyModule module) {
boolean isDirectlyIncluded = false;
for (RubyModule includedModule : includedModules()) {
if (includedModule == module) {
isDirectlyIncluded = true;
break;
ModuleChain included = parentModule;
while (included instanceof IncludedModule) {
if (included.getActualModule() == module) {
return true;
}
included = included.getParentModule();
}
return false;
}

@TruffleBoundary
public void prepend(Node currentNode, RubyModule module) {
checkFrozen(currentNode);

// If the module we want to prepend already includes us, it is cyclic
if (ModuleOperations.includesModule(module, this)) {
throw new RaiseException(getContext().getCoreLibrary().argumentError("cyclic prepend detected", currentNode));
}
return isDirectlyIncluded;

if (start == this) {
// Create an PrependMarker pointing to an empty module or class
// serving as an indirection to the first prepended module
start = new PrependMarker(this);
}

Stack<RubyModule> modulesToPrepend = new Stack<>();
modulesToPrepend.push(module);
for (RubyModule includedModule : module.prependedAndIncludedModules()) {
modulesToPrepend.push(includedModule);
}

for (RubyModule mod : modulesToPrepend) {
start.insertAfter(mod);
mod.addDependent(this);
}

newVersion();
}

/**
@@ -509,15 +559,17 @@ public void visitObjectGraphChildren(ObjectSpaceManager.ObjectGraphVisitor visit
}
}

@Override
public ModuleChain getParentModule() {
return parentModule;
}

@Override
public RubyModule getActualModule() {
return this;
}

private class AncestorIterator implements Iterator<RubyModule> {
private static class AncestorIterator implements Iterator<RubyModule> {
ModuleChain module;

public AncestorIterator(ModuleChain top) {
@@ -534,8 +586,14 @@ public RubyModule next() throws NoSuchElementException {
if (!hasNext()) {
throw new NoSuchElementException();
}

ModuleChain mod = module;
module = module.getParentModule();
if (mod instanceof PrependMarker) {
mod = mod.getParentModule();
}

module = mod.getParentModule();

return mod.getActualModule();
}

@@ -545,19 +603,31 @@ public void remove() {
}
}

private class IncludedModulesIterator extends AncestorIterator {
public IncludedModulesIterator(ModuleChain top) {
super(top);
private static class IncludedModulesIterator extends AncestorIterator {
private final RubyModule currentModule;

public IncludedModulesIterator(ModuleChain top, RubyModule currentModule) {
super(top instanceof PrependMarker ? top.getParentModule() : top);
this.currentModule = currentModule;
}

@Override
public boolean hasNext() {
return super.hasNext() && !(module instanceof RubyClass);
if (!super.hasNext()) {
return false;
}

if (module == currentModule) {
module = module.getParentModule(); // skip self
return hasNext();
}

return module instanceof IncludedModule;
}
}

public Iterable<RubyModule> ancestors() {
final RubyModule top = this;
final ModuleChain top = start;
return new Iterable<RubyModule>() {
@Override
public Iterator<RubyModule> iterator() {
@@ -567,21 +637,27 @@ public Iterator<RubyModule> iterator() {
}

public Iterable<RubyModule> parentAncestors() {
final ModuleChain top = parentModule;
final ModuleChain top = start;
return new Iterable<RubyModule>() {
@Override
public Iterator<RubyModule> iterator() {
return new AncestorIterator(top);
final AncestorIterator iterator = new AncestorIterator(top);
if (iterator.hasNext()) {
iterator.next();
}
return iterator;
}
};
}

public Iterable<RubyModule> includedModules() {
final ModuleChain top = parentModule;
/** Iterates over include'd and prepend'ed modules. */
public Iterable<RubyModule> prependedAndIncludedModules() {
final ModuleChain top = start;
final RubyModule currentModule = this;
return new Iterable<RubyModule>() {
@Override
public Iterator<RubyModule> iterator() {
return new IncludedModulesIterator(top);
return new IncludedModulesIterator(top, currentModule);
}
};
}
1 change: 1 addition & 0 deletions truffle/src/main/ruby/core/rubinius/delta/class.rb
Original file line number Diff line number Diff line change
@@ -30,4 +30,5 @@ class Class
undef_method :append_features
undef_method :extend_object
undef_method :module_function
undef_method :prepend_features
end
20 changes: 20 additions & 0 deletions truffle/src/main/ruby/core/rubinius/delta/module.rb
Original file line number Diff line number Diff line change
@@ -48,4 +48,24 @@ def include(*modules)
self
end

def prepended(mod); end
private :prepended

def prepend(*modules)
modules.reverse_each do |mod|
if !mod.kind_of?(Module) or mod.kind_of?(Class)
raise TypeError, "wrong argument type #{mod.class} (expected Module)"
end

Rubinius.privately do
mod.prepend_features self
end

Rubinius.privately do
mod.prepended self
end
end
self
end

end

0 comments on commit e409258

Please sign in to comment.