diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 20195bf5a505..fa6b2ab6b44f 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -643,6 +643,15 @@ pub const ExportOptions = struct { section: ?[]const u8 = null, }; +/// This data structure is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. +pub const ExternOptions = struct { + name: []const u8, + library_name: ?[]const u8 = null, + linkage: GlobalLinkage = .Strong, + is_thread_local: bool = false, +}; + /// This function type is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. pub const TestFn = struct { diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index 89c2002e3312..d56bcffff119 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -1808,6 +1808,7 @@ enum BuiltinFnId { BuiltinFnIdThis, BuiltinFnIdSetAlignStack, BuiltinFnIdExport, + BuiltinFnIdExtern, BuiltinFnIdErrorReturnTrace, BuiltinFnIdAtomicRmw, BuiltinFnIdAtomicLoad, @@ -2619,6 +2620,7 @@ enum IrInstSrcId { IrInstSrcIdSetAlignStack, IrInstSrcIdArgType, IrInstSrcIdExport, + IrInstSrcIdExtern, IrInstSrcIdErrorReturnTrace, IrInstSrcIdErrorUnion, IrInstSrcIdAtomicRmw, @@ -2736,6 +2738,7 @@ enum IrInstGenId { IrInstGenIdConst, IrInstGenIdWasmMemorySize, IrInstGenIdWasmMemoryGrow, + IrInstGenIdExtern, }; // Common fields between IrInstSrc and IrInstGen. This allows future passes @@ -4146,6 +4149,21 @@ struct IrInstSrcExport { IrInstSrc *options; }; +struct IrInstSrcExtern { + IrInstSrc base; + + IrInstSrc *type; + IrInstSrc *options; +}; + +struct IrInstGenExtern { + IrInstGen base; + + Buf *name; + GlobalLinkageId linkage; + bool is_thread_local; +}; + enum IrInstErrorReturnTraceOptional { IrInstErrorReturnTraceNull, IrInstErrorReturnTraceNonNull, diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 4a999b33ca3d..26fe00228e23 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -6389,6 +6389,30 @@ static LLVMValueRef ir_render_bswap(CodeGen *g, IrExecutableGen *executable, IrI return LLVMBuildTrunc(g->builder, shifted, get_llvm_type(g, expr_type), ""); } +static LLVMValueRef ir_render_extern(CodeGen *g, IrExecutableGen *executable, + IrInstGenExtern *instruction) +{ + ZigType *expr_type = instruction->base.value->type; + assert(get_src_ptr_type(expr_type)); + + const char *symbol_name = buf_ptr(instruction->name); + const LLVMLinkage linkage = to_llvm_linkage(instruction->linkage, true); + + LLVMValueRef global_value = LLVMGetNamedGlobal(g->module, symbol_name); + if (global_value == nullptr) { + global_value = LLVMAddGlobal(g->module, get_llvm_type(g, expr_type), symbol_name); + LLVMSetLinkage(global_value, linkage); + LLVMSetGlobalConstant(global_value, true); + if (instruction->is_thread_local) + LLVMSetThreadLocalMode(global_value, LLVMGeneralDynamicTLSModel); + } else if (LLVMGetLinkage(global_value) != linkage) { + // XXX: Handle this case better! + zig_panic("duplicate extern symbol"); + } + + return LLVMBuildBitCast(g->builder, global_value, get_llvm_type(g, expr_type), ""); +} + static LLVMValueRef ir_render_bit_reverse(CodeGen *g, IrExecutableGen *executable, IrInstGenBitReverse *instruction) { LLVMValueRef op = ir_llvm_value(g, instruction->op); ZigType *int_type = instruction->base.value->type; @@ -6902,6 +6926,8 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutableGen *executabl return ir_render_wasm_memory_size(g, executable, (IrInstGenWasmMemorySize *) instruction); case IrInstGenIdWasmMemoryGrow: return ir_render_wasm_memory_grow(g, executable, (IrInstGenWasmMemoryGrow *) instruction); + case IrInstGenIdExtern: + return ir_render_extern(g, executable, (IrInstGenExtern *) instruction); } zig_unreachable(); } @@ -8800,6 +8826,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdAlignCast, "alignCast", 2); create_builtin_fn(g, BuiltinFnIdSetAlignStack, "setAlignStack", 1); create_builtin_fn(g, BuiltinFnIdExport, "export", 2); + create_builtin_fn(g, BuiltinFnIdExtern, "extern", 2); create_builtin_fn(g, BuiltinFnIdErrorReturnTrace, "errorReturnTrace", 0); create_builtin_fn(g, BuiltinFnIdAtomicRmw, "atomicRmw", 5); create_builtin_fn(g, BuiltinFnIdAtomicLoad, "atomicLoad", 3); diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index 16884c6751e9..561a51271ee3 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -519,6 +519,8 @@ static void destroy_instruction_src(IrInstSrc *inst) { return heap::c_allocator.destroy(reinterpret_cast(inst)); case IrInstSrcIdExport: return heap::c_allocator.destroy(reinterpret_cast(inst)); + case IrInstSrcIdExtern: + return heap::c_allocator.destroy(reinterpret_cast(inst)); case IrInstSrcIdErrorReturnTrace: return heap::c_allocator.destroy(reinterpret_cast(inst)); case IrInstSrcIdErrorUnion: @@ -755,6 +757,8 @@ void destroy_instruction_gen(IrInstGen *inst) { return heap::c_allocator.destroy(reinterpret_cast(inst)); case IrInstGenIdWasmMemoryGrow: return heap::c_allocator.destroy(reinterpret_cast(inst)); + case IrInstGenIdExtern: + return heap::c_allocator.destroy(reinterpret_cast(inst)); } zig_unreachable(); } @@ -1555,6 +1559,10 @@ static constexpr IrInstSrcId ir_inst_id(IrInstSrcExport *) { return IrInstSrcIdExport; } +static constexpr IrInstSrcId ir_inst_id(IrInstSrcExtern *) { + return IrInstSrcIdExtern; +} + static constexpr IrInstSrcId ir_inst_id(IrInstSrcErrorReturnTrace *) { return IrInstSrcIdErrorReturnTrace; } @@ -1999,6 +2007,10 @@ static constexpr IrInstGenId ir_inst_id(IrInstGenWasmMemoryGrow *) { return IrInstGenIdWasmMemoryGrow; } +static constexpr IrInstGenId ir_inst_id(IrInstGenExtern *) { + return IrInstGenIdExtern; +} + template static T *ir_create_instruction(IrBuilderSrc *irb, Scope *scope, AstNode *source_node) { T *special_instruction = heap::c_allocator.create(); @@ -2807,6 +2819,33 @@ static IrInstSrc *ir_build_export(IrBuilderSrc *irb, Scope *scope, AstNode *sour return &export_instruction->base; } +static IrInstSrc *ir_build_extern(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, + IrInstSrc *type, IrInstSrc *options) +{ + IrInstSrcExtern *extern_instruction = ir_build_instruction( + irb, scope, source_node); + extern_instruction->type = type; + extern_instruction->options = options; + + ir_ref_instruction(type, irb->current_basic_block); + ir_ref_instruction(options, irb->current_basic_block); + + return &extern_instruction->base; +} + +static IrInstGen *ir_build_extern_gen(IrAnalyze *ira, IrInst *source_instr, Buf *name, + GlobalLinkageId linkage, bool is_thread_local, ZigType *expr_type) +{ + IrInstGenExtern *instruction = ir_build_inst_gen(&ira->new_irb, + source_instr->scope, source_instr->source_node); + instruction->base.value->type = expr_type; + instruction->name = name; + instruction->linkage = linkage; + instruction->is_thread_local = is_thread_local; + + return &instruction->base; +} + static IrInstSrc *ir_build_load_ptr(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, IrInstSrc *ptr) { IrInstSrcLoadPtr *instruction = ir_build_instruction(irb, scope, source_node); instruction->ptr = ptr; @@ -7376,6 +7415,30 @@ static IrInstSrc *ir_gen_builtin_fn_call(IrBuilderSrc *irb, Scope *scope, AstNod IrInstSrc *ir_export = ir_build_export(irb, scope, node, target_value, casted_options_value); return ir_lval_wrap(irb, scope, ir_export, lval, result_loc); } + case BuiltinFnIdExtern: + { + // Cast the options parameter to the options type + ZigType *options_type = get_builtin_type(irb->codegen, "ExternOptions"); + IrInstSrc *options_type_inst = ir_build_const_type(irb, scope, node, options_type); + ResultLocCast *result_loc_cast = ir_build_cast_result_loc(irb, options_type_inst, no_result_loc()); + + AstNode *type_node = node->data.fn_call_expr.params.at(0); + IrInstSrc *type_value = ir_gen_node(irb, type_node, scope); + if (type_value == irb->codegen->invalid_inst_src) + return type_value; + + AstNode *options_node = node->data.fn_call_expr.params.at(1); + IrInstSrc *options_value = ir_gen_node_extra(irb, options_node, + scope, LValNone, &result_loc_cast->base); + if (options_value == irb->codegen->invalid_inst_src) + return options_value; + + IrInstSrc *casted_options_value = ir_build_implicit_cast( + irb, scope, options_node, options_value, result_loc_cast); + + IrInstSrc *ir_extern = ir_build_extern(irb, scope, node, type_value, casted_options_value); + return ir_lval_wrap(irb, scope, ir_extern, lval, result_loc); + } case BuiltinFnIdErrorReturnTrace: { IrInstSrc *error_return_trace = ir_build_error_return_trace_src(irb, scope, node, @@ -19166,6 +19229,126 @@ static IrInstGen *ir_analyze_instruction_export(IrAnalyze *ira, IrInstSrcExport return ir_const_void(ira, &instruction->base.base); } +static void add_link_lib_symbol(IrAnalyze *ira, Buf *lib_name, Buf *symbol_name, AstNode *source_node); + +static IrInstGen *ir_analyze_instruction_extern(IrAnalyze *ira, IrInstSrcExtern *instruction) { + IrInstGen *type_inst = instruction->type->child; + if (type_is_invalid(type_inst->value->type)) + return ira->codegen->invalid_inst_gen; + + IrInstGen *options = instruction->options->child; + if (type_is_invalid(options->value->type)) + return ira->codegen->invalid_inst_gen; + + ZigType *options_type = options->value->type; + assert(options_type->id == ZigTypeIdStruct); + + TypeStructField *name_field = find_struct_type_field(options_type, buf_create_from_str("name")); + ir_assert(name_field != nullptr, &instruction->base.base); + IrInstGen *name_inst = ir_analyze_struct_value_field_value(ira, &instruction->base.base, options, name_field); + if (type_is_invalid(name_inst->value->type)) + return ira->codegen->invalid_inst_gen; + + TypeStructField *linkage_field = find_struct_type_field(options_type, buf_create_from_str("linkage")); + ir_assert(linkage_field != nullptr, &instruction->base.base); + IrInstGen *linkage_inst = ir_analyze_struct_value_field_value(ira, &instruction->base.base, options, linkage_field); + if (type_is_invalid(linkage_inst->value->type)) + return ira->codegen->invalid_inst_gen; + + TypeStructField *is_thread_local_field = find_struct_type_field(options_type, buf_create_from_str("is_thread_local")); + ir_assert(is_thread_local_field != nullptr, &instruction->base.base); + IrInstGen *is_thread_local_inst = ir_analyze_struct_value_field_value(ira, &instruction->base.base, options, is_thread_local_field); + if (type_is_invalid(is_thread_local_inst->value->type)) + return ira->codegen->invalid_inst_gen; + + TypeStructField *library_name_field = find_struct_type_field(options_type, buf_create_from_str("library_name")); + ir_assert(library_name_field != nullptr, &instruction->base.base); + IrInstGen *library_name_inst = ir_analyze_struct_value_field_value(ira, &instruction->base.base, options, library_name_field); + if (type_is_invalid(library_name_inst->value->type)) + return ira->codegen->invalid_inst_gen; + + // The `library_name` field is optional, we have to unwrap it first + IrInstGen *non_null_check = ir_analyze_test_non_null(ira, &instruction->base.base, library_name_inst); + bool is_non_null; + if (!ir_resolve_bool(ira, non_null_check, &is_non_null)) + return ira->codegen->invalid_inst_gen; + + IrInstGen *library_name_val_inst = nullptr; + if (is_non_null) { + library_name_val_inst = ir_analyze_optional_value_payload_value(ira, &instruction->base.base, library_name_inst, false); + if (type_is_invalid(library_name_val_inst->value->type)) + return ira->codegen->invalid_inst_gen; + } + + // Resolve all the comptime values + ZigType *value_type = ir_resolve_type(ira, type_inst); + if (type_is_invalid(value_type)) + return ira->codegen->invalid_inst_gen; + + if (get_src_ptr_type(value_type) == nullptr) { + ir_add_error(ira, &name_inst->base, + buf_sprintf("expected (optional) pointer type or function")); + return ira->codegen->invalid_inst_gen; + } + + Buf *symbol_name = ir_resolve_str(ira, name_inst); + if (!symbol_name) + return ira->codegen->invalid_inst_gen; + + if (buf_len(symbol_name) == 0) { + ir_add_error(ira, &name_inst->base, + buf_sprintf("extern symbol name cannot be empty")); + return ira->codegen->invalid_inst_gen; + } + + Buf *library_name = nullptr; + if (library_name_val_inst) { + library_name = ir_resolve_str(ira, library_name_val_inst); + if (!library_name) + return ira->codegen->invalid_inst_gen; + + if (buf_len(library_name) == 0) { + ir_add_error(ira, &library_name_inst->base, + buf_sprintf("library name name cannot be empty")); + return ira->codegen->invalid_inst_gen; + } + + add_link_lib_symbol(ira, library_name, symbol_name, instruction->base.base.source_node); + + buf_destroy(library_name); + } + + GlobalLinkageId global_linkage_id; + if (!ir_resolve_global_linkage(ira, linkage_inst, &global_linkage_id)) + return ira->codegen->invalid_inst_gen; + + bool is_thread_local; + if (!ir_resolve_bool(ira, is_thread_local_inst, &is_thread_local)) + return ira->codegen->invalid_inst_gen; + + ZigType *expr_type = value_type; + if (global_linkage_id == GlobalLinkageIdWeak && value_type->id != ZigTypeIdOptional) + expr_type = get_optional_type(ira->codegen, expr_type); + + // Create a bogus Tld object to keep track of the extern symbol. + // XXX: Find a better way to do this (in stage2). + TldFn *tld_fn = heap::c_allocator.create(); + tld_fn->base.id = TldIdFn; + tld_fn->base.source_node = instruction->base.base.source_node; + + auto entry = ira->codegen->external_symbol_names.put_unique(symbol_name, &tld_fn->base); + if (entry) { + AstNode *other_extern_node = entry->value->source_node; + ErrorMsg *msg = ir_add_error(ira, &instruction->base.base, + buf_sprintf("extern symbol collision: '%s'", buf_ptr(symbol_name))); + add_error_note(ira->codegen, msg, other_extern_node, buf_sprintf("other symbol is here")); + return ira->codegen->invalid_inst_gen; + } + + return ir_build_extern_gen(ira, &instruction->base.base, symbol_name, global_linkage_id, + is_thread_local, expr_type); +} + static bool exec_has_err_ret_trace(CodeGen *g, IrExecutableSrc *exec) { ZigFn *fn_entry = exec_fn_entry(exec); return fn_entry != nullptr && fn_entry->calls_or_awaits_errorable_fn && g->have_err_ret_tracing; @@ -32110,6 +32293,8 @@ static IrInstGen *ir_analyze_instruction_base(IrAnalyze *ira, IrInstSrc *instruc return ir_analyze_instruction_tag_type(ira, (IrInstSrcTagType *)instruction); case IrInstSrcIdExport: return ir_analyze_instruction_export(ira, (IrInstSrcExport *)instruction); + case IrInstSrcIdExtern: + return ir_analyze_instruction_extern(ira, (IrInstSrcExtern *)instruction); case IrInstSrcIdErrorReturnTrace: return ir_analyze_instruction_error_return_trace(ira, (IrInstSrcErrorReturnTrace *)instruction); case IrInstSrcIdErrorUnion: @@ -32345,6 +32530,7 @@ bool ir_inst_gen_has_side_effects(IrInstGen *instruction) { case IrInstGenIdAwait: case IrInstGenIdSpillBegin: case IrInstGenIdWasmMemoryGrow: + case IrInstGenIdExtern: return true; case IrInstGenIdPhi: @@ -32465,6 +32651,7 @@ bool ir_inst_src_has_side_effects(IrInstSrc *instruction) { case IrInstSrcIdPtrType: case IrInstSrcIdSetAlignStack: case IrInstSrcIdExport: + case IrInstSrcIdExtern: case IrInstSrcIdSaveErrRetAddr: case IrInstSrcIdAddImplicitReturnType: case IrInstSrcIdAtomicRmw: diff --git a/src/stage1/ir_print.cpp b/src/stage1/ir_print.cpp index 6a90f5fe5c80..572551e61512 100644 --- a/src/stage1/ir_print.cpp +++ b/src/stage1/ir_print.cpp @@ -314,6 +314,8 @@ const char* ir_inst_src_type_str(IrInstSrcId id) { return "SrcArgType"; case IrInstSrcIdExport: return "SrcExport"; + case IrInstSrcIdExtern: + return "SrcExtern"; case IrInstSrcIdErrorReturnTrace: return "SrcErrorReturnTrace"; case IrInstSrcIdErrorUnion: @@ -544,6 +546,8 @@ const char* ir_inst_gen_type_str(IrInstGenId id) { return "GenWasmMemorySize"; case IrInstGenIdWasmMemoryGrow: return "GenWasmMemoryGrow"; + case IrInstGenIdExtern: + return "GenExtrern"; } zig_unreachable(); } @@ -2364,6 +2368,18 @@ static void ir_print_export(IrPrintSrc *irp, IrInstSrcExport *instruction) { fprintf(irp->f, ")"); } +static void ir_print_extern(IrPrintGen *irp, IrInstGenExtern *instruction) { + fprintf(irp->f, "@extern(...)"); +} + +static void ir_print_extern(IrPrintSrc *irp, IrInstSrcExtern *instruction) { + fprintf(irp->f, "@extern("); + ir_print_other_inst_src(irp, instruction->type); + fprintf(irp->f, ","); + ir_print_other_inst_src(irp, instruction->options); + fprintf(irp->f, ")"); +} + static void ir_print_error_return_trace(IrPrintSrc *irp, IrInstSrcErrorReturnTrace *instruction) { fprintf(irp->f, "@errorReturnTrace("); switch (instruction->optional) { @@ -2943,6 +2959,9 @@ static void ir_print_inst_src(IrPrintSrc *irp, IrInstSrc *instruction, bool trai case IrInstSrcIdExport: ir_print_export(irp, (IrInstSrcExport *)instruction); break; + case IrInstSrcIdExtern: + ir_print_extern(irp, (IrInstSrcExtern*)instruction); + break; case IrInstSrcIdErrorReturnTrace: ir_print_error_return_trace(irp, (IrInstSrcErrorReturnTrace *)instruction); break; @@ -3294,6 +3313,10 @@ static void ir_print_inst_gen(IrPrintGen *irp, IrInstGen *instruction, bool trai case IrInstGenIdWasmMemoryGrow: ir_print_wasm_memory_grow(irp, (IrInstGenWasmMemoryGrow *)instruction); break; + case IrInstGenIdExtern: + ir_print_extern(irp, (IrInstGenExtern *)instruction); + break; + } fprintf(irp->f, "\n"); } diff --git a/src/stage1/parser.cpp b/src/stage1/parser.cpp index a49773e9ca59..eab4ea0f7709 100644 --- a/src/stage1/parser.cpp +++ b/src/stage1/parser.cpp @@ -1652,18 +1652,21 @@ static AstNode *ast_parse_primary_type_expr(ParseContext *pc) { // TODO: This is not in line with the grammar. // Because the prev stage 1 tokenizer does not parse // @[a-zA-Z_][a-zA-Z0-9_] as one token, it has to do a - // hack, where it accepts '@' (IDENTIFIER / KEYWORD_export). + // hack, where it accepts '@' (IDENTIFIER / KEYWORD_export / + // KEYWORD_extern). // I'd say that it's better if '@' is part of the builtin // identifier token. Token *at_sign = eat_token_if(pc, TokenIdAtSign); if (at_sign != nullptr) { Buf *name; - Token *token = eat_token_if(pc, TokenIdKeywordExport); - if (token == nullptr) { + Token *token; + if ((token = eat_token_if(pc, TokenIdKeywordExport)) != nullptr) { + name = buf_create_from_str("export"); + } else if ((token = eat_token_if(pc, TokenIdKeywordExtern)) != nullptr) { + name = buf_create_from_str("extern"); + } else { token = expect_token(pc, TokenIdSymbol); name = token_buf(token); - } else { - name = buf_create_from_str("export"); } AstNode *res = ast_expect(pc, ast_parse_fn_call_arguments);