1 /* gc_heap.h -- heap packing, run-time image generation    */
2 /* Copyright (c) 2016 Chris Walsh.  All rights reserved.   */
3 /* BSD-style license: http://synthcode.com/license.txt     */
4 
5 #include "chibi/gc_heap.h"
6 
7 #if SEXP_USE_IMAGE_LOADING
8 
9 #define ERR_STR_SIZE 256
10 static char gc_heap_err_str[ERR_STR_SIZE];
11 
12 
sexp_gc_allocated_bytes(sexp ctx,sexp * types,size_t types_cnt,sexp x)13 static sexp_uint_t sexp_gc_allocated_bytes (sexp ctx, sexp *types, size_t types_cnt, sexp x) {
14   sexp_uint_t res = 0;
15   if (!sexp_pointerp(x) || (sexp_pointer_tag(x) >= types_cnt)) {
16     res = 1;
17   } else {
18     res = sexp_type_size_of_object(types[sexp_pointer_tag(x)], x) + SEXP_GC_PAD;
19   }
20   return sexp_heap_align(res);
21 }
22 
23 
sexp_gc_heap_walk(sexp ctx,sexp_heap h,sexp * t,size_t t_cnt,void * user,sexp (* heap_callback)(sexp ctx,sexp_heap h,void * user),sexp (* free_callback)(sexp ctx,sexp_free_list f,void * user),sexp (* sexp_callback)(sexp ctx,sexp s,void * user))24 sexp sexp_gc_heap_walk(sexp ctx,
25                        sexp_heap h,  /* normally set to sexp_context_heap(ctx) */
26                        sexp *t,      /* normally set to sexp_context_types(ctx) */
27                        size_t t_cnt, /* normally set to sexp_context_num_types(ctx) */
28                        void *user,
29                        sexp (*heap_callback)(sexp ctx, sexp_heap h, void *user),
30                        sexp (*free_callback)(sexp ctx, sexp_free_list f, void *user),
31                        sexp (*sexp_callback)(sexp ctx, sexp s, void *user))
32 {
33   sexp res = SEXP_FALSE;
34 
35   size_t size = 0;
36   while (h) {
37     sexp p = sexp_heap_first_block(h);
38     sexp_free_list q = h->free_list;
39     sexp end = sexp_heap_end(h);
40 
41     while (p < end) {
42       /* find the preceding and succeeding free list pointers */
43       sexp_free_list r = q->next;
44       while (r && ((unsigned char*)r < (unsigned char*)p)) {
45         q = r;
46         r = r->next;
47       }
48 
49       if ( (unsigned char*)r == (unsigned char*)p ) {
50         if (free_callback && (res = free_callback(ctx, r, user)) != SEXP_TRUE) {
51           return res; }
52         size = r ? r->size : 0;
53       } else {
54         if (sexp_callback && (res = sexp_callback(ctx, p, user)) != SEXP_TRUE) {
55           return res; }
56         size = sexp_gc_allocated_bytes(ctx, t, t_cnt, p);
57         if (size == 0) {
58           snprintf(gc_heap_err_str, ERR_STR_SIZE, "Heap element with a zero size detected");
59           goto done;
60         }
61       }
62       p = (sexp)(((unsigned char*)p) + size);
63     }
64 
65     if (heap_callback && (res = heap_callback(ctx, h, user)) != SEXP_TRUE) {
66       return res; }
67     h = h->next;
68   }
69   res = SEXP_TRUE;
70 done:
71   if (res != SEXP_TRUE) res = sexp_user_exception(ctx, NULL, gc_heap_err_str, SEXP_NULL);
72   return res;
73 }
74 
75 
76 struct sexp_remap {
77   sexp srcp;
78   sexp dstp;
79 };
80 
81 struct sexp_remap_state {
82   size_t index, heaps_count, sexps_count, sexps_size;
83   sexp p, end, ctx_src, ctx_dst;
84   sexp_heap heap;
85   int mode;
86   struct sexp_remap *remap;
87 };
88 
89 
heap_callback_count(sexp ctx,sexp_heap h,void * user)90 static sexp heap_callback_count(sexp ctx, sexp_heap h, void *user) {
91   struct sexp_remap_state* state = user;
92   state->heaps_count += 1;
93   return SEXP_TRUE;
94 }
95 
sexp_callback_count(sexp ctx,sexp s,void * user)96 static sexp sexp_callback_count(sexp ctx, sexp s, void *user) {
97   struct sexp_remap_state* state = user;
98   size_t size = sexp_gc_allocated_bytes(ctx, sexp_context_types(ctx),
99                                         sexp_context_num_types(ctx), s);
100   state->sexps_count += 1;
101   state->sexps_size  += size;
102   return SEXP_TRUE;
103 }
104 
heap_callback_remap(sexp ctx,sexp_heap h,void * user)105 static sexp heap_callback_remap(sexp ctx, sexp_heap h, void *user) {
106   return SEXP_NULL;
107 }
108 
sexp_callback_remap(sexp ctx,sexp s,void * user)109 static sexp sexp_callback_remap(sexp ctx, sexp s, void *user) {
110   struct sexp_remap_state* state = user;
111   size_t size = sexp_gc_allocated_bytes(ctx, sexp_context_types(ctx),
112                                         sexp_context_num_types(ctx), s);
113   if (state->p >= state->end) {
114     snprintf(gc_heap_err_str, ERR_STR_SIZE, "callback_remap i=%zu p>end internal error", state->index);
115     return SEXP_FALSE; }
116   memcpy(state->p, s, size);
117 
118   state->remap[state->index].srcp = s;
119   state->remap[state->index].dstp = state->p;
120   if (ctx == s) state->ctx_dst = state->p;
121 
122   state->p = (sexp)(((unsigned char*)state->p) + size);
123   state->index += 1;
124 
125   return SEXP_TRUE;
126 }
127 
128 
129 
130 /* Return a destination (remapped) pointer for a given source pointer */
sexp_gc_heap_pack_src_to_dst(void * adata,sexp srcp)131 static sexp sexp_gc_heap_pack_src_to_dst(void* adata, sexp srcp) {
132 
133   struct sexp_remap_state* state = adata;
134   sexp_sint_t imin = 0;
135   sexp_sint_t imax = state->sexps_count - 1;
136 
137   while (imin <= imax) {
138     sexp_sint_t imid = ((imax - imin) / 2) + imin;
139     sexp midp = state->remap[imid].srcp;
140     if (midp == srcp) {
141       return state->remap[imid].dstp;
142     } else if (midp < srcp) {
143       imin = imid + 1;
144     } else {
145       imax = imid - 1;
146     }
147   }
148   snprintf(gc_heap_err_str, ERR_STR_SIZE, "Source SEXP not found in src->dst mapping");
149   return SEXP_FALSE;
150 }
151 
152 
sexp_adjust_fields(sexp dstp,sexp * types,sexp (* adjust_fn)(void *,sexp),void * adata)153 static sexp sexp_adjust_fields(sexp dstp, sexp* types, sexp (* adjust_fn)(void *, sexp), void *adata) {
154   sexp_tag_t tag           = sexp_pointer_tag(dstp);
155   sexp       type_spec     = types[tag];
156   size_t     type_sexp_cnt = sexp_type_num_slots_of_object(type_spec, dstp);
157   sexp*      vec           = (sexp*)((unsigned char*)dstp + sexp_type_field_base(type_spec));
158   int        i;
159 
160   for (i = 0; i < type_sexp_cnt; i++) {
161     sexp src = vec[i];
162     sexp dst = src;
163     if (src && sexp_pointerp(src)) {
164       dst = adjust_fn(adata, src);
165       if (!sexp_pointerp(dst)) {
166         size_t sz = strlen(gc_heap_err_str);
167         snprintf(gc_heap_err_str + sz, ERR_STR_SIZE - sz, " from adjust fields, tag=%u i=%d", tag, i);
168         return SEXP_FALSE; }
169     }
170     vec[i] = dst;
171   }
172   return SEXP_TRUE;
173 }
174 
175 
sexp_adjust_bytecode(sexp dstp,sexp (* adjust_fn)(void *,sexp),void * adata)176 static sexp sexp_adjust_bytecode(sexp dstp, sexp (*adjust_fn)(void *, sexp), void *adata) {
177   sexp res = SEXP_FALSE;
178   sexp   src, dst;
179   sexp*  vec;
180   int    i;
181 
182   for (i=0; i < sexp_bytecode_length(dstp); ) {
183     switch (sexp_bytecode_data(dstp)[i++]) {
184     case SEXP_OP_FCALL0:      case SEXP_OP_FCALL1:
185     case SEXP_OP_FCALL2:      case SEXP_OP_FCALL3:
186     case SEXP_OP_FCALL4:      case SEXP_OP_CALL:
187     case SEXP_OP_TAIL_CALL:   case SEXP_OP_PUSH:
188     case SEXP_OP_GLOBAL_REF:  case SEXP_OP_GLOBAL_KNOWN_REF:
189 #if SEXP_USE_GREEN_THREADS
190     case SEXP_OP_PARAMETER_REF:
191 #endif
192 #if SEXP_USE_EXTENDED_FCALL
193     case SEXP_OP_FCALLN:
194 #endif
195       vec = (sexp*)(&(sexp_bytecode_data(dstp)[i]));
196       src = vec[0];
197       if (src && sexp_pointerp(src)) {
198         dst = adjust_fn(adata, src);
199         if (!sexp_pointerp(dst)) {
200           size_t sz = strlen(gc_heap_err_str);
201           snprintf(gc_heap_err_str + sz, ERR_STR_SIZE - sz, " from adjust bytecode, FCALLN");
202           goto done; }
203         vec[0] = dst;
204       }
205       /* ... FALLTHROUGH ... */
206     case SEXP_OP_JUMP:        case SEXP_OP_JUMP_UNLESS:
207     case SEXP_OP_STACK_REF:   case SEXP_OP_CLOSURE_REF:
208     case SEXP_OP_LOCAL_REF:   case SEXP_OP_LOCAL_SET:
209     case SEXP_OP_TYPEP:
210 #if SEXP_USE_RESERVE_OPCODE
211     case SEXP_OP_RESERVE:
212 #endif
213       i += sizeof(sexp); break;
214     case SEXP_OP_MAKE: case SEXP_OP_SLOT_REF: case SEXP_OP_SLOT_SET:
215       i += 2*sizeof(sexp); break;
216     case SEXP_OP_MAKE_PROCEDURE:
217       vec = (sexp*)(&(sexp_bytecode_data(dstp)[i]));
218       src = vec[2];
219       if (src && sexp_pointerp(src)) {
220         dst = adjust_fn(adata, src);
221         if (!sexp_pointerp(dst)) {
222           size_t sz = strlen(gc_heap_err_str);
223           snprintf(gc_heap_err_str + sz, ERR_STR_SIZE - sz, " from adjust bytecode, PROCEDURE");
224           goto done; }
225         vec[2] = dst;
226       }
227       i += 3*sizeof(sexp); break;
228     }
229   }
230   res = SEXP_TRUE;
231 done:
232   return res;
233 }
234 
sexp_gc_heap_pack_adjust(sexp dstp,sexp * types,struct sexp_remap_state * state)235 static sexp sexp_gc_heap_pack_adjust(sexp dstp, sexp* types, struct sexp_remap_state* state) {
236   sexp res = SEXP_FALSE;
237   /* Adjust internal types which contain fields of sexp pointer(s)
238      within in the heap */
239   if ((res = sexp_adjust_fields(dstp, types, sexp_gc_heap_pack_src_to_dst, state)) != SEXP_TRUE) {
240     goto done; }
241 
242   /* Other adjustments - context heap pointer, bytecode pointers */
243   if (sexp_contextp(dstp)) {
244     sexp_context_heap(dstp) = state->heap;
245   } else if (sexp_bytecodep(dstp)) {
246     if ((res = sexp_adjust_bytecode(dstp, sexp_gc_heap_pack_src_to_dst, state)) != SEXP_TRUE) {
247       goto done; }
248   }
249   res = SEXP_TRUE;
250 done:
251   return res;
252 }
253 
254 
sexp_gc_packed_heap_make(size_t packed_size,size_t free_size)255 static sexp_heap sexp_gc_packed_heap_make(size_t packed_size, size_t free_size) {
256   if (free_size > 0 && free_size < 2*sexp_free_chunk_size) {
257     free_size = 2*sexp_free_chunk_size;
258   }
259   free_size = sexp_heap_align(free_size);
260   size_t req_size = packed_size + free_size + sexp_free_chunk_size + 128;
261   sexp_heap heap = sexp_make_heap(sexp_heap_align(req_size), 0, 0);
262   if (!heap) {
263     snprintf(gc_heap_err_str, ERR_STR_SIZE, "Could not allocate memory for heap");
264     return NULL;
265   }
266   sexp base = sexp_heap_first_block(heap);
267   size_t pad = (unsigned char *)base - (unsigned char *)heap->data;
268   heap->size = packed_size + free_size + pad;
269   heap->free_list->size = 0;
270   if (free_size == 0) {
271     heap->free_list->next = NULL;
272   } else {
273     heap->free_list->next = (sexp_free_list)((unsigned char *)base + packed_size);
274     heap->free_list->next->next = NULL;
275     heap->free_list->next->size = free_size;
276   }
277   return heap;
278 }
279 
heaps_compar(const void * v1,const void * v2)280 static int heaps_compar(const void* v1, const void* v2) {
281   sexp_heap h1 = *((sexp_heap*)v1);
282   sexp_heap h2 = *((sexp_heap*)v2);
283   return
284     (h1 < h2) ? -1 :
285     (h1 > h2) ?  1 : 0;
286 }
287 
288 /* Pack the heap.  Return a new context with a unified, packed heap.  No change to original context. */
sexp_gc_heap_pack(sexp ctx_src,sexp_uint_t heap_free_size)289 sexp sexp_gc_heap_pack(sexp ctx_src, sexp_uint_t heap_free_size) {
290 
291   sexp res = NULL;
292   sexp_gc(ctx_src, NULL);
293   sexp_heap* heaps = NULL;
294   int i = 0;
295 
296   /* 1.  Collect statistics - sexp count, size, heap count */
297 
298   struct sexp_remap_state state;
299   memset(&state, 0, sizeof(struct sexp_remap_state));
300   state.ctx_src = ctx_src;
301   if ((res = sexp_gc_heap_walk(ctx_src, sexp_context_heap(ctx_src),
302                                sexp_context_types(ctx_src), sexp_context_num_types(ctx_src),
303                                &state, heap_callback_count, NULL, sexp_callback_count)) != SEXP_TRUE) {
304     goto done; }
305 
306   /* 2.  Make a new heap of the correct size to hold the sexps from the old heap. */
307 
308   state.heap = sexp_gc_packed_heap_make(state.sexps_size, heap_free_size);
309   if (!state.heap) {
310     res = sexp_global(ctx_src, SEXP_G_OOM_ERROR);
311     goto done; }
312 
313   /* 3.  Create a list of heaps sorted by increasing memory address, for srcp search lookup */
314 
315   heaps = malloc(sizeof(sexp_heap) * state.heaps_count);
316   if (!heaps) {
317     res = sexp_global(ctx_src, SEXP_G_OOM_ERROR);
318     goto done; }
319   sexp_heap h = sexp_context_heap(ctx_src);
320   for (i = 0; h; i++, h=h->next) { heaps[i] = h; }
321   qsort(heaps, state.heaps_count, sizeof(sexp_heap), heaps_compar);
322 
323   /* 4.  Pack the sexps into the new heap */
324 
325   state.p     = sexp_heap_first_block(state.heap);
326   state.end   = sexp_heap_end(state.heap);
327   state.index = 0;
328   state.remap = malloc(sizeof(struct sexp_remap) * state.sexps_count);
329   if (!state.remap) {
330     res = sexp_global(ctx_src, SEXP_G_OOM_ERROR);
331     goto done; }
332 
333   for (i = 0; i < state.heaps_count; i++) {
334     res = sexp_gc_heap_walk(ctx_src, heaps[i],
335                             sexp_context_types(ctx_src), sexp_context_num_types(ctx_src),
336                             &state, heap_callback_remap, NULL, sexp_callback_remap);
337     if (!(res == SEXP_TRUE || res == SEXP_NULL)) {
338       size_t sz = strlen(gc_heap_err_str);
339       snprintf(gc_heap_err_str + sz, ERR_STR_SIZE - sz, "; remap heap %d %p walk heap_pack", i, heaps[i]);
340       goto done; }
341   }
342 
343   /* 5.  Adjust sexp pointers to new locations inside the new heap */
344 
345   sexp* types = sexp_context_types(state.ctx_src);
346   int idx;
347   for (idx = 0; idx < state.sexps_count; idx++) {
348     sexp dstp = state.remap[idx].dstp;
349     res = sexp_gc_heap_pack_adjust(dstp, types, &state);
350     if (res != SEXP_TRUE) {
351       size_t sz = strlen(gc_heap_err_str);
352       snprintf(gc_heap_err_str + sz, ERR_STR_SIZE - sz, "; src->dst idx=%d heap_pack", idx);
353       goto done; }
354   }
355 
356   res = SEXP_TRUE;
357 
358 done:
359   /* 6. Clean up. */
360 
361   if (state.heap && res != SEXP_TRUE) { sexp_free_heap(state.heap); }
362   if (state.remap) { free(state.remap); }
363   if (heaps) { free(heaps); }
364 
365   return (res == SEXP_TRUE) ? state.ctx_dst : res;
366 }
367 
368 
369 #define SEXP_IMAGE_MAGIC "\a\achibi\n\0"
370 #define SEXP_IMAGE_MAJOR_VERSION 1
371 #define SEXP_IMAGE_MINOR_VERSION 1
372 
373 struct sexp_image_header_t {
374   char magic[8];
375   short major, minor;
376   sexp_abi_identifier_t abi;
377   sexp_uint_t size;
378   sexp base;
379   sexp context;
380 };
381 
382 
sexp_save_image(sexp ctx_in,const char * filename)383 sexp sexp_save_image (sexp ctx_in, const char* filename) {
384   sexp_heap heap = NULL;
385   sexp res = NULL;
386   FILE *fp = fopen(filename, "wb");
387   if (!fp) {
388     snprintf(gc_heap_err_str, ERR_STR_SIZE, "Could not open image file for writing: %s", filename);
389     goto done;
390   }
391 
392   /* Save ONLY packed, active SEXPs.  No free list structures or padding. */
393   sexp ctx_out = sexp_gc_heap_pack(ctx_in, 0);
394   if (!ctx_out || !sexp_contextp(ctx_out)) {
395     goto done;
396   }
397   heap = sexp_context_heap(ctx_out);
398   sexp   base = sexp_heap_first_block(heap);
399   size_t pad  = (size_t)((unsigned char *)base - (unsigned char *)heap->data);
400   size_t size = heap->size - pad;
401 
402   struct sexp_image_header_t header;
403   memcpy(&header.magic, SEXP_IMAGE_MAGIC, sizeof(header.magic));
404   memcpy(&header.abi, SEXP_ABI_IDENTIFIER, sizeof(header.abi));
405   header.major   = SEXP_IMAGE_MAJOR_VERSION;
406   header.minor   = SEXP_IMAGE_MINOR_VERSION;
407   header.size    = size;
408   header.base    = base;
409   header.context = ctx_out;
410 
411   if (! (fwrite(&header, sizeof(header), 1, fp) == 1 &&
412          fwrite(base, size, 1, fp) == 1)) {
413     snprintf(gc_heap_err_str, ERR_STR_SIZE, "Error writing image file: %s", filename);
414     goto done;
415   }
416 
417   res = SEXP_TRUE;
418 done:
419   if (fp) fclose(fp);
420   if (heap) sexp_free_heap(heap);
421   if (res != SEXP_TRUE) res = sexp_user_exception(ctx_in, NULL, gc_heap_err_str, SEXP_NULL);
422   return res;
423 }
424 
425 
426 
427 #if SEXP_USE_DL
428 
429 #ifdef __APPLE__
430 #define SEXP_RTLD_DEFAULT RTLD_SELF
431 #else
432 #define SEXP_RTLD_DEFAULT RTLD_DEFAULT
433 #endif
434 
435 struct load_image_state {
436   sexp_sint_t offset;
437   sexp_heap heap;
438   sexp *types;
439   size_t types_cnt;
440 };
441 
442 /* Return a destination (remapped) pointer for a given source pointer */
load_image_src_to_dst(void * adata,sexp srcp)443 static sexp load_image_src_to_dst(void* adata, sexp srcp) {
444   struct load_image_state* state = adata;
445   return (sexp)((unsigned char *)srcp + state->offset);
446 }
447 
448 
load_image_callback_p1(sexp ctx,sexp p,void * user)449 static sexp load_image_callback_p1 (sexp ctx, sexp p, void *user) {
450   sexp res = NULL;
451   struct load_image_state* state = user;
452 
453   if ((res = sexp_adjust_fields(p, state->types, load_image_src_to_dst, state)) != SEXP_TRUE) {
454     goto done; }
455 
456   if (sexp_contextp(p)) {
457 #if SEXP_USE_GREEN_THREADS
458     sexp_context_ip(p) += state->offset;
459 #endif
460     sexp_context_last_fp(p) += state->offset;
461     sexp_stack_top(sexp_context_stack(p)) = 0;
462     sexp_context_saves(p) = NULL;
463     sexp_context_heap(p) = state->heap;
464 
465   } else if (sexp_bytecodep(p)) {
466     if ((res = sexp_adjust_bytecode(p, load_image_src_to_dst, state)) != SEXP_TRUE) {
467       goto done; }
468 
469   } else if (sexp_portp(p) && sexp_port_stream(p)) {
470     sexp_port_stream(p) = 0;
471     sexp_port_openp(p) = 0;
472     sexp_freep(p) = 0;
473 
474   } else if (sexp_dlp(p)) {
475     sexp_dl_handle(p) = NULL;
476 
477   }
478   res = SEXP_TRUE;
479 done:
480   return res;
481 }
482 
483 #ifdef _WIN32
load_image_fn(sexp ctx,sexp dl,sexp name)484 static void* load_image_fn(sexp ctx, sexp dl, sexp name) {
485   snprintf(gc_heap_err_str, ERR_STR_SIZE,
486            "load_image_fn: Needed to be ported to Win32");
487   return NULL;
488 }
489 #else
load_image_fn(sexp ctx,sexp dl,sexp name)490 static void* load_image_fn(sexp ctx, sexp dl, sexp name) {
491   sexp ls;
492   void *fn = NULL;
493   char *file_name, *rel_name=NULL, *new_file_name;
494   char *handle_name = "<static>";
495   char *symbol_name = sexp_string_data(name);
496   if (dl && sexp_dlp(dl)) {
497     if (!sexp_dl_handle(dl)) {
498       /* try exact file, then the search path */
499       file_name = sexp_string_data(sexp_dl_file(dl));
500       sexp_dl_handle(dl) = dlopen(file_name, RTLD_LAZY);
501       if (!sexp_dl_handle(dl)) {
502         for (ls = sexp_global(ctx, SEXP_G_MODULE_PATH); sexp_pairp(ls); ls=sexp_cdr(ls)) {
503           if (strstr(file_name, sexp_string_data(sexp_car(ls))) == file_name) {
504             rel_name = file_name + sexp_string_size(sexp_car(ls));
505             while (*rel_name == '/')
506               ++rel_name;
507             new_file_name = sexp_find_module_file_raw(ctx, rel_name);
508             if (new_file_name) {
509               sexp_dl_handle(dl) = dlopen(new_file_name, RTLD_LAZY);
510               free(new_file_name);
511               if (sexp_dl_handle(dl))
512                 break;
513             }
514           }
515         }
516         if (!sexp_dl_handle(dl)) {
517           handle_name = sexp_string_data(sexp_dl_file(dl));
518           snprintf(gc_heap_err_str, ERR_STR_SIZE, "dlopen failure: %s",
519                    handle_name);
520           return NULL;
521         }
522       }
523     }
524     fn = dlsym(sexp_dl_handle(dl), symbol_name);
525   } else {
526     fn = dlsym(SEXP_RTLD_DEFAULT, symbol_name);
527   }
528   if (!fn) {
529     snprintf(gc_heap_err_str, ERR_STR_SIZE,
530              "dynamic function lookup failure: %s %s",
531              handle_name, symbol_name);
532   }
533   return fn;
534 }
535 #endif
536 
load_image_callback_p2(sexp ctx,sexp dstp,void * user)537 static sexp load_image_callback_p2 (sexp ctx, sexp dstp, void *user) {
538   sexp res = NULL;
539   sexp name = NULL;
540   void *fn = NULL;
541 
542   if (sexp_opcodep(dstp) && sexp_opcode_func(dstp)) {
543     if (sexp_opcode_data2(dstp) && sexp_stringp(sexp_opcode_data2(dstp))) {
544       name = sexp_opcode_data2(dstp);
545     } else {
546       name = sexp_opcode_name(dstp);
547     }
548     if (!name) {
549       snprintf(gc_heap_err_str, ERR_STR_SIZE, "opcode func field missing function name");
550       return SEXP_FALSE;
551     }
552 
553     fn = load_image_fn(ctx, sexp_opcode_dl(dstp), name);
554     if (!fn) {
555       return SEXP_FALSE;
556     }
557     sexp_opcode_func(dstp) = fn;
558 
559   } else if (sexp_typep(dstp) && sexp_type_finalize(dstp)) {
560     name = sexp_type_finalize_name(dstp);
561     if (!name) {
562       snprintf(gc_heap_err_str, ERR_STR_SIZE, "type finalize field missing function name");
563       return SEXP_FALSE;
564     }
565     fn = load_image_fn(ctx, sexp_type_dl(dstp), name);
566     if (!fn) {
567       return SEXP_FALSE;
568     }
569     sexp_type_finalize(dstp) = fn;
570   }
571   res = SEXP_TRUE;
572   return res;
573 }
574 
575 
load_image_header(FILE * fp,struct sexp_image_header_t * header)576 static int load_image_header(FILE *fp, struct sexp_image_header_t* header) {
577   if (!fp || !header) { return 0; }
578 
579   if (fread(header, sizeof(struct sexp_image_header_t), 1, fp) != 1) {
580     snprintf(gc_heap_err_str, ERR_STR_SIZE, "couldn't read image header");
581     return 0;
582   }
583   if (memcmp(header->magic, SEXP_IMAGE_MAGIC, sizeof(header->magic)) != 0) {
584     snprintf(gc_heap_err_str, ERR_STR_SIZE, "invalid image file magic %s\n", header->magic);
585     return 0;
586   } else if (header->major != SEXP_IMAGE_MAJOR_VERSION
587              || header->major < SEXP_IMAGE_MINOR_VERSION) {
588     snprintf(gc_heap_err_str, ERR_STR_SIZE, "unsupported image version: %d.%d\n",
589              header->major, header->minor);
590     return 0;
591   } else if (!sexp_abi_compatible(NULL, header->abi, SEXP_ABI_IDENTIFIER)) {
592     snprintf(gc_heap_err_str, ERR_STR_SIZE, "unsupported ABI: %s (expected %s)\n",
593              header->abi, SEXP_ABI_IDENTIFIER);
594     return 0;
595   }
596   return 1;
597 }
598 
sexp_load_image_err()599 char* sexp_load_image_err() {
600   gc_heap_err_str[ERR_STR_SIZE-1] = 0;
601   return gc_heap_err_str;
602 }
603 
604 static const char* all_paths[] = {sexp_default_module_path, sexp_default_user_module_path};
605 
sexp_load_image(const char * filename,off_t offset,sexp_uint_t heap_free_size,sexp_uint_t heap_max_size)606 sexp sexp_load_image (const char* filename, off_t offset, sexp_uint_t heap_free_size, sexp_uint_t heap_max_size) {
607   struct load_image_state state;
608   struct sexp_image_header_t header;
609   const char *mod_path, *colon, *end;
610   char path[512];
611   FILE *fp;
612   int i, len;
613   sexp res = NULL, ctx = NULL, base, *ctx_globals, *ctx_types;
614 
615   gc_heap_err_str[0] = 0;
616 
617   memset(&state, 0, sizeof(struct load_image_state));
618 
619   fp = fopen(filename, "rb");
620   /* fallback to the default search path (can't use sexp_find_module_file */
621   /* since there's no context yet) */
622   for (i=0; !fp && i<sizeof(all_paths)/sizeof(all_paths[0]); ++i) {
623     for (mod_path=all_paths[i]; *mod_path; mod_path=colon+1) {
624       colon = strchr(mod_path, ':');
625       end = colon ? colon : mod_path + strlen(mod_path);
626       snprintf(path, sizeof(path), "%s", mod_path);
627       if (end[-1] != '/') path[end-mod_path] = '/';
628       len = (end-mod_path) + (end[-1] == '/' ? 0 : 1);
629       snprintf(path + len, sizeof(path) - len, "%s", filename);
630       fp = fopen(path, "rb");
631       if (fp || !colon) break;
632     }
633   }
634   if (!fp) {
635     snprintf(gc_heap_err_str, ERR_STR_SIZE, "couldn't open image file for reading: %s\n", filename);
636     goto done;
637   }
638   if (offset > 0 && fseek(fp, offset, SEEK_SET) < 0) {
639     snprintf(gc_heap_err_str, ERR_STR_SIZE, "couldn't seek to image offset: %s -> %"SEXP_PRIdOFF": %s\n", filename, offset, strerror(errno));
640     goto done;
641   }
642 
643   if (!load_image_header(fp, &header)) { goto done; }
644 
645   state.heap = sexp_gc_packed_heap_make(header.size, heap_free_size);
646   if (!state.heap) {
647     snprintf(gc_heap_err_str, ERR_STR_SIZE, "couldn't malloc heap\n");
648     goto done;
649   }
650   base = sexp_heap_first_block(state.heap);
651 
652   if (fread(base, 1, header.size, fp) != header.size) {
653     snprintf(gc_heap_err_str, ERR_STR_SIZE, "error reading image\n");
654     goto done;
655   }
656 
657   /* Adjust pointers in loaded packed heap. */
658 
659   state.offset = (sexp_sint_t)((sexp_sint_t)base - (sexp_sint_t)header.base);
660   ctx = (sexp)((unsigned char *)header.context + state.offset);
661   sexp_context_heap(ctx) = state.heap;
662 
663   /* Type information (specifically, how big types are) is stored as sexps in the
664      heap.  This information is needed to sucessfully walk an arbitrary heap.  A
665      copy of the type array pointers with correct offsets is applied is created outside
666      of the new heap to be used with the pointer adjustment process.
667   */
668   ctx_globals = sexp_vector_data((sexp)((unsigned char*)sexp_context_globals(ctx) + state.offset));
669   ctx_types   = sexp_vector_data((sexp)((unsigned char*)(ctx_globals[SEXP_G_TYPES]) + state.offset));
670   state.types_cnt   = sexp_unbox_fixnum(ctx_globals[SEXP_G_NUM_TYPES]);
671   state.types = malloc(sizeof(sexp) * state.types_cnt);
672   if (!state.types) goto done;
673   for (i = 0; i < state.types_cnt; i++) {
674     state.types[i] = (sexp)((unsigned char *)ctx_types[i] + state.offset);
675   }
676 
677   if (sexp_gc_heap_walk(ctx, sexp_context_heap(ctx), state.types, state.types_cnt,
678                         &state, NULL, NULL, load_image_callback_p1) != SEXP_TRUE)
679     goto done;
680 
681   /* Second pass to fix code references */
682   if (sexp_gc_heap_walk(ctx, sexp_context_heap(ctx), state.types, state.types_cnt,
683                         &state, NULL, NULL, load_image_callback_p2) != SEXP_TRUE)
684     goto done;
685 
686   if (heap_max_size > SEXP_INITIAL_HEAP_SIZE) {
687     sexp_context_heap(ctx)->max_size = heap_max_size;
688   }
689 
690   res = ctx;
691 done:
692   if (fp) fclose(fp);
693   if (state.heap && !ctx) free(state.heap);
694   if (state.types) free(state.types);
695   return res;
696 }
697 
698 #else
699 
sexp_load_image(const char * filename,sexp_uint_t heap_free_size,sexp_uint_t heap_max_size)700 sexp sexp_load_image (const char* filename, sexp_uint_t heap_free_size, sexp_uint_t heap_max_size) {
701   return NULL;
702 }
703 
704 #endif
705 
706 
707 
708 
709 /****************** Debugging ************************/
710 
711 /* you can use (chibi heap-stats) without debug enabled */
712 #if SEXP_USE_DEBUG_GC
713 
714 #define SEXP_CORE_TYPES_MAX 255
715 
716 struct sexp_stats_entry {
717   size_t count;
718   size_t size_all;
719   size_t size_min;
720   size_t size_max;
721 };
722 
723 struct sexp_stats {
724   struct sexp_stats_entry sexps[SEXP_CORE_TYPES_MAX+1];
725   struct sexp_stats_entry heaps;
726   struct sexp_stats_entry frees;
727   size_t sexp_count;
728 };
729 
sexp_stats_entry_set(struct sexp_stats_entry * entry,size_t value)730 static void sexp_stats_entry_set(struct sexp_stats_entry *entry, size_t value) {
731   entry->count += 1;
732   entry->size_all += value;
733   if (entry->size_min == 0 || value < entry->size_min) entry->size_min = value;
734   if (value > entry->size_max) entry->size_max = value;
735 }
736 
heap_stats_callback(sexp ctx,sexp_heap h,void * user)737 static sexp heap_stats_callback(sexp ctx, sexp_heap h, void *user) {
738   struct sexp_stats *stats = user;
739   sexp_stats_entry_set(&(stats->heaps), h->size);
740   return SEXP_TRUE;
741 }
742 
free_stats_callback(sexp ctx,sexp_free_list f,void * user)743 static sexp free_stats_callback(sexp ctx, sexp_free_list f, void *user) {
744   struct sexp_stats *stats = user;
745   sexp_stats_entry_set(&(stats->frees), f->size);
746   return SEXP_TRUE;
747 }
748 
sexp_stats_callback(sexp ctx,sexp s,void * user)749 static sexp sexp_stats_callback(sexp ctx, sexp s, void *user) {
750   struct sexp_stats *stats = user;
751   int tag = sexp_pointer_tag(s);
752   size_t size = sexp_gc_allocated_bytes(ctx, sexp_context_types(ctx),
753                                         sexp_context_num_types(ctx), s);
754   if (tag > SEXP_CORE_TYPES_MAX) tag = SEXP_CORE_TYPES_MAX;
755   sexp_stats_entry_set(&(stats->sexps[tag]), size);
756   stats->sexp_count += 1;
757   return SEXP_TRUE;
758 }
759 
sexp_gc_heap_stats_print(sexp ctx)760 void sexp_gc_heap_stats_print(sexp ctx)
761 {
762   if (!ctx || !sexp_contextp(ctx)) return;
763 
764   struct sexp_stats stats;
765   memset(&stats, 0, sizeof(struct sexp_stats));
766   sexp_gc_heap_walk(ctx, sexp_context_heap(ctx), sexp_context_types(ctx), sexp_context_num_types(ctx),
767                     &stats, heap_stats_callback, free_stats_callback, sexp_stats_callback);
768 
769   printf("Heap Stats\n    %6zu %7zu\n",
770          stats.heaps.count, stats.heaps.size_all);
771   printf("Free Stats\n    %6zu %7zu %5zu %5zu\n",
772          stats.frees.count, stats.frees.size_all, stats.frees.size_min, stats.frees.size_max);
773   printf("Sexp Stats\n");
774   size_t total_count = 0;
775   size_t total_size = 0;
776   int i;
777   for (i = 0; i <= SEXP_CORE_TYPES_MAX; i++) {
778     if (stats.sexps[i].count == 0) continue;
779     printf("%3d %6zu %7zu %5zu %5zu\n", i,
780            stats.sexps[i].count, stats.sexps[i].size_all, stats.sexps[i].size_min, stats.sexps[i].size_max);
781     total_count += stats.sexps[i].count;
782     total_size  += stats.sexps[i].size_all;
783   }
784   printf(" ========================================\n");
785   printf("    %6zu %7zu\n", total_count, total_size);
786 }
787 #endif
788 
789 #endif  /* SEXP_USE_IMAGE_LOADING */
790