nitvm: Throw an error when trying to read an uninitialized attribute
[nit.git] / src / vm.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2014 Julien Pagès <julien.pages@lirmm.fr>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Implementation of the Nit virtual machine
18 module vm
19
20 intrude import interpreter::naive_interpreter
21 import model_utils
22 import perfect_hashing
23
24 redef class ModelBuilder
25 redef fun run_naive_interpreter(mainmodule: MModule, arguments: Array[String])
26 do
27 var time0 = get_time
28 self.toolcontext.info("*** NITVM STARTING ***", 1)
29
30 var interpreter = new VirtualMachine(self, mainmodule, arguments)
31 init_naive_interpreter(interpreter, mainmodule)
32
33 var time1 = get_time
34 self.toolcontext.info("*** NITVM STOPPING : {time1-time0} ***", 2)
35 end
36 end
37
38 # A virtual machine based on the naive_interpreter
39 class VirtualMachine super NaiveInterpreter
40
41 # Perfect hashing and perfect numbering
42 var ph: Perfecthashing = new Perfecthashing
43
44 # Handles memory and garbage collection
45 var memory_manager: MemoryManager = new MemoryManager
46
47 # The unique instance of the `MInit` value
48 var initialization_value: Instance
49
50 init(modelbuilder: ModelBuilder, mainmodule: MModule, arguments: Array[String])
51 do
52 super
53 var init_type = new MInitType(mainmodule.model)
54 initialization_value = new MutableInstance(init_type)
55 end
56
57 # Subtyping test for the virtual machine
58 redef fun is_subtype(sub, sup: MType): Bool
59 do
60 var anchor = self.frame.arguments.first.mtype.as(MClassType)
61 var sup_accept_null = false
62 if sup isa MNullableType then
63 sup_accept_null = true
64 sup = sup.mtype
65 else if sup isa MNullType then
66 sup_accept_null = true
67 end
68
69 # Can `sub` provides null or not?
70 # Thus we can match with `sup_accept_null`
71 # Also discard the nullable marker if it exists
72 if sub isa MNullableType then
73 if not sup_accept_null then return false
74 sub = sub.mtype
75 else if sub isa MNullType then
76 return sup_accept_null
77 end
78 # Now the case of direct null and nullable is over
79
80 # An unfixed formal type can only accept itself
81 if sup isa MParameterType or sup isa MVirtualType then
82 return sub == sup
83 end
84
85 if sub isa MParameterType or sub isa MVirtualType then
86 sub = sub.anchor_to(mainmodule, anchor)
87 # Manage the second layer of null/nullable
88 if sub isa MNullableType then
89 if not sup_accept_null then return false
90 sub = sub.mtype
91 else if sub isa MNullType then
92 return sup_accept_null
93 end
94 end
95
96 assert sub isa MClassType
97
98 # `sup` accepts only null
99 if sup isa MNullType then return false
100
101 assert sup isa MClassType
102
103 # Create the sup vtable if not create
104 if not sup.mclass.loaded then create_class(sup.mclass)
105
106 # Sub can be discovered inside a Generic type during the subtyping test
107 if not sub.mclass.loaded then create_class(sub.mclass)
108
109 if anchor == null then anchor = sub
110 if sup isa MGenericType then
111 var sub2 = sub.supertype_to(mainmodule, anchor, sup.mclass)
112 assert sub2.mclass == sup.mclass
113
114 for i in [0..sup.mclass.arity[ do
115 var sub_arg = sub2.arguments[i]
116 var sup_arg = sup.arguments[i]
117 var res = is_subtype(sub_arg, sup_arg)
118
119 if res == false then return false
120 end
121 return true
122 end
123
124 var super_id = sup.mclass.vtable.id
125 var mask = sub.mclass.vtable.mask
126
127 return inter_is_subtype(super_id, mask, sub.mclass.vtable.internal_vtable)
128 end
129
130 # Subtyping test with perfect hashing
131 private fun inter_is_subtype(id: Int, mask:Int, vtable: Pointer): Bool `{
132 // hv is the position in hashtable
133 int hv = id & mask;
134
135 // Follow the pointer to somewhere in the vtable
136 long unsigned int *offset = (long unsigned int*)(((long int *)vtable)[-hv]);
137
138 // If the pointed value is corresponding to the identifier, the test is true, otherwise false
139 return *offset == id;
140 `}
141
142 # Redef init_instance to simulate the loading of a class
143 redef fun init_instance(recv: Instance)
144 do
145 if not recv.mtype.as(MClassType).mclass.loaded then create_class(recv.mtype.as(MClassType).mclass)
146
147 recv.vtable = recv.mtype.as(MClassType).mclass.vtable
148
149 assert(recv isa MutableInstance)
150
151 recv.internal_attributes = init_internal_attributes(initialization_value, recv.mtype.as(MClassType).mclass.all_mattributes(mainmodule, none_visibility).length)
152 super
153 end
154
155 # Initialize the internal representation of an object (its attribute values)
156 # `init_instance` is the initial value of attributes
157 private fun init_internal_attributes(init_instance: Instance, size: Int): Pointer
158 import Array[Instance].length, Array[Instance].[] `{
159
160 Instance* attributes = malloc(sizeof(Instance) * size);
161
162 int i;
163 for(i=0; i<size; i++)
164 attributes[i] = init_instance;
165
166 Instance_incr_ref(init_instance);
167 return attributes;
168 `}
169
170 # Creates the runtime structures for this class
171 fun create_class(mclass: MClass) do mclass.make_vt(self)
172
173 # Return the value of the attribute `mproperty` for the object `recv`
174 redef fun read_attribute(mproperty: MAttribute, recv: Instance): Instance
175 do
176 assert recv isa MutableInstance
177
178 # Read the attribute value with perfect hashing
179 var id = mproperty.intro_mclassdef.mclass.vtable.id
180
181 var i = read_attribute_ph(recv.internal_attributes, recv.vtable.internal_vtable,
182 recv.vtable.mask, id, mproperty.offset)
183
184 # If we get a `MInit` value, throw an error
185 if i == initialization_value then
186 fatal("Uninitialized attribute {mproperty.name}")
187 abort
188 end
189
190 return i
191 end
192
193 # Return the attribute value in `instance` with a sequence of perfect_hashing
194 # `instance` is the attributes array of the receiver
195 # `vtable` is the pointer to the virtual table of the class (of the receiver)
196 # `mask` is the perfect hashing mask of the class
197 # `id` is the identifier of the class
198 # `offset` is the relative offset of this attribute
199 private fun read_attribute_ph(instance: Pointer, vtable: Pointer, mask: Int, id: Int, offset: Int): Instance `{
200 // Perfect hashing position
201 int hv = mask & id;
202 long unsigned int *pointer = (long unsigned int*)(((long int *)vtable)[-hv]);
203
204 // pointer+1 is the position where the delta of the class is
205 int absolute_offset = *(pointer + 1);
206
207 Instance res = ((Instance *)instance)[absolute_offset + offset];
208
209 return res;
210 `}
211
212 # Replace in `recv` the value of the attribute `mproperty` by `value`
213 redef fun write_attribute(mproperty: MAttribute, recv: Instance, value: Instance)
214 do
215 assert recv isa MutableInstance
216
217 var id = mproperty.intro_mclassdef.mclass.vtable.id
218
219 # Replace the old value of mproperty in recv
220 write_attribute_ph(recv.internal_attributes, recv.vtable.internal_vtable,
221 recv.vtable.mask, id, mproperty.offset, value)
222 end
223
224 # Replace the value of an attribute in an instance
225 # `instance` is the attributes array of the receiver
226 # `vtable` is the pointer to the virtual table of the class (of the receiver)
227 # `mask` is the perfect hashing mask of the class
228 # `id` is the identifier of the class
229 # `offset` is the relative offset of this attribute
230 # `value` is the new value for this attribute
231 private fun write_attribute_ph(instance: Pointer, vtable: Pointer, mask: Int, id: Int, offset: Int, value: Instance) `{
232 // Perfect hashing position
233 int hv = mask & id;
234 long unsigned int *pointer = (long unsigned int*)(((long int *)vtable)[-hv]);
235
236 // pointer+1 is the position where the delta of the class is
237 int absolute_offset = *(pointer + 1);
238
239 ((Instance *)instance)[absolute_offset + offset] = value;
240 Instance_incr_ref(value);
241 `}
242 end
243
244 redef class MClass
245 # A reference to the virtual table of this class
246 var vtable: nullable VTable
247
248 # True when the class is effectively loaded by the vm, false otherwise
249 var loaded: Bool = false
250
251 # For each loaded subclass, keep the position of the group of attributes
252 # introduced by self class in the object
253 var positions_attributes: HashMap[MClass, Int] = new HashMap[MClass, Int]
254
255 # For each loaded subclass, keep the position of the group of methods
256 # introduced by self class in the vtable
257 var positions_methods: HashMap[MClass, Int] = new HashMap[MClass, Int]
258
259 # Allocates a VTable for this class and gives it an id
260 private fun make_vt(v: VirtualMachine)
261 do
262 if loaded then return
263
264 # `superclasses` contains the order of superclasses for virtual tables
265 var superclasses = superclasses_ordering(v)
266 superclasses.remove(self)
267
268 # Make_vt for super-classes
269 var ids = new Array[Int]
270 var nb_methods = new Array[Int]
271 var nb_attributes = new Array[Int]
272
273 # Absolute offset of the beginning of the attributes table
274 var offset_attributes = 0
275 # Absolute offset of the beginning of the methods table
276 var offset_methods = 0
277
278 for parent in superclasses do
279 if not parent.loaded then parent.make_vt(v)
280
281 # Get the number of introduced methods and attributes for this class
282 var methods = 0
283 var attributes = 0
284
285 for p in parent.intro_mproperties(none_visibility) do
286 if p isa MMethod then methods += 1
287 if p isa MAttribute then
288 attributes += 1
289 end
290 end
291
292 ids.push(parent.vtable.id)
293 nb_methods.push(methods)
294 nb_attributes.push(attributes)
295
296 # Update `positions_attributes` and `positions_methods` in `parent`
297 update_positions(offset_attributes, offset_methods, parent)
298
299 offset_attributes += attributes
300 offset_methods += methods
301 end
302
303 # When all super-classes have their identifiers and vtables, allocate current one
304 allocate_vtable(v, ids, nb_methods, nb_attributes, offset_attributes, offset_methods)
305 loaded = true
306 # The virtual table now needs to be filled with pointer to methods
307 end
308
309 # Allocate a single vtable
310 # `ids : Array of superclasses identifiers
311 # `nb_methods : Array which contain the number of introduced methods for each class in ids
312 # `nb_attributes : Array which contain the number of introduced attributes for each class in ids
313 # `offset_attributes : Offset from the beginning of the table of the group of attributes
314 # `offset_methods : Offset from the beginning of the table of the group of methods
315 private fun allocate_vtable(v: VirtualMachine, ids: Array[Int], nb_methods: Array[Int], nb_attributes: Array[Int],
316 offset_attributes: Int, offset_methods: Int)
317 do
318 vtable = new VTable
319 var idc = new Array[Int]
320
321 vtable.mask = v.ph.pnand(ids, 1, idc) - 1
322 vtable.id = idc[0]
323 vtable.classname = name
324
325 # Add current id to Array of super-ids
326 var ids_total = new Array[Int]
327 ids_total.add_all(ids)
328 ids_total.push(vtable.id)
329
330 var nb_methods_total = new Array[Int]
331 var nb_attributes_total = new Array[Int]
332
333 var self_methods = 0
334 var nb_introduced_attributes = 0
335
336 # For self attributes, fixing offsets
337 var relative_offset = 0
338 for p in intro_mproperties(none_visibility) do
339 if p isa MMethod then self_methods += 1
340 if p isa MAttribute then
341 nb_introduced_attributes += 1
342 p.offset = relative_offset
343 relative_offset += 1
344 end
345 end
346
347 nb_methods_total.add_all(nb_methods)
348 nb_methods_total.push(self_methods)
349
350 nb_attributes_total.add_all(nb_attributes)
351 nb_attributes_total.push(nb_introduced_attributes)
352
353 # Save the offsets of self class
354 offset_attributes += nb_introduced_attributes
355 offset_methods += self_methods
356 update_positions(offset_attributes, offset_methods, self)
357
358 # Since we have the number of attributes for each class, calculate the delta
359 var d = calculate_delta(nb_attributes_total)
360 vtable.internal_vtable = v.memory_manager.init_vtable(ids_total, nb_methods_total, d, vtable.mask)
361 end
362
363 # Computes delta for each class
364 # A delta represents the offset for this group of attributes in the object
365 # `nb_attributes` : number of attributes for each class (classes are linearized from Object to current)
366 # return deltas for each class
367 private fun calculate_delta(nb_attributes: Array[Int]): Array[Int]
368 do
369 var deltas = new Array[Int]
370
371 var total = 0
372 for nb in nb_attributes do
373 deltas.push(total)
374 total += nb
375 end
376
377 return deltas
378 end
379
380 # Order superclasses of self
381 # Return the order of superclasses in runtime structures of this class
382 private fun superclasses_ordering(v: VirtualMachine): Array[MClass]
383 do
384 var superclasses = new Array[MClass]
385 superclasses.add_all(ancestors)
386
387 var res = new Array[MClass]
388 if superclasses.length > 1 then
389 # Starting at self
390 var ordering = self.dfs(v, res)
391
392 return ordering
393 else
394 # There is no super-class, self is Object
395 return superclasses
396 end
397 end
398
399 # A kind of Depth-First-Search for superclasses ordering
400 # `v` : the current executed instance of VirtualMachine
401 # `res` : Result Array, ie current superclasses ordering
402 private fun dfs(v: VirtualMachine, res: Array[MClass]): Array[MClass]
403 do
404 # Add this class at the beginning
405 res.insert(self, 0)
406
407 var direct_parents = self.in_hierarchy(v.mainmodule).direct_greaters.to_a
408
409 if direct_parents.length > 1 then
410 # Prefix represents the class which has the most properties
411 # we try to choose it in first to reduce the number of potential recompilations
412 var prefix = null
413 var max = -1
414 for cl in direct_parents do
415 # If we never have visited this class
416 if not res.has(cl) then
417 var properties_length = cl.all_mproperties(v.mainmodule, none_visibility).length
418 if properties_length > max then
419 max = properties_length
420 prefix = cl
421 end
422 end
423 end
424
425 if prefix != null then
426 # Add the prefix class ordering at the beginning of our sequence
427 var prefix_res = new Array[MClass]
428 prefix_res = prefix.dfs(v, prefix_res)
429
430 # Then we recurse on other classes
431 for cl in direct_parents do
432 if cl != prefix then
433 res = new Array[MClass]
434 res = cl.dfs(v, res)
435
436 for cl_res in res do
437 if not prefix_res.has(cl_res) then prefix_res.push(cl_res)
438 end
439 end
440 end
441 res = prefix_res
442 end
443
444 res.push(self)
445 else
446 if direct_parents.length > 0 then
447 res = direct_parents.first.dfs(v, res)
448 end
449 end
450
451 if not res.has(self) then res.push(self)
452
453 return res
454 end
455
456 # Update positions of self class in `parent`
457 # `attributes_offset`: absolute offset of introduced attributes
458 # `methods_offset`: absolute offset of introduced methods
459 private fun update_positions(attributes_offsets: Int, methods_offset:Int, parent: MClass)
460 do
461 parent.positions_attributes[self] = attributes_offsets
462 parent.positions_methods[self] = methods_offset
463 end
464 end
465
466 redef class MAttribute
467 # Represents the relative offset of this attribute in the runtime instance
468 var offset: Int
469 end
470
471 # Redef MutableInstance to improve implementation of attributes in objects
472 redef class MutableInstance
473
474 # C-array to store pointers to attributes of this Object
475 var internal_attributes: Pointer
476 end
477
478 # Is the type of the initial value inside attributes
479 class MInitType
480 super MType
481
482 redef var model: Model
483 protected init(model: Model)
484 do
485 self.model = model
486 end
487
488 redef fun to_s do return "InitType"
489 redef fun as_nullable do return self
490 redef fun need_anchor do return false
491 redef fun resolve_for(mtype, anchor, mmodule, cleanup_virtual) do return self
492 redef fun can_resolve_for(mtype, anchor, mmodule) do return true
493
494 redef fun collect_mclassdefs(mmodule) do return new HashSet[MClassDef]
495
496 redef fun collect_mclasses(mmodule) do return new HashSet[MClass]
497
498 redef fun collect_mtypes(mmodule) do return new HashSet[MClassType]
499 end
500
501 # A VTable contains the virtual method table for the dispatch
502 # and informations to perform subtyping tests
503 class VTable
504 # The mask to perform perfect hashing
505 var mask: Int is noinit
506
507 # Unique identifier given by perfect hashing
508 var id: Int is noinit
509
510 # Pointer to the c-allocated area, represents the virtual table
511 var internal_vtable: Pointer is noinit
512
513 # The short classname of this class
514 var classname: String is noinit
515 end
516
517 redef class Instance
518 var vtable: nullable VTable
519 end
520
521 # Handle memory, used for allocate virtual table and associated structures
522 class MemoryManager
523
524 # Allocate and fill a virtual table
525 fun init_vtable(ids: Array[Int], nb_methods: Array[Int], nb_attributes: Array[Int], mask: Int): Pointer
526 do
527 # Allocate in C current virtual table
528 var res = intern_init_vtable(ids, nb_methods, nb_attributes, mask)
529
530 return res
531 end
532
533 # Construct virtual tables with a bi-dimensional layout
534 private fun intern_init_vtable(ids: Array[Int], nb_methods: Array[Int], deltas: Array[Int], mask: Int): Pointer
535 import Array[Int].length, Array[Int].[] `{
536
537 // Allocate and fill current virtual table
538 int i;
539 int total_size = 0; // total size of this virtual table
540 int nb_classes = Array_of_Int_length(nb_methods);
541 for(i = 0; i<nb_classes; i++) {
542 /* - One for each method of this class
543 * - One for the delta (offset of this group of attributes in objects)
544 * - One for the id
545 */
546 total_size += Array_of_Int__index(nb_methods, i);
547 total_size += 2;
548 }
549
550 // Add the size of the perfect hashtable (mask +1)
551 // Add one because we start to fill the vtable at position 1 (0 is the init position)
552 total_size += mask+2;
553 long unsigned int* vtable = malloc(sizeof(long unsigned int)*total_size);
554
555 // Initialisation to the first position of the virtual table (ie : Object)
556 long unsigned int *init = vtable + mask + 2;
557 for(i=0; i<total_size; i++)
558 vtable[i] = (long unsigned int)init;
559
560 // Set the virtual table to its position 0
561 // ie: after the hashtable
562 vtable = vtable + mask + 1;
563
564 int current_size = 1;
565 for(i = 0; i < nb_classes; i++) {
566 /*
567 vtable[hv] contains a pointer to the group of introduced methods
568 For each superclasse we have in virtual table :
569 (id | delta | introduced methods)
570 */
571 int hv = mask & Array_of_Int__index(ids, i);
572
573 vtable[current_size] = Array_of_Int__index(ids, i);
574 vtable[current_size + 1] = Array_of_Int__index(deltas, i);
575 vtable[-hv] = (long unsigned int)&(vtable[current_size]);
576
577 current_size += 2;
578 current_size += Array_of_Int__index(nb_methods, i);
579 }
580
581 return vtable;
582 `}
583 end