/**************************************************************************/ /* */ /* OCaml */ /* */ /* Mark Shinwell and Leo White, Jane Street Europe */ /* */ /* Copyright 2013--2016, Jane Street Group, LLC */ /* */ /* All rights reserved. This file is distributed under the terms of */ /* the GNU Lesser General Public License version 2.1, with the */ /* special exception on linking described in the file LICENSE. */ /* */ /**************************************************************************/ #define CAML_INTERNALS #include #include #include #include #include #include "caml/alloc.h" #include "caml/backtrace_prim.h" #include "caml/config.h" #include "caml/custom.h" #include "caml/fail.h" #include "caml/gc.h" #include "caml/gc_ctrl.h" #include "caml/intext.h" #include "caml/major_gc.h" #include "caml/memory.h" #include "caml/minor_gc.h" #include "caml/misc.h" #include "caml/mlvalues.h" #include "caml/roots.h" #include "caml/signals.h" #include "caml/stack.h" #include "caml/sys.h" #include "caml/spacetime.h" #ifdef WITH_SPACETIME /* The following structures must match the type definitions in the [Spacetime] module. */ typedef struct { /* (GC header here.) */ value minor_words; value promoted_words; value major_words; value minor_collections; value major_collections; value heap_words; value heap_chunks; value compactions; value top_heap_words; } gc_stats; typedef struct { value profinfo; value num_blocks; value num_words_including_headers; } snapshot_entry; typedef struct { /* (GC header here.) */ snapshot_entry entries[0]; } snapshot_entries; typedef struct { /* (GC header here.) */ value time; value gc_stats; value entries; value words_scanned; value words_scanned_with_profinfo; value total_allocations; } snapshot; typedef struct { uintnat num_blocks; uintnat num_words_including_headers; } raw_snapshot_entry; static value allocate_outside_heap_with_tag(mlsize_t size_in_bytes, tag_t tag) { /* CR-soon mshinwell: this function should live somewhere else */ header_t* block; CAMLassert(size_in_bytes % sizeof(value) == 0); block = caml_stat_alloc(sizeof(header_t) + size_in_bytes); *block = Make_header(size_in_bytes / sizeof(value), tag, Caml_black); return (value) &block[1]; } static value allocate_outside_heap(mlsize_t size_in_bytes) { CAMLassert(size_in_bytes > 0); return allocate_outside_heap_with_tag(size_in_bytes, 0); } static value take_gc_stats(void) { value v_stats; gc_stats* stats; v_stats = allocate_outside_heap(sizeof(gc_stats)); stats = (gc_stats*) v_stats; stats->minor_words = Val_long(Caml_state->stat_minor_words); stats->promoted_words = Val_long(Caml_state->stat_promoted_words); stats->major_words = Val_long(((uintnat) Caml_state->stat_major_words) + ((uintnat) caml_allocated_words)); stats->minor_collections = Val_long(Caml_state->stat_minor_collections); stats->major_collections = Val_long(Caml_state->stat_major_collections); stats->heap_words = Val_long(Caml_state->stat_heap_wsz / sizeof(value)); stats->heap_chunks = Val_long(Caml_state->stat_heap_chunks); stats->compactions = Val_long(Caml_state->stat_compactions); stats->top_heap_words = Val_long(Caml_state->stat_top_heap_wsz / sizeof(value)); return v_stats; } static value get_total_allocations(void) { value v_total_allocations = Val_unit; allocation_point* total = caml_all_allocation_points; while (total != NULL) { value v_total; v_total = allocate_outside_heap_with_tag(3 * sizeof(value), 0); /* [v_total] is of type [Raw_spacetime_lib.total_allocations]. */ Field(v_total, 0) = Val_long(Profinfo_hd(total->profinfo)); Field(v_total, 1) = total->count; Field(v_total, 2) = v_total_allocations; v_total_allocations = v_total; CAMLassert (total->next == Val_unit || (Is_block(total->next) && Tag_val(total->next) == Infix_tag)); if (total->next == Val_unit) { total = NULL; } else { total = (allocation_point*) Hp_val(total->next); } } return v_total_allocations; } static value take_snapshot(double time_override, int use_time_override) { value v_snapshot; snapshot* heap_snapshot; value v_entries; snapshot_entries* entries; char* chunk; value gc_stats; uintnat index; uintnat target_index; value v_time; double time; uintnat profinfo; uintnat num_distinct_profinfos; /* Fixed size buffer to avoid needing a hash table: */ static raw_snapshot_entry* raw_entries = NULL; uintnat words_scanned = 0; uintnat words_scanned_with_profinfo = 0; value v_total_allocations; if (!use_time_override) { time = caml_sys_time_unboxed(Val_unit); } else { time = time_override; } gc_stats = take_gc_stats(); if (raw_entries == NULL) { size_t size = (PROFINFO_MASK + 1) * sizeof(raw_snapshot_entry); raw_entries = caml_stat_alloc(size); memset(raw_entries, '\0', size); } else { size_t size = (PROFINFO_MASK + 1) * sizeof(raw_snapshot_entry); memset(raw_entries, '\0', size); } num_distinct_profinfos = 0; /* CR-someday mshinwell: consider reintroducing minor heap scanning, properly from roots, which would then give a snapshot function that doesn't do a minor GC. Although this may not be that important and potentially not worth the effort (it's quite tricky). */ /* Scan the major heap. */ chunk = caml_heap_start; while (chunk != NULL) { char* hp; char* limit; hp = chunk; limit = chunk + Chunk_size (chunk); while (hp < limit) { header_t hd = Hd_hp (hp); switch (Color_hd(hd)) { case Caml_blue: break; default: if (Wosize_hd(hd) > 0) { /* ignore atoms */ profinfo = Profinfo_hd(hd); words_scanned += Whsize_hd(hd); if (profinfo > 0 && profinfo < PROFINFO_MASK) { words_scanned_with_profinfo += Whsize_hd(hd); CAMLassert (raw_entries[profinfo].num_blocks >= 0); if (raw_entries[profinfo].num_blocks == 0) { num_distinct_profinfos++; } raw_entries[profinfo].num_blocks++; raw_entries[profinfo].num_words_including_headers += Whsize_hd(hd); } } break; } hp += Bhsize_hd (hd); CAMLassert (hp <= limit); } chunk = Chunk_next (chunk); } if (num_distinct_profinfos > 0) { v_entries = allocate_outside_heap( num_distinct_profinfos*sizeof(snapshot_entry)); entries = (snapshot_entries*) v_entries; target_index = 0; for (index = 0; index <= PROFINFO_MASK; index++) { CAMLassert(raw_entries[index].num_blocks >= 0); if (raw_entries[index].num_blocks > 0) { CAMLassert(target_index < num_distinct_profinfos); entries->entries[target_index].profinfo = Val_long(index); entries->entries[target_index].num_blocks = Val_long(raw_entries[index].num_blocks); entries->entries[target_index].num_words_including_headers = Val_long(raw_entries[index].num_words_including_headers); target_index++; } } } else { v_entries = Atom(0); } CAMLassert(sizeof(double) == sizeof(value)); v_time = allocate_outside_heap_with_tag(sizeof(double), Double_tag); Store_double_val(v_time, time); v_snapshot = allocate_outside_heap(sizeof(snapshot)); heap_snapshot = (snapshot*) v_snapshot; v_total_allocations = get_total_allocations(); heap_snapshot->time = v_time; heap_snapshot->gc_stats = gc_stats; heap_snapshot->entries = v_entries; heap_snapshot->words_scanned = Val_long(words_scanned); heap_snapshot->words_scanned_with_profinfo = Val_long(words_scanned_with_profinfo); heap_snapshot->total_allocations = v_total_allocations; return v_snapshot; } void caml_spacetime_save_snapshot (struct channel *chan, double time_override, int use_time_override) { value v_snapshot; value v_total_allocations; snapshot* heap_snapshot; Lock(chan); v_snapshot = take_snapshot(time_override, use_time_override); caml_output_val(chan, Val_long(0), Val_long(0)); caml_extern_allow_out_of_heap = 1; caml_output_val(chan, v_snapshot, Val_long(0)); caml_extern_allow_out_of_heap = 0; Unlock(chan); heap_snapshot = (snapshot*) v_snapshot; caml_stat_free(Hp_val(heap_snapshot->time)); caml_stat_free(Hp_val(heap_snapshot->gc_stats)); if (Wosize_val(heap_snapshot->entries) > 0) { caml_stat_free(Hp_val(heap_snapshot->entries)); } v_total_allocations = heap_snapshot->total_allocations; while (v_total_allocations != Val_unit) { value next = Field(v_total_allocations, 2); caml_stat_free(Hp_val(v_total_allocations)); v_total_allocations = next; } caml_stat_free(Hp_val(v_snapshot)); } CAMLprim value caml_spacetime_take_snapshot(value v_time_opt, value v_channel) { struct channel * channel = Channel(v_channel); double time_override = 0.0; int use_time_override = 0; if (Is_block(v_time_opt)) { time_override = Double_field(Field(v_time_opt, 0), 0); use_time_override = 1; } caml_spacetime_save_snapshot(channel, time_override, use_time_override); return Val_unit; } extern struct custom_operations caml_int64_ops; /* ints.c */ static value allocate_int64_outside_heap(uint64_t i) { value v; v = allocate_outside_heap_with_tag(2 * sizeof(value), Custom_tag); Custom_ops_val(v) = &caml_int64_ops; Int64_val(v) = i; return v; } static value copy_string_outside_heap(char const *s) { int len; mlsize_t wosize, offset_index; value result; len = strlen(s); wosize = (len + sizeof (value)) / sizeof (value); result = allocate_outside_heap_with_tag(wosize * sizeof(value), String_tag); Field (result, wosize - 1) = 0; offset_index = Bsize_wsize (wosize) - 1; Byte (result, offset_index) = offset_index - len; memmove(Bytes_val(result), s, len); return result; } static value allocate_loc_outside_heap(struct caml_loc_info li) { value result; if (li.loc_valid) { result = allocate_outside_heap_with_tag(5 * sizeof(value), 0); Field(result, 0) = Val_bool(li.loc_is_raise); Field(result, 1) = copy_string_outside_heap(li.loc_filename); Field(result, 2) = Val_int(li.loc_lnum); Field(result, 3) = Val_int(li.loc_startchr); Field(result, 4) = Val_int(li.loc_endchr); } else { result = allocate_outside_heap_with_tag(sizeof(value), 1); Field(result, 0) = Val_bool(li.loc_is_raise); } return result; } value caml_spacetime_timestamp(double time_override, int use_time_override) { double time; value v_time; if (!use_time_override) { time = caml_sys_time_unboxed(Val_unit); } else { time = time_override; } v_time = allocate_outside_heap_with_tag(sizeof(double), Double_tag); Store_double_val(v_time, time); return v_time; } value caml_spacetime_frame_table(void) { /* Flatten the frame table into a single associative list. */ value list = Val_long(0); /* the empty list */ uintnat i; if (!caml_debug_info_available()) { return list; } if (caml_frame_descriptors == NULL) { caml_init_frame_descriptors(); } for (i = 0; i <= caml_frame_descriptors_mask; i++) { frame_descr* descr = caml_frame_descriptors[i]; if (descr != NULL) { value location, return_address, pair, new_list_element, location_list; struct caml_loc_info li; debuginfo dbg; if (descr->frame_size != 0xffff) { dbg = caml_debuginfo_extract(descr); if (dbg != NULL) { location_list = Val_unit; while (dbg != NULL) { value list_element; caml_debuginfo_location(dbg, &li); location = allocate_loc_outside_heap(li); list_element = allocate_outside_heap_with_tag(2 * sizeof(value), 0 /* (::) */); Field(list_element, 0) = location; Field(list_element, 1) = location_list; location_list = list_element; dbg = caml_debuginfo_next(dbg); } return_address = allocate_int64_outside_heap(descr->retaddr); pair = allocate_outside_heap_with_tag(2 * sizeof(value), 0); Field(pair, 0) = return_address; Field(pair, 1) = location_list; new_list_element = allocate_outside_heap_with_tag(2 * sizeof(value), 0 /* (::) */); Field(new_list_element, 0) = pair; Field(new_list_element, 1) = list; list = new_list_element; } } } } return list; } static void add_unit_to_shape_table(uint64_t *unit_table, value *list) { /* This function reverses the order of the lists giving the layout of each node; however, spacetime_profiling.ml ensures they are emitted in reverse order, so at the end of it all they're not reversed. */ uint64_t* ptr = unit_table; while (*ptr != (uint64_t) 0) { value new_list_element, pair, function_address, layout; function_address = allocate_int64_outside_heap(*ptr++); layout = Val_long(0); /* the empty list */ while (*ptr != (uint64_t) 0) { int tag; int stored_tag; value part_of_shape; value new_part_list_element; value location; int has_extra_argument = 0; stored_tag = *ptr++; /* CR-soon mshinwell: share with emit.mlp */ switch (stored_tag) { case 1: /* direct call to given location */ tag = 0; has_extra_argument = 1; /* the address of the callee */ break; case 2: /* indirect call to given location */ tag = 1; break; case 3: /* allocation at given location */ tag = 2; break; default: CAMLassert(0); abort(); /* silence compiler warning */ } location = allocate_int64_outside_heap(*ptr++); part_of_shape = allocate_outside_heap_with_tag( sizeof(value) * (has_extra_argument ? 2 : 1), tag); Field(part_of_shape, 0) = location; if (has_extra_argument) { Field(part_of_shape, 1) = allocate_int64_outside_heap(*ptr++); } new_part_list_element = allocate_outside_heap_with_tag(2 * sizeof(value), 0 /* (::) */); Field(new_part_list_element, 0) = part_of_shape; Field(new_part_list_element, 1) = layout; layout = new_part_list_element; } pair = allocate_outside_heap_with_tag(2 * sizeof(value), 0); Field(pair, 0) = function_address; Field(pair, 1) = layout; new_list_element = allocate_outside_heap_with_tag(2 * sizeof(value), 0 /* (::) */); Field(new_list_element, 0) = pair; Field(new_list_element, 1) = *list; *list = new_list_element; ptr++; } } value caml_spacetime_shape_table(void) { value list; uint64_t* unit_table; shape_table *dynamic_table; uint64_t** static_table; /* Flatten the hierarchy of shape tables into a single associative list mapping from function symbols to node layouts. The node layouts are themselves lists. */ list = Val_long(0); /* the empty list */ /* Add static shape tables */ static_table = caml_spacetime_static_shape_tables; while (*static_table != (uint64_t) 0) { unit_table = *static_table++; add_unit_to_shape_table(unit_table, &list); } /* Add dynamic shape tables */ dynamic_table = caml_spacetime_dynamic_shape_tables; while (dynamic_table != NULL) { unit_table = dynamic_table->table; add_unit_to_shape_table(unit_table, &list); dynamic_table = dynamic_table->next; } return list; } #else CAMLprim value caml_spacetime_take_snapshot(value v_time_opt, value v_channel) { return Val_unit; } #endif