Commit a32b5929cc

Andrew Kelley <superjoe30@gmail.com>
2017-03-27 03:07:07
add stack protector safety when linking libc
* introduce zigrt file. it contains only weak symbols so that multiple instances can be merged. it contains __zig_panic so that multiple .o files can call the same panic function. * remove `@setFnVisible` builtin and add @setGlobalLinkage builtin which is more powerful * add `@panic` builtin function. * fix collision of symbols with extern prototypes and internal function names * add stack protector safety when linking against libc. To add the safety mechanism without libc requires implementing Thread Local Storage. See #276
1 parent 8aeea72
doc/emacs/zig-mode.el
@@ -1,5 +1,5 @@
 (setq zig
-      '(("\\b\\(@sizeOf\\|@alignOf\\|@maxValue\\|@minValue\\|@memberCount\\|@typeOf\\|@addWithOverflow\\|@subWithOverflow\\|@mulWithOverflow\\|@shlWithOverflow\\|@cInclude\\|@cDefine\\|@cUndef\\|@compileVar\\|@generatedCode\\|@ctz\\|@clz\\|@import\\|@cImport\\|@errorName\\|@typeName\\|@isInteger\\|@isFloat\\|@canImplicitCast\\|@embedFile\\|@cmpxchg\\|@fence\\|@divExact\\|@truncate\\|@compileError\\|@compileLog\\|@intType\\|@unreachable\\|@setFnTest\\|@setFnVisible\\|@setDebugSafety\\|@alloca\\|@setGlobalAlign\\|@setGlobalSection\\)" . font-lock-builtin-face)
+      '(("\\b\\(@sizeOf\\|@alignOf\\|@maxValue\\|@minValue\\|@memberCount\\|@typeOf\\|@addWithOverflow\\|@subWithOverflow\\|@mulWithOverflow\\|@shlWithOverflow\\|@cInclude\\|@cDefine\\|@cUndef\\|@compileVar\\|@generatedCode\\|@ctz\\|@clz\\|@import\\|@cImport\\|@errorName\\|@typeName\\|@isInteger\\|@isFloat\\|@canImplicitCast\\|@embedFile\\|@cmpxchg\\|@fence\\|@divExact\\|@truncate\\|@compileError\\|@compileLog\\|@intType\\|@unreachable\\|@setDebugSafety\\|@alloca\\|@setGlobalAlign\\|@setGlobalLinkage\\|@setGlobalSection\\)" . font-lock-builtin-face)
 
 ("\\b\\(fn\\|use\\|while\\|for\\|break\\|continue\\|goto\\|if\\|else\\|switch\\|try\\|return\\|defer\\|asm\\|unreachable\\|const\\|var\\|extern\\|packed\\|export\\|pub\\|noalias\\|inline\\|comptime\\|nakedcc\\|coldcc\\|volatile\\|struct\\|enum\\|union\\)\\b" . font-lock-keyword-face)
 
doc/langref.md
@@ -626,3 +626,10 @@ Sets the alignment property of a global variable.
 ### @setGlobalSection(global_variable_name, section_name: []u8) -> bool
 
 Puts the global variable in the specified section.
+
+### @panic(message: []const u8) -> noreturn
+
+Invokes the panic handler function. By default the panic handler function
+calls the public `panic` function exposed in the root source file, or
+if there is not one specified, invokes the one provided in
+`std/special/panic.zig`.
src/all_types.hpp
@@ -237,6 +237,13 @@ enum VisibMod {
     VisibModExport,
 };
 
+enum GlobalLinkageId {
+    GlobalLinkageIdInternal,
+    GlobalLinkageIdStrong,
+    GlobalLinkageIdWeak,
+    GlobalLinkageIdLinkOnce,
+};
+
 enum TldId {
     TldIdVar,
     TldIdFn,
@@ -273,6 +280,8 @@ struct TldVar {
     uint64_t alignment;
     AstNode *set_global_section_node;
     Buf *section_name;
+    AstNode *set_global_linkage_node;
+    GlobalLinkageId linkage;
 };
 
 struct TldFn {
@@ -1116,8 +1125,6 @@ struct FnTableEntry {
     Buf symbol_name;
     TypeTableEntry *type_entry; // function type
     TypeTableEntry *implicit_return_type;
-    bool internal_linkage;
-    bool disable_export;
     bool is_test;
     FnInline fn_inline;
     FnAnalState anal_state;
@@ -1128,7 +1135,6 @@ struct FnTableEntry {
     Buf **param_names;
 
     AstNode *fn_no_inline_set_node;
-    AstNode *fn_export_set_node;
     AstNode *fn_static_eval_set_node;
 
     ZigList<IrInstruction *> alloca_list;
@@ -1138,6 +1144,8 @@ struct FnTableEntry {
     uint64_t alignment;
     AstNode *set_global_section_node;
     Buf *section_name;
+    AstNode *set_global_linkage_node;
+    GlobalLinkageId linkage;
 };
 
 uint32_t fn_table_entry_hash(FnTableEntry*);
@@ -1178,7 +1186,6 @@ enum BuiltinFnId {
     BuiltinFnIdDivExact,
     BuiltinFnIdTruncate,
     BuiltinFnIdIntType,
-    BuiltinFnIdSetFnVisible,
     BuiltinFnIdSetDebugSafety,
     BuiltinFnIdAlloca,
     BuiltinFnIdTypeName,
@@ -1187,6 +1194,8 @@ enum BuiltinFnId {
     BuiltinFnIdCanImplicitCast,
     BuiltinFnIdSetGlobalAlign,
     BuiltinFnIdSetGlobalSection,
+    BuiltinFnIdSetGlobalLinkage,
+    BuiltinFnIdPanic,
 };
 
 struct BuiltinFnEntry {
@@ -1300,7 +1309,8 @@ struct CodeGen {
     HashMap<Scope *, IrInstruction *, fn_eval_hash, fn_eval_eql> memoized_fn_eval_table;
     HashMap<ZigLLVMFnKey, LLVMValueRef, zig_llvm_fn_key_hash, zig_llvm_fn_key_eql> llvm_fn_table;
     HashMap<Buf *, ConstExprValue *, buf_hash, buf_eql_buf> compile_vars;
-    HashMap<Buf *, Tld *, buf_hash, buf_eql_buf> external_symbol_names;
+    HashMap<Buf *, Tld *, buf_hash, buf_eql_buf> exported_symbol_names;
+    HashMap<Buf *, Tld *, buf_hash, buf_eql_buf> external_prototypes;
 
     ZigList<ImportTableEntry *> import_queue;
     size_t import_queue_index;
@@ -1346,6 +1356,7 @@ struct CodeGen {
         TypeTableEntry *entry_environ_enum;
         TypeTableEntry *entry_oformat_enum;
         TypeTableEntry *entry_atomic_order_enum;
+        TypeTableEntry *entry_global_linkage_enum;
         TypeTableEntry *entry_arg_tuple;
     } builtin_types;
 
@@ -1378,7 +1389,7 @@ struct CodeGen {
     bool is_native_target;
     PackageTableEntry *root_package;
     PackageTableEntry *std_package;
-    PackageTableEntry *panic_package;
+    PackageTableEntry *zigrt_package;
     Buf *root_out_name;
     bool windows_subsystem_windows;
     bool windows_subsystem_console;
@@ -1388,6 +1399,7 @@ struct CodeGen {
     Buf *mios_version_min;
     bool linker_rdynamic;
     const char *linker_script;
+    bool omit_zigrt;
 
     // The function definitions this module includes. There must be a corresponding
     // fn_protos entry.
@@ -1401,7 +1413,8 @@ struct CodeGen {
     OutType out_type;
     FnTableEntry *cur_fn;
     FnTableEntry *main_fn;
-    FnTableEntry *panic_fn;
+    FnTableEntry *user_panic_fn;
+    FnTableEntry *extern_panic_fn;
     LLVMValueRef cur_ret_ptr;
     LLVMValueRef cur_fn_val;
     ZigList<LLVMBasicBlockRef> break_block_stack;
@@ -1656,7 +1669,6 @@ enum IrInstructionId {
     IrInstructionIdTypeOf,
     IrInstructionIdToPtrType,
     IrInstructionIdPtrTypeChild,
-    IrInstructionIdSetFnVisible,
     IrInstructionIdSetDebugSafety,
     IrInstructionIdArrayType,
     IrInstructionIdSliceType,
@@ -1718,7 +1730,9 @@ enum IrInstructionId {
     IrInstructionIdCanImplicitCast,
     IrInstructionIdSetGlobalAlign,
     IrInstructionIdSetGlobalSection,
+    IrInstructionIdSetGlobalLinkage,
     IrInstructionIdDeclRef,
+    IrInstructionIdPanic,
 };
 
 struct IrInstruction {
@@ -1999,13 +2013,6 @@ struct IrInstructionPtrTypeChild {
     IrInstruction *value;
 };
 
-struct IrInstructionSetFnVisible {
-    IrInstruction base;
-
-    IrInstruction *fn_value;
-    IrInstruction *is_visible;
-};
-
 struct IrInstructionSetDebugSafety {
     IrInstruction base;
 
@@ -2439,6 +2446,13 @@ struct IrInstructionSetGlobalSection {
     IrInstruction *value;
 };
 
+struct IrInstructionSetGlobalLinkage {
+    IrInstruction base;
+
+    Tld *tld;
+    IrInstruction *value;
+};
+
 struct IrInstructionDeclRef {
     IrInstruction base;
 
@@ -2446,6 +2460,12 @@ struct IrInstructionDeclRef {
     LVal lval;
 };
 
+struct IrInstructionPanic {
+    IrInstruction base;
+
+    IrInstruction *msg;
+};
+
 static const size_t slice_ptr_index = 0;
 static const size_t slice_len_index = 1;
 
src/analyze.cpp
@@ -1762,7 +1762,7 @@ static void get_fully_qualified_decl_name(Buf *buf, Tld *tld, uint8_t sep) {
     buf_append_buf(buf, tld->name);
 }
 
-FnTableEntry *create_fn_raw(FnInline inline_value, bool internal_linkage) {
+FnTableEntry *create_fn_raw(FnInline inline_value, GlobalLinkageId linkage) {
     FnTableEntry *fn_entry = allocate<FnTableEntry>(1);
 
     fn_entry->analyzed_executable.backward_branch_count = &fn_entry->prealloc_bbc;
@@ -1770,7 +1770,7 @@ FnTableEntry *create_fn_raw(FnInline inline_value, bool internal_linkage) {
     fn_entry->analyzed_executable.fn_entry = fn_entry;
     fn_entry->ir_executable.fn_entry = fn_entry;
     fn_entry->fn_inline = inline_value;
-    fn_entry->internal_linkage = internal_linkage;
+    fn_entry->linkage = linkage;
 
     return fn_entry;
 }
@@ -1780,8 +1780,9 @@ FnTableEntry *create_fn(AstNode *proto_node) {
     AstNodeFnProto *fn_proto = &proto_node->data.fn_proto;
 
     FnInline inline_value = fn_proto->is_inline ? FnInlineAlways : FnInlineAuto;
-    bool internal_linkage = (fn_proto->visib_mod != VisibModExport && !proto_node->data.fn_proto.is_extern);
-    FnTableEntry *fn_entry = create_fn_raw(inline_value, internal_linkage);
+    GlobalLinkageId linkage = (fn_proto->visib_mod == VisibModExport || proto_node->data.fn_proto.is_extern) ?
+        GlobalLinkageIdStrong : GlobalLinkageIdInternal;
+    FnTableEntry *fn_entry = create_fn_raw(inline_value, linkage);
 
     fn_entry->proto_node = proto_node;
     fn_entry->body_node = (proto_node->data.fn_proto.fn_def_node == nullptr) ? nullptr :
@@ -1807,12 +1808,10 @@ static void wrong_panic_prototype(CodeGen *g, AstNode *proto_node, TypeTableEntr
                 buf_ptr(&fn_type->name)));
 }
 
-static void typecheck_panic_fn(CodeGen *g) {
-    assert(g->panic_fn);
-
-    AstNode *proto_node = g->panic_fn->proto_node;
+static void typecheck_panic_fn(CodeGen *g, FnTableEntry *panic_fn) {
+    AstNode *proto_node = panic_fn->proto_node;
     assert(proto_node->type == NodeTypeFnProto);
-    TypeTableEntry *fn_type = g->panic_fn->type_entry;
+    TypeTableEntry *fn_type = panic_fn->type_entry;
     FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id;
     if (fn_type_id->param_count != 1) {
         return wrong_panic_prototype(g, proto_node, fn_type);
@@ -1862,6 +1861,8 @@ static void resolve_decl_fn(CodeGen *g, TldFn *tld_fn) {
                     add_node_error(g, param_node, buf_sprintf("missing parameter name"));
                 }
             }
+        } else if (fn_table_entry->linkage != GlobalLinkageIdInternal) {
+            g->external_prototypes.put_unique(tld_fn->base.name, &tld_fn->base);
         }
 
         Scope *child_scope = fn_table_entry->fndef_scope ? &fn_table_entry->fndef_scope->base : tld_fn->base.parent_scope;
@@ -1892,18 +1893,16 @@ static void resolve_decl_fn(CodeGen *g, TldFn *tld_fn) {
                         }
                     }
                 } else if (buf_eql_str(&fn_table_entry->symbol_name, "panic")) {
-                    g->panic_fn = fn_table_entry;
-                    typecheck_panic_fn(g);
+                    typecheck_panic_fn(g, fn_table_entry);
                 }
-            } else if (import->package == g->panic_package && scope_is_root_decls(tld_fn->base.parent_scope)) {
-                if (buf_eql_str(&fn_table_entry->symbol_name, "panic")) {
-                    g->panic_fn = fn_table_entry;
-                    typecheck_panic_fn(g);
+            } else if (import->package == g->zigrt_package && scope_is_root_decls(tld_fn->base.parent_scope)) {
+                if (buf_eql_str(&fn_table_entry->symbol_name, "__zig_panic")) {
+                    g->extern_panic_fn = fn_table_entry;
                 }
             }
         }
     } else if (source_node->type == NodeTypeTestDecl) {
-        FnTableEntry *fn_table_entry = create_fn_raw(FnInlineAuto, false);
+        FnTableEntry *fn_table_entry = create_fn_raw(FnInlineAuto, GlobalLinkageIdStrong);
 
         get_fully_qualified_decl_name(&fn_table_entry->symbol_name, &tld_fn->base, '_');
 
@@ -1931,16 +1930,12 @@ static void resolve_decl_comptime(CodeGen *g, TldCompTime *tld_comptime) {
 }
 
 static void add_top_level_decl(CodeGen *g, ScopeDecls *decls_scope, Tld *tld) {
-    if (tld->visib_mod == VisibModExport ||
-        (buf_eql_str(tld->name, "panic") &&
-            (decls_scope->import->package == g->panic_package || decls_scope->import == g->root_import)) ||
-        (tld->id == TldIdVar && g->is_test_build))
-    {
+    if (tld->visib_mod == VisibModExport || (tld->id == TldIdVar && g->is_test_build)) {
         g->resolve_queue.append(tld);
     }
 
     if (tld->visib_mod == VisibModExport) {
-        auto entry = g->external_symbol_names.put_unique(tld->name, tld);
+        auto entry = g->exported_symbol_names.put_unique(tld->name, tld);
         if (entry) {
             Tld *other_tld = entry->value;
             ErrorMsg *msg = add_node_error(g, tld->source_node,
@@ -2060,6 +2055,15 @@ void scan_decls(CodeGen *g, ScopeDecls *decls_scope, AstNode *node) {
                 TldFn *tld_fn = allocate<TldFn>(1);
                 init_tld(&tld_fn->base, TldIdFn, fn_name, visib_mod, node, &decls_scope->base);
                 add_top_level_decl(g, decls_scope, &tld_fn->base);
+
+                ImportTableEntry *import = get_scope_import(&decls_scope->base);
+                if (import == g->root_import && scope_is_root_decls(&decls_scope->base) &&
+                    buf_eql_str(fn_name, "panic"))
+                {
+                    g->compile_vars.put(buf_create_from_str("panic_implementation_provided"),
+                            create_const_bool(g, true));
+                }
+
                 break;
             }
         case NodeTypeUse:
@@ -4206,3 +4210,36 @@ ConstParent *get_const_val_parent(ConstExprValue *value) {
     }
     return nullptr;
 }
+
+FnTableEntry *get_extern_panic_fn(CodeGen *g) {
+    if (g->extern_panic_fn)
+        return g->extern_panic_fn;
+
+    FnTypeId fn_type_id = {0};
+    fn_type_id.is_extern = true;
+    fn_type_id.is_cold = true;
+    fn_type_id.param_count = 2;
+    fn_type_id.param_info = allocate<FnTypeParamInfo>(2);
+    fn_type_id.next_param_index = 0;
+    fn_type_id.param_info[0].type = get_pointer_to_type(g, g->builtin_types.entry_u8, true);
+    fn_type_id.param_info[1].type = g->builtin_types.entry_usize;
+    fn_type_id.return_type = g->builtin_types.entry_unreachable;
+
+    TypeTableEntry *fn_type = get_fn_type(g, &fn_type_id);
+    assert(!type_is_invalid(fn_type));
+
+    FnTableEntry *fn_entry = create_fn_raw(FnInlineAuto, GlobalLinkageIdStrong);
+    buf_init_from_str(&fn_entry->symbol_name, "__zig_panic");
+
+    TldFn *tld_fn = allocate<TldFn>(1);
+    init_tld(&tld_fn->base, TldIdFn, &fn_entry->symbol_name, VisibModPrivate, nullptr, nullptr);
+    tld_fn->fn_entry = fn_entry;
+
+    g->external_prototypes.put_unique(tld_fn->base.name, &tld_fn->base);
+
+    fn_entry->type_entry = fn_type;
+
+    g->extern_panic_fn = fn_entry;
+    return g->extern_panic_fn;
+}
+
src/analyze.hpp
@@ -72,7 +72,7 @@ VariableTableEntry *add_variable(CodeGen *g, AstNode *source_node, Scope *parent
     bool is_const, ConstExprValue *init_value, Tld *src_tld);
 TypeTableEntry *analyze_type_expr(CodeGen *g, Scope *scope, AstNode *node);
 FnTableEntry *create_fn(AstNode *proto_node);
-FnTableEntry *create_fn_raw(FnInline inline_value, bool internal_linkage);
+FnTableEntry *create_fn_raw(FnInline inline_value, GlobalLinkageId linkage);
 void init_fn_type_id(FnTypeId *fn_type_id, AstNode *proto_node, size_t param_count_alloc);
 AstNode *get_param_decl_node(FnTableEntry *fn_entry, size_t index);
 FnTableEntry *scope_get_fn_if_root(Scope *scope);
@@ -148,5 +148,6 @@ void init_const_undefined(CodeGen *g, ConstExprValue *const_val);
 
 TypeTableEntry *make_int_type(CodeGen *g, bool is_signed, size_t size_in_bits);
 ConstParent *get_const_val_parent(ConstExprValue *value);
+FnTableEntry *get_extern_panic_fn(CodeGen *g);
 
 #endif
src/codegen.cpp
@@ -67,7 +67,8 @@ CodeGen *codegen_create(Buf *root_source_dir, const ZigTarget *target) {
     g->llvm_fn_table.init(16);
     g->memoized_fn_eval_table.init(16);
     g->compile_vars.init(16);
-    g->external_symbol_names.init(8);
+    g->exported_symbol_names.init(8);
+    g->external_prototypes.init(8);
     g->is_release_build = false;
     g->is_test_build = false;
     g->want_h_file = true;
@@ -140,6 +141,10 @@ void codegen_set_is_release(CodeGen *g, bool is_release_build) {
     g->is_release_build = is_release_build;
 }
 
+void codegen_set_omit_zigrt(CodeGen *g, bool omit_zigrt) {
+    g->omit_zigrt = omit_zigrt;
+}
+
 void codegen_set_is_test(CodeGen *g, bool is_test_build) {
     g->is_test_build = is_test_build;
 }
@@ -256,10 +261,22 @@ static void addLLVMAttr(LLVMValueRef val, LLVMAttributeIndex attr_index, const c
     LLVMAddAttributeAtIndex(val, attr_index, llvm_attr);
 }
 
+static void addLLVMAttrStr(LLVMValueRef val, LLVMAttributeIndex attr_index,
+        const char *attr_name, const char *attr_val)
+{
+    LLVMAttributeRef llvm_attr = LLVMCreateStringAttribute(LLVMGetGlobalContext(),
+            attr_name, strlen(attr_name), attr_val, strlen(attr_val));
+    LLVMAddAttributeAtIndex(val, attr_index, llvm_attr);
+}
+
 static void addLLVMFnAttr(LLVMValueRef fn_val, const char *attr_name) {
     return addLLVMAttr(fn_val, -1, attr_name);
 }
 
+static void addLLVMFnAttrStr(LLVMValueRef fn_val, const char *attr_name, const char *attr_val) {
+    return addLLVMAttrStr(fn_val, -1, attr_name, attr_val);
+}
+
 static void addLLVMArgAttr(LLVMValueRef arg_val, unsigned param_index, const char *attr_name) {
     return addLLVMAttr(arg_val, param_index + 1, attr_name);
 }
@@ -271,15 +288,19 @@ static void addLLVMCallsiteAttr(LLVMValueRef call_instr, unsigned param_index, c
     LLVMAddCallSiteAttribute(call_instr, param_index + 1, llvm_attr);
 }
 
+static bool is_symbol_available(CodeGen *g, Buf *name) {
+    return g->exported_symbol_names.maybe_get(name) == nullptr && g->external_prototypes.maybe_get(name) == nullptr;
+}
+
 static Buf *get_mangled_name(CodeGen *g, Buf *original_name, bool external_linkage) {
-    if (external_linkage || g->external_symbol_names.maybe_get(original_name) == nullptr) {
+    if (external_linkage || is_symbol_available(g, original_name)) {
         return original_name;
     }
 
     int n = 0;
     for (;; n += 1) {
         Buf *new_name = buf_sprintf("%s.%d", buf_ptr(original_name), n);
-        if (g->external_symbol_names.maybe_get(new_name) == nullptr) {
+        if (is_symbol_available(g, new_name)) {
             return new_name;
         }
     }
@@ -289,11 +310,12 @@ static LLVMValueRef fn_llvm_value(CodeGen *g, FnTableEntry *fn_table_entry) {
     if (fn_table_entry->llvm_value)
         return fn_table_entry->llvm_value;
 
-    Buf *symbol_name = get_mangled_name(g, &fn_table_entry->symbol_name, !fn_table_entry->internal_linkage);
+    bool external_linkage = (fn_table_entry->linkage != GlobalLinkageIdInternal);
+    Buf *symbol_name = get_mangled_name(g, &fn_table_entry->symbol_name, external_linkage);
 
     TypeTableEntry *fn_type = fn_table_entry->type_entry;
     LLVMTypeRef fn_llvm_type = fn_type->data.fn.raw_type_ref;
-    if (!fn_table_entry->internal_linkage && fn_table_entry->body_node == nullptr) {
+    if (external_linkage && fn_table_entry->body_node == nullptr) {
         LLVMValueRef existing_llvm_fn = LLVMGetNamedFunction(g->module, buf_ptr(symbol_name));
         if (existing_llvm_fn) {
             fn_table_entry->llvm_value = LLVMConstBitCast(existing_llvm_fn, LLVMPointerType(fn_llvm_type, 0));
@@ -318,12 +340,35 @@ static LLVMValueRef fn_llvm_value(CodeGen *g, FnTableEntry *fn_table_entry) {
         addLLVMFnAttr(fn_table_entry->llvm_value, "naked");
     }
 
-    LLVMSetLinkage(fn_table_entry->llvm_value, fn_table_entry->internal_linkage ?
-        LLVMInternalLinkage : LLVMExternalLinkage);
+    switch (fn_table_entry->linkage) {
+        case GlobalLinkageIdInternal:
+            LLVMSetLinkage(fn_table_entry->llvm_value, LLVMInternalLinkage);
+            break;
+        case GlobalLinkageIdStrong:
+            LLVMSetLinkage(fn_table_entry->llvm_value, LLVMExternalLinkage);
+            break;
+        case GlobalLinkageIdWeak:
+            LLVMSetLinkage(fn_table_entry->llvm_value, LLVMWeakODRLinkage);
+            break;
+        case GlobalLinkageIdLinkOnce:
+            LLVMSetLinkage(fn_table_entry->llvm_value, LLVMLinkOnceODRLinkage);
+            break;
+    }
 
     if (fn_type->data.fn.fn_type_id.return_type->id == TypeTableEntryIdUnreachable) {
         addLLVMFnAttr(fn_table_entry->llvm_value, "noreturn");
     }
+
+    if (fn_table_entry->body_node != nullptr) {
+        bool want_fn_safety = !g->is_release_build && !fn_table_entry->def_scope->safety_off;
+        if (want_fn_safety) {
+            if (g->link_libc) {
+                addLLVMFnAttr(fn_table_entry->llvm_value, "sspstrong");
+                addLLVMFnAttrStr(fn_table_entry->llvm_value, "stack-protector-buffer-size", "4");
+            }
+        }
+    }
+
     LLVMSetFunctionCallConv(fn_table_entry->llvm_value, fn_type->data.fn.calling_convention);
     if (fn_type->data.fn.fn_type_id.is_cold) {
         ZigLLVMAddFunctionAttrCold(fn_table_entry->llvm_value);
@@ -363,10 +408,11 @@ static ZigLLVMDIScope *get_di_scope(CodeGen *g, Scope *scope) {
             bool is_definition = fn_table_entry->body_node != nullptr;
             unsigned flags = 0;
             bool is_optimized = g->is_release_build;
+            bool is_internal_linkage = (fn_table_entry->linkage == GlobalLinkageIdInternal);
             ZigLLVMDISubprogram *subprogram = ZigLLVMCreateFunction(g->dbuilder,
                 get_di_scope(g, scope->parent), buf_ptr(&fn_table_entry->symbol_name), "",
                 import->di_file, line_number,
-                fn_table_entry->type_entry->di_type, fn_table_entry->internal_linkage,
+                fn_table_entry->type_entry->di_type, is_internal_linkage,
                 is_definition, scope_line, flags, is_optimized, nullptr);
 
             scope->di_scope = ZigLLVMSubprogramToScope(subprogram);
@@ -544,13 +590,28 @@ static LLVMValueRef get_panic_msg_ptr_val(CodeGen *g, PanicMsgId msg_id) {
     return val->llvm_global;
 }
 
-static void gen_debug_safety_crash(CodeGen *g, PanicMsgId msg_id) {
-    LLVMValueRef fn_val = fn_llvm_value(g, g->panic_fn);
-    LLVMValueRef msg_arg = get_panic_msg_ptr_val(g, msg_id);
-    ZigLLVMBuildCall(g->builder, fn_val, &msg_arg, 1, g->panic_fn->type_entry->data.fn.calling_convention, "");
+static void gen_panic(CodeGen *g, LLVMValueRef msg_arg) {
+    FnTableEntry *panic_fn = get_extern_panic_fn(g);
+    LLVMValueRef fn_val = fn_llvm_value(g, panic_fn);
+
+    TypeTableEntry *str_type = get_slice_type(g, g->builtin_types.entry_u8, true);
+    size_t ptr_index = str_type->data.structure.fields[slice_ptr_index].gen_index;
+    size_t len_index = str_type->data.structure.fields[slice_len_index].gen_index;
+    LLVMValueRef ptr_ptr = LLVMBuildStructGEP(g->builder, msg_arg, ptr_index, "");
+    LLVMValueRef len_ptr = LLVMBuildStructGEP(g->builder, msg_arg, len_index, "");
+
+    LLVMValueRef args[] = {
+        LLVMBuildLoad(g->builder, ptr_ptr, ""),
+        LLVMBuildLoad(g->builder, len_ptr, ""),
+    };
+    ZigLLVMBuildCall(g->builder, fn_val, args, 2, panic_fn->type_entry->data.fn.calling_convention, "");
     LLVMBuildUnreachable(g->builder);
 }
 
+static void gen_debug_safety_crash(CodeGen *g, PanicMsgId msg_id) {
+    gen_panic(g, get_panic_msg_ptr_val(g, msg_id));
+}
+
 static void add_bounds_check(CodeGen *g, LLVMValueRef target_val,
         LLVMIntPredicate lower_pred, LLVMValueRef lower_value,
         LLVMIntPredicate upper_pred, LLVMValueRef upper_value)
@@ -2564,6 +2625,11 @@ static LLVMValueRef ir_render_container_init_list(CodeGen *g, IrExecutable *exec
     return tmp_array_ptr;
 }
 
+static LLVMValueRef ir_render_panic(CodeGen *g, IrExecutable *executable, IrInstructionPanic *instruction) {
+    gen_panic(g, ir_llvm_value(g, instruction->msg));
+    return nullptr;
+}
+
 static void set_debug_location(CodeGen *g, IrInstruction *instruction) {
     AstNode *source_node = instruction->source_node;
     Scope *scope = instruction->scope;
@@ -2584,7 +2650,6 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
         case IrInstructionIdToPtrType:
         case IrInstructionIdPtrTypeChild:
         case IrInstructionIdFieldPtr:
-        case IrInstructionIdSetFnVisible:
         case IrInstructionIdSetDebugSafety:
         case IrInstructionIdArrayType:
         case IrInstructionIdSliceType:
@@ -2615,7 +2680,9 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
         case IrInstructionIdCanImplicitCast:
         case IrInstructionIdSetGlobalAlign:
         case IrInstructionIdSetGlobalSection:
+        case IrInstructionIdSetGlobalLinkage:
         case IrInstructionIdDeclRef:
+        case IrInstructionIdSwitchVar:
             zig_unreachable();
         case IrInstructionIdReturn:
             return ir_render_return(g, executable, (IrInstructionReturn *)instruction);
@@ -2721,8 +2788,8 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
             return ir_render_int_to_enum(g, executable, (IrInstructionIntToEnum *)instruction);
         case IrInstructionIdContainerInitList:
             return ir_render_container_init_list(g, executable, (IrInstructionContainerInitList *)instruction);
-        case IrInstructionIdSwitchVar:
-            zig_panic("TODO render switch var instruction to LLVM");
+        case IrInstructionIdPanic:
+            return ir_render_panic(g, executable, (IrInstructionPanic *)instruction);
     }
     zig_unreachable();
 }
@@ -3659,6 +3726,18 @@ static const CIntTypeInfo c_int_type_infos[] = {
 
 static const bool is_signed_list[] = { false, true, };
 
+struct GlobalLinkageValue {
+    GlobalLinkageId id;
+    const char *name;
+};
+
+static const GlobalLinkageValue global_linkage_values[] = {
+    {GlobalLinkageIdInternal, "Internal"},
+    {GlobalLinkageIdStrong, "Strong"},
+    {GlobalLinkageIdWeak, "Weak"},
+    {GlobalLinkageIdLinkOnce, "LinkOnce"},
+};
+
 static void define_builtin_types(CodeGen *g) {
     {
         // if this type is anywhere in the AST, we should never hit codegen.
@@ -3995,6 +4074,30 @@ static void define_builtin_types(CodeGen *g) {
         g->primitive_type_table.put(&entry->name, entry);
     }
 
+    {
+        TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdEnum);
+        entry->zero_bits = true; // only allowed at compile time
+        buf_init_from_str(&entry->name, "GlobalLinkage");
+        uint32_t field_count = array_length(global_linkage_values);
+        entry->data.enumeration.src_field_count = field_count;
+        entry->data.enumeration.fields = allocate<TypeEnumField>(field_count);
+        for (uint32_t i = 0; i < field_count; i += 1) {
+            TypeEnumField *type_enum_field = &entry->data.enumeration.fields[i];
+            const GlobalLinkageValue *value = &global_linkage_values[i];
+            type_enum_field->name = buf_create_from_str(value->name);
+            type_enum_field->value = i;
+            type_enum_field->type_entry = g->builtin_types.entry_void;
+        }
+        entry->data.enumeration.complete = true;
+        entry->data.enumeration.zero_bits_known = true;
+
+        TypeTableEntry *tag_type_entry = get_smallest_unsigned_int_type(g, field_count);
+        entry->data.enumeration.tag_type = tag_type_entry;
+
+        g->builtin_types.entry_global_linkage_enum = entry;
+        g->primitive_type_table.put(&entry->name, entry);
+    }
+
     {
         TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdEnum);
         entry->zero_bits = true; // only allowed at compile time
@@ -4145,11 +4248,12 @@ static void define_builtin_fns(CodeGen *g) {
     create_builtin_fn(g, BuiltinFnIdCompileErr, "compileError", 1);
     create_builtin_fn(g, BuiltinFnIdCompileLog, "compileLog", SIZE_MAX);
     create_builtin_fn(g, BuiltinFnIdIntType, "intType", 2);
-    create_builtin_fn(g, BuiltinFnIdSetFnVisible, "setFnVisible", 2);
     create_builtin_fn(g, BuiltinFnIdSetDebugSafety, "setDebugSafety", 2);
     create_builtin_fn(g, BuiltinFnIdAlloca, "alloca", 2);
     create_builtin_fn(g, BuiltinFnIdSetGlobalAlign, "setGlobalAlign", 2);
     create_builtin_fn(g, BuiltinFnIdSetGlobalSection, "setGlobalSection", 2);
+    create_builtin_fn(g, BuiltinFnIdSetGlobalLinkage, "setGlobalLinkage", 2);
+    create_builtin_fn(g, BuiltinFnIdPanic, "panic", 1);
 }
 
 static void add_compile_var(CodeGen *g, const char *name, ConstExprValue *value) {
@@ -4180,6 +4284,7 @@ static void define_builtin_compile_vars(CodeGen *g) {
 
         add_compile_var(g, "link_libs", const_val);
     }
+    add_compile_var(g, "panic_implementation_provided", create_const_bool(g, false));
 }
 
 static void init(CodeGen *g, Buf *source_path) {
@@ -4309,9 +4414,10 @@ static PackageTableEntry *create_bootstrap_pkg(CodeGen *g) {
     return package;
 }
 
-static PackageTableEntry *create_panic_pkg(CodeGen *g) {
+static PackageTableEntry *create_zigrt_pkg(CodeGen *g) {
     PackageTableEntry *package = new_package(buf_ptr(g->zig_std_special_dir), "");
     package->package_table.put(buf_create_from_str("std"), g->std_package);
+    package->package_table.put(buf_create_from_str("@root"), g->root_package);
     return package;
 }
 
@@ -4337,9 +4443,9 @@ void codegen_add_root_code(CodeGen *g, Buf *src_dir, Buf *src_basename, Buf *sou
     if (!g->is_test_build && g->have_pub_main && (g->out_type == OutTypeObj || g->out_type == OutTypeExe)) {
         g->bootstrap_import = add_special_code(g, create_bootstrap_pkg(g), "bootstrap.zig");
     }
-    if (!g->have_pub_panic) {
-        g->panic_package = create_panic_pkg(g);
-        add_special_code(g, g->panic_package, "panic.zig");
+    if (!g->omit_zigrt) {
+        g->zigrt_package = create_zigrt_pkg(g);
+        add_special_code(g, g->zigrt_package, "zigrt.zig");
     }
 
     if (g->verbose) {
@@ -4509,7 +4615,7 @@ void codegen_generate_h_file(CodeGen *g) {
     for (size_t fn_def_i = 0; fn_def_i < g->fn_defs.length; fn_def_i += 1) {
         FnTableEntry *fn_table_entry = g->fn_defs.at(fn_def_i);
 
-        if (fn_table_entry->internal_linkage)
+        if (fn_table_entry->linkage == GlobalLinkageIdInternal)
             continue;
 
         FnTypeId *fn_type_id = &fn_table_entry->type_entry->data.fn.fn_type_id;
src/codegen.hpp
@@ -43,6 +43,7 @@ void codegen_set_rdynamic(CodeGen *g, bool rdynamic);
 void codegen_set_mmacosx_version_min(CodeGen *g, Buf *mmacosx_version_min);
 void codegen_set_mios_version_min(CodeGen *g, Buf *mios_version_min);
 void codegen_set_linker_script(CodeGen *g, const char *linker_script);
+void codegen_set_omit_zigrt(CodeGen *g, bool omit_zigrt);
 
 void codegen_add_root_code(CodeGen *g, Buf *source_dir, Buf *source_basename, Buf *source_code);
 
src/ir.cpp
@@ -276,10 +276,6 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionPtrTypeChild *)
     return IrInstructionIdPtrTypeChild;
 }
 
-static constexpr IrInstructionId ir_instruction_id(IrInstructionSetFnVisible *) {
-    return IrInstructionIdSetFnVisible;
-}
-
 static constexpr IrInstructionId ir_instruction_id(IrInstructionSetDebugSafety *) {
     return IrInstructionIdSetDebugSafety;
 }
@@ -528,10 +524,18 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionSetGlobalSection
     return IrInstructionIdSetGlobalSection;
 }
 
+static constexpr IrInstructionId ir_instruction_id(IrInstructionSetGlobalLinkage *) {
+    return IrInstructionIdSetGlobalLinkage;
+}
+
 static constexpr IrInstructionId ir_instruction_id(IrInstructionDeclRef *) {
     return IrInstructionIdDeclRef;
 }
 
+static constexpr IrInstructionId ir_instruction_id(IrInstructionPanic *) {
+    return IrInstructionIdPanic;
+}
+
 template<typename T>
 static T *ir_create_instruction(IrBuilder *irb, Scope *scope, AstNode *source_node) {
     T *special_instruction = allocate<T>(1);
@@ -1147,19 +1151,6 @@ static IrInstruction *ir_build_ptr_type_child(IrBuilder *irb, Scope *scope, AstN
     return &instruction->base;
 }
 
-static IrInstruction *ir_build_set_fn_visible(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *fn_value,
-        IrInstruction *is_visible)
-{
-    IrInstructionSetFnVisible *instruction = ir_build_instruction<IrInstructionSetFnVisible>(irb, scope, source_node);
-    instruction->fn_value = fn_value;
-    instruction->is_visible = is_visible;
-
-    ir_ref_instruction(fn_value, irb->current_basic_block);
-    ir_ref_instruction(is_visible, irb->current_basic_block);
-
-    return &instruction->base;
-}
-
 static IrInstruction *ir_build_set_debug_safety(IrBuilder *irb, Scope *scope, AstNode *source_node,
         IrInstruction *scope_value, IrInstruction *debug_safety_on)
 {
@@ -2092,6 +2083,19 @@ static IrInstruction *ir_build_set_global_section(IrBuilder *irb, Scope *scope,
     return &instruction->base;
 }
 
+static IrInstruction *ir_build_set_global_linkage(IrBuilder *irb, Scope *scope, AstNode *source_node,
+        Tld *tld, IrInstruction *value)
+{
+    IrInstructionSetGlobalLinkage *instruction = ir_build_instruction<IrInstructionSetGlobalLinkage>(
+            irb, scope, source_node);
+    instruction->tld = tld;
+    instruction->value = value;
+
+    ir_ref_instruction(value, irb->current_basic_block);
+
+    return &instruction->base;
+}
+
 static IrInstruction *ir_build_decl_ref(IrBuilder *irb, Scope *scope, AstNode *source_node,
         Tld *tld, LVal lval)
 {
@@ -2103,6 +2107,17 @@ static IrInstruction *ir_build_decl_ref(IrBuilder *irb, Scope *scope, AstNode *s
     return &instruction->base;
 }
 
+static IrInstruction *ir_build_panic(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *msg) {
+    IrInstructionPanic *instruction = ir_build_instruction<IrInstructionPanic>(irb, scope, source_node);
+    instruction->base.value.special = ConstValSpecialStatic;
+    instruction->base.value.type = irb->codegen->builtin_types.entry_unreachable;
+    instruction->msg = msg;
+
+    ir_ref_instruction(msg, irb->current_basic_block);
+
+    return &instruction->base;
+}
+
 static IrInstruction *ir_instruction_br_get_dep(IrInstructionBr *instruction, size_t index) {
     return nullptr;
 }
@@ -2287,14 +2302,6 @@ static IrInstruction *ir_instruction_ptrtypechild_get_dep(IrInstructionPtrTypeCh
     }
 }
 
-static IrInstruction *ir_instruction_setfnvisible_get_dep(IrInstructionSetFnVisible *instruction, size_t index) {
-    switch (index) {
-        case 0: return instruction->fn_value;
-        case 1: return instruction->is_visible;
-        default: return nullptr;
-    }
-}
-
 static IrInstruction *ir_instruction_setdebugsafety_get_dep(IrInstructionSetDebugSafety *instruction, size_t index) {
     switch (index) {
         case 0: return instruction->scope_value;
@@ -2742,10 +2749,24 @@ static IrInstruction *ir_instruction_setglobalsection_get_dep(IrInstructionSetGl
     }
 }
 
+static IrInstruction *ir_instruction_setgloballinkage_get_dep(IrInstructionSetGlobalLinkage *instruction, size_t index) {
+    switch (index) {
+        case 0: return instruction->value;
+        default: return nullptr;
+    }
+}
+
 static IrInstruction *ir_instruction_declref_get_dep(IrInstructionDeclRef *instruction, size_t index) {
     return nullptr;
 }
 
+static IrInstruction *ir_instruction_panic_get_dep(IrInstructionPanic *instruction, size_t index) {
+    switch (index) {
+        case 0: return instruction->msg;
+        default: return nullptr;
+    }
+}
+
 static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t index) {
     switch (instruction->id) {
         case IrInstructionIdInvalid:
@@ -2804,8 +2825,6 @@ static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t
             return ir_instruction_toptrtype_get_dep((IrInstructionToPtrType *) instruction, index);
         case IrInstructionIdPtrTypeChild:
             return ir_instruction_ptrtypechild_get_dep((IrInstructionPtrTypeChild *) instruction, index);
-        case IrInstructionIdSetFnVisible:
-            return ir_instruction_setfnvisible_get_dep((IrInstructionSetFnVisible *) instruction, index);
         case IrInstructionIdSetDebugSafety:
             return ir_instruction_setdebugsafety_get_dep((IrInstructionSetDebugSafety *) instruction, index);
         case IrInstructionIdArrayType:
@@ -2928,8 +2947,12 @@ static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t
             return ir_instruction_setglobalalign_get_dep((IrInstructionSetGlobalAlign *) instruction, index);
         case IrInstructionIdSetGlobalSection:
             return ir_instruction_setglobalsection_get_dep((IrInstructionSetGlobalSection *) instruction, index);
+        case IrInstructionIdSetGlobalLinkage:
+            return ir_instruction_setgloballinkage_get_dep((IrInstructionSetGlobalLinkage *) instruction, index);
         case IrInstructionIdDeclRef:
             return ir_instruction_declref_get_dep((IrInstructionDeclRef *) instruction, index);
+        case IrInstructionIdPanic:
+            return ir_instruction_panic_get_dep((IrInstructionPanic *) instruction, index);
     }
     zig_unreachable();
 }
@@ -3759,20 +3782,6 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
                     return arg;
                 return ir_build_typeof(irb, scope, node, arg);
             }
-        case BuiltinFnIdSetFnVisible:
-            {
-                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
-                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
-                if (arg0_value == irb->codegen->invalid_instruction)
-                    return arg0_value;
-
-                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
-                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
-                if (arg1_value == irb->codegen->invalid_instruction)
-                    return arg1_value;
-
-                return ir_build_set_fn_visible(irb, scope, node, arg0_value, arg1_value);
-            }
         case BuiltinFnIdSetDebugSafety:
             {
                 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
@@ -4145,6 +4154,7 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
             }
         case BuiltinFnIdSetGlobalAlign:
         case BuiltinFnIdSetGlobalSection:
+        case BuiltinFnIdSetGlobalLinkage:
             {
                 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                 if (arg0_node->type != NodeTypeSymbol) {
@@ -4170,10 +4180,23 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
 
                 if (builtin_fn->id == BuiltinFnIdSetGlobalAlign) {
                     return ir_build_set_global_align(irb, scope, node, tld, arg1_value);
-                } else {
+                } else if (builtin_fn->id == BuiltinFnIdSetGlobalSection) {
                     return ir_build_set_global_section(irb, scope, node, tld, arg1_value);
+                } else if (builtin_fn->id == BuiltinFnIdSetGlobalLinkage) {
+                    return ir_build_set_global_linkage(irb, scope, node, tld, arg1_value);
+                } else {
+                    zig_unreachable();
                 }
             }
+        case BuiltinFnIdPanic:
+            {
+                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
+                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
+                if (arg0_value == irb->codegen->invalid_instruction)
+                    return arg0_value;
+
+                return ir_build_panic(irb, scope, node, arg0_value);
+            }
     }
     zig_unreachable();
 }
@@ -4624,6 +4647,10 @@ static IrInstruction *ir_gen_this_literal(IrBuilder *irb, Scope *scope, AstNode
     if (fn_entry)
         return ir_build_const_fn(irb, scope, node, fn_entry);
 
+    while (scope->id != ScopeIdBlock && scope->id != ScopeIdDecls) {
+        scope = scope->parent;
+    }
+
     if (scope->id == ScopeIdDecls) {
         ScopeDecls *decls_scope = (ScopeDecls *)scope;
         TypeTableEntry *container_type = decls_scope->container_type;
@@ -7096,6 +7123,23 @@ static bool ir_resolve_atomic_order(IrAnalyze *ira, IrInstruction *value, Atomic
     return true;
 }
 
+static bool ir_resolve_global_linkage(IrAnalyze *ira, IrInstruction *value, GlobalLinkageId *out) {
+    if (type_is_invalid(value->value.type))
+        return false;
+
+    IrInstruction *casted_value = ir_implicit_cast(ira, value, ira->codegen->builtin_types.entry_global_linkage_enum);
+    if (type_is_invalid(casted_value->value.type))
+        return false;
+
+    ConstExprValue *const_val = ir_resolve_const(ira, casted_value, UndefBad);
+    if (!const_val)
+        return false;
+
+    *out = (GlobalLinkageId)const_val->data.x_enum.tag;
+    return true;
+}
+
+
 static Buf *ir_resolve_str(IrAnalyze *ira, IrInstruction *value) {
     if (type_is_invalid(value->value.type))
         return nullptr;
@@ -9557,42 +9601,6 @@ static TypeTableEntry *ir_analyze_instruction_ptr_type_child(IrAnalyze *ira,
     return ira->codegen->builtin_types.entry_type;
 }
 
-static TypeTableEntry *ir_analyze_instruction_set_fn_visible(IrAnalyze *ira,
-        IrInstructionSetFnVisible *set_fn_visible_instruction)
-{
-    IrInstruction *fn_value = set_fn_visible_instruction->fn_value->other;
-    IrInstruction *is_visible_value = set_fn_visible_instruction->is_visible->other;
-
-    FnTableEntry *fn_entry = ir_resolve_fn(ira, fn_value);
-    if (!fn_entry)
-        return ira->codegen->builtin_types.entry_invalid;
-
-    bool want_export;
-    if (!ir_resolve_bool(ira, is_visible_value, &want_export))
-        return ira->codegen->builtin_types.entry_invalid;
-
-    AstNode *source_node = set_fn_visible_instruction->base.source_node;
-    if (fn_entry->fn_export_set_node) {
-        ErrorMsg *msg = ir_add_error_node(ira, source_node,
-                buf_sprintf("function visibility set twice"));
-        add_error_note(ira->codegen, msg, fn_entry->fn_export_set_node, buf_sprintf("first set here"));
-        return ira->codegen->builtin_types.entry_invalid;
-    }
-    fn_entry->fn_export_set_node = source_node;
-
-    AstNodeFnProto *fn_proto = &fn_entry->proto_node->data.fn_proto;
-    if (fn_proto->visib_mod != VisibModExport) {
-        ErrorMsg *msg = ir_add_error_node(ira, source_node,
-            buf_sprintf("function must be marked export to set function visibility"));
-        add_error_note(ira->codegen, msg, fn_entry->proto_node, buf_sprintf("function declared here"));
-        return ira->codegen->builtin_types.entry_invalid;
-    }
-    fn_entry->internal_linkage = !want_export;
-
-    ir_build_const_from(ira, &set_fn_visible_instruction->base);
-    return ira->codegen->builtin_types.entry_void;
-}
-
 static TypeTableEntry *ir_analyze_instruction_set_global_align(IrAnalyze *ira,
         IrInstructionSetGlobalAlign *instruction)
 {
@@ -9677,6 +9685,45 @@ static TypeTableEntry *ir_analyze_instruction_set_global_section(IrAnalyze *ira,
     return ira->codegen->builtin_types.entry_void;
 }
 
+static TypeTableEntry *ir_analyze_instruction_set_global_linkage(IrAnalyze *ira,
+        IrInstructionSetGlobalLinkage *instruction)
+{
+    Tld *tld = instruction->tld;
+    IrInstruction *linkage_value = instruction->value->other;
+
+    GlobalLinkageId linkage_scalar;
+    if (!ir_resolve_global_linkage(ira, linkage_value, &linkage_scalar))
+        return ira->codegen->builtin_types.entry_invalid;
+
+    AstNode **set_global_linkage_node;
+    GlobalLinkageId *dest_linkage_ptr;
+    if (tld->id == TldIdVar) {
+        TldVar *tld_var = (TldVar *)tld;
+        set_global_linkage_node = &tld_var->set_global_linkage_node;
+        dest_linkage_ptr = &tld_var->linkage;
+    } else if (tld->id == TldIdFn) {
+        TldFn *tld_fn = (TldFn *)tld;
+        FnTableEntry *fn_entry = tld_fn->fn_entry;
+        set_global_linkage_node = &fn_entry->set_global_linkage_node;
+        dest_linkage_ptr = &fn_entry->linkage;
+    } else {
+        // error is caught in pass1 IR gen
+        zig_unreachable();
+    }
+
+    AstNode *source_node = instruction->base.source_node;
+    if (*set_global_linkage_node) {
+        ErrorMsg *msg = ir_add_error_node(ira, source_node, buf_sprintf("linkage set twice"));
+        add_error_note(ira->codegen, msg, *set_global_linkage_node, buf_sprintf("first set here"));
+        return ira->codegen->builtin_types.entry_invalid;
+    }
+    *set_global_linkage_node = source_node;
+    *dest_linkage_ptr = linkage_scalar;
+
+    ir_build_const_from(ira, &instruction->base);
+    return ira->codegen->builtin_types.entry_void;
+}
+
 static TypeTableEntry *ir_analyze_instruction_set_debug_safety(IrAnalyze *ira,
         IrInstructionSetDebugSafety *set_debug_safety_instruction)
 {
@@ -12147,6 +12194,22 @@ static TypeTableEntry *ir_analyze_instruction_can_implicit_cast(IrAnalyze *ira,
     return ira->codegen->builtin_types.entry_bool;
 }
 
+static TypeTableEntry *ir_analyze_instruction_panic(IrAnalyze *ira, IrInstructionPanic *instruction) {
+    IrInstruction *msg = instruction->msg->other;
+    if (type_is_invalid(msg->value.type))
+        return ira->codegen->builtin_types.entry_invalid;
+
+    TypeTableEntry *str_type = get_slice_type(ira->codegen, ira->codegen->builtin_types.entry_u8, true);
+    IrInstruction *casted_msg = ir_implicit_cast(ira, msg, str_type);
+    if (type_is_invalid(casted_msg->value.type))
+        return ira->codegen->builtin_types.entry_invalid;
+
+    IrInstruction *new_instruction = ir_build_panic(&ira->new_irb, instruction->base.scope,
+            instruction->base.source_node, casted_msg);
+    ir_link_new_instruction(new_instruction, &instruction->base);
+    return ir_finish_anal(ira, ira->codegen->builtin_types.entry_unreachable);
+}
+
 static TypeTableEntry *ir_analyze_instruction_decl_ref(IrAnalyze *ira,
         IrInstructionDeclRef *instruction)
 {
@@ -12266,12 +12329,12 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
             return ir_analyze_instruction_to_ptr_type(ira, (IrInstructionToPtrType *)instruction);
         case IrInstructionIdPtrTypeChild:
             return ir_analyze_instruction_ptr_type_child(ira, (IrInstructionPtrTypeChild *)instruction);
-        case IrInstructionIdSetFnVisible:
-            return ir_analyze_instruction_set_fn_visible(ira, (IrInstructionSetFnVisible *)instruction);
         case IrInstructionIdSetGlobalAlign:
             return ir_analyze_instruction_set_global_align(ira, (IrInstructionSetGlobalAlign *)instruction);
         case IrInstructionIdSetGlobalSection:
             return ir_analyze_instruction_set_global_section(ira, (IrInstructionSetGlobalSection *)instruction);
+        case IrInstructionIdSetGlobalLinkage:
+            return ir_analyze_instruction_set_global_linkage(ira, (IrInstructionSetGlobalLinkage *)instruction);
         case IrInstructionIdSetDebugSafety:
             return ir_analyze_instruction_set_debug_safety(ira, (IrInstructionSetDebugSafety *)instruction);
         case IrInstructionIdSliceType:
@@ -12384,6 +12447,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
             return ir_analyze_instruction_can_implicit_cast(ira, (IrInstructionCanImplicitCast *)instruction);
         case IrInstructionIdDeclRef:
             return ir_analyze_instruction_decl_ref(ira, (IrInstructionDeclRef *)instruction);
+        case IrInstructionIdPanic:
+            return ir_analyze_instruction_panic(ira, (IrInstructionPanic *)instruction);
         case IrInstructionIdMaybeWrap:
         case IrInstructionIdErrWrapCode:
         case IrInstructionIdErrWrapPayload:
@@ -12482,7 +12547,6 @@ bool ir_has_side_effects(IrInstruction *instruction) {
         case IrInstructionIdCall:
         case IrInstructionIdReturn:
         case IrInstructionIdUnreachable:
-        case IrInstructionIdSetFnVisible:
         case IrInstructionIdSetDebugSafety:
         case IrInstructionIdImport:
         case IrInstructionIdCompileErr:
@@ -12500,6 +12564,8 @@ bool ir_has_side_effects(IrInstruction *instruction) {
         case IrInstructionIdCheckSwitchProngs:
         case IrInstructionIdSetGlobalAlign:
         case IrInstructionIdSetGlobalSection:
+        case IrInstructionIdSetGlobalLinkage:
+        case IrInstructionIdPanic:
             return true;
         case IrInstructionIdPhi:
         case IrInstructionIdUnOp:
@@ -12580,7 +12646,7 @@ bool ir_has_side_effects(IrInstruction *instruction) {
 }
 
 FnTableEntry *ir_create_inline_fn(CodeGen *codegen, Buf *fn_name, VariableTableEntry *var, Scope *parent_scope) {
-    FnTableEntry *fn_entry = create_fn_raw(FnInlineAuto, true);
+    FnTableEntry *fn_entry = create_fn_raw(FnInlineAuto, GlobalLinkageIdInternal);
     buf_init_from_buf(&fn_entry->symbol_name, fn_name);
 
     fn_entry->fndef_scope = create_fndef_scope(nullptr, parent_scope, fn_entry);
src/ir_print.cpp
@@ -339,14 +339,6 @@ static void ir_print_enum_field_ptr(IrPrint *irp, IrInstructionEnumFieldPtr *ins
     fprintf(irp->f, ")");
 }
 
-static void ir_print_set_fn_visible(IrPrint *irp, IrInstructionSetFnVisible *instruction) {
-    fprintf(irp->f, "@setFnVisible(");
-    ir_print_other_instruction(irp, instruction->fn_value);
-    fprintf(irp->f, ", ");
-    ir_print_other_instruction(irp, instruction->is_visible);
-    fprintf(irp->f, ")");
-}
-
 static void ir_print_set_debug_safety(IrPrint *irp, IrInstructionSetDebugSafety *instruction) {
     fprintf(irp->f, "@setDebugSafety(");
     ir_print_other_instruction(irp, instruction->scope_value);
@@ -849,6 +841,13 @@ static void ir_print_set_global_section(IrPrint *irp, IrInstructionSetGlobalSect
     fprintf(irp->f, ")");
 }
 
+static void ir_print_set_global_linkage(IrPrint *irp, IrInstructionSetGlobalLinkage *instruction) {
+    fprintf(irp->f, "@setGlobalLinkage(%s,", buf_ptr(instruction->tld->name));
+    ir_print_other_instruction(irp, instruction->value);
+    fprintf(irp->f, ")");
+}
+
+
 static void ir_print_decl_ref(IrPrint *irp, IrInstructionDeclRef *instruction) {
     const char *ptr_str = instruction->lval.is_ptr ? "ptr " : "";
     const char *const_str = instruction->lval.is_const ? "const " : "";
@@ -856,6 +855,13 @@ static void ir_print_decl_ref(IrPrint *irp, IrInstructionDeclRef *instruction) {
     fprintf(irp->f, "declref %s%s%s%s", const_str, volatile_str, ptr_str, buf_ptr(instruction->tld->name));
 }
 
+static void ir_print_panic(IrPrint *irp, IrInstructionPanic *instruction) {
+    fprintf(irp->f, "@panic(");
+    ir_print_other_instruction(irp, instruction->msg);
+    fprintf(irp->f, ")");
+}
+
+
 static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
     ir_print_prefix(irp, instruction);
     switch (instruction->id) {
@@ -933,9 +939,6 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdEnumFieldPtr:
             ir_print_enum_field_ptr(irp, (IrInstructionEnumFieldPtr *)instruction);
             break;
-        case IrInstructionIdSetFnVisible:
-            ir_print_set_fn_visible(irp, (IrInstructionSetFnVisible *)instruction);
-            break;
         case IrInstructionIdSetDebugSafety:
             ir_print_set_debug_safety(irp, (IrInstructionSetDebugSafety *)instruction);
             break;
@@ -1128,9 +1131,15 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdSetGlobalSection:
             ir_print_set_global_section(irp, (IrInstructionSetGlobalSection *)instruction);
             break;
+        case IrInstructionIdSetGlobalLinkage:
+            ir_print_set_global_linkage(irp, (IrInstructionSetGlobalLinkage *)instruction);
+            break;
         case IrInstructionIdDeclRef:
             ir_print_decl_ref(irp, (IrInstructionDeclRef *)instruction);
             break;
+        case IrInstructionIdPanic:
+            ir_print_panic(irp, (IrInstructionPanic *)instruction);
+            break;
     }
     fprintf(irp->f, "\n");
 }
src/link.cpp
@@ -52,6 +52,7 @@ static Buf *build_o(CodeGen *parent_gen, const char *oname) {
         child_gen->link_libs.items[i] = parent_gen->link_libs.items[i];
     }
 
+    codegen_set_omit_zigrt(child_gen, true);
     child_gen->want_h_file = false;
 
     codegen_set_is_release(child_gen, parent_gen->is_release_build);
src/parseh.cpp
@@ -635,8 +635,7 @@ static void visit_fn_decl(Context *c, const FunctionDecl *fn_decl) {
     }
     assert(fn_type->id == TypeTableEntryIdFn);
 
-    bool internal_linkage = false;
-    FnTableEntry *fn_entry = create_fn_raw(FnInlineAuto, internal_linkage);
+    FnTableEntry *fn_entry = create_fn_raw(FnInlineAuto, GlobalLinkageIdStrong);
     buf_init_from_buf(&fn_entry->symbol_name, fn_name);
     fn_entry->type_entry = fn_type;
 
std/special/bootstrap.zig
@@ -13,7 +13,7 @@ var argc: usize = undefined;
 var argv: &&u8 = undefined;
 
 export nakedcc fn _start() -> noreturn {
-    @setFnVisible(this, want_start_symbol);
+    @setGlobalLinkage(_start, if (want_start_symbol) GlobalLinkage.Strong else GlobalLinkage.Internal);
     if (!want_start_symbol) {
         unreachable;
     }
@@ -47,7 +47,7 @@ fn callMainAndExit() -> noreturn {
 }
 
 export fn main(c_argc: i32, c_argv: &&u8) -> i32 {
-    @setFnVisible(this, want_main_symbol);
+    @setGlobalLinkage(main, if (want_main_symbol) GlobalLinkage.Strong else GlobalLinkage.Internal);
     if (!want_main_symbol) {
         unreachable;
     }
std/special/builtin.zig
@@ -30,7 +30,6 @@ export fn memcpy(noalias dest: ?&u8, noalias src: ?&const u8, n: usize) {
         d[index] = s[index];
 }
 
-// Avoid dragging in the debug safety mechanisms into this .o file.
-pub fn panic(message: []const u8) -> noreturn {
-    unreachable;
+export fn __stack_chk_fail() {
+    @panic("stack smashing detected");
 }
std/special/compiler_rt.zig
@@ -1,13 +1,3 @@
-// Avoid dragging in the debug safety mechanisms into this .o file,
-// unless we're trying to test this file.
-pub fn panic(message: []const u8) -> noreturn {
-    if (@compileVar("is_test")) {
-        @import("std").debug.panic(message);
-    } else {
-        unreachable;
-    }
-}
-
 const CHAR_BIT = 8;
 const du_int = u64;
 const di_int = i64;
@@ -262,7 +252,7 @@ export nakedcc fn __aeabi_uidivmod() {
         unreachable;
     }
 
-    @setFnVisible(this, false);
+    @setGlobalLinkage(__aeabi_uidivmod, GlobalLinkage.Internal);
 }
 
 export fn __udivmodsi4(a: su_int, b: su_int, rem: &su_int) -> su_int {
std/special/panic.zig
@@ -1,12 +0,0 @@
-// This file is included if and only if the user's main source file does not
-// include a public panic function.
-// If this file wants to import other files *by name*, support for that would
-// have to be added in the compiler.
-
-pub coldcc fn panic(message: []const u8) -> noreturn {
-    if (@compileVar("os") == Os.freestanding) {
-        while (true) {}
-    } else {
-        @import("std").debug.panic(message);
-    }
-}
std/special/zigrt.zig
@@ -0,0 +1,16 @@
+// This file contains functions that zig depends on to coordinate between
+// multiple .o files. The symbols are defined LinkOnce so that multiple
+// instances of zig_rt.zig do not conflict with each other.
+
+export coldcc fn __zig_panic(message_ptr: &const u8, message_len: usize) -> noreturn {
+    @setGlobalLinkage(__zig_panic, GlobalLinkage.Weak);
+    @setDebugSafety(this, false);
+
+    if (@compileVar("panic_implementation_provided")) {
+        @import("@root").panic(message_ptr[0...message_len]);
+    } else if (@compileVar("os") == Os.freestanding) {
+        while (true) {}
+    } else {
+        @import("std").debug.panic(message_ptr[0...message_len]);
+    }
+}
test/cases/misc.zig
@@ -12,7 +12,7 @@ test "emptyFunctionWithComments" {
 }
 
 export fn disabledExternFn() {
-    @setFnVisible(this, false);
+    @setGlobalLinkage(disabledExternFn, GlobalLinkage.Internal);
 }
 
 test "callDisabledExternFn" {
test/run_tests.cpp
@@ -1828,6 +1828,18 @@ export fn entry() {
 //////////////////////////////////////////////////////////////////////////////
 
 static void add_debug_safety_test_cases(void) {
+    add_debug_safety_case("calling panic", R"SOURCE(
+pub fn panic(message: []const u8) -> noreturn {
+    @breakpoint();
+    while (true) {}
+}
+pub fn main(args: [][]u8) -> %void {
+    if (!@compileVar("is_release")) {
+        @panic("oh no");
+    }
+}
+    )SOURCE");
+
     add_debug_safety_case("out of bounds slice access", R"SOURCE(
 pub fn panic(message: []const u8) -> noreturn {
     @breakpoint();
CMakeLists.txt
@@ -233,8 +233,8 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/sort.zig" DESTINATION "${ZIG_STD_DEST}")
 install(FILES "${CMAKE_SOURCE_DIR}/std/special/bootstrap.zig" DESTINATION "${ZIG_STD_DEST}/special")
 install(FILES "${CMAKE_SOURCE_DIR}/std/special/builtin.zig" DESTINATION "${ZIG_STD_DEST}/special")
 install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt.zig" DESTINATION "${ZIG_STD_DEST}/special")
-install(FILES "${CMAKE_SOURCE_DIR}/std/special/panic.zig" DESTINATION "${ZIG_STD_DEST}/special")
 install(FILES "${CMAKE_SOURCE_DIR}/std/special/test_runner.zig" DESTINATION "${ZIG_STD_DEST}/special")
+install(FILES "${CMAKE_SOURCE_DIR}/std/special/zigrt.zig" DESTINATION "${ZIG_STD_DEST}/special")
 install(FILES "${CMAKE_SOURCE_DIR}/std/target.zig" DESTINATION "${ZIG_STD_DEST}")
 
 add_executable(run_tests ${TEST_SOURCES})