memory.c
1 #include <time.h>
2 #include <kuroko/vm.h>
3 #include <kuroko/memory.h>
4 #include <kuroko/object.h>
5 #include <kuroko/compiler.h>
6 #include <kuroko/table.h>
7 #include <kuroko/util.h>
8 
9 #include "private.h"
10 
11 #define FREE_OBJECT(t,p) krk_reallocate(p,sizeof(t),0)
12 
13 #if defined(KRK_EXTENSIVE_MEMORY_DEBUGGING)
54 typedef struct DHE {
55  const void* ptr;
56  size_t size;
57  struct DHE * next;
58 } _dhe;
59 
64 #define DHE_SIZE 256
65 static struct DHE * _debug_mem[DHE_SIZE];
66 
67 static inline unsigned int _debug_mem_hash(const void * ptr) {
68  /* Pointers to objects are very often 16-byte aligned, and most
69  * allocations are of objects, so it would make sense to ignore
70  * the lower bits or we'll end up with a pretty bad hash. */
71  uintptr_t p = (uintptr_t)ptr;
72  return (p >> 4) & (DHE_SIZE-1);
73 }
74 
75 static void _debug_mem_set(const void* ptr, size_t size) {
76  unsigned int hash = _debug_mem_hash(ptr);
77  _dhe * x = _debug_mem[hash];
78  if (!x) {
79  x = malloc(sizeof(_dhe));
80  x->ptr = ptr;
81  x->size = size;
82  x->next = NULL;
83  _debug_mem[hash] = x;
84  } else {
85  _dhe * p = NULL;
86  do {
87  if (x->ptr == ptr) {
88  x->size = size;
89  return;
90  } else {
91  p = x;
92  x = x->next;
93  }
94  } while (x);
95  x = malloc(sizeof(_dhe));
96  x->ptr = ptr;
97  x->size = size;
98  x->next = NULL;
99  p->next = x;
100  }
101 }
102 
103 static size_t _debug_mem_get(const void * ptr) {
104  unsigned int hash = _debug_mem_hash(ptr);
105  _dhe * x = _debug_mem[hash];
106  if (!x) return 0;
107  do {
108  if (x->ptr == ptr) return x->size;
109  x = x->next;
110  } while (x);
111  return 0;
112 }
113 
114 static void _debug_mem_remove(const void *ptr) {
115  unsigned int hash = _debug_mem_hash(ptr);
116  _dhe * x = _debug_mem[hash];
117  if (!x) return;
118 
119  if (x->ptr == ptr) {
120  _debug_mem[hash] = x->next;
121  free(x);
122  } else {
123  _dhe * p = x;
124  x = x->next;
125  do {
126  if (x->ptr == ptr) {
127  p->next = x->next;
128  free(x);
129  return;
130  }
131  p = x;
132  x = x->next;
133  } while (x);
134  }
135 }
136 
137 static int _debug_mem_has(const void *ptr) {
138  unsigned int hash = _debug_mem_hash(ptr);
139  _dhe * x = _debug_mem[hash];
140  if (!x) return 0;
141  do {
142  if (x->ptr == ptr) return 1;
143  x = x->next;
144  } while (x);
145  return 0;
146 }
147 #endif
148 
149 void krk_gcTakeBytes(const void * ptr, size_t size) {
150 #if defined(KRK_EXTENSIVE_MEMORY_DEBUGGING)
151  _debug_mem_set(ptr, size);
152 #endif
153 
154  vm.bytesAllocated += size;
155 }
156 
157 void * krk_reallocate(void * ptr, size_t old, size_t new) {
158 
159  vm.bytesAllocated -= old;
160  vm.bytesAllocated += new;
161 
162  if (new > old && ptr != krk_currentThread.stack && &krk_currentThread == vm.threads && !(vm.globalFlags & KRK_GLOBAL_GC_PAUSED)) {
163 #ifndef KRK_NO_STRESS_GC
164  if (vm.globalFlags & KRK_GLOBAL_ENABLE_STRESS_GC) {
166  }
167 #endif
168  if (vm.bytesAllocated > vm.nextGC) {
170  }
171  }
172 
173  void * out;
174  if (new == 0) {
175  free(ptr);
176  out = NULL;
177  } else {
178  out = realloc(ptr, new);
179  }
180 
181 #if defined(KRK_EXTENSIVE_MEMORY_DEBUGGING)
182  if (ptr) {
183  if (!_debug_mem_has(ptr)) {
184  fprintf(stderr, "Invalid reallocation of %p from %zu to %zu\n", ptr, old, new);
185  abort();
186  }
187 
188  size_t t = _debug_mem_get(ptr);
189  if (t != old) {
190  fprintf(stderr, "Invalid reallocation of %p from %zu - should be %zu - to %zu\n", ptr, old, t, new);
191  abort();
192  }
193 
194  _debug_mem_remove(ptr);
195  }
196  if (out) {
197  _debug_mem_set(out, new);
198  }
199 #endif
200 
201  return out;
202 }
203 
204 static void freeObject(KrkObj * object) {
205  switch (object->type) {
206  case KRK_OBJ_STRING: {
207  KrkString * string = (KrkString*)object;
208  KRK_FREE_ARRAY(char, string->chars, string->length + 1);
209  if (string->codes && string->codes != string->chars) free(string->codes);
210  FREE_OBJECT(KrkString, object);
211  break;
212  }
213  case KRK_OBJ_CODEOBJECT: {
214  KrkCodeObject * function = (KrkCodeObject*)object;
215  krk_freeChunk(&function->chunk);
216  krk_freeValueArray(&function->positionalArgNames);
217  krk_freeValueArray(&function->keywordArgNames);
218  KRK_FREE_ARRAY(KrkLocalEntry, function->localNames, function->localNameCount);
219  KRK_FREE_ARRAY(KrkExpressionsMap, function->expressions, function->expressionsCapacity);
220  KRK_FREE_ARRAY(KrkOverlongJump, function->overlongJumps, function->overlongJumpsCapacity);
221  function->localNameCount = 0;
222  FREE_OBJECT(KrkCodeObject, object);
223  break;
224  }
225  case KRK_OBJ_NATIVE: {
226  FREE_OBJECT(KrkNative, object);
227  break;
228  }
229  case KRK_OBJ_CLOSURE: {
230  KrkClosure * closure = (KrkClosure*)object;
231  KRK_FREE_ARRAY(KrkUpvalue*,closure->upvalues,closure->upvalueCount);
232  krk_freeTable(&closure->fields);
233  FREE_OBJECT(KrkClosure, object);
234  break;
235  }
236  case KRK_OBJ_UPVALUE: {
237  FREE_OBJECT(KrkUpvalue, object);
238  break;
239  }
240  case KRK_OBJ_CLASS: {
241  KrkClass * _class = (KrkClass*)object;
242  krk_freeTable(&_class->methods);
243  krk_freeTable(&_class->subclasses);
244  if (_class->base) {
245  krk_tableDeleteExact(&_class->base->subclasses, OBJECT_VAL(object));
246  }
247  FREE_OBJECT(KrkClass, object);
248  break;
249  }
250  case KRK_OBJ_INSTANCE: {
251  KrkInstance * inst = (KrkInstance*)object;
252  if (inst->_class->_ongcsweep) {
253  inst->_class->_ongcsweep(inst);
254  }
255  krk_freeTable(&inst->fields);
256  krk_reallocate(object,inst->_class->allocSize,0);
257  break;
258  }
259  case KRK_OBJ_BOUND_METHOD:
260  FREE_OBJECT(KrkBoundMethod, object);
261  break;
262  case KRK_OBJ_TUPLE: {
263  KrkTuple * tuple = (KrkTuple*)object;
264  krk_freeValueArray(&tuple->values);
265  FREE_OBJECT(KrkTuple, object);
266  break;
267  }
268  case KRK_OBJ_BYTES: {
269  KrkBytes * bytes = (KrkBytes*)object;
270  KRK_FREE_ARRAY(uint8_t, bytes->bytes, bytes->length);
271  FREE_OBJECT(KrkBytes, bytes);
272  break;
273  }
274  }
275 }
276 
277 void krk_freeObjects(void) {
278  KrkObj * object = vm.objects;
279  KrkObj * other = NULL;
280 
281  while (object) {
282  KrkObj * next = object->next;
283  if (object->type == KRK_OBJ_INSTANCE) {
284  freeObject(object);
285  } else {
286  object->next = other;
287  other = object;
288  }
289  object = next;
290  }
291 
292  while (other) {
293  KrkObj * next = other->next;
294  if (other->type == KRK_OBJ_CLASS) {
295  ((KrkClass*)other)->base = NULL;
296  }
297  freeObject(other);
298  other = next;
299  }
300 
301  free(vm.grayStack);
302 }
303 
304 void krk_freeMemoryDebugger(void) {
305 #if defined(KRK_EXTENSIVE_MEMORY_DEBUGGING)
306  for (unsigned int i = 0; i < DHE_SIZE; ++i) {
307  _dhe * x = _debug_mem[i];
308  _debug_mem[i] = NULL;
309  while (x) {
310  _dhe * n = x->next;
311  free(x);
312  x = n;
313  }
314  }
315  if (vm.bytesAllocated != 0) {
316  abort();
317  }
318 #endif
319 }
320 
321 void krk_markObject(KrkObj * object) {
322  if (!object) return;
323  if (object->flags & KRK_OBJ_FLAGS_IS_MARKED) return;
324  object->flags |= KRK_OBJ_FLAGS_IS_MARKED;
325 
326  if (vm.grayCapacity < vm.grayCount + 1) {
327  vm.grayCapacity = KRK_GROW_CAPACITY(vm.grayCapacity);
328  vm.grayStack = realloc(vm.grayStack, sizeof(KrkObj*) * vm.grayCapacity);
329  if (!vm.grayStack) exit(1);
330  }
331  vm.grayStack[vm.grayCount++] = object;
332 }
333 
334 void krk_markValue(KrkValue value) {
335  if (!IS_OBJECT(value)) return;
336  krk_markObject(AS_OBJECT(value));
337 }
338 
339 static void markArray(KrkValueArray * array) {
340  for (size_t i = 0; i < array->count; ++i) {
341  krk_markValue(array->values[i]);
342  }
343 }
344 
345 static void blackenObject(KrkObj * object) {
346  switch (object->type) {
347  case KRK_OBJ_CLOSURE: {
348  KrkClosure * closure = (KrkClosure *)object;
349  krk_markObject((KrkObj*)closure->function);
350  for (size_t i = 0; i < closure->upvalueCount; ++i) {
351  krk_markObject((KrkObj*)closure->upvalues[i]);
352  }
353  krk_markValue(closure->annotations);
354  krk_markTable(&closure->fields);
355  krk_markValue(closure->globalsOwner);
356  break;
357  }
358  case KRK_OBJ_CODEOBJECT: {
359  KrkCodeObject * function = (KrkCodeObject *)object;
360  krk_markObject((KrkObj*)function->name);
361  krk_markObject((KrkObj*)function->qualname);
362  krk_markObject((KrkObj*)function->docstring);
363  krk_markObject((KrkObj*)function->chunk.filename);
364  markArray(&function->positionalArgNames);
365  markArray(&function->keywordArgNames);
366  markArray(&function->chunk.constants);
367  for (size_t i = 0; i < function->localNameCount; ++i) {
368  krk_markObject((KrkObj*)function->localNames[i].name);
369  }
370  krk_markValue(function->jumpTargets);
371  break;
372  }
373  case KRK_OBJ_UPVALUE:
374  krk_markValue(((KrkUpvalue*)object)->closed);
375  break;
376  case KRK_OBJ_CLASS: {
377  KrkClass * _class = (KrkClass *)object;
378  krk_markObject((KrkObj*)_class->name);
379  krk_markObject((KrkObj*)_class->filename);
380  krk_markObject((KrkObj*)_class->base);
381  krk_markObject((KrkObj*)_class->_class);
382  krk_markTable(&_class->methods);
383  break;
384  }
385  case KRK_OBJ_INSTANCE: {
386  krk_markObject((KrkObj*)((KrkInstance*)object)->_class);
387  if (((KrkInstance*)object)->_class->_ongcscan) ((KrkInstance*)object)->_class->_ongcscan((KrkInstance*)object);
388  krk_markTable(&((KrkInstance*)object)->fields);
389  break;
390  }
391  case KRK_OBJ_BOUND_METHOD: {
392  KrkBoundMethod * bound = (KrkBoundMethod *)object;
393  krk_markValue(bound->receiver);
394  krk_markObject((KrkObj*)bound->method);
395  break;
396  }
397  case KRK_OBJ_TUPLE: {
398  KrkTuple * tuple = (KrkTuple *)object;
399  markArray(&tuple->values);
400  break;
401  }
402  case KRK_OBJ_NATIVE:
403  case KRK_OBJ_STRING:
404  case KRK_OBJ_BYTES:
405  break;
406  }
407 }
408 
409 static void traceReferences(void) {
410  while (vm.grayCount > 0) {
411  KrkObj * object = vm.grayStack[--vm.grayCount];
412  blackenObject(object);
413  }
414 }
415 
416 static size_t sweep(void) {
417  KrkObj * previous = NULL;
418  KrkObj * object = vm.objects;
419  size_t count = 0;
420  while (object) {
421  if (object->flags & (KRK_OBJ_FLAGS_IMMORTAL | KRK_OBJ_FLAGS_IS_MARKED)) {
422  object->flags &= ~(KRK_OBJ_FLAGS_IS_MARKED | KRK_OBJ_FLAGS_SECOND_CHANCE);
423  previous = object;
424  object = object->next;
425  } else if (object->flags & KRK_OBJ_FLAGS_SECOND_CHANCE) {
426  KrkObj * unreached = object;
427  object = object->next;
428  if (previous != NULL) {
429  previous->next = object;
430  } else {
431  vm.objects = object;
432  }
433  freeObject(unreached);
434  count++;
435  } else {
436  object->flags |= KRK_OBJ_FLAGS_SECOND_CHANCE;
437  previous = object;
438  object = object->next;
439  }
440  }
441  return count;
442 }
443 
444 void krk_markTable(KrkTable * table) {
445  for (size_t i = 0; i < table->used; ++i) {
446  KrkTableEntry * entry = &table->entries[i];
447  krk_markValue(entry->key);
448  krk_markValue(entry->value);
449  }
450 }
451 
452 static void tableRemoveWhite(KrkTable * table) {
453  for (size_t i = 0; i < table->used; ++i) {
454  KrkTableEntry * entry = &table->entries[i];
455  if (IS_OBJECT(entry->key) && !((AS_OBJECT(entry->key))->flags & KRK_OBJ_FLAGS_IS_MARKED)) {
456  krk_tableDeleteExact(table, entry->key);
457  }
458  }
459 }
460 
461 static void markThreadRoots(KrkThreadState * thread) {
462  for (KrkValue * slot = thread->stack; slot && slot < thread->stackTop; ++slot) {
463  krk_markValue(*slot);
464  }
465  for (KrkUpvalue * upvalue = thread->openUpvalues; upvalue; upvalue = upvalue->next) {
466  krk_markObject((KrkObj*)upvalue);
467  }
469 
470  if (thread->module) krk_markObject((KrkObj*)thread->module);
471 
472  for (int i = 0; i < KRK_THREAD_SCRATCH_SIZE; ++i) {
473  krk_markValue(thread->scratchSpace[i]);
474  }
475 }
476 
477 static void markRoots(void) {
478  KrkThreadState * thread = vm.threads;
479  while (thread) {
480  markThreadRoots(thread);
481  thread = thread->next;
482  }
483 
484  krk_markObject((KrkObj*)vm.builtins);
485  krk_markTable(&vm.modules);
486 
487  if (vm.specialMethodNames) {
488  for (int i = 0; i < METHOD__MAX; ++i) {
489  krk_markValue(vm.specialMethodNames[i]);
490  }
491  }
492 }
493 
494 #ifndef KRK_NO_GC_TRACING
495 static int smartSize(char _out[100], size_t s) {
496 #if UINTPTR_MAX == 0xFFFFFFFF
497  size_t count = 3;
498  char * prefix = "GMK";
499 #else
500  size_t count = 5;
501  char * prefix = "PTGMK";
502 #endif
503  for (; count > 0 && *prefix; count--, prefix++) {
504  size_t base = 1UL << (count * 10);
505  if (s >= base) {
506  size_t t = s / base;
507  return snprintf(_out, 100, "%zu.%1zu %ciB", t, (s - t * base) / (base / 10), *prefix);
508  }
509  }
510  return snprintf(_out, 100, "%d B", (int)s);
511 }
512 #endif
513 
514 size_t krk_collectGarbage(void) {
515 #ifndef KRK_NO_GC_TRACING
516  struct timespec outTime, inTime;
517 
518  if (vm.globalFlags & KRK_GLOBAL_REPORT_GC_COLLECTS) {
519  clock_gettime(CLOCK_MONOTONIC, &inTime);
520  }
521 
522  size_t bytesBefore = vm.bytesAllocated;
523 #endif
524 
525  markRoots();
526  traceReferences();
527  tableRemoveWhite(&vm.strings);
528  size_t out = sweep();
529 
540  if (vm.bytesAllocated < 0x4000000) {
541  vm.nextGC = vm.bytesAllocated * 2;
542  } else {
543  vm.nextGC = vm.bytesAllocated + 0x4000000;
544  }
545 
546 #ifndef KRK_NO_GC_TRACING
547  if (vm.globalFlags & KRK_GLOBAL_REPORT_GC_COLLECTS) {
548  clock_gettime(CLOCK_MONOTONIC, &outTime);
549  struct timespec diff;
550  diff.tv_sec = outTime.tv_sec - inTime.tv_sec;
551  diff.tv_nsec = outTime.tv_nsec - inTime.tv_nsec;
552  if (diff.tv_nsec < 0) { diff.tv_sec--; diff.tv_nsec += 1000000000L; }
553 
554  char smartBefore[100];
555  smartSize(smartBefore, bytesBefore);
556  char smartAfter[100];
557  smartSize(smartAfter, vm.bytesAllocated);
558  char smartFreed[100];
559  smartSize(smartFreed, bytesBefore - vm.bytesAllocated);
560  char smartNext[100];
561  smartSize(smartNext, vm.nextGC);
562 
563  fprintf(stderr, "[gc] %lld.%.9lds %s before; %s after; freed %s in %llu objects; next collection at %s\n",
564  (long long)diff.tv_sec, diff.tv_nsec,
565  smartBefore,smartAfter,smartFreed,(unsigned long long)out, smartNext);
566  }
567 #endif
568  return out;
569 }
570 
Exported methods for the source compiler.
Functions for dealing with garbage collection and memory allocation.
void krk_markValue(KrkValue value)
During a GC scan cycle, mark a value as used.
Definition: memory.c:334
void krk_markTable(KrkTable *table)
During a GC scan cycle, mark the contents of a table as used.
Definition: memory.c:444
void krk_freeObjects(void)
Release all objects.
Definition: memory.c:277
void krk_markObject(KrkObj *object)
During a GC scan cycle, mark an object as used.
Definition: memory.c:321
void * krk_reallocate(void *ptr, size_t old, size_t new)
Resize an allocated heap object.
Definition: memory.c:157
void krk_gcTakeBytes(const void *ptr, size_t size)
Assume ownership of size bytes at ptr.
Definition: memory.c:149
size_t krk_collectGarbage(void)
Run a cycle of the garbage collector.
Definition: memory.c:514
Struct definitions for core object types.
Internal header.
A function that has been attached to an object to serve as a method.
Definition: object.h:295
KrkValue receiver
Object to pass as implicit first argument.
Definition: object.h:297
KrkObj * method
Function to call.
Definition: object.h:298
Immutable sequence of bytes.
Definition: object.h:105
size_t length
Length of data in bytes.
Definition: object.h:107
uint8_t * bytes
Pointer to separately-stored bytes data.
Definition: object.h:108
void krk_freeChunk(KrkChunk *chunk)
Release the resources allocated to an opcode chunk.
Definition: chunk.c:43
Type object.
Definition: object.h:215
KrkString * filename
Filename of the original source that defined the codeobject for the class.
Definition: object.h:220
KrkCleanupCallback _ongcsweep
C function to call when the garbage collector is discarding an instance of this class.
Definition: object.h:224
KrkCleanupCallback _ongcscan
C function to call when the garbage collector visits an instance of this class in the scan phase.
Definition: object.h:223
KrkString * name
Name of the class.
Definition: object.h:219
struct KrkClass * base
Pointer to base class implementation.
Definition: object.h:221
KrkTable subclasses
Set of classes that subclass this class.
Definition: object.h:225
size_t allocSize
Size to allocate when creating instances of this class.
Definition: object.h:222
KrkTable methods
General attributes table.
Definition: object.h:218
struct KrkClass * _class
Metaclass.
Definition: object.h:217
Function object.
Definition: object.h:195
KrkCodeObject * function
The codeobject containing the bytecode run when this function is called.
Definition: object.h:197
KrkValue globalsOwner
Owner of the globals table for this function.
Definition: object.h:202
size_t upvalueCount
Number of entries in upvalues.
Definition: object.h:199
KrkValue annotations
Dictionary of type hints.
Definition: object.h:200
KrkUpvalue ** upvalues
Array of upvalues collected from the surrounding context when the closure was created.
Definition: object.h:198
KrkTable fields
Object attributes table.
Definition: object.h:201
Code object.
Definition: object.h:163
Map entry of opcode offsets to expressions spans.
Definition: object.h:141
An object of a class.
Definition: object.h:281
KrkClass * _class
Type.
Definition: object.h:283
KrkTable fields
Attributes table.
Definition: object.h:284
Metadata on a local variable name in a function.
Definition: object.h:129
Managed binding to a C function.
Definition: object.h:309
The most basic object type.
Definition: object.h:41
uint16_t flags
General object flags, mostly related to garbage collection.
Definition: object.h:43
uint16_t type
Tag indicating core type.
Definition: object.h:42
struct KrkObj * next
Invasive linked list of all objects in the VM.
Definition: object.h:45
Immutable sequence of Unicode codepoints.
Definition: object.h:93
char * chars
UTF8 canonical data.
Definition: object.h:97
void * codes
Codepoint data.
Definition: object.h:98
size_t length
String length in bytes.
Definition: object.h:95
One (key,value) pair in a table.
Definition: table.h:20
Simple hash table of arbitrary keys to values.
Definition: table.h:28
int krk_tableDeleteExact(KrkTable *table, KrkValue key)
Remove a key from a hash table, with identity lookup.
Definition: table.c:249
KrkTableEntry * entries
Definition: table.h:32
void krk_freeTable(KrkTable *table)
Release resources associated with a hash table.
Definition: table.c:41
size_t used
Definition: table.h:31
Execution state of a VM thread.
Definition: vm.h:152
KrkValue currentException
Definition: vm.h:164
struct KrkThreadState * next
Definition: vm.h:153
KrkValue * stack
Definition: vm.h:158
KrkUpvalue * openUpvalues
Definition: vm.h:160
KrkValue scratchSpace[KRK_THREAD_SCRATCH_SIZE]
Definition: vm.h:169
KrkInstance * module
Definition: vm.h:163
Immutable sequence of arbitrary values.
Definition: object.h:323
KrkValueArray values
Stores the length, capacity, and actual values of the tuple.
Definition: object.h:325
Storage for values referenced from nested functions.
Definition: object.h:115
struct KrkUpvalue * next
Invasive linked list pointer to next upvalue.
Definition: object.h:119
Flexible vector of stack references.
Definition: value.h:75
KrkValue * values
Definition: value.h:78
void krk_freeValueArray(KrkValueArray *array)
Release relesources used by a value array.
Definition: value.c:28
size_t count
Definition: value.h:77
Stack reference or primative value.
Implementation of a generic hash table.
Utilities for creating native bindings.
Core API for the bytecode virtual machine.
krk_threadLocal KrkThreadState krk_currentThread
Thread-local VM state.
#define vm
Convenience macro for namespacing.
Definition: vm.h:257
#define KRK_THREAD_SCRATCH_SIZE
Extra space for each thread to store a set of working values safe from the GC.
Definition: vm.h:30