13514
|
1 /*
|
|
2 * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. This
|
|
3 * code is released under a tri EPL/GPL/LGPL license. You can use it,
|
|
4 * redistribute it and/or modify it under the terms of the:
|
|
5 *
|
|
6 * Eclipse Public License version 1.0
|
|
7 * GNU General Public License version 2
|
|
8 * GNU Lesser General Public License version 2.1
|
|
9 */
|
|
10 package com.oracle.truffle.ruby.runtime.objects;
|
|
11
|
|
12 import java.util.*;
|
|
13 import java.util.Map.Entry;
|
|
14
|
|
15 import com.oracle.truffle.api.*;
|
|
16 import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
|
17 import com.oracle.truffle.ruby.runtime.*;
|
|
18 import com.oracle.truffle.ruby.runtime.control.*;
|
|
19 import com.oracle.truffle.ruby.runtime.core.*;
|
|
20 import com.oracle.truffle.ruby.runtime.lookup.*;
|
|
21 import com.oracle.truffle.ruby.runtime.methods.*;
|
|
22
|
|
23 /**
|
|
24 * Represents the Ruby {@code BasicObject} class - the root of the Ruby class hierarchy.
|
|
25 */
|
|
26 public class RubyBasicObject {
|
|
27
|
|
28 @CompilationFinal protected RubyClass rubyClass;
|
|
29 protected RubyClass rubySingletonClass;
|
|
30
|
|
31 protected LookupNode lookupNode;
|
|
32
|
|
33 protected long objectID = -1;
|
|
34
|
|
35 public boolean hasPrivateLayout = false;
|
|
36 private ObjectLayout objectLayout;
|
|
37
|
|
38 public static final int PRIMITIVE_STORAGE_LOCATIONS_COUNT = 14;
|
|
39 protected int primitiveStorageLocation01;
|
|
40 protected int primitiveStorageLocation02;
|
|
41 protected int primitiveStorageLocation03;
|
|
42 protected int primitiveStorageLocation04;
|
|
43 protected int primitiveStorageLocation05;
|
|
44 protected int primitiveStorageLocation06;
|
|
45 protected int primitiveStorageLocation07;
|
|
46 protected int primitiveStorageLocation08;
|
|
47 protected int primitiveStorageLocation09;
|
|
48 protected int primitiveStorageLocation10;
|
|
49 protected int primitiveStorageLocation11;
|
|
50 protected int primitiveStorageLocation12;
|
|
51 protected int primitiveStorageLocation13;
|
|
52 protected int primitiveStorageLocation14;
|
|
53
|
|
54 // A bit map to indicate which primitives are set, so that they can be Nil
|
|
55 protected int primitiveSetMap;
|
|
56
|
|
57 protected Object[] objectStorageLocations;
|
|
58
|
|
59 public RubyBasicObject(RubyClass rubyClass) {
|
|
60 if (rubyClass != null) {
|
|
61 unsafeSetRubyClass(rubyClass);
|
|
62
|
|
63 if (rubyClass.getContext().getConfiguration().getFullObjectSpace()) {
|
|
64 rubyClass.getContext().getObjectSpaceManager().add(this);
|
|
65 }
|
|
66 }
|
|
67 }
|
|
68
|
|
69 public void initialize() {
|
|
70 }
|
|
71
|
|
72 public LookupNode getLookupNode() {
|
|
73 return lookupNode;
|
|
74 }
|
|
75
|
|
76 public RubyClass getRubyClass() {
|
|
77 assert rubyClass != null;
|
|
78 return rubyClass;
|
|
79 }
|
|
80
|
|
81 public boolean hasPrivateLayout() {
|
|
82 return hasPrivateLayout;
|
|
83 }
|
|
84
|
|
85 public ObjectLayout getObjectLayout() {
|
|
86 return objectLayout;
|
|
87 }
|
|
88
|
|
89 public ObjectLayout getUpdatedObjectLayout() {
|
|
90 updateLayout();
|
|
91 return objectLayout;
|
|
92 }
|
|
93
|
|
94 /**
|
|
95 * Does this object have an instance variable defined?
|
|
96 */
|
|
97 public boolean isInstanceVariableDefined(String name) {
|
|
98 if (!hasPrivateLayout && objectLayout != rubyClass.getObjectLayoutForInstances()) {
|
|
99 updateLayout();
|
|
100 }
|
|
101
|
|
102 return objectLayout.findStorageLocation(name) != null;
|
|
103 }
|
|
104
|
|
105 /**
|
|
106 * Set an instance variable to be a value. Slow path.
|
|
107 */
|
|
108 public void setInstanceVariable(String name, Object value) {
|
|
109 CompilerAsserts.neverPartOfCompilation();
|
|
110
|
|
111 // If the object's layout doesn't match the class, update
|
|
112
|
|
113 if (!hasPrivateLayout && objectLayout != rubyClass.getObjectLayoutForInstances()) {
|
|
114 updateLayout();
|
|
115 }
|
|
116
|
|
117 // Find the storage location
|
|
118
|
|
119 StorageLocation storageLocation = objectLayout.findStorageLocation(name);
|
|
120
|
|
121 if (storageLocation == null) {
|
|
122 /*
|
|
123 * It doesn't exist, so create a new layout for the class that includes it and update
|
|
124 * the layout of this object.
|
|
125 */
|
|
126
|
|
127 rubyClass.setObjectLayoutForInstances(rubyClass.getObjectLayoutForInstances().withNewVariable(rubyClass.getContext(), name, value.getClass()));
|
|
128 updateLayout();
|
|
129
|
|
130 storageLocation = objectLayout.findStorageLocation(name);
|
|
131 }
|
|
132
|
|
133 // Try to write to that storage location
|
|
134
|
|
135 try {
|
|
136 storageLocation.write(this, value);
|
|
137 } catch (GeneralizeStorageLocationException e) {
|
|
138 /*
|
|
139 * It might not be able to store the type that we passed, if not generalize the class's
|
|
140 * layout and update the layout of this object.
|
|
141 */
|
|
142
|
|
143 rubyClass.setObjectLayoutForInstances(rubyClass.getObjectLayoutForInstances().withGeneralisedVariable(rubyClass.getContext(), name));
|
|
144 updateLayout();
|
|
145
|
|
146 storageLocation = objectLayout.findStorageLocation(name);
|
|
147
|
|
148 // Try to write to the generalized storage location
|
|
149
|
|
150 try {
|
|
151 storageLocation.write(this, value);
|
|
152 } catch (GeneralizeStorageLocationException e1) {
|
|
153 // We know that we just generalized it, so this should not happen
|
|
154 throw new RuntimeException("Generalised an instance variable, but it still rejected the value");
|
|
155 }
|
|
156 }
|
|
157 }
|
|
158
|
|
159 /**
|
|
160 * Get the value of an instance variable, or Nil if it isn't defined. Slow path.
|
|
161 */
|
|
162 public Object getInstanceVariable(String name) {
|
|
163 CompilerAsserts.neverPartOfCompilation();
|
|
164
|
|
165 // If the object's layout doesn't match the class, update
|
|
166
|
|
167 if (!hasPrivateLayout && objectLayout != rubyClass.getObjectLayoutForInstances()) {
|
|
168 updateLayout();
|
|
169 }
|
|
170
|
|
171 // Find the storage location
|
|
172
|
|
173 final StorageLocation storageLocation = objectLayout.findStorageLocation(name);
|
|
174
|
|
175 // Get the value
|
|
176
|
|
177 if (storageLocation == null) {
|
|
178 return NilPlaceholder.INSTANCE;
|
|
179 }
|
|
180
|
|
181 return storageLocation.read(this, true);
|
|
182 }
|
|
183
|
|
184 public String[] getInstanceVariableNames() {
|
|
185 final Set<String> instanceVariableNames = getInstanceVariables().keySet();
|
|
186 return instanceVariableNames.toArray(new String[instanceVariableNames.size()]);
|
|
187 }
|
|
188
|
|
189 public RubyClass getSingletonClass() {
|
|
190 if (rubySingletonClass == null) {
|
|
191 /*
|
|
192 * The object a of class A has a singleton class a' of class Class, with name
|
|
193 * #<Class:#<A:objectid>>, and with superclass that is A.
|
|
194 *
|
|
195 * irb(main):001:0> class A; end
|
|
196 *
|
|
197 * => nil
|
|
198 *
|
|
199 * irb(main):002:0> a = A.new
|
|
200 *
|
|
201 * => #<A:0x007ff612a631e0>
|
|
202 *
|
|
203 * irb(main):003:0> a.singleton_class
|
|
204 *
|
|
205 * => #<Class:#<A:0x007ff612a631e0>>
|
|
206 *
|
|
207 * irb(main):004:0> a.singleton_class.class
|
|
208 *
|
|
209 * => Class
|
|
210 *
|
|
211 * irb(main):005:0> a.singleton_class.superclass
|
|
212 *
|
|
213 * => A
|
|
214 */
|
|
215
|
|
216 rubySingletonClass = new RubyClass(rubyClass.getParentModule(), rubyClass, String.format("#<Class:#<%s:%d>>", rubyClass.getName(), getObjectID()), true);
|
|
217
|
|
218 lookupNode = new LookupFork(rubySingletonClass, rubyClass);
|
|
219 }
|
|
220
|
|
221 return rubySingletonClass;
|
|
222 }
|
|
223
|
|
224 public long getObjectID() {
|
|
225 if (objectID == -1) {
|
|
226 objectID = rubyClass.getContext().getNextObjectID();
|
|
227 }
|
|
228
|
|
229 return objectID;
|
|
230 }
|
|
231
|
|
232 public String inspect() {
|
|
233 return toString();
|
|
234 }
|
|
235
|
|
236 /**
|
|
237 * Get a map of all instance variables.
|
|
238 */
|
|
239 protected Map<String, Object> getInstanceVariables() {
|
|
240 if (objectLayout == null) {
|
|
241 return Collections.emptyMap();
|
|
242 }
|
|
243
|
|
244 final Map<String, Object> instanceVariableMap = new HashMap<>();
|
|
245
|
|
246 for (Entry<String, StorageLocation> entry : objectLayout.getAllStorageLocations().entrySet()) {
|
|
247 final String name = entry.getKey();
|
|
248 final StorageLocation storageLocation = entry.getValue();
|
|
249
|
|
250 if (storageLocation.isSet(this)) {
|
|
251 instanceVariableMap.put(name, storageLocation.read(this, true));
|
|
252 }
|
|
253 }
|
|
254
|
|
255 return instanceVariableMap;
|
|
256 }
|
|
257
|
|
258 /**
|
|
259 * Set instance variables from a map.
|
|
260 */
|
|
261 protected void setInstanceVariables(Map<String, Object> instanceVariables) {
|
|
262 assert instanceVariables != null;
|
|
263
|
|
264 if (objectLayout == null) {
|
|
265 updateLayout();
|
|
266 }
|
|
267
|
|
268 for (Entry<String, Object> entry : instanceVariables.entrySet()) {
|
|
269 final StorageLocation storageLocation = objectLayout.findStorageLocation(entry.getKey());
|
|
270 assert storageLocation != null;
|
|
271
|
|
272 try {
|
|
273 storageLocation.write(this, entry.getValue());
|
|
274 } catch (GeneralizeStorageLocationException e) {
|
|
275 throw new RuntimeException("Should not have to be generalising when setting instance variables - " + entry.getValue().getClass().getName() + ", " +
|
|
276 storageLocation.getStoredClass().getName());
|
|
277 }
|
|
278 }
|
|
279 }
|
|
280
|
|
281 /**
|
|
282 * Update the layout of this object to match that of its class.
|
|
283 */
|
|
284 @CompilerDirectives.SlowPath
|
|
285 public void updateLayout() {
|
|
286 // Get the current values of instance variables
|
|
287
|
|
288 final Map<String, Object> instanceVariableMap = getInstanceVariables();
|
|
289
|
|
290 // Use the layout of the class
|
|
291
|
|
292 objectLayout = rubyClass.getObjectLayoutForInstances();
|
|
293
|
|
294 // Make all primitives as unset
|
|
295
|
|
296 primitiveSetMap = 0;
|
|
297
|
|
298 // Create a new array for objects
|
|
299
|
|
300 allocateObjectStorageLocations();
|
|
301
|
|
302 // Restore values
|
|
303
|
|
304 setInstanceVariables(instanceVariableMap);
|
|
305 }
|
|
306
|
|
307 private void allocateObjectStorageLocations() {
|
|
308 final int objectStorageLocationsUsed = objectLayout.getObjectStorageLocationsUsed();
|
|
309
|
|
310 if (objectStorageLocationsUsed == 0) {
|
|
311 objectStorageLocations = null;
|
|
312 } else {
|
|
313 objectStorageLocations = new Object[objectStorageLocationsUsed];
|
|
314 }
|
|
315 }
|
|
316
|
|
317 public void switchToPrivateLayout() {
|
|
318 final RubyContext context = getRubyClass().getContext();
|
|
319
|
|
320 final Map<String, Object> instanceVariables = getInstanceVariables();
|
|
321
|
|
322 hasPrivateLayout = true;
|
|
323 objectLayout = ObjectLayout.EMPTY;
|
|
324
|
|
325 for (Entry<String, Object> entry : instanceVariables.entrySet()) {
|
|
326 objectLayout = objectLayout.withNewVariable(context, entry.getKey(), entry.getValue().getClass());
|
|
327 }
|
|
328
|
|
329 setInstanceVariables(instanceVariables);
|
|
330 }
|
|
331
|
|
332 public void extend(RubyModule module) {
|
|
333 getSingletonClass().include(module);
|
|
334 }
|
|
335
|
|
336 @Override
|
|
337 public String toString() {
|
|
338 return "#<" + rubyClass.getName() + ":0x" + Long.toHexString(getObjectID()) + ">";
|
|
339 }
|
|
340
|
|
341 public boolean hasSingletonClass() {
|
|
342 return rubySingletonClass != null;
|
|
343 }
|
|
344
|
|
345 public Object send(String name, RubyProc block, Object... args) {
|
|
346 final RubyMethod method = getLookupNode().lookupMethod(name);
|
|
347
|
|
348 if (method == null || method.isUndefined()) {
|
|
349 throw new RaiseException(getRubyClass().getContext().getCoreLibrary().noMethodError(name, toString()));
|
|
350 }
|
|
351
|
|
352 return method.call(null, this, block, args);
|
|
353 }
|
|
354
|
|
355 public void unsafeSetRubyClass(RubyClass newRubyClass) {
|
|
356 assert rubyClass == null;
|
|
357
|
|
358 rubyClass = newRubyClass;
|
|
359 lookupNode = rubyClass;
|
|
360 }
|
|
361
|
|
362 }
|