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