Commit 59ac7b91da

Andrew Kelley <andrew@ziglang.org>
2019-10-03 23:58:22
add -fdump-analysis to dump type information to json
This commit adds -fdump-analysis which creates a `$NAME-analysis.json` file with all of the finished semantic analysis that the stage1 compiler produced. It contains types, packages, declarations, and files. This is an initial implementation; some data will be missing. However it's easy to improve the implementation, which is in `src/dump_analysis.cpp`. The next step for #21 will be to create Zig code which parses this json file and creates user-facing HTML documentation. This feature has other uses, however; for example, it could be used for IDE integration features until the self-hosted compiler is available.
1 parent 7640bec
src/all_types.hpp
@@ -1300,6 +1300,12 @@ struct ZigTypeEnum {
 uint32_t type_ptr_hash(const ZigType *ptr);
 bool type_ptr_eql(const ZigType *a, const ZigType *b);
 
+uint32_t pkg_ptr_hash(const ZigPackage *ptr);
+bool pkg_ptr_eql(const ZigPackage *a, const ZigPackage *b);
+
+uint32_t tld_ptr_hash(const Tld *ptr);
+bool tld_ptr_eql(const Tld *a, const Tld *b);
+
 struct ZigTypeUnion {
     AstNode *decl_node;
     TypeUnionField *fields;
@@ -2056,6 +2062,7 @@ struct CodeGen {
     bool have_dynamic_link; // this is whether the final thing will be dynamically linked. see also is_dynamic
     bool have_stack_probing;
     bool function_sections;
+    bool enable_dump_analysis;
 
     Buf *mmacosx_version_min;
     Buf *mios_version_min;
src/analyze.cpp
@@ -7303,6 +7303,22 @@ bool type_ptr_eql(const ZigType *a, const ZigType *b) {
     return a == b;
 }
 
+uint32_t pkg_ptr_hash(const ZigPackage *ptr) {
+    return hash_ptr((void*)ptr);
+}
+
+bool pkg_ptr_eql(const ZigPackage *a, const ZigPackage *b) {
+    return a == b;
+}
+
+uint32_t tld_ptr_hash(const Tld *ptr) {
+    return hash_ptr((void*)ptr);
+}
+
+bool tld_ptr_eql(const Tld *a, const Tld *b) {
+    return a == b;
+}
+
 ConstExprValue *get_builtin_value(CodeGen *codegen, const char *name) {
     Tld *tld = get_container_scope(codegen->compile_var_import)->decl_table.get(buf_create_from_str(name));
     resolve_top_level_decl(codegen, tld, nullptr, false);
src/codegen.cpp
@@ -20,6 +20,7 @@
 #include "util.hpp"
 #include "zig_llvm.h"
 #include "userland.h"
+#include "dump_analysis.hpp"
 
 #include <stdio.h>
 #include <errno.h>
@@ -1724,7 +1725,7 @@ static LLVMValueRef ir_llvm_value(CodeGen *g, IrInstruction *instruction) {
 }
 
 ATTRIBUTE_NORETURN
-static void report_errors_and_exit(CodeGen *g) {
+void codegen_report_errors_and_exit(CodeGen *g) {
     assert(g->errors.length != 0);
     for (size_t i = 0; i < g->errors.length; i += 1) {
         ErrorMsg *err = g->errors.at(i);
@@ -1735,7 +1736,7 @@ static void report_errors_and_exit(CodeGen *g) {
 
 static void report_errors_and_maybe_exit(CodeGen *g) {
     if (g->errors.length != 0) {
-        report_errors_and_exit(g);
+        codegen_report_errors_and_exit(g);
     }
 }
 
@@ -1745,7 +1746,7 @@ static void give_up_with_c_abi_error(CodeGen *g, AstNode *source_node) {
             buf_sprintf("TODO: support C ABI for more targets. https://github.com/ziglang/zig/issues/1481"));
     add_error_note(g, msg, source_node,
         buf_sprintf("pointers, integers, floats, bools, and enums work on all targets"));
-    report_errors_and_exit(g);
+    codegen_report_errors_and_exit(g);
 }
 
 static LLVMValueRef build_alloca(CodeGen *g, ZigType *type_entry, const char *name, uint32_t alignment) {
@@ -3456,7 +3457,7 @@ static bool value_is_all_undef(CodeGen *g, ConstExprValue *const_val) {
     Error err;
     if (const_val->special == ConstValSpecialLazy &&
         (err = ir_resolve_lazy(g, nullptr, const_val)))
-        report_errors_and_exit(g);
+        codegen_report_errors_and_exit(g);
 
     switch (const_val->special) {
         case ConstValSpecialLazy:
@@ -4253,7 +4254,7 @@ static LLVMValueRef ir_render_struct_field_ptr(CodeGen *g, IrExecutable *executa
     ZigType *struct_type = (struct_ptr_type->id == ZigTypeIdPointer) ?
         struct_ptr_type->data.pointer.child_type : struct_ptr_type;
     if ((err = type_resolve(g, struct_type, ResolveStatusLLVMFull)))
-        report_errors_and_exit(g);
+        codegen_report_errors_and_exit(g);
 
     assert(field->gen_index != SIZE_MAX);
     return LLVMBuildStructGEP(g->builder, struct_ptr, (unsigned)field->gen_index, "");
@@ -6625,7 +6626,7 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val, const c
 check: switch (const_val->special) {
         case ConstValSpecialLazy:
             if ((err = ir_resolve_lazy(g, nullptr, const_val))) {
-                report_errors_and_exit(g);
+                codegen_report_errors_and_exit(g);
             }
             goto check;
         case ConstValSpecialRuntime:
@@ -10157,6 +10158,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
     cache_bool(ch, g->have_stack_probing);
     cache_bool(ch, g->is_dummy_so);
     cache_bool(ch, g->function_sections);
+    cache_bool(ch, g->enable_dump_analysis);
     cache_buf_opt(ch, g->mmacosx_version_min);
     cache_buf_opt(ch, g->mios_version_min);
     cache_usize(ch, g->version_major);
@@ -10338,6 +10340,21 @@ void codegen_build_and_link(CodeGen *g) {
                 gen_h_file(g);
             }
         }
+        if (g->enable_dump_analysis) {
+            const char *analysis_json_filename = buf_ptr(buf_sprintf("%s" OS_SEP "%s-analysis.json",
+                        buf_ptr(g->output_dir), buf_ptr(g->root_out_name)));
+            FILE *f = fopen(analysis_json_filename, "wb");
+            if (f == nullptr) {
+                fprintf(stderr, "Unable to open '%s': %s\n", analysis_json_filename, strerror(errno));
+                exit(1);
+            }
+            zig_print_analysis_dump(g, f);
+            if (fclose(f) != 0) {
+                fprintf(stderr, "Unable to write '%s': %s\n", analysis_json_filename, strerror(errno));
+                exit(1);
+            }
+
+        }
 
         // If we're outputting assembly or llvm IR we skip linking.
         // If we're making a library or executable we must link.
src/codegen.hpp
@@ -64,4 +64,6 @@ void codegen_release_caches(CodeGen *codegen);
 bool codegen_fn_has_err_ret_tracing_arg(CodeGen *g, ZigType *return_type);
 bool codegen_fn_has_err_ret_tracing_stack(CodeGen *g, ZigFn *fn, bool is_async);
 
+void codegen_report_errors_and_exit(CodeGen *g);
+
 #endif
src/dump_analysis.cpp
@@ -0,0 +1,769 @@
+/*
+ * Copyright (c) 2019 Andrew Kelley
+ *
+ * This file is part of zig, which is MIT licensed.
+ * See http://opensource.org/licenses/MIT
+ */
+
+#include "dump_analysis.hpp"
+#include "compiler.hpp"
+#include "analyze.hpp"
+#include "config.h"
+#include "ir.hpp"
+#include "codegen.hpp"
+
+enum JsonWriterState {
+    JsonWriterStateInvalid,
+    JsonWriterStateValue,
+    JsonWriterStateArrayStart,
+    JsonWriterStateArray,
+    JsonWriterStateObjectStart,
+    JsonWriterStateObject,
+};
+
+#define JSON_MAX_DEPTH 10
+
+struct JsonWriter {
+    size_t state_index;
+    FILE *f;
+    const char *one_indent;
+    const char *nl;
+    JsonWriterState state[JSON_MAX_DEPTH];
+};
+
+static void jw_init(JsonWriter *jw, FILE *f, const char *one_indent, const char *nl) {
+    jw->state_index = 1;
+    jw->f = f;
+    jw->one_indent = one_indent;
+    jw->nl = nl;
+    jw->state[0] = JsonWriterStateInvalid;
+    jw->state[1] = JsonWriterStateValue;
+}
+
+static void jw_nl_indent(JsonWriter *jw) {
+    assert(jw->state_index >= 1);
+    fprintf(jw->f, "%s", jw->nl);
+    for (size_t i = 0; i < jw->state_index - 1; i += 1) {
+        fprintf(jw->f, jw->one_indent);
+    }
+}
+
+static void jw_push_state(JsonWriter *jw, JsonWriterState state) {
+    jw->state_index += 1;
+    assert(jw->state_index < JSON_MAX_DEPTH);
+    jw->state[jw->state_index] = state;
+}
+
+static void jw_pop_state(JsonWriter *jw) {
+    assert(jw->state_index != 0);
+    jw->state_index -= 1;
+}
+
+static void jw_begin_array(JsonWriter *jw) {
+    assert(jw->state[jw->state_index] == JsonWriterStateValue);
+    fprintf(jw->f, "[");
+    jw->state[jw->state_index] = JsonWriterStateArrayStart;
+}
+
+static void jw_begin_object(JsonWriter *jw) {
+    assert(jw->state[jw->state_index] == JsonWriterStateValue);
+    fprintf(jw->f, "{");
+    jw->state[jw->state_index] = JsonWriterStateObjectStart;
+}
+
+static void jw_array_elem(JsonWriter *jw) {
+    switch (jw->state[jw->state_index]) {
+        case JsonWriterStateInvalid:
+        case JsonWriterStateValue:
+        case JsonWriterStateObjectStart:
+        case JsonWriterStateObject:
+            zig_unreachable();
+        case JsonWriterStateArray:
+            fprintf(jw->f, ",");
+            // fallthrough
+        case JsonWriterStateArrayStart:
+            jw->state[jw->state_index] = JsonWriterStateArray;
+            jw_push_state(jw, JsonWriterStateValue);
+            jw_nl_indent(jw);
+            return;
+    }
+    zig_unreachable();
+}
+
+static void jw_write_escaped_string(JsonWriter *jw, const char *s) {
+    fprintf(jw->f, "\"");
+    for (;; s += 1) {
+        switch (*s) {
+            case 0:
+                fprintf(jw->f, "\"");
+                return;
+            case '"':
+                fprintf(jw->f, "\\\"");
+                continue;
+            case '\t':
+                fprintf(jw->f, "\\t");
+                continue;
+            case '\r':
+                fprintf(jw->f, "\\r");
+                continue;
+            case '\n':
+                fprintf(jw->f, "\\n");
+                continue;
+            case '\b':
+                fprintf(jw->f, "\\b");
+                continue;
+            case '\f':
+                fprintf(jw->f, "\\f");
+                continue;
+            case '\\':
+                fprintf(jw->f, "\\\\");
+                continue;
+            default:
+                fprintf(jw->f, "%c", *s);
+                continue;
+        }
+    }
+}
+
+static void jw_object_field(JsonWriter *jw, const char *name) {
+    switch (jw->state[jw->state_index]) {
+        case JsonWriterStateInvalid:
+        case JsonWriterStateValue:
+        case JsonWriterStateArray:
+        case JsonWriterStateArrayStart:
+            zig_unreachable();
+        case JsonWriterStateObject:
+            fprintf(jw->f, ",");
+            // fallthrough
+        case JsonWriterStateObjectStart:
+            jw->state[jw->state_index] = JsonWriterStateObject;
+            jw_push_state(jw, JsonWriterStateValue);
+            jw_nl_indent(jw);
+            jw_write_escaped_string(jw, name);
+            fprintf(jw->f, ": ");
+            return;
+    }
+    zig_unreachable();
+}
+
+static void jw_end_array(JsonWriter *jw) {
+    switch (jw->state[jw->state_index]) {
+        case JsonWriterStateInvalid:
+        case JsonWriterStateValue:
+        case JsonWriterStateObjectStart:
+        case JsonWriterStateObject:
+            zig_unreachable();
+        case JsonWriterStateArrayStart:
+            fprintf(jw->f, "]");
+            jw_pop_state(jw);
+            return;
+        case JsonWriterStateArray:
+            jw_nl_indent(jw);
+            jw_pop_state(jw);
+            fprintf(jw->f, "]");
+            return;
+    }
+    zig_unreachable();
+}
+
+
+static void jw_end_object(JsonWriter *jw) {
+    switch (jw->state[jw->state_index]) {
+        case JsonWriterStateInvalid:
+            zig_unreachable();
+        case JsonWriterStateValue:
+            zig_unreachable();
+        case JsonWriterStateArray:
+            zig_unreachable();
+        case JsonWriterStateArrayStart:
+            zig_unreachable();
+        case JsonWriterStateObjectStart:
+            fprintf(jw->f, "}");
+            jw_pop_state(jw);
+            return;
+        case JsonWriterStateObject:
+            jw_nl_indent(jw);
+            jw_pop_state(jw);
+            fprintf(jw->f, "}");
+            return;
+    }
+    zig_unreachable();
+}
+
+static void jw_null(JsonWriter *jw) {
+    assert(jw->state[jw->state_index] == JsonWriterStateValue);
+    fprintf(jw->f, "null");
+    jw_pop_state(jw);
+}
+
+static void jw_bool(JsonWriter *jw, bool x) {
+    assert(jw->state[jw->state_index] == JsonWriterStateValue);
+    if (x) {
+        fprintf(jw->f, "true");
+    } else {
+        fprintf(jw->f, "false");
+    }
+    jw_pop_state(jw);
+}
+
+static void jw_int(JsonWriter *jw, int64_t x) {
+    assert(jw->state[jw->state_index] == JsonWriterStateValue);
+    if (x > 4503599627370496 || x < -4503599627370496) {
+        fprintf(jw->f, "\"%" ZIG_PRI_i64 "\"", x);
+    } else {
+        fprintf(jw->f, "%" ZIG_PRI_i64, x);
+    }
+    jw_pop_state(jw);
+}
+
+static void jw_string(JsonWriter *jw, const char *s) {
+    assert(jw->state[jw->state_index] == JsonWriterStateValue);
+    jw_write_escaped_string(jw, s);
+    jw_pop_state(jw);
+}
+
+
+static void tree_print(FILE *f, ZigType *ty, size_t indent);
+
+static void pretty_print_bytes(FILE *f, double n) {
+    if (n > 1024.0 * 1024.0 * 1024.0) {
+        fprintf(f, "%.02f GiB", n / 1024.0 / 1024.0 / 1024.0);
+        return;
+    }
+    if (n > 1024.0 * 1024.0) {
+        fprintf(f, "%.02f MiB", n / 1024.0 / 1024.0);
+        return;
+    }
+    if (n > 1024.0) {
+        fprintf(f, "%.02f KiB", n / 1024.0);
+        return;
+    }
+    fprintf(f, "%.02f bytes", n );
+    return;
+}
+
+static int compare_type_abi_sizes_desc(const void *a, const void *b) {
+    uint64_t size_a = (*(ZigType * const*)(a))->abi_size;
+    uint64_t size_b = (*(ZigType * const*)(b))->abi_size;
+    if (size_a > size_b)
+        return -1;
+    if (size_a < size_b)
+        return 1;
+    return 0;
+}
+
+static void start_child(FILE *f, size_t indent) {
+    fprintf(f, "\n");
+    for (size_t i = 0; i < indent; i += 1) {
+        fprintf(f, " ");
+    }
+}
+
+static void start_peer(FILE *f, size_t indent) {
+    fprintf(f, ",\n");
+    for (size_t i = 0; i < indent; i += 1) {
+        fprintf(f, " ");
+    }
+}
+
+static void tree_print_struct(FILE *f, ZigType *struct_type, size_t indent) {
+    ZigList<ZigType *> children = {};
+    uint64_t sum_from_fields = 0;
+    for (size_t i = 0; i < struct_type->data.structure.src_field_count; i += 1) {
+        TypeStructField *field = &struct_type->data.structure.fields[i];
+        children.append(field->type_entry);
+        sum_from_fields += field->type_entry->abi_size;
+    }
+    qsort(children.items, children.length, sizeof(ZigType *), compare_type_abi_sizes_desc);
+
+    start_peer(f, indent);
+    fprintf(f, "\"padding\": \"%" ZIG_PRI_u64 "\"", struct_type->abi_size - sum_from_fields);
+
+    start_peer(f, indent);
+    fprintf(f, "\"fields\": [");
+
+    for (size_t i = 0; i < children.length; i += 1) {
+        if (i == 0) {
+            start_child(f, indent + 1);
+        } else {
+            start_peer(f, indent + 1);
+        }
+        fprintf(f, "{");
+
+        ZigType *child_type = children.at(i);
+        tree_print(f, child_type, indent + 2);
+
+        start_child(f, indent + 1);
+        fprintf(f, "}");
+    }
+
+    start_child(f, indent);
+    fprintf(f, "]");
+}
+
+static void tree_print(FILE *f, ZigType *ty, size_t indent) {
+    start_child(f, indent);
+    fprintf(f, "\"type\": \"%s\"", buf_ptr(&ty->name));
+
+    start_peer(f, indent);
+    fprintf(f, "\"sizef\": \"");
+    pretty_print_bytes(f, ty->abi_size);
+    fprintf(f, "\"");
+
+    start_peer(f, indent);
+    fprintf(f, "\"size\": \"%" ZIG_PRI_usize "\"", ty->abi_size);
+
+    switch (ty->id) {
+        case ZigTypeIdFnFrame:
+            return tree_print_struct(f, ty->data.frame.locals_struct, indent);
+        case ZigTypeIdStruct:
+            return tree_print_struct(f, ty, indent);
+        default:
+            start_child(f, indent);
+            return;
+    }
+}
+
+void zig_print_stack_report(CodeGen *g, FILE *f) {
+    if (g->largest_frame_fn == nullptr) {
+        fprintf(f, "{\"error\": \"No async function frames in entire compilation.\"}\n");
+        return;
+    }
+    fprintf(f, "{");
+    tree_print(f, g->largest_frame_fn->frame_type, 1);
+
+    start_child(f, 0);
+    fprintf(f, "}\n");
+}
+
+struct AnalDumpCtx {
+    CodeGen *g;
+    JsonWriter jw;
+
+    ZigList<ZigType *> type_list;
+    HashMap<const ZigType *, uint32_t, type_ptr_hash, type_ptr_eql> type_map;
+
+    ZigList<ZigPackage *> pkg_list;
+    HashMap<const ZigPackage *, uint32_t, pkg_ptr_hash, pkg_ptr_eql> pkg_map;
+
+    ZigList<Buf *> file_list;
+    HashMap<Buf *, uint32_t, buf_hash, buf_eql_buf> file_map;
+
+    ZigList<Tld *> decl_list;
+    HashMap<const Tld *, uint32_t, tld_ptr_hash, tld_ptr_eql> decl_map;
+};
+
+static uint32_t anal_dump_get_type_id(AnalDumpCtx *ctx, ZigType *ty);
+static void anal_dump_value(AnalDumpCtx *ctx, AstNode *source_node, ZigType *ty, ConstExprValue *value);
+
+static void anal_dump_poke_value(AnalDumpCtx *ctx, AstNode *source_node, ZigType *ty, ConstExprValue *value) {
+    Error err;
+    if (value->type != ty) {
+        return;
+    }
+    if ((err = ir_resolve_lazy(ctx->g, source_node, value))) {
+        codegen_report_errors_and_exit(ctx->g);
+    }
+    if (value->special == ConstValSpecialUndef) {
+        return;
+    }
+    if (value->special == ConstValSpecialRuntime) {
+        return;
+    }
+    switch (ty->id) {
+        case ZigTypeIdMetaType: {
+            ZigType *val_ty = value->data.x_type;
+            (void)anal_dump_get_type_id(ctx, val_ty);
+            return;
+        }
+        default:
+            return;
+    }
+    zig_unreachable();
+}
+
+static uint32_t anal_dump_get_type_id(AnalDumpCtx *ctx, ZigType *ty) {
+    uint32_t type_id = ctx->type_list.length;
+    auto existing_entry = ctx->type_map.put_unique(ty, type_id);
+    if (existing_entry == nullptr) {
+        ctx->type_list.append(ty);
+    } else {
+        type_id = existing_entry->value;
+    }
+    return type_id;
+}
+
+static uint32_t anal_dump_get_pkg_id(AnalDumpCtx *ctx, ZigPackage *pkg) {
+    assert(pkg != nullptr);
+    uint32_t pkg_id = ctx->pkg_list.length;
+    auto existing_entry = ctx->pkg_map.put_unique(pkg, pkg_id);
+    if (existing_entry == nullptr) {
+        ctx->pkg_list.append(pkg);
+    } else {
+        pkg_id = existing_entry->value;
+    }
+    return pkg_id;
+}
+
+static uint32_t anal_dump_get_file_id(AnalDumpCtx *ctx, Buf *file) {
+    uint32_t file_id = ctx->file_list.length;
+    auto existing_entry = ctx->file_map.put_unique(file, file_id);
+    if (existing_entry == nullptr) {
+        ctx->file_list.append(file);
+    } else {
+        file_id = existing_entry->value;
+    }
+    return file_id;
+}
+
+static uint32_t anal_dump_get_decl_id(AnalDumpCtx *ctx, Tld *tld) {
+    uint32_t decl_id = ctx->decl_list.length;
+    auto existing_entry = ctx->decl_map.put_unique(tld, decl_id);
+    if (existing_entry == nullptr) {
+        ctx->decl_list.append(tld);
+
+        if (tld->import != nullptr) {
+            (void)anal_dump_get_type_id(ctx, tld->import);
+        }
+
+        // poke the types
+        switch (tld->id) {
+            case TldIdVar: {
+                TldVar *tld_var = reinterpret_cast<TldVar *>(tld);
+                ZigVar *var = tld_var->var;
+
+                if (var != nullptr) {
+                    (void)anal_dump_get_type_id(ctx, var->var_type);
+
+                    if (var->const_value != nullptr) {
+                        anal_dump_poke_value(ctx, var->decl_node, var->var_type, var->const_value);
+                    }
+                }
+                break;
+            }
+            case TldIdFn: {
+                TldFn *tld_fn = reinterpret_cast<TldFn *>(tld);
+                ZigFn *fn = tld_fn->fn_entry;
+
+                if (fn != nullptr) {
+                    (void)anal_dump_get_type_id(ctx, fn->type_entry);
+                }
+                break;
+            }
+            default:
+                break;
+        }
+
+    } else {
+        decl_id = existing_entry->value;
+    }
+    return decl_id;
+}
+
+static void anal_dump_type_ref(AnalDumpCtx *ctx, ZigType *ty) {
+    uint32_t type_id = anal_dump_get_type_id(ctx, ty);
+    jw_int(&ctx->jw, type_id);
+}
+
+static void anal_dump_pkg_ref(AnalDumpCtx *ctx, ZigPackage *pkg) {
+    uint32_t pkg_id = anal_dump_get_pkg_id(ctx, pkg);
+    jw_int(&ctx->jw, pkg_id);
+}
+
+static void anal_dump_file_ref(AnalDumpCtx *ctx, Buf *file) {
+    uint32_t file_id = anal_dump_get_file_id(ctx, file);
+    jw_int(&ctx->jw, file_id);
+}
+
+static void anal_dump_decl_ref(AnalDumpCtx *ctx, Tld *tld) {
+    uint32_t decl_id = anal_dump_get_decl_id(ctx, tld);
+    jw_int(&ctx->jw, decl_id);
+}
+
+static void anal_dump_pkg(AnalDumpCtx *ctx, ZigPackage *pkg) {
+    JsonWriter *jw = &ctx->jw;
+    jw_array_elem(jw);
+    jw_begin_object(jw);
+
+    jw_object_field(jw, "name");
+    jw_string(jw, buf_ptr(&pkg->pkg_path));
+
+    jw_object_field(jw, "file");
+    Buf full_path_buf = BUF_INIT;
+    os_path_join(&pkg->root_src_dir, &pkg->root_src_path, &full_path_buf);
+    Buf *resolve_paths[] = { &full_path_buf, };
+    Buf *resolved_path = buf_alloc();
+    *resolved_path = os_path_resolve(resolve_paths, 1);
+    anal_dump_file_ref(ctx, resolved_path);
+
+    auto import_entry = ctx->g->import_table.maybe_get(resolved_path);
+    if (!import_entry) {
+        fprintf(stderr, "due to a race condition or bug, files moved around during analysis\n");
+        exit(1);
+    }
+    jw_object_field(jw, "main");
+    anal_dump_type_ref(ctx, import_entry->value);
+
+    jw_object_field(jw, "table");
+    jw_begin_object(jw);
+    auto it = pkg->package_table.entry_iterator();
+    for (;;) {
+        auto *entry = it.next();
+        if (!entry)
+            break;
+
+        ZigPackage *child_pkg = entry->value;
+        if (child_pkg != nullptr) {
+            jw_object_field(jw, buf_ptr(entry->key));
+            anal_dump_pkg_ref(ctx, child_pkg);
+        }
+    }
+    jw_end_object(jw);
+
+    jw_end_object(jw);
+}
+
+static void anal_dump_decl(AnalDumpCtx *ctx, Tld *tld) {
+    JsonWriter *jw = &ctx->jw;
+
+    bool make_obj = tld->id == TldIdVar || tld->id == TldIdFn;
+    if (make_obj) {
+        jw_array_elem(jw);
+        jw_begin_object(jw);
+
+        jw_object_field(jw, "import");
+        anal_dump_type_ref(ctx, tld->import);
+
+        jw_object_field(jw, "line");
+        jw_int(jw, tld->source_node->line);
+
+        jw_object_field(jw, "col");
+        jw_int(jw, tld->source_node->column);
+
+        jw_object_field(jw, "name");
+        jw_string(jw, buf_ptr(tld->name));
+    }
+
+    switch (tld->id) {
+        case TldIdVar: {
+            TldVar *tld_var = reinterpret_cast<TldVar *>(tld);
+            ZigVar *var = tld_var->var;
+
+            if (var != nullptr) {
+                jw_object_field(jw, "kind");
+                if (var->src_is_const) {
+                    jw_string(jw, "const");
+                } else {
+                    jw_string(jw, "var");
+                }
+
+                if (var->is_thread_local) {
+                    jw_object_field(jw, "threadlocal");
+                    jw_bool(jw, true);
+                }
+
+                jw_object_field(jw, "type");
+                anal_dump_type_ref(ctx, var->var_type);
+
+                if (var->const_value != nullptr) {
+                    jw_object_field(jw, "value");
+                    anal_dump_value(ctx, var->decl_node, var->var_type, var->const_value);
+                }
+            }
+            break;
+        }
+        case TldIdFn: {
+            TldFn *tld_fn = reinterpret_cast<TldFn *>(tld);
+            ZigFn *fn = tld_fn->fn_entry;
+
+            if (fn != nullptr) {
+                jw_object_field(jw, "kind");
+                jw_string(jw, "const");
+
+                jw_object_field(jw, "type");
+                anal_dump_type_ref(ctx, fn->type_entry);
+            }
+
+            break;
+        }
+        default:
+            break;
+    }
+
+    if (make_obj) {
+        jw_end_object(jw);
+    }
+}
+
+static void anal_dump_file(AnalDumpCtx *ctx, Buf *file) {
+    JsonWriter *jw = &ctx->jw;
+    jw_string(jw, buf_ptr(file));
+}
+
+static void anal_dump_value(AnalDumpCtx *ctx, AstNode *source_node, ZigType *ty, ConstExprValue *value) {
+    Error err;
+
+    if (value->type != ty) {
+        jw_null(&ctx->jw);
+        return;
+    }
+    if ((err = ir_resolve_lazy(ctx->g, source_node, value))) {
+        codegen_report_errors_and_exit(ctx->g);
+    }
+    if (value->special == ConstValSpecialUndef) {
+        jw_string(&ctx->jw, "undefined");
+        return;
+    }
+    if (value->special == ConstValSpecialRuntime) {
+        jw_null(&ctx->jw);
+        return;
+    }
+    switch (ty->id) {
+        case ZigTypeIdMetaType: {
+            ZigType *val_ty = value->data.x_type;
+            anal_dump_type_ref(ctx, val_ty);
+            return;
+        }
+        default:
+            jw_null(&ctx->jw);
+            return;
+    }
+    zig_unreachable();
+}
+
+static void anal_dump_type(AnalDumpCtx *ctx, ZigType *ty) {
+    JsonWriter *jw = &ctx->jw;
+    jw_array_elem(jw);
+    jw_begin_object(jw);
+
+    jw_object_field(jw, "name");
+    jw_string(jw, buf_ptr(&ty->name));
+
+    jw_object_field(jw, "kind");
+    jw_int(jw, type_id_index(ty));
+
+    switch (ty->id) {
+        case ZigTypeIdStruct: {
+            if (ty->data.structure.is_slice) {
+                // TODO
+                break;
+            }
+            jw_object_field(jw, "decls");
+            jw_begin_array(jw);
+
+            ScopeDecls *decls_scope = ty->data.structure.decls_scope;
+            auto it = decls_scope->decl_table.entry_iterator();
+            for (;;) {
+                auto *entry = it.next();
+                if (!entry)
+                    break;
+
+                Tld *tld = entry->value;
+
+                jw_array_elem(jw);
+                anal_dump_decl_ref(ctx, tld);
+            }
+            jw_end_array(jw);
+
+            if (ty->data.structure.root_struct != nullptr) {
+                Buf *path_buf = ty->data.structure.root_struct->path;
+
+                jw_object_field(jw, "file");
+                anal_dump_file_ref(ctx, path_buf);
+
+            }
+            break;
+        }
+        case ZigTypeIdFloat: {
+            jw_object_field(jw, "bits");
+            jw_int(jw, ty->data.floating.bit_count);
+            break;
+        }
+        default:
+            // TODO
+            break;
+    }
+    jw_end_object(jw);
+}
+
+void zig_print_analysis_dump(CodeGen *g, FILE *f) {
+    Error err;
+    AnalDumpCtx ctx = {};
+    ctx.g = g;
+    JsonWriter *jw = &ctx.jw;
+    jw_init(jw, f, " ", "\n");
+    ctx.type_map.init(16);
+    ctx.pkg_map.init(16);
+    ctx.file_map.init(16);
+    ctx.decl_map.init(16);
+
+    jw_begin_object(jw);
+
+    jw_object_field(jw, "typeKinds");
+    jw_begin_array(jw);
+    for (size_t i = 0; i < type_id_len(); i += 1) {
+        jw_array_elem(jw);
+        jw_string(jw, type_id_name(type_id_at_index(i)));
+    }
+    jw_end_array(jw);
+
+    jw_object_field(jw, "params");
+    jw_begin_object(jw);
+    {
+        jw_object_field(jw, "zigId");
+
+        Buf *compiler_id;
+        if ((err = get_compiler_id(&compiler_id))) {
+            fprintf(stderr, "Unable to determine compiler id: %s\n", err_str(err));
+            exit(1);
+        }
+        jw_string(jw, buf_ptr(compiler_id));
+
+        jw_object_field(jw, "zigVersion");
+        jw_string(jw, ZIG_VERSION_STRING);
+
+        jw_object_field(jw, "target");
+        Buf triple_buf = BUF_INIT;
+        target_triple_zig(&triple_buf, g->zig_target);
+        jw_string(jw, buf_ptr(&triple_buf));
+    }
+    jw_end_object(jw);
+
+    jw_object_field(jw, "rootPkg");
+    anal_dump_pkg_ref(&ctx, g->root_package);
+
+    jw_object_field(jw, "packages");
+    jw_begin_array(jw);
+    for (uint32_t i = 0; i < ctx.pkg_list.length; i += 1) {
+        anal_dump_pkg(&ctx, ctx.pkg_list.at(i));
+    }
+    jw_end_array(jw);
+
+    jw_object_field(jw, "types");
+    jw_begin_array(jw);
+
+    for (uint32_t i = 0; i < ctx.type_list.length; i += 1) {
+        ZigType *ty = ctx.type_list.at(i);
+        anal_dump_type(&ctx, ty);
+    }
+    jw_end_array(jw);
+
+    jw_object_field(jw, "decls");
+    jw_begin_array(jw);
+    for (uint32_t i = 0; i < ctx.decl_list.length; i += 1) {
+        Tld *decl = ctx.decl_list.at(i);
+        anal_dump_decl(&ctx, decl);
+    }
+    jw_end_array(jw);
+
+    jw_object_field(jw, "files");
+    jw_begin_array(jw);
+    for (uint32_t i = 0; i < ctx.file_list.length; i += 1) {
+        Buf *file = ctx.file_list.at(i);
+        jw_array_elem(jw);
+        anal_dump_file(&ctx, file);
+    }
+    jw_end_array(jw);
+
+    jw_end_object(jw);
+}
src/stack_report.hpp → src/dump_analysis.hpp
@@ -5,12 +5,13 @@
  * See http://opensource.org/licenses/MIT
  */
 
-#ifndef ZIG_STACK_REPORT_HPP
-#define ZIG_STACK_REPORT_HPP
+#ifndef ZIG_DUMP_ANALYSIS_HPP
+#define ZIG_DUMP_ANALYSIS_HPP
 
 #include "all_types.hpp"
 #include <stdio.h>
 
 void zig_print_stack_report(CodeGen *g, FILE *f);
+void zig_print_analysis_dump(CodeGen *g, FILE *f);
 
 #endif
src/main.cpp
@@ -16,7 +16,7 @@
 #include "libc_installation.hpp"
 #include "userland.h"
 #include "glibc.hpp"
-#include "stack_report.hpp"
+#include "dump_analysis.hpp"
 
 #include <stdio.h>
 
@@ -64,6 +64,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
         "  -fno-PIC                     disable Position Independent Code\n"
         "  -ftime-report                print timing diagnostics\n"
         "  -fstack-report               print stack size diagnostics\n"
+        "  -fdump-analysis              write analysis.json file for use with zig docs\n"
         "  --libc [file]                Provide a file which specifies libc paths\n"
         "  --name [name]                override output name\n"
         "  --output-dir [dir]           override output directory (defaults to cwd)\n"
@@ -479,6 +480,7 @@ int main(int argc, char **argv) {
     size_t ver_patch = 0;
     bool timing_info = false;
     bool stack_report = false;
+    bool enable_dump_analysis = false;
     const char *cache_dir = nullptr;
     CliPkg *cur_pkg = allocate<CliPkg>(1);
     BuildMode build_mode = BuildModeDebug;
@@ -662,6 +664,8 @@ int main(int argc, char **argv) {
                 timing_info = true;
             } else if (strcmp(arg, "-fstack-report") == 0) {
                 stack_report = true;
+            } else if (strcmp(arg, "-fdump-analysis") == 0) {
+                enable_dump_analysis = true;
             } else if (strcmp(arg, "--enable-valgrind") == 0) {
                 valgrind_support = ValgrindSupportEnabled;
             } else if (strcmp(arg, "--disable-valgrind") == 0) {
@@ -1138,6 +1142,7 @@ int main(int argc, char **argv) {
 
             g->enable_time_report = timing_info;
             g->enable_stack_report = stack_report;
+            g->enable_dump_analysis = enable_dump_analysis;
             codegen_set_out_name(g, buf_out_name);
             codegen_set_lib_version(g, ver_major, ver_minor, ver_patch);
             g->want_single_threaded = want_single_threaded;
src/os.hpp
@@ -47,6 +47,7 @@ extern const char *possible_ld_names[];
 
 #if defined(ZIG_OS_WINDOWS)
 #define ZIG_PRI_usize "I64u"
+#define ZIG_PRI_i64 "I64d"
 #define ZIG_PRI_u64 "I64u"
 #define ZIG_PRI_llu "I64u"
 #define ZIG_PRI_x64 "I64x"
@@ -54,6 +55,7 @@ extern const char *possible_ld_names[];
 #define ZIG_OS_SEP_CHAR '\\'
 #else
 #define ZIG_PRI_usize "zu"
+#define ZIG_PRI_i64 PRId64
 #define ZIG_PRI_u64 PRIu64
 #define ZIG_PRI_llu "llu"
 #define ZIG_PRI_x64 PRIx64
src/stack_report.cpp
@@ -1,121 +0,0 @@
-/*
- * Copyright (c) 2019 Andrew Kelley
- *
- * This file is part of zig, which is MIT licensed.
- * See http://opensource.org/licenses/MIT
- */
-
-#include "stack_report.hpp"
-
-static void tree_print(FILE *f, ZigType *ty, size_t indent);
-
-static void pretty_print_bytes(FILE *f, double n) {
-    if (n > 1024.0 * 1024.0 * 1024.0) {
-        fprintf(f, "%.02f GiB", n / 1024.0 / 1024.0 / 1024.0);
-        return;
-    }
-    if (n > 1024.0 * 1024.0) {
-        fprintf(f, "%.02f MiB", n / 1024.0 / 1024.0);
-        return;
-    }
-    if (n > 1024.0) {
-        fprintf(f, "%.02f KiB", n / 1024.0);
-        return;
-    }
-    fprintf(f, "%.02f bytes", n );
-    return;
-}
-
-static int compare_type_abi_sizes_desc(const void *a, const void *b) {
-    uint64_t size_a = (*(ZigType * const*)(a))->abi_size;
-    uint64_t size_b = (*(ZigType * const*)(b))->abi_size;
-    if (size_a > size_b)
-        return -1;
-    if (size_a < size_b)
-        return 1;
-    return 0;
-}
-
-static void start_child(FILE *f, size_t indent) {
-    fprintf(f, "\n");
-    for (size_t i = 0; i < indent; i += 1) {
-        fprintf(f, " ");
-    }
-}
-
-static void start_peer(FILE *f, size_t indent) {
-    fprintf(f, ",\n");
-    for (size_t i = 0; i < indent; i += 1) {
-        fprintf(f, " ");
-    }
-}
-
-static void tree_print_struct(FILE *f, ZigType *struct_type, size_t indent) {
-    ZigList<ZigType *> children = {};
-    uint64_t sum_from_fields = 0;
-    for (size_t i = 0; i < struct_type->data.structure.src_field_count; i += 1) {
-        TypeStructField *field = &struct_type->data.structure.fields[i];
-        children.append(field->type_entry);
-        sum_from_fields += field->type_entry->abi_size;
-    }
-    qsort(children.items, children.length, sizeof(ZigType *), compare_type_abi_sizes_desc);
-
-    start_peer(f, indent);
-    fprintf(f, "\"padding\": \"%" ZIG_PRI_u64 "\"", struct_type->abi_size - sum_from_fields);
-
-    start_peer(f, indent);
-    fprintf(f, "\"fields\": [");
-
-    for (size_t i = 0; i < children.length; i += 1) {
-        if (i == 0) {
-            start_child(f, indent + 1);
-        } else {
-            start_peer(f, indent + 1);
-        }
-        fprintf(f, "{");
-
-        ZigType *child_type = children.at(i);
-        tree_print(f, child_type, indent + 2);
-
-        start_child(f, indent + 1);
-        fprintf(f, "}");
-    }
-
-    start_child(f, indent);
-    fprintf(f, "]");
-}
-
-static void tree_print(FILE *f, ZigType *ty, size_t indent) {
-    start_child(f, indent);
-    fprintf(f, "\"type\": \"%s\"", buf_ptr(&ty->name));
-
-    start_peer(f, indent);
-    fprintf(f, "\"sizef\": \"");
-    pretty_print_bytes(f, ty->abi_size);
-    fprintf(f, "\"");
-
-    start_peer(f, indent);
-    fprintf(f, "\"size\": \"%" ZIG_PRI_usize "\"", ty->abi_size);
-
-    switch (ty->id) {
-        case ZigTypeIdFnFrame:
-            return tree_print_struct(f, ty->data.frame.locals_struct, indent);
-        case ZigTypeIdStruct:
-            return tree_print_struct(f, ty, indent);
-        default:
-            start_child(f, indent);
-            return;
-    }
-}
-
-void zig_print_stack_report(CodeGen *g, FILE *f) {
-    if (g->largest_frame_fn == nullptr) {
-        fprintf(f, "{\"error\": \"No async function frames in entire compilation.\"}\n");
-        return;
-    }
-    fprintf(f, "{");
-    tree_print(f, g->largest_frame_fn->frame_type, 1);
-
-    start_child(f, 0);
-    fprintf(f, "}\n");
-}
CMakeLists.txt
@@ -443,6 +443,7 @@ set(ZIG_SOURCES
     "${CMAKE_SOURCE_DIR}/src/cache_hash.cpp"
     "${CMAKE_SOURCE_DIR}/src/codegen.cpp"
     "${CMAKE_SOURCE_DIR}/src/compiler.cpp"
+    "${CMAKE_SOURCE_DIR}/src/dump_analysis.cpp"
     "${CMAKE_SOURCE_DIR}/src/errmsg.cpp"
     "${CMAKE_SOURCE_DIR}/src/error.cpp"
     "${CMAKE_SOURCE_DIR}/src/glibc.cpp"
@@ -453,7 +454,6 @@ set(ZIG_SOURCES
     "${CMAKE_SOURCE_DIR}/src/os.cpp"
     "${CMAKE_SOURCE_DIR}/src/parser.cpp"
     "${CMAKE_SOURCE_DIR}/src/range_set.cpp"
-    "${CMAKE_SOURCE_DIR}/src/stack_report.cpp"
     "${CMAKE_SOURCE_DIR}/src/target.cpp"
     "${CMAKE_SOURCE_DIR}/src/tokenizer.cpp"
     "${CMAKE_SOURCE_DIR}/src/translate_c.cpp"