Skip to content

Commit

Permalink
Showing 14 changed files with 767 additions and 184 deletions.
271 changes: 271 additions & 0 deletions lib/ruby/truffle/truffle/objspace.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
# Copyright (c) 2015 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

require 'json'
require 'tempfile'
require 'weakref'

module ObjectSpace

def count_nodes(nodes = {})
ObjectSpace.each_object(Module) do |mod|
mod.methods(false).each do |name|
count_nodes_method mod.method(name), nodes
end

mod.private_methods(false).each do |name|
count_nodes_method mod.method(name), nodes
end
end

ObjectSpace.each_object(Proc) do |proc|
count_nodes_method proc, nodes
end

ObjectSpace.each_object(Method) do |method|
count_nodes_method method, nodes
end

ObjectSpace.each_object(UnboundMethod) do |umethod|
count_nodes_method umethod, nodes
end

nodes
end

module_function :count_nodes

class << self

def count_nodes_method(method, nodes)
count_nodes_tree Truffle::Primitive.ast(method), nodes
end

private :count_nodes_method

def count_nodes_tree(tree, nodes)
return if tree.nil?
name = tree.first
children = tree.drop(1)
nodes[name] ||= 0
nodes[name] += 1
children.each do |child|
count_nodes_tree child, nodes
end
end

private :count_nodes_tree

end

def count_objects_size(hash = {})
total = 0
ObjectSpace.each_object(Class) do |klass|
per_klass = memsize_of_all(klass)
hash[klass.name.to_sym] = per_klass
total += per_klass
end
hash[:TOTAL] = total
hash
end

module_function :count_objects_size

def count_tdata_objects(hash = {})
hash
end

module_function :count_tdata_objects

def dump(object, output: :string)
case output
when :string
json = {
address: "0x" + object.object_id.to_s(16),
class: "0x" + object.class.object_id.to_s(16),
memsize: memsize_of(object),
flags: { }
}
case object
when String
json.merge!({
type: "STRING",
bytesize: object.bytesize,
value: object,
encoding: object.encoding.name
})
when Array
json.merge!({
type: "ARRAY",
length: object.size
})
when Hash
json.merge!({
type: "HASH",
size: object.size
})
else
json.merge!({
type: "OBJECT",
length: object.instance_variables.size
})
end
JSON.generate(json)
when :file
f = Tempfile.new(['rubyobj', '.json'])
f.write dump(object, output: :string)
f.close
f.path
when :stdout
puts dump(object, output: :string)
nil
end
end

module_function :dump

def dump_all(output: :file)
case output
when :string
objects = []
ObjectSpace.each_object do |object|
objects.push dump(object)
end
objects.join("\n")
when :file
f = Tempfile.new(['ruby', '.json'])
f.write dump_all(output: :string)
f.close
f.path
when :stdout
puts dump_all(output: :string)
nil
when IO
output.write dump_all(output: :string)
nil
end
end

module_function :dump_all

def memsize_of(object)
Truffle::ObjSpace.memsize_of(object)
end

module_function :memsize_of

def memsize_of_all(klass = BasicObject)
total = 0
ObjectSpace.each_object(klass) do |object|
total += ObjectSpace.memsize_of(object)
end
total
end

module_function :memsize_of_all

def reachable_objects_from(object)
Truffle::ObjSpace.adjacent_objects(object)
end

module_function :reachable_objects_from

def reachable_objects_from_root
{"roots" => Truffle::ObjSpace.root_objects}
end

module_function :reachable_objects_from_root

def trace_object_allocations
trace_object_allocations_start

begin
yield
ensure
trace_object_allocations_stop
end
end

module_function :trace_object_allocations

def trace_object_allocations_clear
ALLOCATIONS.clear
end

module_function :trace_object_allocations_clear

def trace_object_allocations_debug_start
trace_object_allocations_start
end

module_function :trace_object_allocations_debug_start

def trace_object_allocations_start
Truffle::ObjSpace.trace_allocations_start
end

module_function :trace_object_allocations_start

def trace_object_allocations_stop
Truffle::ObjSpace.trace_allocations_stop
end

module_function :trace_object_allocations_stop

def allocation_class_path(object)
allocation = ALLOCATIONS[object]
return nil if allocation.nil?
allocation.class_path
end

module_function :allocation_class_path

def allocation_generation(object)
allocation = ALLOCATIONS[object]
return nil if allocation.nil?
0
end

module_function :allocation_generation

def allocation_method_id(object)
allocation = ALLOCATIONS[object]
return nil if allocation.nil?
allocation.method_id
end

module_function :allocation_method_id

def allocation_sourcefile(object)
allocation = ALLOCATIONS[object]
return nil if allocation.nil?
allocation.sourcefile
end

module_function :allocation_sourcefile

def allocation_sourceline(object)
allocation = ALLOCATIONS[object]
return nil if allocation.nil?
allocation.sourceline
end

module_function :allocation_sourceline

Allocation = Struct.new(:class_path, :method_id, :sourcefile, :sourceline)

ALLOCATIONS = {}
ALLOCATIONS.compare_by_identity

def trace_allocation(object, class_path, method_id, sourcefile, sourceline)
ALLOCATIONS[object] = Allocation.new(class_path, method_id, sourcefile, sourceline)
end

module_function :trace_allocation

end
Original file line number Diff line number Diff line change
@@ -19,10 +19,7 @@
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.layouts.Layouts;
import org.jruby.truffle.runtime.object.ObjectGraph;
import org.jruby.truffle.runtime.object.ObjectGraphVisitor;
import org.jruby.truffle.runtime.object.ObjectIDOperations;
import org.jruby.truffle.runtime.object.StopVisitingObjectsException;
import org.jruby.util.Memo;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
@@ -65,32 +62,26 @@ public long id2RefSmallInt(long id) {
return ObjectIDOperations.toFixnum(id);
}

@TruffleBoundary
@Specialization(guards = "isBasicObjectID(id)")
public Object id2Ref(final VirtualFrame frame, final long id,
@Cached("createReadObjectIDNode()") final ReadHeadObjectFieldNode readObjectIdNode) {
CompilerDirectives.transferToInterpreter();
public DynamicObject id2Ref(
final long id,
@Cached("createReadObjectIDNode()") ReadHeadObjectFieldNode readObjectIdNode) {
for (DynamicObject object : ObjectGraph.stopAndGetAllObjects(this, getContext())) {
final long objectID;

try {
objectID = readObjectIdNode.executeLong(object);
} catch (UnexpectedResultException e) {
throw new UnsupportedOperationException(e);
}

final Memo<Object> result = new Memo<Object>(nil());

new ObjectGraph(getContext()).visitObjects(new ObjectGraphVisitor() {
@Override
public boolean visit(DynamicObject object) throws StopVisitingObjectsException {
final long objectID;
try {
objectID = readObjectIdNode.executeLong(object);
} catch (UnexpectedResultException e) {
throw new UnsupportedOperationException(e);
}

if (objectID == id) {
result.set(object);
throw new StopVisitingObjectsException();
}
return true;
if (objectID == id) {
return object;
}
});
}

return result.get();
return nil();
}

@Specialization(guards = { "isRubyBignum(id)", "isLargeFixnumID(id)" })
@@ -130,7 +121,7 @@ public int eachObject(VirtualFrame frame, NotProvided ofClass, DynamicObject blo

int count = 0;

for (DynamicObject object : new ObjectGraph(getContext()).getObjects()) {
for (DynamicObject object : ObjectGraph.stopAndGetAllObjects(this, getContext())) {
if (!isHidden(object)) {
yield(frame, block, object);
count++;
@@ -146,7 +137,7 @@ public int eachObject(VirtualFrame frame, DynamicObject ofClass, DynamicObject b

int count = 0;

for (DynamicObject object : new ObjectGraph(getContext()).getObjects()) {
for (DynamicObject object : ObjectGraph.stopAndGetAllObjects(this, getContext())) {
if (!isHidden(object) && ModuleOperations.assignableTo(Layouts.BASIC_OBJECT.getLogicalClass(object), ofClass)) {
yield(frame, block, object);
count++;
Original file line number Diff line number Diff line change
@@ -10,12 +10,15 @@
package org.jruby.truffle.nodes.core;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.FrameInstance;
import com.oracle.truffle.api.frame.FrameInstanceVisitor;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeVisitor;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
@@ -37,15 +40,13 @@
import org.jruby.truffle.runtime.core.CoreLibrary;
import org.jruby.truffle.runtime.hash.BucketsStrategy;
import org.jruby.truffle.runtime.layouts.Layouts;
import org.jruby.truffle.runtime.methods.InternalMethod;
import org.jruby.truffle.runtime.subsystems.SimpleShell;
import org.jruby.util.ByteList;
import org.jruby.util.Memo;
import org.jruby.util.StringSupport;

import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.*;

@CoreClass(name = "Truffle::Primitive")
public abstract class TrufflePrimitiveNodes {
@@ -565,4 +566,53 @@ public DynamicObject printInterleavedBacktrace() {

}

@CoreMethod(names = "ast", onSingleton = true, required = 1)
public abstract static class ASTNode extends CoreMethodArrayArgumentsNode {

public ASTNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

@Specialization(guards = "isRubyMethod(method)")
public DynamicObject astMethod(DynamicObject method) {
return ast(Layouts.METHOD.getMethod(method));
}

@Specialization(guards = "isRubyUnboundMethod(method)")
public DynamicObject astUnboundMethod(DynamicObject method) {
return ast(Layouts.UNBOUND_METHOD.getMethod(method));
}

@Specialization(guards = "isRubyProc(proc)")
public DynamicObject astProc(DynamicObject proc) {
return ast(Layouts.PROC.getMethod(proc));
}

@TruffleBoundary
private DynamicObject ast(InternalMethod method) {
if (method.getCallTarget() instanceof RootCallTarget) {
return ast(((RootCallTarget) method.getCallTarget()).getRootNode());
} else {
return nil();
}
}

private DynamicObject ast(Node node) {
if (node == null) {
return nil();
}

final List<Object> array = new ArrayList<>();

array.add(getSymbol(node.getClass().getSimpleName()));

for (Node child : node.getChildren()) {
array.add(ast(child));
}

return Layouts.ARRAY.createArray(getContext().getCoreLibrary().getArrayFactory(), array.toArray(), array.size());
}

}

}
141 changes: 141 additions & 0 deletions truffle/src/main/java/org/jruby/truffle/nodes/ext/ObjSpaceNodes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2015 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.nodes.ext;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.nodes.core.CoreClass;
import org.jruby.truffle.nodes.core.CoreMethod;
import org.jruby.truffle.nodes.core.CoreMethodArrayArgumentsNode;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.layouts.Layouts;
import org.jruby.truffle.runtime.object.ObjectGraph;

import java.util.Set;

@CoreClass(name = "Truffle::ObjSpace")
public abstract class ObjSpaceNodes {

@CoreMethod(names = "memsize_of", isModuleFunction = true, required = 1)
public abstract static class MemsizeOfNode extends CoreMethodArrayArgumentsNode {

public MemsizeOfNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

@Specialization
public int memsizeOf(boolean object) {
return 0;
}

@Specialization
public int memsizeOf(int object) {
return 0;
}

@Specialization
public int memsizeOf(long object) {
return 0;
}

@Specialization
public int memsizeOf(double object) {
return 0;
}

@Specialization(guards = "isRubyArray(object)")
public int memsizeOfArray(DynamicObject object) {
return 1 + object.getShape().getPropertyListInternal(false).size() + Layouts.ARRAY.getSize(object);
}

@Specialization(guards = "isRubyHash(object)")
public int memsizeOfHash(DynamicObject object) {
return 1 + object.getShape().getPropertyListInternal(false).size() + Layouts.HASH.getSize(object);
}

@Specialization(guards = "isRubyString(object)")
public int memsizeOfString(DynamicObject object) {
return 1 + object.getShape().getPropertyListInternal(false).size() + Layouts.STRING.getByteList(object).getRealSize();
}

@Specialization(guards = {"!isRubyArray(object)", "!isRubyHash(object)", "!isRubyString(object)"})
public int memsizeOfObject(DynamicObject object) {
return 1 + object.getShape().getPropertyListInternal(false).size();
}

}

@CoreMethod(names = "adjacent_objects", isModuleFunction = true, required = 1)
public abstract static class AdjacentObjectsNode extends CoreMethodArrayArgumentsNode {

public AdjacentObjectsNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

@CompilerDirectives.TruffleBoundary
@Specialization
public DynamicObject adjacentObjects(DynamicObject object) {
final Set<DynamicObject> objects = ObjectGraph.getAdjacentObjects(object);
return Layouts.ARRAY.createArray(getContext().getCoreLibrary().getArrayFactory(), objects.toArray(), objects.size());
}

}

@CoreMethod(names = "root_objects", isModuleFunction = true)
public abstract static class RootObjectsNode extends CoreMethodArrayArgumentsNode {

public RootObjectsNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

@CompilerDirectives.TruffleBoundary
@Specialization
public DynamicObject rootObjects() {
final Set<DynamicObject> objects = ObjectGraph.stopAndGetRootObjects(this, getContext());
return Layouts.ARRAY.createArray(getContext().getCoreLibrary().getArrayFactory(), objects.toArray(), objects.size());
}

}

@CoreMethod(names = "trace_allocations_start", isModuleFunction = true)
public abstract static class TraceAllocationsStartNode extends CoreMethodArrayArgumentsNode {

public TraceAllocationsStartNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

@CompilerDirectives.TruffleBoundary
@Specialization
public DynamicObject traceAllocationsStart() {
getContext().getObjectSpaceManager().traceAllocationsStart();
return nil();
}

}

@CoreMethod(names = "trace_allocations_stop", isModuleFunction = true)
public abstract static class TraceAllocationsStopNode extends CoreMethodArrayArgumentsNode {

public TraceAllocationsStopNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

@CompilerDirectives.TruffleBoundary
@Specialization
public DynamicObject traceAllocationsStop() {
getContext().getObjectSpaceManager().traceAllocationsStop();
return nil();
}

}

}
Original file line number Diff line number Diff line change
@@ -9,18 +9,27 @@
*/
package org.jruby.truffle.nodes.objects;

import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.Truffle;
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.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.DynamicObjectFactory;
import com.oracle.truffle.api.source.SourceSection;
import org.jcodings.specific.UTF8Encoding;
import org.jruby.RubyString;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.RubyRootNode;
import org.jruby.truffle.runtime.RubyCallStack;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.layouts.Layouts;
import org.jruby.util.StringSupport;

@NodeChildren({
@NodeChild("classToAllocate"),
@@ -41,7 +50,7 @@ public DynamicObject allocate(DynamicObject classToAllocate, Object... values) {
@Specialization(guards = {
"cachedClassToAllocate == classToAllocate",
"!cachedIsSingleton"
}, limit = "getCacheLimit()")
}, assumptions = "getTracingAssumption()", limit = "getCacheLimit()")
public DynamicObject allocateCached(
DynamicObject classToAllocate,
Object[] values,
@@ -52,17 +61,62 @@ public DynamicObject allocateCached(
}

@CompilerDirectives.TruffleBoundary
@Specialization(contains = "allocateCached", guards = "!isSingleton(classToAllocate)")
@Specialization(
contains = "allocateCached",
guards = "!isSingleton(classToAllocate)",
assumptions = "getTracingAssumption()")
public DynamicObject allocateUncached(DynamicObject classToAllocate, Object[] values) {
return getInstanceFactory(classToAllocate).newInstance(values);
}

@CompilerDirectives.TruffleBoundary
@Specialization(guards = {"!isSingleton(classToAllocate)", "isTracing()"})
public DynamicObject allocateTracing(DynamicObject classToAllocate, Object[] values) {
final DynamicObject object = getInstanceFactory(classToAllocate).newInstance(values);

final Node caller = RubyCallStack.getTopMostUserCallNode();
final SourceSection callerSource = caller.getEncapsulatingSourceSection();

final String callerMethod;

if (caller.getRootNode() instanceof RubyRootNode) {
callerMethod = ((RubyRootNode) caller.getRootNode()).getSharedMethodInfo().getName();
} else {
callerMethod = "(unknown)";
}

getContext().getObjectSpaceManager().traceAllocation(
object,
string(Layouts.CLASS.getFields(classToAllocate).getName()),
string(callerMethod),
string(callerSource.getSource().getShortName()),
callerSource.getStartLine());

return object;
}

private DynamicObject string(String value) {
return Layouts.STRING.createString(
getContext().getCoreLibrary().getStringFactory(),
RubyString.encodeBytelist(value, UTF8Encoding.INSTANCE),
StringSupport.CR_UNKNOWN,
null);
}

@Specialization(guards = "isSingleton(classToAllocate)")
public DynamicObject allocateSingleton(DynamicObject classToAllocate, Object[] values) {
CompilerDirectives.transferToInterpreter();
throw new RaiseException(getContext().getCoreLibrary().typeError("can't create instance of singleton class", this));
}

protected Assumption getTracingAssumption() {
return getContext().getObjectSpaceManager().getTracingAssumption();
}

protected boolean isTracing() {
return !getContext().getObjectSpaceManager().getTracingAssumption().isValid();
}

protected boolean isSingleton(DynamicObject classToAllocate) {
return Layouts.CLASS.getIsSingleton(classToAllocate);
}
14 changes: 14 additions & 0 deletions truffle/src/main/java/org/jruby/truffle/runtime/RubyArguments.java
Original file line number Diff line number Diff line change
@@ -116,6 +116,20 @@ public static DynamicObject getUserKeywordsHash(Object[] internalArguments, int
return null;
}

public static MaterializedFrame tryGetDeclarationFrame(Object[] arguments) {
if (DECLARATION_FRAME_INDEX >= arguments.length) {
return null;
}

final Object frame = arguments[DECLARATION_FRAME_INDEX];

if (frame instanceof MaterializedFrame) {
return (MaterializedFrame) frame;
}

return null;
}

public static MaterializedFrame getDeclarationFrame(Object[] arguments) {
return (MaterializedFrame) arguments[DECLARATION_FRAME_INDEX];
}
Original file line number Diff line number Diff line change
@@ -38,6 +38,7 @@
import org.jruby.truffle.nodes.core.hash.HashNodesFactory;
import org.jruby.truffle.nodes.ext.BigDecimalNodesFactory;
import org.jruby.truffle.nodes.ext.DigestNodesFactory;
import org.jruby.truffle.nodes.ext.ObjSpaceNodesFactory;
import org.jruby.truffle.nodes.ext.ZlibNodesFactory;
import org.jruby.truffle.nodes.objects.FreezeNode;
import org.jruby.truffle.nodes.objects.FreezeNodeGen;
@@ -161,6 +162,7 @@ public class CoreLibrary {
private final DynamicObject internalBufferClass;
private final DynamicObject weakRefClass;
private final DynamicObjectFactory weakRefFactory;
private final DynamicObject objectSpaceModule;

private final DynamicObject argv;
private final DynamicObject globalVariablesObject;
@@ -416,7 +418,7 @@ public CoreLibrary(RubyContext context) {
defineModule("GC");
kernelModule = defineModule("Kernel");
defineModule("Math");
defineModule("ObjectSpace");
objectSpaceModule = defineModule("ObjectSpace");
signalModule = defineModule("Signal");

// The rest
@@ -432,6 +434,7 @@ public CoreLibrary(RubyContext context) {
defineModule(truffleModule, "Primitive");
defineModule(truffleModule, "Digest");
defineModule(truffleModule, "Zlib");
defineModule(truffleModule, "ObjSpace");
bigDecimalClass = defineClass(truffleModule, numericClass, "BigDecimal");
Layouts.CLASS.setInstanceFactoryUnsafe(bigDecimalClass, Layouts.BIG_DECIMAL.createBigDecimalShape(bigDecimalClass, bigDecimalClass));

@@ -551,6 +554,7 @@ private void addCoreMethods() {
coreMethodNodeManager.addCoreMethodNodes(DigestNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(BigDecimalNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(ZlibNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(ObjSpaceNodesFactory.getFactories());
coreMethodNodeManager.allMethodInstalled();

basicObjectSendMethod = Layouts.MODULE.getFields(basicObjectClass).getMethods().get("__send__");
@@ -1610,4 +1614,8 @@ public DynamicObjectFactory getWeakRefFactory() {
return weakRefFactory;
}

public Object getObjectSpaceModule() {
return objectSpaceModule;
}

}
Original file line number Diff line number Diff line change
@@ -27,12 +27,13 @@
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.layouts.Layouts;
import org.jruby.truffle.runtime.methods.InternalMethod;
import org.jruby.truffle.runtime.object.ObjectGraphNode;
import org.jruby.truffle.runtime.object.ObjectIDOperations;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class ModuleFields implements ModuleChain {
public class ModuleFields implements ModuleChain, ObjectGraphNode {

public static void debugModuleChain(DynamicObject module) {
assert RubyGuards.isRubyModule(module);
@@ -596,4 +597,28 @@ public DynamicObject getLogicalClass() {
return Layouts.BASIC_OBJECT.getLogicalClass(rubyModuleObject);
}

@Override
public Set<DynamicObject> getAdjacentObjects() {
final Set<DynamicObject> adjacent = new HashSet<>();

if (lexicalParent != null) {
adjacent.add(lexicalParent);
}

for (RubyConstant constant : constants.values()) {
final Object value = constant.getValue();

if (value instanceof DynamicObject) {
adjacent.add((DynamicObject) value);
}
}

for (Object value : classVariables.values()) {
if (value instanceof DynamicObject) {
adjacent.add((DynamicObject) value);
}
}

return adjacent;
}
}
Original file line number Diff line number Diff line change
@@ -10,18 +10,24 @@
package org.jruby.truffle.runtime.methods;

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.DynamicObject;
import org.jruby.runtime.Visibility;
import org.jruby.truffle.nodes.RubyGuards;
import org.jruby.truffle.runtime.layouts.Layouts;
import org.jruby.truffle.runtime.object.ObjectGraph;
import org.jruby.truffle.runtime.object.ObjectGraphNode;

import java.util.HashSet;
import java.util.Set;

/**
* Any kind of Ruby method - so normal methods in classes and modules, but also blocks, procs,
* lambdas and native methods written in Java.
*/
public class InternalMethod {
public class InternalMethod implements ObjectGraphNode {

private final SharedMethodInfo sharedMethodInfo;
private final String name;
@@ -135,4 +141,19 @@ public String toString() {
return sharedMethodInfo.toString();
}

@Override
public Set<DynamicObject> getAdjacentObjects() {
final Set<DynamicObject> adjacent = new HashSet<>();

if (declaringModule != null) {
adjacent.add(declaringModule);
}

if (declarationFrame != null) {
adjacent.addAll(ObjectGraph.getObjectsInFrame(declarationFrame));
}

return adjacent;
}

}
225 changes: 95 additions & 130 deletions truffle/src/main/java/org/jruby/truffle/runtime/object/ObjectGraph.java
Original file line number Diff line number Diff line change
@@ -14,190 +14,155 @@
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.Property;
import org.jruby.truffle.nodes.RubyGuards;
import org.jruby.truffle.runtime.RubyArguments;
import org.jruby.truffle.runtime.RubyConstant;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.core.ModuleFields;
import org.jruby.truffle.runtime.hash.Entry;
import org.jruby.truffle.runtime.layouts.Layouts;
import org.jruby.truffle.runtime.methods.InternalMethod;
import org.jruby.truffle.runtime.subsystems.SafepointAction;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;

public class ObjectGraph {
public abstract class ObjectGraph {

private final RubyContext context;
public static Set<DynamicObject> stopAndGetAllObjects(
Node currentNode, final RubyContext context) {
final Set<DynamicObject> visited = new HashSet<>();

public ObjectGraph(RubyContext context) {
this.context = context;
}

public Set<DynamicObject> getObjects() {
return visitObjects(new ObjectGraphVisitor() {

@Override
public boolean visit(DynamicObject object) throws StopVisitingObjectsException {
return true;
}

});
}

public Set<DynamicObject> visitObjects(final ObjectGraphVisitor visitor) {
final Set<DynamicObject> objects = new HashSet<>();
final Thread stoppingThread = Thread.currentThread();

visitRoots(new ObjectGraphVisitor() {
context.getSafepointManager().pauseAllThreadsAndExecute(currentNode, false, new SafepointAction() {

@Override
public boolean visit(DynamicObject object) throws StopVisitingObjectsException {
if (objects.add(object)) {
visitor.visit(object);
return true;
} else {
return false;
}
}

});

return objects;
}
public void run(DynamicObject thread, Node currentNode) {
synchronized (visited) {
final Deque<DynamicObject> stack = new ArrayDeque<>();

private void visitRoots(final ObjectGraphVisitor visitor) {
final Thread mainThread = Thread.currentThread();
stack.add(thread);

context.getSafepointManager().pauseAllThreadsAndExecute(null, false, new SafepointAction() {
if (Thread.currentThread() == stoppingThread) {
stack.addAll(ObjectGraph.getAdjacentObjects(context.getCoreLibrary().getGlobalVariablesObject()));
stack.addAll(context.getAtExitManager().getHandlers());
stack.addAll(context.getObjectSpaceManager().getFinalizerHandlers());
}

boolean keepVisiting = true;
final FrameInstance currentFrame = Truffle.getRuntime().getCurrentFrame();

@Override
public void run(DynamicObject thread, Node currentNode) {
synchronized (this) {
if (!keepVisiting) {
return;
if (currentFrame != null) {
stack.addAll(getObjectsInFrame(currentFrame.getFrame(FrameInstance.FrameAccess.READ_ONLY, false)));
}

try {
// We only visit the global variables and other global state from the root thread
Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<Object>() {

if (Thread.currentThread() == mainThread) {
visitObject(context.getCoreLibrary().getGlobalVariablesObject(), visitor);
@Override
public Object visitFrame(FrameInstance frameInstance) {
stack.addAll(getObjectsInFrame(frameInstance
.getFrame(FrameInstance.FrameAccess.READ_ONLY, false)));

for (DynamicObject handler : context.getAtExitManager().getHandlers()) {
visitObject(handler, visitor);
}

for (DynamicObject handler : context.getObjectSpaceManager().getFinalizerHandlers()) {
visitObject(handler, visitor);
}
return null;
}

// All threads visit their thread object

visitObject(thread, visitor);
});

// All threads visit their call stack
while (!stack.isEmpty()) {
final DynamicObject object = stack.pop();

if (Truffle.getRuntime().getCurrentFrame() != null) {
visitFrameInstance(Truffle.getRuntime().getCurrentFrame(), visitor);
if (visited.add(object)) {
stack.addAll(ObjectGraph.getAdjacentObjects(object));
}

Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<Object>() {

@Override
public Object visitFrame(FrameInstance frameInstance) {
try {
visitFrameInstance(frameInstance, visitor);
} catch (StopVisitingObjectsException e) {
return new Object();
}
return null;
}

});
} catch (StopVisitingObjectsException e) {
keepVisiting = false;
}
}
}

});

return visited;
}

private void visitObject(DynamicObject object, ObjectGraphVisitor visitor) throws StopVisitingObjectsException {
if (visitor.visit(object)) {
// Visiting the meta class will also visit the logical class eventually
public static Set<DynamicObject> stopAndGetRootObjects(Node currentNode, final RubyContext context) {
final Set<DynamicObject> objects = new HashSet<>();

visitObject(Layouts.BASIC_OBJECT.getMetaClass(object), visitor);
final Thread stoppingThread = Thread.currentThread();

// Visit all properties
context.getSafepointManager().pauseAllThreadsAndExecute(currentNode, false, new SafepointAction() {

for (Property property : object.getShape().getPropertyListInternal(false)) {
visitObject(property.get(object, object.getShape()), visitor);
@Override
public void run(DynamicObject thread, Node currentNode) {
objects.add(thread);

if (Thread.currentThread() == stoppingThread) {
objects.addAll(ObjectGraph.getAdjacentObjects(context.getCoreLibrary().getGlobalVariablesObject()));
objects.addAll(context.getAtExitManager().getHandlers());
objects.addAll(context.getObjectSpaceManager().getFinalizerHandlers());
}
}

// Visit specific objects that we're managing
});

if (RubyGuards.isRubyModule(object)) {
visitModule(object, visitor);
}
}
return objects;
}

private void visitModule(DynamicObject module, ObjectGraphVisitor visitor) {
final ModuleFields fields = Layouts.MODULE.getFields(module);
public static Set<DynamicObject> getAdjacentObjects(DynamicObject object) {
final Set<DynamicObject> reachable = new HashSet<>();

for (DynamicObject ancestor : fields.ancestors()) {
visitObject(ancestor, visitor);
}
reachable.add(Layouts.BASIC_OBJECT.getLogicalClass(object));
reachable.add(Layouts.BASIC_OBJECT.getMetaClass(object));

for (RubyConstant constant : fields.getConstants().values()) {
visitObject(constant.getValue(), visitor);
}
}
for (Property property : object.getShape().getPropertyListInternal(false)) {
final Object propertyValue = property.get(object, object.getShape());

if (propertyValue instanceof DynamicObject) {
reachable.add((DynamicObject) propertyValue);
} else if (propertyValue instanceof Entry[]) {
for (Entry bucket : (Entry[]) propertyValue) {
while (bucket != null) {
if (bucket.getKey() instanceof DynamicObject) {
reachable.add((DynamicObject) bucket.getKey());
}

private void visitObject(Object object, ObjectGraphVisitor visitor) throws StopVisitingObjectsException {
if (object instanceof DynamicObject) {
visitObject((DynamicObject) object, visitor);
} else if (object instanceof Object[]) {
for (Object child : (Object[]) object) {
visitObject(child, visitor);
if (bucket.getValue() instanceof DynamicObject) {
reachable.add((DynamicObject) bucket.getValue());
}

bucket = bucket.getNextInLookup();
}
}
} else if (propertyValue instanceof Object[]) {
for (Object element : (Object[]) propertyValue) {
if (element instanceof DynamicObject) {
reachable.add((DynamicObject) element);
}
}
} else if (propertyValue instanceof Frame) {
reachable.addAll(getObjectsInFrame((Frame) propertyValue));
} else if (propertyValue instanceof ObjectGraphNode) {
reachable.addAll(((ObjectGraphNode) propertyValue).getAdjacentObjects());
}
} else if (object instanceof Entry) {
final Entry entry = (Entry) object;
visitObject(entry.getKey(), visitor);
visitObject(entry.getValue(), visitor);
visitObject(entry.getNextInLookup(), visitor);
} else if (object instanceof Frame) {
visitFrame((Frame) object, visitor);
} else if (object instanceof InternalMethod) {
final InternalMethod method = (InternalMethod) object;
visitObject(method.getDeclarationFrame(), visitor);
}
}

private void visitFrameInstance(FrameInstance frameInstance, ObjectGraphVisitor visitor) throws StopVisitingObjectsException {
visitFrame(frameInstance.getFrame(FrameInstance.FrameAccess.READ_ONLY, true), visitor);
return reachable;
}

private void visitFrame(Frame frame, ObjectGraphVisitor visitor) throws StopVisitingObjectsException {
for (FrameSlot slot : frame.getFrameDescriptor().getSlots()) {
visitObject(frame.getValue(slot), visitor);
}
public static Set<DynamicObject> getObjectsInFrame(Frame frame) {
final Set<DynamicObject> objects = new HashSet<>();

Frame declarationFrame;
final Frame lexicalParentFrame = RubyArguments.tryGetDeclarationFrame(frame.getArguments());

try {
declarationFrame = RubyArguments.getDeclarationFrame(frame.getArguments());
} catch (Exception e) {
declarationFrame = null;
if (lexicalParentFrame != null) {
objects.addAll(getObjectsInFrame(lexicalParentFrame));
}

if (declarationFrame != null) {
visitFrame(declarationFrame, visitor);
for (FrameSlot slot : frame.getFrameDescriptor().getSlots()) {
final Object slotValue = frame.getValue(slot);

if (slotValue instanceof DynamicObject) {
objects.add((DynamicObject) slotValue);
}
}

return objects;
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2015 Oracle and/or its affiliates. All rights reserved. This
* Copyright (c) 2015 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:
*
@@ -11,8 +11,10 @@

import com.oracle.truffle.api.object.DynamicObject;

public interface ObjectGraphVisitor {
import java.util.Set;

boolean visit(DynamicObject object) throws StopVisitingObjectsException;
public interface ObjectGraphNode {

Set<DynamicObject> getAdjacentObjects();

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -9,9 +9,12 @@
*/
package org.jruby.truffle.runtime.subsystems;

import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.utilities.CyclicAssumption;
import org.jruby.truffle.nodes.core.ThreadNodes;
import org.jruby.truffle.nodes.dispatch.CallDispatchHeadNode;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.subsystems.ThreadManager.BlockingAction;
@@ -55,6 +58,10 @@ public void clearFinalizers() {
private final ReferenceQueue<DynamicObject> finalizerQueue = new ReferenceQueue<>();
private DynamicObject finalizerThread;

private final CyclicAssumption tracingAssumption = new CyclicAssumption("objspec-tracing");
private int tracingAssumptionActivations = 0;
private boolean tracingPaused = false;

public ObjectSpaceManager(RubyContext context) {
this.context = context;
}
@@ -135,4 +142,38 @@ public List<DynamicObject> getFinalizerHandlers() {
return handlers;
}

public void traceAllocationsStart() {
tracingAssumptionActivations++;

if (tracingAssumptionActivations == 1) {
tracingAssumption.getAssumption().invalidate();
}
}

public void traceAllocationsStop() {
tracingAssumptionActivations--;

if (tracingAssumptionActivations == 0) {
tracingAssumption.invalidate();
}
}

public void traceAllocation(DynamicObject object, DynamicObject classPath, DynamicObject methodId, DynamicObject sourcefile, int sourceline) {
if (tracingPaused) {
return;
}

tracingPaused = true;

try {
context.send(context.getCoreLibrary().getObjectSpaceModule(), "trace_allocation", null, object, classPath, methodId, sourcefile, sourceline);
} finally {
tracingPaused = false;
}
}

public Assumption getTracingAssumption() {
return tracingAssumption.getAssumption();
}

}
Original file line number Diff line number Diff line change
@@ -124,6 +124,21 @@ private void step(Node currentNode, boolean isDrivingThread) {
}
}

public void pauseAllThreadsAndExecuteOnOneThread(Node currentNode, boolean deferred, final SafepointAction action) {
final Thread mainThread = Thread.currentThread();

pauseAllThreadsAndExecute(currentNode, deferred, new SafepointAction() {

@Override
public void run(DynamicObject thread, Node currentNode) {
if (Thread.currentThread() == mainThread) {
action.run(thread, currentNode);
}
}

});
}

public void pauseAllThreadsAndExecute(Node currentNode, boolean deferred, SafepointAction action) {
if (lock.isHeldByCurrentThread()) {
throw new IllegalStateException("Re-entered SafepointManager");

0 comments on commit 63ab92d

Please sign in to comment.