src: remove useless comparisons on null
[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 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 interpreter.start(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 sup isa MGenericType then
110 var sub2 = sub.supertype_to(mainmodule, anchor, sup.mclass)
111 assert sub2.mclass == sup.mclass
112
113 for i in [0..sup.mclass.arity[ do
114 var sub_arg = sub2.arguments[i]
115 var sup_arg = sup.arguments[i]
116 var res = is_subtype(sub_arg, sup_arg)
117
118 if res == false then return false
119 end
120 return true
121 end
122
123 var super_id = sup.mclass.vtable.id
124 var mask = sub.mclass.vtable.mask
125
126 return inter_is_subtype(super_id, mask, sub.mclass.vtable.internal_vtable)
127 end
128
129 # Subtyping test with perfect hashing
130 private fun inter_is_subtype(id: Int, mask:Int, vtable: Pointer): Bool `{
131 // hv is the position in hashtable
132 int hv = id & mask;
133
134 // Follow the pointer to somewhere in the vtable
135 long unsigned int *offset = (long unsigned int*)(((long int *)vtable)[-hv]);
136
137 // If the pointed value is corresponding to the identifier, the test is true, otherwise false
138 return *offset == id;
139 `}
140
141 # Redef init_instance to simulate the loading of a class
142 redef fun init_instance(recv: Instance)
143 do
144 if not recv.mtype.as(MClassType).mclass.loaded then create_class(recv.mtype.as(MClassType).mclass)
145
146 recv.vtable = recv.mtype.as(MClassType).mclass.vtable
147
148 assert(recv isa MutableInstance)
149
150 recv.internal_attributes = init_internal_attributes(initialization_value, recv.mtype.as(MClassType).mclass.all_mattributes(mainmodule, none_visibility).length)
151 super
152 end
153
154 # Initialize the internal representation of an object (its attribute values)
155 # `init_instance` is the initial value of attributes
156 private fun init_internal_attributes(init_instance: Instance, size: Int): Pointer
157 import Array[Instance].length, Array[Instance].[] `{
158
159 Instance* attributes = malloc(sizeof(Instance) * size);
160
161 int i;
162 for(i=0; i<size; i++)
163 attributes[i] = init_instance;
164
165 Instance_incr_ref(init_instance);
166 return attributes;
167 `}
168
169 # Creates the runtime structures for this class
170 fun create_class(mclass: MClass) do mclass.make_vt(self)
171
172 # Return the value of the attribute `mproperty` for the object `recv`
173 redef fun read_attribute(mproperty: MAttribute, recv: Instance): Instance
174 do
175 assert recv isa MutableInstance
176
177 # Read the attribute value with perfect hashing
178 var id = mproperty.intro_mclassdef.mclass.vtable.id
179
180 var i = read_attribute_ph(recv.internal_attributes, recv.vtable.internal_vtable,
181 recv.vtable.mask, id, mproperty.offset)
182
183 # If we get a `MInit` value, throw an error
184 if i == initialization_value then
185 fatal("Uninitialized attribute {mproperty.name}")
186 abort
187 end
188
189 return i
190 end
191
192 # Return the attribute value in `instance` with a sequence of perfect_hashing
193 # `instance` is the attributes array of the receiver
194 # `vtable` is the pointer to the virtual table of the class (of the receiver)
195 # `mask` is the perfect hashing mask of the class
196 # `id` is the identifier of the class
197 # `offset` is the relative offset of this attribute
198 private fun read_attribute_ph(instance: Pointer, vtable: Pointer, mask: Int, id: Int, offset: Int): Instance `{
199 // Perfect hashing position
200 int hv = mask & id;
201 long unsigned int *pointer = (long unsigned int*)(((long int *)vtable)[-hv]);
202
203 // pointer+1 is the position where the delta of the class is
204 int absolute_offset = *(pointer + 1);
205
206 Instance res = ((Instance *)instance)[absolute_offset + offset];
207
208 return res;
209 `}
210
211 # Replace in `recv` the value of the attribute `mproperty` by `value`
212 redef fun write_attribute(mproperty: MAttribute, recv: Instance, value: Instance)
213 do
214 assert recv isa MutableInstance
215
216 var id = mproperty.intro_mclassdef.mclass.vtable.id
217
218 # Replace the old value of mproperty in recv
219 write_attribute_ph(recv.internal_attributes, recv.vtable.internal_vtable,
220 recv.vtable.mask, id, mproperty.offset, value)
221 end
222
223 # Replace the value of an attribute in an instance
224 # `instance` is the attributes array of the receiver
225 # `vtable` is the pointer to the virtual table of the class (of the receiver)
226 # `mask` is the perfect hashing mask of the class
227 # `id` is the identifier of the class
228 # `offset` is the relative offset of this attribute
229 # `value` is the new value for this attribute
230 private fun write_attribute_ph(instance: Pointer, vtable: Pointer, mask: Int, id: Int, offset: Int, value: Instance) `{
231 // Perfect hashing position
232 int hv = mask & id;
233 long unsigned int *pointer = (long unsigned int*)(((long int *)vtable)[-hv]);
234
235 // pointer+1 is the position where the delta of the class is
236 int absolute_offset = *(pointer + 1);
237
238 ((Instance *)instance)[absolute_offset + offset] = value;
239 Instance_incr_ref(value);
240 `}
241
242 # Is the attribute `mproperty` initialized in the instance `recv`?
243 redef fun isset_attribute(mproperty: MAttribute, recv: Instance): Bool
244 do
245 assert recv isa MutableInstance
246
247 # Read the attribute value with internal perfect hashing read
248 # because we do not want to throw an error if the value is `initialization_value`
249 var id = mproperty.intro_mclassdef.mclass.vtable.id
250
251 var i = read_attribute_ph(recv.internal_attributes, recv.vtable.internal_vtable,
252 recv.vtable.mask, id, mproperty.offset)
253
254 return i != initialization_value
255 end
256 end
257
258 redef class MClass
259 # A reference to the virtual table of this class
260 var vtable: nullable VTable
261
262 # True when the class is effectively loaded by the vm, false otherwise
263 var loaded: Bool = false
264
265 # For each loaded subclass, keep the position of the group of attributes
266 # introduced by self class in the object
267 var positions_attributes: HashMap[MClass, Int] = new HashMap[MClass, Int]
268
269 # For each loaded subclass, keep the position of the group of methods
270 # introduced by self class in the vtable
271 var positions_methods: HashMap[MClass, Int] = new HashMap[MClass, Int]
272
273 # Allocates a VTable for this class and gives it an id
274 private fun make_vt(v: VirtualMachine)
275 do
276 if loaded then return
277
278 # `superclasses` contains the order of superclasses for virtual tables
279 var superclasses = superclasses_ordering(v)
280 superclasses.remove(self)
281
282 # Make_vt for super-classes
283 var ids = new Array[Int]
284 var nb_methods = new Array[Int]
285 var nb_attributes = new Array[Int]
286
287 # Absolute offset of the beginning of the attributes table
288 var offset_attributes = 0
289 # Absolute offset of the beginning of the methods table
290 var offset_methods = 0
291
292 for parent in superclasses do
293 if not parent.loaded then parent.make_vt(v)
294
295 # Get the number of introduced methods and attributes for this class
296 var methods = 0
297 var attributes = 0
298
299 for p in parent.intro_mproperties(none_visibility) do
300 if p isa MMethod then methods += 1
301 if p isa MAttribute then
302 attributes += 1
303 end
304 end
305
306 ids.push(parent.vtable.id)
307 nb_methods.push(methods)
308 nb_attributes.push(attributes)
309
310 # Update `positions_attributes` and `positions_methods` in `parent`
311 update_positions(offset_attributes, offset_methods, parent)
312
313 offset_attributes += attributes
314 offset_methods += methods
315 end
316
317 # When all super-classes have their identifiers and vtables, allocate current one
318 allocate_vtable(v, ids, nb_methods, nb_attributes, offset_attributes, offset_methods)
319 loaded = true
320 # The virtual table now needs to be filled with pointer to methods
321 end
322
323 # Allocate a single vtable
324 # `ids : Array of superclasses identifiers
325 # `nb_methods : Array which contain the number of introduced methods for each class in ids
326 # `nb_attributes : Array which contain the number of introduced attributes for each class in ids
327 # `offset_attributes : Offset from the beginning of the table of the group of attributes
328 # `offset_methods : Offset from the beginning of the table of the group of methods
329 private fun allocate_vtable(v: VirtualMachine, ids: Array[Int], nb_methods: Array[Int], nb_attributes: Array[Int],
330 offset_attributes: Int, offset_methods: Int)
331 do
332 vtable = new VTable
333 var idc = new Array[Int]
334
335 vtable.mask = v.ph.pnand(ids, 1, idc) - 1
336 vtable.id = idc[0]
337 vtable.classname = name
338
339 # Add current id to Array of super-ids
340 var ids_total = new Array[Int]
341 ids_total.add_all(ids)
342 ids_total.push(vtable.id)
343
344 var nb_methods_total = new Array[Int]
345 var nb_attributes_total = new Array[Int]
346
347 var self_methods = 0
348 var nb_introduced_attributes = 0
349
350 # For self attributes, fixing offsets
351 var relative_offset = 0
352 for p in intro_mproperties(none_visibility) do
353 if p isa MMethod then self_methods += 1
354 if p isa MAttribute then
355 nb_introduced_attributes += 1
356 p.offset = relative_offset
357 relative_offset += 1
358 end
359 end
360
361 nb_methods_total.add_all(nb_methods)
362 nb_methods_total.push(self_methods)
363
364 nb_attributes_total.add_all(nb_attributes)
365 nb_attributes_total.push(nb_introduced_attributes)
366
367 # Save the offsets of self class
368 offset_attributes += nb_introduced_attributes
369 offset_methods += self_methods
370 update_positions(offset_attributes, offset_methods, self)
371
372 # Since we have the number of attributes for each class, calculate the delta
373 var d = calculate_delta(nb_attributes_total)
374 vtable.internal_vtable = v.memory_manager.init_vtable(ids_total, nb_methods_total, d, vtable.mask)
375 end
376
377 # Computes delta for each class
378 # A delta represents the offset for this group of attributes in the object
379 # `nb_attributes` : number of attributes for each class (classes are linearized from Object to current)
380 # return deltas for each class
381 private fun calculate_delta(nb_attributes: Array[Int]): Array[Int]
382 do
383 var deltas = new Array[Int]
384
385 var total = 0
386 for nb in nb_attributes do
387 deltas.push(total)
388 total += nb
389 end
390
391 return deltas
392 end
393
394 # Order superclasses of self
395 # Return the order of superclasses in runtime structures of this class
396 private fun superclasses_ordering(v: VirtualMachine): Array[MClass]
397 do
398 var superclasses = new Array[MClass]
399 superclasses.add_all(ancestors)
400
401 var res = new Array[MClass]
402 if superclasses.length > 1 then
403 # Starting at self
404 var ordering = self.dfs(v, res)
405
406 return ordering
407 else
408 # There is no super-class, self is Object
409 return superclasses
410 end
411 end
412
413 # A kind of Depth-First-Search for superclasses ordering
414 # `v` : the current executed instance of VirtualMachine
415 # `res` : Result Array, ie current superclasses ordering
416 private fun dfs(v: VirtualMachine, res: Array[MClass]): Array[MClass]
417 do
418 # Add this class at the beginning
419 res.insert(self, 0)
420
421 var direct_parents = self.in_hierarchy(v.mainmodule).direct_greaters.to_a
422
423 if direct_parents.length > 1 then
424 # Prefix represents the class which has the most properties
425 # we try to choose it in first to reduce the number of potential recompilations
426 var prefix = null
427 var max = -1
428 for cl in direct_parents do
429 # If we never have visited this class
430 if not res.has(cl) then
431 var properties_length = cl.all_mproperties(v.mainmodule, none_visibility).length
432 if properties_length > max then
433 max = properties_length
434 prefix = cl
435 end
436 end
437 end
438
439 if prefix != null then
440 # Add the prefix class ordering at the beginning of our sequence
441 var prefix_res = new Array[MClass]
442 prefix_res = prefix.dfs(v, prefix_res)
443
444 # Then we recurse on other classes
445 for cl in direct_parents do
446 if cl != prefix then
447 res = new Array[MClass]
448 res = cl.dfs(v, res)
449
450 for cl_res in res do
451 if not prefix_res.has(cl_res) then prefix_res.push(cl_res)
452 end
453 end
454 end
455 res = prefix_res
456 end
457
458 res.push(self)
459 else
460 if direct_parents.length > 0 then
461 res = direct_parents.first.dfs(v, res)
462 end
463 end
464
465 if not res.has(self) then res.push(self)
466
467 return res
468 end
469
470 # Update positions of self class in `parent`
471 # `attributes_offset`: absolute offset of introduced attributes
472 # `methods_offset`: absolute offset of introduced methods
473 private fun update_positions(attributes_offsets: Int, methods_offset:Int, parent: MClass)
474 do
475 parent.positions_attributes[self] = attributes_offsets
476 parent.positions_methods[self] = methods_offset
477 end
478 end
479
480 redef class MAttribute
481 # Represents the relative offset of this attribute in the runtime instance
482 var offset: Int
483 end
484
485 # Redef MutableInstance to improve implementation of attributes in objects
486 redef class MutableInstance
487
488 # C-array to store pointers to attributes of this Object
489 var internal_attributes: Pointer
490 end
491
492 # Is the type of the initial value inside attributes
493 class MInitType
494 super MType
495
496 redef var model: Model
497 protected init(model: Model)
498 do
499 self.model = model
500 end
501
502 redef fun to_s do return "InitType"
503 redef fun as_nullable do return self
504 redef fun need_anchor do return false
505 redef fun resolve_for(mtype, anchor, mmodule, cleanup_virtual) do return self
506 redef fun can_resolve_for(mtype, anchor, mmodule) do return true
507
508 redef fun collect_mclassdefs(mmodule) do return new HashSet[MClassDef]
509
510 redef fun collect_mclasses(mmodule) do return new HashSet[MClass]
511
512 redef fun collect_mtypes(mmodule) do return new HashSet[MClassType]
513 end
514
515 # A VTable contains the virtual method table for the dispatch
516 # and informations to perform subtyping tests
517 class VTable
518 # The mask to perform perfect hashing
519 var mask: Int is noinit
520
521 # Unique identifier given by perfect hashing
522 var id: Int is noinit
523
524 # Pointer to the c-allocated area, represents the virtual table
525 var internal_vtable: Pointer is noinit
526
527 # The short classname of this class
528 var classname: String is noinit
529 end
530
531 redef class Instance
532 var vtable: nullable VTable
533 end
534
535 # Handle memory, used for allocate virtual table and associated structures
536 class MemoryManager
537
538 # Allocate and fill a virtual table
539 fun init_vtable(ids: Array[Int], nb_methods: Array[Int], nb_attributes: Array[Int], mask: Int): Pointer
540 do
541 # Allocate in C current virtual table
542 var res = intern_init_vtable(ids, nb_methods, nb_attributes, mask)
543
544 return res
545 end
546
547 # Construct virtual tables with a bi-dimensional layout
548 private fun intern_init_vtable(ids: Array[Int], nb_methods: Array[Int], deltas: Array[Int], mask: Int): Pointer
549 import Array[Int].length, Array[Int].[] `{
550
551 // Allocate and fill current virtual table
552 int i;
553 int total_size = 0; // total size of this virtual table
554 int nb_classes = Array_of_Int_length(nb_methods);
555 for(i = 0; i<nb_classes; i++) {
556 /* - One for each method of this class
557 * - One for the delta (offset of this group of attributes in objects)
558 * - One for the id
559 */
560 total_size += Array_of_Int__index(nb_methods, i);
561 total_size += 2;
562 }
563
564 // Add the size of the perfect hashtable (mask +1)
565 // Add one because we start to fill the vtable at position 1 (0 is the init position)
566 total_size += mask+2;
567 long unsigned int* vtable = malloc(sizeof(long unsigned int)*total_size);
568
569 // Initialisation to the first position of the virtual table (ie : Object)
570 long unsigned int *init = vtable + mask + 2;
571 for(i=0; i<total_size; i++)
572 vtable[i] = (long unsigned int)init;
573
574 // Set the virtual table to its position 0
575 // ie: after the hashtable
576 vtable = vtable + mask + 1;
577
578 int current_size = 1;
579 for(i = 0; i < nb_classes; i++) {
580 /*
581 vtable[hv] contains a pointer to the group of introduced methods
582 For each superclasse we have in virtual table :
583 (id | delta | introduced methods)
584 */
585 int hv = mask & Array_of_Int__index(ids, i);
586
587 vtable[current_size] = Array_of_Int__index(ids, i);
588 vtable[current_size + 1] = Array_of_Int__index(deltas, i);
589 vtable[-hv] = (long unsigned int)&(vtable[current_size]);
590
591 current_size += 2;
592 current_size += Array_of_Int__index(nb_methods, i);
593 }
594
595 return vtable;
596 `}
597 end