From 08436b8f86b2d9b9bf0268fcaca22883b92ba05f Mon Sep 17 00:00:00 2001 From: John Viega Date: Tue, 9 Jul 2024 18:28:55 -0400 Subject: [PATCH] Provide C backtraces on exceptions (and via API) when available. --- dev | 1 + include/compiler/datatypes/cfg.h | 4 +- include/con4m.h | 6 +- include/con4m/cbacktrace.h | 12 +++ include/con4m/datatypes/types.h | 2 +- include/con4m/exception.h | 29 ++++-- include/vendor.h | 1 + include/vendor/backtrace.h | 173 +++++++++++++++++++++++++++++++ meson.build | 10 +- src/con4m/collect.c | 4 + src/con4m/compiler/cfg_build.c | 18 +++- src/con4m/compiler/check_pass.c | 2 - src/con4m/compiler/codegen.c | 1 - src/con4m/ctrace.c | 122 ++++++++++++++++++++++ src/con4m/init.c | 1 + src/con4m/string.c | 17 ++- src/con4m/tree.c | 2 + src/con4m/vm.c | 6 +- 18 files changed, 379 insertions(+), 32 deletions(-) create mode 100644 include/con4m/cbacktrace.h create mode 100644 include/vendor/backtrace.h create mode 100644 src/con4m/ctrace.c diff --git a/dev b/dev index 9f832d82..b88ee862 100755 --- a/dev +++ b/dev @@ -55,6 +55,7 @@ function meson_build { if [[ ${OS} = "Darwin" ]] ; then meson compile + dsymutil c4test else CC=musl-gcc meson compile fi diff --git a/include/compiler/datatypes/cfg.h b/include/compiler/datatypes/cfg.h index f46a6b97..ebbe6c76 100644 --- a/include/compiler/datatypes/cfg.h +++ b/include/compiler/datatypes/cfg.h @@ -36,8 +36,8 @@ typedef struct { c4m_cfg_node_t *exit_node; c4m_cfg_node_t **branch_targets; c4m_utf8_t *label; // For loops - int32_t num_branches; - int32_t next_to_process; + int64_t num_branches; + int64_t next_to_process; } c4m_cfg_branch_info_t; typedef struct { diff --git a/include/con4m.h b/include/con4m.h index 01107e61..eba80334 100644 --- a/include/con4m.h +++ b/include/con4m.h @@ -1,9 +1,11 @@ #pragma once // #define C4M_FULL_MEMCHECK -// #define C4M_DEBUG +#define C4M_DEBUG // #define C4M_TRACE_GC +// #define C4M_GC_SHOW_COLLECT_STACK_TRACES + // #define C4M_GCT_MOVE 1 // #define C4M_GCT_PTR_TO_MOVE 1 @@ -161,6 +163,8 @@ // For functions we need to wrap to use through the FFI. #include "con4m/wrappers.h" +#include "con4m/cbacktrace.h" + // The compiler. #include "compiler/ast_utils.h" #include "compiler/compile.h" diff --git a/include/con4m/cbacktrace.h b/include/con4m/cbacktrace.h new file mode 100644 index 00000000..620740c1 --- /dev/null +++ b/include/con4m/cbacktrace.h @@ -0,0 +1,12 @@ +#pragma once +#include "con4m.h" + +#ifdef BACKTRACE_SUPPORTED +void c4m_backtrace_init(char *); +void c4m_print_c_backtrace(); +#else +#define c4m_backtrace_init(x) +#define c4m_print_c_backtrace() +#endif + +c4m_grid_t *c4m_get_c_backtrace(); diff --git a/include/con4m/datatypes/types.h b/include/con4m/datatypes/types.h index 74dd4667..1da018be 100644 --- a/include/con4m/datatypes/types.h +++ b/include/con4m/datatypes/types.h @@ -23,7 +23,7 @@ typedef struct c4m_type_info_t { c4m_dt_info_t *base_type; c4m_list_t *items; void *tsi; // Type-specific info. - uint8_t flags; + uint64_t flags; } c4m_type_info_t; #define C4M_FN_TY_VARARGS 1 diff --git a/include/con4m/exception.h b/include/con4m/exception.h index fab120e5..814671cb 100644 --- a/include/con4m/exception.h +++ b/include/con4m/exception.h @@ -131,15 +131,26 @@ #define C4M_FINALLY C4M_LFINALLY(default_label) #define C4M_TRY_END C4M_LTRY_END(default_label) -#define C4M_CRAISE(s, ...) c4m_exception_raise( \ - c4m_alloc_exception(s, __VA_OPT__(, ) __VA_ARGS__), \ - __FILE__, \ - __LINE__) - -#define C4M_RAISE(s, ...) c4m_exception_raise( \ - c4m_alloc_str_exception(s __VA_OPT__(, ) __VA_ARGS__), \ - __FILE__, \ - __LINE__) +#if defined(C4M_DEBUG) && defined(BACKTRACE_SUPPORTED) +extern void c4m_print_c_backtrace(); +#define c4m_trace() c4m_print_c_backtrace() +#else +#define c4m_trace() +#endif + +#define C4M_CRAISE(s, ...) \ + c4m_trace(); \ + c4m_exception_raise( \ + c4m_alloc_exception(s, __VA_OPT__(, ) __VA_ARGS__), \ + __FILE__, \ + __LINE__) + +#define C4M_RAISE(s, ...) \ + c4m_trace(); \ + c4m_exception_raise( \ + c4m_alloc_str_exception(s __VA_OPT__(, ) __VA_ARGS__), \ + __FILE__, \ + __LINE__) #define C4M_RERAISE() \ _c4x_exception_state = C4M_EXCEPTION_NOT_HANDLED; \ diff --git a/include/vendor.h b/include/vendor.h index c3eaab4a..81b1491d 100644 --- a/include/vendor.h +++ b/include/vendor.h @@ -3,3 +3,4 @@ #include "vendor/unibreak.h" #include "vendor/utf8proc.h" #include "vendor/md4c.h" +#include "vendor/backtrace.h" diff --git a/include/vendor/backtrace.h b/include/vendor/backtrace.h new file mode 100644 index 00000000..0ccaaeff --- /dev/null +++ b/include/vendor/backtrace.h @@ -0,0 +1,173 @@ +/* backtrace.h -- Public header file for stack backtrace library. + Copyright (C) 2012-2021 Free Software Foundation, Inc. + Written by Ian Lance Taylor, Google. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. */ + +#ifndef BACKTRACE_H +#define BACKTRACE_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* The backtrace state. This struct is intentionally not defined in + the public interface. */ + +struct backtrace_state; + +/* The type of the error callback argument to backtrace functions. + This function, if not NULL, will be called for certain error cases. + The DATA argument is passed to the function that calls this one. + The MSG argument is an error message. The ERRNUM argument, if + greater than 0, holds an errno value. The MSG buffer may become + invalid after this function returns. + + As a special case, the ERRNUM argument will be passed as -1 if no + debug info can be found for the executable, or if the debug info + exists but has an unsupported version, but the function requires + debug info (e.g., backtrace_full, backtrace_pcinfo). The MSG in + this case will be something along the lines of "no debug info". + Similarly, ERRNUM will be passed as -1 if there is no symbol table, + but the function requires a symbol table (e.g., backtrace_syminfo). + This may be used as a signal that some other approach should be + tried. */ + +typedef void (*backtrace_error_callback)(void *data, const char *msg, int errnum); + +/* Create state information for the backtrace routines. This must be + called before any of the other routines, and its return value must + be passed to all of the other routines. FILENAME is the path name + of the executable file; if it is NULL the library will try + system-specific path names. If not NULL, FILENAME must point to a + permanent buffer. If THREADED is non-zero the state may be + accessed by multiple threads simultaneously, and the library will + use appropriate atomic operations. If THREADED is zero the state + may only be accessed by one thread at a time. This returns a state + pointer on success, NULL on error. If an error occurs, this will + call the ERROR_CALLBACK routine. + + Calling this function allocates resources that cannot be freed. + There is no backtrace_free_state function. The state is used to + cache information that is expensive to recompute. Programs are + expected to call this function at most once and to save the return + value for all later calls to backtrace functions. */ + +extern struct backtrace_state *backtrace_create_state( + const char *filename, + int threaded, + backtrace_error_callback error_callback, + void *data); + +/* The type of the callback argument to the backtrace_full function. + DATA is the argument passed to backtrace_full. PC is the program + counter. FILENAME is the name of the file containing PC, or NULL + if not available. LINENO is the line number in FILENAME containing + PC, or 0 if not available. FUNCTION is the name of the function + containing PC, or NULL if not available. This should return 0 to + continuing tracing. The FILENAME and FUNCTION buffers may become + invalid after this function returns. */ + +typedef int (*backtrace_full_callback)(void *data, uintptr_t pc, const char *filename, int lineno, const char *function); + +/* Get a full stack backtrace. SKIP is the number of frames to skip; + passing 0 will start the trace with the function calling + backtrace_full. DATA is passed to the callback routine. If any + call to CALLBACK returns a non-zero value, the stack backtrace + stops, and backtrace returns that value; this may be used to limit + the number of stack frames desired. If all calls to CALLBACK + return 0, backtrace returns 0. The backtrace_full function will + make at least one call to either CALLBACK or ERROR_CALLBACK. This + function requires debug info for the executable. */ + +extern int backtrace_full(struct backtrace_state *state, int skip, backtrace_full_callback callback, backtrace_error_callback error_callback, void *data); + +/* The type of the callback argument to the backtrace_simple function. + DATA is the argument passed to simple_backtrace. PC is the program + counter. This should return 0 to continue tracing. */ + +typedef int (*backtrace_simple_callback)(void *data, uintptr_t pc); + +/* Get a simple backtrace. SKIP is the number of frames to skip, as + in backtrace. DATA is passed to the callback routine. If any call + to CALLBACK returns a non-zero value, the stack backtrace stops, + and backtrace_simple returns that value. Otherwise + backtrace_simple returns 0. The backtrace_simple function will + make at least one call to either CALLBACK or ERROR_CALLBACK. This + function does not require any debug info for the executable. */ + +extern int backtrace_simple(struct backtrace_state *state, int skip, backtrace_simple_callback callback, backtrace_error_callback error_callback, void *data); + +/* Print the current backtrace in a user readable format to a FILE. + SKIP is the number of frames to skip, as in backtrace_full. Any + error messages are printed to stderr. This function requires debug + info for the executable. */ + +extern void backtrace_print(struct backtrace_state *state, int skip, FILE *); + +/* Given PC, a program counter in the current program, call the + callback function with filename, line number, and function name + information. This will normally call the callback function exactly + once. However, if the PC happens to describe an inlined call, and + the debugging information contains the necessary information, then + this may call the callback function multiple times. This will make + at least one call to either CALLBACK or ERROR_CALLBACK. This + returns the first non-zero value returned by CALLBACK, or 0. */ + +extern int backtrace_pcinfo(struct backtrace_state *state, uintptr_t pc, backtrace_full_callback callback, backtrace_error_callback error_callback, void *data); + +/* The type of the callback argument to backtrace_syminfo. DATA and + PC are the arguments passed to backtrace_syminfo. SYMNAME is the + name of the symbol for the corresponding code. SYMVAL is the + value and SYMSIZE is the size of the symbol. SYMNAME will be NULL + if no error occurred but the symbol could not be found. */ + +typedef void (*backtrace_syminfo_callback)(void *data, uintptr_t pc, const char *symname, uintptr_t symval, uintptr_t symsize); + +/* Given ADDR, an address or program counter in the current program, + call the callback information with the symbol name and value + describing the function or variable in which ADDR may be found. + This will call either CALLBACK or ERROR_CALLBACK exactly once. + This returns 1 on success, 0 on failure. This function requires + the symbol table but does not require the debug info. Note that if + the symbol table is present but ADDR could not be found in the + table, CALLBACK will be called with a NULL SYMNAME argument. + Returns 1 on success, 0 on error. */ + +extern int backtrace_syminfo(struct backtrace_state *state, uintptr_t addr, backtrace_syminfo_callback callback, backtrace_error_callback error_callback, void *data); + +#ifdef __cplusplus +} /* End extern "C". */ +#endif + +#endif diff --git a/meson.build b/meson.build index 2030598b..8e86d5a0 100644 --- a/meson.build +++ b/meson.build @@ -46,6 +46,13 @@ else c_args = c_args + ['-D_GNU_SOURCE'] endif +backtrace = cc.find_library('backtrace', + required: false) + +if backtrace.found() +c_args = c_args + ['-DBACKTRACE_SUPPORTED'] +endif + fpty_code = ''' #include #include @@ -141,6 +148,7 @@ c4m_src = ['src/con4m/style.c', 'src/con4m/ffi.c', 'src/con4m/watch.c', 'src/con4m/wrappers.c', + 'src/con4m/ctrace.c', 'src/con4m/crypto/sha.c', 'src/con4m/compiler/compile.c', 'src/con4m/compiler/lex.c', @@ -221,7 +229,7 @@ ssl = cc.find_library('ssl') unibreak = dependency('libunibreak') utf8proc = dependency('libutf8proc') -deps = [unibreak, utf8proc, threads, ffi, ssl, crypto] +deps = [unibreak, utf8proc, threads, ffi, ssl, crypto, backtrace] opts = [math] if using_glibc opts = opts + [cc.find_library('atomic')] diff --git a/src/con4m/collect.c b/src/con4m/collect.c index 7fbbc539..6f4030c0 100644 --- a/src/con4m/collect.c +++ b/src/con4m/collect.c @@ -1045,6 +1045,10 @@ c4m_collect_arena(c4m_arena_t *from_space) c4m_printf("[b]Average allocation size:[/] [em]{:,}[/] bytes", c4m_box_u64((c4m_total_words * 8) / c4m_total_allocs)); +#ifdef C4M_GC_SHOW_COLLECT_STACK_TRACES + c4m_c_backtrace(); +#endif + #endif return ctx.to_space; } diff --git a/src/con4m/compiler/cfg_build.c b/src/con4m/compiler/cfg_build.c index fd44f75f..df9ce809 100644 --- a/src/con4m/compiler/cfg_build.c +++ b/src/con4m/compiler/cfg_build.c @@ -39,12 +39,19 @@ c4m_cfg_enter_block(c4m_cfg_node_t *parent, result->contents.block_entrance.inbound_links = c4m_new( c4m_type_list(c4m_type_ref())); + + exit->reference_location = treeloc; + exit->parent = result; + exit->starting_liveness_info = NULL; + exit->starting_sometimes = NULL; + exit->liveness_info = NULL; + exit->sometimes_live = NULL; + exit->kind = c4m_cfg_block_exit; + exit->contents.block_exit.next_node = NULL; + exit->contents.block_exit.entry_node = result; + exit->contents.block_exit.to_merge = NULL; exit->contents.block_exit.inbound_links = c4m_new( c4m_type_list(c4m_type_ref())); - exit->contents.block_exit.entry_node = result; - - exit->parent = result; - exit->kind = c4m_cfg_block_exit; if (parent != NULL) { add_child(parent, result); @@ -444,6 +451,8 @@ c4m_cfg_repr_internal(c4m_cfg_node_t *node, c4m_kw("contents", c4m_ka(label))); c4m_cfg_node_t *kid = node->contents.branches.branch_targets[i]; + assert(kid != NULL); + c4m_tree_adopt_node(result, sub); c4m_cfg_repr_internal(kid, sub, node, NULL); } @@ -452,6 +461,7 @@ c4m_cfg_repr_internal(c4m_cfg_node_t *node, tree_parent, node, NULL); + return result; case c4m_cfg_block_exit: diff --git a/src/con4m/compiler/check_pass.c b/src/con4m/compiler/check_pass.c index 05c390ba..ffec9960 100644 --- a/src/con4m/compiler/check_pass.c +++ b/src/con4m/compiler/check_pass.c @@ -1726,8 +1726,6 @@ handle_typeof_statement(pass2_ctx *ctx) base_check_pass_dispatch(ctx); - c4m_print_parse_node(branch->children[1]); - ctx->cfg = c4m_cfg_exit_block(ctx->cfg, bstart, ctx->node); if (c4m_list_len(tmp->sym_defs) > c4m_list_len(sym->sym_defs)) { diff --git a/src/con4m/compiler/codegen.c b/src/con4m/compiler/codegen.c index 29ccccf9..09a70652 100644 --- a/src/con4m/compiler/codegen.c +++ b/src/con4m/compiler/codegen.c @@ -907,7 +907,6 @@ gen_typeof(gen_ctx *ctx) gen_one_tcase(ctx, switch_exit); } else { - emit(ctx, C4M_ZPop); gen_one_node(ctx); } } diff --git a/src/con4m/ctrace.c b/src/con4m/ctrace.c new file mode 100644 index 00000000..b03ef154 --- /dev/null +++ b/src/con4m/ctrace.c @@ -0,0 +1,122 @@ +#include "con4m.h" + +#ifdef BACKTRACE_SUPPORTED + +static void +c4m_bt_err(void *data, const char *msg, int errnum) +{ + fprintf(stderr, "ERROR: %s (%d)", msg, errnum); +} + +static thread_local c4m_grid_t *c4m_trace_grid; + +struct backtrace_state *btstate; + +static int +c4m_bt_create_backtrace(void *data, + uintptr_t pc, + const char *pathname, + int line_number, + const char *function) +{ + c4m_utf8_t *file; + c4m_utf8_t *fn; + + if (pathname == NULL) { + file = c4m_rich_lit("[em]??[/] (unavailable)"); + } + else { + char *filename = rindex(pathname, '/'); + if (filename) { + filename++; + } + else { + filename = (char *)pathname; + } + + file = c4m_cstr_format("{}:{}", + c4m_new_utf8(filename), + c4m_box_u64(line_number)); + } + + if (function == NULL) { + fn = c4m_rich_lit("[em]??[/] (unavailable)"); + } + else { + fn = c4m_new_utf8(function); + } + + c4m_list_t *x = c4m_list(c4m_type_utf8()); + + c4m_list_append(x, c4m_cstr_format("{:x}", c4m_box_u64(pc))); + c4m_list_append(x, file); + c4m_list_append(x, fn); + c4m_grid_add_row(c4m_trace_grid, x); + + return 0; +}; + +#define backtrace_core() \ + c4m_trace_grid = c4m_new(c4m_type_grid(), \ + c4m_kw("start_cols", \ + c4m_ka(3), \ + "start_rows", \ + c4m_ka(2), \ + "header_rows", \ + c4m_ka(1), \ + "container_tag", \ + c4m_ka("table"), \ + "striped", \ + c4m_ka(true))); \ + \ + c4m_list_t *x = c4m_list(c4m_type_utf8()); \ + c4m_list_append(x, c4m_new_utf8("PC")); \ + c4m_list_append(x, c4m_new_utf8("Location")); \ + c4m_list_append(x, c4m_new_utf8("Function")); \ + \ + c4m_grid_add_row(c4m_trace_grid, x); \ + \ + c4m_snap_column(c4m_trace_grid, 0); \ + c4m_snap_column(c4m_trace_grid, 1); \ + c4m_snap_column(c4m_trace_grid, 2); \ + \ + backtrace_full(btstate, 0, c4m_bt_create_backtrace, c4m_bt_err, NULL); + +void +c4m_print_c_backtrace() +{ + backtrace_core(); + c4m_printf("[h6]C Stack Trace:"); + c4m_print(c4m_trace_grid); +} + +static void +c4m_crash_handler(int n) +{ + c4m_print(c4m_callout(c4m_new_utf8("PROGRAM CRASHED."))); + backtrace_core(); + c4m_printf("[h6]C Stack Trace:"); + c4m_print(c4m_trace_grid); + _exit(-4); +} + +void +c4m_backtrace_init(char *fname) +{ + signal(SIGSEGV, c4m_crash_handler); + btstate = backtrace_create_state(fname, 1, c4m_bt_err, NULL); +} + +c4m_grid_t * +c4m_get_c_backtrace() +{ + backtrace_core(); + return c4m_trace_grid; +} +#else +c4m_grid_t * +c4m_get_c_backtrace() +{ + return c4m_callout(c4m_new_utf8("Stack traces not enabled.")); +} +#endif diff --git a/src/con4m/init.c b/src/con4m/init.c index 1a5bd816..ce5a00f6 100644 --- a/src/con4m/init.c +++ b/src/con4m/init.c @@ -34,6 +34,7 @@ c4m_init(int argc, char **argv, char **envp) c4m_stashed_argv = argv; c4m_stashed_envp = envp; + c4m_backtrace_init(argv[0]); c4m_gc_openssl(); c4m_initialize_gc(); c4m_gc_set_finalize_callback((void *)c4m_finalize_allocation); diff --git a/src/con4m/string.c b/src/con4m/string.c index fe217943..7292c87a 100644 --- a/src/con4m/string.c +++ b/src/con4m/string.c @@ -577,7 +577,7 @@ c4m_to_utf32(const c4m_utf8_t *instr) return outstr; } - if (!instr || c4m_str_is_u32(instr)) { + if (c4m_str_is_u32(instr)) { return (c4m_utf32_t *)instr; } @@ -595,7 +595,6 @@ c4m_to_utf32(const c4m_utf8_t *instr) for (int i = 0; i < len; i++) { int val = utf8proc_iterate(inp, 4, outp + i); if (val < 0) { - printf("i = %d\n", i); C4M_CRAISE("Invalid utf8 in string when convering to utf32."); } inp += val; @@ -709,8 +708,8 @@ utf32_init(c4m_utf32_t *s, va_list args) "When specifying 'codepoints', must provide a valid " "'length' containing the number of codepoints."); } - s->byte_len = (length + 1) * 4; - s->data = c4m_gc_raw_alloc(s->byte_len, NULL); + s->byte_len = length * 4; + s->data = c4m_gc_raw_alloc((length + 1) * 4, NULL); c4m_codepoint_t *local = (c4m_codepoint_t *)s->data; @@ -731,8 +730,8 @@ utf32_init(c4m_utf32_t *s, va_list args) "Invalid string constructor call: " "len(cstring) is less than the start index"); } - s->byte_len = (length + 1) * 4; - s->data = c4m_gc_raw_alloc(s->byte_len, NULL); + s->byte_len = length * 4; + s->data = c4m_gc_raw_alloc((length + 1) * 4, NULL); for (int64_t i = 0; i < length; i++) { ((uint32_t *)s->data)[i] = (uint32_t)(cstring[i]); @@ -745,8 +744,8 @@ utf32_init(c4m_utf32_t *s, va_list args) "Must specify a valid length if not initializing " "with a null-terminated cstring."); } - s->byte_len = (length + 1) * 4; - s->data = c4m_gc_raw_alloc(s->byte_len, NULL); + s->byte_len = length * 4; + s->data = c4m_gc_raw_alloc((length + 1) * 4, NULL); } } @@ -1318,7 +1317,7 @@ c4m_string_unmarshal(c4m_str_t *s, c4m_stream_t *in, c4m_dict_t *memos) } if (s->byte_len) { - s->data = c4m_gc_raw_alloc(s->byte_len + 1, NULL); + s->data = c4m_gc_raw_alloc(s->byte_len + 4, NULL); c4m_stream_raw_read(in, s->byte_len, s->data); } } diff --git a/src/con4m/tree.c b/src/con4m/tree.c index 2dc03512..2540469c 100644 --- a/src/con4m/tree.c +++ b/src/con4m/tree.c @@ -168,6 +168,8 @@ tree_node_unmarshal(c4m_tree_node_t *t, c4m_stream_t *s, c4m_dict_t *memos) } } +bool print_xform_info = false; + c4m_tree_node_t * c4m_tree_str_transform(c4m_tree_node_t *t, c4m_str_t *(*fn)(void *)) { diff --git a/src/con4m/vm.c b/src/con4m/vm.c index a45a1420..d2bf45f2 100644 --- a/src/con4m/vm.c +++ b/src/con4m/vm.c @@ -1272,9 +1272,11 @@ c4m_vm_runloop(c4m_vmthread_t *tstate_arg) case C4M_ZTypeCmp: STACK_REQUIRE_VALUES(2); do { - c4m_type_t *t1 = tstate->sp->rvalue.obj; + c4m_type_t *t1 = tstate->sp[0].rvalue.obj; + c4m_type_t *t2 = tstate->sp[1].rvalue.obj; + ++tstate->sp; - c4m_type_t *t2 = tstate->sp->rvalue.obj; + // Does NOT check for coercible. tstate->sp->uint = (uint64_t)c4m_types_are_compat(t1, t2,