Skip to content

Commit

Permalink
Showing 19 changed files with 827 additions and 193 deletions.
253 changes: 253 additions & 0 deletions lib/ruby/truffle/truffle/objspace.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
# 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)
node_stack = [Truffle::Primitive.ast(method)]

until node_stack.empty?
node = node_stack.pop
next if node.nil?

name = node.first
children = node.drop(1)
nodes[name] ||= 0
nodes[name] += 1
node_stack.push(*children)
end
end
private :count_nodes_method

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 unless klass.name.nil?
total += per_klass
end
hash[:TOTAL] = total
hash
end
module_function :count_objects_size

def count_tdata_objects(hash = {})
ObjectSpace.each_object do |object|
object_type = Truffle::Primitive.object_type_of(object)
hash[object_type] ||= 0
hash[object_type] += 1
end
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?
allocation.generation
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, :generation)

ALLOCATIONS = {}.compare_by_identity

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

end
9 changes: 9 additions & 0 deletions test/mri/excludes_truffle/TestObjSpace.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
exclude :test_memsize_of_root_shared_string, "we don't share strings yet"
exclude :test_dump_flags, "we don't expose the same GC information as MRI"
exclude :test_dump_to_default, "object attributes are not the same as MRI"
exclude :test_dump_to_io, "uses a pipe in a way that isn't quite working for us"
exclude :test_dump_uninitialized_file, "needs spawn"
exclude :test_reachable_objects_from, "needs spawn"
exclude :test_reachable_objects_size, "needs spawn"
exclude :test_dump_all, "needs spawn"
exclude :test_argf_memsize, "we store ext as an ivar so it doesn't show up in the object size as they expect"
2 changes: 1 addition & 1 deletion test/mri_truffle.index
Original file line number Diff line number Diff line change
@@ -486,7 +486,7 @@ logger/test_severity.rb
# mkmf/test_signedness.rb
# mkmf/test_sizeof.rb
# net/ftp/test_buffered_socket.rb
# objspace/test_objspace.rb
objspace/test_objspace.rb
# open-uri/test_open-uri.rb
# open-uri/test_ssl.rb
# openssl/test_fips.rb
Original file line number Diff line number Diff line change
@@ -156,7 +156,7 @@ private static RubyRootNode makeGenericMethod(RubyContext context, MethodDetails

final Arity arity = new Arity(required, optional, method.rest());

final SharedMethodInfo sharedMethodInfo = new SharedMethodInfo(sourceSection, LexicalScope.NONE, arity, methodDetails.getIndicativeName(), false, null, context.getOptions().CORE_ALWAYS_CLONE);
final SharedMethodInfo sharedMethodInfo = new SharedMethodInfo(sourceSection, LexicalScope.NONE, arity, method.names()[0], false, null, context.getOptions().CORE_ALWAYS_CLONE);

final List<RubyNode> argumentsNodes = new ArrayList<>();

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();
throw new RaiseException(getContext().getCoreLibrary().rangeError(String.format("0x%016x is not id value", id), this));
}

@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,9 +137,9 @@ 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())) {
final DynamicObject metaClass = Layouts.BASIC_OBJECT.getMetaClass(object);
if (!isHidden(object) && ModuleOperations.includesModule(metaClass, ofClass)) {
if (!isHidden(object) && ModuleOperations.assignableTo(metaClass, 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;
@@ -39,11 +42,13 @@
import org.jruby.truffle.runtime.core.StringOperations;
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.*;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@@ -569,6 +574,68 @@ 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());
}

}

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

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

@Specialization
public DynamicObject objectTypeOf(DynamicObject value) {
return getSymbol(value.getShape().getObjectType().getClass().getSimpleName());
}
}

@CoreMethod(names = "spawn_process", onSingleton = true, required = 3)
public abstract static class SpawnProcess extends CoreMethodArrayArgumentsNode {

Original file line number Diff line number Diff line change
@@ -28,10 +28,12 @@
public abstract class ArrayLiteralNode extends RubyNode {

@Children protected final RubyNode[] values;
@Child protected AllocateObjectNode allocateObjectNode;

public ArrayLiteralNode(RubyContext context, SourceSection sourceSection, RubyNode[] values) {
super(context, sourceSection);
this.values = values;
allocateObjectNode = AllocateObjectNodeGen.create(context, sourceSection, false, null, null);
}

protected DynamicObject makeGeneric(VirtualFrame frame, Object[] alreadyExecuted) {
@@ -49,7 +51,7 @@ protected DynamicObject makeGeneric(VirtualFrame frame, Object[] alreadyExecuted
}
}

return Layouts.ARRAY.createArray(getContext().getCoreLibrary().getArrayFactory(), executedValues, executedValues.length);
return allocateObjectNode.allocate(getContext().getCoreLibrary().getArrayClass(), executedValues, executedValues.length);
}

@Override
@@ -88,7 +90,7 @@ public EmptyArrayLiteralNode(RubyContext context, SourceSection sourceSection, R

@Override
public Object execute(VirtualFrame frame) {
return Layouts.ARRAY.createArray(getContext().getCoreLibrary().getArrayFactory(), null, 0);
return allocateObjectNode.allocate(getContext().getCoreLibrary().getArrayClass(), null, 0);
}

}
@@ -112,7 +114,7 @@ public Object execute(VirtualFrame frame) {
}
}

return Layouts.ARRAY.createArray(getContext().getCoreLibrary().getArrayFactory(), executedValues, values.length);
return allocateObjectNode.allocate(getContext().getCoreLibrary().getArrayClass(), executedValues, values.length);
}

private DynamicObject makeGeneric(VirtualFrame frame,
@@ -147,7 +149,7 @@ public Object execute(VirtualFrame frame) {
}
}

return Layouts.ARRAY.createArray(getContext().getCoreLibrary().getArrayFactory(), executedValues, values.length);
return allocateObjectNode.allocate(getContext().getCoreLibrary().getArrayClass(), executedValues, values.length);
}

private DynamicObject makeGeneric(VirtualFrame frame,
@@ -182,7 +184,7 @@ public Object execute(VirtualFrame frame) {
}
}

return Layouts.ARRAY.createArray(getContext().getCoreLibrary().getArrayFactory(), executedValues, values.length);
return allocateObjectNode.allocate(getContext().getCoreLibrary().getArrayClass(), executedValues, values.length);
}

private DynamicObject makeGeneric(VirtualFrame frame,
@@ -213,7 +215,7 @@ public Object execute(VirtualFrame frame) {
executedValues[n] = values[n].execute(frame);
}

return Layouts.ARRAY.createArray(getContext().getCoreLibrary().getArrayFactory(), executedValues, values.length);
return allocateObjectNode.allocate(getContext().getCoreLibrary().getArrayClass(), executedValues, values.length);
}

}
@@ -235,7 +237,7 @@ public Object execute(VirtualFrame frame) {
executedValues[n] = values[n].execute(frame);
}

final DynamicObject array = Layouts.ARRAY.createArray(getContext().getCoreLibrary().getArrayFactory(), storeSpecialisedFromObjects(executedValues), executedValues.length);
final DynamicObject array = allocateObjectNode.allocate(getContext().getCoreLibrary().getArrayClass(), storeSpecialisedFromObjects(executedValues), executedValues.length);
final Object store = Layouts.ARRAY.getStore(array);

if (store == null) {
152 changes: 152 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,152 @@
/*
* 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(guards = "isNil(nil)")
public int memsizeOf(Object nil) {
return 0;
}

@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 = "isRubyMatchData(object)")
public int memsizeOfMatchData(DynamicObject object) {
return 1 + object.getShape().getPropertyListInternal(false).size() + Layouts.MATCH_DATA.getValues(object).length;
}

@Specialization(guards = {"!isNil(object)", "!isRubyArray(object)", "!isRubyHash(object)",
"!isRubyString(object)", "!isRubyMatchData(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
@@ -13,6 +13,8 @@
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.objects.AllocateObjectNode;
import org.jruby.truffle.nodes.objects.AllocateObjectNodeGen;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.layouts.Layouts;
import org.jruby.util.ByteList;
@@ -23,17 +25,19 @@ public class StringLiteralNode extends RubyNode {
private final ByteList bytes;
private final int codeRange;

@Child private AllocateObjectNode allocateObjectNode;

public StringLiteralNode(RubyContext context, SourceSection sourceSection, ByteList bytes, int codeRange) {
super(context, sourceSection);
assert bytes != null;
this.bytes = bytes;
this.codeRange = codeRange;
allocateObjectNode = AllocateObjectNodeGen.create(context, sourceSection, false, null, null);
}

@Override
public DynamicObject execute(VirtualFrame frame) {
final DynamicObject string = Layouts.STRING.createString(getContext().getCoreLibrary().getStringFactory(), bytes.dup(), codeRange, null);
return string;
return allocateObjectNode.allocate(getContext().getCoreLibrary().getStringClass(), bytes.dup(), codeRange, null);
}

}
Original file line number Diff line number Diff line change
@@ -9,27 +9,47 @@
*/
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.frame.Frame;
import com.oracle.truffle.api.frame.FrameInstance;
import com.oracle.truffle.api.frame.VirtualFrame;
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.RubyArguments;
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"),
@NodeChild("values")
})
public abstract class AllocateObjectNode extends RubyNode {

private final boolean useCallerFrame;

public AllocateObjectNode(RubyContext context, SourceSection sourceSection) {
this(context, sourceSection, true);
}

public AllocateObjectNode(RubyContext context, SourceSection sourceSection, boolean useCallerFrame) {
super(context, sourceSection);
this.useCallerFrame = useCallerFrame;
}

public DynamicObject allocate(DynamicObject classToAllocate, Object... values) {
@@ -40,8 +60,9 @@ public DynamicObject allocate(DynamicObject classToAllocate, Object... values) {

@Specialization(guards = {
"cachedClassToAllocate == classToAllocate",
"!cachedIsSingleton"
}, limit = "getCacheLimit()")
"!cachedIsSingleton",
"!isTracing()"
}, assumptions = "getTracingAssumption()", limit = "getCacheLimit()")
public DynamicObject allocateCached(
DynamicObject classToAllocate,
Object[] values,
@@ -52,17 +73,69 @@ public DynamicObject allocateCached(
}

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

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

final FrameInstance allocatingFrameInstance;
final Node allocatingNode;

if (useCallerFrame) {
allocatingFrameInstance = RubyCallStack.getCallerFrame(getContext());
allocatingNode = RubyCallStack.getTopMostUserCallNode();
} else {
allocatingFrameInstance = Truffle.getRuntime().getCurrentFrame();
allocatingNode = this;
}

final Frame allocatingFrame = allocatingFrameInstance.getFrame(FrameInstance.FrameAccess.READ_ONLY, true);

final Object allocatingSelf = RubyArguments.getSelf(allocatingFrame.getArguments());
final String allocatingMethod = RubyArguments.getMethod(allocatingFrame.getArguments()).getName();
final SourceSection allocatingSourceSection = allocatingNode.getEncapsulatingSourceSection();

getContext().getObjectSpaceManager().traceAllocation(
object,
string(Layouts.CLASS.getFields(getContext().getCoreLibrary().getLogicalClass(allocatingSelf)).getName()),
getSymbol(allocatingMethod),
string(allocatingSourceSection.getSource().getName()),
allocatingSourceSection.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().isTracing();
}

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.ext.EtcNodesFactory;
import org.jruby.truffle.nodes.objects.FreezeNode;
@@ -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");
defineModule(truffleModule, "Etc");
bigDecimalClass = defineClass(truffleModule, numericClass, "BigDecimal");
Layouts.CLASS.setInstanceFactoryUnsafe(bigDecimalClass, Layouts.BIG_DECIMAL.createBigDecimalShape(bigDecimalClass, bigDecimalClass));
@@ -552,6 +555,7 @@ private void addCoreMethods() {
coreMethodNodeManager.addCoreMethodNodes(DigestNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(BigDecimalNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(ZlibNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(ObjSpaceNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(EtcNodesFactory.getFactories());
coreMethodNodeManager.allMethodInstalled();

@@ -1563,4 +1567,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,14 @@
*/
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.AssumedValue;
import com.oracle.truffle.api.utilities.CyclicAssumption;
import org.jruby.RubyGC;
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 +60,11 @@ public void clearFinalizers() {
private final ReferenceQueue<DynamicObject> finalizerQueue = new ReferenceQueue<>();
private DynamicObject finalizerThread;

private final CyclicAssumption tracingAssumption = new CyclicAssumption("objspace-tracing");
@CompilerDirectives.CompilationFinal private boolean isTracing = false;
private int tracingAssumptionActivations = 0;
private boolean tracingPaused = false;

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

public void traceAllocationsStart() {
tracingAssumptionActivations++;

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

public void traceAllocationsStop() {
tracingAssumptionActivations--;

if (tracingAssumptionActivations == 0) {
isTracing = false;
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, RubyGC.getCollectionCount());
} finally {
tracingPaused = false;
}
}

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

public boolean isTracing() {
return isTracing;
}

}
13 changes: 13 additions & 0 deletions truffle/src/main/ruby/core/shims.rb
Original file line number Diff line number Diff line change
@@ -234,3 +234,16 @@ module Kernel
def gem(*args)
end
end

# Find out why Rubinius doesn't implement this
class Rubinius::ARGFClass

def inplace_mode
@ext
end

def inplace_mode=(ext)
@ext = ext
end

end

0 comments on commit 4447250

Please sign in to comment.