1 | #include <math.h> |
2 | #include <stdarg.h> |
3 | #include <stdio.h> |
4 | #include <string.h> |
5 | |
6 | #include "wren.h" |
7 | #include "wren_value.h" |
8 | #include "wren_vm.h" |
9 | |
10 | #if WREN_DEBUG_TRACE_MEMORY |
11 | #include "wren_debug.h" |
12 | #endif |
13 | |
14 | // TODO: Tune these. |
15 | // The initial (and minimum) capacity of a non-empty list or map object. |
16 | #define MIN_CAPACITY 16 |
17 | |
18 | // The rate at which a collection's capacity grows when the size exceeds the |
19 | // current capacity. The new capacity will be determined by *multiplying* the |
20 | // old capacity by this. Growing geometrically is necessary to ensure that |
21 | // adding to a collection has O(1) amortized complexity. |
22 | #define GROW_FACTOR 2 |
23 | |
24 | // The maximum percentage of map entries that can be filled before the map is |
25 | // grown. A lower load takes more memory but reduces collisions which makes |
26 | // lookup faster. |
27 | #define MAP_LOAD_PERCENT 75 |
28 | |
29 | // The number of call frames initially allocated when a fiber is created. Making |
30 | // this smaller makes fibers use less memory (at first) but spends more time |
31 | // reallocating when the call stack grows. |
32 | #define INITIAL_CALL_FRAMES 4 |
33 | |
34 | DEFINE_BUFFER(Value, Value); |
35 | DEFINE_BUFFER(Method, Method); |
36 | |
37 | static void initObj(WrenVM* vm, Obj* obj, ObjType type, ObjClass* classObj) |
38 | { |
39 | obj->type = type; |
40 | obj->isDark = false; |
41 | obj->classObj = classObj; |
42 | obj->next = vm->first; |
43 | vm->first = obj; |
44 | } |
45 | |
46 | ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name) |
47 | { |
48 | ObjClass* classObj = ALLOCATE(vm, ObjClass); |
49 | initObj(vm, &classObj->obj, OBJ_CLASS, NULL); |
50 | classObj->superclass = NULL; |
51 | classObj->numFields = numFields; |
52 | classObj->name = name; |
53 | classObj->attributes = NULL_VAL; |
54 | |
55 | wrenPushRoot(vm, (Obj*)classObj); |
56 | wrenMethodBufferInit(&classObj->methods); |
57 | wrenPopRoot(vm); |
58 | |
59 | return classObj; |
60 | } |
61 | |
62 | void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass) |
63 | { |
64 | ASSERT(superclass != NULL, "Must have superclass." ); |
65 | |
66 | subclass->superclass = superclass; |
67 | |
68 | // Include the superclass in the total number of fields. |
69 | if (subclass->numFields != -1) |
70 | { |
71 | subclass->numFields += superclass->numFields; |
72 | } |
73 | else |
74 | { |
75 | ASSERT(superclass->numFields == 0, |
76 | "A foreign class cannot inherit from a class with fields." ); |
77 | } |
78 | |
79 | // Inherit methods from its superclass. |
80 | for (int i = 0; i < superclass->methods.count; i++) |
81 | { |
82 | wrenBindMethod(vm, subclass, i, superclass->methods.data[i]); |
83 | } |
84 | } |
85 | |
86 | ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, |
87 | ObjString* name) |
88 | { |
89 | // Create the metaclass. |
90 | Value metaclassName = wrenStringFormat(vm, "@ metaclass" , OBJ_VAL(name)); |
91 | wrenPushRoot(vm, AS_OBJ(metaclassName)); |
92 | |
93 | ObjClass* metaclass = wrenNewSingleClass(vm, 0, AS_STRING(metaclassName)); |
94 | metaclass->obj.classObj = vm->classClass; |
95 | |
96 | wrenPopRoot(vm); |
97 | |
98 | // Make sure the metaclass isn't collected when we allocate the class. |
99 | wrenPushRoot(vm, (Obj*)metaclass); |
100 | |
101 | // Metaclasses always inherit Class and do not parallel the non-metaclass |
102 | // hierarchy. |
103 | wrenBindSuperclass(vm, metaclass, vm->classClass); |
104 | |
105 | ObjClass* classObj = wrenNewSingleClass(vm, numFields, name); |
106 | |
107 | // Make sure the class isn't collected while the inherited methods are being |
108 | // bound. |
109 | wrenPushRoot(vm, (Obj*)classObj); |
110 | |
111 | classObj->obj.classObj = metaclass; |
112 | wrenBindSuperclass(vm, classObj, superclass); |
113 | |
114 | wrenPopRoot(vm); |
115 | wrenPopRoot(vm); |
116 | |
117 | return classObj; |
118 | } |
119 | |
120 | void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method) |
121 | { |
122 | // Make sure the buffer is big enough to contain the symbol's index. |
123 | if (symbol >= classObj->methods.count) |
124 | { |
125 | Method noMethod; |
126 | noMethod.type = METHOD_NONE; |
127 | wrenMethodBufferFill(vm, &classObj->methods, noMethod, |
128 | symbol - classObj->methods.count + 1); |
129 | } |
130 | |
131 | classObj->methods.data[symbol] = method; |
132 | } |
133 | |
134 | ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn) |
135 | { |
136 | ObjClosure* closure = ALLOCATE_FLEX(vm, ObjClosure, |
137 | ObjUpvalue*, fn->numUpvalues); |
138 | initObj(vm, &closure->obj, OBJ_CLOSURE, vm->fnClass); |
139 | |
140 | closure->fn = fn; |
141 | |
142 | // Clear the upvalue array. We need to do this in case a GC is triggered |
143 | // after the closure is created but before the upvalue array is populated. |
144 | for (int i = 0; i < fn->numUpvalues; i++) closure->upvalues[i] = NULL; |
145 | |
146 | return closure; |
147 | } |
148 | |
149 | ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure) |
150 | { |
151 | // Allocate the arrays before the fiber in case it triggers a GC. |
152 | CallFrame* frames = ALLOCATE_ARRAY(vm, CallFrame, INITIAL_CALL_FRAMES); |
153 | |
154 | // Add one slot for the unused implicit receiver slot that the compiler |
155 | // assumes all functions have. |
156 | int stackCapacity = closure == NULL |
157 | ? 1 |
158 | : wrenPowerOf2Ceil(closure->fn->maxSlots + 1); |
159 | Value* stack = ALLOCATE_ARRAY(vm, Value, stackCapacity); |
160 | |
161 | ObjFiber* fiber = ALLOCATE(vm, ObjFiber); |
162 | initObj(vm, &fiber->obj, OBJ_FIBER, vm->fiberClass); |
163 | |
164 | fiber->stack = stack; |
165 | fiber->stackTop = fiber->stack; |
166 | fiber->stackCapacity = stackCapacity; |
167 | |
168 | fiber->frames = frames; |
169 | fiber->frameCapacity = INITIAL_CALL_FRAMES; |
170 | fiber->numFrames = 0; |
171 | |
172 | fiber->openUpvalues = NULL; |
173 | fiber->caller = NULL; |
174 | fiber->error = NULL_VAL; |
175 | fiber->state = FIBER_OTHER; |
176 | |
177 | if (closure != NULL) |
178 | { |
179 | // Initialize the first call frame. |
180 | wrenAppendCallFrame(vm, fiber, closure, fiber->stack); |
181 | |
182 | // The first slot always holds the closure. |
183 | fiber->stackTop[0] = OBJ_VAL(closure); |
184 | fiber->stackTop++; |
185 | } |
186 | |
187 | return fiber; |
188 | } |
189 | |
190 | void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed) |
191 | { |
192 | if (fiber->stackCapacity >= needed) return; |
193 | |
194 | int capacity = wrenPowerOf2Ceil(needed); |
195 | |
196 | Value* oldStack = fiber->stack; |
197 | fiber->stack = (Value*)wrenReallocate(vm, fiber->stack, |
198 | sizeof(Value) * fiber->stackCapacity, |
199 | sizeof(Value) * capacity); |
200 | fiber->stackCapacity = capacity; |
201 | |
202 | // If the reallocation moves the stack, then we need to recalculate every |
203 | // pointer that points into the old stack to into the same relative distance |
204 | // in the new stack. We have to be a little careful about how these are |
205 | // calculated because pointer subtraction is only well-defined within a |
206 | // single array, hence the slightly redundant-looking arithmetic below. |
207 | if (fiber->stack != oldStack) |
208 | { |
209 | // Top of the stack. |
210 | if (vm->apiStack >= oldStack && vm->apiStack <= fiber->stackTop) |
211 | { |
212 | vm->apiStack = fiber->stack + (vm->apiStack - oldStack); |
213 | } |
214 | |
215 | // Stack pointer for each call frame. |
216 | for (int i = 0; i < fiber->numFrames; i++) |
217 | { |
218 | CallFrame* frame = &fiber->frames[i]; |
219 | frame->stackStart = fiber->stack + (frame->stackStart - oldStack); |
220 | } |
221 | |
222 | // Open upvalues. |
223 | for (ObjUpvalue* upvalue = fiber->openUpvalues; |
224 | upvalue != NULL; |
225 | upvalue = upvalue->next) |
226 | { |
227 | upvalue->value = fiber->stack + (upvalue->value - oldStack); |
228 | } |
229 | |
230 | fiber->stackTop = fiber->stack + (fiber->stackTop - oldStack); |
231 | } |
232 | } |
233 | |
234 | ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size) |
235 | { |
236 | ObjForeign* object = ALLOCATE_FLEX(vm, ObjForeign, uint8_t, size); |
237 | initObj(vm, &object->obj, OBJ_FOREIGN, classObj); |
238 | |
239 | // Zero out the bytes. |
240 | memset(object->data, 0, size); |
241 | return object; |
242 | } |
243 | |
244 | ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots) |
245 | { |
246 | FnDebug* debug = ALLOCATE(vm, FnDebug); |
247 | debug->name = NULL; |
248 | wrenIntBufferInit(&debug->sourceLines); |
249 | |
250 | ObjFn* fn = ALLOCATE(vm, ObjFn); |
251 | initObj(vm, &fn->obj, OBJ_FN, vm->fnClass); |
252 | |
253 | wrenValueBufferInit(&fn->constants); |
254 | wrenByteBufferInit(&fn->code); |
255 | fn->module = module; |
256 | fn->maxSlots = maxSlots; |
257 | fn->numUpvalues = 0; |
258 | fn->arity = 0; |
259 | fn->debug = debug; |
260 | |
261 | return fn; |
262 | } |
263 | |
264 | void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length) |
265 | { |
266 | fn->debug->name = ALLOCATE_ARRAY(vm, char, length + 1); |
267 | memcpy(fn->debug->name, name, length); |
268 | fn->debug->name[length] = '\0'; |
269 | } |
270 | |
271 | Value wrenNewInstance(WrenVM* vm, ObjClass* classObj) |
272 | { |
273 | ObjInstance* instance = ALLOCATE_FLEX(vm, ObjInstance, |
274 | Value, classObj->numFields); |
275 | initObj(vm, &instance->obj, OBJ_INSTANCE, classObj); |
276 | |
277 | // Initialize fields to null. |
278 | for (int i = 0; i < classObj->numFields; i++) |
279 | { |
280 | instance->fields[i] = NULL_VAL; |
281 | } |
282 | |
283 | return OBJ_VAL(instance); |
284 | } |
285 | |
286 | ObjList* wrenNewList(WrenVM* vm, uint32_t numElements) |
287 | { |
288 | // Allocate this before the list object in case it triggers a GC which would |
289 | // free the list. |
290 | Value* elements = NULL; |
291 | if (numElements > 0) |
292 | { |
293 | elements = ALLOCATE_ARRAY(vm, Value, numElements); |
294 | } |
295 | |
296 | ObjList* list = ALLOCATE(vm, ObjList); |
297 | initObj(vm, &list->obj, OBJ_LIST, vm->listClass); |
298 | list->elements.capacity = numElements; |
299 | list->elements.count = numElements; |
300 | list->elements.data = elements; |
301 | return list; |
302 | } |
303 | |
304 | void wrenListInsert(WrenVM* vm, ObjList* list, Value value, uint32_t index) |
305 | { |
306 | if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); |
307 | |
308 | // Add a slot at the end of the list. |
309 | wrenValueBufferWrite(vm, &list->elements, NULL_VAL); |
310 | |
311 | if (IS_OBJ(value)) wrenPopRoot(vm); |
312 | |
313 | // Shift the existing elements down. |
314 | for (uint32_t i = list->elements.count - 1; i > index; i--) |
315 | { |
316 | list->elements.data[i] = list->elements.data[i - 1]; |
317 | } |
318 | |
319 | // Store the new element. |
320 | list->elements.data[index] = value; |
321 | } |
322 | |
323 | int wrenListIndexOf(WrenVM* vm, ObjList* list, Value value) |
324 | { |
325 | int count = list->elements.count; |
326 | for (int i = 0; i < count; i++) |
327 | { |
328 | Value item = list->elements.data[i]; |
329 | if(wrenValuesEqual(item, value)) { |
330 | return i; |
331 | } |
332 | } |
333 | return -1; |
334 | } |
335 | |
336 | Value wrenListRemoveAt(WrenVM* vm, ObjList* list, uint32_t index) |
337 | { |
338 | Value removed = list->elements.data[index]; |
339 | |
340 | if (IS_OBJ(removed)) wrenPushRoot(vm, AS_OBJ(removed)); |
341 | |
342 | // Shift items up. |
343 | for (int i = index; i < list->elements.count - 1; i++) |
344 | { |
345 | list->elements.data[i] = list->elements.data[i + 1]; |
346 | } |
347 | |
348 | // If we have too much excess capacity, shrink it. |
349 | if (list->elements.capacity / GROW_FACTOR >= list->elements.count) |
350 | { |
351 | list->elements.data = (Value*)wrenReallocate(vm, list->elements.data, |
352 | sizeof(Value) * list->elements.capacity, |
353 | sizeof(Value) * (list->elements.capacity / GROW_FACTOR)); |
354 | list->elements.capacity /= GROW_FACTOR; |
355 | } |
356 | |
357 | if (IS_OBJ(removed)) wrenPopRoot(vm); |
358 | |
359 | list->elements.count--; |
360 | return removed; |
361 | } |
362 | |
363 | ObjMap* wrenNewMap(WrenVM* vm) |
364 | { |
365 | ObjMap* map = ALLOCATE(vm, ObjMap); |
366 | initObj(vm, &map->obj, OBJ_MAP, vm->mapClass); |
367 | map->capacity = 0; |
368 | map->count = 0; |
369 | map->entries = NULL; |
370 | return map; |
371 | } |
372 | |
373 | static inline uint32_t hashBits(uint64_t hash) |
374 | { |
375 | // From v8's ComputeLongHash() which in turn cites: |
376 | // Thomas Wang, Integer Hash Functions. |
377 | // http://www.concentric.net/~Ttwang/tech/inthash.htm |
378 | hash = ~hash + (hash << 18); // hash = (hash << 18) - hash - 1; |
379 | hash = hash ^ (hash >> 31); |
380 | hash = hash * 21; // hash = (hash + (hash << 2)) + (hash << 4); |
381 | hash = hash ^ (hash >> 11); |
382 | hash = hash + (hash << 6); |
383 | hash = hash ^ (hash >> 22); |
384 | return (uint32_t)(hash & 0x3fffffff); |
385 | } |
386 | |
387 | // Generates a hash code for [num]. |
388 | static inline uint32_t hashNumber(double num) |
389 | { |
390 | // Hash the raw bits of the value. |
391 | return hashBits(wrenDoubleToBits(num)); |
392 | } |
393 | |
394 | // Generates a hash code for [object]. |
395 | static uint32_t hashObject(Obj* object) |
396 | { |
397 | switch (object->type) |
398 | { |
399 | case OBJ_CLASS: |
400 | // Classes just use their name. |
401 | return hashObject((Obj*)((ObjClass*)object)->name); |
402 | |
403 | // Allow bare (non-closure) functions so that we can use a map to find |
404 | // existing constants in a function's constant table. This is only used |
405 | // internally. Since user code never sees a non-closure function, they |
406 | // cannot use them as map keys. |
407 | case OBJ_FN: |
408 | { |
409 | ObjFn* fn = (ObjFn*)object; |
410 | return hashNumber(fn->arity) ^ hashNumber(fn->code.count); |
411 | } |
412 | |
413 | case OBJ_RANGE: |
414 | { |
415 | ObjRange* range = (ObjRange*)object; |
416 | return hashNumber(range->from) ^ hashNumber(range->to); |
417 | } |
418 | |
419 | case OBJ_STRING: |
420 | return ((ObjString*)object)->hash; |
421 | |
422 | default: |
423 | ASSERT(false, "Only immutable objects can be hashed." ); |
424 | return 0; |
425 | } |
426 | } |
427 | |
428 | // Generates a hash code for [value], which must be one of the built-in |
429 | // immutable types: null, bool, class, num, range, or string. |
430 | static uint32_t hashValue(Value value) |
431 | { |
432 | // TODO: We'll probably want to randomize this at some point. |
433 | |
434 | #if WREN_NAN_TAGGING |
435 | if (IS_OBJ(value)) return hashObject(AS_OBJ(value)); |
436 | |
437 | // Hash the raw bits of the unboxed value. |
438 | return hashBits(value); |
439 | #else |
440 | switch (value.type) |
441 | { |
442 | case VAL_FALSE: return 0; |
443 | case VAL_NULL: return 1; |
444 | case VAL_NUM: return hashNumber(AS_NUM(value)); |
445 | case VAL_TRUE: return 2; |
446 | case VAL_OBJ: return hashObject(AS_OBJ(value)); |
447 | default: UNREACHABLE(); |
448 | } |
449 | |
450 | return 0; |
451 | #endif |
452 | } |
453 | |
454 | // Looks for an entry with [key] in an array of [capacity] [entries]. |
455 | // |
456 | // If found, sets [result] to point to it and returns `true`. Otherwise, |
457 | // returns `false` and points [result] to the entry where the key/value pair |
458 | // should be inserted. |
459 | static bool findEntry(MapEntry* entries, uint32_t capacity, Value key, |
460 | MapEntry** result) |
461 | { |
462 | // If there is no entry array (an empty map), we definitely won't find it. |
463 | if (capacity == 0) return false; |
464 | |
465 | // Figure out where to insert it in the table. Use open addressing and |
466 | // basic linear probing. |
467 | uint32_t startIndex = hashValue(key) % capacity; |
468 | uint32_t index = startIndex; |
469 | |
470 | // If we pass a tombstone and don't end up finding the key, its entry will |
471 | // be re-used for the insert. |
472 | MapEntry* tombstone = NULL; |
473 | |
474 | // Walk the probe sequence until we've tried every slot. |
475 | do |
476 | { |
477 | MapEntry* entry = &entries[index]; |
478 | |
479 | if (IS_UNDEFINED(entry->key)) |
480 | { |
481 | // If we found an empty slot, the key is not in the table. If we found a |
482 | // slot that contains a deleted key, we have to keep looking. |
483 | if (IS_FALSE(entry->value)) |
484 | { |
485 | // We found an empty slot, so we've reached the end of the probe |
486 | // sequence without finding the key. If we passed a tombstone, then |
487 | // that's where we should insert the item, otherwise, put it here at |
488 | // the end of the sequence. |
489 | *result = tombstone != NULL ? tombstone : entry; |
490 | return false; |
491 | } |
492 | else |
493 | { |
494 | // We found a tombstone. We need to keep looking in case the key is |
495 | // after it, but we'll use this entry as the insertion point if the |
496 | // key ends up not being found. |
497 | if (tombstone == NULL) tombstone = entry; |
498 | } |
499 | } |
500 | else if (wrenValuesEqual(entry->key, key)) |
501 | { |
502 | // We found the key. |
503 | *result = entry; |
504 | return true; |
505 | } |
506 | |
507 | // Try the next slot. |
508 | index = (index + 1) % capacity; |
509 | } |
510 | while (index != startIndex); |
511 | |
512 | // If we get here, the table is full of tombstones. Return the first one we |
513 | // found. |
514 | ASSERT(tombstone != NULL, "Map should have tombstones or empty entries." ); |
515 | *result = tombstone; |
516 | return false; |
517 | } |
518 | |
519 | // Inserts [key] and [value] in the array of [entries] with the given |
520 | // [capacity]. |
521 | // |
522 | // Returns `true` if this is the first time [key] was added to the map. |
523 | static bool insertEntry(MapEntry* entries, uint32_t capacity, |
524 | Value key, Value value) |
525 | { |
526 | ASSERT(entries != NULL, "Should ensure capacity before inserting." ); |
527 | |
528 | MapEntry* entry; |
529 | if (findEntry(entries, capacity, key, &entry)) |
530 | { |
531 | // Already present, so just replace the value. |
532 | entry->value = value; |
533 | return false; |
534 | } |
535 | else |
536 | { |
537 | entry->key = key; |
538 | entry->value = value; |
539 | return true; |
540 | } |
541 | } |
542 | |
543 | // Updates [map]'s entry array to [capacity]. |
544 | static void resizeMap(WrenVM* vm, ObjMap* map, uint32_t capacity) |
545 | { |
546 | // Create the new empty hash table. |
547 | MapEntry* entries = ALLOCATE_ARRAY(vm, MapEntry, capacity); |
548 | for (uint32_t i = 0; i < capacity; i++) |
549 | { |
550 | entries[i].key = UNDEFINED_VAL; |
551 | entries[i].value = FALSE_VAL; |
552 | } |
553 | |
554 | // Re-add the existing entries. |
555 | if (map->capacity > 0) |
556 | { |
557 | for (uint32_t i = 0; i < map->capacity; i++) |
558 | { |
559 | MapEntry* entry = &map->entries[i]; |
560 | |
561 | // Don't copy empty entries or tombstones. |
562 | if (IS_UNDEFINED(entry->key)) continue; |
563 | |
564 | insertEntry(entries, capacity, entry->key, entry->value); |
565 | } |
566 | } |
567 | |
568 | // Replace the array. |
569 | DEALLOCATE(vm, map->entries); |
570 | map->entries = entries; |
571 | map->capacity = capacity; |
572 | } |
573 | |
574 | Value wrenMapGet(ObjMap* map, Value key) |
575 | { |
576 | MapEntry* entry; |
577 | if (findEntry(map->entries, map->capacity, key, &entry)) return entry->value; |
578 | |
579 | return UNDEFINED_VAL; |
580 | } |
581 | |
582 | void wrenMapSet(WrenVM* vm, ObjMap* map, Value key, Value value) |
583 | { |
584 | // If the map is getting too full, make room first. |
585 | if (map->count + 1 > map->capacity * MAP_LOAD_PERCENT / 100) |
586 | { |
587 | // Figure out the new hash table size. |
588 | uint32_t capacity = map->capacity * GROW_FACTOR; |
589 | if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; |
590 | |
591 | resizeMap(vm, map, capacity); |
592 | } |
593 | |
594 | if (insertEntry(map->entries, map->capacity, key, value)) |
595 | { |
596 | // A new key was added. |
597 | map->count++; |
598 | } |
599 | } |
600 | |
601 | void wrenMapClear(WrenVM* vm, ObjMap* map) |
602 | { |
603 | DEALLOCATE(vm, map->entries); |
604 | map->entries = NULL; |
605 | map->capacity = 0; |
606 | map->count = 0; |
607 | } |
608 | |
609 | Value wrenMapRemoveKey(WrenVM* vm, ObjMap* map, Value key) |
610 | { |
611 | MapEntry* entry; |
612 | if (!findEntry(map->entries, map->capacity, key, &entry)) return NULL_VAL; |
613 | |
614 | // Remove the entry from the map. Set this value to true, which marks it as a |
615 | // deleted slot. When searching for a key, we will stop on empty slots, but |
616 | // continue past deleted slots. |
617 | Value value = entry->value; |
618 | entry->key = UNDEFINED_VAL; |
619 | entry->value = TRUE_VAL; |
620 | |
621 | if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); |
622 | |
623 | map->count--; |
624 | |
625 | if (map->count == 0) |
626 | { |
627 | // Removed the last item, so free the array. |
628 | wrenMapClear(vm, map); |
629 | } |
630 | else if (map->capacity > MIN_CAPACITY && |
631 | map->count < map->capacity / GROW_FACTOR * MAP_LOAD_PERCENT / 100) |
632 | { |
633 | uint32_t capacity = map->capacity / GROW_FACTOR; |
634 | if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; |
635 | |
636 | // The map is getting empty, so shrink the entry array back down. |
637 | // TODO: Should we do this less aggressively than we grow? |
638 | resizeMap(vm, map, capacity); |
639 | } |
640 | |
641 | if (IS_OBJ(value)) wrenPopRoot(vm); |
642 | return value; |
643 | } |
644 | |
645 | ObjModule* wrenNewModule(WrenVM* vm, ObjString* name) |
646 | { |
647 | ObjModule* module = ALLOCATE(vm, ObjModule); |
648 | |
649 | // Modules are never used as first-class objects, so don't need a class. |
650 | initObj(vm, (Obj*)module, OBJ_MODULE, NULL); |
651 | |
652 | wrenPushRoot(vm, (Obj*)module); |
653 | |
654 | wrenSymbolTableInit(&module->variableNames); |
655 | wrenValueBufferInit(&module->variables); |
656 | |
657 | module->name = name; |
658 | |
659 | wrenPopRoot(vm); |
660 | return module; |
661 | } |
662 | |
663 | Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive) |
664 | { |
665 | ObjRange* range = ALLOCATE(vm, ObjRange); |
666 | initObj(vm, &range->obj, OBJ_RANGE, vm->rangeClass); |
667 | range->from = from; |
668 | range->to = to; |
669 | range->isInclusive = isInclusive; |
670 | |
671 | return OBJ_VAL(range); |
672 | } |
673 | |
674 | // Creates a new string object with a null-terminated buffer large enough to |
675 | // hold a string of [length] but does not fill in the bytes. |
676 | // |
677 | // The caller is expected to fill in the buffer and then calculate the string's |
678 | // hash. |
679 | static ObjString* allocateString(WrenVM* vm, size_t length) |
680 | { |
681 | ObjString* string = ALLOCATE_FLEX(vm, ObjString, char, length + 1); |
682 | initObj(vm, &string->obj, OBJ_STRING, vm->stringClass); |
683 | string->length = (int)length; |
684 | string->value[length] = '\0'; |
685 | |
686 | return string; |
687 | } |
688 | |
689 | // Calculates and stores the hash code for [string]. |
690 | static void hashString(ObjString* string) |
691 | { |
692 | // FNV-1a hash. See: http://www.isthe.com/chongo/tech/comp/fnv/ |
693 | uint32_t hash = 2166136261u; |
694 | |
695 | // This is O(n) on the length of the string, but we only call this when a new |
696 | // string is created. Since the creation is also O(n) (to copy/initialize all |
697 | // the bytes), we allow this here. |
698 | for (uint32_t i = 0; i < string->length; i++) |
699 | { |
700 | hash ^= string->value[i]; |
701 | hash *= 16777619; |
702 | } |
703 | |
704 | string->hash = hash; |
705 | } |
706 | |
707 | Value wrenNewString(WrenVM* vm, const char* text) |
708 | { |
709 | return wrenNewStringLength(vm, text, strlen(text)); |
710 | } |
711 | |
712 | Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length) |
713 | { |
714 | // Allow NULL if the string is empty since byte buffers don't allocate any |
715 | // characters for a zero-length string. |
716 | ASSERT(length == 0 || text != NULL, "Unexpected NULL string." ); |
717 | |
718 | ObjString* string = allocateString(vm, length); |
719 | |
720 | // Copy the string (if given one). |
721 | if (length > 0 && text != NULL) memcpy(string->value, text, length); |
722 | |
723 | hashString(string); |
724 | return OBJ_VAL(string); |
725 | } |
726 | |
727 | |
728 | Value wrenNewStringFromRange(WrenVM* vm, ObjString* source, int start, |
729 | uint32_t count, int step) |
730 | { |
731 | uint8_t* from = (uint8_t*)source->value; |
732 | int length = 0; |
733 | for (uint32_t i = 0; i < count; i++) |
734 | { |
735 | length += wrenUtf8DecodeNumBytes(from[start + i * step]); |
736 | } |
737 | |
738 | ObjString* result = allocateString(vm, length); |
739 | result->value[length] = '\0'; |
740 | |
741 | uint8_t* to = (uint8_t*)result->value; |
742 | for (uint32_t i = 0; i < count; i++) |
743 | { |
744 | int index = start + i * step; |
745 | int codePoint = wrenUtf8Decode(from + index, source->length - index); |
746 | |
747 | if (codePoint != -1) |
748 | { |
749 | to += wrenUtf8Encode(codePoint, to); |
750 | } |
751 | } |
752 | |
753 | hashString(result); |
754 | return OBJ_VAL(result); |
755 | } |
756 | |
757 | Value wrenNumToString(WrenVM* vm, double value) |
758 | { |
759 | // Edge case: If the value is NaN or infinity, different versions of libc |
760 | // produce different outputs (some will format it signed and some won't). To |
761 | // get reliable output, handle it ourselves. |
762 | if (isnan(value)) return CONST_STRING(vm, "nan" ); |
763 | if (isinf(value)) |
764 | { |
765 | if (value > 0.0) |
766 | { |
767 | return CONST_STRING(vm, "infinity" ); |
768 | } |
769 | else |
770 | { |
771 | return CONST_STRING(vm, "-infinity" ); |
772 | } |
773 | } |
774 | |
775 | // This is large enough to hold any double converted to a string using |
776 | // "%.14g". Example: |
777 | // |
778 | // -1.12345678901234e-1022 |
779 | // |
780 | // So we have: |
781 | // |
782 | // + 1 char for sign |
783 | // + 1 char for digit |
784 | // + 1 char for "." |
785 | // + 14 chars for decimal digits |
786 | // + 1 char for "e" |
787 | // + 1 char for "-" or "+" |
788 | // + 4 chars for exponent |
789 | // + 1 char for "\0" |
790 | // = 24 |
791 | char buffer[24]; |
792 | int length = sprintf(buffer, "%.14g" , value); |
793 | return wrenNewStringLength(vm, buffer, length); |
794 | } |
795 | |
796 | Value wrenStringFromCodePoint(WrenVM* vm, int value) |
797 | { |
798 | int length = wrenUtf8EncodeNumBytes(value); |
799 | ASSERT(length != 0, "Value out of range." ); |
800 | |
801 | ObjString* string = allocateString(vm, length); |
802 | |
803 | wrenUtf8Encode(value, (uint8_t*)string->value); |
804 | hashString(string); |
805 | |
806 | return OBJ_VAL(string); |
807 | } |
808 | |
809 | Value wrenStringFromByte(WrenVM *vm, uint8_t value) |
810 | { |
811 | int length = 1; |
812 | ObjString* string = allocateString(vm, length); |
813 | string->value[0] = value; |
814 | hashString(string); |
815 | return OBJ_VAL(string); |
816 | } |
817 | |
818 | Value wrenStringFormat(WrenVM* vm, const char* format, ...) |
819 | { |
820 | va_list argList; |
821 | |
822 | // Calculate the length of the result string. Do this up front so we can |
823 | // create the final string with a single allocation. |
824 | va_start(argList, format); |
825 | size_t totalLength = 0; |
826 | for (const char* c = format; *c != '\0'; c++) |
827 | { |
828 | switch (*c) |
829 | { |
830 | case '$': |
831 | totalLength += strlen(va_arg(argList, const char*)); |
832 | break; |
833 | |
834 | case '@': |
835 | totalLength += AS_STRING(va_arg(argList, Value))->length; |
836 | break; |
837 | |
838 | default: |
839 | // Any other character is interpreted literally. |
840 | totalLength++; |
841 | } |
842 | } |
843 | va_end(argList); |
844 | |
845 | // Concatenate the string. |
846 | ObjString* result = allocateString(vm, totalLength); |
847 | |
848 | va_start(argList, format); |
849 | char* start = result->value; |
850 | for (const char* c = format; *c != '\0'; c++) |
851 | { |
852 | switch (*c) |
853 | { |
854 | case '$': |
855 | { |
856 | const char* string = va_arg(argList, const char*); |
857 | size_t length = strlen(string); |
858 | memcpy(start, string, length); |
859 | start += length; |
860 | break; |
861 | } |
862 | |
863 | case '@': |
864 | { |
865 | ObjString* string = AS_STRING(va_arg(argList, Value)); |
866 | memcpy(start, string->value, string->length); |
867 | start += string->length; |
868 | break; |
869 | } |
870 | |
871 | default: |
872 | // Any other character is interpreted literally. |
873 | *start++ = *c; |
874 | } |
875 | } |
876 | va_end(argList); |
877 | |
878 | hashString(result); |
879 | |
880 | return OBJ_VAL(result); |
881 | } |
882 | |
883 | Value wrenStringCodePointAt(WrenVM* vm, ObjString* string, uint32_t index) |
884 | { |
885 | ASSERT(index < string->length, "Index out of bounds." ); |
886 | |
887 | int codePoint = wrenUtf8Decode((uint8_t*)string->value + index, |
888 | string->length - index); |
889 | if (codePoint == -1) |
890 | { |
891 | // If it isn't a valid UTF-8 sequence, treat it as a single raw byte. |
892 | char bytes[2]; |
893 | bytes[0] = string->value[index]; |
894 | bytes[1] = '\0'; |
895 | return wrenNewStringLength(vm, bytes, 1); |
896 | } |
897 | |
898 | return wrenStringFromCodePoint(vm, codePoint); |
899 | } |
900 | |
901 | // Uses the Boyer-Moore-Horspool string matching algorithm. |
902 | uint32_t wrenStringFind(ObjString* haystack, ObjString* needle, uint32_t start) |
903 | { |
904 | // Edge case: An empty needle is always found. |
905 | if (needle->length == 0) return start; |
906 | |
907 | // If the needle goes past the haystack it won't be found. |
908 | if (start + needle->length > haystack->length) return UINT32_MAX; |
909 | |
910 | // If the startIndex is too far it also won't be found. |
911 | if (start >= haystack->length) return UINT32_MAX; |
912 | |
913 | // Pre-calculate the shift table. For each character (8-bit value), we |
914 | // determine how far the search window can be advanced if that character is |
915 | // the last character in the haystack where we are searching for the needle |
916 | // and the needle doesn't match there. |
917 | uint32_t shift[UINT8_MAX]; |
918 | uint32_t needleEnd = needle->length - 1; |
919 | |
920 | // By default, we assume the character is not the needle at all. In that case |
921 | // case, if a match fails on that character, we can advance one whole needle |
922 | // width since. |
923 | for (uint32_t index = 0; index < UINT8_MAX; index++) |
924 | { |
925 | shift[index] = needle->length; |
926 | } |
927 | |
928 | // Then, for every character in the needle, determine how far it is from the |
929 | // end. If a match fails on that character, we can advance the window such |
930 | // that it the last character in it lines up with the last place we could |
931 | // find it in the needle. |
932 | for (uint32_t index = 0; index < needleEnd; index++) |
933 | { |
934 | char c = needle->value[index]; |
935 | shift[(uint8_t)c] = needleEnd - index; |
936 | } |
937 | |
938 | // Slide the needle across the haystack, looking for the first match or |
939 | // stopping if the needle goes off the end. |
940 | char lastChar = needle->value[needleEnd]; |
941 | uint32_t range = haystack->length - needle->length; |
942 | |
943 | for (uint32_t index = start; index <= range; ) |
944 | { |
945 | // Compare the last character in the haystack's window to the last character |
946 | // in the needle. If it matches, see if the whole needle matches. |
947 | char c = haystack->value[index + needleEnd]; |
948 | if (lastChar == c && |
949 | memcmp(haystack->value + index, needle->value, needleEnd) == 0) |
950 | { |
951 | // Found a match. |
952 | return index; |
953 | } |
954 | |
955 | // Otherwise, slide the needle forward. |
956 | index += shift[(uint8_t)c]; |
957 | } |
958 | |
959 | // Not found. |
960 | return UINT32_MAX; |
961 | } |
962 | |
963 | ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value) |
964 | { |
965 | ObjUpvalue* upvalue = ALLOCATE(vm, ObjUpvalue); |
966 | |
967 | // Upvalues are never used as first-class objects, so don't need a class. |
968 | initObj(vm, &upvalue->obj, OBJ_UPVALUE, NULL); |
969 | |
970 | upvalue->value = value; |
971 | upvalue->closed = NULL_VAL; |
972 | upvalue->next = NULL; |
973 | return upvalue; |
974 | } |
975 | |
976 | void wrenGrayObj(WrenVM* vm, Obj* obj) |
977 | { |
978 | if (obj == NULL) return; |
979 | |
980 | // Stop if the object is already darkened so we don't get stuck in a cycle. |
981 | if (obj->isDark) return; |
982 | |
983 | // It's been reached. |
984 | obj->isDark = true; |
985 | |
986 | // Add it to the gray list so it can be recursively explored for |
987 | // more marks later. |
988 | if (vm->grayCount >= vm->grayCapacity) |
989 | { |
990 | vm->grayCapacity = vm->grayCount * 2; |
991 | vm->gray = (Obj**)vm->config.reallocateFn(vm->gray, |
992 | vm->grayCapacity * sizeof(Obj*), |
993 | vm->config.userData); |
994 | } |
995 | |
996 | vm->gray[vm->grayCount++] = obj; |
997 | } |
998 | |
999 | void wrenGrayValue(WrenVM* vm, Value value) |
1000 | { |
1001 | if (!IS_OBJ(value)) return; |
1002 | wrenGrayObj(vm, AS_OBJ(value)); |
1003 | } |
1004 | |
1005 | void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer) |
1006 | { |
1007 | for (int i = 0; i < buffer->count; i++) |
1008 | { |
1009 | wrenGrayValue(vm, buffer->data[i]); |
1010 | } |
1011 | } |
1012 | |
1013 | static void blackenClass(WrenVM* vm, ObjClass* classObj) |
1014 | { |
1015 | // The metaclass. |
1016 | wrenGrayObj(vm, (Obj*)classObj->obj.classObj); |
1017 | |
1018 | // The superclass. |
1019 | wrenGrayObj(vm, (Obj*)classObj->superclass); |
1020 | |
1021 | // Method function objects. |
1022 | for (int i = 0; i < classObj->methods.count; i++) |
1023 | { |
1024 | if (classObj->methods.data[i].type == METHOD_BLOCK) |
1025 | { |
1026 | wrenGrayObj(vm, (Obj*)classObj->methods.data[i].as.closure); |
1027 | } |
1028 | } |
1029 | |
1030 | wrenGrayObj(vm, (Obj*)classObj->name); |
1031 | |
1032 | if(!IS_NULL(classObj->attributes)) wrenGrayObj(vm, AS_OBJ(classObj->attributes)); |
1033 | |
1034 | // Keep track of how much memory is still in use. |
1035 | vm->bytesAllocated += sizeof(ObjClass); |
1036 | vm->bytesAllocated += classObj->methods.capacity * sizeof(Method); |
1037 | } |
1038 | |
1039 | static void blackenClosure(WrenVM* vm, ObjClosure* closure) |
1040 | { |
1041 | // Mark the function. |
1042 | wrenGrayObj(vm, (Obj*)closure->fn); |
1043 | |
1044 | // Mark the upvalues. |
1045 | for (int i = 0; i < closure->fn->numUpvalues; i++) |
1046 | { |
1047 | wrenGrayObj(vm, (Obj*)closure->upvalues[i]); |
1048 | } |
1049 | |
1050 | // Keep track of how much memory is still in use. |
1051 | vm->bytesAllocated += sizeof(ObjClosure); |
1052 | vm->bytesAllocated += sizeof(ObjUpvalue*) * closure->fn->numUpvalues; |
1053 | } |
1054 | |
1055 | static void blackenFiber(WrenVM* vm, ObjFiber* fiber) |
1056 | { |
1057 | // Stack functions. |
1058 | for (int i = 0; i < fiber->numFrames; i++) |
1059 | { |
1060 | wrenGrayObj(vm, (Obj*)fiber->frames[i].closure); |
1061 | } |
1062 | |
1063 | // Stack variables. |
1064 | for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++) |
1065 | { |
1066 | wrenGrayValue(vm, *slot); |
1067 | } |
1068 | |
1069 | // Open upvalues. |
1070 | ObjUpvalue* upvalue = fiber->openUpvalues; |
1071 | while (upvalue != NULL) |
1072 | { |
1073 | wrenGrayObj(vm, (Obj*)upvalue); |
1074 | upvalue = upvalue->next; |
1075 | } |
1076 | |
1077 | // The caller. |
1078 | wrenGrayObj(vm, (Obj*)fiber->caller); |
1079 | wrenGrayValue(vm, fiber->error); |
1080 | |
1081 | // Keep track of how much memory is still in use. |
1082 | vm->bytesAllocated += sizeof(ObjFiber); |
1083 | vm->bytesAllocated += fiber->frameCapacity * sizeof(CallFrame); |
1084 | vm->bytesAllocated += fiber->stackCapacity * sizeof(Value); |
1085 | } |
1086 | |
1087 | static void blackenFn(WrenVM* vm, ObjFn* fn) |
1088 | { |
1089 | // Mark the constants. |
1090 | wrenGrayBuffer(vm, &fn->constants); |
1091 | |
1092 | // Keep track of how much memory is still in use. |
1093 | vm->bytesAllocated += sizeof(ObjFn); |
1094 | vm->bytesAllocated += sizeof(uint8_t) * fn->code.capacity; |
1095 | vm->bytesAllocated += sizeof(Value) * fn->constants.capacity; |
1096 | |
1097 | // The debug line number buffer. |
1098 | vm->bytesAllocated += sizeof(int) * fn->code.capacity; |
1099 | // TODO: What about the function name? |
1100 | } |
1101 | |
1102 | static void blackenForeign(WrenVM* vm, ObjForeign* foreign) |
1103 | { |
1104 | // TODO: Keep track of how much memory the foreign object uses. We can store |
1105 | // this in each foreign object, but it will balloon the size. We may not want |
1106 | // that much overhead. One option would be to let the foreign class register |
1107 | // a C function that returns a size for the object. That way the VM doesn't |
1108 | // always have to explicitly store it. |
1109 | } |
1110 | |
1111 | static void blackenInstance(WrenVM* vm, ObjInstance* instance) |
1112 | { |
1113 | wrenGrayObj(vm, (Obj*)instance->obj.classObj); |
1114 | |
1115 | // Mark the fields. |
1116 | for (int i = 0; i < instance->obj.classObj->numFields; i++) |
1117 | { |
1118 | wrenGrayValue(vm, instance->fields[i]); |
1119 | } |
1120 | |
1121 | // Keep track of how much memory is still in use. |
1122 | vm->bytesAllocated += sizeof(ObjInstance); |
1123 | vm->bytesAllocated += sizeof(Value) * instance->obj.classObj->numFields; |
1124 | } |
1125 | |
1126 | static void blackenList(WrenVM* vm, ObjList* list) |
1127 | { |
1128 | // Mark the elements. |
1129 | wrenGrayBuffer(vm, &list->elements); |
1130 | |
1131 | // Keep track of how much memory is still in use. |
1132 | vm->bytesAllocated += sizeof(ObjList); |
1133 | vm->bytesAllocated += sizeof(Value) * list->elements.capacity; |
1134 | } |
1135 | |
1136 | static void blackenMap(WrenVM* vm, ObjMap* map) |
1137 | { |
1138 | // Mark the entries. |
1139 | for (uint32_t i = 0; i < map->capacity; i++) |
1140 | { |
1141 | MapEntry* entry = &map->entries[i]; |
1142 | if (IS_UNDEFINED(entry->key)) continue; |
1143 | |
1144 | wrenGrayValue(vm, entry->key); |
1145 | wrenGrayValue(vm, entry->value); |
1146 | } |
1147 | |
1148 | // Keep track of how much memory is still in use. |
1149 | vm->bytesAllocated += sizeof(ObjMap); |
1150 | vm->bytesAllocated += sizeof(MapEntry) * map->capacity; |
1151 | } |
1152 | |
1153 | static void blackenModule(WrenVM* vm, ObjModule* module) |
1154 | { |
1155 | // Top-level variables. |
1156 | for (int i = 0; i < module->variables.count; i++) |
1157 | { |
1158 | wrenGrayValue(vm, module->variables.data[i]); |
1159 | } |
1160 | |
1161 | wrenBlackenSymbolTable(vm, &module->variableNames); |
1162 | |
1163 | wrenGrayObj(vm, (Obj*)module->name); |
1164 | |
1165 | // Keep track of how much memory is still in use. |
1166 | vm->bytesAllocated += sizeof(ObjModule); |
1167 | } |
1168 | |
1169 | static void blackenRange(WrenVM* vm, ObjRange* range) |
1170 | { |
1171 | // Keep track of how much memory is still in use. |
1172 | vm->bytesAllocated += sizeof(ObjRange); |
1173 | } |
1174 | |
1175 | static void blackenString(WrenVM* vm, ObjString* string) |
1176 | { |
1177 | // Keep track of how much memory is still in use. |
1178 | vm->bytesAllocated += sizeof(ObjString) + string->length + 1; |
1179 | } |
1180 | |
1181 | static void blackenUpvalue(WrenVM* vm, ObjUpvalue* upvalue) |
1182 | { |
1183 | // Mark the closed-over object (in case it is closed). |
1184 | wrenGrayValue(vm, upvalue->closed); |
1185 | |
1186 | // Keep track of how much memory is still in use. |
1187 | vm->bytesAllocated += sizeof(ObjUpvalue); |
1188 | } |
1189 | |
1190 | static void blackenObject(WrenVM* vm, Obj* obj) |
1191 | { |
1192 | #if WREN_DEBUG_TRACE_MEMORY |
1193 | printf("mark " ); |
1194 | wrenDumpValue(OBJ_VAL(obj)); |
1195 | printf(" @ %p\n" , obj); |
1196 | #endif |
1197 | |
1198 | // Traverse the object's fields. |
1199 | switch (obj->type) |
1200 | { |
1201 | case OBJ_CLASS: blackenClass( vm, (ObjClass*) obj); break; |
1202 | case OBJ_CLOSURE: blackenClosure( vm, (ObjClosure*) obj); break; |
1203 | case OBJ_FIBER: blackenFiber( vm, (ObjFiber*) obj); break; |
1204 | case OBJ_FN: blackenFn( vm, (ObjFn*) obj); break; |
1205 | case OBJ_FOREIGN: blackenForeign( vm, (ObjForeign*) obj); break; |
1206 | case OBJ_INSTANCE: blackenInstance(vm, (ObjInstance*)obj); break; |
1207 | case OBJ_LIST: blackenList( vm, (ObjList*) obj); break; |
1208 | case OBJ_MAP: blackenMap( vm, (ObjMap*) obj); break; |
1209 | case OBJ_MODULE: blackenModule( vm, (ObjModule*) obj); break; |
1210 | case OBJ_RANGE: blackenRange( vm, (ObjRange*) obj); break; |
1211 | case OBJ_STRING: blackenString( vm, (ObjString*) obj); break; |
1212 | case OBJ_UPVALUE: blackenUpvalue( vm, (ObjUpvalue*) obj); break; |
1213 | } |
1214 | } |
1215 | |
1216 | void wrenBlackenObjects(WrenVM* vm) |
1217 | { |
1218 | while (vm->grayCount > 0) |
1219 | { |
1220 | // Pop an item from the gray stack. |
1221 | Obj* obj = vm->gray[--vm->grayCount]; |
1222 | blackenObject(vm, obj); |
1223 | } |
1224 | } |
1225 | |
1226 | void wrenFreeObj(WrenVM* vm, Obj* obj) |
1227 | { |
1228 | #if WREN_DEBUG_TRACE_MEMORY |
1229 | printf("free " ); |
1230 | wrenDumpValue(OBJ_VAL(obj)); |
1231 | printf(" @ %p\n" , obj); |
1232 | #endif |
1233 | |
1234 | switch (obj->type) |
1235 | { |
1236 | case OBJ_CLASS: |
1237 | wrenMethodBufferClear(vm, &((ObjClass*)obj)->methods); |
1238 | break; |
1239 | |
1240 | case OBJ_FIBER: |
1241 | { |
1242 | ObjFiber* fiber = (ObjFiber*)obj; |
1243 | DEALLOCATE(vm, fiber->frames); |
1244 | DEALLOCATE(vm, fiber->stack); |
1245 | break; |
1246 | } |
1247 | |
1248 | case OBJ_FN: |
1249 | { |
1250 | ObjFn* fn = (ObjFn*)obj; |
1251 | wrenValueBufferClear(vm, &fn->constants); |
1252 | wrenByteBufferClear(vm, &fn->code); |
1253 | wrenIntBufferClear(vm, &fn->debug->sourceLines); |
1254 | DEALLOCATE(vm, fn->debug->name); |
1255 | DEALLOCATE(vm, fn->debug); |
1256 | break; |
1257 | } |
1258 | |
1259 | case OBJ_FOREIGN: |
1260 | wrenFinalizeForeign(vm, (ObjForeign*)obj); |
1261 | break; |
1262 | |
1263 | case OBJ_LIST: |
1264 | wrenValueBufferClear(vm, &((ObjList*)obj)->elements); |
1265 | break; |
1266 | |
1267 | case OBJ_MAP: |
1268 | DEALLOCATE(vm, ((ObjMap*)obj)->entries); |
1269 | break; |
1270 | |
1271 | case OBJ_MODULE: |
1272 | wrenSymbolTableClear(vm, &((ObjModule*)obj)->variableNames); |
1273 | wrenValueBufferClear(vm, &((ObjModule*)obj)->variables); |
1274 | break; |
1275 | |
1276 | case OBJ_CLOSURE: |
1277 | case OBJ_INSTANCE: |
1278 | case OBJ_RANGE: |
1279 | case OBJ_STRING: |
1280 | case OBJ_UPVALUE: |
1281 | break; |
1282 | } |
1283 | |
1284 | DEALLOCATE(vm, obj); |
1285 | } |
1286 | |
1287 | ObjClass* wrenGetClass(WrenVM* vm, Value value) |
1288 | { |
1289 | return wrenGetClassInline(vm, value); |
1290 | } |
1291 | |
1292 | bool wrenValuesEqual(Value a, Value b) |
1293 | { |
1294 | if (wrenValuesSame(a, b)) return true; |
1295 | |
1296 | // If we get here, it's only possible for two heap-allocated immutable objects |
1297 | // to be equal. |
1298 | if (!IS_OBJ(a) || !IS_OBJ(b)) return false; |
1299 | |
1300 | Obj* aObj = AS_OBJ(a); |
1301 | Obj* bObj = AS_OBJ(b); |
1302 | |
1303 | // Must be the same type. |
1304 | if (aObj->type != bObj->type) return false; |
1305 | |
1306 | switch (aObj->type) |
1307 | { |
1308 | case OBJ_RANGE: |
1309 | { |
1310 | ObjRange* aRange = (ObjRange*)aObj; |
1311 | ObjRange* bRange = (ObjRange*)bObj; |
1312 | return aRange->from == bRange->from && |
1313 | aRange->to == bRange->to && |
1314 | aRange->isInclusive == bRange->isInclusive; |
1315 | } |
1316 | |
1317 | case OBJ_STRING: |
1318 | { |
1319 | ObjString* aString = (ObjString*)aObj; |
1320 | ObjString* bString = (ObjString*)bObj; |
1321 | return aString->hash == bString->hash && |
1322 | wrenStringEqualsCString(aString, bString->value, bString->length); |
1323 | } |
1324 | |
1325 | default: |
1326 | // All other types are only equal if they are same, which they aren't if |
1327 | // we get here. |
1328 | return false; |
1329 | } |
1330 | } |
1331 | |