diff --git a/CMakeLists.txt b/CMakeLists.txt index c3ee37ea89..ee9f9c2817 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -390,7 +390,7 @@ if(MSVC) ) else() set_target_properties(embedded_softfloat PROPERTIES - COMPILE_FLAGS "-std=c99" + COMPILE_FLAGS "-std=c99 -O3" ) endif() target_include_directories(embedded_softfloat PUBLIC @@ -409,7 +409,9 @@ set(ZIG_SOURCES "${CMAKE_SOURCE_DIR}/src/bigint.cpp" "${CMAKE_SOURCE_DIR}/src/buffer.cpp" "${CMAKE_SOURCE_DIR}/src/c_tokenizer.cpp" + "${CMAKE_SOURCE_DIR}/src/cache_hash.cpp" "${CMAKE_SOURCE_DIR}/src/codegen.cpp" + "${CMAKE_SOURCE_DIR}/src/compiler.cpp" "${CMAKE_SOURCE_DIR}/src/errmsg.cpp" "${CMAKE_SOURCE_DIR}/src/error.cpp" "${CMAKE_SOURCE_DIR}/src/ir.cpp" @@ -424,6 +426,9 @@ set(ZIG_SOURCES "${CMAKE_SOURCE_DIR}/src/util.cpp" "${CMAKE_SOURCE_DIR}/src/translate_c.cpp" ) +set(BLAKE_SOURCES + "${CMAKE_SOURCE_DIR}/src/blake2b.c" +) set(ZIG_CPP_SOURCES "${CMAKE_SOURCE_DIR}/src/zig_llvm.cpp" "${CMAKE_SOURCE_DIR}/src/windows_sdk.cpp" @@ -790,6 +795,7 @@ else() set(EXE_CFLAGS "${EXE_CFLAGS} -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -D_GNU_SOURCE -fno-exceptions -fno-rtti -Werror=strict-prototypes -Werror=old-style-definition -Werror=type-limits -Wno-missing-braces") endif() +set(BLAKE_CFLAGS "-std=c99") set(EXE_LDFLAGS " ") if(MINGW) @@ -811,6 +817,11 @@ set_target_properties(zig_cpp PROPERTIES COMPILE_FLAGS ${EXE_CFLAGS} ) +add_library(embedded_blake STATIC ${BLAKE_SOURCES}) +set_target_properties(embedded_blake PROPERTIES + COMPILE_FLAGS "${BLAKE_CFLAGS} -O3" +) + add_executable(zig ${ZIG_SOURCES}) set_target_properties(zig PROPERTIES COMPILE_FLAGS ${EXE_CFLAGS} @@ -819,6 +830,7 @@ set_target_properties(zig PROPERTIES target_link_libraries(zig LINK_PUBLIC zig_cpp + embedded_blake ${SOFTFLOAT_LIBRARIES} ${CLANG_LIBRARIES} ${LLD_LIBRARIES} diff --git a/build.zig b/build.zig index d4a159e5e6..e886377139 100644 --- a/build.zig +++ b/build.zig @@ -16,11 +16,12 @@ pub fn build(b: *Builder) !void { var docgen_exe = b.addExecutable("docgen", "doc/docgen.zig"); const rel_zig_exe = try os.path.relative(b.allocator, b.build_root, b.zig_exe); + const langref_out_path = os.path.join(b.allocator, b.cache_root, "langref.html") catch unreachable; var docgen_cmd = b.addCommand(null, b.env_map, [][]const u8{ docgen_exe.getOutputPath(), rel_zig_exe, "doc" ++ os.path.sep_str ++ "langref.html.in", - os.path.join(b.allocator, b.cache_root, "langref.html") catch unreachable, + langref_out_path, }); docgen_cmd.step.dependOn(&docgen_exe.step); diff --git a/doc/docgen.zig b/doc/docgen.zig index c1158dc03f..5d77a1ea14 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -11,6 +11,7 @@ const max_doc_file_size = 10 * 1024 * 1024; const exe_ext = std.build.Target(std.build.Target.Native).exeFileExt(); const obj_ext = std.build.Target(std.build.Target.Native).oFileExt(); const tmp_dir_name = "docgen_tmp"; +const test_out_path = tmp_dir_name ++ os.path.sep_str ++ "test" ++ exe_ext; pub fn main() !void { var direct_allocator = std.heap.DirectAllocator.init(); @@ -821,6 +822,8 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var zig_exe, "test", tmp_source_file_name, + "--output", + test_out_path, }); try out.print("
$ zig test {}.zig", code.name);
                         switch (code.mode) {
@@ -863,6 +866,8 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
                             "--color",
                             "on",
                             tmp_source_file_name,
+                            "--output",
+                            test_out_path,
                         });
                         try out.print("
$ zig test {}.zig", code.name);
                         switch (code.mode) {
@@ -918,6 +923,8 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
                             zig_exe,
                             "test",
                             tmp_source_file_name,
+                            "--output",
+                            test_out_path,
                         });
                         switch (code.mode) {
                             builtin.Mode.Debug => {},
diff --git a/src/all_types.hpp b/src/all_types.hpp
index 6adb53b774..8aa2718d30 100644
--- a/src/all_types.hpp
+++ b/src/all_types.hpp
@@ -10,6 +10,7 @@
 
 #include "list.hpp"
 #include "buffer.hpp"
+#include "cache_hash.hpp"
 #include "zig_llvm.h"
 #include "hash_map.hpp"
 #include "errmsg.hpp"
@@ -1550,22 +1551,50 @@ struct LinkLib {
     bool provided_explicitly;
 };
 
+// When adding fields, check if they should be added to the hash computation in build_with_cache
 struct CodeGen {
+    //////////////////////////// Runtime State
     LLVMModuleRef module;
     ZigList errors;
     LLVMBuilderRef builder;
     ZigLLVMDIBuilder *dbuilder;
     ZigLLVMDICompileUnit *compile_unit;
     ZigLLVMDIFile *compile_unit_file;
-
-    ZigList link_libs_list;
     LinkLib *libc_link_lib;
-
-    // add -framework [name] args to linker
-    ZigList darwin_frameworks;
-    // add -rpath [name] args to linker
-    ZigList rpath_list;
-
+    LLVMTargetDataRef target_data_ref;
+    LLVMTargetMachineRef target_machine;
+    ZigLLVMDIFile *dummy_di_file;
+    LLVMValueRef cur_ret_ptr;
+    LLVMValueRef cur_fn_val;
+    LLVMValueRef cur_err_ret_trace_val_arg;
+    LLVMValueRef cur_err_ret_trace_val_stack;
+    LLVMValueRef memcpy_fn_val;
+    LLVMValueRef memset_fn_val;
+    LLVMValueRef trap_fn_val;
+    LLVMValueRef return_address_fn_val;
+    LLVMValueRef frame_address_fn_val;
+    LLVMValueRef coro_destroy_fn_val;
+    LLVMValueRef coro_id_fn_val;
+    LLVMValueRef coro_alloc_fn_val;
+    LLVMValueRef coro_size_fn_val;
+    LLVMValueRef coro_begin_fn_val;
+    LLVMValueRef coro_suspend_fn_val;
+    LLVMValueRef coro_end_fn_val;
+    LLVMValueRef coro_free_fn_val;
+    LLVMValueRef coro_resume_fn_val;
+    LLVMValueRef coro_save_fn_val;
+    LLVMValueRef coro_promise_fn_val;
+    LLVMValueRef coro_alloc_helper_fn_val;
+    LLVMValueRef coro_frame_fn_val;
+    LLVMValueRef merge_err_ret_traces_fn_val;
+    LLVMValueRef add_error_return_trace_addr_fn_val;
+    LLVMValueRef stacksave_fn_val;
+    LLVMValueRef stackrestore_fn_val;
+    LLVMValueRef write_register_fn_val;
+    LLVMValueRef sp_md_node;
+    LLVMValueRef err_name_table;
+    LLVMValueRef safety_crash_err_fn;
+    LLVMValueRef return_err_fn;
 
     // reminder: hash tables must be initialized before use
     HashMap import_table;
@@ -1582,15 +1611,29 @@ struct CodeGen {
     HashMap string_literals_table;
     HashMap type_info_cache;
 
-
     ZigList import_queue;
     size_t import_queue_index;
     ZigList resolve_queue;
     size_t resolve_queue_index;
     ZigList use_queue;
     size_t use_queue_index;
+    ZigList timing_events;
+    ZigList error_di_types;
+    ZigList tld_ref_source_node_stack;
+    ZigList inline_fns;
+    ZigList test_fns;
+    ZigList err_enumerators;
+    ZigList errors_by_index;
+    size_t largest_err_name_len;
 
-    uint32_t next_unresolved_index;
+    PackageTableEntry *std_package;
+    PackageTableEntry *panic_package;
+    PackageTableEntry *test_runner_package;
+    PackageTableEntry *compile_var_package;
+    ImportTableEntry *compile_var_import;
+    ImportTableEntry *root_import;
+    ImportTableEntry *bootstrap_import;
+    ImportTableEntry *test_runner_import;
 
     struct {
         ZigType *entry_bool;
@@ -1626,14 +1669,45 @@ struct CodeGen {
         ZigType *entry_arg_tuple;
         ZigType *entry_promise;
     } builtin_types;
+    ZigType *align_amt_type;
+    ZigType *stack_trace_type;
+    ZigType *ptr_to_stack_trace_type;
+    ZigType *err_tag_type;
+    ZigType *test_fn_type;
 
-    EmitFileType emit_file_type;
-    ZigTarget zig_target;
-    LLVMTargetDataRef target_data_ref;
+    Buf triple_str;
+    Buf global_asm;
+    Buf *out_h_path;
+    Buf artifact_dir;
+    Buf output_file_path;
+    Buf o_file_output_path;
+    Buf *wanted_output_file_path;
+    Buf cache_dir;
+
+    IrInstruction *invalid_instruction;
+
+    ConstExprValue const_void_val;
+    ConstExprValue panic_msg_vals[PanicMsgIdCount];
+
+    // The function definitions this module includes.
+    ZigList fn_defs;
+    size_t fn_defs_index;
+    ZigList global_vars;
+
+    ZigFn *cur_fn;
+    ZigFn *main_fn;
+    ZigFn *panic_fn;
+    AstNode *root_export_decl;
+
+    CacheHash cache_hash;
+    ErrColor err_color;
+    uint32_t next_unresolved_index;
     unsigned pointer_size_bytes;
+    uint32_t target_os_index;
+    uint32_t target_arch_index;
+    uint32_t target_environ_index;
+    uint32_t target_oformat_index;
     bool is_big_endian;
-    bool is_static;
-    bool strip_debug_symbols;
     bool want_h_file;
     bool have_pub_main;
     bool have_c_main;
@@ -1641,6 +1715,65 @@ struct CodeGen {
     bool have_winmain_crt_startup;
     bool have_dllmain_crt_startup;
     bool have_pub_panic;
+    bool have_err_ret_tracing;
+    bool c_want_stdint;
+    bool c_want_stdbool;
+    bool verbose_tokenize;
+    bool verbose_ast;
+    bool verbose_link;
+    bool verbose_ir;
+    bool verbose_llvm_ir;
+    bool verbose_cimport;
+    bool error_during_imports;
+    bool generate_error_name_table;
+    bool enable_cache;
+    bool enable_time_report;
+
+    //////////////////////////// Participates in Input Parameter Cache Hash
+    ZigList link_libs_list;
+    // add -framework [name] args to linker
+    ZigList darwin_frameworks;
+    // add -rpath [name] args to linker
+    ZigList rpath_list;
+    ZigList forbidden_libs;
+    ZigList link_objects;
+    ZigList assembly_files;
+    ZigList lib_dirs;
+
+    size_t version_major;
+    size_t version_minor;
+    size_t version_patch;
+    const char *linker_script;
+
+    EmitFileType emit_file_type;
+    BuildMode build_mode;
+    OutType out_type;
+    ZigTarget zig_target;
+    bool is_static;
+    bool strip_debug_symbols;
+    bool is_test_build;
+    bool is_native_target;
+    bool windows_subsystem_windows;
+    bool windows_subsystem_console;
+    bool linker_rdynamic;
+    bool no_rosegment_workaround;
+    bool each_lib_rpath;
+
+    Buf *mmacosx_version_min;
+    Buf *mios_version_min;
+    Buf *root_out_name;
+    Buf *test_filter;
+    Buf *test_name_prefix;
+    PackageTableEntry *root_package;
+
+    const char **llvm_argv;
+    size_t llvm_argv_len;
+
+    const char **clang_argv;
+    size_t clang_argv_len;
+
+    //////////////////////////// Unsorted
+
     Buf *libc_lib_dir;
     Buf *libc_static_lib_dir;
     Buf *libc_include_dir;
@@ -1651,140 +1784,7 @@ struct CodeGen {
     Buf *zig_c_headers_dir;
     Buf *zig_std_special_dir;
     Buf *dynamic_linker;
-    Buf *ar_path;
     ZigWindowsSDK *win_sdk;
-    Buf triple_str;
-    BuildMode build_mode;
-    bool is_test_build;
-    bool have_err_ret_tracing;
-    uint32_t target_os_index;
-    uint32_t target_arch_index;
-    uint32_t target_environ_index;
-    uint32_t target_oformat_index;
-    LLVMTargetMachineRef target_machine;
-    ZigLLVMDIFile *dummy_di_file;
-    bool is_native_target;
-    PackageTableEntry *root_package;
-    PackageTableEntry *std_package;
-    PackageTableEntry *panic_package;
-    PackageTableEntry *test_runner_package;
-    PackageTableEntry *compile_var_package;
-    ImportTableEntry *compile_var_import;
-    Buf *root_out_name;
-    bool windows_subsystem_windows;
-    bool windows_subsystem_console;
-    Buf *mmacosx_version_min;
-    Buf *mios_version_min;
-    bool linker_rdynamic;
-    const char *linker_script;
-
-    // The function definitions this module includes.
-    ZigList fn_defs;
-    size_t fn_defs_index;
-    ZigList global_vars;
-
-    OutType out_type;
-    ZigFn *cur_fn;
-    ZigFn *main_fn;
-    ZigFn *panic_fn;
-    LLVMValueRef cur_ret_ptr;
-    LLVMValueRef cur_fn_val;
-    LLVMValueRef cur_err_ret_trace_val_arg;
-    LLVMValueRef cur_err_ret_trace_val_stack;
-    bool c_want_stdint;
-    bool c_want_stdbool;
-    AstNode *root_export_decl;
-    size_t version_major;
-    size_t version_minor;
-    size_t version_patch;
-    bool verbose_tokenize;
-    bool verbose_ast;
-    bool verbose_link;
-    bool verbose_ir;
-    bool verbose_llvm_ir;
-    bool verbose_cimport;
-    ErrColor err_color;
-    ImportTableEntry *root_import;
-    ImportTableEntry *bootstrap_import;
-    ImportTableEntry *test_runner_import;
-    LLVMValueRef memcpy_fn_val;
-    LLVMValueRef memset_fn_val;
-    LLVMValueRef trap_fn_val;
-    LLVMValueRef return_address_fn_val;
-    LLVMValueRef frame_address_fn_val;
-    LLVMValueRef coro_destroy_fn_val;
-    LLVMValueRef coro_id_fn_val;
-    LLVMValueRef coro_alloc_fn_val;
-    LLVMValueRef coro_size_fn_val;
-    LLVMValueRef coro_begin_fn_val;
-    LLVMValueRef coro_suspend_fn_val;
-    LLVMValueRef coro_end_fn_val;
-    LLVMValueRef coro_free_fn_val;
-    LLVMValueRef coro_resume_fn_val;
-    LLVMValueRef coro_save_fn_val;
-    LLVMValueRef coro_promise_fn_val;
-    LLVMValueRef coro_alloc_helper_fn_val;
-    LLVMValueRef coro_frame_fn_val;
-    LLVMValueRef merge_err_ret_traces_fn_val;
-    LLVMValueRef add_error_return_trace_addr_fn_val;
-    LLVMValueRef stacksave_fn_val;
-    LLVMValueRef stackrestore_fn_val;
-    LLVMValueRef write_register_fn_val;
-    bool error_during_imports;
-
-    LLVMValueRef sp_md_node;
-
-    const char **clang_argv;
-    size_t clang_argv_len;
-    ZigList lib_dirs;
-
-    const char **llvm_argv;
-    size_t llvm_argv_len;
-
-    ZigList test_fns;
-    ZigType *test_fn_type;
-
-    bool each_lib_rpath;
-
-    ZigType *err_tag_type;
-    ZigList err_enumerators;
-    ZigList errors_by_index;
-    bool generate_error_name_table;
-    LLVMValueRef err_name_table;
-    size_t largest_err_name_len;
-    LLVMValueRef safety_crash_err_fn;
-
-    LLVMValueRef return_err_fn;
-
-    IrInstruction *invalid_instruction;
-    ConstExprValue const_void_val;
-
-    ConstExprValue panic_msg_vals[PanicMsgIdCount];
-
-    Buf global_asm;
-    ZigList link_objects;
-    ZigList assembly_files;
-
-    Buf *test_filter;
-    Buf *test_name_prefix;
-
-    ZigList timing_events;
-
-    Buf cache_dir;
-    Buf *out_h_path;
-
-    ZigList inline_fns;
-    ZigList tld_ref_source_node_stack;
-
-    ZigType *align_amt_type;
-    ZigType *stack_trace_type;
-    ZigType *ptr_to_stack_trace_type;
-
-    ZigList error_di_types;
-
-    ZigList forbidden_libs;
-
-    bool no_rosegment_workaround;
 };
 
 enum VarLinkage {
diff --git a/src/analyze.cpp b/src/analyze.cpp
index 07c7b94e25..59be76518b 100644
--- a/src/analyze.cpp
+++ b/src/analyze.cpp
@@ -6289,6 +6289,12 @@ LinkLib *add_link_lib(CodeGen *g, Buf *name) {
     if (is_libc && g->libc_link_lib != nullptr)
         return g->libc_link_lib;
 
+    if (g->enable_cache && is_libc && g->zig_target.os != OsMacOSX && g->zig_target.os != OsIOS) {
+        fprintf(stderr, "TODO linking against libc is currently incompatible with `--cache on`.\n"
+        "Zig is not yet capable of determining whether the libc installation has changed on subsequent builds.\n");
+        exit(1);
+    }
+
     for (size_t i = 0; i < g->link_libs_list.length; i += 1) {
         LinkLib *existing_lib = g->link_libs_list.at(i);
         if (buf_eql_buf(existing_lib->name, name)) {
@@ -6398,6 +6404,14 @@ not_integer:
     return nullptr;
 }
 
+Error file_fetch(CodeGen *g, Buf *resolved_path, Buf *contents) {
+    if (g->enable_cache) {
+        return cache_add_file_fetch(&g->cache_hash, resolved_path, contents);
+    } else {
+        return os_fetch_file_path(resolved_path, contents, false);
+    }
+}
+
 X64CABIClass type_c_abi_x86_64_class(CodeGen *g, ZigType *ty) {
     size_t ty_size = type_size(g, ty);
     if (get_codegen_ptr_type(ty) != nullptr)
@@ -6478,4 +6492,3 @@ bool type_is_c_abi_int(CodeGen *g, ZigType *ty) {
         ty->id == ZigTypeIdUnreachable ||
         get_codegen_ptr_type(ty) != nullptr);
 }
-
diff --git a/src/analyze.hpp b/src/analyze.hpp
index cd3f52d9c5..d69265f974 100644
--- a/src/analyze.hpp
+++ b/src/analyze.hpp
@@ -209,6 +209,8 @@ ZigType *get_primitive_type(CodeGen *g, Buf *name);
 bool calling_convention_allows_zig_types(CallingConvention cc);
 const char *calling_convention_name(CallingConvention cc);
 
+Error ATTRIBUTE_MUST_USE file_fetch(CodeGen *g, Buf *resolved_path, Buf *contents);
+
 void walk_function_params(CodeGen *g, ZigType *fn_type, FnWalk *fn_walk);
 X64CABIClass type_c_abi_x86_64_class(CodeGen *g, ZigType *ty);
 bool type_is_c_abi_int(CodeGen *g, ZigType *ty);
diff --git a/src/blake2.h b/src/blake2.h
new file mode 100644
index 0000000000..6420c5367a
--- /dev/null
+++ b/src/blake2.h
@@ -0,0 +1,196 @@
+/*
+   BLAKE2 reference source code package - reference C implementations
+
+   Copyright 2012, Samuel Neves .  You may use this under the
+   terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at
+   your option.  The terms of these licenses can be found at:
+
+   - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0
+   - OpenSSL license   : https://www.openssl.org/source/license.html
+   - Apache 2.0        : http://www.apache.org/licenses/LICENSE-2.0
+
+   More information about the BLAKE2 hash function can be found at
+   https://blake2.net.
+*/
+#ifndef BLAKE2_H
+#define BLAKE2_H
+
+#include 
+#include 
+
+#if defined(_MSC_VER)
+#define BLAKE2_PACKED(x) __pragma(pack(push, 1)) x __pragma(pack(pop))
+#else
+#define BLAKE2_PACKED(x) x __attribute__((packed))
+#endif
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+  enum blake2s_constant
+  {
+    BLAKE2S_BLOCKBYTES = 64,
+    BLAKE2S_OUTBYTES   = 32,
+    BLAKE2S_KEYBYTES   = 32,
+    BLAKE2S_SALTBYTES  = 8,
+    BLAKE2S_PERSONALBYTES = 8
+  };
+
+  enum blake2b_constant
+  {
+    BLAKE2B_BLOCKBYTES = 128,
+    BLAKE2B_OUTBYTES   = 64,
+    BLAKE2B_KEYBYTES   = 64,
+    BLAKE2B_SALTBYTES  = 16,
+    BLAKE2B_PERSONALBYTES = 16
+  };
+
+  typedef struct blake2s_state__
+  {
+    uint32_t h[8];
+    uint32_t t[2];
+    uint32_t f[2];
+    uint8_t  buf[BLAKE2S_BLOCKBYTES];
+    size_t   buflen;
+    size_t   outlen;
+    uint8_t  last_node;
+  } blake2s_state;
+
+  typedef struct blake2b_state__
+  {
+    uint64_t h[8];
+    uint64_t t[2];
+    uint64_t f[2];
+    uint8_t  buf[BLAKE2B_BLOCKBYTES];
+    size_t   buflen;
+    size_t   outlen;
+    uint8_t  last_node;
+  } blake2b_state;
+
+  typedef struct blake2sp_state__
+  {
+    blake2s_state S[8][1];
+    blake2s_state R[1];
+    uint8_t       buf[8 * BLAKE2S_BLOCKBYTES];
+    size_t        buflen;
+    size_t        outlen;
+  } blake2sp_state;
+
+  typedef struct blake2bp_state__
+  {
+    blake2b_state S[4][1];
+    blake2b_state R[1];
+    uint8_t       buf[4 * BLAKE2B_BLOCKBYTES];
+    size_t        buflen;
+    size_t        outlen;
+  } blake2bp_state;
+
+
+  BLAKE2_PACKED(struct blake2s_param__
+  {
+    uint8_t  digest_length; /* 1 */
+    uint8_t  key_length;    /* 2 */
+    uint8_t  fanout;        /* 3 */
+    uint8_t  depth;         /* 4 */
+    uint32_t leaf_length;   /* 8 */
+    uint32_t node_offset;  /* 12 */
+    uint16_t xof_length;    /* 14 */
+    uint8_t  node_depth;    /* 15 */
+    uint8_t  inner_length;  /* 16 */
+    /* uint8_t  reserved[0]; */
+    uint8_t  salt[BLAKE2S_SALTBYTES]; /* 24 */
+    uint8_t  personal[BLAKE2S_PERSONALBYTES];  /* 32 */
+  });
+
+  typedef struct blake2s_param__ blake2s_param;
+
+  BLAKE2_PACKED(struct blake2b_param__
+  {
+    uint8_t  digest_length; /* 1 */
+    uint8_t  key_length;    /* 2 */
+    uint8_t  fanout;        /* 3 */
+    uint8_t  depth;         /* 4 */
+    uint32_t leaf_length;   /* 8 */
+    uint32_t node_offset;   /* 12 */
+    uint32_t xof_length;    /* 16 */
+    uint8_t  node_depth;    /* 17 */
+    uint8_t  inner_length;  /* 18 */
+    uint8_t  reserved[14];  /* 32 */
+    uint8_t  salt[BLAKE2B_SALTBYTES]; /* 48 */
+    uint8_t  personal[BLAKE2B_PERSONALBYTES];  /* 64 */
+  });
+
+  typedef struct blake2b_param__ blake2b_param;
+
+  typedef struct blake2xs_state__
+  {
+    blake2s_state S[1];
+    blake2s_param P[1];
+  } blake2xs_state;
+
+  typedef struct blake2xb_state__
+  {
+    blake2b_state S[1];
+    blake2b_param P[1];
+  } blake2xb_state;
+
+  /* Padded structs result in a compile-time error */
+  enum {
+    BLAKE2_DUMMY_1 = 1/(sizeof(blake2s_param) == BLAKE2S_OUTBYTES),
+    BLAKE2_DUMMY_2 = 1/(sizeof(blake2b_param) == BLAKE2B_OUTBYTES)
+  };
+
+  /* Streaming API */
+  int blake2s_init( blake2s_state *S, size_t outlen );
+  int blake2s_init_key( blake2s_state *S, size_t outlen, const void *key, size_t keylen );
+  int blake2s_init_param( blake2s_state *S, const blake2s_param *P );
+  int blake2s_update( blake2s_state *S, const void *in, size_t inlen );
+  int blake2s_final( blake2s_state *S, void *out, size_t outlen );
+
+  int blake2b_init( blake2b_state *S, size_t outlen );
+  int blake2b_init_key( blake2b_state *S, size_t outlen, const void *key, size_t keylen );
+  int blake2b_init_param( blake2b_state *S, const blake2b_param *P );
+  int blake2b_update( blake2b_state *S, const void *in, size_t inlen );
+  int blake2b_final( blake2b_state *S, void *out, size_t outlen );
+
+  int blake2sp_init( blake2sp_state *S, size_t outlen );
+  int blake2sp_init_key( blake2sp_state *S, size_t outlen, const void *key, size_t keylen );
+  int blake2sp_update( blake2sp_state *S, const void *in, size_t inlen );
+  int blake2sp_final( blake2sp_state *S, void *out, size_t outlen );
+
+  int blake2bp_init( blake2bp_state *S, size_t outlen );
+  int blake2bp_init_key( blake2bp_state *S, size_t outlen, const void *key, size_t keylen );
+  int blake2bp_update( blake2bp_state *S, const void *in, size_t inlen );
+  int blake2bp_final( blake2bp_state *S, void *out, size_t outlen );
+
+  /* Variable output length API */
+  int blake2xs_init( blake2xs_state *S, const size_t outlen );
+  int blake2xs_init_key( blake2xs_state *S, const size_t outlen, const void *key, size_t keylen );
+  int blake2xs_update( blake2xs_state *S, const void *in, size_t inlen );
+  int blake2xs_final(blake2xs_state *S, void *out, size_t outlen);
+
+  int blake2xb_init( blake2xb_state *S, const size_t outlen );
+  int blake2xb_init_key( blake2xb_state *S, const size_t outlen, const void *key, size_t keylen );
+  int blake2xb_update( blake2xb_state *S, const void *in, size_t inlen );
+  int blake2xb_final(blake2xb_state *S, void *out, size_t outlen);
+
+  /* Simple API */
+  int blake2s( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen );
+  int blake2b( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen );
+
+  int blake2sp( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen );
+  int blake2bp( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen );
+
+  int blake2xs( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen );
+  int blake2xb( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen );
+
+  /* This is simply an alias for blake2b */
+  int blake2( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen );
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+
diff --git a/src/blake2b.c b/src/blake2b.c
new file mode 100644
index 0000000000..600f951b9b
--- /dev/null
+++ b/src/blake2b.c
@@ -0,0 +1,539 @@
+/*
+   BLAKE2 reference source code package - reference C implementations
+
+   Copyright 2012, Samuel Neves .  You may use this under the
+   terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at
+   your option.  The terms of these licenses can be found at:
+
+   - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0
+   - OpenSSL license   : https://www.openssl.org/source/license.html
+   - Apache 2.0        : http://www.apache.org/licenses/LICENSE-2.0
+
+   More information about the BLAKE2 hash function can be found at
+   https://blake2.net.
+*/
+
+#include 
+#include 
+#include 
+
+#include "blake2.h"
+/*
+   BLAKE2 reference source code package - reference C implementations
+
+   Copyright 2012, Samuel Neves .  You may use this under the
+   terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at
+   your option.  The terms of these licenses can be found at:
+
+   - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0
+   - OpenSSL license   : https://www.openssl.org/source/license.html
+   - Apache 2.0        : http://www.apache.org/licenses/LICENSE-2.0
+
+   More information about the BLAKE2 hash function can be found at
+   https://blake2.net.
+*/
+#ifndef BLAKE2_IMPL_H
+#define BLAKE2_IMPL_H
+
+#include 
+#include 
+
+#if !defined(__cplusplus) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L)
+  #if   defined(_MSC_VER)
+    #define BLAKE2_INLINE __inline
+  #elif defined(__GNUC__)
+    #define BLAKE2_INLINE __inline__
+  #else
+    #define BLAKE2_INLINE
+  #endif
+#else
+  #define BLAKE2_INLINE inline
+#endif
+
+static BLAKE2_INLINE uint32_t load32( const void *src )
+{
+#if defined(NATIVE_LITTLE_ENDIAN)
+  uint32_t w;
+  memcpy(&w, src, sizeof w);
+  return w;
+#else
+  const uint8_t *p = ( const uint8_t * )src;
+  return (( uint32_t )( p[0] ) <<  0) |
+         (( uint32_t )( p[1] ) <<  8) |
+         (( uint32_t )( p[2] ) << 16) |
+         (( uint32_t )( p[3] ) << 24) ;
+#endif
+}
+
+static BLAKE2_INLINE uint64_t load64( const void *src )
+{
+#if defined(NATIVE_LITTLE_ENDIAN)
+  uint64_t w;
+  memcpy(&w, src, sizeof w);
+  return w;
+#else
+  const uint8_t *p = ( const uint8_t * )src;
+  return (( uint64_t )( p[0] ) <<  0) |
+         (( uint64_t )( p[1] ) <<  8) |
+         (( uint64_t )( p[2] ) << 16) |
+         (( uint64_t )( p[3] ) << 24) |
+         (( uint64_t )( p[4] ) << 32) |
+         (( uint64_t )( p[5] ) << 40) |
+         (( uint64_t )( p[6] ) << 48) |
+         (( uint64_t )( p[7] ) << 56) ;
+#endif
+}
+
+static BLAKE2_INLINE uint16_t load16( const void *src )
+{
+#if defined(NATIVE_LITTLE_ENDIAN)
+  uint16_t w;
+  memcpy(&w, src, sizeof w);
+  return w;
+#else
+  const uint8_t *p = ( const uint8_t * )src;
+  return ( uint16_t )((( uint32_t )( p[0] ) <<  0) |
+                      (( uint32_t )( p[1] ) <<  8));
+#endif
+}
+
+static BLAKE2_INLINE void store16( void *dst, uint16_t w )
+{
+#if defined(NATIVE_LITTLE_ENDIAN)
+  memcpy(dst, &w, sizeof w);
+#else
+  uint8_t *p = ( uint8_t * )dst;
+  *p++ = ( uint8_t )w; w >>= 8;
+  *p++ = ( uint8_t )w;
+#endif
+}
+
+static BLAKE2_INLINE void store32( void *dst, uint32_t w )
+{
+#if defined(NATIVE_LITTLE_ENDIAN)
+  memcpy(dst, &w, sizeof w);
+#else
+  uint8_t *p = ( uint8_t * )dst;
+  p[0] = (uint8_t)(w >>  0);
+  p[1] = (uint8_t)(w >>  8);
+  p[2] = (uint8_t)(w >> 16);
+  p[3] = (uint8_t)(w >> 24);
+#endif
+}
+
+static BLAKE2_INLINE void store64( void *dst, uint64_t w )
+{
+#if defined(NATIVE_LITTLE_ENDIAN)
+  memcpy(dst, &w, sizeof w);
+#else
+  uint8_t *p = ( uint8_t * )dst;
+  p[0] = (uint8_t)(w >>  0);
+  p[1] = (uint8_t)(w >>  8);
+  p[2] = (uint8_t)(w >> 16);
+  p[3] = (uint8_t)(w >> 24);
+  p[4] = (uint8_t)(w >> 32);
+  p[5] = (uint8_t)(w >> 40);
+  p[6] = (uint8_t)(w >> 48);
+  p[7] = (uint8_t)(w >> 56);
+#endif
+}
+
+static BLAKE2_INLINE uint64_t load48( const void *src )
+{
+  const uint8_t *p = ( const uint8_t * )src;
+  return (( uint64_t )( p[0] ) <<  0) |
+         (( uint64_t )( p[1] ) <<  8) |
+         (( uint64_t )( p[2] ) << 16) |
+         (( uint64_t )( p[3] ) << 24) |
+         (( uint64_t )( p[4] ) << 32) |
+         (( uint64_t )( p[5] ) << 40) ;
+}
+
+static BLAKE2_INLINE void store48( void *dst, uint64_t w )
+{
+  uint8_t *p = ( uint8_t * )dst;
+  p[0] = (uint8_t)(w >>  0);
+  p[1] = (uint8_t)(w >>  8);
+  p[2] = (uint8_t)(w >> 16);
+  p[3] = (uint8_t)(w >> 24);
+  p[4] = (uint8_t)(w >> 32);
+  p[5] = (uint8_t)(w >> 40);
+}
+
+static BLAKE2_INLINE uint32_t rotr32( const uint32_t w, const unsigned c )
+{
+  return ( w >> c ) | ( w << ( 32 - c ) );
+}
+
+static BLAKE2_INLINE uint64_t rotr64( const uint64_t w, const unsigned c )
+{
+  return ( w >> c ) | ( w << ( 64 - c ) );
+}
+
+/* prevents compiler optimizing out memset() */
+static BLAKE2_INLINE void secure_zero_memory(void *v, size_t n)
+{
+  static void *(*const volatile memset_v)(void *, int, size_t) = &memset;
+  memset_v(v, 0, n);
+}
+
+#endif
+
+static const uint64_t blake2b_IV[8] =
+{
+  0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL,
+  0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL,
+  0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL,
+  0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL
+};
+
+static const uint8_t blake2b_sigma[12][16] =
+{
+  {  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15 } ,
+  { 14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3 } ,
+  { 11,  8, 12,  0,  5,  2, 15, 13, 10, 14,  3,  6,  7,  1,  9,  4 } ,
+  {  7,  9,  3,  1, 13, 12, 11, 14,  2,  6,  5, 10,  4,  0, 15,  8 } ,
+  {  9,  0,  5,  7,  2,  4, 10, 15, 14,  1, 11, 12,  6,  8,  3, 13 } ,
+  {  2, 12,  6, 10,  0, 11,  8,  3,  4, 13,  7,  5, 15, 14,  1,  9 } ,
+  { 12,  5,  1, 15, 14, 13,  4, 10,  0,  7,  6,  3,  9,  2,  8, 11 } ,
+  { 13, 11,  7, 14, 12,  1,  3,  9,  5,  0, 15,  4,  8,  6,  2, 10 } ,
+  {  6, 15, 14,  9, 11,  3,  0,  8, 12,  2, 13,  7,  1,  4, 10,  5 } ,
+  { 10,  2,  8,  4,  7,  6,  1,  5, 15, 11,  9, 14,  3, 12, 13 , 0 } ,
+  {  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15 } ,
+  { 14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3 }
+};
+
+
+static void blake2b_set_lastnode( blake2b_state *S )
+{
+  S->f[1] = (uint64_t)-1;
+}
+
+/* Some helper functions, not necessarily useful */
+static int blake2b_is_lastblock( const blake2b_state *S )
+{
+  return S->f[0] != 0;
+}
+
+static void blake2b_set_lastblock( blake2b_state *S )
+{
+  if( S->last_node ) blake2b_set_lastnode( S );
+
+  S->f[0] = (uint64_t)-1;
+}
+
+static void blake2b_increment_counter( blake2b_state *S, const uint64_t inc )
+{
+  S->t[0] += inc;
+  S->t[1] += ( S->t[0] < inc );
+}
+
+static void blake2b_init0( blake2b_state *S )
+{
+  size_t i;
+  memset( S, 0, sizeof( blake2b_state ) );
+
+  for( i = 0; i < 8; ++i ) S->h[i] = blake2b_IV[i];
+}
+
+/* init xors IV with input parameter block */
+int blake2b_init_param( blake2b_state *S, const blake2b_param *P )
+{
+  const uint8_t *p = ( const uint8_t * )( P );
+  size_t i;
+
+  blake2b_init0( S );
+
+  /* IV XOR ParamBlock */
+  for( i = 0; i < 8; ++i )
+    S->h[i] ^= load64( p + sizeof( S->h[i] ) * i );
+
+  S->outlen = P->digest_length;
+  return 0;
+}
+
+
+
+int blake2b_init( blake2b_state *S, size_t outlen )
+{
+  blake2b_param P[1];
+
+  if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1;
+
+  P->digest_length = (uint8_t)outlen;
+  P->key_length    = 0;
+  P->fanout        = 1;
+  P->depth         = 1;
+  store32( &P->leaf_length, 0 );
+  store32( &P->node_offset, 0 );
+  store32( &P->xof_length, 0 );
+  P->node_depth    = 0;
+  P->inner_length  = 0;
+  memset( P->reserved, 0, sizeof( P->reserved ) );
+  memset( P->salt,     0, sizeof( P->salt ) );
+  memset( P->personal, 0, sizeof( P->personal ) );
+  return blake2b_init_param( S, P );
+}
+
+
+int blake2b_init_key( blake2b_state *S, size_t outlen, const void *key, size_t keylen )
+{
+  blake2b_param P[1];
+
+  if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1;
+
+  if ( !key || !keylen || keylen > BLAKE2B_KEYBYTES ) return -1;
+
+  P->digest_length = (uint8_t)outlen;
+  P->key_length    = (uint8_t)keylen;
+  P->fanout        = 1;
+  P->depth         = 1;
+  store32( &P->leaf_length, 0 );
+  store32( &P->node_offset, 0 );
+  store32( &P->xof_length, 0 );
+  P->node_depth    = 0;
+  P->inner_length  = 0;
+  memset( P->reserved, 0, sizeof( P->reserved ) );
+  memset( P->salt,     0, sizeof( P->salt ) );
+  memset( P->personal, 0, sizeof( P->personal ) );
+
+  if( blake2b_init_param( S, P ) < 0 ) return -1;
+
+  {
+    uint8_t block[BLAKE2B_BLOCKBYTES];
+    memset( block, 0, BLAKE2B_BLOCKBYTES );
+    memcpy( block, key, keylen );
+    blake2b_update( S, block, BLAKE2B_BLOCKBYTES );
+    secure_zero_memory( block, BLAKE2B_BLOCKBYTES ); /* Burn the key from stack */
+  }
+  return 0;
+}
+
+#define G(r,i,a,b,c,d)                      \
+  do {                                      \
+    a = a + b + m[blake2b_sigma[r][2*i+0]]; \
+    d = rotr64(d ^ a, 32);                  \
+    c = c + d;                              \
+    b = rotr64(b ^ c, 24);                  \
+    a = a + b + m[blake2b_sigma[r][2*i+1]]; \
+    d = rotr64(d ^ a, 16);                  \
+    c = c + d;                              \
+    b = rotr64(b ^ c, 63);                  \
+  } while(0)
+
+#define ROUND(r)                    \
+  do {                              \
+    G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \
+    G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \
+    G(r,2,v[ 2],v[ 6],v[10],v[14]); \
+    G(r,3,v[ 3],v[ 7],v[11],v[15]); \
+    G(r,4,v[ 0],v[ 5],v[10],v[15]); \
+    G(r,5,v[ 1],v[ 6],v[11],v[12]); \
+    G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \
+    G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \
+  } while(0)
+
+static void blake2b_compress( blake2b_state *S, const uint8_t block[BLAKE2B_BLOCKBYTES] )
+{
+  uint64_t m[16];
+  uint64_t v[16];
+  size_t i;
+
+  for( i = 0; i < 16; ++i ) {
+    m[i] = load64( block + i * sizeof( m[i] ) );
+  }
+
+  for( i = 0; i < 8; ++i ) {
+    v[i] = S->h[i];
+  }
+
+  v[ 8] = blake2b_IV[0];
+  v[ 9] = blake2b_IV[1];
+  v[10] = blake2b_IV[2];
+  v[11] = blake2b_IV[3];
+  v[12] = blake2b_IV[4] ^ S->t[0];
+  v[13] = blake2b_IV[5] ^ S->t[1];
+  v[14] = blake2b_IV[6] ^ S->f[0];
+  v[15] = blake2b_IV[7] ^ S->f[1];
+
+  ROUND( 0 );
+  ROUND( 1 );
+  ROUND( 2 );
+  ROUND( 3 );
+  ROUND( 4 );
+  ROUND( 5 );
+  ROUND( 6 );
+  ROUND( 7 );
+  ROUND( 8 );
+  ROUND( 9 );
+  ROUND( 10 );
+  ROUND( 11 );
+
+  for( i = 0; i < 8; ++i ) {
+    S->h[i] = S->h[i] ^ v[i] ^ v[i + 8];
+  }
+}
+
+#undef G
+#undef ROUND
+
+int blake2b_update( blake2b_state *S, const void *pin, size_t inlen )
+{
+  const unsigned char * in = (const unsigned char *)pin;
+  if( inlen > 0 )
+  {
+    size_t left = S->buflen;
+    size_t fill = BLAKE2B_BLOCKBYTES - left;
+    if( inlen > fill )
+    {
+      S->buflen = 0;
+      memcpy( S->buf + left, in, fill ); /* Fill buffer */
+      blake2b_increment_counter( S, BLAKE2B_BLOCKBYTES );
+      blake2b_compress( S, S->buf ); /* Compress */
+      in += fill; inlen -= fill;
+      while(inlen > BLAKE2B_BLOCKBYTES) {
+        blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES);
+        blake2b_compress( S, in );
+        in += BLAKE2B_BLOCKBYTES;
+        inlen -= BLAKE2B_BLOCKBYTES;
+      }
+    }
+    memcpy( S->buf + S->buflen, in, inlen );
+    S->buflen += inlen;
+  }
+  return 0;
+}
+
+int blake2b_final( blake2b_state *S, void *out, size_t outlen )
+{
+  uint8_t buffer[BLAKE2B_OUTBYTES] = {0};
+  size_t i;
+
+  if( out == NULL || outlen < S->outlen )
+    return -1;
+
+  if( blake2b_is_lastblock( S ) )
+    return -1;
+
+  blake2b_increment_counter( S, S->buflen );
+  blake2b_set_lastblock( S );
+  memset( S->buf + S->buflen, 0, BLAKE2B_BLOCKBYTES - S->buflen ); /* Padding */
+  blake2b_compress( S, S->buf );
+
+  for( i = 0; i < 8; ++i ) /* Output full hash to temp buffer */
+    store64( buffer + sizeof( S->h[i] ) * i, S->h[i] );
+
+  memcpy( out, buffer, S->outlen );
+  secure_zero_memory(buffer, sizeof(buffer));
+  return 0;
+}
+
+/* inlen, at least, should be uint64_t. Others can be size_t. */
+int blake2b( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen )
+{
+  blake2b_state S[1];
+
+  /* Verify parameters */
+  if ( NULL == in && inlen > 0 ) return -1;
+
+  if ( NULL == out ) return -1;
+
+  if( NULL == key && keylen > 0 ) return -1;
+
+  if( !outlen || outlen > BLAKE2B_OUTBYTES ) return -1;
+
+  if( keylen > BLAKE2B_KEYBYTES ) return -1;
+
+  if( keylen > 0 )
+  {
+    if( blake2b_init_key( S, outlen, key, keylen ) < 0 ) return -1;
+  }
+  else
+  {
+    if( blake2b_init( S, outlen ) < 0 ) return -1;
+  }
+
+  blake2b_update( S, ( const uint8_t * )in, inlen );
+  blake2b_final( S, out, outlen );
+  return 0;
+}
+
+int blake2( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ) {
+  return blake2b(out, outlen, in, inlen, key, keylen);
+}
+
+#if defined(SUPERCOP)
+int crypto_hash( unsigned char *out, unsigned char *in, unsigned long long inlen )
+{
+  return blake2b( out, BLAKE2B_OUTBYTES, in, inlen, NULL, 0 );
+}
+#endif
+
+#if defined(BLAKE2B_SELFTEST)
+#include 
+#include "blake2-kat.h"
+int main( void )
+{
+  uint8_t key[BLAKE2B_KEYBYTES];
+  uint8_t buf[BLAKE2_KAT_LENGTH];
+  size_t i, step;
+
+  for( i = 0; i < BLAKE2B_KEYBYTES; ++i )
+    key[i] = ( uint8_t )i;
+
+  for( i = 0; i < BLAKE2_KAT_LENGTH; ++i )
+    buf[i] = ( uint8_t )i;
+
+  /* Test simple API */
+  for( i = 0; i < BLAKE2_KAT_LENGTH; ++i )
+  {
+    uint8_t hash[BLAKE2B_OUTBYTES];
+    blake2b( hash, BLAKE2B_OUTBYTES, buf, i, key, BLAKE2B_KEYBYTES );
+
+    if( 0 != memcmp( hash, blake2b_keyed_kat[i], BLAKE2B_OUTBYTES ) )
+    {
+      goto fail;
+    }
+  }
+
+  /* Test streaming API */
+  for(step = 1; step < BLAKE2B_BLOCKBYTES; ++step) {
+    for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) {
+      uint8_t hash[BLAKE2B_OUTBYTES];
+      blake2b_state S;
+      uint8_t * p = buf;
+      size_t mlen = i;
+      int err = 0;
+
+      if( (err = blake2b_init_key(&S, BLAKE2B_OUTBYTES, key, BLAKE2B_KEYBYTES)) < 0 ) {
+        goto fail;
+      }
+
+      while (mlen >= step) {
+        if ( (err = blake2b_update(&S, p, step)) < 0 ) {
+          goto fail;
+        }
+        mlen -= step;
+        p += step;
+      }
+      if ( (err = blake2b_update(&S, p, mlen)) < 0) {
+        goto fail;
+      }
+      if ( (err = blake2b_final(&S, hash, BLAKE2B_OUTBYTES)) < 0) {
+        goto fail;
+      }
+
+      if (0 != memcmp(hash, blake2b_keyed_kat[i], BLAKE2B_OUTBYTES)) {
+        goto fail;
+      }
+    }
+  }
+
+  puts( "ok" );
+  return 0;
+fail:
+  puts("error");
+  return -1;
+}
+#endif
+
diff --git a/src/buffer.hpp b/src/buffer.hpp
index 501e44b5ac..8155df87a1 100644
--- a/src/buffer.hpp
+++ b/src/buffer.hpp
@@ -78,6 +78,10 @@ static inline Buf *buf_create_from_mem(const char *ptr, size_t len) {
     return buf;
 }
 
+static inline Buf *buf_create_from_slice(Slice slice) {
+    return buf_create_from_mem((const char *)slice.ptr, slice.len);
+}
+
 static inline Buf *buf_create_from_str(const char *str) {
     return buf_create_from_mem(str, strlen(str));
 }
diff --git a/src/cache_hash.cpp b/src/cache_hash.cpp
new file mode 100644
index 0000000000..b302946310
--- /dev/null
+++ b/src/cache_hash.cpp
@@ -0,0 +1,469 @@
+/*
+ * Copyright (c) 2018 Andrew Kelley
+ *
+ * This file is part of zig, which is MIT licensed.
+ * See http://opensource.org/licenses/MIT
+ */
+
+#include "cache_hash.hpp"
+#include "all_types.hpp"
+#include "buffer.hpp"
+#include "os.hpp"
+
+#include 
+
+void cache_init(CacheHash *ch, Buf *manifest_dir) {
+    int rc = blake2b_init(&ch->blake, 48);
+    assert(rc == 0);
+    ch->files = {};
+    ch->manifest_dir = manifest_dir;
+    ch->manifest_file_path = nullptr;
+    ch->manifest_dirty = false;
+}
+
+void cache_str(CacheHash *ch, const char *ptr) {
+    assert(ch->manifest_file_path == nullptr);
+    assert(ptr != nullptr);
+    // + 1 to include the null byte
+    blake2b_update(&ch->blake, ptr, strlen(ptr) + 1);
+}
+
+void cache_int(CacheHash *ch, int x) {
+    assert(ch->manifest_file_path == nullptr);
+    // + 1 to include the null byte
+    uint8_t buf[sizeof(int) + 1];
+    memcpy(buf, &x, sizeof(int));
+    buf[sizeof(int)] = 0;
+    blake2b_update(&ch->blake, buf, sizeof(int) + 1);
+}
+
+void cache_usize(CacheHash *ch, size_t x) {
+    assert(ch->manifest_file_path == nullptr);
+    // + 1 to include the null byte
+    uint8_t buf[sizeof(size_t) + 1];
+    memcpy(buf, &x, sizeof(size_t));
+    buf[sizeof(size_t)] = 0;
+    blake2b_update(&ch->blake, buf, sizeof(size_t) + 1);
+}
+
+void cache_bool(CacheHash *ch, bool x) {
+    assert(ch->manifest_file_path == nullptr);
+    blake2b_update(&ch->blake, &x, 1);
+}
+
+void cache_buf(CacheHash *ch, Buf *buf) {
+    assert(ch->manifest_file_path == nullptr);
+    assert(buf != nullptr);
+    // + 1 to include the null byte
+    blake2b_update(&ch->blake, buf_ptr(buf), buf_len(buf) + 1);
+}
+
+void cache_buf_opt(CacheHash *ch, Buf *buf) {
+    assert(ch->manifest_file_path == nullptr);
+    if (buf == nullptr) {
+        cache_str(ch, "");
+        cache_str(ch, "");
+    } else {
+        cache_buf(ch, buf);
+    }
+}
+
+void cache_list_of_link_lib(CacheHash *ch, LinkLib **ptr, size_t len) {
+    assert(ch->manifest_file_path == nullptr);
+    for (size_t i = 0; i < len; i += 1) {
+        LinkLib *lib = ptr[i];
+        if (lib->provided_explicitly) {
+            cache_buf(ch, lib->name);
+        }
+    }
+    cache_str(ch, "");
+}
+
+void cache_list_of_buf(CacheHash *ch, Buf **ptr, size_t len) {
+    assert(ch->manifest_file_path == nullptr);
+    for (size_t i = 0; i < len; i += 1) {
+        Buf *buf = ptr[i];
+        cache_buf(ch, buf);
+    }
+    cache_str(ch, "");
+}
+
+void cache_list_of_file(CacheHash *ch, Buf **ptr, size_t len) {
+    assert(ch->manifest_file_path == nullptr);
+
+    for (size_t i = 0; i < len; i += 1) {
+        Buf *buf = ptr[i];
+        cache_file(ch, buf);
+    }
+    cache_str(ch, "");
+}
+
+void cache_list_of_str(CacheHash *ch, const char **ptr, size_t len) {
+    assert(ch->manifest_file_path == nullptr);
+
+    for (size_t i = 0; i < len; i += 1) {
+        const char *s = ptr[i];
+        cache_str(ch, s);
+    }
+    cache_str(ch, "");
+}
+
+void cache_file(CacheHash *ch, Buf *file_path) {
+    assert(ch->manifest_file_path == nullptr);
+    assert(file_path != nullptr);
+    Buf *resolved_path = buf_alloc();
+    *resolved_path = os_path_resolve(&file_path, 1);
+    CacheHashFile *chf = ch->files.add_one();
+    chf->path = resolved_path;
+    cache_buf(ch, resolved_path);
+}
+
+void cache_file_opt(CacheHash *ch, Buf *file_path) {
+    assert(ch->manifest_file_path == nullptr);
+    if (file_path == nullptr) {
+        cache_str(ch, "");
+        cache_str(ch, "");
+    } else {
+        cache_file(ch, file_path);
+    }
+}
+
+// Ported from std/base64.zig
+static uint8_t base64_fs_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+static void base64_encode(Slice dest, Slice source) {
+    size_t dest_len = ((source.len + 2) / 3) * 4;
+    assert(dest.len == dest_len);
+
+    size_t i = 0;
+    size_t out_index = 0;
+    for (; i + 2 < source.len; i += 3) {
+        dest.ptr[out_index] = base64_fs_alphabet[(source.ptr[i] >> 2) & 0x3f];
+        out_index += 1;
+
+        dest.ptr[out_index] = base64_fs_alphabet[((source.ptr[i] & 0x3) << 4) | ((source.ptr[i + 1] & 0xf0) >> 4)];
+        out_index += 1;
+
+        dest.ptr[out_index] = base64_fs_alphabet[((source.ptr[i + 1] & 0xf) << 2) | ((source.ptr[i + 2] & 0xc0) >> 6)];
+        out_index += 1;
+
+        dest.ptr[out_index] = base64_fs_alphabet[source.ptr[i + 2] & 0x3f];
+        out_index += 1;
+    }
+
+    // Assert that we never need pad characters.
+    assert(i == source.len);
+}
+
+// Ported from std/base64.zig
+static Error base64_decode(Slice dest, Slice source) {
+    assert(source.len % 4 == 0);
+    assert(dest.len == (source.len / 4) * 3);
+
+    // In Zig this is comptime computed. In C++ it's not worth it to do that.
+    uint8_t char_to_index[256];
+    bool char_in_alphabet[256] = {0};
+    for (size_t i = 0; i < 64; i += 1) {
+        uint8_t c = base64_fs_alphabet[i];
+        assert(!char_in_alphabet[c]);
+        char_in_alphabet[c] = true;
+        char_to_index[c] = i;
+    }
+
+    size_t src_cursor = 0;
+    size_t dest_cursor = 0;
+
+    for (;src_cursor < source.len; src_cursor += 4) {
+        if (!char_in_alphabet[source.ptr[src_cursor + 0]]) return ErrorInvalidFormat;
+        if (!char_in_alphabet[source.ptr[src_cursor + 1]]) return ErrorInvalidFormat;
+        if (!char_in_alphabet[source.ptr[src_cursor + 2]]) return ErrorInvalidFormat;
+        if (!char_in_alphabet[source.ptr[src_cursor + 3]]) return ErrorInvalidFormat;
+        dest.ptr[dest_cursor + 0] = (char_to_index[source.ptr[src_cursor + 0]] << 2) | (char_to_index[source.ptr[src_cursor + 1]] >> 4);
+        dest.ptr[dest_cursor + 1] = (char_to_index[source.ptr[src_cursor + 1]] << 4) | (char_to_index[source.ptr[src_cursor + 2]] >> 2);
+        dest.ptr[dest_cursor + 2] = (char_to_index[source.ptr[src_cursor + 2]] << 6) | (char_to_index[source.ptr[src_cursor + 3]]);
+        dest_cursor += 3;
+    }
+
+    assert(src_cursor == source.len);
+    assert(dest_cursor == dest.len);
+    return ErrorNone;
+}
+
+static Error hash_file(uint8_t *digest, OsFile handle, Buf *contents) {
+    Error err;
+
+    if (contents) {
+        buf_resize(contents, 0);
+    }
+
+    blake2b_state blake;
+    int rc = blake2b_init(&blake, 48);
+    assert(rc == 0);
+
+    for (;;) {
+        uint8_t buf[4096];
+        size_t amt = 4096;
+        if ((err = os_file_read(handle, buf, &amt)))
+            return err;
+        if (amt == 0) {
+            rc = blake2b_final(&blake, digest, 48);
+            assert(rc == 0);
+            return ErrorNone;
+        }
+        blake2b_update(&blake, buf, amt);
+        if (contents) {
+            buf_append_mem(contents, (char*)buf, amt);
+        }
+    }
+}
+
+static Error populate_file_hash(CacheHash *ch, CacheHashFile *chf, Buf *contents) {
+    Error err;
+
+    assert(chf->path != nullptr);
+
+    OsFile this_file;
+    if ((err = os_file_open_r(chf->path, &this_file)))
+        return err;
+
+    if ((err = os_file_mtime(this_file, &chf->mtime))) {
+        os_file_close(this_file);
+        return err;
+    }
+
+    if ((err = hash_file(chf->bin_digest, this_file, contents))) {
+        os_file_close(this_file);
+        return err;
+    }
+    os_file_close(this_file);
+
+    blake2b_update(&ch->blake, chf->bin_digest, 48);
+
+    return ErrorNone;
+}
+
+Error cache_hit(CacheHash *ch, Buf *out_digest) {
+    Error err;
+
+    uint8_t bin_digest[48];
+    int rc = blake2b_final(&ch->blake, bin_digest, 48);
+    assert(rc == 0);
+
+    if (ch->files.length == 0) {
+        buf_resize(out_digest, 64);
+        base64_encode(buf_to_slice(out_digest), {bin_digest, 48});
+        return ErrorNone;
+    }
+
+    Buf b64_digest = BUF_INIT;
+    buf_resize(&b64_digest, 64);
+    base64_encode(buf_to_slice(&b64_digest), {bin_digest, 48});
+
+    rc = blake2b_init(&ch->blake, 48);
+    assert(rc == 0);
+    blake2b_update(&ch->blake, bin_digest, 48);
+
+    ch->manifest_file_path = buf_alloc();
+    os_path_join(ch->manifest_dir, &b64_digest, ch->manifest_file_path);
+
+    buf_append_str(ch->manifest_file_path, ".txt");
+
+    if ((err = os_make_path(ch->manifest_dir)))
+        return err;
+
+    if ((err = os_file_open_lock_rw(ch->manifest_file_path, &ch->manifest_file)))
+        return err;
+
+    Buf line_buf = BUF_INIT;
+    buf_resize(&line_buf, 512);
+    if ((err = os_file_read_all(ch->manifest_file, &line_buf))) {
+        os_file_close(ch->manifest_file);
+        return err;
+    }
+
+    size_t input_file_count = ch->files.length;
+    bool any_file_changed = false;
+    size_t file_i = 0;
+    SplitIterator line_it = memSplit(buf_to_slice(&line_buf), str("\n"));
+    for (;; file_i += 1) {
+        Optional> opt_line = SplitIterator_next(&line_it);
+        if (!opt_line.is_some)
+            break;
+
+        CacheHashFile *chf;
+        if (file_i < input_file_count) {
+            chf = &ch->files.at(file_i);
+        } else if (any_file_changed) {
+            // cache miss.
+            // keep the the manifest file open with the rw lock
+            // reset the hash
+            rc = blake2b_init(&ch->blake, 48);
+            assert(rc == 0);
+            blake2b_update(&ch->blake, bin_digest, 48);
+            ch->files.resize(input_file_count);
+            // bring the hash up to the input file hashes
+            for (file_i = 0; file_i < input_file_count; file_i += 1) {
+                blake2b_update(&ch->blake, ch->files.at(file_i).bin_digest, 48);
+            }
+            // caller can notice that out_digest is unmodified.
+            return ErrorNone;
+        } else {
+            chf = ch->files.add_one();
+            chf->path = nullptr;
+        }
+
+        SplitIterator it = memSplit(opt_line.value, str(" "));
+
+        Optional> opt_mtime_sec = SplitIterator_next(&it);
+        if (!opt_mtime_sec.is_some) {
+            os_file_close(ch->manifest_file);
+            return ErrorInvalidFormat;
+        }
+        chf->mtime.sec = strtoull((const char *)opt_mtime_sec.value.ptr, nullptr, 10);
+
+        Optional> opt_mtime_nsec = SplitIterator_next(&it);
+        if (!opt_mtime_nsec.is_some) {
+            os_file_close(ch->manifest_file);
+            return ErrorInvalidFormat;
+        }
+        chf->mtime.nsec = strtoull((const char *)opt_mtime_nsec.value.ptr, nullptr, 10);
+
+        Optional> opt_digest = SplitIterator_next(&it);
+        if (!opt_digest.is_some) {
+            os_file_close(ch->manifest_file);
+            return ErrorInvalidFormat;
+        }
+        if ((err = base64_decode({chf->bin_digest, 48}, opt_digest.value))) {
+            os_file_close(ch->manifest_file);
+            return ErrorInvalidFormat;
+        }
+
+        Optional> opt_file_path = SplitIterator_next(&it);
+        if (!opt_file_path.is_some) {
+            os_file_close(ch->manifest_file);
+            return ErrorInvalidFormat;
+        }
+        Buf *this_path = buf_create_from_slice(opt_file_path.value);
+        if (chf->path != nullptr && !buf_eql_buf(this_path, chf->path)) {
+            os_file_close(ch->manifest_file);
+            return ErrorInvalidFormat;
+        }
+        chf->path = this_path;
+
+        // if the mtime matches we can trust the digest
+        OsFile this_file;
+        if ((err = os_file_open_r(chf->path, &this_file))) {
+            os_file_close(ch->manifest_file);
+            return err;
+        }
+        OsTimeStamp actual_mtime;
+        if ((err = os_file_mtime(this_file, &actual_mtime))) {
+            os_file_close(this_file);
+            os_file_close(ch->manifest_file);
+            return err;
+        }
+        if (chf->mtime.sec == actual_mtime.sec && chf->mtime.nsec == actual_mtime.nsec) {
+            os_file_close(this_file);
+        } else {
+            // we have to recompute the digest.
+            // later we'll rewrite the manifest with the new mtime/digest values
+            ch->manifest_dirty = true;
+            chf->mtime = actual_mtime;
+
+            uint8_t actual_digest[48];
+            if ((err = hash_file(actual_digest, this_file, nullptr))) {
+                os_file_close(this_file);
+                os_file_close(ch->manifest_file);
+                return err;
+            }
+            os_file_close(this_file);
+            if (memcmp(chf->bin_digest, actual_digest, 48) != 0) {
+                memcpy(chf->bin_digest, actual_digest, 48);
+                // keep going until we have the input file digests
+                any_file_changed = true;
+            }
+        }
+        if (!any_file_changed) {
+            blake2b_update(&ch->blake, chf->bin_digest, 48);
+        }
+    }
+    if (file_i < input_file_count) {
+        // manifest file is empty or missing entries, so this is a cache miss
+        ch->manifest_dirty = true;
+        for (; file_i < input_file_count; file_i += 1) {
+            CacheHashFile *chf = &ch->files.at(file_i);
+            if ((err = populate_file_hash(ch, chf, nullptr))) {
+                os_file_close(ch->manifest_file);
+                return err;
+            }
+        }
+        return ErrorNone;
+    }
+    // Cache Hit
+    return cache_final(ch, out_digest);
+}
+
+Error cache_add_file_fetch(CacheHash *ch, Buf *resolved_path, Buf *contents) {
+    Error err;
+
+    assert(ch->manifest_file_path != nullptr);
+    CacheHashFile *chf = ch->files.add_one();
+    chf->path = resolved_path;
+    if ((err = populate_file_hash(ch, chf, contents))) {
+        os_file_close(ch->manifest_file);
+        return err;
+    }
+
+    return ErrorNone;
+}
+
+Error cache_add_file(CacheHash *ch, Buf *path) {
+    Buf *resolved_path = buf_alloc();
+    *resolved_path = os_path_resolve(&path, 1);
+    return cache_add_file_fetch(ch, resolved_path, nullptr);
+}
+
+static Error write_manifest_file(CacheHash *ch) {
+    Error err;
+    Buf contents = BUF_INIT;
+    buf_resize(&contents, 0);
+    uint8_t encoded_digest[65];
+    encoded_digest[64] = 0;
+    for (size_t i = 0; i < ch->files.length; i += 1) {
+        CacheHashFile *chf = &ch->files.at(i);
+        base64_encode({encoded_digest, 64}, {chf->bin_digest, 48});
+        buf_appendf(&contents, "%" ZIG_PRI_u64 " %" ZIG_PRI_u64 " %s %s\n",
+            chf->mtime.sec, chf->mtime.nsec, encoded_digest, buf_ptr(chf->path));
+    }
+    if ((err = os_file_overwrite(ch->manifest_file, &contents)))
+        return err;
+
+    return ErrorNone;
+}
+
+Error cache_final(CacheHash *ch, Buf *out_digest) {
+    Error err;
+
+    assert(ch->manifest_file_path != nullptr);
+
+    if (ch->manifest_dirty) {
+        if ((err = write_manifest_file(ch))) {
+            fprintf(stderr, "Warning: Unable to write cache file '%s': %s\n",
+                    buf_ptr(ch->manifest_file_path), err_str(err));
+        }
+    }
+    // We don't close the manifest file yet, because we want to
+    // keep it locked until the API user is done using it.
+
+    uint8_t bin_digest[48];
+    int rc = blake2b_final(&ch->blake, bin_digest, 48);
+    assert(rc == 0);
+    buf_resize(out_digest, 64);
+    base64_encode(buf_to_slice(out_digest), {bin_digest, 48});
+
+    return ErrorNone;
+}
+
+void cache_release(CacheHash *ch) {
+    assert(ch->manifest_file_path != nullptr);
+    os_file_close(ch->manifest_file);
+}
diff --git a/src/cache_hash.hpp b/src/cache_hash.hpp
new file mode 100644
index 0000000000..db1c42ec03
--- /dev/null
+++ b/src/cache_hash.hpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018 Andrew Kelley
+ *
+ * This file is part of zig, which is MIT licensed.
+ * See http://opensource.org/licenses/MIT
+ */
+
+#ifndef ZIG_CACHE_HASH_HPP
+#define ZIG_CACHE_HASH_HPP
+
+#include "blake2.h"
+#include "os.hpp"
+
+struct LinkLib;
+
+struct CacheHashFile {
+    Buf *path;
+    OsTimeStamp mtime;
+    uint8_t bin_digest[48];
+    Buf *contents;
+};
+
+struct CacheHash {
+    blake2b_state blake;
+    ZigList files;
+    Buf *manifest_dir;
+    Buf *manifest_file_path;
+    OsFile manifest_file;
+    bool manifest_dirty;
+};
+
+// Always call this first to set up.
+void cache_init(CacheHash *ch, Buf *manifest_dir);
+
+// Next, use the hash population functions to add the initial parameters.
+void cache_str(CacheHash *ch, const char *ptr);
+void cache_int(CacheHash *ch, int x);
+void cache_bool(CacheHash *ch, bool x);
+void cache_usize(CacheHash *ch, size_t x);
+void cache_buf(CacheHash *ch, Buf *buf);
+void cache_buf_opt(CacheHash *ch, Buf *buf);
+void cache_list_of_link_lib(CacheHash *ch, LinkLib **ptr, size_t len);
+void cache_list_of_buf(CacheHash *ch, Buf **ptr, size_t len);
+void cache_list_of_file(CacheHash *ch, Buf **ptr, size_t len);
+void cache_list_of_str(CacheHash *ch, const char **ptr, size_t len);
+void cache_file(CacheHash *ch, Buf *path);
+void cache_file_opt(CacheHash *ch, Buf *path);
+
+// Then call cache_hit when you're ready to see if you can skip the next step.
+// out_b64_digest will be left unchanged if it was a cache miss.
+// If you got a cache hit, the next step is cache_release.
+// From this point on, there is a lock on the input params. Release
+// the lock with cache_release.
+Error ATTRIBUTE_MUST_USE cache_hit(CacheHash *ch, Buf *out_b64_digest);
+
+// If you did not get a cache hit, call this function for every file
+// that is depended on, and then finish with cache_final.
+Error ATTRIBUTE_MUST_USE cache_add_file(CacheHash *ch, Buf *path);
+
+// This variant of cache_add_file returns the file contents.
+// Also the file path argument must be already resolved.
+Error ATTRIBUTE_MUST_USE cache_add_file_fetch(CacheHash *ch, Buf *resolved_path, Buf *contents);
+
+// out_b64_digest will be the same thing that cache_hit returns if you got a cache hit
+Error ATTRIBUTE_MUST_USE cache_final(CacheHash *ch, Buf *out_b64_digest);
+
+// Until this function is called, no one will be able to get a lock on your input params.
+void cache_release(CacheHash *ch);
+
+
+#endif
diff --git a/src/codegen.cpp b/src/codegen.cpp
index 5b6c53e8f8..6f21ceecab 100644
--- a/src/codegen.cpp
+++ b/src/codegen.cpp
@@ -8,12 +8,12 @@
 #include "analyze.hpp"
 #include "ast_render.hpp"
 #include "codegen.hpp"
+#include "compiler.hpp"
 #include "config.h"
 #include "errmsg.hpp"
 #include "error.hpp"
 #include "hash_map.hpp"
 #include "ir.hpp"
-#include "link.hpp"
 #include "os.hpp"
 #include "translate_c.hpp"
 #include "target.hpp"
@@ -183,14 +183,14 @@ CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out
     return g;
 }
 
-void codegen_destroy(CodeGen *codegen) {
-    LLVMDisposeTargetMachine(codegen->target_machine);
-}
-
 void codegen_set_output_h_path(CodeGen *g, Buf *h_path) {
     g->out_h_path = h_path;
 }
 
+void codegen_set_output_path(CodeGen *g, Buf *path) {
+    g->wanted_output_file_path = path;
+}
+
 void codegen_set_clang_argv(CodeGen *g, const char **args, size_t len) {
     g->clang_argv = args;
     g->clang_argv_len = len;
@@ -243,10 +243,6 @@ void codegen_set_out_name(CodeGen *g, Buf *out_name) {
     g->root_out_name = out_name;
 }
 
-void codegen_set_cache_dir(CodeGen *g, Buf cache_dir) {
-    g->cache_dir = cache_dir;
-}
-
 void codegen_set_libc_lib_dir(CodeGen *g, Buf *libc_lib_dir) {
     g->libc_lib_dir = libc_lib_dir;
 }
@@ -6076,13 +6072,6 @@ static void gen_global_var(CodeGen *g, ZigVar *var, LLVMValueRef init_val,
     // TODO ^^ make an actual global variable
 }
 
-static void ensure_cache_dir(CodeGen *g) {
-    int err;
-    if ((err = os_make_path(&g->cache_dir))) {
-        zig_panic("unable to make cache dir: %s", err_str(err));
-    }
-}
-
 static void validate_inline_fns(CodeGen *g) {
     for (size_t i = 0; i < g->inline_fns.length; i += 1) {
         ZigFn *fn_entry = g->inline_fns.at(i);
@@ -6097,8 +6086,6 @@ static void validate_inline_fns(CodeGen *g) {
 static void do_code_gen(CodeGen *g) {
     assert(!g->errors.length);
 
-    codegen_add_time_event(g, "Code Generation");
-
     {
         // create debug type for error sets
         assert(g->err_enumerators.length == g->errors_by_index.length);
@@ -6401,45 +6388,18 @@ static void do_code_gen(CodeGen *g) {
     char *error = nullptr;
     LLVMVerifyModule(g->module, LLVMAbortProcessAction, &error);
 #endif
+}
 
-    codegen_add_time_event(g, "LLVM Emit Output");
-
-    char *err_msg = nullptr;
-    Buf *o_basename = buf_create_from_buf(g->root_out_name);
-
-    switch (g->emit_file_type) {
-        case EmitFileTypeBinary:
-        {
-            const char *o_ext = target_o_file_ext(&g->zig_target);
-            buf_append_str(o_basename, o_ext);
-            break;
-        }
-        case EmitFileTypeAssembly:
-        {
-            const char *asm_ext = target_asm_file_ext(&g->zig_target);
-            buf_append_str(o_basename, asm_ext);
-            break;
-        }
-        case EmitFileTypeLLVMIr:
-        {
-            const char *llvm_ir_ext = target_llvm_ir_file_ext(&g->zig_target);
-            buf_append_str(o_basename, llvm_ir_ext);
-            break;
-        }
-        default:
-            zig_unreachable();
-    }
-
-    Buf *output_path = buf_alloc();
-    os_path_join(&g->cache_dir, o_basename, output_path);
-    ensure_cache_dir(g);
-
+static void zig_llvm_emit_output(CodeGen *g) {
     bool is_small = g->build_mode == BuildModeSmallRelease;
 
+    Buf *output_path = &g->o_file_output_path;
+    char *err_msg = nullptr;
     switch (g->emit_file_type) {
         case EmitFileTypeBinary:
             if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, buf_ptr(output_path),
-                        ZigLLVM_EmitBinary, &err_msg, g->build_mode == BuildModeDebug, is_small))
+                        ZigLLVM_EmitBinary, &err_msg, g->build_mode == BuildModeDebug, is_small,
+                        g->enable_time_report))
             {
                 zig_panic("unable to write object file %s: %s", buf_ptr(output_path), err_msg);
             }
@@ -6449,22 +6409,22 @@ static void do_code_gen(CodeGen *g) {
 
         case EmitFileTypeAssembly:
             if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, buf_ptr(output_path),
-                        ZigLLVM_EmitAssembly, &err_msg, g->build_mode == BuildModeDebug, is_small))
+                        ZigLLVM_EmitAssembly, &err_msg, g->build_mode == BuildModeDebug, is_small,
+                        g->enable_time_report))
             {
                 zig_panic("unable to write assembly file %s: %s", buf_ptr(output_path), err_msg);
             }
             validate_inline_fns(g);
-            g->link_objects.append(output_path);
             break;
 
         case EmitFileTypeLLVMIr:
             if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, buf_ptr(output_path),
-                        ZigLLVM_EmitLLVMIr, &err_msg, g->build_mode == BuildModeDebug, is_small))
+                        ZigLLVM_EmitLLVMIr, &err_msg, g->build_mode == BuildModeDebug, is_small,
+                        g->enable_time_report))
             {
                 zig_panic("unable to write llvm-ir file %s: %s", buf_ptr(output_path), err_msg);
             }
             validate_inline_fns(g);
-            g->link_objects.append(output_path);
             break;
 
         default:
@@ -7189,36 +7149,84 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
     return contents;
 }
 
-static void define_builtin_compile_vars(CodeGen *g) {
+static Error define_builtin_compile_vars(CodeGen *g) {
     if (g->std_package == nullptr)
-        return;
+        return ErrorNone;
+
+    Error err;
+
+    Buf *manifest_dir = buf_alloc();
+    os_path_join(get_stage1_cache_path(), buf_create_from_str("builtin"), manifest_dir);
+
+    CacheHash cache_hash;
+    cache_init(&cache_hash, manifest_dir);
+
+    Buf *compiler_id;
+    if ((err = get_compiler_id(&compiler_id)))
+        return err;
+
+    // Only a few things affect builtin.zig
+    cache_buf(&cache_hash, compiler_id);
+    cache_int(&cache_hash, g->build_mode);
+    cache_bool(&cache_hash, g->is_test_build);
+    cache_int(&cache_hash, g->zig_target.arch.arch);
+    cache_int(&cache_hash, g->zig_target.arch.sub_arch);
+    cache_int(&cache_hash, g->zig_target.vendor);
+    cache_int(&cache_hash, g->zig_target.os);
+    cache_int(&cache_hash, g->zig_target.env_type);
+    cache_int(&cache_hash, g->zig_target.oformat);
+    cache_bool(&cache_hash, g->have_err_ret_tracing);
+    cache_bool(&cache_hash, g->libc_link_lib != nullptr);
+
+    Buf digest = BUF_INIT;
+    buf_resize(&digest, 0);
+    if ((err = cache_hit(&cache_hash, &digest)))
+        return err;
+
+    // We should always get a cache hit because there are no
+    // files in the input hash.
+    assert(buf_len(&digest) != 0);
+
+    Buf *this_dir = buf_alloc();
+    os_path_join(manifest_dir, &digest, this_dir);
+
+    if ((err = os_make_path(this_dir)))
+        return err;
 
     const char *builtin_zig_basename = "builtin.zig";
     Buf *builtin_zig_path = buf_alloc();
-    os_path_join(&g->cache_dir, buf_create_from_str(builtin_zig_basename), builtin_zig_path);
+    os_path_join(this_dir, buf_create_from_str(builtin_zig_basename), builtin_zig_path);
 
-    Buf *contents = codegen_generate_builtin_source(g);
-    ensure_cache_dir(g);
-    os_write_file(builtin_zig_path, contents);
-
-    Buf *resolved_path = buf_alloc();
-    Buf *resolve_paths[] = {builtin_zig_path};
-    *resolved_path = os_path_resolve(resolve_paths, 1);
+    bool hit;
+    if ((err = os_file_exists(builtin_zig_path, &hit)))
+        return err;
+    Buf *contents;
+    if (hit) {
+        contents = buf_alloc();
+        if ((err = os_fetch_file_path(builtin_zig_path, contents, false))) {
+            fprintf(stderr, "Unable to open '%s': %s\n", buf_ptr(builtin_zig_path), err_str(err));
+            exit(1);
+        }
+    } else {
+        contents = codegen_generate_builtin_source(g);
+        os_write_file(builtin_zig_path, contents);
+    }
 
     assert(g->root_package);
     assert(g->std_package);
-    g->compile_var_package = new_package(buf_ptr(&g->cache_dir), builtin_zig_basename);
+    g->compile_var_package = new_package(buf_ptr(this_dir), builtin_zig_basename);
     g->root_package->package_table.put(buf_create_from_str("builtin"), g->compile_var_package);
     g->std_package->package_table.put(buf_create_from_str("builtin"), g->compile_var_package);
-    g->compile_var_import = add_source_file(g, g->compile_var_package, resolved_path, contents);
+    g->compile_var_import = add_source_file(g, g->compile_var_package, builtin_zig_path, contents);
     scan_import(g, g->compile_var_import);
+
+    return ErrorNone;
 }
 
 static void init(CodeGen *g) {
     if (g->module)
         return;
 
-
     if (g->llvm_argv_len > 0) {
         const char **args = allocate_nonzero(g->llvm_argv_len + 2);
         args[0] = "zig (LLVM option parsing)";
@@ -7325,7 +7333,11 @@ static void init(CodeGen *g) {
     g->have_err_ret_tracing = g->build_mode != BuildModeFastRelease && g->build_mode != BuildModeSmallRelease;
 
     define_builtin_fns(g);
-    define_builtin_compile_vars(g);
+    Error err;
+    if ((err = define_builtin_compile_vars(g))) {
+        fprintf(stderr, "Unable to create builtin.zig: %s\n", err_str(err));
+        exit(1);
+    }
 }
 
 void codegen_translate_c(CodeGen *g, Buf *full_path) {
@@ -7371,8 +7383,8 @@ static ImportTableEntry *add_special_code(CodeGen *g, PackageTableEntry *package
     Buf *resolved_path = buf_alloc();
     *resolved_path = os_path_resolve(resolve_paths, 1);
     Buf *import_code = buf_alloc();
-    int err;
-    if ((err = os_fetch_file_path(resolved_path, import_code, false))) {
+    Error err;
+    if ((err = file_fetch(g, resolved_path, import_code))) {
         zig_panic("unable to open '%s': %s\n", buf_ptr(&path_to_code_src), err_str(err));
     }
 
@@ -7445,23 +7457,32 @@ static void create_test_compile_var_and_add_test_runner(CodeGen *g) {
     g->test_runner_import = add_special_code(g, g->test_runner_package, "test_runner.zig");
 }
 
-static void gen_root_source(CodeGen *g) {
+static Buf *get_resolved_root_src_path(CodeGen *g) {
+    // TODO memoize
     if (buf_len(&g->root_package->root_src_path) == 0)
-        return;
+        return nullptr;
 
-    codegen_add_time_event(g, "Semantic Analysis");
-
-    Buf *rel_full_path = buf_alloc();
-    os_path_join(&g->root_package->root_src_dir, &g->root_package->root_src_path, rel_full_path);
+    Buf rel_full_path = BUF_INIT;
+    os_path_join(&g->root_package->root_src_dir, &g->root_package->root_src_path, &rel_full_path);
 
     Buf *resolved_path = buf_alloc();
-    Buf *resolve_paths[] = {rel_full_path};
+    Buf *resolve_paths[] = {&rel_full_path};
     *resolved_path = os_path_resolve(resolve_paths, 1);
 
+    return resolved_path;
+}
+
+static void gen_root_source(CodeGen *g) {
+    Buf *resolved_path = get_resolved_root_src_path(g);
+    if (resolved_path == nullptr)
+        return;
+
     Buf *source_code = buf_alloc();
     int err;
-    if ((err = os_fetch_file_path(rel_full_path, source_code, true))) {
-        fprintf(stderr, "unable to open '%s': %s\n", buf_ptr(rel_full_path), err_str(err));
+    // No need for using the caching system for this file fetch because it is handled
+    // separately.
+    if ((err = os_fetch_file_path(resolved_path, source_code, true))) {
+        fprintf(stderr, "unable to open '%s': %s\n", buf_ptr(resolved_path), err_str(err));
         exit(1);
     }
 
@@ -7526,6 +7547,8 @@ static void gen_global_asm(CodeGen *g) {
     int err;
     for (size_t i = 0; i < g->assembly_files.length; i += 1) {
         Buf *asm_file = g->assembly_files.at(i);
+        // No need to use the caching system for these fetches because they
+        // are handled separately.
         if ((err = os_fetch_file_path(asm_file, &contents,  false))) {
             zig_panic("Unable to read %s: %s", buf_ptr(asm_file), err_str(err));
         }
@@ -7789,19 +7812,11 @@ static Buf *preprocessor_mangle(Buf *src) {
 }
 
 static void gen_h_file(CodeGen *g) {
-    if (!g->want_h_file)
-        return;
-
     GenH gen_h_data = {0};
     GenH *gen_h = &gen_h_data;
 
-    codegen_add_time_event(g, "Generate .h");
-
     assert(!g->is_test_build);
-
-    if (!g->out_h_path) {
-        g->out_h_path = buf_sprintf("%s.h", buf_ptr(g->root_out_name));
-    }
+    assert(g->out_h_path != nullptr);
 
     FILE *out_h = fopen(buf_ptr(g->out_h_path), "wb");
     if (!out_h)
@@ -8004,14 +8019,231 @@ void codegen_add_time_event(CodeGen *g, const char *name) {
     g->timing_events.append({os_get_time(), name});
 }
 
-void codegen_build(CodeGen *g) {
-    assert(g->out_type != OutTypeUnknown);
-    init(g);
+static void add_cache_pkg(CodeGen *g, CacheHash *ch, PackageTableEntry *pkg) {
+    if (buf_len(&pkg->root_src_path) == 0)
+        return;
 
-    gen_global_asm(g);
-    gen_root_source(g);
-    do_code_gen(g);
-    gen_h_file(g);
+    Buf *rel_full_path = buf_alloc();
+    os_path_join(&pkg->root_src_dir, &pkg->root_src_path, rel_full_path);
+    cache_file(ch, rel_full_path);
+
+    auto it = pkg->package_table.entry_iterator();
+    for (;;) {
+        auto *entry = it.next();
+        if (!entry)
+            break;
+
+        cache_buf(ch, entry->key);
+        add_cache_pkg(g, ch, entry->value);
+    }
+}
+
+// Called before init()
+static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
+    Error err;
+
+    Buf *compiler_id;
+    if ((err = get_compiler_id(&compiler_id)))
+        return err;
+
+    CacheHash *ch = &g->cache_hash;
+    cache_init(ch, manifest_dir);
+
+    add_cache_pkg(g, ch, g->root_package);
+    if (g->linker_script != nullptr) {
+        cache_file(ch, buf_create_from_str(g->linker_script));
+    }
+    cache_buf(ch, compiler_id);
+    cache_buf(ch, g->root_out_name);
+    cache_list_of_link_lib(ch, g->link_libs_list.items, g->link_libs_list.length);
+    cache_list_of_buf(ch, g->darwin_frameworks.items, g->darwin_frameworks.length);
+    cache_list_of_buf(ch, g->rpath_list.items, g->rpath_list.length);
+    cache_list_of_buf(ch, g->forbidden_libs.items, g->forbidden_libs.length);
+    cache_list_of_file(ch, g->link_objects.items, g->link_objects.length);
+    cache_list_of_file(ch, g->assembly_files.items, g->assembly_files.length);
+    cache_int(ch, g->emit_file_type);
+    cache_int(ch, g->build_mode);
+    cache_int(ch, g->out_type);
+    cache_int(ch, g->zig_target.arch.arch);
+    cache_int(ch, g->zig_target.arch.sub_arch);
+    cache_int(ch, g->zig_target.vendor);
+    cache_int(ch, g->zig_target.os);
+    cache_int(ch, g->zig_target.env_type);
+    cache_int(ch, g->zig_target.oformat);
+    cache_bool(ch, g->is_static);
+    cache_bool(ch, g->strip_debug_symbols);
+    cache_bool(ch, g->is_test_build);
+    cache_bool(ch, g->is_native_target);
+    cache_bool(ch, g->windows_subsystem_windows);
+    cache_bool(ch, g->windows_subsystem_console);
+    cache_bool(ch, g->linker_rdynamic);
+    cache_bool(ch, g->no_rosegment_workaround);
+    cache_bool(ch, g->each_lib_rpath);
+    cache_buf_opt(ch, g->mmacosx_version_min);
+    cache_buf_opt(ch, g->mios_version_min);
+    cache_usize(ch, g->version_major);
+    cache_usize(ch, g->version_minor);
+    cache_usize(ch, g->version_patch);
+    cache_buf_opt(ch, g->test_filter);
+    cache_buf_opt(ch, g->test_name_prefix);
+    cache_list_of_str(ch, g->llvm_argv, g->llvm_argv_len);
+    cache_list_of_str(ch, g->clang_argv, g->clang_argv_len);
+    cache_list_of_str(ch, g->lib_dirs.items, g->lib_dirs.length);
+
+    buf_resize(digest, 0);
+    if ((err = cache_hit(ch, digest)))
+        return err;
+
+    return ErrorNone;
+}
+
+static void resolve_out_paths(CodeGen *g) {
+    Buf *o_basename = buf_create_from_buf(g->root_out_name);
+
+    switch (g->emit_file_type) {
+        case EmitFileTypeBinary:
+        {
+            const char *o_ext = target_o_file_ext(&g->zig_target);
+            buf_append_str(o_basename, o_ext);
+            break;
+        }
+        case EmitFileTypeAssembly:
+        {
+            const char *asm_ext = target_asm_file_ext(&g->zig_target);
+            buf_append_str(o_basename, asm_ext);
+            break;
+        }
+        case EmitFileTypeLLVMIr:
+        {
+            const char *llvm_ir_ext = target_llvm_ir_file_ext(&g->zig_target);
+            buf_append_str(o_basename, llvm_ir_ext);
+            break;
+        }
+        default:
+            zig_unreachable();
+    }
+
+    if (g->enable_cache || g->out_type != OutTypeObj) {
+        os_path_join(&g->artifact_dir, o_basename, &g->o_file_output_path);
+    } else if (g->wanted_output_file_path != nullptr && g->out_type == OutTypeObj) {
+        buf_init_from_buf(&g->o_file_output_path, g->wanted_output_file_path);
+    } else {
+        buf_init_from_buf(&g->o_file_output_path, o_basename);
+    }
+
+    if (g->out_type == OutTypeObj) {
+        buf_init_from_buf(&g->output_file_path, &g->o_file_output_path);
+    } else if (g->out_type == OutTypeExe) {
+        if (!g->enable_cache && g->wanted_output_file_path != nullptr) {
+            buf_init_from_buf(&g->output_file_path, g->wanted_output_file_path);
+        } else {
+            assert(g->root_out_name);
+
+            Buf basename = BUF_INIT;
+            buf_init_from_buf(&basename, g->root_out_name);
+            buf_append_str(&basename, target_exe_file_ext(&g->zig_target));
+            if (g->enable_cache) {
+                os_path_join(&g->artifact_dir, &basename, &g->output_file_path);
+            } else {
+                buf_init_from_buf(&g->output_file_path, &basename);
+            }
+        }
+    } else if (g->out_type == OutTypeLib) {
+        if (!g->enable_cache && g->wanted_output_file_path != nullptr) {
+            buf_init_from_buf(&g->output_file_path, g->wanted_output_file_path);
+        } else {
+            Buf basename = BUF_INIT;
+            buf_init_from_buf(&basename, g->root_out_name);
+            buf_append_str(&basename, target_lib_file_ext(&g->zig_target, g->is_static,
+                        g->version_major, g->version_minor, g->version_patch));
+            if (g->enable_cache) {
+                os_path_join(&g->artifact_dir, &basename, &g->output_file_path);
+            } else {
+                buf_init_from_buf(&g->output_file_path, &basename);
+            }
+        }
+    } else {
+        zig_unreachable();
+    }
+
+    if (g->want_h_file && !g->out_h_path) {
+        assert(g->root_out_name);
+        Buf *h_basename = buf_sprintf("%s.h", buf_ptr(g->root_out_name)); 
+        if (g->enable_cache) {
+            g->out_h_path = buf_alloc();
+            os_path_join(&g->artifact_dir, h_basename, g->out_h_path);
+        } else {
+            g->out_h_path = h_basename;
+        }
+    }
+}
+
+
+void codegen_build_and_link(CodeGen *g) {
+    Error err;
+    assert(g->out_type != OutTypeUnknown);
+
+    Buf *stage1_dir = get_stage1_cache_path();
+    Buf *artifact_dir = buf_alloc();
+    Buf digest = BUF_INIT;
+    if (g->enable_cache) {
+        codegen_add_time_event(g, "Check Cache");
+
+        Buf *manifest_dir = buf_alloc();
+        os_path_join(stage1_dir, buf_create_from_str("build"), manifest_dir);
+
+        if ((err = check_cache(g, manifest_dir, &digest))) {
+            fprintf(stderr, "Unable to check cache: %s\n", err_str(err));
+            exit(1);
+        }
+
+        os_path_join(stage1_dir, buf_create_from_str("artifact"), artifact_dir);
+    }
+
+    if (g->enable_cache && buf_len(&digest) != 0) {
+        os_path_join(artifact_dir, &digest, &g->artifact_dir);
+        resolve_out_paths(g);
+    } else {
+        init(g);
+
+        codegen_add_time_event(g, "Semantic Analysis");
+
+        gen_global_asm(g);
+        gen_root_source(g);
+
+        if (g->enable_cache) {
+            if ((err = cache_final(&g->cache_hash, &digest))) {
+                fprintf(stderr, "Unable to finalize cache hash: %s\n", err_str(err));
+                exit(1);
+            }
+            os_path_join(artifact_dir, &digest, &g->artifact_dir);
+        } else {
+            buf_init_from_buf(&g->artifact_dir, &g->cache_dir);
+        }
+        if ((err = os_make_path(&g->artifact_dir))) {
+            fprintf(stderr, "Unable to create artifact directory: %s\n", err_str(err));
+            exit(1);
+        }
+        resolve_out_paths(g);
+
+        codegen_add_time_event(g, "Code Generation");
+        do_code_gen(g);
+        codegen_add_time_event(g, "LLVM Emit Output");
+        zig_llvm_emit_output(g);
+
+        if (g->want_h_file) {
+            codegen_add_time_event(g, "Generate .h");
+            gen_h_file(g);
+        }
+        if (g->out_type != OutTypeObj) {
+            codegen_link(g);
+        }
+    }
+
+    if (g->enable_cache) {
+        cache_release(&g->cache_hash);
+    }
+    codegen_add_time_event(g, "Done");
 }
 
 PackageTableEntry *codegen_create_package(CodeGen *g, const char *root_src_dir, const char *root_src_path) {
diff --git a/src/codegen.hpp b/src/codegen.hpp
index b1a4dbf6e2..1d39e4bf5e 100644
--- a/src/codegen.hpp
+++ b/src/codegen.hpp
@@ -16,7 +16,6 @@
 
 CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out_type, BuildMode build_mode,
     Buf *zig_lib_dir);
-void codegen_destroy(CodeGen *codegen);
 
 void codegen_set_clang_argv(CodeGen *codegen, const char **args, size_t len);
 void codegen_set_llvm_argv(CodeGen *codegen, const char **args, size_t len);
@@ -47,11 +46,12 @@ void codegen_set_linker_script(CodeGen *g, const char *linker_script);
 void codegen_set_test_filter(CodeGen *g, Buf *filter);
 void codegen_set_test_name_prefix(CodeGen *g, Buf *prefix);
 void codegen_set_lib_version(CodeGen *g, size_t major, size_t minor, size_t patch);
-void codegen_set_cache_dir(CodeGen *g, Buf cache_dir);
 void codegen_set_output_h_path(CodeGen *g, Buf *h_path);
+void codegen_set_output_path(CodeGen *g, Buf *path);
 void codegen_add_time_event(CodeGen *g, const char *name);
 void codegen_print_timing_report(CodeGen *g, FILE *f);
-void codegen_build(CodeGen *g);
+void codegen_link(CodeGen *g);
+void codegen_build_and_link(CodeGen *g);
 
 PackageTableEntry *codegen_create_package(CodeGen *g, const char *root_src_dir, const char *root_src_path);
 void codegen_add_assembly(CodeGen *g, Buf *path);
diff --git a/src/compiler.cpp b/src/compiler.cpp
new file mode 100644
index 0000000000..dd02b541dd
--- /dev/null
+++ b/src/compiler.cpp
@@ -0,0 +1,66 @@
+#include "cache_hash.hpp"
+
+#include 
+
+static Buf saved_compiler_id = BUF_INIT;
+static Buf saved_app_data_dir = BUF_INIT;
+static Buf saved_stage1_path = BUF_INIT;
+
+Buf *get_stage1_cache_path() {
+    if (saved_stage1_path.list.length != 0) {
+        return &saved_stage1_path;
+    }
+    Error err;
+    if ((err = os_get_app_data_dir(&saved_app_data_dir, "zig"))) {
+        fprintf(stderr, "Unable to get app data dir: %s\n", err_str(err));
+        exit(1);
+    }
+    os_path_join(&saved_app_data_dir, buf_create_from_str("stage1"), &saved_stage1_path);
+    return &saved_stage1_path;
+}
+
+Error get_compiler_id(Buf **result) {
+    if (saved_compiler_id.list.length != 0) {
+        *result = &saved_compiler_id;
+        return ErrorNone;
+    }
+
+    Error err;
+    Buf *stage1_dir = get_stage1_cache_path();
+    Buf *manifest_dir = buf_alloc();
+    os_path_join(stage1_dir, buf_create_from_str("exe"), manifest_dir);
+
+    CacheHash cache_hash;
+    CacheHash *ch = &cache_hash;
+    cache_init(ch, manifest_dir);
+    Buf self_exe_path = BUF_INIT;
+    if ((err = os_self_exe_path(&self_exe_path)))
+        return err;
+
+    cache_file(ch, &self_exe_path);
+
+    buf_resize(&saved_compiler_id, 0);
+    if ((err = cache_hit(ch, &saved_compiler_id)))
+        return err;
+    if (buf_len(&saved_compiler_id) != 0) {
+        cache_release(ch);
+        *result = &saved_compiler_id;
+        return ErrorNone;
+    }
+    ZigList lib_paths = {};
+    if ((err = os_self_exe_shared_libs(lib_paths)))
+        return err;
+    for (size_t i = 0; i < lib_paths.length; i += 1) {
+        Buf *lib_path = lib_paths.at(i);
+        if ((err = cache_add_file(ch, lib_path)))
+            return err;
+    }
+    if ((err = cache_final(ch, &saved_compiler_id)))
+        return err;
+
+    cache_release(ch);
+
+    *result = &saved_compiler_id;
+    return ErrorNone;
+}
+
diff --git a/src/compiler.hpp b/src/compiler.hpp
new file mode 100644
index 0000000000..b95e4ceda7
--- /dev/null
+++ b/src/compiler.hpp
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2018 Andrew Kelley
+ *
+ * This file is part of zig, which is MIT licensed.
+ * See http://opensource.org/licenses/MIT
+ */
+
+#ifndef ZIG_COMPILER_HPP
+#define ZIG_COMPILER_HPP
+
+#include "buffer.hpp"
+#include "error.hpp"
+
+Buf *get_stage1_cache_path();
+Error get_compiler_id(Buf **result);
+
+#endif
diff --git a/src/error.cpp b/src/error.cpp
index 8303a80a1d..7a9bd963bb 100644
--- a/src/error.cpp
+++ b/src/error.cpp
@@ -27,6 +27,11 @@ const char *err_str(int err) {
         case ErrorNegativeDenominator: return "negative denominator";
         case ErrorShiftedOutOneBits: return "exact shift shifted out one bits";
         case ErrorCCompileErrors: return "C compile errors";
+        case ErrorEndOfFile: return "end of file";
+        case ErrorIsDir: return "is directory";
+        case ErrorUnsupportedOperatingSystem: return "unsupported operating system";
+        case ErrorSharingViolation: return "sharing violation";
+        case ErrorPipeBusy: return "pipe busy";
     }
     return "(invalid error)";
 }
diff --git a/src/error.hpp b/src/error.hpp
index e3b87fc6b8..0996a41d58 100644
--- a/src/error.hpp
+++ b/src/error.hpp
@@ -27,6 +27,11 @@ enum Error {
     ErrorNegativeDenominator,
     ErrorShiftedOutOneBits,
     ErrorCCompileErrors,
+    ErrorEndOfFile,
+    ErrorIsDir,
+    ErrorUnsupportedOperatingSystem,
+    ErrorSharingViolation,
+    ErrorPipeBusy,
 };
 
 const char *err_str(int err);
diff --git a/src/ir.cpp b/src/ir.cpp
index 77b34e7558..030f6627ad 100644
--- a/src/ir.cpp
+++ b/src/ir.cpp
@@ -16231,6 +16231,8 @@ static ZigType *ir_analyze_instruction_union_tag(IrAnalyze *ira, IrInstructionUn
 }
 
 static ZigType *ir_analyze_instruction_import(IrAnalyze *ira, IrInstructionImport *import_instruction) {
+    Error err;
+
     IrInstruction *name_value = import_instruction->name->other;
     Buf *import_target_str = ir_resolve_str(ira, name_value);
     if (!import_target_str)
@@ -16274,8 +16276,7 @@ static ZigType *ir_analyze_instruction_import(IrAnalyze *ira, IrInstructionImpor
         return ira->codegen->builtin_types.entry_namespace;
     }
 
-    int err;
-    if ((err = os_fetch_file_path(resolved_path, import_code, true))) {
+    if ((err = file_fetch(ira->codegen, resolved_path, import_code))) {
         if (err == ErrorFileNotFound) {
             ir_add_error_node(ira, source_node,
                     buf_sprintf("unable to find '%s'", buf_ptr(import_target_path)));
@@ -16286,6 +16287,7 @@ static ZigType *ir_analyze_instruction_import(IrAnalyze *ira, IrInstructionImpor
             return ira->codegen->builtin_types.entry_invalid;
         }
     }
+
     ImportTableEntry *target_import = add_source_file(ira->codegen, target_package, resolved_path, import_code);
 
     scan_import(ira->codegen, target_import);
@@ -17959,6 +17961,12 @@ static ZigType *ir_analyze_instruction_type_name(IrAnalyze *ira, IrInstructionTy
 }
 
 static ZigType *ir_analyze_instruction_c_import(IrAnalyze *ira, IrInstructionCImport *instruction) {
+    if (ira->codegen->enable_cache) {
+        ir_add_error(ira, &instruction->base,
+            buf_sprintf("TODO @cImport is incompatible with --cache on. The cache system currently is unable to detect subsequent changes in .h files."));
+        return ira->codegen->builtin_types.entry_invalid;
+    }
+
     AstNode *node = instruction->base.source_node;
     assert(node->type == NodeTypeFnCallExpr);
     AstNode *block_node = node->data.fn_call_expr.params.at(0);
@@ -18105,7 +18113,7 @@ static ZigType *ir_analyze_instruction_embed_file(IrAnalyze *ira, IrInstructionE
     // load from file system into const expr
     Buf *file_contents = buf_alloc();
     int err;
-    if ((err = os_fetch_file_path(&file_path, file_contents, false))) {
+    if ((err = file_fetch(ira->codegen, &file_path, file_contents))) {
         if (err == ErrorFileNotFound) {
             ir_add_error(ira, instruction->name, buf_sprintf("unable to find '%s'", buf_ptr(&file_path)));
             return ira->codegen->builtin_types.entry_invalid;
@@ -18115,9 +18123,6 @@ static ZigType *ir_analyze_instruction_embed_file(IrAnalyze *ira, IrInstructionE
         }
     }
 
-    // TODO add dependency on the file we embedded so that we know if it changes
-    // we'll have to invalidate the cache
-
     ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base);
     init_const_str_lit(ira->codegen, out_val, file_contents);
 
diff --git a/src/link.cpp b/src/link.cpp
index f44e8b105e..aa0edde61b 100644
--- a/src/link.cpp
+++ b/src/link.cpp
@@ -5,7 +5,6 @@
  * See http://opensource.org/licenses/MIT
  */
 
-#include "link.hpp"
 #include "os.hpp"
 #include "config.h"
 #include "codegen.hpp"
@@ -13,7 +12,6 @@
 
 struct LinkJob {
     CodeGen *codegen;
-    Buf out_file;
     ZigList args;
     bool link_in_crt;
     HashMap rpath_table;
@@ -44,8 +42,6 @@ static Buf *build_o_raw(CodeGen *parent_gen, const char *oname, Buf *full_path)
     child_gen->verbose_llvm_ir = parent_gen->verbose_llvm_ir;
     child_gen->verbose_cimport = parent_gen->verbose_cimport;
 
-    codegen_set_cache_dir(child_gen, parent_gen->cache_dir);
-
     codegen_set_strip(child_gen, parent_gen->strip_debug_symbols);
     codegen_set_is_static(child_gen, parent_gen->is_static);
 
@@ -62,16 +58,9 @@ static Buf *build_o_raw(CodeGen *parent_gen, const char *oname, Buf *full_path)
         new_link_lib->provided_explicitly = link_lib->provided_explicitly;
     }
 
-    codegen_build(child_gen);
-    const char *o_ext = target_o_file_ext(&child_gen->zig_target);
-    Buf *o_out_name = buf_sprintf("%s%s", oname, o_ext);
-    Buf *output_path = buf_alloc();
-    os_path_join(&parent_gen->cache_dir, o_out_name, output_path);
-    codegen_link(child_gen, buf_ptr(output_path));
-
-    codegen_destroy(child_gen);
-
-    return output_path;
+    child_gen->enable_cache = true;
+    codegen_build_and_link(child_gen);
+    return &child_gen->output_file_path;
 }
 
 static Buf *build_o(CodeGen *parent_gen, const char *oname) {
@@ -239,15 +228,15 @@ static void construct_linker_job_elf(LinkJob *lj) {
     } else if (shared) {
         lj->args.append("-shared");
 
-        if (buf_len(&lj->out_file) == 0) {
-            buf_appendf(&lj->out_file, "lib%s.so.%" ZIG_PRI_usize ".%" ZIG_PRI_usize ".%" ZIG_PRI_usize "",
+        if (buf_len(&g->output_file_path) == 0) {
+            buf_appendf(&g->output_file_path, "lib%s.so.%" ZIG_PRI_usize ".%" ZIG_PRI_usize ".%" ZIG_PRI_usize "",
                     buf_ptr(g->root_out_name), g->version_major, g->version_minor, g->version_patch);
         }
         soname = buf_sprintf("lib%s.so.%" ZIG_PRI_usize "", buf_ptr(g->root_out_name), g->version_major);
     }
 
     lj->args.append("-o");
-    lj->args.append(buf_ptr(&lj->out_file));
+    lj->args.append(buf_ptr(&g->output_file_path));
 
     if (lj->link_in_crt) {
         const char *crt1o;
@@ -399,7 +388,7 @@ static void construct_linker_job_wasm(LinkJob *lj) {
 
     lj->args.append("--relocatable");  // So lld doesn't look for _start.
     lj->args.append("-o");
-    lj->args.append(buf_ptr(&lj->out_file));
+    lj->args.append(buf_ptr(&g->output_file_path));
 
     // .o files
     for (size_t i = 0; i < g->link_objects.length; i += 1) {
@@ -480,7 +469,7 @@ static void construct_linker_job_coff(LinkJob *lj) {
     //    }
     //}
 
-    lj->args.append(buf_ptr(buf_sprintf("-OUT:%s", buf_ptr(&lj->out_file))));
+    lj->args.append(buf_ptr(buf_sprintf("-OUT:%s", buf_ptr(&g->output_file_path))));
 
     if (g->libc_link_lib != nullptr) {
         lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", buf_ptr(g->msvc_lib_dir))));
@@ -587,11 +576,11 @@ static void construct_linker_job_coff(LinkJob *lj) {
             buf_appendf(def_contents, "\n");
 
             Buf *def_path = buf_alloc();
-            os_path_join(&g->cache_dir, buf_sprintf("%s.def", buf_ptr(link_lib->name)), def_path);
+            os_path_join(&g->artifact_dir, buf_sprintf("%s.def", buf_ptr(link_lib->name)), def_path);
             os_write_file(def_path, def_contents);
 
             Buf *generated_lib_path = buf_alloc();
-            os_path_join(&g->cache_dir, buf_sprintf("%s.lib", buf_ptr(link_lib->name)), generated_lib_path);
+            os_path_join(&g->artifact_dir, buf_sprintf("%s.lib", buf_ptr(link_lib->name)), generated_lib_path);
 
             gen_lib_args.resize(0);
             gen_lib_args.append("link");
@@ -799,8 +788,8 @@ static void construct_linker_job_macho(LinkJob *lj) {
             //lj->args.append("-install_name");
             //lj->args.append(buf_ptr(dylib_install_name));
 
-            if (buf_len(&lj->out_file) == 0) {
-                buf_appendf(&lj->out_file, "lib%s.%" ZIG_PRI_usize ".%" ZIG_PRI_usize ".%" ZIG_PRI_usize ".dylib",
+            if (buf_len(&g->output_file_path) == 0) {
+                buf_appendf(&g->output_file_path, "lib%s.%" ZIG_PRI_usize ".%" ZIG_PRI_usize ".%" ZIG_PRI_usize ".dylib",
                     buf_ptr(g->root_out_name), g->version_major, g->version_minor, g->version_patch);
             }
         }
@@ -834,13 +823,13 @@ static void construct_linker_job_macho(LinkJob *lj) {
     }
 
     lj->args.append("-o");
-    lj->args.append(buf_ptr(&lj->out_file));
+    lj->args.append(buf_ptr(&g->output_file_path));
 
     for (size_t i = 0; i < g->rpath_list.length; i += 1) {
         Buf *rpath = g->rpath_list.at(i);
         add_rpath(lj, rpath);
     }
-    add_rpath(lj, &lj->out_file);
+    add_rpath(lj, &g->output_file_path);
 
     if (shared) {
         lj->args.append("-headerpad_max_install_names");
@@ -944,7 +933,8 @@ static void construct_linker_job(LinkJob *lj) {
     }
 }
 
-void codegen_link(CodeGen *g, const char *out_file) {
+void codegen_link(CodeGen *g) {
+    assert(g->out_type != OutTypeObj);
     codegen_add_time_event(g, "Build Dependencies");
 
     LinkJob lj = {0};
@@ -955,11 +945,6 @@ void codegen_link(CodeGen *g, const char *out_file) {
 
     lj.rpath_table.init(4);
     lj.codegen = g;
-    if (out_file) {
-        buf_init_from_str(&lj.out_file, out_file);
-    } else {
-        buf_resize(&lj.out_file, 0);
-    }
 
     if (g->verbose_llvm_ir) {
         fprintf(stderr, "\nOptimization:\n");
@@ -968,35 +953,9 @@ void codegen_link(CodeGen *g, const char *out_file) {
         LLVMDumpModule(g->module);
     }
 
-    bool override_out_file = (buf_len(&lj.out_file) != 0);
-    if (!override_out_file) {
-        assert(g->root_out_name);
-
-        buf_init_from_buf(&lj.out_file, g->root_out_name);
-        if (g->out_type == OutTypeExe) {
-            buf_append_str(&lj.out_file, target_exe_file_ext(&g->zig_target));
-        }
-    }
-
-    if (g->out_type == OutTypeObj) {
-        if (override_out_file) {
-            assert(g->link_objects.length == 1);
-            Buf *o_file_path = g->link_objects.at(0);
-            int err;
-            if ((err = os_rename(o_file_path, &lj.out_file))) {
-                zig_panic("unable to rename object file %s into final output %s: %s", buf_ptr(o_file_path), buf_ptr(&lj.out_file), err_str(err));
-            }
-        }
-        return;
-    }
-
     if (g->out_type == OutTypeLib && g->is_static) {
-        // invoke `ar`
-        // example:
-        // # static link into libfoo.a
-        // ar rcs libfoo.a foo1.o foo2.o
-        zig_panic("TODO invoke ar");
-        return;
+        fprintf(stderr, "Zig does not yet support creating static libraries\nSee https://github.com/ziglang/zig/issues/1493\n");
+        exit(1);
     }
 
     lj.link_in_crt = (g->libc_link_lib != nullptr && g->out_type == OutTypeExe);
@@ -1019,6 +978,4 @@ void codegen_link(CodeGen *g, const char *out_file) {
         fprintf(stderr, "%s\n", buf_ptr(&diag));
         exit(1);
     }
-
-    codegen_add_time_event(g, "Done");
 }
diff --git a/src/link.hpp b/src/link.hpp
deleted file mode 100644
index 9f978c28d9..0000000000
--- a/src/link.hpp
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright (c) 2015 Andrew Kelley
- *
- * This file is part of zig, which is MIT licensed.
- * See http://opensource.org/licenses/MIT
- */
-
-#ifndef ZIG_LINK_HPP
-#define ZIG_LINK_HPP
-
-#include "all_types.hpp"
-
-void codegen_link(CodeGen *g, const char *out_file);
-
-
-#endif
-
diff --git a/src/main.cpp b/src/main.cpp
index 4394a1d9ae..bf6c142975 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -8,9 +8,9 @@
 #include "ast_render.hpp"
 #include "buffer.hpp"
 #include "codegen.hpp"
+#include "compiler.hpp"
 #include "config.h"
 #include "error.hpp"
-#include "link.hpp"
 #include "os.hpp"
 #include "target.hpp"
 
@@ -24,6 +24,7 @@ static int usage(const char *arg0) {
         "  build-lib [source]           create library from source or object files\n"
         "  build-obj [source]           create object from source or assembly\n"
         "  builtin                      show the source code of that @import(\"builtin\")\n"
+        "  id                           print the base64-encoded compiler id\n"
         "  run [source]                 create executable and run immediately\n"
         "  translate-c [source]         convert c code to zig code\n"
         "  targets                      list available compilation targets\n"
@@ -33,9 +34,10 @@ static int usage(const char *arg0) {
         "Compile Options:\n"
         "  --assembly [source]          add assembly file to build\n"
         "  --cache-dir [path]           override the cache directory\n"
+        "  --cache [auto|off|on]        build to the global cache and print output path to stdout\n"
         "  --color [auto|off|on]        enable or disable colored error messages\n"
         "  --emit [asm|bin|llvm-ir]     emit a specific file format as compilation output\n"
-        "  --enable-timing-info         print timing diagnostics\n"
+        "  -ftime-report                print timing diagnostics\n"
         "  --libc-include-dir [path]    directory where libc stdlib.h resides\n"
         "  --name [name]                override output name\n"
         "  --output [file]              override destination path\n"
@@ -256,6 +258,24 @@ static void add_package(CodeGen *g, CliPkg *cli_pkg, PackageTableEntry *pkg) {
     }
 }
 
+enum CacheOpt {
+    CacheOptAuto,
+    CacheOptOn,
+    CacheOptOff,
+};
+
+static bool get_cache_opt(CacheOpt opt, bool default_value) {
+    switch (opt) {
+        case CacheOptAuto:
+            return default_value;
+        case CacheOptOn:
+            return true;
+        case CacheOptOff:
+            return false;
+    }
+    zig_unreachable();
+}
+
 int main(int argc, char **argv) {
     if (argc == 2 && strcmp(argv[1], "BUILD_INFO") == 0) {
         printf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
@@ -270,6 +290,17 @@ int main(int argc, char **argv) {
         return 0;
     }
 
+    if (argc == 2 && strcmp(argv[1], "id") == 0) {
+        Error err;
+        Buf *compiler_id;
+        if ((err = get_compiler_id(&compiler_id))) {
+            fprintf(stderr, "Unable to determine compiler id: %s\n", err_str(err));
+            return EXIT_FAILURE;
+        }
+        printf("%s\n", buf_ptr(compiler_id));
+        return EXIT_SUCCESS;
+    }
+
     os_init();
 
     char *arg0 = argv[0];
@@ -289,6 +320,7 @@ int main(int argc, char **argv) {
     bool verbose_llvm_ir = false;
     bool verbose_cimport = false;
     ErrColor color = ErrColorAuto;
+    CacheOpt enable_cache = CacheOptAuto;
     const char *libc_lib_dir = nullptr;
     const char *libc_static_lib_dir = nullptr;
     const char *libc_include_dir = nullptr;
@@ -325,8 +357,7 @@ int main(int argc, char **argv) {
     CliPkg *cur_pkg = allocate(1);
     BuildMode build_mode = BuildModeDebug;
     ZigList test_exec_args = {0};
-    int comptime_args_end = 0;
-    int runtime_args_start = argc;
+    int runtime_args_start = -1;
     bool no_rosegment_workaround = false;
 
     if (argc >= 2 && strcmp(argv[1], "build") == 0) {
@@ -370,8 +401,9 @@ int main(int argc, char **argv) {
         Buf *build_runner_path = buf_alloc();
         os_path_join(special_dir, buf_create_from_str("build_runner.zig"), build_runner_path);
 
-
         CodeGen *g = codegen_create(build_runner_path, nullptr, OutTypeExe, BuildModeDebug, zig_lib_dir_buf);
+        g->enable_time_report = timing_info;
+        buf_init_from_str(&g->cache_dir, cache_dir ? cache_dir : default_zig_cache_name);
         codegen_set_out_name(g, buf_create_from_str("build"));
 
         Buf *build_file_buf = buf_create_from_str(build_file);
@@ -380,6 +412,7 @@ int main(int argc, char **argv) {
         Buf build_file_dirname = BUF_INIT;
         os_path_split(&build_file_abs, &build_file_dirname, &build_file_basename);
 
+
         Buf full_cache_dir = BUF_INIT;
         if (cache_dir == nullptr) {
             os_path_join(&build_file_dirname, buf_create_from_str(default_zig_cache_name), &full_cache_dir);
@@ -388,10 +421,6 @@ int main(int argc, char **argv) {
             full_cache_dir = os_path_resolve(&cache_dir_buf, 1);
         }
 
-        Buf *path_to_build_exe = buf_alloc();
-        os_path_join(&full_cache_dir, buf_create_from_str("build"), path_to_build_exe);
-        codegen_set_cache_dir(g, full_cache_dir);
-
         args.items[1] = buf_ptr(&build_file_dirname);
         args.items[2] = buf_ptr(&full_cache_dir);
 
@@ -459,15 +488,14 @@ int main(int argc, char **argv) {
         PackageTableEntry *build_pkg = codegen_create_package(g, buf_ptr(&build_file_dirname),
                 buf_ptr(&build_file_basename));
         g->root_package->package_table.put(buf_create_from_str("@build"), build_pkg);
-        codegen_build(g);
-        codegen_link(g, buf_ptr(path_to_build_exe));
-        codegen_destroy(g);
+        g->enable_cache = get_cache_opt(enable_cache, true);
+        codegen_build_and_link(g);
 
         Termination term;
-        os_spawn_process(buf_ptr(path_to_build_exe), args, &term);
+        os_spawn_process(buf_ptr(&g->output_file_path), args, &term);
         if (term.how != TerminationIdClean || term.code != 0) {
             fprintf(stderr, "\nBuild failed. The following command failed:\n");
-            fprintf(stderr, "%s", buf_ptr(path_to_build_exe));
+            fprintf(stderr, "%s", buf_ptr(&g->output_file_path));
             for (size_t i = 0; i < args.length; i += 1) {
                 fprintf(stderr, " %s", args.at(i));
             }
@@ -476,15 +504,11 @@ int main(int argc, char **argv) {
         return (term.how == TerminationIdClean) ? term.code : -1;
     }
 
-    for (int i = 1; i < argc; i += 1, comptime_args_end += 1) {
+    for (int i = 1; i < argc; i += 1) {
         char *arg = argv[i];
 
         if (arg[0] == '-') {
-            if (strcmp(arg, "--") == 0) {
-                // ignore -- from both compile and runtime arg sets
-                runtime_args_start = i + 1;
-                break;
-            } else if (strcmp(arg, "--release-fast") == 0) {
+            if (strcmp(arg, "--release-fast") == 0) {
                 build_mode = BuildModeFastRelease;
             } else if (strcmp(arg, "--release-safe") == 0) {
                 build_mode = BuildModeSafeRelease;
@@ -516,7 +540,7 @@ int main(int argc, char **argv) {
                 no_rosegment_workaround = true;
             } else if (strcmp(arg, "--each-lib-rpath") == 0) {
                 each_lib_rpath = true;
-            } else if (strcmp(arg, "--enable-timing-info") == 0) {
+            } else if (strcmp(arg, "-ftime-report") == 0) {
                 timing_info = true;
             } else if (strcmp(arg, "--test-cmd-bin") == 0) {
                 test_exec_args.append(nullptr);
@@ -562,6 +586,17 @@ int main(int argc, char **argv) {
                         fprintf(stderr, "--color options are 'auto', 'on', or 'off'\n");
                         return usage(arg0);
                     }
+                } else if (strcmp(arg, "--cache") == 0) {
+                    if (strcmp(argv[i], "auto") == 0) {
+                        enable_cache = CacheOptAuto;
+                    } else if (strcmp(argv[i], "on") == 0) {
+                        enable_cache = CacheOptOn;
+                    } else if (strcmp(argv[i], "off") == 0) {
+                        enable_cache = CacheOptOff;
+                    } else {
+                        fprintf(stderr, "--cache options are 'auto', 'on', or 'off'\n");
+                        return usage(arg0);
+                    }
                 } else if (strcmp(arg, "--emit") == 0) {
                     if (strcmp(argv[i], "asm") == 0) {
                         emit_file_type = EmitFileTypeAssembly;
@@ -681,6 +716,10 @@ int main(int argc, char **argv) {
                 case CmdTest:
                     if (!in_file) {
                         in_file = arg;
+                        if (cmd == CmdRun) {
+                            runtime_args_start = i + 1;
+                            break; // rest of the args are for the program
+                        }
                     } else {
                         fprintf(stderr, "Unexpected extra parameter: %s\n", arg);
                         return usage(arg0);
@@ -790,32 +829,18 @@ int main(int argc, char **argv) {
 
             Buf *zig_root_source_file = (cmd == CmdTranslateC) ? nullptr : in_file_buf;
 
-            Buf full_cache_dir = BUF_INIT;
-            Buf *run_exec_path = buf_alloc();
-            if (cmd == CmdRun) {
-                if (buf_out_name == nullptr) {
-                    buf_out_name = buf_create_from_str("run");
-                }
-
-                Buf *global_cache_dir = buf_alloc();
-                os_get_global_cache_directory(global_cache_dir);
-                os_path_join(global_cache_dir, buf_out_name, run_exec_path);
-                full_cache_dir = os_path_resolve(&global_cache_dir, 1);
-
-                out_file = buf_ptr(run_exec_path);
-            } else {
-                Buf *resolve_paths = buf_create_from_str((cache_dir == nullptr) ? default_zig_cache_name : cache_dir);
-                full_cache_dir = os_path_resolve(&resolve_paths, 1);
+            if (cmd == CmdRun && buf_out_name == nullptr) {
+                buf_out_name = buf_create_from_str("run");
             }
-
             Buf *zig_lib_dir_buf = resolve_zig_lib_dir();
 
             CodeGen *g = codegen_create(zig_root_source_file, target, out_type, build_mode, zig_lib_dir_buf);
+            g->enable_time_report = timing_info;
+            buf_init_from_str(&g->cache_dir, cache_dir ? cache_dir : default_zig_cache_name);
             codegen_set_out_name(g, buf_out_name);
             codegen_set_lib_version(g, ver_major, ver_minor, ver_patch);
             codegen_set_is_test(g, cmd == CmdTest);
             codegen_set_linker_script(g, linker_script);
-            codegen_set_cache_dir(g, full_cache_dir);
             if (each_lib_rpath)
                 codegen_set_each_lib_rpath(g, each_lib_rpath);
 
@@ -885,6 +910,8 @@ int main(int argc, char **argv) {
                 codegen_set_test_name_prefix(g, buf_create_from_str(test_name_prefix));
             }
 
+            if (out_file)
+                codegen_set_output_path(g, buf_create_from_str(out_file));
             if (out_file_h)
                 codegen_set_output_h_path(g, buf_create_from_str(out_file_h));
 
@@ -904,8 +931,8 @@ int main(int argc, char **argv) {
             if (cmd == CmdBuild || cmd == CmdRun) {
                 codegen_set_emit_file_type(g, emit_file_type);
 
-                codegen_build(g);
-                codegen_link(g, out_file);
+                g->enable_cache = get_cache_opt(enable_cache, cmd == CmdRun);
+                codegen_build_and_link(g);
                 if (timing_info)
                     codegen_print_timing_report(g, stdout);
 
@@ -915,12 +942,26 @@ int main(int argc, char **argv) {
                         args.append(argv[i]);
                     }
 
-                    Termination term;
-                    os_spawn_process(buf_ptr(run_exec_path), args, &term);
-                    return term.code;
-                }
+                    const char *exec_path = buf_ptr(&g->output_file_path);
+                    args.append(nullptr);
 
-                return EXIT_SUCCESS;
+                    os_execv(exec_path, args.items);
+
+                    args.pop();
+                    Termination term;
+                    os_spawn_process(exec_path, args, &term);
+                    return term.code;
+                } else if (cmd == CmdBuild) {
+                    if (g->enable_cache) {
+                        printf("%s\n", buf_ptr(&g->output_file_path));
+                        if (g->out_h_path != nullptr) {
+                            printf("%s\n", buf_ptr(g->out_h_path));
+                        }
+                    }
+                    return EXIT_SUCCESS;
+                } else {
+                    zig_unreachable();
+                }
             } else if (cmd == CmdTranslateC) {
                 codegen_translate_c(g, in_file_buf);
                 ast_render(g, stdout, g->root_import->root, 4);
@@ -933,11 +974,16 @@ int main(int argc, char **argv) {
                 ZigTarget native;
                 get_native_target(&native);
 
-                ZigTarget *non_null_target = target ? target : &native;
+                g->enable_cache = get_cache_opt(enable_cache, false);
+                codegen_build_and_link(g);
 
-                Buf *test_exe_name = buf_sprintf("test%s", target_exe_file_ext(non_null_target));
+                if (timing_info) {
+                    codegen_print_timing_report(g, stdout);
+                }
+
+                Buf *test_exe_path_unresolved = &g->output_file_path;
                 Buf *test_exe_path = buf_alloc();
-                os_path_join(&full_cache_dir, test_exe_name, test_exe_path);
+                *test_exe_path = os_path_resolve(&test_exe_path_unresolved, 1);
 
                 for (size_t i = 0; i < test_exec_args.length; i += 1) {
                     if (test_exec_args.items[i] == nullptr) {
@@ -945,9 +991,6 @@ int main(int argc, char **argv) {
                     }
                 }
 
-                codegen_build(g);
-                codegen_link(g, buf_ptr(test_exe_path));
-
                 if (!target_can_exec(&native, target)) {
                     fprintf(stderr, "Created %s but skipping execution because it is non-native.\n",
                             buf_ptr(test_exe_path));
@@ -969,8 +1012,6 @@ int main(int argc, char **argv) {
                 if (term.how != TerminationIdClean || term.code != 0) {
                     fprintf(stderr, "\nTests failed. Use the following command to reproduce the failure:\n");
                     fprintf(stderr, "%s\n", buf_ptr(test_exe_path));
-                } else if (timing_info) {
-                    codegen_print_timing_report(g, stdout);
                 }
                 return (term.how == TerminationIdClean) ? term.code : -1;
             } else {
diff --git a/src/os.cpp b/src/os.cpp
index 31ed703e62..6e46b96e41 100644
--- a/src/os.cpp
+++ b/src/os.cpp
@@ -24,6 +24,7 @@
 #endif
 
 #include 
+#include 
 #include 
 #include 
 
@@ -40,6 +41,10 @@ typedef SSIZE_T ssize_t;
 
 #endif
 
+#if defined(ZIG_OS_LINUX)
+#include 
+#endif
+
 
 #if defined(__MACH__)
 #include 
@@ -57,54 +62,6 @@ static clock_serv_t cclock;
 #include 
 #include 
 
-// Ported from std/mem.zig.
-// Coordinate struct fields with memSplit function
-struct SplitIterator {
-    size_t index;
-    Slice buffer;
-    Slice split_bytes;
-};
-
-// Ported from std/mem.zig.
-static bool SplitIterator_isSplitByte(SplitIterator *self, uint8_t byte) {
-    for (size_t i = 0; i < self->split_bytes.len; i += 1) {
-        if (byte == self->split_bytes.ptr[i]) {
-            return true;
-        }
-    }
-    return false;
-}
-
-// Ported from std/mem.zig.
-static Optional> SplitIterator_next(SplitIterator *self) {
-    // move to beginning of token
-    while (self->index < self->buffer.len &&
-        SplitIterator_isSplitByte(self, self->buffer.ptr[self->index]))
-    {
-        self->index += 1;
-    }
-    size_t start = self->index;
-    if (start == self->buffer.len) {
-        return {};
-    }
-
-    // move to end of token
-    while (self->index < self->buffer.len &&
-        !SplitIterator_isSplitByte(self, self->buffer.ptr[self->index]))
-    {
-        self->index += 1;
-    }
-    size_t end = self->index;
-
-    return Optional>::some(self->buffer.slice(start, end));
-}
-
-// Ported from std/mem.zig
-static SplitIterator memSplit(Slice buffer, Slice split_bytes) {
-    return SplitIterator{0, buffer, split_bytes};
-}
-
-
 #if defined(ZIG_OS_POSIX)
 static void populate_termination(Termination *term, int status) {
     if (WIFEXITED(status)) {
@@ -765,7 +722,7 @@ Buf os_path_resolve(Buf **paths_ptr, size_t paths_len) {
 #endif
 }
 
-int os_fetch_file(FILE *f, Buf *out_buf, bool skip_shebang) {
+Error os_fetch_file(FILE *f, Buf *out_buf, bool skip_shebang) {
     static const ssize_t buf_size = 0x2000;
     buf_resize(out_buf, buf_size);
     ssize_t actual_buf_len = 0;
@@ -801,7 +758,7 @@ int os_fetch_file(FILE *f, Buf *out_buf, bool skip_shebang) {
         if (amt_read != buf_size) {
             if (feof(f)) {
                 buf_resize(out_buf, actual_buf_len);
-                return 0;
+                return ErrorNone;
             } else {
                 return ErrorFileSystem;
             }
@@ -813,13 +770,13 @@ int os_fetch_file(FILE *f, Buf *out_buf, bool skip_shebang) {
     zig_unreachable();
 }
 
-int os_file_exists(Buf *full_path, bool *result) {
+Error os_file_exists(Buf *full_path, bool *result) {
 #if defined(ZIG_OS_WINDOWS)
     *result = GetFileAttributes(buf_ptr(full_path)) != INVALID_FILE_ATTRIBUTES;
-    return 0;
+    return ErrorNone;
 #else
     *result = access(buf_ptr(full_path), F_OK) != -1;
-    return 0;
+    return ErrorNone;
 #endif
 }
 
@@ -878,13 +835,15 @@ static int os_exec_process_posix(const char *exe, ZigList &args,
 
         FILE *stdout_f = fdopen(stdout_pipe[0], "rb");
         FILE *stderr_f = fdopen(stderr_pipe[0], "rb");
-        os_fetch_file(stdout_f, out_stdout, false);
-        os_fetch_file(stderr_f, out_stderr, false);
+        Error err1 = os_fetch_file(stdout_f, out_stdout, false);
+        Error err2 = os_fetch_file(stderr_f, out_stderr, false);
 
         fclose(stdout_f);
         fclose(stderr_f);
 
-        return 0;
+        if (err1) return err1;
+        if (err2) return err2;
+        return ErrorNone;
     }
 }
 #endif
@@ -1016,6 +975,22 @@ static int os_exec_process_windows(const char *exe, ZigList &args,
 }
 #endif
 
+Error os_execv(const char *exe, const char **argv) {
+#if defined(ZIG_OS_WINDOWS)
+    return ErrorUnsupportedOperatingSystem;
+#else
+    execv(exe, (char *const *)argv);
+    switch (errno) {
+        case ENOMEM:
+            return ErrorSystemResources;
+        case EIO:
+            return ErrorFileSystem;
+        default:
+            return ErrorUnexpected;
+    }
+#endif
+}
+
 int os_exec_process(const char *exe, ZigList &args,
         Termination *term, Buf *out_stderr, Buf *out_stdout)
 {
@@ -1092,7 +1067,7 @@ int os_copy_file(Buf *src_path, Buf *dest_path) {
     }
 }
 
-int os_fetch_file_path(Buf *full_path, Buf *out_contents, bool skip_shebang) {
+Error os_fetch_file_path(Buf *full_path, Buf *out_contents, bool skip_shebang) {
     FILE *f = fopen(buf_ptr(full_path), "rb");
     if (!f) {
         switch (errno) {
@@ -1111,7 +1086,7 @@ int os_fetch_file_path(Buf *full_path, Buf *out_contents, bool skip_shebang) {
                 return ErrorFileSystem;
         }
     }
-    int result = os_fetch_file(f, out_contents, skip_shebang);
+    Error result = os_fetch_file(f, out_contents, skip_shebang);
     fclose(f);
     return result;
 }
@@ -1282,44 +1257,6 @@ int os_buf_to_tmp_file(Buf *contents, Buf *suffix, Buf *out_tmp_path) {
 #endif
 }
 
-#if defined(ZIG_OS_POSIX)
-int os_get_global_cache_directory(Buf *out_tmp_path) {
-    const char *tmp_dir = getenv("TMPDIR");
-    if (!tmp_dir) {
-        tmp_dir = P_tmpdir;
-    }
-
-    Buf *tmp_dir_buf = buf_create_from_str(tmp_dir);
-    Buf *cache_dirname_buf = buf_create_from_str("zig-cache");
-
-    buf_resize(out_tmp_path, 0);
-    os_path_join(tmp_dir_buf, cache_dirname_buf, out_tmp_path);
-
-    buf_deinit(tmp_dir_buf);
-    buf_deinit(cache_dirname_buf);
-    return 0;
-}
-#endif
-
-#if defined(ZIG_OS_WINDOWS)
-int os_get_global_cache_directory(Buf *out_tmp_path) {
-    char tmp_dir[MAX_PATH + 1];
-    if (GetTempPath(MAX_PATH, tmp_dir) == 0) {
-        zig_panic("GetTempPath failed");
-    }
-
-    Buf *tmp_dir_buf = buf_create_from_str(tmp_dir);
-    Buf *cache_dirname_buf = buf_create_from_str("zig-cache");
-
-    buf_resize(out_tmp_path, 0);
-    os_path_join(tmp_dir_buf, cache_dirname_buf, out_tmp_path);
-
-    buf_deinit(tmp_dir_buf);
-    buf_deinit(cache_dirname_buf);
-    return 0;
-}
-#endif
-
 int os_delete_file(Buf *path) {
     if (remove(buf_ptr(path))) {
         return ErrorFileSystem;
@@ -1368,16 +1305,16 @@ double os_get_time(void) {
 #endif
 }
 
-int os_make_path(Buf *path) {
+Error os_make_path(Buf *path) {
     Buf resolved_path = os_path_resolve(&path, 1);
 
     size_t end_index = buf_len(&resolved_path);
-    int err;
+    Error err;
     while (true) {
         if ((err = os_make_dir(buf_slice(&resolved_path, 0, end_index)))) {
             if (err == ErrorPathAlreadyExists) {
                 if (end_index == buf_len(&resolved_path))
-                    return 0;
+                    return ErrorNone;
             } else if (err == ErrorFileNotFound) {
                 // march end_index backward until next path component
                 while (true) {
@@ -1391,7 +1328,7 @@ int os_make_path(Buf *path) {
             }
         }
         if (end_index == buf_len(&resolved_path))
-            return 0;
+            return ErrorNone;
         // march end_index forward until next path component
         while (true) {
             end_index += 1;
@@ -1399,10 +1336,10 @@ int os_make_path(Buf *path) {
                 break;
         }
     }
-    return 0;
+    return ErrorNone;
 }
 
-int os_make_dir(Buf *path) {
+Error os_make_dir(Buf *path) {
 #if defined(ZIG_OS_WINDOWS)
     if (!CreateDirectory(buf_ptr(path), NULL)) {
         if (GetLastError() == ERROR_ALREADY_EXISTS)
@@ -1413,7 +1350,7 @@ int os_make_dir(Buf *path) {
             return ErrorAccess;
         return ErrorUnexpected;
     }
-    return 0;
+    return ErrorNone;
 #else
     if (mkdir(buf_ptr(path), 0755) == -1) {
         if (errno == EEXIST)
@@ -1424,7 +1361,7 @@ int os_make_dir(Buf *path) {
             return ErrorAccess;
         return ErrorUnexpected;
     }
-    return 0;
+    return ErrorNone;
 #endif
 }
 
@@ -1447,7 +1384,7 @@ int os_init(void) {
     return 0;
 }
 
-int os_self_exe_path(Buf *out_path) {
+Error os_self_exe_path(Buf *out_path) {
 #if defined(ZIG_OS_WINDOWS)
     buf_resize(out_path, 256);
     for (;;) {
@@ -1457,7 +1394,7 @@ int os_self_exe_path(Buf *out_path) {
         }
         if (copied_amt < buf_len(out_path)) {
             buf_resize(out_path, copied_amt);
-            return 0;
+            return ErrorNone;
         }
         buf_resize(out_path, buf_len(out_path) * 2);
     }
@@ -1480,27 +1417,21 @@ int os_self_exe_path(Buf *out_path) {
     char *real_path = realpath(buf_ptr(tmp), buf_ptr(out_path));
     if (!real_path) {
         buf_init_from_buf(out_path, tmp);
-        return 0;
+        return ErrorNone;
     }
 
     // Resize out_path for the correct length.
     buf_resize(out_path, strlen(buf_ptr(out_path)));
 
-    return 0;
+    return ErrorNone;
 #elif defined(ZIG_OS_LINUX)
-    buf_resize(out_path, 256);
-    for (;;) {
-        ssize_t amt = readlink("/proc/self/exe", buf_ptr(out_path), buf_len(out_path));
-        if (amt == -1) {
-            return ErrorUnexpected;
-        }
-        if (amt == (ssize_t)buf_len(out_path)) {
-            buf_resize(out_path, buf_len(out_path) * 2);
-            continue;
-        }
-        buf_resize(out_path, amt);
-        return 0;
+    buf_resize(out_path, PATH_MAX);
+    ssize_t amt = readlink("/proc/self/exe", buf_ptr(out_path), buf_len(out_path));
+    if (amt == -1) {
+        return ErrorUnexpected;
     }
+    buf_resize(out_path, amt);
+    return ErrorNone;
 #endif
     return ErrorFileNotFound;
 }
@@ -1685,3 +1616,431 @@ int os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_ArchTy
     return ErrorFileNotFound;
 #endif
 }
+
+#if defined(ZIG_OS_WINDOWS)
+// Ported from std/unicode.zig
+struct Utf16LeIterator {
+    uint8_t *bytes;
+    size_t i;
+};
+
+// Ported from std/unicode.zig
+static Utf16LeIterator Utf16LeIterator_init(WCHAR *ptr) {
+    return {(uint8_t*)ptr, 0};
+}
+
+// Ported from std/unicode.zig
+static Optional Utf16LeIterator_nextCodepoint(Utf16LeIterator *it) {
+    if (it->bytes[it->i] == 0 && it->bytes[it->i + 1] == 0)
+        return {};
+    uint32_t c0 = ((uint32_t)it->bytes[it->i]) | (((uint32_t)it->bytes[it->i + 1]) << 8);
+    if (c0 & ~((uint32_t)0x03ff) == 0xd800) {
+        // surrogate pair
+        it->i += 2;
+        assert(it->bytes[it->i] != 0 || it->bytes[it->i + 1] != 0);
+        uint32_t c1 = ((uint32_t)it->bytes[it->i]) | (((uint32_t)it->bytes[it->i + 1]) << 8);
+        assert(c1 & ~((uint32_t)0x03ff) == 0xdc00);
+        it->i += 2;
+        return Optional::some(0x10000 + (((c0 & 0x03ff) << 10) | (c1 & 0x03ff)));
+    } else {
+        assert(c0 & ~((uint32_t)0x03ff) != 0xdc00);
+        it->i += 2;
+        return Optional::some(c0);
+    }
+}
+
+// Ported from std/unicode.zig
+static uint8_t utf8CodepointSequenceLength(uint32_t c) {
+    if (c < 0x80) return 1;
+    if (c < 0x800) return 2;
+    if (c < 0x10000) return 3;
+    if (c < 0x110000) return 4;
+    zig_unreachable();
+}
+
+// Ported from std/unicode.zig
+static size_t utf8Encode(uint32_t c, Slice out) {
+    size_t length = utf8CodepointSequenceLength(c);
+    assert(out.len >= length);
+    switch (length) {
+        // The pattern for each is the same
+        // - Increasing the initial shift by 6 each time
+        // - Each time after the first shorten the shifted
+        //   value to a max of 0b111111 (63)
+        case 1:
+            out.ptr[0] = c; // Can just do 0 + codepoint for initial range
+            break;
+        case 2:
+            out.ptr[0] = 0b11000000 | (c >> 6);
+            out.ptr[1] = 0b10000000 | (c & 0b111111);
+            break;
+        case 3:
+            assert(!(0xd800 <= c && c <= 0xdfff));
+            out.ptr[0] = 0b11100000 | (c >> 12);
+            out.ptr[1] = 0b10000000 | ((c >> 6) & 0b111111);
+            out.ptr[2] = 0b10000000 | (c & 0b111111);
+            break;
+        case 4:
+            out.ptr[0] = 0b11110000 | (c >> 18);
+            out.ptr[1] = 0b10000000 | ((c >> 12) & 0b111111);
+            out.ptr[2] = 0b10000000 | ((c >> 6) & 0b111111);
+            out.ptr[3] = 0b10000000 | (c & 0b111111);
+            break;
+        default:
+            zig_unreachable();
+    }
+    return length;
+}
+
+// Ported from std.unicode.utf16leToUtf8Alloc
+static void utf16le_ptr_to_utf8(Buf *out, WCHAR *utf16le) {
+    // optimistically guess that it will all be ascii.
+    buf_resize(out, 0);
+    size_t out_index = 0;
+    Utf16LeIterator it = Utf16LeIterator_init(utf16le);
+    for (;;) {
+        Optional opt_codepoint = Utf16LeIterator_nextCodepoint(&it);
+        if (!opt_codepoint.is_some) break;
+        uint32_t codepoint = opt_codepoint.value;
+
+        size_t utf8_len = utf8CodepointSequenceLength(codepoint);
+        buf_resize(out, buf_len(out) + utf8_len);
+        utf8Encode(codepoint, {(uint8_t*)buf_ptr(out)+out_index, buf_len(out)-out_index});
+        out_index += utf8_len;
+    }
+}
+#endif
+
+// Ported from std.os.getAppDataDir
+Error os_get_app_data_dir(Buf *out_path, const char *appname) {
+#if defined(ZIG_OS_WINDOWS)
+    Error err;
+    WCHAR *dir_path_ptr;
+    switch (SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, nullptr, &dir_path_ptr)) {
+        case S_OK:
+            // defer os.windows.CoTaskMemFree(@ptrCast(*c_void, dir_path_ptr));
+            utf16le_ptr_to_utf8(out_path, dir_path_ptr);
+            CoTaskMemFree(dir_path_ptr);
+            buf_appendf(out_path, "\\%s", appname);
+            return ErrorNone;
+        case E_OUTOFMEMORY:
+            return ErrorNoMem;
+        default:
+            return ErrorUnexpected;
+    }
+    zig_unreachable();
+#elif defined(ZIG_OS_DARWIN)
+    const char *home_dir = getenv("HOME");
+    if (home_dir == nullptr) {
+        // TODO use /etc/passwd
+        return ErrorFileNotFound;
+    }
+    buf_resize(out_path, 0);
+    buf_appendf(out_path, "%s/Library/Application Support/%s", home_dir, appname);
+    return ErrorNone;
+#elif defined(ZIG_OS_LINUX)
+    const char *home_dir = getenv("HOME");
+    if (home_dir == nullptr) {
+        // TODO use /etc/passwd
+        return ErrorFileNotFound;
+    }
+    buf_resize(out_path, 0);
+    buf_appendf(out_path, "%s/.local/share/%s", home_dir, appname);
+    return ErrorNone;
+#endif
+}
+
+
+#if defined(ZIG_OS_LINUX)
+static int self_exe_shared_libs_callback(struct dl_phdr_info *info, size_t size, void *data) {
+    ZigList *libs = reinterpret_cast< ZigList *>(data);
+    if (info->dlpi_name[0] == '/') {
+        libs->append(buf_create_from_str(info->dlpi_name));
+    }
+    return 0;
+}
+#endif
+
+Error os_self_exe_shared_libs(ZigList &paths) {
+#if defined(ZIG_OS_LINUX)
+    paths.resize(0);
+    dl_iterate_phdr(self_exe_shared_libs_callback, &paths);
+    return ErrorNone;
+#elif defined(ZIG_OS_DARWIN)
+    paths.resize(0);
+    uint32_t img_count = _dyld_image_count();
+    for (uint32_t i = 0; i != img_count; i += 1) {
+        const char *name = _dyld_get_image_name(i);
+        paths.append(buf_create_from_str(name));
+    }
+    return ErrorNone;
+#elif defined(ZIG_OS_WINDOWS)
+    // zig is built statically on windows, so we can return an empty list
+    paths.resize(0);
+    return ErrorNone;
+#else
+#error unimplemented
+#endif
+}
+
+Error os_file_open_r(Buf *full_path, OsFile *out_file) {
+#if defined(ZIG_OS_WINDOWS)
+    // TODO use CreateFileW
+    HANDLE result = CreateFileA(buf_ptr(full_path), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
+
+    if (result == INVALID_HANDLE_VALUE) {
+        DWORD err = GetLastError();
+        switch (err) {
+            case ERROR_SHARING_VIOLATION:
+                return ErrorSharingViolation;
+            case ERROR_ALREADY_EXISTS:
+                return ErrorPathAlreadyExists;
+            case ERROR_FILE_EXISTS:
+                return ErrorPathAlreadyExists;
+            case ERROR_FILE_NOT_FOUND:
+                return ErrorFileNotFound;
+            case ERROR_PATH_NOT_FOUND:
+                return ErrorFileNotFound;
+            case ERROR_ACCESS_DENIED:
+                return ErrorAccess;
+            case ERROR_PIPE_BUSY:
+                return ErrorPipeBusy;
+            default:
+                return ErrorUnexpected;
+        }
+    }
+
+    *out_file = result;
+    return ErrorNone;
+#else
+    for (;;) {
+        int fd = open(buf_ptr(full_path), O_RDONLY|O_CLOEXEC);
+        if (fd == -1) {
+            switch (errno) {
+                case EINTR:
+                    continue;
+                case EINVAL:
+                    zig_unreachable();
+                case EFAULT:
+                    zig_unreachable();
+                case EACCES:
+                    return ErrorAccess;
+                case EISDIR:
+                    return ErrorIsDir;
+                case ENOENT:
+                    return ErrorFileNotFound;
+                default:
+                    return ErrorFileSystem;
+            }
+        }
+        *out_file = fd;
+        return ErrorNone;
+    }
+#endif
+}
+
+Error os_file_open_lock_rw(Buf *full_path, OsFile *out_file) {
+#if defined(ZIG_OS_WINDOWS)
+    for (;;) {
+        HANDLE result = CreateFileA(buf_ptr(full_path), GENERIC_READ | GENERIC_WRITE,
+            0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
+
+        if (result == INVALID_HANDLE_VALUE) {
+            DWORD err = GetLastError();
+            switch (err) {
+                case ERROR_SHARING_VIOLATION:
+                    // TODO wait for the lock instead of sleeping
+                    Sleep(10);
+                    continue;
+                case ERROR_ALREADY_EXISTS:
+                    return ErrorPathAlreadyExists;
+                case ERROR_FILE_EXISTS:
+                    return ErrorPathAlreadyExists;
+                case ERROR_FILE_NOT_FOUND:
+                    return ErrorFileNotFound;
+                case ERROR_PATH_NOT_FOUND:
+                    return ErrorFileNotFound;
+                case ERROR_ACCESS_DENIED:
+                    return ErrorAccess;
+                case ERROR_PIPE_BUSY:
+                    return ErrorPipeBusy;
+                default:
+                    return ErrorUnexpected;
+            }
+        }
+        *out_file = result;
+        return ErrorNone;
+    }
+#else
+    int fd;
+    for (;;) {
+        fd = open(buf_ptr(full_path), O_RDWR|O_CLOEXEC|O_CREAT, 0666);
+        if (fd == -1) {
+            switch (errno) {
+                case EINTR:
+                    continue;
+                case EINVAL:
+                    zig_unreachable();
+                case EFAULT:
+                    zig_unreachable();
+                case EACCES:
+                    return ErrorAccess;
+                case EISDIR:
+                    return ErrorIsDir;
+                case ENOENT:
+                    return ErrorFileNotFound;
+                default:
+                    return ErrorFileSystem;
+            }
+        }
+        break;
+    }
+    for (;;) {
+        struct flock lock;
+        lock.l_type = F_WRLCK;
+        lock.l_whence = SEEK_SET;
+        lock.l_start = 0;
+        lock.l_len = 0;
+        if (fcntl(fd, F_SETLKW, &lock) == -1) {
+            switch (errno) {
+                case EINTR:
+                    continue;
+                case EBADF:
+                    zig_unreachable();
+                case EFAULT:
+                    zig_unreachable();
+                case EINVAL:
+                    zig_unreachable();
+                default:
+                    close(fd);
+                    return ErrorFileSystem;
+            }
+        }
+        break;
+    }
+    *out_file = fd;
+    return ErrorNone;
+#endif
+}
+
+Error os_file_mtime(OsFile file, OsTimeStamp *mtime) {
+#if defined(ZIG_OS_WINDOWS)
+    FILETIME last_write_time;
+    if (!GetFileTime(file, nullptr, nullptr, &last_write_time))
+        return ErrorUnexpected;
+    mtime->sec = last_write_time.dwLowDateTime | (last_write_time.dwHighDateTime << 32);
+    mtime->nsec = 0;
+    return ErrorNone;
+#elif defined(ZIG_OS_LINUX)
+    struct stat statbuf;
+    if (fstat(file, &statbuf) == -1)
+        return ErrorFileSystem;
+
+    mtime->sec = statbuf.st_mtim.tv_sec;
+    mtime->nsec = statbuf.st_mtim.tv_nsec;
+    return ErrorNone;
+#elif defined(ZIG_OS_DARWIN)
+    struct stat statbuf;
+    if (fstat(file, &statbuf) == -1)
+        return ErrorFileSystem;
+
+    mtime->sec = statbuf.st_mtimespec.tv_sec;
+    mtime->nsec = statbuf.st_mtimespec.tv_nsec;
+    return ErrorNone;
+#else
+#error unimplemented
+#endif
+}
+
+Error os_file_read(OsFile file, void *ptr, size_t *len) {
+#if defined(ZIG_OS_WINDOWS)
+    DWORD amt_read;
+    if (ReadFile(file, ptr, *len, &amt_read, nullptr) == 0)
+        return ErrorUnexpected;
+    *len = amt_read;
+    return ErrorNone;
+#else
+    for (;;) {
+        ssize_t rc = read(file, ptr, *len);
+        if (rc == -1) {
+            switch (errno) {
+                case EINTR:
+                    continue;
+                case EBADF:
+                    zig_unreachable();
+                case EFAULT:
+                    zig_unreachable();
+                case EISDIR:
+                    zig_unreachable();
+                default:
+                    return ErrorFileSystem;
+            }
+        }
+        *len = rc;
+        return ErrorNone;
+    }
+#endif
+}
+
+Error os_file_read_all(OsFile file, Buf *contents) {
+    Error err;
+    size_t index = 0;
+    for (;;) {
+        size_t amt = buf_len(contents) - index;
+
+        if (amt < 4096) {
+            buf_resize(contents, buf_len(contents) + (4096 - amt));
+            amt = buf_len(contents) - index;
+        }
+
+        if ((err = os_file_read(file, buf_ptr(contents) + index, &amt)))
+            return err;
+
+        if (amt == 0) {
+            buf_resize(contents, index);
+            return ErrorNone;
+        }
+
+        index += amt;
+    }
+}
+
+Error os_file_overwrite(OsFile file, Buf *contents) {
+#if defined(ZIG_OS_WINDOWS)
+    if (SetFilePointer(file, 0, nullptr, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
+        return ErrorFileSystem;
+    if (!SetEndOfFile(file))
+        return ErrorFileSystem;
+    if (!WriteFile(file, buf_ptr(contents), buf_len(contents), nullptr, nullptr))
+        return ErrorFileSystem;
+    return ErrorNone;
+#else
+    if (lseek(file, 0, SEEK_SET) == -1)
+        return ErrorFileSystem;
+    if (ftruncate(file, 0) == -1)
+        return ErrorFileSystem;
+    for (;;) {
+        if (write(file, buf_ptr(contents), buf_len(contents)) == -1) {
+            switch (errno) {
+                case EINTR:
+                    continue;
+                case EINVAL:
+                    zig_unreachable();
+                case EBADF:
+                    zig_unreachable();
+                default:
+                    return ErrorFileSystem;
+            }
+        }
+        return ErrorNone;
+    }
+#endif
+}
+
+void os_file_close(OsFile file) {
+#if defined(ZIG_OS_WINDOWS)
+    CloseHandle(file);
+#else
+    close(file);
+#endif
+}
diff --git a/src/os.hpp b/src/os.hpp
index a44fa8160e..1054bf24a7 100644
--- a/src/os.hpp
+++ b/src/os.hpp
@@ -13,77 +13,11 @@
 #include "error.hpp"
 #include "zig_llvm.h"
 #include "windows_sdk.h"
+#include "result.hpp"
 
 #include 
 #include 
 
-enum TermColor {
-    TermColorRed,
-    TermColorGreen,
-    TermColorCyan,
-    TermColorWhite,
-    TermColorBold,
-    TermColorReset,
-};
-
-enum TerminationId {
-    TerminationIdClean,
-    TerminationIdSignaled,
-    TerminationIdStopped,
-    TerminationIdUnknown,
-};
-
-struct Termination {
-    TerminationId how;
-    int code;
-};
-
-int os_init(void);
-
-void os_spawn_process(const char *exe, ZigList &args, Termination *term);
-int os_exec_process(const char *exe, ZigList &args,
-        Termination *term, Buf *out_stderr, Buf *out_stdout);
-
-void os_path_dirname(Buf *full_path, Buf *out_dirname);
-void os_path_split(Buf *full_path, Buf *out_dirname, Buf *out_basename);
-void os_path_extname(Buf *full_path, Buf *out_basename, Buf *out_extname);
-void os_path_join(Buf *dirname, Buf *basename, Buf *out_full_path);
-int os_path_real(Buf *rel_path, Buf *out_abs_path);
-Buf os_path_resolve(Buf **paths_ptr, size_t paths_len);
-bool os_path_is_absolute(Buf *path);
-
-int os_get_global_cache_directory(Buf *out_tmp_path);
-
-int os_make_path(Buf *path);
-int os_make_dir(Buf *path);
-
-void os_write_file(Buf *full_path, Buf *contents);
-int os_copy_file(Buf *src_path, Buf *dest_path);
-
-int os_fetch_file(FILE *file, Buf *out_contents, bool skip_shebang);
-int os_fetch_file_path(Buf *full_path, Buf *out_contents, bool skip_shebang);
-
-int os_get_cwd(Buf *out_cwd);
-
-bool os_stderr_tty(void);
-void os_stderr_set_color(TermColor color);
-
-int os_buf_to_tmp_file(Buf *contents, Buf *suffix, Buf *out_tmp_path);
-int os_delete_file(Buf *path);
-
-int os_file_exists(Buf *full_path, bool *result);
-
-int os_rename(Buf *src_path, Buf *dest_path);
-double os_get_time(void);
-
-bool os_is_sep(uint8_t c);
-
-int os_self_exe_path(Buf *out_path);
-
-int os_get_win32_ucrt_include_path(ZigWindowsSDK *sdk, Buf *output_buf);
-int os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf *output_buf, ZigLLVM_ArchType platform_type);
-int os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf *output_buf, ZigLLVM_ArchType platform_type);
-
 #if defined(__APPLE__)
 #define ZIG_OS_DARWIN
 #elif defined(_WIN32)
@@ -116,4 +50,93 @@ int os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf *output_buf, ZigLLVM_ArchTy
 #define ZIG_OS_SEP_CHAR '/'
 #endif
 
+enum TermColor {
+    TermColorRed,
+    TermColorGreen,
+    TermColorCyan,
+    TermColorWhite,
+    TermColorBold,
+    TermColorReset,
+};
+
+enum TerminationId {
+    TerminationIdClean,
+    TerminationIdSignaled,
+    TerminationIdStopped,
+    TerminationIdUnknown,
+};
+
+struct Termination {
+    TerminationId how;
+    int code;
+};
+
+#if defined(ZIG_OS_WINDOWS)
+#define OsFile void *
+#else
+#define OsFile int
+#endif
+
+struct OsTimeStamp {
+    uint64_t sec;
+    uint64_t nsec;
+};
+
+int os_init(void);
+
+void os_spawn_process(const char *exe, ZigList &args, Termination *term);
+int os_exec_process(const char *exe, ZigList &args,
+        Termination *term, Buf *out_stderr, Buf *out_stdout);
+Error os_execv(const char *exe, const char **argv);
+
+void os_path_dirname(Buf *full_path, Buf *out_dirname);
+void os_path_split(Buf *full_path, Buf *out_dirname, Buf *out_basename);
+void os_path_extname(Buf *full_path, Buf *out_basename, Buf *out_extname);
+void os_path_join(Buf *dirname, Buf *basename, Buf *out_full_path);
+int os_path_real(Buf *rel_path, Buf *out_abs_path);
+Buf os_path_resolve(Buf **paths_ptr, size_t paths_len);
+bool os_path_is_absolute(Buf *path);
+
+Error ATTRIBUTE_MUST_USE os_make_path(Buf *path);
+Error ATTRIBUTE_MUST_USE os_make_dir(Buf *path);
+
+Error ATTRIBUTE_MUST_USE os_file_open_r(Buf *full_path, OsFile *out_file);
+Error ATTRIBUTE_MUST_USE os_file_open_lock_rw(Buf *full_path, OsFile *out_file);
+Error ATTRIBUTE_MUST_USE os_file_mtime(OsFile file, OsTimeStamp *mtime);
+Error ATTRIBUTE_MUST_USE os_file_read(OsFile file, void *ptr, size_t *len);
+Error ATTRIBUTE_MUST_USE os_file_read_all(OsFile file, Buf *contents);
+Error ATTRIBUTE_MUST_USE os_file_overwrite(OsFile file, Buf *contents);
+void os_file_close(OsFile file);
+
+void os_write_file(Buf *full_path, Buf *contents);
+int os_copy_file(Buf *src_path, Buf *dest_path);
+
+Error ATTRIBUTE_MUST_USE os_fetch_file(FILE *file, Buf *out_contents, bool skip_shebang);
+Error ATTRIBUTE_MUST_USE os_fetch_file_path(Buf *full_path, Buf *out_contents, bool skip_shebang);
+
+int os_get_cwd(Buf *out_cwd);
+
+bool os_stderr_tty(void);
+void os_stderr_set_color(TermColor color);
+
+int os_buf_to_tmp_file(Buf *contents, Buf *suffix, Buf *out_tmp_path);
+int os_delete_file(Buf *path);
+
+Error ATTRIBUTE_MUST_USE os_file_exists(Buf *full_path, bool *result);
+
+int os_rename(Buf *src_path, Buf *dest_path);
+double os_get_time(void);
+
+bool os_is_sep(uint8_t c);
+
+Error ATTRIBUTE_MUST_USE os_self_exe_path(Buf *out_path);
+
+Error ATTRIBUTE_MUST_USE os_get_app_data_dir(Buf *out_path, const char *appname);
+
+int os_get_win32_ucrt_include_path(ZigWindowsSDK *sdk, Buf *output_buf);
+int os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf *output_buf, ZigLLVM_ArchType platform_type);
+int os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf *output_buf, ZigLLVM_ArchType platform_type);
+
+Error ATTRIBUTE_MUST_USE os_self_exe_shared_libs(ZigList &paths);
+
 #endif
diff --git a/src/target.cpp b/src/target.cpp
index 91d36c5109..f657af8af3 100644
--- a/src/target.cpp
+++ b/src/target.cpp
@@ -813,6 +813,22 @@ const char *target_exe_file_ext(ZigTarget *target) {
     }
 }
 
+const char *target_lib_file_ext(ZigTarget *target, bool is_static, size_t version_major, size_t version_minor, size_t version_patch) {
+    if (target->os == OsWindows) {
+        if (is_static) {
+            return ".lib";
+        } else {
+            return ".dll";
+        }
+    } else {
+        if (is_static) {
+            return ".a";
+        } else {
+            return buf_ptr(buf_sprintf(".so.%zu", version_major));
+        }
+    }
+}
+
 enum FloatAbi {
     FloatAbiHard,
     FloatAbiSoft,
diff --git a/src/target.hpp b/src/target.hpp
index 5a118f6d8d..c8658bf352 100644
--- a/src/target.hpp
+++ b/src/target.hpp
@@ -113,6 +113,7 @@ const char *target_o_file_ext(ZigTarget *target);
 const char *target_asm_file_ext(ZigTarget *target);
 const char *target_llvm_ir_file_ext(ZigTarget *target);
 const char *target_exe_file_ext(ZigTarget *target);
+const char *target_lib_file_ext(ZigTarget *target, bool is_static, size_t version_major, size_t version_minor, size_t version_patch);
 
 Buf *target_dynamic_linker(ZigTarget *target);
 
diff --git a/src/util.cpp b/src/util.cpp
index cafd2c3d85..060d7f8fb5 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -43,3 +43,42 @@ uint32_t ptr_hash(const void *ptr) {
 bool ptr_eq(const void *a, const void *b) {
     return a == b;
 }
+
+// Ported from std/mem.zig.
+bool SplitIterator_isSplitByte(SplitIterator *self, uint8_t byte) {
+    for (size_t i = 0; i < self->split_bytes.len; i += 1) {
+        if (byte == self->split_bytes.ptr[i]) {
+            return true;
+        }
+    }
+    return false;
+}
+
+// Ported from std/mem.zig.
+Optional> SplitIterator_next(SplitIterator *self) {
+    // move to beginning of token
+    while (self->index < self->buffer.len &&
+        SplitIterator_isSplitByte(self, self->buffer.ptr[self->index]))
+    {
+        self->index += 1;
+    }
+    size_t start = self->index;
+    if (start == self->buffer.len) {
+        return {};
+    }
+
+    // move to end of token
+    while (self->index < self->buffer.len &&
+        !SplitIterator_isSplitByte(self, self->buffer.ptr[self->index]))
+    {
+        self->index += 1;
+    }
+    size_t end = self->index;
+
+    return Optional>::some(self->buffer.slice(start, end));
+}
+
+// Ported from std/mem.zig
+SplitIterator memSplit(Slice buffer, Slice split_bytes) {
+    return SplitIterator{0, buffer, split_bytes};
+}
diff --git a/src/util.hpp b/src/util.hpp
index 1e02e3043c..f18c369fb5 100644
--- a/src/util.hpp
+++ b/src/util.hpp
@@ -254,4 +254,16 @@ static inline void memCopy(Slice dest, Slice src) {
     memcpy(dest.ptr, src.ptr, src.len * sizeof(T));
 }
 
+// Ported from std/mem.zig.
+// Coordinate struct fields with memSplit function
+struct SplitIterator {
+    size_t index;
+    Slice buffer;
+    Slice split_bytes;
+};
+
+bool SplitIterator_isSplitByte(SplitIterator *self, uint8_t byte);
+Optional> SplitIterator_next(SplitIterator *self);
+SplitIterator memSplit(Slice buffer, Slice split_bytes);
+
 #endif
diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp
index a43d2d182c..61287f620c 100644
--- a/src/zig_llvm.cpp
+++ b/src/zig_llvm.cpp
@@ -30,6 +30,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -81,8 +82,11 @@ static const bool assertions_on = false;
 #endif
 
 bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMModuleRef module_ref,
-        const char *filename, ZigLLVM_EmitOutputType output_type, char **error_message, bool is_debug, bool is_small)
+        const char *filename, ZigLLVM_EmitOutputType output_type, char **error_message, bool is_debug,
+        bool is_small, bool time_report)
 {
+    TimePassesIsEnabled = time_report;
+
     std::error_code EC;
     raw_fd_ostream dest(filename, EC, sys::fs::F_None);
     if (EC) {
@@ -182,6 +186,9 @@ bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMM
         }
     }
 
+    if (time_report) {
+        TimerGroup::printAll(errs());
+    }
     return false;
 }
 
diff --git a/src/zig_llvm.h b/src/zig_llvm.h
index 63d69bd23e..5cdc6cc041 100644
--- a/src/zig_llvm.h
+++ b/src/zig_llvm.h
@@ -55,7 +55,8 @@ enum ZigLLVM_EmitOutputType {
 };
 
 ZIG_EXTERN_C bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMModuleRef module_ref,
-        const char *filename, enum ZigLLVM_EmitOutputType output_type, char **error_message, bool is_debug, bool is_small);
+        const char *filename, enum ZigLLVM_EmitOutputType output_type, char **error_message, bool is_debug,
+        bool is_small, bool time_report);
 
 ZIG_EXTERN_C LLVMTypeRef ZigLLVMTokenTypeInContext(LLVMContextRef context_ref);
 
diff --git a/std/build.zig b/std/build.zig
index 4e323eaf7b..5800e69c62 100644
--- a/std/build.zig
+++ b/std/build.zig
@@ -232,6 +232,8 @@ pub const Builder = struct {
     }
 
     pub fn make(self: *Builder, step_names: []const []const u8) !void {
+        try self.makePath(self.cache_root);
+
         var wanted_steps = ArrayList(*Step).init(self.allocator);
         defer wanted_steps.deinit();
 
@@ -1641,6 +1643,7 @@ pub const TestStep = struct {
     lib_paths: ArrayList([]const u8),
     object_files: ArrayList([]const u8),
     no_rosegment: bool,
+    output_path: ?[]const u8,
 
     pub fn init(builder: *Builder, root_src: []const u8) TestStep {
         const step_name = builder.fmt("test {}", root_src);
@@ -1659,6 +1662,7 @@ pub const TestStep = struct {
             .lib_paths = ArrayList([]const u8).init(builder.allocator),
             .object_files = ArrayList([]const u8).init(builder.allocator),
             .no_rosegment = false,
+            .output_path = null,
         };
     }
 
@@ -1682,6 +1686,24 @@ pub const TestStep = struct {
         self.build_mode = mode;
     }
 
+    pub fn setOutputPath(self: *TestStep, file_path: []const u8) void {
+        self.output_path = file_path;
+
+        // catch a common mistake
+        if (mem.eql(u8, self.builder.pathFromRoot(file_path), self.builder.pathFromRoot("."))) {
+            debug.panic("setOutputPath wants a file path, not a directory\n");
+        }
+    }
+
+    pub fn getOutputPath(self: *TestStep) []const u8 {
+        if (self.output_path) |output_path| {
+            return output_path;
+        } else {
+            const basename = self.builder.fmt("test{}", self.target.exeFileExt());
+            return os.path.join(self.builder.allocator, self.builder.cache_root, basename) catch unreachable;
+        }
+    }
+
     pub fn linkSystemLibrary(self: *TestStep, name: []const u8) void {
         self.link_libs.put(name) catch unreachable;
     }
@@ -1746,6 +1768,10 @@ pub const TestStep = struct {
             builtin.Mode.ReleaseSmall => try zig_args.append("--release-small"),
         }
 
+        const output_path = builder.pathFromRoot(self.getOutputPath());
+        try zig_args.append("--output");
+        try zig_args.append(output_path);
+
         switch (self.target) {
             Target.Native => {},
             Target.Cross => |cross_target| {