diff --git a/dev b/dev index 13e4f44c..b7f157b1 100755 --- a/dev +++ b/dev @@ -184,6 +184,7 @@ case $1 in build) meson_build build --buildtype=plain ;; debug) meson_build debug --buildtype=debug + meson configure debug -Duse_ubsan=true -Duse_asan=true -Duse_memcheck=true debug_it ;; release) meson_build release --buildtype=release diff --git a/doc/reference.md b/doc/reference.md index c7abe4ea..62259a77 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -158,7 +158,7 @@ Additionally, type names are all contextual keywords. The following are punctuation tokens that will eat one trailing newline: ``` -+ += - -= -> * *= / /= % %= < <= << <<= > >= >> >>= ! != , . { [ ( & &= | |= ^ ^= = : ++ += - -= -> * *= / /= % %= < <= << <<= > >= >> >>= ! != , . { [ ( & &= | |= ^ ^= = ``` The following punctuation tokens do NOT eat a following newline: diff --git a/include/compiler/datatypes/parse.h b/include/compiler/datatypes/parse.h index 334c94c8..775867fb 100644 --- a/include/compiler/datatypes/parse.h +++ b/include/compiler/datatypes/parse.h @@ -137,7 +137,5 @@ typedef struct { // call_resolution_t; this one is NOT pre-alloc'd for us. // - For breaks, continues, returns, it will hold the c4m_loop_info_t // (the pnode_t not the tree node) that constitutes the jump target. - // - - - bool have_value; + bool have_value; } c4m_pnode_t; diff --git a/include/compiler/parse.h b/include/compiler/parse.h index f6830e62..20433a4c 100644 --- a/include/compiler/parse.h +++ b/include/compiler/parse.h @@ -52,7 +52,7 @@ c4m_node_get_loc_str(c4m_tree_node_t *n) static inline c4m_utf8_t * c4m_node_list_join(c4m_list_t *nodes, c4m_str_t *joiner, bool trailing) { - int64_t n = c4m_list_len(nodes); + int64_t n = c4m_list_len(nodes); c4m_list_t *strarr = c4m_new(c4m_type_list(c4m_type_utf8())); for (int64_t i = 0; i < n; i++) { @@ -80,15 +80,14 @@ c4m_node_simp_literal(c4m_tree_node_t *n) return tok->literal_value; } - typedef struct c4m_pass1_ctx { c4m_tree_node_t *cur_tnode; c4m_pnode_t *cur; c4m_spec_t *spec; c4m_file_compile_ctx *file_ctx; c4m_scope_t *static_scope; + c4m_list_t *extern_decls; bool in_func; - c4m_list_t *extern_decls; } c4m_pass1_ctx; static inline c4m_tree_node_t * @@ -119,6 +118,9 @@ c4m_node_down(c4m_pass1_ctx *ctx, int i) return false; } + if (n->children[i]->parent != n) { + c4m_print_parse_node(n->children[i]); + } assert(n->children[i]->parent == n); c4m_set_current_node(ctx, n->children[i]); diff --git a/include/con4m.h b/include/con4m.h index 2ce34a34..39fcccf8 100644 --- a/include/con4m.h +++ b/include/con4m.h @@ -1,7 +1,8 @@ #pragma once +// #define C4M_FULL_MEMCHECK // #define C4M_DEBUG -#define C4M_GC_STATS +// #define C4M_GC_STATS // #define C4M_TRACE_GC // #define C4M_GCT_MOVE 1 @@ -20,13 +21,17 @@ // This won't work on systems that require aligned pointers. // #define C4M_PARANOID_STACK_SCAN +// #define C4M_PARSE_DEBUG // UBSan hates our underflow check. -#define C4M_OMIT_UNDERFLOW_CHECKS - +// #define C4M_OMIT_UNDERFLOW_CHECKS #ifdef C4M_NO_DEV_MODE #undef C4M_DEV +#undef C4M_PARSE_DEBUG #else +#ifdef C4M_PARSE_DEBUG +#define C4M_DEV +#endif #define C4M_DEV #endif diff --git a/include/con4m/box.h b/include/con4m/box.h index 2f8019e2..0686c331 100644 --- a/include/con4m/box.h +++ b/include/con4m/box.h @@ -15,6 +15,7 @@ c4m_box_obj(c4m_box_t value, c4m_type_t *type) // However, the allocated item allocated the actual item's size, so we // have to make sure to get it right on both ends; we can't just // dereference a uint64_t, for instance. + static inline c4m_box_t c4m_unbox_obj(c4m_box_t *box) { @@ -22,9 +23,21 @@ c4m_unbox_obj(c4m_box_t *box) .u64 = 0, }; - switch (c4m_get_alloc_len(c4m_get_my_type(box))) { + c4m_type_t *t = c4m_type_unbox(c4m_get_my_type(box)); + + switch (c4m_get_alloc_len(t)) { case 1: - result.u8 = box->u8; + // On my mac, when this gets compiled w/ ASAN, ASAN somehow + // mangles the bool even when properly going through the union + // here. + // + // So this shouldn't be necessary, yet here it is. + if (t->details->base_type->typeid == C4M_T_BOOL) { + result.u64 = !!box->u64; + } + else { + result.u8 = box->u8; + } break; case 2: result.u16 = box->u16; diff --git a/include/con4m/datatypes/memory.h b/include/con4m/datatypes/memory.h index 3d7df881..6f63f8c6 100644 --- a/include/con4m/datatypes/memory.h +++ b/include/con4m/datatypes/memory.h @@ -1,6 +1,10 @@ #pragma once #include "con4m.h" +#if defined(C4M_FULL_MEMCHECK) && !defined(C4M_GC_STATS) +#define C4M_GC_STATS +#endif + #define C4M_FORCED_ALIGNMENT 16 typedef void (*c4m_mem_scan_fn)(uint64_t *, int); @@ -61,12 +65,13 @@ typedef struct c4m_alloc_hdr { // and a pointer to a bitfield that contains that many bits. The // bits that correspond to words with pointers should be set. c4m_mem_scan_fn scan_fn; - -#if defined(C4M_GC_STATS) || defined(C4M_DEBUG) - char *alloc_file; - int alloc_line; +#ifdef C4M_FULL_MEMCHECK + uint64_t *end_guard_loc; + int request_len; #endif - // + char *alloc_file; + int alloc_line; + __uint128_t cached_hash; // The actual exposed data. This must be 16-byte aligned! alignas(C4M_FORCED_ALIGNMENT) uint64_t data[]; } c4m_alloc_hdr; @@ -76,6 +81,18 @@ typedef struct c4m_finalizer_info_t { struct c4m_finalizer_info_t *next; } c4m_finalizer_info_t; +#ifdef C4M_FULL_MEMCHECK +typedef struct c4m_shadow_alloc_t { + struct c4m_shadow_alloc_t *next; + struct c4m_shadow_alloc_t *prev; + char *file; + int line; + int len; + c4m_alloc_hdr *start; + uint64_t *end; +} c4m_shadow_alloc_t; +#endif + typedef struct { void *ptr; uint64_t num_items; @@ -90,6 +107,10 @@ typedef struct { #endif typedef struct c4m_arena_t { +#ifdef C4M_FULL_MEMCHECK + c4m_shadow_alloc_t *shadow_start; + c4m_shadow_alloc_t *shadow_end; +#endif c4m_alloc_hdr *next_alloc; hatrack_zarray_t *roots; c4m_set_t *external_holds; diff --git a/include/con4m/datatypes/objects.h b/include/con4m/datatypes/objects.h index f2382f16..6d606d9d 100644 --- a/include/con4m/datatypes/objects.h +++ b/include/con4m/datatypes/objects.h @@ -64,7 +64,6 @@ typedef struct { struct c4m_base_obj_t { c4m_dt_info_t *base_data_type; struct c4m_type_t *concrete_type; - __uint128_t cached_hash; // The exposed object data. uint64_t data[]; }; diff --git a/include/con4m/datatypes/strings.h b/include/con4m/datatypes/strings.h index 4c4a5c19..9809c9f4 100644 --- a/include/con4m/datatypes/strings.h +++ b/include/con4m/datatypes/strings.h @@ -8,7 +8,9 @@ **/ #define C4M_STR_HASH_KEY_POINTER_OFFSET 0 -#define C4M_HASH_CACHE_OFFSET (-2 * (int32_t)sizeof(uint64_t)) +#define C4M_HASH_CACHE_OBJ_OFFSET (-4 * (int32_t)sizeof(uint64_t)) +#define C4M_HASH_CACHE_RAW_OFFSET (-2 * (int32_t)sizeof(uint64_t)) + typedef struct c4m_str_t { char *data; c4m_style_info_t *styling; diff --git a/include/con4m/gc.h b/include/con4m/gc.h index 05061ae1..72a8d5b5 100644 --- a/include/con4m/gc.h +++ b/include/con4m/gc.h @@ -127,10 +127,8 @@ #ifndef C4M_DEFAULT_ARENA_SIZE -#define C4M_DEFAULT_ARENA_SIZE (1 << 26) -// Was previously using 1 << 19 -// But this needs to be much bigger than the stack size; 21 is probably -// the minimum value without adjusting the stack. +// This is the size any test case that prints a thing grows to awfully fast. +#define C4M_DEFAULT_ARENA_SIZE (1 << 24) #endif // In the future, we would expect that a writer seeing the @@ -302,8 +300,6 @@ c4m_round_up_to_given_power_of_2(uint64_t power, uint64_t n) typedef void (*c4m_gc_hook)(); -extern void c4m_get_stack_scan_region(uint64_t *top, - uint64_t *bottom); extern void c4m_initialize_gc(); extern void c4m_gc_heap_stats(uint64_t *, uint64_t *, uint64_t *); extern void c4m_gc_add_hold(c4m_obj_t); @@ -315,9 +311,40 @@ extern void c4m_internal_lock_then_unstash_heap(); extern void c4m_get_heap_bounds(uint64_t *, uint64_t *, uint64_t *); extern void c4m_gc_register_collect_fns(c4m_gc_hook, c4m_gc_hook); extern c4m_alloc_hdr *c4m_find_alloc(void *); +extern bool c4m_in_heap(void *); #ifdef C4M_GC_STATS uint64_t c4m_get_alloc_counter(); #else #define c4m_get_alloc_counter() (0) #endif + +#ifdef C4M_FULL_MEMCHECK +void c4m_alloc_display_front_guard_error(c4m_alloc_hdr *, void *, char *, int, bool); +void c4m_alloc_display_rear_guard_error(c4m_alloc_hdr *, void *, int, void *, char *, int, bool); + +void _c4m_memcheck_raw_alloc(void *, char *, int); +void _c4m_memcheck_object(c4m_obj_t, char *, int); +#define c4m_memcheck_raw_alloc(x) \ + _c4m_memcheck_raw_alloc(((void *)x), __FILE__, __LINE__); +#define c4m_memcheck_object(x) \ + _c4m_memcheck_object(((void *)x), __FILE__, __LINE__); +#else +#define c4m_memcheck_raw_alloc(x) +#define c4m_memcheck_object(x) +#endif + +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +void __asan_poison_memory_region(void const volatile *addr, size_t size); +void __asan_unpoison_memory_region(void const volatile *addr, size_t size); + +#define ASAN_POISON_MEMORY_REGION(addr, size) \ + __asan_poison_memory_region((addr), (size)) +#define ASAN_UNPOISON_MEMORY_REGION(addr, size) \ + __asan_unpoison_memory_region((addr), (size)) +#else +#define ASAN_POISON_MEMORY_REGION(addr, size) \ + ((void)(addr), (void)(size)) +#define ASAN_UNPOISON_MEMORY_REGION(addr, size) \ + ((void)(addr), (void)(size)) +#endif diff --git a/include/con4m/type.h b/include/con4m/type.h index 93f33d5c..d923f208 100644 --- a/include/con4m/type.h +++ b/include/con4m/type.h @@ -547,7 +547,6 @@ c4m_type_is_box(c4m_type_t *t) static inline c4m_type_t * c4m_type_unbox(c4m_type_t *t) { - assert(c4m_type_is_box(t)); return (c4m_type_t *)t->details->tsi; } diff --git a/meson.build b/meson.build index e53711e9..75b2220c 100644 --- a/meson.build +++ b/meson.build @@ -58,19 +58,29 @@ if (get_option('buildtype') == 'release') link_args = link_args + ['-flto'] endif - - -if (get_option('buildtype') == 'debug') +if (get_option('use_asan') == true) c_args = c_args + ['-fsanitize=address', - '-fsanitize=undefined', - '-fsanitize-recover=all', + '-fsanitize-recover=all' ] link_args = link_args + ['-fsanitize=address', - '-fsanitize=undefined', '-fsanitize-recover=all' ] endif +if (get_option('use_ubsan') == true) + c_args = c_args + ['-fsanitize=undefined', + '-fsanitize-recover=all' + ] +endif + +if (get_option('use_memcheck') == true) + c_args = c_args + ['-DC4M_FULL_MEMCHECK'] +endif + +if (get_option('show_gc_stats') == true) + c_args = c_args + ['-DC4M_GC_STATS'] +endif + exe_link_args = link_args + ['-flto', '-w'] exe_c_args = c_args + ['-flto', '-DHATRACK_REFERENCE_ALGORITHMS'] @@ -222,12 +232,14 @@ libhat = static_library('hatrack', c_args : c_args, link_args : link_args) -# library('con4m-dll', -# lib_src, -# include_directories : incdir, -# dependencies : all_deps, -# c_args : c4m_c_args, -# link_args: link_args) +if get_option('build_con4m_dll') == true + library('con4m-dll', + lib_src, + include_directories : incdir, + dependencies : all_deps, + c_args : c4m_c_args, + link_args: link_args) +endif executable('c4test', test_src, include_directories : incdir, @@ -236,9 +248,11 @@ executable('c4test', test_src, link_args : exe_link_args, link_with : libc4m) -# executable('hash', hash_test_src, -# include_directories : incdir, -# dependencies : all_deps, -# link_args : exe_link_args, -# link_with : libhat, -# c_args : exe_c_args) +if get_option('build_hatrack') == true + executable('hash', hash_test_src, + include_directories : incdir, + dependencies : all_deps, + link_args : exe_link_args, + link_with : libhat, + c_args : exe_c_args) +endif diff --git a/meson.options b/meson.options new file mode 100644 index 00000000..23b2616a --- /dev/null +++ b/meson.options @@ -0,0 +1,7 @@ +option('use_asan', type: 'boolean', value: false) +option('use_ubsan', type: 'boolean', value: false) +option('build_hatrack', type: 'boolean', value: false) +option('use_memcheck', type: 'boolean', value: false) +option('build_con4m_dll', type: 'boolean', value: false) +# Currently, if this isn't on, there's an issue. +option('show_gc_stats', type: 'boolean', value: true) diff --git a/src/con4m/collect.c b/src/con4m/collect.c index 6627f314..68f71dd5 100644 --- a/src/con4m/collect.c +++ b/src/con4m/collect.c @@ -44,9 +44,11 @@ c4m_get_alloc_counter() #endif #ifdef C4M_GC_FULL_TRACE - int c4m_gc_trace_on = 1; +#endif +#ifdef C4M_FULL_MEMCHECK +extern uint64_t c4m_end_guard; #endif typedef struct hook_record_t { @@ -56,6 +58,49 @@ typedef struct hook_record_t { static hook_record_t *c4m_gc_hooks = NULL; +#if defined(__linux__) +static inline void +c4m_get_stack_scan_region(uint64_t *top, uint64_t *bottom) +{ + pthread_t self = pthread_self(); + + pthread_attr_t attrs; + uint64_t addr; + + pthread_getattr_np(self, &attrs); + +#ifdef C4M_USE_FRAME_INTRINSIC + pthread_attr_getstackaddr(&attrs, (void **)&addr); + *bottom = (uint64_t)__builtin_frame_address(0); +#else + size_t size; + pthread_attr_getstack(&attrs, (void **)&addr, &size); + *bottom = (uint64_t)addr + size; +#endif + + *top = (uint64_t)addr; +} + +#elif defined(__APPLE__) || defined(BSD) +// Apple at least has no way to get the thread's attr struct that +// I can find. But it does provide an API to get at the same data. +static inline void +c4m_get_stack_scan_region(uint64_t *top, uint64_t *bottom) +{ + pthread_t self = pthread_self(); + + *bottom = (uint64_t)pthread_get_stackaddr_np(self); + +#ifdef C4M_USE_FRAME_INTRINSIC + *top = (uint64_t)__builtin_frame_address(0); +#else + *top = (uint64_t)&self; +#endif +} +#else +#error "Unsupported platform." +#endif + void c4m_gc_register_collect_fn(c4m_gc_hook post) { @@ -215,6 +260,8 @@ get_header(c4m_collection_ctx *ctx, void *ptr) return result; } +static void process_worklist(c4m_collection_ctx *); + static inline void add_copy_to_worklist(c4m_collection_ctx *ctx, c4m_alloc_hdr *hdr) { @@ -235,9 +282,13 @@ add_copy_to_worklist(c4m_collection_ctx *ctx, c4m_alloc_hdr *hdr) } } - if (ctx->next_item > ctx->worklist_end) { + if (ctx->next_item >= ctx->worklist_end) { ctx->next_item = ctx->worklist_start; } + + if (ctx->next_item == ctx->worklist) { + process_worklist(ctx); + } } static inline void @@ -248,8 +299,6 @@ add_forward_to_worklist(c4m_collection_ctx *ctx, void **addr) addr, ctx->next_item); - assert(value_in_fromspace(ctx, *addr)); - *ctx->next_item++ = addr; *ctx->next_item++ = NULL; @@ -262,9 +311,13 @@ add_forward_to_worklist(c4m_collection_ctx *ctx, void **addr) } } - if (ctx->next_item > ctx->worklist_end) { + if (ctx->next_item >= ctx->worklist_end) { ctx->next_item = ctx->worklist_start; } + + if (ctx->next_item == ctx->worklist) { + process_worklist(ctx); + } } static inline void @@ -295,6 +348,7 @@ scan_allocation(c4m_collection_ctx *ctx, c4m_alloc_hdr *hdr) if ((void *)scanner == C4M_GC_SCAN_ALL) { while (p < end) { + ASAN_UNPOISON_MEMORY_REGION(p, 8); contents = *p; // We haven't copied anything yet. @@ -414,7 +468,7 @@ process_worklist(c4m_collection_ctx *ctx) // causing the to-space memory to be allocated, allowing us to // update the pointer. - while (ctx->worklist != ctx->next_item) { + while (ctx->worklist < ctx->next_item) { void **p = (void *)*ctx->worklist++; void *copy = (void *)*ctx->worklist++; @@ -430,6 +484,11 @@ process_worklist(c4m_collection_ctx *ctx) c4m_alloc_hdr *dst = src->fw_addr; memcpy(dst->data, src->data, src->alloc_len * 8); + +#ifdef C4M_FULL_MEMCHECK + // We just wiped the back guard with the memcpy. + *dst->end_guard_loc = c4m_end_guard; +#endif ctx->copied_allocs++; c4m_gc_trace(C4M_GCT_MOVED, "%d words moved from %p to %p (%s:%d)\n", @@ -440,6 +499,9 @@ process_worklist(c4m_collection_ctx *ctx) dst->alloc_line); } } + + ctx->worklist = ctx->worklist_start; + ctx->next_item = ctx->worklist_start; } // This is only used for roots, not for memory allocations. @@ -450,6 +512,12 @@ static void scan_range_for_allocs(c4m_collection_ctx *ctx, void **start, int num) { for (int i = 0; i < num; i++) { +#ifdef HAS_ADDRESS_SANITIZER + if (__asan_addr_is_in_fake_stack(__asan_get_current_fake_stack(), start)) { + start++; + continue; + } +#endif if (value_in_fromspace(ctx, *start)) { forward_one_allocation(ctx, start); } @@ -514,7 +582,12 @@ raw_trace(c4m_collection_ctx *ctx) len <<= 1; } - ctx->to_space = c4m_new_arena((size_t)len, r); + ctx->to_space = c4m_new_arena((size_t)len, r); + + ASAN_UNPOISON_MEMORY_REGION( + ctx->to_space, + (((char *)ctx->to_space->heap_end) - (char *)ctx->to_space->data)); + uint64_t alloc_len = cur->largest_alloc * 32; if (alloc_len & c4m_page_modulus) { alloc_len = (alloc_len & c4m_modulus_mask) + c4m_page_bytes; @@ -561,8 +634,227 @@ raw_trace(c4m_collection_ctx *ctx) if (system_finalizer != NULL) { migrate_finalizers(cur, ctx->to_space); } + + ASAN_UNPOISON_MEMORY_REGION( + ctx->to_space->next_alloc, + (((char *)ctx->to_space->heap_end) - (char *)ctx->to_space->next_alloc)); } +#ifdef C4M_FULL_MEMCHECK + +extern uint64_t c4m_end_guard; + +static inline char * +name_alloc(c4m_alloc_hdr *alloc) +{ + if (!alloc->con4m_obj) { + return "raw alloc"; + } + c4m_base_obj_t *base = (c4m_base_obj_t *)alloc->data; + return (char *)base->base_data_type->name; +} + +void +_c4m_memcheck_raw_alloc(void *a, char *file, int line) +{ + // This uses info int the alloc itself that might be corrupt; + // It's a TODO to make the external records log n searchable, + // to remove that potential issue. + + if (!c4m_in_heap(a)) { + fprintf(stderr, + "\n%s:%d: Heap pointer %p is corrupt; not in heap.\n", + file, + line, + a); + return; + abort(); + } + + c4m_alloc_hdr *h = (c4m_alloc_hdr *)(((unsigned char *)a) - sizeof(c4m_alloc_hdr)); + if (h->guard != c4m_gc_guard) { + fprintf(stderr, "\n%s:%d: ", file, line); + c4m_alloc_display_front_guard_error(h, a, NULL, 0, true); + } + + if (*h->end_guard_loc != c4m_end_guard) { + fprintf(stderr, "\n%s:%d: ", file, line); + c4m_alloc_display_rear_guard_error(h, + a, + h->request_len, + a, + NULL, + 0, + true); + } +} + +void +_c4m_memcheck_object(c4m_obj_t o, char *file, int line) +{ + if (!c4m_in_heap(o)) { + fprintf(stderr, + "\n%s:%d: Heap pointer %p is corrupt; not in heap.\n", + file, + line, + o); + abort(); + } + + c4m_base_obj_t *b = &((c4m_base_obj_t *)o)[-1]; + c4m_alloc_hdr *h = &((c4m_alloc_hdr *)b)[-1]; + + if (h->guard != c4m_gc_guard) { + fprintf(stderr, "\n%s:%d: ", file, line); + c4m_alloc_display_front_guard_error(h, o, NULL, 0, true); + } + + if (*h->end_guard_loc != c4m_end_guard) { + fprintf(stderr, "\n%s:%d: ", file, line); + c4m_alloc_display_rear_guard_error(h, + o, + h->request_len, + h->end_guard_loc, + NULL, + 0, + true); + } +} + +void +c4m_alloc_display_front_guard_error(c4m_alloc_hdr *hdr, + void *ptr, + char *file, + int line, + bool bail) +{ + fprintf(stderr, + "%s @%p is corrupt; its guard has been overwritten.\n" + "Expected '%llx', but got '%llx'\n" + "Alloc location: %s:%d\n\n", + name_alloc(hdr), + ptr, + c4m_gc_guard, + hdr->guard, + file ? file : hdr->alloc_file, + file ? line : hdr->alloc_line); + + if (bail) { + abort(); + } +} + +void +c4m_alloc_display_rear_guard_error(c4m_alloc_hdr *hdr, + void *ptr, + int len, + void *rear_guard_loc, + char *file, + int line, + bool bail) +{ + if (hdr->con4m_obj) { + len -= sizeof(c4m_base_obj_t); + } + + fprintf(stderr, + "%s @%p overflowed. It was allocated to %d bytes, and had its " + "end guard at %p.\n" + "End guard should have been '%llx' but was actually '%llx'\n" + "Alloc location: %s:%d\n\n", + name_alloc(hdr), + ptr, + len, + rear_guard_loc, + c4m_end_guard, + *(uint64_t *)rear_guard_loc, + file ? file : hdr->alloc_file, + file ? line : hdr->alloc_line); + + if (bail) { + abort(); + } +} + +static void +memcheck_validate_old_records(c4m_arena_t *from_space) +{ + uint64_t *low = (void *)from_space->data; + uint64_t *high = (void *)from_space->heap_end; + c4m_shadow_alloc_t *a = from_space->shadow_start; + + while (a != NULL) { + c4m_shadow_alloc_t *next = a->next; + + if (a->start->guard != c4m_gc_guard) { + c4m_alloc_display_front_guard_error(a->start, + a->start->data, + a->file, + a->line, + false); + } + + if (*a->end != c4m_end_guard) { + c4m_alloc_display_rear_guard_error(a->start, + a->start->data, + a->len, + a->end, + a->file, + a->line, + false); + } + + if (a->start->fw_addr != NULL) { + uint64_t **p = (void *)a->start->data; + uint64_t **end = (void *)a->end; + + while (p < end) { + uint64_t *v = *p; + if (v > low && v < high) { + void **probe; + probe = (void **)(((uint64_t)v) & ~0x0000000000000007); + + while (*(uint64_t *)probe != c4m_gc_guard) { + --probe; + } + + c4m_alloc_hdr *h = (c4m_alloc_hdr *)probe; + if (!h->fw_addr) { + fprintf(stderr, + "Possible missed allocation. Found a pointer " + " to %p, which was NOT copied. The pointer " + " was found in a live allocation " + " from %s:%d, now residing at %p.\n", + *p, + h->alloc_file, + h->alloc_line, + h->fw_addr); + } + } + p++; + } + } + if (free) { + c4m_rc_free(a); + } + a = next; + } +} + +static void +memcheck_delete_old_records(c4m_arena_t *from_space) +{ + c4m_shadow_alloc_t *a = from_space->shadow_start; + + while (a != NULL) { + c4m_shadow_alloc_t *next = a->next; + c4m_rc_free(a); + a = next; + } +} + +#endif + c4m_arena_t * c4m_collect_arena(c4m_arena_t *from_space) { @@ -596,6 +888,14 @@ c4m_collect_arena(c4m_arena_t *from_space) c4m_total_collects++; #endif + ASAN_UNPOISON_MEMORY_REGION( + from_space, + (((char *)ctx.from_space->heap_end) - (char *)ctx.from_space->data)); + +#ifdef C4M_FULL_MEMCHECK + memcheck_validate_old_records(old_arena); +#endif + #if defined(C4M_GC_FULL_TRACE) && C4M_GCT_COLLECT != 0 c4m_gc_trace(C4M_GCT_COLLECT, "=========== COLLECT START; arena @%p", @@ -608,6 +908,10 @@ c4m_collect_arena(c4m_arena_t *from_space) raw_trace(&ctx); #endif +#ifdef C4M_FULL_MEMCHECK + memcheck_delete_old_records(old_arena); +#endif + uint64_t start = (uint64_t)ctx.to_space; uint64_t end = (uint64_t)ctx.to_space->heap_end; uint64_t where = (uint64_t)ctx.to_space->next_alloc; @@ -620,6 +924,8 @@ c4m_collect_arena(c4m_arena_t *from_space) run_post_collect_hooks(); + c4m_delete_arena(ctx.from_space); + // Free the worklist. char *unmap_s = ((char *)ctx.worklist_start); char *unmap_e = ((char *)ctx.worklist_end); diff --git a/src/con4m/compiler/cfg.c b/src/con4m/compiler/cfg.c index 25ebb957..51d1554d 100644 --- a/src/con4m/compiler/cfg.c +++ b/src/con4m/compiler/cfg.c @@ -9,7 +9,7 @@ typedef struct { typedef struct { c4m_file_compile_ctx *file_ctx; c4m_dict_t *du_info; - c4m_list_t *sometimes_info; + c4m_list_t *sometimes_info; } cfg_ctx; static c4m_symbol_t * @@ -29,10 +29,10 @@ sym_cmp(c4m_symbol_t *sym1, c4m_symbol_t *sym2) } static bool -cfg_propogate_def(cfg_ctx *ctx, - c4m_symbol_t *sym, - c4m_cfg_node_t *n, - c4m_list_t *deps) +cfg_propogate_def(cfg_ctx *ctx, + c4m_symbol_t *sym, + c4m_cfg_node_t *n, + c4m_list_t *deps) { c4m_dict_t *du_info; @@ -61,9 +61,9 @@ cfg_propogate_def(cfg_ctx *ctx, } static bool -cfg_propogate_use(cfg_ctx *ctx, - c4m_symbol_t *sym, - c4m_cfg_node_t *n) +cfg_propogate_use(cfg_ctx *ctx, + c4m_symbol_t *sym, + c4m_cfg_node_t *n) { c4m_dict_t *du_info; @@ -85,8 +85,8 @@ cfg_propogate_use(cfg_ctx *ctx, new->last_def = NULL; for (unsigned int i = 0; i < x; i++) { - c4m_symbol_t *s = view[i].key; - c4m_cfg_node_t *cfg = view[i].value; + c4m_symbol_t *s = view[i].key; + c4m_cfg_node_t *cfg = view[i].value; if (!strcmp(s->name->data, sym->name->data)) { new->last_def = cfg; @@ -109,7 +109,7 @@ static void cfg_copy_du_info(cfg_ctx *ctx, c4m_cfg_node_t *node, c4m_dict_t **new_dict, - c4m_list_t **new_sometimes) + c4m_list_t **new_sometimes) { c4m_dict_t *copy = c4m_dict(c4m_type_ref(), c4m_type_ref()); uint64_t n; @@ -139,8 +139,8 @@ cfg_copy_du_info(cfg_ctx *ctx, int l = c4m_list_len(old); for (int i = 0; i < l; i++) { c4m_list_add_if_unique(*new_sometimes, - c4m_list_get(old, i, NULL), - (bool (*)(void *, void *))sym_cmp); + c4m_list_get(old, i, NULL), + (bool (*)(void *, void *))sym_cmp); } } } @@ -159,15 +159,15 @@ check_for_fn_exit_errors(c4m_file_compile_ctx *file, c4m_fn_decl_t *fn_decl) return; } - c4m_scope_t *fn_scope = fn_decl->signature_info->fn_scope; - c4m_symbol_t *ressym = c4m_symbol_lookup(fn_scope, - NULL, - NULL, - NULL, - result_text); - c4m_cfg_node_t *node = fn_decl->cfg; - c4m_cfg_node_t *exit_node = node->contents.block_entrance.exit_node; - c4m_cfg_status_t *status = hatrack_dict_get(exit_node->liveness_info, + c4m_scope_t *fn_scope = fn_decl->signature_info->fn_scope; + c4m_symbol_t *ressym = c4m_symbol_lookup(fn_scope, + NULL, + NULL, + NULL, + result_text); + c4m_cfg_node_t *node = fn_decl->cfg; + c4m_cfg_node_t *exit_node = node->contents.block_entrance.exit_node; + c4m_cfg_status_t *status = hatrack_dict_get(exit_node->liveness_info, ressym, NULL); @@ -235,9 +235,9 @@ check_block_for_errors(cfg_ctx *ctx, c4m_cfg_node_t *node) case c4m_cfg_use: if (node->use_without_def) { - c4m_symbol_t *sym; + c4m_symbol_t *sym; bool sometimes = false; - c4m_symbol_t *check; + c4m_symbol_t *check; c4m_compile_error_t err; sym = node->contents.flow.dst_symbol; @@ -298,10 +298,10 @@ typedef union { static void cfg_merge_aux_entries_to_top(cfg_ctx *ctx, c4m_cfg_node_t *node) { - c4m_list_t *inbounds = node->contents.block_entrance.inbound_links; - c4m_cfg_node_t *exit = node->contents.block_entrance.exit_node; - c4m_dict_t *exit_du = exit->liveness_info; - c4m_symbol_t *sym; + c4m_list_t *inbounds = node->contents.block_entrance.inbound_links; + c4m_cfg_node_t *exit = node->contents.block_entrance.exit_node; + c4m_dict_t *exit_du = exit->liveness_info; + c4m_symbol_t *sym; if (inbounds == NULL) { return; @@ -373,8 +373,8 @@ cfg_merge_aux_entries_to_top(cfg_ctx *ctx, c4m_cfg_node_t *node) for (int i = 0; i < c4m_list_len(not_unique); i++) { void *item = c4m_list_get(not_unique, i, NULL); c4m_list_add_if_unique(exit->sometimes_live, - item, - (bool (*)(void *, void *))sym_cmp); + item, + (bool (*)(void *, void *))sym_cmp); } } @@ -389,7 +389,7 @@ process_branch_exit(cfg_ctx *ctx, c4m_cfg_node_t *node) c4m_dict_t *merged = c4m_new(c4m_type_dict(c4m_type_ref(), c4m_type_ref())); c4m_cfg_node_t *exit_node; - c4m_symbol_t *sym; + c4m_symbol_t *sym; hatrack_dict_item_t *view; c4m_cfg_status_t *status; c4m_cfg_status_t *old_status; @@ -399,7 +399,7 @@ process_branch_exit(cfg_ctx *ctx, c4m_cfg_node_t *node) for (int i = 0; i < bi->num_branches; i++) { exit_node = bi->branch_targets[i]->contents.block_entrance.exit_node; - c4m_dict_t *duinfo = exit_node->liveness_info; + c4m_dict_t *duinfo = exit_node->liveness_info; c4m_list_t *stinfo = exit_node->sometimes_live; // TODO: fix this. @@ -487,8 +487,8 @@ process_branch_exit(cfg_ctx *ctx, c4m_cfg_node_t *node) for (int i = 0; i < c4m_list_len(not_unique); i++) { void *item = c4m_list_get(not_unique, i, NULL); c4m_list_add_if_unique(node->sometimes_live, - item, - (bool (*)(void *, void *))sym_cmp); + item, + (bool (*)(void *, void *))sym_cmp); } } @@ -652,8 +652,8 @@ cfg_process_node(cfg_ctx *ctx, c4m_cfg_node_t *node, c4m_cfg_node_t *parent) for (int i = 0; i < c4m_list_len(node->contents.flow.deps); i++) { c4m_symbol_t *sym = c4m_list_get(node->contents.flow.deps, - i, - NULL); + i, + NULL); cfg_propogate_use(ctx, sym, node); } cfg_process_node(ctx, node->contents.flow.next_node, node); @@ -675,15 +675,15 @@ cfg_process_node(cfg_ctx *ctx, c4m_cfg_node_t *node, c4m_cfg_node_t *parent) for (uint64_t i = 0; i < n; i++) { c4m_list_add_if_unique(ta->sometimes_live, - v[i], - (bool (*)(void *, void *))sym_cmp); + v[i], + (bool (*)(void *, void *))sym_cmp); } c4m_list_t *old = node->sometimes_live; for (int64_t i = 0; i < c4m_list_len(old); i++) { c4m_list_add_if_unique(ta->sometimes_live, - c4m_list_get(old, i, NULL), - (bool (*)(void *, void *))sym_cmp); + c4m_list_get(old, i, NULL), + (bool (*)(void *, void *))sym_cmp); } return NULL; @@ -720,7 +720,7 @@ c4m_cfg_analyze(c4m_file_compile_ctx *file_ctx, c4m_dict_t *du_info) for (uint64_t i = 0; i < nparams; i++) { c4m_module_param_info_t *param = view[i]; - c4m_symbol_t *sym = param->linked_symbol; + c4m_symbol_t *sym = param->linked_symbol; cfg_propogate_def(&ctx, sym, NULL, NULL); } @@ -735,13 +735,13 @@ c4m_cfg_analyze(c4m_file_compile_ctx *file_ctx, c4m_dict_t *du_info) c4m_cfg_node_t *modexit = file_ctx->cfg->contents.block_entrance.exit_node; c4m_dict_t *moddefs = modexit->liveness_info; - c4m_list_t *stdefs = modexit->sometimes_live; + c4m_list_t *stdefs = modexit->sometimes_live; for (int i = 0; i < n; i++) { - ctx.du_info = moddefs; - ctx.sometimes_info = stdefs; - c4m_symbol_t *sym = c4m_list_get(file_ctx->fn_def_syms, i, NULL); - c4m_fn_decl_t *decl = sym->value; + ctx.du_info = moddefs; + ctx.sometimes_info = stdefs; + c4m_symbol_t *sym = c4m_list_get(file_ctx->fn_def_syms, i, NULL); + c4m_fn_decl_t *decl = sym->value; cfg_process_node(&ctx, decl->cfg, NULL); check_block_for_errors(&ctx, decl->cfg); @@ -752,8 +752,8 @@ c4m_cfg_analyze(c4m_file_compile_ctx *file_ctx, c4m_dict_t *du_info) for (int i = 0; i < c4m_list_len(stdefs); i++) { void *item = c4m_list_get(stdefs, i, NULL); c4m_list_add_if_unique(cleanup, - item, - (bool (*)(void *, void *))sym_cmp); + item, + (bool (*)(void *, void *))sym_cmp); } modexit->sometimes_live = stdefs; } diff --git a/src/con4m/compiler/check_pass.c b/src/con4m/compiler/check_pass.c index 2c6df271..37a89813 100644 --- a/src/con4m/compiler/check_pass.c +++ b/src/con4m/compiler/check_pass.c @@ -1172,16 +1172,19 @@ handle_if(pass2_ctx *ctx) handle_else(ctx, else_match); } ctx->cfg = c4m_cfg_exit_block(ctx->cfg, branch_enter, NULL); - ctx->cfg = c4m_cfg_exit_block(ctx->cfg, enter, saved); } static bool -range_runs_check(c4m_tree_node_t *n) +range_runs_check(pass2_ctx *ctx, c4m_tree_node_t *n) { c4m_pnode_t *pnode1 = c4m_get_pnode(n->children[0]); c4m_pnode_t *pnode2 = c4m_get_pnode(n->children[1]); + if (c4m_type_is_error(get_pnode_type(n))) { + return false; + } + if (!pnode1->value || !pnode2->value) { return false; } @@ -1409,7 +1412,7 @@ handle_for(pass2_ctx *ctx) // the items are constant integers and not the same, we will // always run the loop. - if (!li->ranged || !range_runs_check(container_node)) { + if (!li->ranged || !range_runs_check(ctx, container_node)) { bstart = c4m_cfg_enter_block(ctx->cfg, n); ctx->cfg = c4m_cfg_exit_block(bstart, bstart, n); } diff --git a/src/con4m/compiler/codegen.c b/src/con4m/compiler/codegen.c index 10509490..2c498509 100644 --- a/src/con4m/compiler/codegen.c +++ b/src/con4m/compiler/codegen.c @@ -1718,7 +1718,7 @@ gen_binary_assign(gen_ctx *ctx) gen_tcall(ctx, C4M_BI_INDEX_SET, ctx->cur_pnode->type); break; default: - // TODO: disallow slice assignments and tuple assignments here.. + // TODO: disallow slice assignments and tuple assignments here. c4m_unreachable(); } } diff --git a/src/con4m/compiler/lex.c b/src/con4m/compiler/lex.c index ca409d14..07fbb38c 100644 --- a/src/con4m/compiler/lex.c +++ b/src/con4m/compiler/lex.c @@ -1011,8 +1011,8 @@ lex(lex_state_t *state) else { */ TOK(c4m_tt_colon); - skip_optional_newline(state); - // } + // skip_optional_newline(state); + // } continue; case '=': if (peek(state) == '=') { diff --git a/src/con4m/compiler/parse.c b/src/con4m/compiler/parse.c index d7b06758..0dc4f3df 100644 --- a/src/con4m/compiler/parse.c +++ b/src/con4m/compiler/parse.c @@ -27,9 +27,7 @@ typedef struct { bool in_function; } parse_ctx; -#undef PARSE_DEBUG - -#ifdef PARSE_DEBUG +#ifdef C4M_PARSE_DEBUG static inline c4m_token_t *_tok_cur(parse_ctx *, int); #define consume(ctx) _consume(ctx, __LINE__) #define tok_cur(ctx) _tok_cur(ctx, __LINE__) @@ -64,7 +62,7 @@ c4m_exit_to_checkpoint(parse_ctx *ctx, int line, const char *fn) { -#ifdef PARSE_DEBUG +#ifdef C4M_PARSE_DEBUG printf("Bailed @%s:%d (func = %s)\n", f, line, fn); c4m_print(c4m_format_one_token(tok_cur(ctx), c4m_new_utf8("Current token: "))); @@ -97,7 +95,7 @@ c4m_exit_to_checkpoint(parse_ctx *ctx, __LINE__, \ __func__) -#ifdef PARSE_DEBUG +#ifdef C4M_PARSE_DEBUG static inline void _consume(parse_ctx *, int); #else static inline void _consume(parse_ctx *); @@ -237,7 +235,7 @@ static const node_type_info_t node_type_info[] = { // clang-format on }; -#ifdef PARSE_DEBUG +#ifdef C4M_PARSE_DEBUG static inline c4m_token_t * _tok_cur(parse_ctx *ctx, int line) #else @@ -252,7 +250,7 @@ _tok_cur(parse_ctx *ctx) ctx->cache_ix = ctx->token_ix; } -#ifdef PARSE_DEBUG +#ifdef C4M_PARSE_DEBUG if (line == -1) { return ctx->cached_token; } @@ -268,7 +266,7 @@ _tok_cur(parse_ctx *ctx) static inline c4m_token_kind_t tok_kind(parse_ctx *ctx) { -#ifdef PARSE_DEBUG +#ifdef C4M_PARSE_DEBUG return _tok_cur(ctx, -1)->kind; #else return _tok_cur(ctx)->kind; @@ -419,7 +417,6 @@ end_of_statement(parse_ctx *ctx) } if (!cur_tok_is_end_of_stmt(ctx) && tok_kind(ctx)) { - DEBUG_CUR("Suck"); err_skip_stmt(ctx, c4m_err_parse_expected_stmt_end); return; } @@ -445,7 +442,7 @@ tok_raw_advance_once(parse_ctx *ctx) ctx->token_ix += 1; } -#ifdef PARSE_DEBUG +#ifdef C4M_PARSE_DEBUG static inline void _consume(parse_ctx *ctx, int line) #else @@ -456,7 +453,7 @@ _consume(parse_ctx *ctx) c4m_pnode_t *pn; c4m_comment_node_t *comment; -#ifdef PARSE_DEBUG +#ifdef C4M_PARSE_DEBUG printf("Consume on line %d\n", line); #endif @@ -544,7 +541,7 @@ _match(parse_ctx *ctx, ...) possibility = va_arg(args, c4m_token_kind_t); if (possibility == c4m_tt_error) { -#ifdef PARSE_DEBUG +#ifdef C4M_PARSE_DEBUG printf("No match for tt = %d\n", current_tt); #endif current_tt = c4m_tt_error; @@ -626,7 +623,7 @@ adopt_kid(parse_ctx *ctx, c4m_tree_node_t *node) c4m_tree_adopt_node(ctx->cur, node); } -#ifdef PARSE_DEBUG +#ifdef C4M_PARSE_DEBUG static inline c4m_tree_node_t * _start_node(parse_ctx *ctx, c4m_node_kind_t kind, bool consume_token, int line) #else @@ -638,7 +635,7 @@ _start_node(parse_ctx *ctx, c4m_node_kind_t kind, bool consume_token) c4m_tree_node_t *parent = ctx->cur; if (consume_token) { -#ifdef PARSE_DEBUG +#ifdef C4M_PARSE_DEBUG printf("In start_node on line: %d\n", line); #endif consume(ctx); @@ -647,7 +644,7 @@ _start_node(parse_ctx *ctx, c4m_node_kind_t kind, bool consume_token) c4m_tree_node_t *result = c4m_tree_add_node(parent, child); ctx->cur = result; -#ifdef PARSE_DEBUG +#ifdef C4M_PARSE_DEBUG printf("started node: "); c4m_print(repr_one_node(child)); #endif @@ -658,7 +655,7 @@ _start_node(parse_ctx *ctx, c4m_node_kind_t kind, bool consume_token) static inline void end_node(parse_ctx *ctx) { -#ifdef PARSE_DEBUG +#ifdef C4M_PARSE_DEBUG printf("Leaving node: "); c4m_print(repr_one_node(ctx->cur->contents)); #endif @@ -1753,8 +1750,9 @@ case_body(parse_ctx *ctx) start_node(ctx, c4m_nt_body, true); DECLARE_CHECKPOINT(); + ENTER_CHECKPOINT(); + while (true) { - ENTER_CHECKPOINT(); if (safety_check) { THROW('!'); } @@ -2010,48 +2008,68 @@ switch_case_block(parse_ctx *ctx) { start_node(ctx, c4m_nt_case, false); - while (true) { - c4m_tree_node_t *expr = expression(ctx); + c4m_tree_node_t *expr; - // If, after the first expression, there's a colon followed by - // anything other then a newline, we'll try to parse a range. - // - // We can't just try to always parse the range, since doing so - // will eat the newline. So in the case the next token's a colon, - // we look ahead one token, and if we a newline, then we - // know not to try the range op. + // If, after the first expression, there's a colon followed by + // anything other then a newline, we'll try to parse a range. + // + // We can't just try to always parse the range, since doing so + // will eat the newline. So in the case the next token's a colon, + // we look ahead one token, and if we a newline, then we + // know not to try the range op. - if (tok_kind(ctx) == c4m_tt_colon) { + while (true) { + expr = expression(ctx); + switch (tok_kind(ctx)) { + case c4m_tt_colon: if (lookahead(ctx, 1, false) == c4m_tt_newline) { adopt_kid(ctx, expr); - break; + consume(ctx); + case_body(ctx); + end_node(ctx); + return; } - } - // optional_case_range is done on the main tree, and adopts - // expr, so only adopt the expr if the case range fails. - if (!optional_case_range(ctx, expr)) { + // fallthrough + case c4m_tt_to: + optional_case_range(ctx, expr); + + if (tok_kind(ctx) == c4m_tt_comma) { + consume(ctx); + continue; + } + + end_node(ctx); + return; + + case c4m_tt_newline: adopt_kid(ctx, expr); - } + consume(ctx); - if (tok_kind(ctx) != c4m_tt_comma) { - break; - } + if (tok_kind(ctx) != c4m_tt_lbrace) { + add_parse_error(ctx, c4m_err_parse_case_body_start); + THROW('!'); + } - consume(ctx); - } + body(ctx, NULL); - if (tok_kind(ctx) == c4m_tt_colon) { - case_body(ctx); - } - else { - opt_one_newline(ctx); - if (tok_kind(ctx) != c4m_tt_lbrace) { + end_node(ctx); + return; + + case c4m_tt_comma: + adopt_kid(ctx, expr); + consume(ctx); + continue; + + case c4m_tt_else: + case_else_block(ctx); + end_node(ctx); + expect(ctx, c4m_tt_rbrace); + return; + default: add_parse_error(ctx, c4m_err_parse_case_body_start); THROW('!'); } - body(ctx, NULL); } - end_node(ctx); } static void @@ -2071,12 +2089,13 @@ switch_stmt(parse_ctx *ctx, bool label) ctx->switch_depth++; - while (true) { - switch_case_block(ctx); + switch_case_block(ctx); + while (true) { switch (match(ctx, c4m_tt_case, c4m_tt_rbrace, c4m_tt_else)) { case c4m_tt_case: consume(ctx); + switch_case_block(ctx); continue; case c4m_tt_rbrace: consume(ctx); @@ -4042,11 +4061,12 @@ body(parse_ctx *ctx, c4m_pnode_t *docstring_target) } } case '!': - END_CHECKPOINT(); if (tok_kind(ctx) == c4m_tt_eof) { + END_CHECKPOINT(); return; } if (safety_check) { + END_CHECKPOINT(); return; } safety_check = 1; diff --git a/src/con4m/dict.c b/src/con4m/dict.c index 4a2aa08b..719c897e 100644 --- a/src/con4m/dict.c +++ b/src/con4m/dict.c @@ -32,7 +32,7 @@ c4m_custom_string_hash(c4m_str_t *s) c4m_gc_register_root(&c4m_null_string, 1); } - hatrack_hash_t *cache = (void *)(((char *)s) + C4M_HASH_CACHE_OFFSET); + hatrack_hash_t *cache = (void *)(((char *)s) + C4M_HASH_CACHE_OBJ_OFFSET); hv.local_hv = *cache; @@ -60,20 +60,24 @@ c4m_dict_init(c4m_dict_t *dict, va_list args) c4m_list_t *type_params; c4m_type_t *key_type; c4m_dt_info_t *info; + bool using_obj = false; c4m_type_t *c4m_dict_type = c4m_get_my_type(dict); if (c4m_dict_type != NULL) { type_params = c4m_type_get_params(c4m_dict_type); key_type = c4m_list_get(type_params, 0, NULL); info = c4m_type_get_data_type_info(key_type); - - hash_fn = info->hash_fn; + hash_fn = info->hash_fn; } - else { hash_fn = va_arg(args, size_t); } + if (hash_fn == HATRACK_DICT_KEY_TYPE_PTR && info->typeid != C4M_T_REF) { + using_obj = true; + hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR; + } + hatrack_dict_init(dict, hash_fn); switch (hash_fn) { @@ -88,10 +92,15 @@ c4m_dict_init(c4m_dict_t *dict, va_list args) case HATRACK_DICT_KEY_TYPE_OBJ_PTR: case HATRACK_DICT_KEY_TYPE_OBJ_INT: case HATRACK_DICT_KEY_TYPE_OBJ_REAL: - hatrack_dict_set_cache_offset(dict, C4M_HASH_CACHE_OFFSET); + if (using_obj) { + hatrack_dict_set_cache_offset(dict, C4M_HASH_CACHE_OBJ_OFFSET); + } + else { + hatrack_dict_set_cache_offset(dict, C4M_HASH_CACHE_RAW_OFFSET); + } break; default: - // nada. + break; } dict->slow_views = false; diff --git a/src/con4m/gcbase.c b/src/con4m/gcbase.c index 43fc6110..70103394 100644 --- a/src/con4m/gcbase.c +++ b/src/con4m/gcbase.c @@ -12,6 +12,29 @@ thread_local uint64_t c4m_words_requested = 0; thread_local uint32_t c4m_total_allocs = 0; #endif +#ifdef C4M_FULL_MEMCHECK +#ifndef C4M_MEMCHECK_RING_SZ +// Must be a power of 2. +#define C4M_MEMCHECK_RING_SZ 64 +#endif +#if C4M_MEMCHECK_RING_SZ != 0 +#define C4M_USE_RING +#else +#undef C4M_USE_RING +#endif + +uint64_t c4m_end_guard; + +#ifdef C4M_USE_RING +static thread_local c4m_shadow_alloc_t *memcheck_ring[C4M_MEMCHECK_RING_SZ] = { + 0, +}; +static thread_local unsigned int ring_head = 0; +static thread_local unsigned int ring_tail = 0; +#endif + +#endif // C4M_FULL_MEMCHECK + static c4m_set_t *external_holds = NULL; static pthread_key_t c4m_thread_key; @@ -35,7 +58,7 @@ c4m_get_heap_bounds(uint64_t *start, uint64_t *next, uint64_t *end) } void -c4m_get_stack_bounds(uint64_t *top, uint64_t *bottom) +c4m_get_stack_scan_region(uint64_t *top, uint64_t *bottom) { pthread_t self = pthread_self(); @@ -47,24 +70,14 @@ c4m_get_stack_bounds(uint64_t *top, uint64_t *bottom) pthread_getattr_np(self, &attrs); pthread_attr_getstack(&attrs, (void **)&addr, &size); - *bottom = (uint64_t)addr + size; - *top = (uint64_t)addr; + *top = (uint64_t)addr; #elif defined(__APPLE__) || defined(BSD) // Apple at least has no way to get the thread's attr struct that // I can find. But it does provide an API to get at the same data. - *bottom = (uint64_t)pthread_get_stackaddr_np(self); - *top = *bottom - pthread_get_stacksize_np(self); + *top = *bottom - pthread_get_stacksize_np(self); #endif -} -// This puts a junk call frame on we scan, which on my mac seems -// to be 256 bytes. Playing it safe and not subtracting it out, though. -void -c4m_get_stack_scan_region(uint64_t *top, uint64_t *bottom) -{ - uint64_t local = 0; - c4m_get_stack_bounds(top, bottom); - *top = (uint64_t)(&local); + *bottom = (uint64_t)__builtin_frame_address(0); } void @@ -93,7 +106,7 @@ c4m_gc_malloc_wrapper(size_t size, void *arg) // Hatrack wants a 16-byte aligned pointer. The con4m gc allocator will // always produce a 16-byte aligned pointer. The raw allocation header is // 48 bytes and its base pointer is always 16-byte aligned. - return c4m_gc_raw_alloc(size, C4M_GC_SCAN_ALL); + return c4m_gc_raw_alloc(size * 2, C4M_GC_SCAN_ALL); } static void @@ -138,6 +151,12 @@ c4m_thread_acquire(void *aux, size_t size) mmm_thread_t *r = (mmm_thread_t *)pt->data; c4m_gc_register_root(&(r->retire_list), 1); return r; + +#ifdef C4M_USE_RING + for (int i = 0; i < C4M_MEMCHECK_RING_SZ; i++) { + memcheck_ring[i] = NULL; + } +#endif } return (mmm_thread_t *)pt->data; @@ -158,6 +177,9 @@ c4m_initialize_gc() .arg = NULL, }; +#ifdef C4M_FULL_MEMCHECK + c4m_end_guard = c4m_rand64(); +#endif c4m_gc_guard = c4m_rand64(); initial_roots = hatrack_zarray_new(C4M_MAX_GC_ROOTS, sizeof(c4m_gc_root_info_t)); @@ -284,6 +306,8 @@ raw_arena_alloc(uint64_t len, void **end) guard, len); + ASAN_POISON_MEMORY_REGION(((c4m_arena_t *)ret)->data, len); + return ret; } @@ -530,6 +554,41 @@ c4m_gcm_remove_root(void *ptr) c4m_arena_remove_root(c4m_current_heap, ptr); } +#ifdef C4M_FULL_MEMCHECK +static inline void +memcheck_process_ring() +{ + unsigned int cur = ring_tail; + + cur &= ~(C4M_MEMCHECK_RING_SZ - 1); + + while (cur != ring_head) { + c4m_shadow_alloc_t *a = memcheck_ring[cur++]; + if (!a) { + return; + } + + if (a->start->guard != c4m_gc_guard) { + c4m_alloc_display_front_guard_error(a->start, + a->start->data, + a->file, + a->line, + true); + } + + if (*a->end != c4m_end_guard) { + c4m_alloc_display_rear_guard_error(a->start, + a->start->data, + a->len, + a->end, + a->file, + a->line, + true); + } + } +} +#endif + #if defined(C4M_GC_STATS) || defined(C4M_DEBUG) void * c4m_alloc_from_arena(c4m_arena_t **arena_ptr, @@ -552,20 +611,18 @@ c4m_alloc_from_arena(c4m_arena_t **arena_ptr, _c4m_watch_scan(file, line); #endif +#ifdef C4M_FULL_MEMCHECK + size_t orig_len = len; + len = c4m_round_up_to_given_power_of_2(8, len); + size_t end_guard_offset = len / sizeof(uint64_t); + len += sizeof(uint64_t); +#endif c4m_arena_t *arena = *arena_ptr; // Round up to aligned length. size_t wordlen = c4m_round_up_to_given_power_of_2(C4M_FORCED_ALIGNMENT, len); - // Come back here if, when we trigger the collector, the resulting - // free space isn't enough, in which case we do a second collect. - // There are better ways to handle this like to just grab enough extra - // zero- mapped pages to ensure we get the allocation, but ideally - // people won't ask for such large allocs relative to the arena size - // without just asking for a new arena, so I'm not going to bother - // right now; maybe someday. - // try_again:; c4m_alloc_hdr *raw = arena->next_alloc; c4m_alloc_hdr *next = (c4m_alloc_hdr *)&(raw->data[wordlen]); @@ -588,6 +645,7 @@ c4m_alloc_from_arena(c4m_arena_t **arena_ptr, arena->largest_alloc = len; } + ASAN_UNPOISON_MEMORY_REGION(raw, ((char *)next - (char *)raw)); arena->alloc_count++; arena->next_alloc = next; raw->guard = c4m_gc_guard; @@ -596,6 +654,46 @@ c4m_alloc_from_arena(c4m_arena_t **arena_ptr, raw->alloc_len = wordlen; raw->scan_fn = scan_fn; +#ifdef C4M_FULL_MEMCHECK + c4m_shadow_alloc_t *record = c4m_rc_alloc(sizeof(c4m_shadow_alloc_t)); + record->start = raw; + record->end = &raw->data[end_guard_offset]; + record->file = file; + record->line = line; + record->len = orig_len; + record->next = NULL; + record->prev = arena->shadow_end; + *record->end = c4m_end_guard; + + // Duplicated in the header for spot-checking; this can get corrupted; + // the out-of-heap list is better, but we don't want to bother searching + // through the whole heap. + raw->end_guard_loc = record->end; + raw->request_len = orig_len; + + assert(*raw->end_guard_loc == c4m_end_guard); + + if (arena->shadow_end != NULL) { + arena->shadow_end->next = record; + arena->shadow_end = record; + } + else { + arena->shadow_start = record; + arena->shadow_end = record; + } + +#ifdef C4M_USE_RING + memcheck_process_ring(); + + memcheck_ring[ring_head++] = record; + ring_head &= ~(C4M_MEMCHECK_RING_SZ - 1); + if (ring_tail == ring_head) { + ring_tail++; + ring_tail &= ~(C4M_MEMCHECK_RING_SZ - 1); + } +#endif // C4M_USE_RING +#endif // C4M_FULL_MEMCHECK + #ifdef C4M_GC_STATS c4m_words_requested += len; c4m_total_words += wordlen; diff --git a/src/con4m/grid.c b/src/con4m/grid.c index a8e13ab1..42ed3bea 100644 --- a/src/con4m/grid.c +++ b/src/con4m/grid.c @@ -57,6 +57,7 @@ styled_repeat(c4m_codepoint_t c, uint32_t width, c4m_style_t style) static inline c4m_utf32_t * get_styled_pad(uint32_t width, c4m_style_t style) { + assert(width < 200); return styled_repeat(' ', width, style); } @@ -559,7 +560,7 @@ static int16_t * calculate_col_widths(c4m_grid_t *grid, int16_t width, int16_t *render_width) { size_t term_width; - int16_t *result = c4m_gc_array_value_alloc(uint16_t, + int16_t *result = c4m_gc_array_value_alloc(uint64_t, grid->num_cols); int16_t sum = get_column_render_overhead(grid); c4m_render_style_t *props; @@ -1514,7 +1515,8 @@ grid_add_cell_contents(c4m_grid_t *grid, NULL)); if (!c4m_str_codepoint_len(piece)) { c4m_style_t pad_style = c4m_get_pad_style(grid_style(grid)); - piece = get_styled_pad(col_widths[i], + assert(col_widths[i] < 1024); + piece = get_styled_pad(col_widths[i], pad_style); } c4m_list_set(lines, i, c4m_str_concat(s, piece)); diff --git a/src/con4m/kargs.c b/src/con4m/kargs.c index e1124294..a49a7e01 100644 --- a/src/con4m/kargs.c +++ b/src/con4m/kargs.c @@ -40,7 +40,6 @@ c4m_pass_kargs(int nargs, ...) } nargs >>= 1; - c4m_karg_info_t *kargs = c4m_new(c4m_type_kargs(), nargs); for (int i = 0; i < nargs; i++) { diff --git a/src/con4m/object.c b/src/con4m/object.c index 83430d5a..9e6b188c 100644 --- a/src/con4m/object.c +++ b/src/con4m/object.c @@ -18,7 +18,7 @@ const c4m_dt_info_t c4m_base_type_info[C4M_NUM_BUILTIN_DTS] = { [C4M_T_BOOL] = { .name = "bool", .typeid = C4M_T_BOOL, - .alloc_len = 4, + .alloc_len = 1, .vtable = &c4m_bool_type, .dt_kind = C4M_DT_KIND_primitive, .hash_fn = HATRACK_DICT_KEY_TYPE_INT, @@ -424,7 +424,7 @@ _c4m_new(c4m_type_t *type, ...) c4m_obj_t result; va_list args; c4m_dt_info_t *tinfo = type->details->base_type; - uint64_t alloc_len = tinfo->alloc_len + sizeof(c4m_obj_t); + uint64_t alloc_len = tinfo->alloc_len + sizeof(c4m_base_obj_t); c4m_vtable_entry init_fn = tinfo->vtable->methods[C4M_BI_CONSTRUCTOR]; #if defined(C4M_GC_STATS) || defined(C4M_DEBUG) diff --git a/src/con4m/set.c b/src/con4m/set.c index e1b65e4c..a0596c45 100644 --- a/src/con4m/set.c +++ b/src/con4m/set.c @@ -6,7 +6,8 @@ static void c4m_set_init(c4m_set_t *set, va_list args) { size_t hash_fn; - c4m_type_t *stype = c4m_get_my_type(set); + c4m_type_t *stype = c4m_get_my_type(set); + bool using_obj = false; c4m_dt_info_t *info; if (stype != NULL) { @@ -18,6 +19,11 @@ c4m_set_init(c4m_set_t *set, va_list args) hash_fn = (uint32_t)va_arg(args, size_t); } + if (hash_fn == HATRACK_DICT_KEY_TYPE_PTR) { + using_obj = true; + hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR; + } + hatrack_set_init(set, hash_fn); switch (hash_fn) { @@ -32,10 +38,17 @@ c4m_set_init(c4m_set_t *set, va_list args) case HATRACK_DICT_KEY_TYPE_OBJ_PTR: case HATRACK_DICT_KEY_TYPE_OBJ_INT: case HATRACK_DICT_KEY_TYPE_OBJ_REAL: - hatrack_set_set_cache_offset(set, C4M_HASH_CACHE_OFFSET); + if (using_obj) { + hatrack_set_set_cache_offset(set, C4M_HASH_CACHE_OBJ_OFFSET); + } + else { + hatrack_set_set_cache_offset(set, C4M_HASH_CACHE_RAW_OFFSET); + } break; default: - // nada. + hatrack_set_set_cache_offset(set, C4M_HASH_CACHE_RAW_OFFSET); + break; + } } diff --git a/src/con4m/streams.c b/src/con4m/streams.c index fb73e0b8..1ab38a54 100644 --- a/src/con4m/streams.c +++ b/src/con4m/streams.c @@ -492,6 +492,7 @@ void c4m_stream_write_object(c4m_stream_t *stream, c4m_obj_t obj, bool ansi) { if (stream->flags & C4M_F_STREAM_CLOSED) { + abort(); C4M_CRAISE("Stream is already closed."); } diff --git a/src/con4m/string.c b/src/con4m/string.c index 7ae4489f..5643dfba 100644 --- a/src/con4m/string.c +++ b/src/con4m/string.c @@ -1,16 +1,5 @@ #include "con4m.h" -// The object header has 4 words that we don't need to scan (there is -// a heap pointer in there, but it points to something definitely -// always available from the roots). -// -// Our pointer shows in our second word. Therefore, the 6th most -// significant bit gets set here. -const uint64_t c4m_pmap_str[2] = { - 0x0000000000000001, - 0x0400000000000000, -}; - C4M_STATIC_ASCII_STR(c4m_empty_string_const, ""); C4M_STATIC_ASCII_STR(c4m_newline_const, "\n"); C4M_STATIC_ASCII_STR(c4m_crlf_const, "\r\n"); @@ -434,8 +423,9 @@ c4m_to_utf8(const c4m_utf32_t *inp) // Allocates 4 bytes per codepoint; this is an overestimate in // cases where UTF8 codepoints are above U+00ff. But nbd. - c4m_utf8_t *res = c4m_new(c4m_type_utf8(), + c4m_utf8_t *res = c4m_new(c4m_type_utf8(), c4m_kw("length", c4m_ka(inp->byte_len))); + c4m_codepoint_t *p = (c4m_codepoint_t *)inp->data; uint8_t *outloc = (uint8_t *)res->data; int l; @@ -694,6 +684,7 @@ c4m_utf8_repeat(c4m_codepoint_t cp, int64_t num) char *p = res->data; res->codepoints = l; + res->byte_len = blen; for (int i = 0; i < blen; i++) { p[i] = buf[buf_ix++]; diff --git a/src/con4m/tree.c b/src/con4m/tree.c index 66580b26..2dc03512 100644 --- a/src/con4m/tree.c +++ b/src/con4m/tree.c @@ -175,7 +175,7 @@ c4m_tree_str_transform(c4m_tree_node_t *t, c4m_str_t *(*fn)(void *)) return NULL; } - c4m_str_t *str = fn(c4m_tree_get_contents(t)); + c4m_str_t *str = c4m_to_utf8(fn(c4m_tree_get_contents(t))); c4m_tree_node_t *result = c4m_new(c4m_type_tree(c4m_type_utf8()), c4m_kw("contents", c4m_ka(str))); diff --git a/src/con4m/types.c b/src/con4m/types.c index bf7bab04..b4da933b 100644 --- a/src/con4m/types.c +++ b/src/con4m/types.c @@ -1664,16 +1664,19 @@ c4m_initialize_global_types() c4m_base_obj_t *envstore; // This needs to not be c4m_new'd. - c4m_type_universe = c4m_gc_raw_alloc(sizeof(c4m_type_universe_t), - C4M_GC_SCAN_ALL); - envstore = c4m_gc_alloc(c4m_dict_t); - c4m_dict_t *store = (c4m_dict_t *)envstore->data; + // clang-format off + c4m_type_universe = c4m_gc_raw_alloc(sizeof(c4m_type_universe_t), + C4M_GC_SCAN_ALL); + envstore = c4m_gc_raw_alloc(sizeof(c4m_dict_t) + + sizeof(c4m_base_obj_t), + C4M_GC_SCAN_ALL); + // clang-format on + c4m_dict_t *store = (c4m_dict_t *)envstore->data; c4m_type_universe->store = store; - // We don't set the heading info up fully, so this dict // won't be directly marshalable unless / until we do. hatrack_dict_init(store, HATRACK_DICT_KEY_TYPE_INT); - + c4m_memcheck_object(store); // Set up the type we need internally for containers. tobj = c4m_gc_raw_alloc(tslen, c4m_type_set_gc_bits); diff --git a/src/con4m/vm.c b/src/con4m/vm.c index 2106f4e2..5169ed79 100644 --- a/src/con4m/vm.c +++ b/src/con4m/vm.c @@ -1634,7 +1634,7 @@ c4m_vm_reset(c4m_vm_t *vm) NULL); vm->module_allocations[n] = c4m_gc_array_alloc(c4m_value_t, - m->module_var_size); + m->module_var_size + 8); } vm->attrs = c4m_new(c4m_type_dict(c4m_type_utf8(), diff --git a/src/hatrack/hash/crown-internal.h b/src/hatrack/hash/crown-internal.h index 1228a1ad..c5aeab8d 100644 --- a/src/hatrack/hash/crown-internal.h +++ b/src/hatrack/hash/crown-internal.h @@ -10,7 +10,7 @@ typedef uint32_t hop_t; #else -#define CROWN_HOME_BIT 0x8000000000000000 +#define CROWN_HOME_BIT 0x8000000000000000ULL typedef uint64_t hop_t; @@ -19,10 +19,10 @@ typedef uint64_t hop_t; #endif enum64(crown_flag_t, - CROWN_F_MOVING = 0x8000000000000000, - CROWN_F_MOVED = 0x4000000000000000, - CROWN_F_INITED = 0x2000000000000000, - CROWN_EPOCH_MASK = 0x1fffffffffffffff); + CROWN_F_MOVING = 0x8000000000000000ULL, + CROWN_F_MOVED = 0x4000000000000000ULL, + CROWN_F_INITED = 0x2000000000000000ULL, + CROWN_EPOCH_MASK = 0x1fffffffffffffffULL); /* These need to be non-static because tophat and hatrack_dict both * need them, so that they can call in without a second call to diff --git a/src/hatrack/hash/crown.c b/src/hatrack/hash/crown.c index d552e273..58434e15 100644 --- a/src/hatrack/hash/crown.c +++ b/src/hatrack/hash/crown.c @@ -546,6 +546,23 @@ crown_store_new(uint64_t size) return store; } +#if defined(__clang__) +/* UBScan can sometimes complain about the line: + * bit_to_set = CROWN_HOME_BIT >> i; + * + * This will happen in more full tables. However, this is a + * non-issue. + * + * Yes, when we get to the end of the cache, we do shift right by + * 64, and they're right that it's technically undefined. But + * until we find a platform where that doesn't result in 0, I + * would much rather avoid adding the extra test; this has the + * desired effect of setting `map` to 0 when we're done checking + * all bits of the cache, at which point we fall back to full + * linear probes. + */ +__attribute__((no_sanitize("all"))) +#endif void * crown_store_get(crown_store_t *self, hatrack_hash_t hv1, bool *found) { @@ -709,6 +726,10 @@ crown_store_get(crown_store_t *self, hatrack_hash_t hv1, bool *found) * would be fine if you know your table is going to be sparsely * populated. */ +#if defined(__clang__) +// See the comment on crown_store_get. +__attribute__((no_sanitize("all"))) +#endif void * crown_store_put(crown_store_t *self, mmm_thread_t *thread, @@ -890,6 +911,10 @@ crown_store_put(crown_store_t *self, return item; } +#if defined(__clang__) +// See the comment on crown_store_get. +__attribute__((no_sanitize("all"))) +#endif void * crown_store_replace(crown_store_t *self, mmm_thread_t *thread, @@ -1012,6 +1037,10 @@ crown_store_replace(crown_store_t *self, * The bucket logic implementation is basically identical. Please * see above for exposition. */ +#if defined(__clang__) +// See the comment on crown_store_get. +__attribute__((no_sanitize("all"))) +#endif bool crown_store_add(crown_store_t *self, mmm_thread_t *thread, @@ -1153,6 +1182,10 @@ crown_store_add(crown_store_t *self, * result, the bucket acquisition logic is the same as with replace, * and effectively identical to "get". Please see above for details. */ +#if defined(__clang__) +// See the comment on crown_store_get. +__attribute__((no_sanitize("all"))) +#endif void * crown_store_remove(crown_store_t *self, mmm_thread_t *thread, @@ -1284,6 +1317,10 @@ crown_store_remove(crown_store_t *self, * ALL threads that write to the new store during migration will be * trying to write the exact same things in the exact same order. */ +#if defined(__clang__) +// See the comment on crown_store_get. +__attribute__((no_sanitize("all"))) +#endif static crown_store_t * crown_store_migrate(crown_store_t *self, mmm_thread_t *thread, crown_t *top) { diff --git a/tests/typeof.c4m b/tests/typeof.c4m index 93c49002..9685bdd5 100644 --- a/tests/typeof.c4m +++ b/tests/typeof.c4m @@ -1,4 +1,5 @@ global x: `t +y = 14 typeof x { case int: @@ -13,4 +14,5 @@ typeof x { else: y = -1 } -z = false \ No newline at end of file +z = false +print(y) \ No newline at end of file diff --git a/tests/valueof.c4m b/tests/valueof.c4m index 1904b934..654152f3 100644 --- a/tests/valueof.c4m +++ b/tests/valueof.c4m @@ -9,6 +9,5 @@ func fib(x) { } } -x = fib(10) -assert x == 55 -assert repr(x) == "55" +print(fib(10)) +