From 97bfeac13f89e1b5a22fcd7d4705341b4c3e1950 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 16 Jul 2018 20:52:50 -0400 Subject: self-hosted: create tmp dir for .o files and emit .o file for fn --- src-self-hosted/codegen.zig | 86 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 8 deletions(-) (limited to 'src-self-hosted/codegen.zig') diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 698f1e5b45..28ba2a1564 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -1,19 +1,22 @@ const std = @import("std"); +const builtin = @import("builtin"); const Compilation = @import("compilation.zig").Compilation; -// we go through llvm instead of c for 2 reasons: -// 1. to avoid accidentally calling the non-thread-safe functions -// 2. patch up some of the types to remove nullability const llvm = @import("llvm.zig"); +const c = @import("c.zig"); const ir = @import("ir.zig"); const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; const event = std.event; const assert = std.debug.assert; +const DW = std.dwarf; pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) !void { fn_val.base.ref(); defer fn_val.base.deref(comp); - defer code.destroy(comp.a()); + defer code.destroy(comp.gpa()); + + var output_path = try await (async comp.createRandomOutputPath(comp.target.oFileExt()) catch unreachable); + errdefer output_path.deinit(); const llvm_handle = try comp.event_loop_local.getAnyLlvmContext(); defer llvm_handle.release(comp.event_loop_local); @@ -23,13 +26,56 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) const module = llvm.ModuleCreateWithNameInContext(comp.name.ptr(), context) orelse return error.OutOfMemory; defer llvm.DisposeModule(module); + llvm.SetTarget(module, comp.llvm_triple.ptr()); + llvm.SetDataLayout(module, comp.target_layout_str); + + if (comp.target.getObjectFormat() == builtin.ObjectFormat.coff) { + llvm.AddModuleCodeViewFlag(module); + } else { + llvm.AddModuleDebugInfoFlag(module); + } + const builder = llvm.CreateBuilderInContext(context) orelse return error.OutOfMemory; defer llvm.DisposeBuilder(builder); + const dibuilder = llvm.CreateDIBuilder(module, true) orelse return error.OutOfMemory; + defer llvm.DisposeDIBuilder(dibuilder); + + // Don't use ZIG_VERSION_STRING here. LLVM misparses it when it includes + // the git revision. + const producer = try std.Buffer.allocPrint( + &code.arena.allocator, + "zig {}.{}.{}", + u32(c.ZIG_VERSION_MAJOR), + u32(c.ZIG_VERSION_MINOR), + u32(c.ZIG_VERSION_PATCH), + ); + const flags = c""; + const runtime_version = 0; + const compile_unit_file = llvm.CreateFile( + dibuilder, + comp.name.ptr(), + comp.root_package.root_src_dir.ptr(), + ) orelse return error.OutOfMemory; + const is_optimized = comp.build_mode != builtin.Mode.Debug; + const compile_unit = llvm.CreateCompileUnit( + dibuilder, + DW.LANG_C99, + compile_unit_file, + producer.ptr(), + is_optimized, + flags, + runtime_version, + c"", + 0, + !comp.strip, + ) orelse return error.OutOfMemory; + var ofile = ObjectFile{ .comp = comp, .module = module, .builder = builder, + .dibuilder = dibuilder, .context = context, .lock = event.Lock.init(comp.loop), }; @@ -41,8 +87,7 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) // LLVMSetModuleInlineAsm(g->module, buf_ptr(&g->global_asm)); //} - // TODO - //ZigLLVMDIBuilderFinalize(g->dbuilder); + llvm.DIBuilderFinalize(dibuilder); if (comp.verbose_llvm_ir) { llvm.DumpModule(ofile.module); @@ -53,17 +98,42 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) var error_ptr: ?[*]u8 = null; _ = llvm.VerifyModule(ofile.module, llvm.AbortProcessAction, &error_ptr); } + + assert(comp.emit_file_type == Compilation.Emit.Binary); // TODO support other types + + const is_small = comp.build_mode == builtin.Mode.ReleaseSmall; + const is_debug = comp.build_mode == builtin.Mode.Debug; + + var err_msg: [*]u8 = undefined; + // TODO integrate this with evented I/O + if (llvm.TargetMachineEmitToFile( + comp.target_machine, + module, + output_path.ptr(), + llvm.EmitBinary, + &err_msg, + is_debug, + is_small, + )) { + if (std.debug.runtime_safety) { + std.debug.panic("unable to write object file {}: {s}\n", output_path.toSliceConst(), err_msg); + } + return error.WritingObjectFileFailed; + } + //validate_inline_fns(g); TODO + fn_val.containing_object = output_path; } pub const ObjectFile = struct { comp: *Compilation, module: llvm.ModuleRef, builder: llvm.BuilderRef, + dibuilder: *llvm.DIBuilder, context: llvm.ContextRef, lock: event.Lock, - fn a(self: *ObjectFile) *std.mem.Allocator { - return self.comp.a(); + fn gpa(self: *ObjectFile) *std.mem.Allocator { + return self.comp.gpa(); } }; -- cgit v1.2.3 From ecf8da00c53b20085cc32e84030caf32e8b3e16b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 17 Jul 2018 13:18:13 -0400 Subject: self-hosted: linking --- src-self-hosted/codegen.zig | 8 + src-self-hosted/compilation.zig | 39 +++-- src-self-hosted/link.zig | 314 ++++++++++++++++++++++++++++++++++++++++ src/zig_llvm.h | 3 + 4 files changed, 348 insertions(+), 16 deletions(-) create mode 100644 src-self-hosted/link.zig (limited to 'src-self-hosted/codegen.zig') diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 28ba2a1564..f8233bc795 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -90,6 +90,7 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) llvm.DIBuilderFinalize(dibuilder); if (comp.verbose_llvm_ir) { + std.debug.warn("raw module:\n"); llvm.DumpModule(ofile.module); } @@ -122,6 +123,13 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) } //validate_inline_fns(g); TODO fn_val.containing_object = output_path; + if (comp.verbose_llvm_ir) { + std.debug.warn("optimized module:\n"); + llvm.DumpModule(ofile.module); + } + if (comp.verbose_link) { + std.debug.warn("created {}\n", output_path.toSliceConst()); + } } pub const ObjectFile = struct { diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig index 4e7526a199..5cb7565a98 100644 --- a/src-self-hosted/compilation.zig +++ b/src-self-hosted/compilation.zig @@ -27,6 +27,7 @@ const Type = Value.Type; const Span = errmsg.Span; const codegen = @import("codegen.zig"); const Package = @import("package.zig").Package; +const link = @import("link.zig").link; /// Data that is local to the event loop. pub const EventLoopLocal = struct { @@ -238,6 +239,7 @@ pub const Compilation = struct { LinkQuotaExceeded, EnvironmentVariableNotFound, AppDataDirUnavailable, + LinkFailed, }; pub const Event = union(enum) { @@ -563,8 +565,7 @@ pub const Compilation = struct { async fn buildAsync(self: *Compilation) void { while (true) { // TODO directly awaiting async should guarantee memory allocation elision - // TODO also async before suspending should guarantee memory allocation elision - const build_result = await (async self.addRootSrc() catch unreachable); + const build_result = await (async self.compileAndLink() catch unreachable); // this makes a handy error return trace and stack trace in debug mode if (std.debug.runtime_safety) { @@ -595,7 +596,7 @@ pub const Compilation = struct { } } - async fn addRootSrc(self: *Compilation) !void { + async fn compileAndLink(self: *Compilation) !void { const root_src_path = self.root_src_path orelse @panic("TODO handle null root src path"); // TODO async/await os.path.real const root_src_real_path = os.path.real(self.gpa(), root_src_path) catch |err| { @@ -669,6 +670,17 @@ pub const Compilation = struct { } try await (async decl_group.wait() catch unreachable); try await (async self.prelink_group.wait() catch unreachable); + + const any_prelink_errors = blk: { + const compile_errors = await (async self.compile_errors.acquire() catch unreachable); + defer compile_errors.release(); + + break :blk compile_errors.value.len != 0; + }; + + if (!any_prelink_errors) { + try link(self); + } } async fn addTopLevelDecl(self: *Compilation, decl: *Decl) !void { @@ -722,11 +734,6 @@ pub const Compilation = struct { } } - pub fn link(self: *Compilation, out_file: ?[]const u8) !void { - warn("TODO link"); - return error.Todo; - } - pub fn haveLibC(self: *Compilation) bool { return self.libc_link_lib != null; } @@ -882,9 +889,8 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { var symbol_name = try std.Buffer.init(comp.gpa(), fn_decl.base.name); errdefer symbol_name.deinit(); + // The Decl.Fn owns the initial 1 reference count const fn_val = try Value.Fn.create(comp, fn_type, fndef_scope, symbol_name); - defer fn_val.base.deref(comp); - fn_decl.value = Decl.Fn.Val{ .Ok = fn_val }; const unanalyzed_code = (await (async ir.gen( @@ -948,22 +954,23 @@ fn getZigDir(allocator: *mem.Allocator) ![]u8 { return getAppDataDir(allocator, "zig"); } - const GetAppDataDirError = error{ OutOfMemory, AppDataDirUnavailable, }; - /// Caller owns returned memory. /// TODO move to zig std lib fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 { switch (builtin.os) { builtin.Os.windows => { var dir_path_ptr: [*]u16 = undefined; - switch (os.windows.SHGetKnownFolderPath(&os.windows.FOLDERID_LocalAppData, os.windows.KF_FLAG_CREATE, - null, &dir_path_ptr,)) - { + switch (os.windows.SHGetKnownFolderPath( + &os.windows.FOLDERID_LocalAppData, + os.windows.KF_FLAG_CREATE, + null, + &dir_path_ptr, + )) { os.windows.S_OK => { defer os.windows.CoTaskMemFree(@ptrCast(*c_void, dir_path_ptr)); const global_dir = try utf16leToUtf8(allocator, utf16lePtrSlice(dir_path_ptr)); @@ -974,7 +981,7 @@ fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataDirEr else => return error.AppDataDirUnavailable, } }, - // TODO for macos it should be "~/Library/Application Support/" + // TODO for macos it should be "~/Library/Application Support/" else => { const home_dir = os.getEnvVarOwned(allocator, "HOME") catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig new file mode 100644 index 0000000000..915451c931 --- /dev/null +++ b/src-self-hosted/link.zig @@ -0,0 +1,314 @@ +const std = @import("std"); +const c = @import("c.zig"); +const builtin = @import("builtin"); +const ObjectFormat = builtin.ObjectFormat; +const Compilation = @import("compilation.zig").Compilation; + +const Context = struct { + comp: *Compilation, + arena: std.heap.ArenaAllocator, + args: std.ArrayList([*]const u8), + link_in_crt: bool, + + link_err: error{OutOfMemory}!void, + link_msg: std.Buffer, +}; + +pub fn link(comp: *Compilation) !void { + var ctx = Context{ + .comp = comp, + .arena = std.heap.ArenaAllocator.init(comp.gpa()), + .args = undefined, + .link_in_crt = comp.haveLibC() and comp.kind == Compilation.Kind.Exe, + .link_err = {}, + .link_msg = undefined, + }; + defer ctx.arena.deinit(); + ctx.args = std.ArrayList([*]const u8).init(&ctx.arena.allocator); + ctx.link_msg = std.Buffer.initNull(&ctx.arena.allocator); + + // even though we're calling LLD as a library it thinks the first + // argument is its own exe name + try ctx.args.append(c"lld"); + + try constructLinkerArgs(&ctx); + + if (comp.verbose_link) { + for (ctx.args.toSliceConst()) |arg, i| { + const space = if (i == 0) "" else " "; + std.debug.warn("{}{s}", space, arg); + } + std.debug.warn("\n"); + } + + const extern_ofmt = toExternObjectFormatType(comp.target.getObjectFormat()); + const args_slice = ctx.args.toSlice(); + if (!ZigLLDLink(extern_ofmt, args_slice.ptr, args_slice.len, linkDiagCallback, @ptrCast(*c_void, &ctx))) { + if (!ctx.link_msg.isNull()) { + // TODO capture these messages and pass them through the system, reporting them through the + // event system instead of printing them directly here. + // perhaps try to parse and understand them. + std.debug.warn("{}\n", ctx.link_msg.toSliceConst()); + } + return error.LinkFailed; + } +} + +extern fn ZigLLDLink( + oformat: c.ZigLLVM_ObjectFormatType, + args: [*]const [*]const u8, + arg_count: usize, + append_diagnostic: extern fn (*c_void, [*]const u8, usize) void, + context: *c_void, +) bool; + +extern fn linkDiagCallback(context: *c_void, ptr: [*]const u8, len: usize) void { + const ctx = @ptrCast(*Context, @alignCast(@alignOf(Context), context)); + ctx.link_err = linkDiagCallbackErrorable(ctx, ptr[0..len]); +} + +fn linkDiagCallbackErrorable(ctx: *Context, msg: []const u8) !void { + if (ctx.link_msg.isNull()) { + try ctx.link_msg.resize(0); + } + try ctx.link_msg.append(msg); +} + +fn toExternObjectFormatType(ofmt: ObjectFormat) c.ZigLLVM_ObjectFormatType { + return switch (ofmt) { + ObjectFormat.unknown => c.ZigLLVM_UnknownObjectFormat, + ObjectFormat.coff => c.ZigLLVM_COFF, + ObjectFormat.elf => c.ZigLLVM_ELF, + ObjectFormat.macho => c.ZigLLVM_MachO, + ObjectFormat.wasm => c.ZigLLVM_Wasm, + }; +} + +fn constructLinkerArgs(ctx: *Context) !void { + switch (ctx.comp.target.getObjectFormat()) { + ObjectFormat.unknown => unreachable, + ObjectFormat.coff => return constructLinkerArgsCoff(ctx), + ObjectFormat.elf => return constructLinkerArgsElf(ctx), + ObjectFormat.macho => return constructLinkerArgsMachO(ctx), + ObjectFormat.wasm => return constructLinkerArgsWasm(ctx), + } +} + +fn constructLinkerArgsElf(ctx: *Context) !void { + //if (g->libc_link_lib != nullptr) { + // find_libc_lib_path(g); + //} + + //if (g->linker_script) { + // lj->args.append("-T"); + // lj->args.append(g->linker_script); + //} + + //if (g->no_rosegment_workaround) { + // lj->args.append("--no-rosegment"); + //} + //lj->args.append("--gc-sections"); + + //lj->args.append("-m"); + //lj->args.append(getLDMOption(&g->zig_target)); + + //bool is_lib = g->out_type == OutTypeLib; + //bool shared = !g->is_static && is_lib; + //Buf *soname = nullptr; + //if (g->is_static) { + // if (g->zig_target.arch.arch == ZigLLVM_arm || g->zig_target.arch.arch == ZigLLVM_armeb || + // g->zig_target.arch.arch == ZigLLVM_thumb || g->zig_target.arch.arch == ZigLLVM_thumbeb) + // { + // lj->args.append("-Bstatic"); + // } else { + // lj->args.append("-static"); + // } + //} 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 "", + // 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)); + + //if (lj->link_in_crt) { + // const char *crt1o; + // const char *crtbegino; + // if (g->is_static) { + // crt1o = "crt1.o"; + // crtbegino = "crtbeginT.o"; + // } else { + // crt1o = "Scrt1.o"; + // crtbegino = "crtbegin.o"; + // } + // lj->args.append(get_libc_file(g, crt1o)); + // lj->args.append(get_libc_file(g, "crti.o")); + // lj->args.append(get_libc_static_file(g, crtbegino)); + //} + + //for (size_t i = 0; i < g->rpath_list.length; i += 1) { + // Buf *rpath = g->rpath_list.at(i); + // add_rpath(lj, rpath); + //} + //if (g->each_lib_rpath) { + // for (size_t i = 0; i < g->lib_dirs.length; i += 1) { + // const char *lib_dir = g->lib_dirs.at(i); + // for (size_t i = 0; i < g->link_libs_list.length; i += 1) { + // LinkLib *link_lib = g->link_libs_list.at(i); + // if (buf_eql_str(link_lib->name, "c")) { + // continue; + // } + // bool does_exist; + // Buf *test_path = buf_sprintf("%s/lib%s.so", lib_dir, buf_ptr(link_lib->name)); + // if (os_file_exists(test_path, &does_exist) != ErrorNone) { + // zig_panic("link: unable to check if file exists: %s", buf_ptr(test_path)); + // } + // if (does_exist) { + // add_rpath(lj, buf_create_from_str(lib_dir)); + // break; + // } + // } + // } + //} + + //for (size_t i = 0; i < g->lib_dirs.length; i += 1) { + // const char *lib_dir = g->lib_dirs.at(i); + // lj->args.append("-L"); + // lj->args.append(lib_dir); + //} + + //if (g->libc_link_lib != nullptr) { + // lj->args.append("-L"); + // lj->args.append(buf_ptr(g->libc_lib_dir)); + + // lj->args.append("-L"); + // lj->args.append(buf_ptr(g->libc_static_lib_dir)); + //} + + //if (!g->is_static) { + // if (g->dynamic_linker != nullptr) { + // assert(buf_len(g->dynamic_linker) != 0); + // lj->args.append("-dynamic-linker"); + // lj->args.append(buf_ptr(g->dynamic_linker)); + // } else { + // Buf *resolved_dynamic_linker = get_dynamic_linker_path(g); + // lj->args.append("-dynamic-linker"); + // lj->args.append(buf_ptr(resolved_dynamic_linker)); + // } + //} + + //if (shared) { + // lj->args.append("-soname"); + // lj->args.append(buf_ptr(soname)); + //} + + // .o files + for (ctx.comp.link_objects) |link_object| { + const link_obj_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, link_object); + try ctx.args.append(link_obj_with_null.ptr); + } + try addFnObjects(ctx); + + //if (g->out_type == OutTypeExe || g->out_type == OutTypeLib) { + // if (g->libc_link_lib == nullptr) { + // Buf *builtin_o_path = build_o(g, "builtin"); + // lj->args.append(buf_ptr(builtin_o_path)); + // } + + // // sometimes libgcc is missing stuff, so we still build compiler_rt and rely on weak linkage + // Buf *compiler_rt_o_path = build_compiler_rt(g); + // lj->args.append(buf_ptr(compiler_rt_o_path)); + //} + + //for (size_t i = 0; i < g->link_libs_list.length; i += 1) { + // LinkLib *link_lib = g->link_libs_list.at(i); + // if (buf_eql_str(link_lib->name, "c")) { + // continue; + // } + // Buf *arg; + // if (buf_starts_with_str(link_lib->name, "/") || buf_ends_with_str(link_lib->name, ".a") || + // buf_ends_with_str(link_lib->name, ".so")) + // { + // arg = link_lib->name; + // } else { + // arg = buf_sprintf("-l%s", buf_ptr(link_lib->name)); + // } + // lj->args.append(buf_ptr(arg)); + //} + + //// libc dep + //if (g->libc_link_lib != nullptr) { + // if (g->is_static) { + // lj->args.append("--start-group"); + // lj->args.append("-lgcc"); + // lj->args.append("-lgcc_eh"); + // lj->args.append("-lc"); + // lj->args.append("-lm"); + // lj->args.append("--end-group"); + // } else { + // lj->args.append("-lgcc"); + // lj->args.append("--as-needed"); + // lj->args.append("-lgcc_s"); + // lj->args.append("--no-as-needed"); + // lj->args.append("-lc"); + // lj->args.append("-lm"); + // lj->args.append("-lgcc"); + // lj->args.append("--as-needed"); + // lj->args.append("-lgcc_s"); + // lj->args.append("--no-as-needed"); + // } + //} + + //// crt end + //if (lj->link_in_crt) { + // lj->args.append(get_libc_static_file(g, "crtend.o")); + // lj->args.append(get_libc_file(g, "crtn.o")); + //} + + //if (!g->is_native_target) { + // lj->args.append("--allow-shlib-undefined"); + //} + + //if (g->zig_target.os == OsZen) { + // lj->args.append("-e"); + // lj->args.append("_start"); + + // lj->args.append("--image-base=0x10000000"); + //} +} + +fn constructLinkerArgsCoff(ctx: *Context) void { + @panic("TODO"); +} + +fn constructLinkerArgsMachO(ctx: *Context) void { + @panic("TODO"); +} + +fn constructLinkerArgsWasm(ctx: *Context) void { + @panic("TODO"); +} + +fn addFnObjects(ctx: *Context) !void { + // at this point it's guaranteed nobody else has this lock, so we circumvent it + // and avoid having to be a coroutine + const fn_link_set = &ctx.comp.fn_link_set.private_data; + + var it = fn_link_set.first; + while (it) |node| { + const fn_val = node.data orelse { + // handle the tombstone. See Value.Fn.destroy. + it = node.next; + fn_link_set.remove(node); + ctx.comp.gpa().destroy(node); + continue; + }; + try ctx.args.append(fn_val.containing_object.ptr()); + it = node.next; + } +} diff --git a/src/zig_llvm.h b/src/zig_llvm.h index 6f25df8674..63d69bd23e 100644 --- a/src/zig_llvm.h +++ b/src/zig_llvm.h @@ -22,6 +22,9 @@ #define ZIG_EXTERN_C #endif +// ATTENTION: If you modify this file, be sure to update the corresponding +// extern function declarations in the self-hosted compiler. + struct ZigLLVMDIType; struct ZigLLVMDIBuilder; struct ZigLLVMDICompileUnit; -- cgit v1.2.3 From aa3b41247f297b4fd8b3bdb7920cb479f5aa004b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 18 Jul 2018 00:34:42 -0400 Subject: self-hosted: linking against libc also introduce `zig libc` command to display paths `zig libc file.txt` will parse equivalent text and use that for libc paths. --- src-self-hosted/codegen.zig | 2 +- src-self-hosted/compilation.zig | 51 ++++-- src-self-hosted/libc_installation.zig | 222 ++++++++++++++++++++---- src-self-hosted/link.zig | 197 ++++++++++++--------- src-self-hosted/main.zig | 87 ++++++---- src-self-hosted/target.zig | 316 +++++++++++++++++++++++++++++++++- std/event/group.zig | 4 +- std/event/loop.zig | 2 +- 8 files changed, 709 insertions(+), 172 deletions(-) (limited to 'src-self-hosted/codegen.zig') diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index f8233bc795..8faef5f31c 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -15,7 +15,7 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) defer fn_val.base.deref(comp); defer code.destroy(comp.gpa()); - var output_path = try await (async comp.createRandomOutputPath(comp.target.oFileExt()) catch unreachable); + var output_path = try await (async comp.createRandomOutputPath(comp.target.objFileExt()) catch unreachable); errdefer output_path.deinit(); const llvm_handle = try comp.event_loop_local.getAnyLlvmContext(); diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig index 1eb4339bbb..57809c8e4c 100644 --- a/src-self-hosted/compilation.zig +++ b/src-self-hosted/compilation.zig @@ -120,7 +120,6 @@ pub const Compilation = struct { linker_script: ?[]const u8, cache_dir: []const u8, - dynamic_linker: ?[]const u8, out_h_path: ?[]const u8, is_test: bool, @@ -201,6 +200,13 @@ pub const Compilation = struct { root_package: *Package, std_package: *Package, + override_libc: ?*LibCInstallation, + + /// need to wait on this group before deinitializing + deinit_group: event.Group(void), + + destroy_handle: promise, + const CompileErrList = std.ArrayList(*errmsg.Msg); // TODO handle some of these earlier and report them in a way other than error codes @@ -246,6 +252,8 @@ pub const Compilation = struct { EnvironmentVariableNotFound, AppDataDirUnavailable, LinkFailed, + LibCRequiredButNotProvidedOrFound, + LibCMissingDynamicLinker, }; pub const Event = union(enum) { @@ -324,7 +332,6 @@ pub const Compilation = struct { .verbose_link = false, .linker_script = null, - .dynamic_linker = null, .out_h_path = null, .is_test = false, .each_lib_rpath = false, @@ -351,6 +358,7 @@ pub const Compilation = struct { .link_out_file = null, .exported_symbol_names = event.Locked(Decl.Table).init(loop, Decl.Table.init(loop.allocator)), .prelink_group = event.Group(BuildError!void).init(loop), + .deinit_group = event.Group(void).init(loop), .compile_errors = event.Locked(CompileErrList).init(loop, CompileErrList.init(loop.allocator)), .meta_type = undefined, @@ -368,6 +376,9 @@ pub const Compilation = struct { .root_package = undefined, .std_package = undefined, + + .override_libc = null, + .destroy_handle = undefined, }); errdefer { comp.arena_allocator.deinit(); @@ -431,6 +442,9 @@ pub const Compilation = struct { } try comp.initTypes(); + errdefer comp.derefTypes(); + + comp.destroy_handle = try async comp.internalDeinit(); return comp; } @@ -526,11 +540,7 @@ pub const Compilation = struct { errdefer comp.gpa().destroy(comp.noreturn_value); } - pub fn destroy(self: *Compilation) void { - if (self.tmp_dir.getOrNull()) |tmp_dir_result| if (tmp_dir_result.*) |tmp_dir| { - os.deleteTree(self.arena(), tmp_dir) catch {}; - } else |_| {}; - + fn derefTypes(self: *Compilation) void { self.noreturn_value.base.deref(self); self.void_value.base.deref(self); self.false_value.base.deref(self); @@ -538,6 +548,17 @@ pub const Compilation = struct { self.noreturn_type.base.base.deref(self); self.void_type.base.base.deref(self); self.meta_type.base.base.deref(self); + } + + async fn internalDeinit(self: *Compilation) void { + suspend; + await (async self.deinit_group.wait() catch unreachable); + if (self.tmp_dir.getOrNull()) |tmp_dir_result| if (tmp_dir_result.*) |tmp_dir| { + // TODO evented I/O? + os.deleteTree(self.arena(), tmp_dir) catch {}; + } else |_| {}; + + self.derefTypes(); self.events.destroy(); @@ -549,6 +570,10 @@ pub const Compilation = struct { self.gpa().destroy(self); } + pub fn destroy(self: *Compilation) void { + resume self.destroy_handle; + } + pub fn build(self: *Compilation) !void { if (self.llvm_argv.len != 0) { var c_compatible_args = try std.cstr.NullTerminated2DArray.fromSlices(self.arena(), [][]const []const u8{ @@ -680,7 +705,7 @@ pub const Compilation = struct { }; if (!any_prelink_errors) { - try link(self); + try await (async link(self) catch unreachable); } } @@ -765,8 +790,8 @@ pub const Compilation = struct { self.libc_link_lib = link_lib; // get a head start on looking for the native libc - if (self.target == Target.Native) { - try async self.startFindingNativeLibC(); + if (self.target == Target.Native and self.override_libc == null) { + try self.deinit_group.call(startFindingNativeLibC, self); } } return link_lib; @@ -774,11 +799,9 @@ pub const Compilation = struct { /// cancels itself so no need to await or cancel the promise. async fn startFindingNativeLibC(self: *Compilation) void { + await (async self.loop.yield() catch unreachable); // we don't care if it fails, we're just trying to kick off the future resolution - _ = (await (async self.loop.call(EventLoopLocal.getNativeLibC, self.event_loop_local) catch unreachable)) catch {}; - suspend |p| { - cancel p; - } + _ = (await (async self.event_loop_local.getNativeLibC() catch unreachable)) catch return; } /// General Purpose Allocator. Must free when done. diff --git a/src-self-hosted/libc_installation.zig b/src-self-hosted/libc_installation.zig index 5606a467e9..8444c47310 100644 --- a/src-self-hosted/libc_installation.zig +++ b/src-self-hosted/libc_installation.zig @@ -1,31 +1,18 @@ const std = @import("std"); const builtin = @import("builtin"); const event = std.event; +const Target = @import("target.zig").Target; +/// See the render function implementation for documentation of the fields. pub const LibCInstallation = struct { - /// The directory that contains `stdlib.h`. - /// On Linux, can be found with: `cc -E -Wp,-v -xc /dev/null` include_dir: []const u8, - - /// The directory that contains `crt1.o`. - /// On Linux, can be found with `cc -print-file-name=crt1.o`. - /// Not needed when targeting MacOS. lib_dir: ?[]const u8, - - /// The directory that contains `crtbegin.o`. - /// On Linux, can be found with `cc -print-file-name=crt1.o`. - /// Not needed when targeting MacOS or Windows. static_lib_dir: ?[]const u8, - - /// The directory that contains `vcruntime.lib`. - /// Only needed when targeting Windows. msvc_lib_dir: ?[]const u8, - - /// The directory that contains `kernel32.lib`. - /// Only needed when targeting Windows. kernel32_lib_dir: ?[]const u8, + dynamic_linker_path: ?[]const u8, - pub const Error = error{ + pub const FindError = error{ OutOfMemory, FileSystem, UnableToSpawnCCompiler, @@ -36,16 +23,124 @@ pub const LibCInstallation = struct { LibCStdLibHeaderNotFound, }; + pub fn parse( + self: *LibCInstallation, + allocator: *std.mem.Allocator, + libc_file: []const u8, + stderr: *std.io.OutStream(std.io.FileOutStream.Error), + ) !void { + self.initEmpty(); + + const keys = []const []const u8{ + "include_dir", + "lib_dir", + "static_lib_dir", + "msvc_lib_dir", + "kernel32_lib_dir", + "dynamic_linker_path", + }; + const FoundKey = struct { + found: bool, + allocated: ?[]u8, + }; + var found_keys = [1]FoundKey{FoundKey{ .found = false, .allocated = null }} ** keys.len; + errdefer { + self.initEmpty(); + for (found_keys) |found_key| { + if (found_key.allocated) |s| allocator.free(s); + } + } + + const contents = try std.io.readFileAlloc(allocator, libc_file); + defer allocator.free(contents); + + var it = std.mem.split(contents, "\n"); + while (it.next()) |line| { + if (line.len == 0 or line[0] == '#') continue; + var line_it = std.mem.split(line, "="); + const name = line_it.next() orelse { + try stderr.print("missing equal sign after field name\n"); + return error.ParseError; + }; + const value = line_it.rest(); + inline for (keys) |key, i| { + if (std.mem.eql(u8, name, key)) { + found_keys[i].found = true; + switch (@typeInfo(@typeOf(@field(self, key)))) { + builtin.TypeId.Optional => { + if (value.len == 0) { + @field(self, key) = null; + } else { + found_keys[i].allocated = try std.mem.dupe(allocator, u8, value); + @field(self, key) = found_keys[i].allocated; + } + }, + else => { + if (value.len == 0) { + try stderr.print("field cannot be empty: {}\n", key); + return error.ParseError; + } + const dupe = try std.mem.dupe(allocator, u8, value); + found_keys[i].allocated = dupe; + @field(self, key) = dupe; + }, + } + break; + } + } + } + for (found_keys) |found_key, i| { + if (!found_key.found) { + try stderr.print("missing field: {}\n", keys[i]); + return error.ParseError; + } + } + } + + pub fn render(self: *const LibCInstallation, out: *std.io.OutStream(std.io.FileOutStream.Error)) !void { + @setEvalBranchQuota(4000); + try out.print( + \\# The directory that contains `stdlib.h`. + \\# On Linux, can be found with: `cc -E -Wp,-v -xc /dev/null` + \\include_dir={} + \\ + \\# The directory that contains `crt1.o`. + \\# On Linux, can be found with `cc -print-file-name=crt1.o`. + \\# Not needed when targeting MacOS. + \\lib_dir={} + \\ + \\# The directory that contains `crtbegin.o`. + \\# On Linux, can be found with `cc -print-file-name=crt1.o`. + \\# Not needed when targeting MacOS or Windows. + \\static_lib_dir={} + \\ + \\# The directory that contains `vcruntime.lib`. + \\# Only needed when targeting Windows. + \\msvc_lib_dir={} + \\ + \\# The directory that contains `kernel32.lib`. + \\# Only needed when targeting Windows. + \\kernel32_lib_dir={} + \\ + \\# The full path to the dynamic linker. + \\# Only needed when targeting Linux. + \\dynamic_linker_path={} + \\ + , + self.include_dir, + self.lib_dir orelse "", + self.static_lib_dir orelse "", + self.msvc_lib_dir orelse "", + self.kernel32_lib_dir orelse "", + self.dynamic_linker_path orelse Target(Target.Native).getDynamicLinkerPath(), + ); + } + /// Finds the default, native libc. pub async fn findNative(self: *LibCInstallation, loop: *event.Loop) !void { - self.* = LibCInstallation{ - .lib_dir = null, - .include_dir = ([*]const u8)(undefined)[0..0], - .static_lib_dir = null, - .msvc_lib_dir = null, - .kernel32_lib_dir = null, - }; - var group = event.Group(Error!void).init(loop); + self.initEmpty(); + var group = event.Group(FindError!void).init(loop); + errdefer group.cancelAll(); switch (builtin.os) { builtin.Os.windows => { try group.call(findNativeIncludeDirWindows, self, loop); @@ -57,6 +152,7 @@ pub const LibCInstallation = struct { try group.call(findNativeIncludeDirLinux, self, loop); try group.call(findNativeLibDirLinux, self, loop); try group.call(findNativeStaticLibDir, self, loop); + try group.call(findNativeDynamicLinker, self, loop); }, builtin.Os.macosx => { try group.call(findNativeIncludeDirMacOS, self, loop); @@ -147,7 +243,7 @@ pub const LibCInstallation = struct { self.include_dir = try std.mem.dupe(loop.allocator, u8, "/usr/include"); } - async fn findNativeLibDirWindows(self: *LibCInstallation, loop: *event.Loop) Error!void { + async fn findNativeLibDirWindows(self: *LibCInstallation, loop: *event.Loop) FindError!void { // TODO //ZigWindowsSDK *sdk = get_windows_sdk(g); @@ -180,31 +276,83 @@ pub const LibCInstallation = struct { @panic("TODO"); } - async fn findNativeLibDirLinux(self: *LibCInstallation, loop: *event.Loop) Error!void { - self.lib_dir = try await (async ccPrintFileNameDir(loop, "crt1.o") catch unreachable); + async fn findNativeLibDirLinux(self: *LibCInstallation, loop: *event.Loop) FindError!void { + self.lib_dir = try await (async ccPrintFileName(loop, "crt1.o", true) catch unreachable); + } + + async fn findNativeStaticLibDir(self: *LibCInstallation, loop: *event.Loop) FindError!void { + self.static_lib_dir = try await (async ccPrintFileName(loop, "crtbegin.o", true) catch unreachable); + } + + async fn findNativeDynamicLinker(self: *LibCInstallation, loop: *event.Loop) FindError!void { + var dyn_tests = []DynTest{ + DynTest{ + .name = "ld-linux-x86-64.so.2", + .result = null, + }, + DynTest{ + .name = "ld-musl-x86_64.so.1", + .result = null, + }, + }; + var group = event.Group(FindError!void).init(loop); + errdefer group.cancelAll(); + for (dyn_tests) |*dyn_test| { + try group.call(testNativeDynamicLinker, self, loop, dyn_test); + } + try await (async group.wait() catch unreachable); + for (dyn_tests) |*dyn_test| { + if (dyn_test.result) |result| { + self.dynamic_linker_path = result; + return; + } + } } - async fn findNativeStaticLibDir(self: *LibCInstallation, loop: *event.Loop) Error!void { - self.static_lib_dir = try await (async ccPrintFileNameDir(loop, "crtbegin.o") catch unreachable); + const DynTest = struct { + name: []const u8, + result: ?[]const u8, + }; + + async fn testNativeDynamicLinker(self: *LibCInstallation, loop: *event.Loop, dyn_test: *DynTest) FindError!void { + if (await (async ccPrintFileName(loop, dyn_test.name, false) catch unreachable)) |result| { + dyn_test.result = result; + return; + } else |err| switch (err) { + error.CCompilerCannotFindCRuntime => return, + else => return err, + } } - async fn findNativeMsvcLibDir(self: *LibCInstallation, loop: *event.Loop) Error!void { + async fn findNativeMsvcLibDir(self: *LibCInstallation, loop: *event.Loop) FindError!void { @panic("TODO"); } - async fn findNativeKernel32LibDir(self: *LibCInstallation, loop: *event.Loop) Error!void { + async fn findNativeKernel32LibDir(self: *LibCInstallation, loop: *event.Loop) FindError!void { @panic("TODO"); } + + fn initEmpty(self: *LibCInstallation) void { + self.* = LibCInstallation{ + .include_dir = ([*]const u8)(undefined)[0..0], + .lib_dir = null, + .static_lib_dir = null, + .msvc_lib_dir = null, + .kernel32_lib_dir = null, + .dynamic_linker_path = null, + }; + } }; /// caller owns returned memory -async fn ccPrintFileNameDir(loop: *event.Loop, o_file: []const u8) ![]u8 { +async fn ccPrintFileName(loop: *event.Loop, o_file: []const u8, want_dirname: bool) ![]u8 { const cc_exe = std.os.getEnvPosix("CC") orelse "cc"; const arg1 = try std.fmt.allocPrint(loop.allocator, "-print-file-name={}", o_file); defer loop.allocator.free(arg1); const argv = []const []const u8{ cc_exe, arg1 }; - // TODO evented I/O + // TODO This simulates evented I/O for the child process exec + await (async loop.yield() catch unreachable); const errorable_result = std.os.ChildProcess.exec(loop.allocator, argv, null, null, 1024 * 1024); const exec_result = if (std.debug.runtime_safety) blk: { break :blk errorable_result catch unreachable; @@ -230,5 +378,9 @@ async fn ccPrintFileNameDir(loop: *event.Loop, o_file: []const u8) ![]u8 { const line = it.next() orelse return error.CCompilerCannotFindCRuntime; const dirname = std.os.path.dirname(line) orelse return error.CCompilerCannotFindCRuntime; - return std.mem.dupe(loop.allocator, u8, dirname); + if (want_dirname) { + return std.mem.dupe(loop.allocator, u8, dirname); + } else { + return std.mem.dupe(loop.allocator, u8, line); + } } diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 915451c931..16d9939fff 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -3,6 +3,8 @@ const c = @import("c.zig"); const builtin = @import("builtin"); const ObjectFormat = builtin.ObjectFormat; const Compilation = @import("compilation.zig").Compilation; +const Target = @import("target.zig").Target; +const LibCInstallation = @import("libc_installation.zig").LibCInstallation; const Context = struct { comp: *Compilation, @@ -12,9 +14,12 @@ const Context = struct { link_err: error{OutOfMemory}!void, link_msg: std.Buffer, + + libc: *LibCInstallation, + out_file_path: std.Buffer, }; -pub fn link(comp: *Compilation) !void { +pub async fn link(comp: *Compilation) !void { var ctx = Context{ .comp = comp, .arena = std.heap.ArenaAllocator.init(comp.gpa()), @@ -22,15 +27,45 @@ pub fn link(comp: *Compilation) !void { .link_in_crt = comp.haveLibC() and comp.kind == Compilation.Kind.Exe, .link_err = {}, .link_msg = undefined, + .libc = undefined, + .out_file_path = undefined, }; defer ctx.arena.deinit(); ctx.args = std.ArrayList([*]const u8).init(&ctx.arena.allocator); ctx.link_msg = std.Buffer.initNull(&ctx.arena.allocator); + if (comp.link_out_file) |out_file| { + ctx.out_file_path = try std.Buffer.init(&ctx.arena.allocator, out_file); + } else { + ctx.out_file_path = try std.Buffer.init(&ctx.arena.allocator, comp.name.toSliceConst()); + switch (comp.kind) { + Compilation.Kind.Exe => { + try ctx.out_file_path.append(comp.target.exeFileExt()); + }, + Compilation.Kind.Lib => { + try ctx.out_file_path.append(comp.target.libFileExt(comp.is_static)); + }, + Compilation.Kind.Obj => { + try ctx.out_file_path.append(comp.target.objFileExt()); + }, + } + } + // even though we're calling LLD as a library it thinks the first // argument is its own exe name try ctx.args.append(c"lld"); + if (comp.haveLibC()) { + ctx.libc = ctx.comp.override_libc orelse blk: { + switch (comp.target) { + Target.Native => { + break :blk (await (async comp.event_loop_local.getNativeLibC() catch unreachable)) catch return error.LibCRequiredButNotProvidedOrFound; + }, + else => return error.LibCRequiredButNotProvidedOrFound, + } + }; + } + try constructLinkerArgs(&ctx); if (comp.verbose_link) { @@ -43,6 +78,7 @@ pub fn link(comp: *Compilation) !void { const extern_ofmt = toExternObjectFormatType(comp.target.getObjectFormat()); const args_slice = ctx.args.toSlice(); + // Not evented I/O. LLD does its own multithreading internally. if (!ZigLLDLink(extern_ofmt, args_slice.ptr, args_slice.len, linkDiagCallback, @ptrCast(*c_void, &ctx))) { if (!ctx.link_msg.isNull()) { // TODO capture these messages and pass them through the system, reporting them through the @@ -95,10 +131,7 @@ fn constructLinkerArgs(ctx: *Context) !void { } fn constructLinkerArgsElf(ctx: *Context) !void { - //if (g->libc_link_lib != nullptr) { - // find_libc_lib_path(g); - //} - + // TODO commented out code in this function //if (g->linker_script) { // lj->args.append("-T"); // lj->args.append(g->linker_script); @@ -107,7 +140,7 @@ fn constructLinkerArgsElf(ctx: *Context) !void { //if (g->no_rosegment_workaround) { // lj->args.append("--no-rosegment"); //} - //lj->args.append("--gc-sections"); + try ctx.args.append(c"--gc-sections"); //lj->args.append("-m"); //lj->args.append(getLDMOption(&g->zig_target)); @@ -115,14 +148,13 @@ fn constructLinkerArgsElf(ctx: *Context) !void { //bool is_lib = g->out_type == OutTypeLib; //bool shared = !g->is_static && is_lib; //Buf *soname = nullptr; - //if (g->is_static) { - // if (g->zig_target.arch.arch == ZigLLVM_arm || g->zig_target.arch.arch == ZigLLVM_armeb || - // g->zig_target.arch.arch == ZigLLVM_thumb || g->zig_target.arch.arch == ZigLLVM_thumbeb) - // { - // lj->args.append("-Bstatic"); - // } else { - // lj->args.append("-static"); - // } + if (ctx.comp.is_static) { + if (ctx.comp.target.isArmOrThumb()) { + try ctx.args.append(c"-Bstatic"); + } else { + try ctx.args.append(c"-static"); + } + } //} else if (shared) { // lj->args.append("-shared"); @@ -133,23 +165,16 @@ fn constructLinkerArgsElf(ctx: *Context) !void { // 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)); + try ctx.args.append(c"-o"); + try ctx.args.append(ctx.out_file_path.ptr()); - //if (lj->link_in_crt) { - // const char *crt1o; - // const char *crtbegino; - // if (g->is_static) { - // crt1o = "crt1.o"; - // crtbegino = "crtbeginT.o"; - // } else { - // crt1o = "Scrt1.o"; - // crtbegino = "crtbegin.o"; - // } - // lj->args.append(get_libc_file(g, crt1o)); - // lj->args.append(get_libc_file(g, "crti.o")); - // lj->args.append(get_libc_static_file(g, crtbegino)); - //} + if (ctx.link_in_crt) { + const crt1o = if (ctx.comp.is_static) "crt1.o" else "Scrt1.o"; + const crtbegino = if (ctx.comp.is_static) "crtbeginT.o" else "crtbegin.o"; + try addPathJoin(ctx, ctx.libc.lib_dir.?, crt1o); + try addPathJoin(ctx, ctx.libc.lib_dir.?, "crti.o"); + try addPathJoin(ctx, ctx.libc.static_lib_dir.?, crtbegino); + } //for (size_t i = 0; i < g->rpath_list.length; i += 1) { // Buf *rpath = g->rpath_list.at(i); @@ -182,25 +207,23 @@ fn constructLinkerArgsElf(ctx: *Context) !void { // lj->args.append(lib_dir); //} - //if (g->libc_link_lib != nullptr) { - // lj->args.append("-L"); - // lj->args.append(buf_ptr(g->libc_lib_dir)); - - // lj->args.append("-L"); - // lj->args.append(buf_ptr(g->libc_static_lib_dir)); - //} - - //if (!g->is_static) { - // if (g->dynamic_linker != nullptr) { - // assert(buf_len(g->dynamic_linker) != 0); - // lj->args.append("-dynamic-linker"); - // lj->args.append(buf_ptr(g->dynamic_linker)); - // } else { - // Buf *resolved_dynamic_linker = get_dynamic_linker_path(g); - // lj->args.append("-dynamic-linker"); - // lj->args.append(buf_ptr(resolved_dynamic_linker)); - // } - //} + if (ctx.comp.haveLibC()) { + try ctx.args.append(c"-L"); + try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, ctx.libc.lib_dir.?)).ptr); + + try ctx.args.append(c"-L"); + try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, ctx.libc.static_lib_dir.?)).ptr); + + if (!ctx.comp.is_static) { + const dl = blk: { + if (ctx.libc.dynamic_linker_path) |dl| break :blk dl; + if (ctx.comp.target.getDynamicLinkerPath()) |dl| break :blk dl; + return error.LibCMissingDynamicLinker; + }; + try ctx.args.append(c"-dynamic-linker"); + try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, dl)).ptr); + } + } //if (shared) { // lj->args.append("-soname"); @@ -241,45 +264,51 @@ fn constructLinkerArgsElf(ctx: *Context) !void { // lj->args.append(buf_ptr(arg)); //} - //// libc dep - //if (g->libc_link_lib != nullptr) { - // if (g->is_static) { - // lj->args.append("--start-group"); - // lj->args.append("-lgcc"); - // lj->args.append("-lgcc_eh"); - // lj->args.append("-lc"); - // lj->args.append("-lm"); - // lj->args.append("--end-group"); - // } else { - // lj->args.append("-lgcc"); - // lj->args.append("--as-needed"); - // lj->args.append("-lgcc_s"); - // lj->args.append("--no-as-needed"); - // lj->args.append("-lc"); - // lj->args.append("-lm"); - // lj->args.append("-lgcc"); - // lj->args.append("--as-needed"); - // lj->args.append("-lgcc_s"); - // lj->args.append("--no-as-needed"); - // } - //} + // libc dep + if (ctx.comp.haveLibC()) { + if (ctx.comp.is_static) { + try ctx.args.append(c"--start-group"); + try ctx.args.append(c"-lgcc"); + try ctx.args.append(c"-lgcc_eh"); + try ctx.args.append(c"-lc"); + try ctx.args.append(c"-lm"); + try ctx.args.append(c"--end-group"); + } else { + try ctx.args.append(c"-lgcc"); + try ctx.args.append(c"--as-needed"); + try ctx.args.append(c"-lgcc_s"); + try ctx.args.append(c"--no-as-needed"); + try ctx.args.append(c"-lc"); + try ctx.args.append(c"-lm"); + try ctx.args.append(c"-lgcc"); + try ctx.args.append(c"--as-needed"); + try ctx.args.append(c"-lgcc_s"); + try ctx.args.append(c"--no-as-needed"); + } + } - //// crt end - //if (lj->link_in_crt) { - // lj->args.append(get_libc_static_file(g, "crtend.o")); - // lj->args.append(get_libc_file(g, "crtn.o")); - //} + // crt end + if (ctx.link_in_crt) { + try addPathJoin(ctx, ctx.libc.static_lib_dir.?, "crtend.o"); + try addPathJoin(ctx, ctx.libc.lib_dir.?, "crtn.o"); + } - //if (!g->is_native_target) { - // lj->args.append("--allow-shlib-undefined"); - //} + if (ctx.comp.target != Target.Native) { + try ctx.args.append(c"--allow-shlib-undefined"); + } - //if (g->zig_target.os == OsZen) { - // lj->args.append("-e"); - // lj->args.append("_start"); + if (ctx.comp.target.getOs() == builtin.Os.zen) { + try ctx.args.append(c"-e"); + try ctx.args.append(c"_start"); - // lj->args.append("--image-base=0x10000000"); - //} + try ctx.args.append(c"--image-base=0x10000000"); + } +} + +fn addPathJoin(ctx: *Context, dirname: []const u8, basename: []const u8) !void { + const full_path = try std.os.path.join(&ctx.arena.allocator, dirname, basename); + const full_path_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, full_path); + try ctx.args.append(full_path_with_null.ptr); } fn constructLinkerArgsCoff(ctx: *Context) void { diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index ff24677b6d..5c27e5f57e 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -18,6 +18,7 @@ const EventLoopLocal = @import("compilation.zig").EventLoopLocal; const Compilation = @import("compilation.zig").Compilation; const Target = @import("target.zig").Target; const errmsg = @import("errmsg.zig"); +const LibCInstallation = @import("libc_installation.zig").LibCInstallation; var stderr_file: os.File = undefined; var stderr: *io.OutStream(io.FileOutStream.Error) = undefined; @@ -28,14 +29,14 @@ const usage = \\ \\Commands: \\ - \\ build-exe [source] Create executable from source or object files - \\ build-lib [source] Create library from source or object files - \\ build-obj [source] Create object from source or assembly - \\ find-libc Show native libc installation paths - \\ fmt [source] Parse file and render in canonical zig format - \\ targets List available compilation targets - \\ version Print version number and exit - \\ zen Print zen of zig and exit + \\ build-exe [source] Create executable from source or object files + \\ build-lib [source] Create library from source or object files + \\ build-obj [source] Create object from source or assembly + \\ fmt [source] Parse file and render in canonical zig format + \\ libc [paths_file] Display native libc paths file or validate one + \\ targets List available compilation targets + \\ version Print version number and exit + \\ zen Print zen of zig and exit \\ \\ ; @@ -82,14 +83,14 @@ pub fn main() !void { .name = "build-obj", .exec = cmdBuildObj, }, - Command{ - .name = "find-libc", - .exec = cmdFindLibc, - }, Command{ .name = "fmt", .exec = cmdFmt, }, + Command{ + .name = "libc", + .exec = cmdLibC, + }, Command{ .name = "targets", .exec = cmdTargets, @@ -135,6 +136,7 @@ const usage_build_generic = \\ --color [auto|off|on] Enable or disable colored error messages \\ \\Compile Options: + \\ --libc [file] Provide a file which specifies libc paths \\ --assembly [source] Add assembly file to build \\ --cache-dir [path] Override the cache directory \\ --emit [filetype] Emit a specific file format as compilation output @@ -167,7 +169,6 @@ const usage_build_generic = \\ \\Link Options: \\ --ar-path [path] Set the path to ar - \\ --dynamic-linker [path] Set the path to ld.so \\ --each-lib-rpath Add rpath for each used dynamic library \\ --library [lib] Link against lib \\ --forbid-library [lib] Make it an error to link against lib @@ -210,6 +211,7 @@ const args_build_generic = []Flag{ "llvm-ir", }), Flag.Bool("--enable-timing-info"), + Flag.Arg1("--libc"), Flag.Arg1("--name"), Flag.Arg1("--output"), Flag.Arg1("--output-h"), @@ -233,7 +235,6 @@ const args_build_generic = []Flag{ Flag.Arg1("-mllvm"), Flag.Arg1("--ar-path"), - Flag.Arg1("--dynamic-linker"), Flag.Bool("--each-lib-rpath"), Flag.ArgMergeN("--library", 1), Flag.ArgMergeN("--forbid-library", 1), @@ -382,6 +383,8 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co const zig_lib_dir = introspect.resolveZigLibDir(allocator) catch os.exit(1); defer allocator.free(zig_lib_dir); + var override_libc: LibCInstallation = undefined; + var loop: event.Loop = undefined; try loop.initMultiThreaded(allocator); defer loop.deinit(); @@ -402,6 +405,15 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co ); defer comp.destroy(); + if (flags.single("libc")) |libc_path| { + parseLibcPaths(loop.allocator, &override_libc, libc_path); + comp.override_libc = &override_libc; + } + + for (flags.many("library")) |lib| { + _ = try comp.addLinkLib(lib, true); + } + comp.version_major = try std.fmt.parseUnsigned(u32, flags.single("ver-major") orelse "0", 10); comp.version_minor = try std.fmt.parseUnsigned(u32, flags.single("ver-minor") orelse "0", 10); comp.version_patch = try std.fmt.parseUnsigned(u32, flags.single("ver-patch") orelse "0", 10); @@ -425,10 +437,6 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co comp.strip = flags.present("strip"); - if (flags.single("dynamic-linker")) |dynamic_linker| { - comp.dynamic_linker = dynamic_linker; - } - comp.verbose_tokenize = flags.present("verbose-tokenize"); comp.verbose_ast_tree = flags.present("verbose-ast-tree"); comp.verbose_ast_fmt = flags.present("verbose-ast-fmt"); @@ -479,7 +487,6 @@ async fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void { switch (build_event) { Compilation.Event.Ok => { - std.debug.warn("Build succeeded\n"); return; }, Compilation.Event.Error => |err| { @@ -559,7 +566,32 @@ const Fmt = struct { } }; -fn cmdFindLibc(allocator: *Allocator, args: []const []const u8) !void { +fn parseLibcPaths(allocator: *Allocator, libc: *LibCInstallation, libc_paths_file: []const u8) void { + libc.parse(allocator, libc_paths_file, stderr) catch |err| { + stderr.print( + "Unable to parse libc path file '{}': {}.\n" ++ + "Try running `zig libc` to see an example for the native target.\n", + libc_paths_file, + @errorName(err), + ) catch os.exit(1); + os.exit(1); + }; +} + +fn cmdLibC(allocator: *Allocator, args: []const []const u8) !void { + switch (args.len) { + 0 => {}, + 1 => { + var libc_installation: LibCInstallation = undefined; + parseLibcPaths(allocator, &libc_installation, args[0]); + return; + }, + else => { + try stderr.print("unexpected extra parameter: {}\n", args[1]); + os.exit(1); + }, + } + var loop: event.Loop = undefined; try loop.initMultiThreaded(allocator); defer loop.deinit(); @@ -578,20 +610,7 @@ async fn findLibCAsync(event_loop_local: *EventLoopLocal) void { stderr.print("unable to find libc: {}\n", @errorName(err)) catch os.exit(1); os.exit(1); }; - stderr.print( - \\include_dir={} - \\lib_dir={} - \\static_lib_dir={} - \\msvc_lib_dir={} - \\kernel32_lib_dir={} - \\ - , - libc.include_dir, - libc.lib_dir, - libc.static_lib_dir orelse "", - libc.msvc_lib_dir orelse "", - libc.kernel32_lib_dir orelse "", - ) catch os.exit(1); + libc.render(stdout) catch os.exit(1); } fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { diff --git a/src-self-hosted/target.zig b/src-self-hosted/target.zig index db673e421a..5a1f43bcf9 100644 --- a/src-self-hosted/target.zig +++ b/src-self-hosted/target.zig @@ -2,6 +2,12 @@ const std = @import("std"); const builtin = @import("builtin"); const llvm = @import("llvm.zig"); +pub const FloatAbi = enum { + Hard, + Soft, + SoftFp, +}; + pub const Target = union(enum) { Native, Cross: Cross, @@ -13,7 +19,7 @@ pub const Target = union(enum) { object_format: builtin.ObjectFormat, }; - pub fn oFileExt(self: Target) []const u8 { + pub fn objFileExt(self: Target) []const u8 { return switch (self.getObjectFormat()) { builtin.ObjectFormat.coff => ".obj", else => ".o", @@ -27,6 +33,13 @@ pub const Target = union(enum) { }; } + pub fn libFileExt(self: Target, is_static: bool) []const u8 { + return switch (self.getOs()) { + builtin.Os.windows => if (is_static) ".lib" else ".dll", + else => if (is_static) ".a" else ".so", + }; + } + pub fn getOs(self: Target) builtin.Os { return switch (self) { Target.Native => builtin.os, @@ -76,6 +89,56 @@ pub const Target = union(enum) { }; } + /// TODO expose the arch and subarch separately + pub fn isArmOrThumb(self: Target) bool { + return switch (self.getArch()) { + builtin.Arch.armv8_3a, + builtin.Arch.armv8_2a, + builtin.Arch.armv8_1a, + builtin.Arch.armv8, + builtin.Arch.armv8r, + builtin.Arch.armv8m_baseline, + builtin.Arch.armv8m_mainline, + builtin.Arch.armv7, + builtin.Arch.armv7em, + builtin.Arch.armv7m, + builtin.Arch.armv7s, + builtin.Arch.armv7k, + builtin.Arch.armv7ve, + builtin.Arch.armv6, + builtin.Arch.armv6m, + builtin.Arch.armv6k, + builtin.Arch.armv6t2, + builtin.Arch.armv5, + builtin.Arch.armv5te, + builtin.Arch.armv4t, + builtin.Arch.armebv8_3a, + builtin.Arch.armebv8_2a, + builtin.Arch.armebv8_1a, + builtin.Arch.armebv8, + builtin.Arch.armebv8r, + builtin.Arch.armebv8m_baseline, + builtin.Arch.armebv8m_mainline, + builtin.Arch.armebv7, + builtin.Arch.armebv7em, + builtin.Arch.armebv7m, + builtin.Arch.armebv7s, + builtin.Arch.armebv7k, + builtin.Arch.armebv7ve, + builtin.Arch.armebv6, + builtin.Arch.armebv6m, + builtin.Arch.armebv6k, + builtin.Arch.armebv6t2, + builtin.Arch.armebv5, + builtin.Arch.armebv5te, + builtin.Arch.armebv4t, + builtin.Arch.thumb, + builtin.Arch.thumbeb, + => true, + else => false, + }; + } + pub fn initializeAll() void { llvm.InitializeAllTargets(); llvm.InitializeAllTargetInfos(); @@ -106,6 +169,257 @@ pub const Target = union(enum) { return result; } + pub fn is64bit(self: Target) bool { + return self.getArchPtrBitWidth() == 64; + } + + pub fn getArchPtrBitWidth(self: Target) u8 { + switch (self.getArch()) { + builtin.Arch.avr, + builtin.Arch.msp430, + => return 16, + + builtin.Arch.arc, + builtin.Arch.armv8_3a, + builtin.Arch.armv8_2a, + builtin.Arch.armv8_1a, + builtin.Arch.armv8, + builtin.Arch.armv8r, + builtin.Arch.armv8m_baseline, + builtin.Arch.armv8m_mainline, + builtin.Arch.armv7, + builtin.Arch.armv7em, + builtin.Arch.armv7m, + builtin.Arch.armv7s, + builtin.Arch.armv7k, + builtin.Arch.armv7ve, + builtin.Arch.armv6, + builtin.Arch.armv6m, + builtin.Arch.armv6k, + builtin.Arch.armv6t2, + builtin.Arch.armv5, + builtin.Arch.armv5te, + builtin.Arch.armv4t, + builtin.Arch.armebv8_3a, + builtin.Arch.armebv8_2a, + builtin.Arch.armebv8_1a, + builtin.Arch.armebv8, + builtin.Arch.armebv8r, + builtin.Arch.armebv8m_baseline, + builtin.Arch.armebv8m_mainline, + builtin.Arch.armebv7, + builtin.Arch.armebv7em, + builtin.Arch.armebv7m, + builtin.Arch.armebv7s, + builtin.Arch.armebv7k, + builtin.Arch.armebv7ve, + builtin.Arch.armebv6, + builtin.Arch.armebv6m, + builtin.Arch.armebv6k, + builtin.Arch.armebv6t2, + builtin.Arch.armebv5, + builtin.Arch.armebv5te, + builtin.Arch.armebv4t, + builtin.Arch.hexagon, + builtin.Arch.le32, + builtin.Arch.mips, + builtin.Arch.mipsel, + builtin.Arch.nios2, + builtin.Arch.powerpc, + builtin.Arch.r600, + builtin.Arch.riscv32, + builtin.Arch.sparc, + builtin.Arch.sparcel, + builtin.Arch.tce, + builtin.Arch.tcele, + builtin.Arch.thumb, + builtin.Arch.thumbeb, + builtin.Arch.i386, + builtin.Arch.xcore, + builtin.Arch.nvptx, + builtin.Arch.amdil, + builtin.Arch.hsail, + builtin.Arch.spir, + builtin.Arch.kalimbav3, + builtin.Arch.kalimbav4, + builtin.Arch.kalimbav5, + builtin.Arch.shave, + builtin.Arch.lanai, + builtin.Arch.wasm32, + builtin.Arch.renderscript32, + => return 32, + + builtin.Arch.aarch64, + builtin.Arch.aarch64_be, + builtin.Arch.mips64, + builtin.Arch.mips64el, + builtin.Arch.powerpc64, + builtin.Arch.powerpc64le, + builtin.Arch.riscv64, + builtin.Arch.x86_64, + builtin.Arch.nvptx64, + builtin.Arch.le64, + builtin.Arch.amdil64, + builtin.Arch.hsail64, + builtin.Arch.spir64, + builtin.Arch.wasm64, + builtin.Arch.renderscript64, + builtin.Arch.amdgcn, + builtin.Arch.bpfel, + builtin.Arch.bpfeb, + builtin.Arch.sparcv9, + builtin.Arch.s390x, + => return 64, + } + } + + pub fn getFloatAbi(self: Target) FloatAbi { + return switch (self.getEnviron()) { + builtin.Environ.gnueabihf, + builtin.Environ.eabihf, + builtin.Environ.musleabihf, + => FloatAbi.Hard, + else => FloatAbi.Soft, + }; + } + + pub fn getDynamicLinkerPath(self: Target) ?[]const u8 { + const env = self.getEnviron(); + const arch = self.getArch(); + switch (env) { + builtin.Environ.android => { + if (self.is64bit()) { + return "/system/bin/linker64"; + } else { + return "/system/bin/linker"; + } + }, + builtin.Environ.gnux32 => { + if (arch == builtin.Arch.x86_64) { + return "/libx32/ld-linux-x32.so.2"; + } + }, + builtin.Environ.musl, + builtin.Environ.musleabi, + builtin.Environ.musleabihf, + => { + if (arch == builtin.Arch.x86_64) { + return "/lib/ld-musl-x86_64.so.1"; + } + }, + else => {}, + } + switch (arch) { + builtin.Arch.i386, + builtin.Arch.sparc, + builtin.Arch.sparcel, + => return "/lib/ld-linux.so.2", + + builtin.Arch.aarch64 => return "/lib/ld-linux-aarch64.so.1", + builtin.Arch.aarch64_be => return "/lib/ld-linux-aarch64_be.so.1", + + builtin.Arch.armv8_3a, + builtin.Arch.armv8_2a, + builtin.Arch.armv8_1a, + builtin.Arch.armv8, + builtin.Arch.armv8r, + builtin.Arch.armv8m_baseline, + builtin.Arch.armv8m_mainline, + builtin.Arch.armv7, + builtin.Arch.armv7em, + builtin.Arch.armv7m, + builtin.Arch.armv7s, + builtin.Arch.armv7k, + builtin.Arch.armv7ve, + builtin.Arch.armv6, + builtin.Arch.armv6m, + builtin.Arch.armv6k, + builtin.Arch.armv6t2, + builtin.Arch.armv5, + builtin.Arch.armv5te, + builtin.Arch.armv4t, + builtin.Arch.thumb, + => return switch (self.getFloatAbi()) { + FloatAbi.Hard => return "/lib/ld-linux-armhf.so.3", + else => return "/lib/ld-linux.so.3", + }, + + builtin.Arch.armebv8_3a, + builtin.Arch.armebv8_2a, + builtin.Arch.armebv8_1a, + builtin.Arch.armebv8, + builtin.Arch.armebv8r, + builtin.Arch.armebv8m_baseline, + builtin.Arch.armebv8m_mainline, + builtin.Arch.armebv7, + builtin.Arch.armebv7em, + builtin.Arch.armebv7m, + builtin.Arch.armebv7s, + builtin.Arch.armebv7k, + builtin.Arch.armebv7ve, + builtin.Arch.armebv6, + builtin.Arch.armebv6m, + builtin.Arch.armebv6k, + builtin.Arch.armebv6t2, + builtin.Arch.armebv5, + builtin.Arch.armebv5te, + builtin.Arch.armebv4t, + builtin.Arch.thumbeb, + => return switch (self.getFloatAbi()) { + FloatAbi.Hard => return "/lib/ld-linux-armhf.so.3", + else => return "/lib/ld-linux.so.3", + }, + + builtin.Arch.mips, + builtin.Arch.mipsel, + builtin.Arch.mips64, + builtin.Arch.mips64el, + => return null, + + builtin.Arch.powerpc => return "/lib/ld.so.1", + builtin.Arch.powerpc64 => return "/lib64/ld64.so.2", + builtin.Arch.powerpc64le => return "/lib64/ld64.so.2", + builtin.Arch.s390x => return "/lib64/ld64.so.1", + builtin.Arch.sparcv9 => return "/lib64/ld-linux.so.2", + builtin.Arch.x86_64 => return "/lib64/ld-linux-x86-64.so.2", + + builtin.Arch.arc, + builtin.Arch.avr, + builtin.Arch.bpfel, + builtin.Arch.bpfeb, + builtin.Arch.hexagon, + builtin.Arch.msp430, + builtin.Arch.nios2, + builtin.Arch.r600, + builtin.Arch.amdgcn, + builtin.Arch.riscv32, + builtin.Arch.riscv64, + builtin.Arch.tce, + builtin.Arch.tcele, + builtin.Arch.xcore, + builtin.Arch.nvptx, + builtin.Arch.nvptx64, + builtin.Arch.le32, + builtin.Arch.le64, + builtin.Arch.amdil, + builtin.Arch.amdil64, + builtin.Arch.hsail, + builtin.Arch.hsail64, + builtin.Arch.spir, + builtin.Arch.spir64, + builtin.Arch.kalimbav3, + builtin.Arch.kalimbav4, + builtin.Arch.kalimbav5, + builtin.Arch.shave, + builtin.Arch.lanai, + builtin.Arch.wasm32, + builtin.Arch.wasm64, + builtin.Arch.renderscript32, + builtin.Arch.renderscript64, + => return null, + } + } + pub fn llvmTargetFromTriple(triple: std.Buffer) !llvm.TargetRef { var result: llvm.TargetRef = undefined; var err_msg: [*]u8 = undefined; diff --git a/std/event/group.zig b/std/event/group.zig index c286803b53..5ca8f5a0ca 100644 --- a/std/event/group.zig +++ b/std/event/group.zig @@ -6,7 +6,7 @@ const AtomicRmwOp = builtin.AtomicRmwOp; const AtomicOrder = builtin.AtomicOrder; const assert = std.debug.assert; -/// ReturnType should be `void` or `E!void` +/// ReturnType must be `void` or `E!void` pub fn Group(comptime ReturnType: type) type { return struct { coro_stack: Stack, @@ -39,7 +39,7 @@ pub fn Group(comptime ReturnType: type) type { } /// This is equivalent to an async call, but the async function is added to the group, instead - /// of returning a promise. func must be async and have return type void. + /// of returning a promise. func must be async and have return type ReturnType. /// Thread-safe. pub fn call(self: *Self, comptime func: var, args: ...) (error{OutOfMemory}!void) { const S = struct { diff --git a/std/event/loop.zig b/std/event/loop.zig index 485a5be19c..cd805f891f 100644 --- a/std/event/loop.zig +++ b/std/event/loop.zig @@ -444,7 +444,7 @@ pub const Loop = struct { .next = undefined, .data = p, }; - loop.onNextTick(&my_tick_node); + self.onNextTick(&my_tick_node); } } -- cgit v1.2.3 From 93e78ee72259b98840f63db0ad87fdddb071e384 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 22 Jul 2018 23:27:58 -0400 Subject: self-hosted can compile libc hello world --- CMakeLists.txt | 1 + src-self-hosted/codegen.zig | 6 +- src-self-hosted/compilation.zig | 185 +++++++++---- src-self-hosted/decl.zig | 8 +- src-self-hosted/errmsg.zig | 9 +- src-self-hosted/ir.zig | 544 +++++++++++++++++++++++++++++++++++---- src-self-hosted/llvm.zig | 38 ++- src-self-hosted/scope.zig | 25 +- src-self-hosted/test.zig | 1 + src-self-hosted/type.zig | 364 +++++++++++++++++++++----- src-self-hosted/value.zig | 280 +++++++++++++++++++- std/event/group.zig | 1 + std/zig/index.zig | 3 + std/zig/parse_string_literal.zig | 76 ++++++ std/zig/tokenizer.zig | 1 + 15 files changed, 1358 insertions(+), 184 deletions(-) create mode 100644 std/zig/parse_string_literal.zig (limited to 'src-self-hosted/codegen.zig') diff --git a/CMakeLists.txt b/CMakeLists.txt index 20755cfc1b..c3f0721bb5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -624,6 +624,7 @@ set(ZIG_STD_FILES "zig/ast.zig" "zig/index.zig" "zig/parse.zig" + "zig/parse_string_literal.zig" "zig/render.zig" "zig/tokenizer.zig" ) diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 8faef5f31c..ad3dce061e 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -78,6 +78,7 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) .dibuilder = dibuilder, .context = context, .lock = event.Lock.init(comp.loop), + .arena = &code.arena.allocator, }; try renderToLlvmModule(&ofile, fn_val, code); @@ -139,6 +140,7 @@ pub const ObjectFile = struct { dibuilder: *llvm.DIBuilder, context: llvm.ContextRef, lock: event.Lock, + arena: *std.mem.Allocator, fn gpa(self: *ObjectFile) *std.mem.Allocator { return self.comp.gpa(); @@ -147,7 +149,7 @@ pub const ObjectFile = struct { pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) !void { // TODO audit more of codegen.cpp:fn_llvm_value and port more logic - const llvm_fn_type = try fn_val.base.typeof.getLlvmType(ofile); + const llvm_fn_type = try fn_val.base.typ.getLlvmType(ofile.arena, ofile.context); const llvm_fn = llvm.AddFunction( ofile.module, fn_val.symbol_name.ptr(), @@ -165,7 +167,7 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) // try addLLVMFnAttrInt(ofile, llvm_fn, "alignstack", align_stack); //} - const fn_type = fn_val.base.typeof.cast(Type.Fn).?; + const fn_type = fn_val.base.typ.cast(Type.Fn).?; try addLLVMFnAttr(ofile, llvm_fn, "nounwind"); //add_uwtable_attr(g, fn_table_entry->llvm_value); diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig index 46cbf14141..3adeb55b56 100644 --- a/src-self-hosted/compilation.zig +++ b/src-self-hosted/compilation.zig @@ -194,6 +194,7 @@ pub const Compilation = struct { bool_type: *Type.Bool, noreturn_type: *Type.NoReturn, comptime_int_type: *Type.ComptimeInt, + u8_type: *Type.Int, void_value: *Value.Void, true_value: *Value.Bool, @@ -203,6 +204,7 @@ pub const Compilation = struct { target_machine: llvm.TargetMachineRef, target_data_ref: llvm.TargetDataRef, target_layout_str: [*]u8, + target_ptr_bits: u32, /// for allocating things which have the same lifetime as this Compilation arena_allocator: std.heap.ArenaAllocator, @@ -223,10 +225,14 @@ pub const Compilation = struct { primitive_type_table: TypeTable, int_type_table: event.Locked(IntTypeTable), + array_type_table: event.Locked(ArrayTypeTable), + ptr_type_table: event.Locked(PtrTypeTable), c_int_types: [CInt.list.len]*Type.Int, const IntTypeTable = std.HashMap(*const Type.Int.Key, *Type.Int, Type.Int.Key.hash, Type.Int.Key.eql); + const ArrayTypeTable = std.HashMap(*const Type.Array.Key, *Type.Array, Type.Array.Key.hash, Type.Array.Key.eql); + const PtrTypeTable = std.HashMap(*const Type.Pointer.Key, *Type.Pointer, Type.Pointer.Key.hash, Type.Pointer.Key.eql); const TypeTable = std.HashMap([]const u8, *Type, mem.hash_slice_u8, mem.eql_slice_u8); const CompileErrList = std.ArrayList(*errmsg.Msg); @@ -383,6 +389,8 @@ pub const Compilation = struct { .deinit_group = event.Group(void).init(loop), .compile_errors = event.Locked(CompileErrList).init(loop, CompileErrList.init(loop.allocator)), .int_type_table = event.Locked(IntTypeTable).init(loop, IntTypeTable.init(loop.allocator)), + .array_type_table = event.Locked(ArrayTypeTable).init(loop, ArrayTypeTable.init(loop.allocator)), + .ptr_type_table = event.Locked(PtrTypeTable).init(loop, PtrTypeTable.init(loop.allocator)), .c_int_types = undefined, .meta_type = undefined, @@ -394,10 +402,12 @@ pub const Compilation = struct { .noreturn_type = undefined, .noreturn_value = undefined, .comptime_int_type = undefined, + .u8_type = undefined, .target_machine = undefined, .target_data_ref = undefined, .target_layout_str = undefined, + .target_ptr_bits = target.getArchPtrBitWidth(), .root_package = undefined, .std_package = undefined, @@ -409,6 +419,8 @@ pub const Compilation = struct { }); errdefer { comp.int_type_table.private_data.deinit(); + comp.array_type_table.private_data.deinit(); + comp.ptr_type_table.private_data.deinit(); comp.arena_allocator.deinit(); comp.loop.allocator.destroy(comp); } @@ -517,15 +529,16 @@ pub const Compilation = struct { .name = "type", .base = Value{ .id = Value.Id.Type, - .typeof = undefined, + .typ = undefined, .ref_count = std.atomic.Int(usize).init(3), // 3 because it references itself twice }, .id = builtin.TypeId.Type, + .abi_alignment = Type.AbiAlignment.init(comp.loop), }, .value = undefined, }); comp.meta_type.value = &comp.meta_type.base; - comp.meta_type.base.base.typeof = &comp.meta_type.base; + comp.meta_type.base.base.typ = &comp.meta_type.base; assert((try comp.primitive_type_table.put(comp.meta_type.base.name, &comp.meta_type.base)) == null); comp.void_type = try comp.arena().create(Type.Void{ @@ -533,10 +546,11 @@ pub const Compilation = struct { .name = "void", .base = Value{ .id = Value.Id.Type, - .typeof = &Type.MetaType.get(comp).base, + .typ = &Type.MetaType.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, .id = builtin.TypeId.Void, + .abi_alignment = Type.AbiAlignment.init(comp.loop), }, }); assert((try comp.primitive_type_table.put(comp.void_type.base.name, &comp.void_type.base)) == null); @@ -546,10 +560,11 @@ pub const Compilation = struct { .name = "noreturn", .base = Value{ .id = Value.Id.Type, - .typeof = &Type.MetaType.get(comp).base, + .typ = &Type.MetaType.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, .id = builtin.TypeId.NoReturn, + .abi_alignment = Type.AbiAlignment.init(comp.loop), }, }); assert((try comp.primitive_type_table.put(comp.noreturn_type.base.name, &comp.noreturn_type.base)) == null); @@ -559,10 +574,11 @@ pub const Compilation = struct { .name = "comptime_int", .base = Value{ .id = Value.Id.Type, - .typeof = &Type.MetaType.get(comp).base, + .typ = &Type.MetaType.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, .id = builtin.TypeId.ComptimeInt, + .abi_alignment = Type.AbiAlignment.init(comp.loop), }, }); assert((try comp.primitive_type_table.put(comp.comptime_int_type.base.name, &comp.comptime_int_type.base)) == null); @@ -572,10 +588,11 @@ pub const Compilation = struct { .name = "bool", .base = Value{ .id = Value.Id.Type, - .typeof = &Type.MetaType.get(comp).base, + .typ = &Type.MetaType.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, .id = builtin.TypeId.Bool, + .abi_alignment = Type.AbiAlignment.init(comp.loop), }, }); assert((try comp.primitive_type_table.put(comp.bool_type.base.name, &comp.bool_type.base)) == null); @@ -583,7 +600,7 @@ pub const Compilation = struct { comp.void_value = try comp.arena().create(Value.Void{ .base = Value{ .id = Value.Id.Void, - .typeof = &Type.Void.get(comp).base, + .typ = &Type.Void.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, }); @@ -591,7 +608,7 @@ pub const Compilation = struct { comp.true_value = try comp.arena().create(Value.Bool{ .base = Value{ .id = Value.Id.Bool, - .typeof = &Type.Bool.get(comp).base, + .typ = &Type.Bool.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, .x = true, @@ -600,7 +617,7 @@ pub const Compilation = struct { comp.false_value = try comp.arena().create(Value.Bool{ .base = Value{ .id = Value.Id.Bool, - .typeof = &Type.Bool.get(comp).base, + .typ = &Type.Bool.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, .x = false, @@ -609,7 +626,7 @@ pub const Compilation = struct { comp.noreturn_value = try comp.arena().create(Value.NoReturn{ .base = Value{ .id = Value.Id.NoReturn, - .typeof = &Type.NoReturn.get(comp).base, + .typ = &Type.NoReturn.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, }); @@ -620,10 +637,11 @@ pub const Compilation = struct { .name = cint.zig_name, .base = Value{ .id = Value.Id.Type, - .typeof = &Type.MetaType.get(comp).base, + .typ = &Type.MetaType.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, .id = builtin.TypeId.Int, + .abi_alignment = Type.AbiAlignment.init(comp.loop), }, .key = Type.Int.Key{ .is_signed = cint.is_signed, @@ -634,6 +652,24 @@ pub const Compilation = struct { comp.c_int_types[i] = c_int_type; assert((try comp.primitive_type_table.put(cint.zig_name, &c_int_type.base)) == null); } + comp.u8_type = try comp.arena().create(Type.Int{ + .base = Type{ + .name = "u8", + .base = Value{ + .id = Value.Id.Type, + .typ = &Type.MetaType.get(comp).base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .id = builtin.TypeId.Int, + .abi_alignment = Type.AbiAlignment.init(comp.loop), + }, + .key = Type.Int.Key{ + .is_signed = false, + .bit_count = 8, + }, + .garbage_node = undefined, + }); + assert((try comp.primitive_type_table.put(comp.u8_type.base.name, &comp.u8_type.base)) == null); } /// This function can safely use async/await, because it manages Compilation's lifetime, @@ -750,7 +786,7 @@ pub const Compilation = struct { ast.Node.Id.Comptime => { const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", decl); - try decl_group.call(addCompTimeBlock, self, &decls.base, comptime_node); + try self.prelink_group.call(addCompTimeBlock, self, &decls.base, comptime_node); }, ast.Node.Id.VarDecl => @panic("TODO"), ast.Node.Id.FnProto => { @@ -770,7 +806,6 @@ pub const Compilation = struct { .name = name, .visib = parseVisibToken(tree, fn_proto.visib_token), .resolution = event.Future(BuildError!void).init(self.loop), - .resolution_in_progress = 0, .parent_scope = &decls.base, }, .value = Decl.Fn.Val{ .Unresolved = {} }, @@ -778,16 +813,22 @@ pub const Compilation = struct { }); errdefer self.gpa().destroy(fn_decl); - try decl_group.call(addTopLevelDecl, self, &fn_decl.base); + try decl_group.call(addTopLevelDecl, self, decls, &fn_decl.base); }, ast.Node.Id.TestDecl => @panic("TODO"), else => unreachable, } } try await (async decl_group.wait() catch unreachable); + + // Now other code can rely on the decls scope having a complete list of names. + decls.name_future.resolve(); } - try await (async self.prelink_group.wait() catch unreachable); + (await (async self.prelink_group.wait() catch unreachable)) catch |err| switch (err) { + error.SemanticAnalysisFailed => {}, + else => return err, + }; const any_prelink_errors = blk: { const compile_errors = await (async self.compile_errors.acquire() catch unreachable); @@ -857,14 +898,31 @@ pub const Compilation = struct { analyzed_code.destroy(comp.gpa()); } - async fn addTopLevelDecl(self: *Compilation, decl: *Decl) !void { + async fn addTopLevelDecl(self: *Compilation, decls: *Scope.Decls, decl: *Decl) !void { const tree = &decl.findRootScope().tree; const is_export = decl.isExported(tree); + var add_to_table_resolved = false; + const add_to_table = async self.addDeclToTable(decls, decl) catch unreachable; + errdefer if (!add_to_table_resolved) cancel add_to_table; // TODO https://github.com/ziglang/zig/issues/1261 + if (is_export) { try self.prelink_group.call(verifyUniqueSymbol, self, decl); try self.prelink_group.call(resolveDecl, self, decl); } + + add_to_table_resolved = true; + try await add_to_table; + } + + async fn addDeclToTable(self: *Compilation, decls: *Scope.Decls, decl: *Decl) !void { + const held = await (async decls.table.acquire() catch unreachable); + defer held.release(); + + if (try held.value.put(decl.name, decl)) |other_decl| { + try self.addCompileError(decls.base.findRoot(), decl.getSpan(), "redefinition of '{}'", decl.name); + // TODO note: other definition here + } } fn addCompileError(self: *Compilation, root: *Scope.Root, span: Span, comptime fmt: []const u8, args: ...) !void { @@ -1043,6 +1101,15 @@ pub const Compilation = struct { return result_val.cast(Type).?; } + + /// This declaration has been blessed as going into the final code generation. + pub async fn resolveDecl(comp: *Compilation, decl: *Decl) !void { + if (await (async decl.resolution.start() catch unreachable)) |ptr| return ptr.*; + + decl.resolution.data = try await (async generateDecl(comp, decl) catch unreachable); + decl.resolution.resolve(); + return decl.resolution.data; + } }; fn printError(comptime format: []const u8, args: ...) !void { @@ -1062,20 +1129,6 @@ fn parseVisibToken(tree: *ast.Tree, optional_token_index: ?ast.TokenIndex) Visib } } -/// This declaration has been blessed as going into the final code generation. -pub async fn resolveDecl(comp: *Compilation, decl: *Decl) !void { - if (await (async decl.resolution.start() catch unreachable)) |ptr| return ptr.*; - - decl.resolution.data = (await (async generateDecl(comp, decl) catch unreachable)) catch |err| switch (err) { - // This poison value should not cause the errdefers to run. It simply means - // that comp.compile_errors is populated. - error.SemanticAnalysisFailed => {}, - else => err, - }; - decl.resolution.resolve(); - return decl.resolution.data; -} - /// The function that actually does the generation. async fn generateDecl(comp: *Compilation, decl: *Decl) !void { switch (decl.id) { @@ -1089,34 +1142,27 @@ async fn generateDecl(comp: *Compilation, decl: *Decl) !void { } async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { - const body_node = fn_decl.fn_proto.body_node orelse @panic("TODO extern fn proto decl"); + const body_node = fn_decl.fn_proto.body_node orelse return await (async generateDeclFnProto(comp, fn_decl) catch unreachable); const fndef_scope = try Scope.FnDef.create(comp, fn_decl.base.parent_scope); defer fndef_scope.base.deref(comp); - const return_type_node = switch (fn_decl.fn_proto.return_type) { - ast.Node.FnProto.ReturnType.Explicit => |n| n, - ast.Node.FnProto.ReturnType.InferErrorSet => |n| n, - }; - const return_type = try await (async comp.analyzeTypeExpr(&fndef_scope.base, return_type_node) catch unreachable); - return_type.base.deref(comp); - - const is_var_args = false; - const params = ([*]Type.Fn.Param)(undefined)[0..0]; - const fn_type = try Type.Fn.create(comp, return_type, params, is_var_args); + const fn_type = try await (async analyzeFnType(comp, fn_decl.base.parent_scope, fn_decl.fn_proto) catch unreachable); defer fn_type.base.base.deref(comp); var symbol_name = try std.Buffer.init(comp.gpa(), fn_decl.base.name); - errdefer symbol_name.deinit(); + var symbol_name_consumed = false; + errdefer if (!symbol_name_consumed) symbol_name.deinit(); // The Decl.Fn owns the initial 1 reference count const fn_val = try Value.Fn.create(comp, fn_type, fndef_scope, symbol_name); - fn_decl.value = Decl.Fn.Val{ .Ok = fn_val }; + fn_decl.value = Decl.Fn.Val{ .Fn = fn_val }; + symbol_name_consumed = true; const analyzed_code = try await (async comp.genAndAnalyzeCode( &fndef_scope.base, body_node, - return_type, + fn_type.return_type, ) catch unreachable); errdefer analyzed_code.destroy(comp.gpa()); @@ -1141,3 +1187,54 @@ async fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) void { fn getZigDir(allocator: *mem.Allocator) ![]u8 { return os.getAppDataDir(allocator, "zig"); } + +async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.FnProto) !*Type.Fn { + const return_type_node = switch (fn_proto.return_type) { + ast.Node.FnProto.ReturnType.Explicit => |n| n, + ast.Node.FnProto.ReturnType.InferErrorSet => |n| n, + }; + const return_type = try await (async comp.analyzeTypeExpr(scope, return_type_node) catch unreachable); + return_type.base.deref(comp); + + var params = ArrayList(Type.Fn.Param).init(comp.gpa()); + var params_consumed = false; + defer if (params_consumed) { + for (params.toSliceConst()) |param| { + param.typ.base.deref(comp); + } + params.deinit(); + }; + + const is_var_args = false; + { + var it = fn_proto.params.iterator(0); + while (it.next()) |param_node_ptr| { + const param_node = param_node_ptr.*.cast(ast.Node.ParamDecl).?; + const param_type = try await (async comp.analyzeTypeExpr(scope, param_node.type_node) catch unreachable); + errdefer param_type.base.deref(comp); + try params.append(Type.Fn.Param{ + .typ = param_type, + .is_noalias = param_node.noalias_token != null, + }); + } + } + const fn_type = try Type.Fn.create(comp, return_type, params.toOwnedSlice(), is_var_args); + params_consumed = true; + errdefer fn_type.base.base.deref(comp); + + return fn_type; +} + +async fn generateDeclFnProto(comp: *Compilation, fn_decl: *Decl.Fn) !void { + const fn_type = try await (async analyzeFnType(comp, fn_decl.base.parent_scope, fn_decl.fn_proto) catch unreachable); + defer fn_type.base.base.deref(comp); + + var symbol_name = try std.Buffer.init(comp.gpa(), fn_decl.base.name); + var symbol_name_consumed = false; + defer if (!symbol_name_consumed) symbol_name.deinit(); + + // The Decl.Fn owns the initial 1 reference count + const fn_proto_val = try Value.FnProto.create(comp, fn_type, symbol_name); + fn_decl.value = Decl.Fn.Val{ .FnProto = fn_proto_val }; + symbol_name_consumed = true; +} diff --git a/src-self-hosted/decl.zig b/src-self-hosted/decl.zig index bb065640c2..6e80243038 100644 --- a/src-self-hosted/decl.zig +++ b/src-self-hosted/decl.zig @@ -15,7 +15,6 @@ pub const Decl = struct { name: []const u8, visib: Visib, resolution: event.Future(Compilation.BuildError!void), - resolution_in_progress: u8, parent_scope: *Scope, pub const Table = std.HashMap([]const u8, *Decl, mem.hash_slice_u8, mem.eql_slice_u8); @@ -63,12 +62,13 @@ pub const Decl = struct { pub const Fn = struct { base: Decl, value: Val, - fn_proto: *const ast.Node.FnProto, + fn_proto: *ast.Node.FnProto, // TODO https://github.com/ziglang/zig/issues/683 and then make this anonymous - pub const Val = union { + pub const Val = union(enum) { Unresolved: void, - Ok: *Value.Fn, + Fn: *Value.Fn, + FnProto: *Value.FnProto, }; pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 { diff --git a/src-self-hosted/errmsg.zig b/src-self-hosted/errmsg.zig index 4e353bfb14..eec4490a81 100644 --- a/src-self-hosted/errmsg.zig +++ b/src-self-hosted/errmsg.zig @@ -16,11 +16,18 @@ pub const Span = struct { last: ast.TokenIndex, pub fn token(i: TokenIndex) Span { - return Span { + return Span{ .first = i, .last = i, }; } + + pub fn node(n: *ast.Node) Span { + return Span{ + .first = n.firstToken(), + .last = n.lastToken(), + }; + } }; pub const Msg = struct { diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 04023980e6..d624fe4dac 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -11,6 +11,7 @@ const Token = std.zig.Token; const Span = @import("errmsg.zig").Span; const llvm = @import("llvm.zig"); const ObjectFile = @import("codegen.zig").ObjectFile; +const Decl = @import("decl.zig").Decl; pub const LVal = enum { None, @@ -30,10 +31,10 @@ pub const IrVal = union(enum) { pub fn dump(self: IrVal) void { switch (self) { - IrVal.Unknown => typeof.dump(), - IrVal.KnownType => |typeof| { + IrVal.Unknown => std.debug.warn("Unknown"), + IrVal.KnownType => |typ| { std.debug.warn("KnownType("); - typeof.dump(); + typ.dump(); std.debug.warn(")"); }, IrVal.KnownValue => |value| { @@ -108,21 +109,29 @@ pub const Inst = struct { unreachable; } - pub fn analyze(base: *Inst, ira: *Analyze) Analyze.Error!*Inst { - comptime var i = 0; - inline while (i < @memberCount(Id)) : (i += 1) { - if (base.id == @field(Id, @memberName(Id, i))) { - const T = @field(Inst, @memberName(Id, i)); - return @fieldParentPtr(T, "base", base).analyze(ira); - } + pub async fn analyze(base: *Inst, ira: *Analyze) Analyze.Error!*Inst { + switch (base.id) { + Id.Return => return @fieldParentPtr(Return, "base", base).analyze(ira), + Id.Const => return @fieldParentPtr(Const, "base", base).analyze(ira), + Id.Call => return @fieldParentPtr(Call, "base", base).analyze(ira), + Id.DeclRef => return await (async @fieldParentPtr(DeclRef, "base", base).analyze(ira) catch unreachable), + Id.Ref => return await (async @fieldParentPtr(Ref, "base", base).analyze(ira) catch unreachable), + Id.DeclVar => return @fieldParentPtr(DeclVar, "base", base).analyze(ira), + Id.CheckVoidStmt => return @fieldParentPtr(CheckVoidStmt, "base", base).analyze(ira), + Id.Phi => return @fieldParentPtr(Phi, "base", base).analyze(ira), + Id.Br => return @fieldParentPtr(Br, "base", base).analyze(ira), + Id.AddImplicitReturnType => return @fieldParentPtr(AddImplicitReturnType, "base", base).analyze(ira), + Id.PtrType => return await (async @fieldParentPtr(PtrType, "base", base).analyze(ira) catch unreachable), } - unreachable; } pub fn render(base: *Inst, ofile: *ObjectFile, fn_val: *Value.Fn) (error{OutOfMemory}!?llvm.ValueRef) { switch (base.id) { Id.Return => return @fieldParentPtr(Return, "base", base).render(ofile, fn_val), Id.Const => return @fieldParentPtr(Const, "base", base).render(ofile, fn_val), + Id.Call => return @fieldParentPtr(Call, "base", base).render(ofile, fn_val), + Id.DeclRef => unreachable, + Id.PtrType => unreachable, Id.Ref => @panic("TODO"), Id.DeclVar => @panic("TODO"), Id.CheckVoidStmt => @panic("TODO"), @@ -135,7 +144,7 @@ pub const Inst = struct { fn ref(base: *Inst, builder: *Builder) void { base.ref_count += 1; if (base.owner_bb != builder.current_basic_block and !base.isCompTime()) { - base.owner_bb.ref(); + base.owner_bb.ref(builder); } } @@ -155,11 +164,51 @@ pub const Inst = struct { } } + fn getConstVal(self: *Inst, ira: *Analyze) !*Value { + if (self.isCompTime()) { + return self.val.KnownValue; + } else { + try ira.addCompileError(self.span, "unable to evaluate constant expression"); + return error.SemanticAnalysisFailed; + } + } + + fn getAsConstType(param: *Inst, ira: *Analyze) !*Type { + const meta_type = Type.MetaType.get(ira.irb.comp); + meta_type.base.base.deref(ira.irb.comp); + + const inst = try param.getAsParam(); + const casted = try ira.implicitCast(inst, &meta_type.base); + const val = try casted.getConstVal(ira); + return val.cast(Value.Type).?; + } + + fn getAsConstAlign(param: *Inst, ira: *Analyze) !u32 { + return error.Unimplemented; + //const align_type = Type.Int.get_align(ira.irb.comp); + //align_type.base.base.deref(ira.irb.comp); + + //const inst = try param.getAsParam(); + //const casted = try ira.implicitCast(inst, align_type); + //const val = try casted.getConstVal(ira); + + //uint32_t align_bytes = bigint_as_unsigned(&const_val->data.x_bigint); + //if (align_bytes == 0) { + // ir_add_error(ira, value, buf_sprintf("alignment must be >= 1")); + // return false; + //} + + //if (!is_power_of_2(align_bytes)) { + // ir_add_error(ira, value, buf_sprintf("alignment value %" PRIu32 " is not a power of 2", align_bytes)); + // return false; + //} + } + /// asserts that the type is known fn getKnownType(self: *Inst) *Type { switch (self.val) { - IrVal.KnownType => |typeof| return typeof, - IrVal.KnownValue => |value| return value.typeof, + IrVal.KnownType => |typ| return typ, + IrVal.KnownValue => |value| return value.typ, IrVal.Unknown => unreachable, } } @@ -171,8 +220,8 @@ pub const Inst = struct { pub fn isNoReturn(base: *const Inst) bool { switch (base.val) { IrVal.Unknown => return false, - IrVal.KnownValue => |x| return x.typeof.id == Type.Id.NoReturn, - IrVal.KnownType => |typeof| return typeof.id == Type.Id.NoReturn, + IrVal.KnownValue => |x| return x.typ.id == Type.Id.NoReturn, + IrVal.KnownType => |typ| return typ.id == Type.Id.NoReturn, } } @@ -196,6 +245,85 @@ pub const Inst = struct { Phi, Br, AddImplicitReturnType, + Call, + DeclRef, + PtrType, + }; + + pub const Call = struct { + base: Inst, + params: Params, + + const Params = struct { + fn_ref: *Inst, + args: []*Inst, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(self: *const Call) void { + std.debug.warn("#{}(", self.params.fn_ref.debug_id); + for (self.params.args) |arg| { + std.debug.warn("#{},", arg.debug_id); + } + std.debug.warn(")"); + } + + pub fn hasSideEffects(self: *const Call) bool { + return true; + } + + pub fn analyze(self: *const Call, ira: *Analyze) !*Inst { + const fn_ref = try self.params.fn_ref.getAsParam(); + const fn_ref_type = fn_ref.getKnownType(); + const fn_type = fn_ref_type.cast(Type.Fn) orelse { + try ira.addCompileError(fn_ref.span, "type '{}' not a function", fn_ref_type.name); + return error.SemanticAnalysisFailed; + }; + + if (fn_type.params.len != self.params.args.len) { + try ira.addCompileError( + self.base.span, + "expected {} arguments, found {}", + fn_type.params.len, + self.params.args.len, + ); + return error.SemanticAnalysisFailed; + } + + const args = try ira.irb.arena().alloc(*Inst, self.params.args.len); + for (self.params.args) |arg, i| { + args[i] = try arg.getAsParam(); + } + const new_inst = try ira.irb.build(Call, self.base.scope, self.base.span, Params{ + .fn_ref = fn_ref, + .args = args, + }); + new_inst.val = IrVal{ .KnownType = fn_type.return_type }; + return new_inst; + } + + pub fn render(self: *Call, ofile: *ObjectFile, fn_val: *Value.Fn) !?llvm.ValueRef { + const fn_ref = self.params.fn_ref.llvm_value.?; + + const args = try ofile.arena.alloc(llvm.ValueRef, self.params.args.len); + for (self.params.args) |arg, i| { + args[i] = arg.llvm_value.?; + } + + const llvm_cc = llvm.CCallConv; + const fn_inline = llvm.FnInline.Auto; + + return llvm.BuildCall( + ofile.builder, + fn_ref, + args.ptr, + @intCast(c_uint, args.len), + llvm_cc, + fn_inline, + c"", + ) orelse error.OutOfMemory; + } }; pub const Const = struct { @@ -254,14 +382,14 @@ pub const Inst = struct { return ira.irb.build(Return, self.base.scope, self.base.span, Params{ .return_value = casted_value }); } - pub fn render(self: *Return, ofile: *ObjectFile, fn_val: *Value.Fn) ?llvm.ValueRef { + pub fn render(self: *Return, ofile: *ObjectFile, fn_val: *Value.Fn) !?llvm.ValueRef { const value = self.params.return_value.llvm_value; const return_type = self.params.return_value.getKnownType(); if (return_type.handleIsPtr()) { @panic("TODO"); } else { - _ = llvm.BuildRet(ofile.builder, value); + _ = llvm.BuildRet(ofile.builder, value) orelse return error.OutOfMemory; } return null; } @@ -285,7 +413,7 @@ pub const Inst = struct { return false; } - pub fn analyze(self: *const Ref, ira: *Analyze) !*Inst { + pub async fn analyze(self: *const Ref, ira: *Analyze) !*Inst { const target = try self.params.target.getAsParam(); if (ira.getCompTimeValOrNullUndefOk(target)) |val| { @@ -294,7 +422,6 @@ pub const Inst = struct { Value.Ptr.Mut.CompTimeConst, self.params.mut, self.params.volatility, - val.typeof.getAbiAlignment(ira.irb.comp), ); } @@ -304,14 +431,13 @@ pub const Inst = struct { .volatility = self.params.volatility, }); const elem_type = target.getKnownType(); - const ptr_type = Type.Pointer.get( - ira.irb.comp, - elem_type, - self.params.mut, - self.params.volatility, - Type.Pointer.Size.One, - elem_type.getAbiAlignment(ira.irb.comp), - ); + const ptr_type = try await (async Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{ + .child_type = elem_type, + .mut = self.params.mut, + .vol = self.params.volatility, + .size = Type.Pointer.Size.One, + .alignment = Type.Pointer.Align.Abi, + }) catch unreachable); // TODO: potentially set the hint that this is a stack pointer. But it might not be - this // could be a ref of a global, for example new_inst.val = IrVal{ .KnownType = &ptr_type.base }; @@ -320,6 +446,97 @@ pub const Inst = struct { } }; + pub const DeclRef = struct { + base: Inst, + params: Params, + + const Params = struct { + decl: *Decl, + lval: LVal, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(inst: *const DeclRef) void {} + + pub fn hasSideEffects(inst: *const DeclRef) bool { + return false; + } + + pub async fn analyze(self: *const DeclRef, ira: *Analyze) !*Inst { + (await (async ira.irb.comp.resolveDecl(self.params.decl) catch unreachable)) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => return error.SemanticAnalysisFailed, + }; + switch (self.params.decl.id) { + Decl.Id.CompTime => unreachable, + Decl.Id.Var => return error.Unimplemented, + Decl.Id.Fn => { + const fn_decl = @fieldParentPtr(Decl.Fn, "base", self.params.decl); + const decl_val = switch (fn_decl.value) { + Decl.Fn.Val.Unresolved => unreachable, + Decl.Fn.Val.Fn => |fn_val| &fn_val.base, + Decl.Fn.Val.FnProto => |fn_proto| &fn_proto.base, + }; + switch (self.params.lval) { + LVal.None => { + return ira.irb.buildConstValue(self.base.scope, self.base.span, decl_val); + }, + LVal.Ptr => return error.Unimplemented, + } + }, + } + } + }; + + pub const PtrType = struct { + base: Inst, + params: Params, + + const Params = struct { + child_type: *Inst, + mut: Type.Pointer.Mut, + vol: Type.Pointer.Vol, + size: Type.Pointer.Size, + alignment: ?*Inst, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(inst: *const PtrType) void {} + + pub fn hasSideEffects(inst: *const PtrType) bool { + return false; + } + + pub async fn analyze(self: *const PtrType, ira: *Analyze) !*Inst { + const child_type = try self.params.child_type.getAsConstType(ira); + // if (child_type->id == TypeTableEntryIdUnreachable) { + // ir_add_error(ira, &instruction->base, buf_sprintf("pointer to noreturn not allowed")); + // return ira->codegen->builtin_types.entry_invalid; + // } else if (child_type->id == TypeTableEntryIdOpaque && instruction->ptr_len == PtrLenUnknown) { + // ir_add_error(ira, &instruction->base, buf_sprintf("unknown-length pointer to opaque")); + // return ira->codegen->builtin_types.entry_invalid; + // } + const alignment = if (self.params.alignment) |align_inst| blk: { + const amt = try align_inst.getAsConstAlign(ira); + break :blk Type.Pointer.Align{ .Override = amt }; + } else blk: { + break :blk Type.Pointer.Align{ .Abi = {} }; + }; + const ptr_type = try await (async Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{ + .child_type = child_type, + .mut = self.params.mut, + .vol = self.params.vol, + .size = self.params.size, + .alignment = alignment, + }) catch unreachable); + ptr_type.base.base.deref(ira.irb.comp); + + return ira.irb.buildConstValue(self.base.scope, self.base.span, &ptr_type.base.base); + } + }; + pub const DeclVar = struct { base: Inst, params: Params, @@ -351,14 +568,21 @@ pub const Inst = struct { const ir_val_init = IrVal.Init.Unknown; - pub fn dump(inst: *const CheckVoidStmt) void {} + pub fn dump(self: *const CheckVoidStmt) void { + std.debug.warn("#{}", self.params.target.debug_id); + } pub fn hasSideEffects(inst: *const CheckVoidStmt) bool { return true; } pub fn analyze(self: *const CheckVoidStmt, ira: *Analyze) !*Inst { - return error.Unimplemented; // TODO + const target = try self.params.target.getAsParam(); + if (target.getKnownType().id != Type.Id.Void) { + try ira.addCompileError(self.base.span, "expression value is ignored"); + return error.SemanticAnalysisFailed; + } + return ira.irb.buildConstVoid(self.base.scope, self.base.span, true); } }; @@ -583,7 +807,7 @@ pub const BasicBlock = struct { /// the basic block that this one derives from in analysis parent: ?*BasicBlock, - pub fn ref(self: *BasicBlock) void { + pub fn ref(self: *BasicBlock, builder: *Builder) void { self.ref_count += 1; } @@ -724,8 +948,42 @@ pub const Builder = struct { ast.Node.Id.VarDecl => return error.Unimplemented, ast.Node.Id.Defer => return error.Unimplemented, ast.Node.Id.InfixOp => return error.Unimplemented, - ast.Node.Id.PrefixOp => return error.Unimplemented, - ast.Node.Id.SuffixOp => return error.Unimplemented, + ast.Node.Id.PrefixOp => { + const prefix_op = @fieldParentPtr(ast.Node.PrefixOp, "base", node); + switch (prefix_op.op) { + ast.Node.PrefixOp.Op.AddressOf => return error.Unimplemented, + ast.Node.PrefixOp.Op.ArrayType => |n| return error.Unimplemented, + ast.Node.PrefixOp.Op.Await => return error.Unimplemented, + ast.Node.PrefixOp.Op.BitNot => return error.Unimplemented, + ast.Node.PrefixOp.Op.BoolNot => return error.Unimplemented, + ast.Node.PrefixOp.Op.Cancel => return error.Unimplemented, + ast.Node.PrefixOp.Op.OptionalType => return error.Unimplemented, + ast.Node.PrefixOp.Op.Negation => return error.Unimplemented, + ast.Node.PrefixOp.Op.NegationWrap => return error.Unimplemented, + ast.Node.PrefixOp.Op.Resume => return error.Unimplemented, + ast.Node.PrefixOp.Op.PtrType => |ptr_info| { + const inst = try await (async irb.genPtrType(prefix_op, ptr_info, scope) catch unreachable); + return irb.lvalWrap(scope, inst, lval); + }, + ast.Node.PrefixOp.Op.SliceType => |ptr_info| return error.Unimplemented, + ast.Node.PrefixOp.Op.Try => return error.Unimplemented, + } + }, + ast.Node.Id.SuffixOp => { + const suffix_op = @fieldParentPtr(ast.Node.SuffixOp, "base", node); + switch (suffix_op.op) { + @TagType(ast.Node.SuffixOp.Op).Call => |*call| { + const inst = try await (async irb.genCall(suffix_op, call, scope) catch unreachable); + return irb.lvalWrap(scope, inst, lval); + }, + @TagType(ast.Node.SuffixOp.Op).ArrayAccess => |n| return error.Unimplemented, + @TagType(ast.Node.SuffixOp.Op).Slice => |slice| return error.Unimplemented, + @TagType(ast.Node.SuffixOp.Op).ArrayInitializer => |init_list| return error.Unimplemented, + @TagType(ast.Node.SuffixOp.Op).StructInitializer => |init_list| return error.Unimplemented, + @TagType(ast.Node.SuffixOp.Op).Deref => return error.Unimplemented, + @TagType(ast.Node.SuffixOp.Op).UnwrapOptional => return error.Unimplemented, + } + }, ast.Node.Id.Switch => return error.Unimplemented, ast.Node.Id.While => return error.Unimplemented, ast.Node.Id.For => return error.Unimplemented, @@ -744,7 +1002,11 @@ pub const Builder = struct { return irb.lvalWrap(scope, try irb.genIntLit(int_lit, scope), lval); }, ast.Node.Id.FloatLiteral => return error.Unimplemented, - ast.Node.Id.StringLiteral => return error.Unimplemented, + ast.Node.Id.StringLiteral => { + const str_lit = @fieldParentPtr(ast.Node.StringLiteral, "base", node); + const inst = try await (async irb.genStrLit(str_lit, scope) catch unreachable); + return irb.lvalWrap(scope, inst, lval); + }, ast.Node.Id.MultilineStringLiteral => return error.Unimplemented, ast.Node.Id.CharLiteral => return error.Unimplemented, ast.Node.Id.BoolLiteral => return error.Unimplemented, @@ -789,6 +1051,99 @@ pub const Builder = struct { } } + async fn genCall(irb: *Builder, suffix_op: *ast.Node.SuffixOp, call: *ast.Node.SuffixOp.Op.Call, scope: *Scope) !*Inst { + const fn_ref = try await (async irb.genNode(suffix_op.lhs, scope, LVal.None) catch unreachable); + + const args = try irb.arena().alloc(*Inst, call.params.len); + var it = call.params.iterator(0); + var i: usize = 0; + while (it.next()) |arg_node_ptr| : (i += 1) { + args[i] = try await (async irb.genNode(arg_node_ptr.*, scope, LVal.None) catch unreachable); + } + + //bool is_async = node->data.fn_call_expr.is_async; + //IrInstruction *async_allocator = nullptr; + //if (is_async) { + // if (node->data.fn_call_expr.async_allocator) { + // async_allocator = ir_gen_node(irb, node->data.fn_call_expr.async_allocator, scope); + // if (async_allocator == irb->codegen->invalid_instruction) + // return async_allocator; + // } + //} + + return irb.build(Inst.Call, scope, Span.token(suffix_op.rtoken), Inst.Call.Params{ + .fn_ref = fn_ref, + .args = args, + }); + //IrInstruction *fn_call = ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, FnInlineAuto, is_async, async_allocator, nullptr); + //return ir_lval_wrap(irb, scope, fn_call, lval); + } + + async fn genPtrType( + irb: *Builder, + prefix_op: *ast.Node.PrefixOp, + ptr_info: ast.Node.PrefixOp.PtrInfo, + scope: *Scope, + ) !*Inst { + // TODO port more logic + + //assert(node->type == NodeTypePointerType); + //PtrLen ptr_len = (node->data.pointer_type.star_token->id == TokenIdStar || + // node->data.pointer_type.star_token->id == TokenIdStarStar) ? PtrLenSingle : PtrLenUnknown; + //bool is_const = node->data.pointer_type.is_const; + //bool is_volatile = node->data.pointer_type.is_volatile; + //AstNode *expr_node = node->data.pointer_type.op_expr; + //AstNode *align_expr = node->data.pointer_type.align_expr; + + //IrInstruction *align_value; + //if (align_expr != nullptr) { + // align_value = ir_gen_node(irb, align_expr, scope); + // if (align_value == irb->codegen->invalid_instruction) + // return align_value; + //} else { + // align_value = nullptr; + //} + const child_type = try await (async irb.genNode(prefix_op.rhs, scope, LVal.None) catch unreachable); + + //uint32_t bit_offset_start = 0; + //if (node->data.pointer_type.bit_offset_start != nullptr) { + // if (!bigint_fits_in_bits(node->data.pointer_type.bit_offset_start, 32, false)) { + // Buf *val_buf = buf_alloc(); + // bigint_append_buf(val_buf, node->data.pointer_type.bit_offset_start, 10); + // exec_add_error_node(irb->codegen, irb->exec, node, + // buf_sprintf("value %s too large for u32 bit offset", buf_ptr(val_buf))); + // return irb->codegen->invalid_instruction; + // } + // bit_offset_start = bigint_as_unsigned(node->data.pointer_type.bit_offset_start); + //} + + //uint32_t bit_offset_end = 0; + //if (node->data.pointer_type.bit_offset_end != nullptr) { + // if (!bigint_fits_in_bits(node->data.pointer_type.bit_offset_end, 32, false)) { + // Buf *val_buf = buf_alloc(); + // bigint_append_buf(val_buf, node->data.pointer_type.bit_offset_end, 10); + // exec_add_error_node(irb->codegen, irb->exec, node, + // buf_sprintf("value %s too large for u32 bit offset", buf_ptr(val_buf))); + // return irb->codegen->invalid_instruction; + // } + // bit_offset_end = bigint_as_unsigned(node->data.pointer_type.bit_offset_end); + //} + + //if ((bit_offset_start != 0 || bit_offset_end != 0) && bit_offset_start >= bit_offset_end) { + // exec_add_error_node(irb->codegen, irb->exec, node, + // buf_sprintf("bit offset start must be less than bit offset end")); + // return irb->codegen->invalid_instruction; + //} + + return irb.build(Inst.PtrType, scope, Span.node(&prefix_op.base), Inst.PtrType.Params{ + .child_type = child_type, + .mut = Type.Pointer.Mut.Mut, + .vol = Type.Pointer.Vol.Non, + .size = Type.Pointer.Size.Many, + .alignment = null, + }); + } + fn isCompTime(irb: *Builder, target_scope: *Scope) bool { if (irb.is_comptime) return true; @@ -847,6 +1202,56 @@ pub const Builder = struct { return inst; } + pub async fn genStrLit(irb: *Builder, str_lit: *ast.Node.StringLiteral, scope: *Scope) !*Inst { + const str_token = irb.root_scope.tree.tokenSlice(str_lit.token); + const src_span = Span.token(str_lit.token); + + var bad_index: usize = undefined; + var buf = std.zig.parseStringLiteral(irb.comp.gpa(), str_token, &bad_index) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.InvalidCharacter => { + try irb.comp.addCompileError( + irb.root_scope, + src_span, + "invalid character in string literal: '{c}'", + str_token[bad_index], + ); + return error.SemanticAnalysisFailed; + }, + }; + var buf_cleaned = false; + errdefer if (!buf_cleaned) irb.comp.gpa().free(buf); + + if (str_token[0] == 'c') { + // first we add a null + buf = try irb.comp.gpa().realloc(u8, buf, buf.len + 1); + buf[buf.len - 1] = 0; + + // next make an array value + const array_val = try await (async Value.Array.createOwnedBuffer(irb.comp, buf) catch unreachable); + buf_cleaned = true; + defer array_val.base.deref(irb.comp); + + // then make a pointer value pointing at the first element + const ptr_val = try await (async Value.Ptr.createArrayElemPtr( + irb.comp, + array_val, + Type.Pointer.Mut.Const, + Type.Pointer.Size.Many, + 0, + ) catch unreachable); + defer ptr_val.base.deref(irb.comp); + + return irb.buildConstValue(scope, src_span, &ptr_val.base); + } else { + const array_val = try await (async Value.Array.createOwnedBuffer(irb.comp, buf) catch unreachable); + buf_cleaned = true; + defer array_val.base.deref(irb.comp); + + return irb.buildConstValue(scope, src_span, &array_val.base); + } + } + pub async fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Inst { const block_scope = try Scope.Block.create(irb.comp, parent_scope); @@ -911,7 +1316,10 @@ pub const Builder = struct { _ = irb.build( Inst.CheckVoidStmt, child_scope, - statement_value.span, + Span{ + .first = statement_node.firstToken(), + .last = statement_node.lastToken(), + }, Inst.CheckVoidStmt.Params{ .target = statement_value }, ); } @@ -1068,6 +1476,8 @@ pub const Builder = struct { if (result) |primitive_type| { defer primitive_type.base.deref(irb.comp); switch (lval) { + // if (lval == LValPtr) { + // return ir_build_ref(irb, scope, node, value, false, false); LVal.Ptr => return error.Unimplemented, LVal.None => return irb.buildConstValue(scope, src_span, &primitive_type.base), } @@ -1079,15 +1489,6 @@ pub const Builder = struct { }, error.OutOfMemory => return error.OutOfMemory, } - //TypeTableEntry *primitive_type = get_primitive_type(irb->codegen, variable_name); - //if (primitive_type != nullptr) { - // IrInstruction *value = ir_build_const_type(irb, scope, node, primitive_type); - // if (lval == LValPtr) { - // return ir_build_ref(irb, scope, node, value, false, false); - // } else { - // return value; - // } - //} //VariableTableEntry *var = find_variable(irb->codegen, scope, variable_name); //if (var) { @@ -1098,9 +1499,12 @@ pub const Builder = struct { // return ir_build_load_ptr(irb, scope, node, var_ptr); //} - //Tld *tld = find_decl(irb->codegen, scope, variable_name); - //if (tld) - // return ir_build_decl_ref(irb, scope, node, tld, lval); + if (await (async irb.findDecl(scope, name) catch unreachable)) |decl| { + return irb.build(Inst.DeclRef, scope, src_span, Inst.DeclRef.Params{ + .decl = decl, + .lval = lval, + }); + } //if (node->owner->any_imports_failed) { // // skip the error message since we had a failing import in this file @@ -1251,8 +1655,26 @@ pub const Builder = struct { const FieldType = comptime @typeOf(@field(I.Params(undefined), @memberName(I.Params, i))); switch (FieldType) { *Inst => @field(inst.params, @memberName(I.Params, i)).ref(self), + *BasicBlock => @field(inst.params, @memberName(I.Params, i)).ref(self), ?*Inst => if (@field(inst.params, @memberName(I.Params, i))) |other| other.ref(self), - else => {}, + []*Inst => { + // TODO https://github.com/ziglang/zig/issues/1269 + for (@field(inst.params, @memberName(I.Params, i))) |other| + other.ref(self); + }, + []*BasicBlock => { + // TODO https://github.com/ziglang/zig/issues/1269 + for (@field(inst.params, @memberName(I.Params, i))) |other| + other.ref(self); + }, + Type.Pointer.Mut, + Type.Pointer.Vol, + Type.Pointer.Size, + LVal, + *Decl, + => {}, + // it's ok to add more types here, just make sure any instructions are ref'd appropriately + else => @compileError("unrecognized type in Params: " ++ @typeName(FieldType)), } } @@ -1348,6 +1770,24 @@ pub const Builder = struct { // is_comptime); //// the above blocks are rendered by ir_gen after the rest of codegen } + + async fn findDecl(irb: *Builder, scope: *Scope, name: []const u8) ?*Decl { + var s = scope; + while (true) { + switch (s.id) { + Scope.Id.Decls => { + const decls = @fieldParentPtr(Scope.Decls, "base", s); + const table = await (async decls.getTableReadOnly() catch unreachable); + if (table.get(name)) |entry| { + return entry.value; + } + }, + Scope.Id.Root => return null, + else => {}, + } + s = s.parent.?; + } + } }; const Analyze = struct { @@ -1930,7 +2370,6 @@ const Analyze = struct { ptr_mut: Value.Ptr.Mut, mut: Type.Pointer.Mut, volatility: Type.Pointer.Vol, - ptr_align: u32, ) Analyze.Error!*Inst { return error.Unimplemented; } @@ -1945,7 +2384,7 @@ pub async fn gen( errdefer irb.abort(); const entry_block = try irb.createBasicBlock(scope, c"Entry"); - entry_block.ref(); // Entry block gets a reference because we enter it to begin. + entry_block.ref(&irb); // Entry block gets a reference because we enter it to begin. try irb.setCursorAtEndAndAppendBlock(entry_block); const result = try await (async irb.genNode(body_node, scope, LVal.None) catch unreachable); @@ -1965,7 +2404,7 @@ pub async fn analyze(comp: *Compilation, old_code: *Code, expected_type: ?*Type) errdefer ira.abort(); const new_entry_bb = try ira.getNewBasicBlock(old_entry_bb, null); - new_entry_bb.ref(); + new_entry_bb.ref(&ira.irb); ira.irb.current_basic_block = new_entry_bb; @@ -1979,7 +2418,8 @@ pub async fn analyze(comp: *Compilation, old_code: *Code, expected_type: ?*Type) continue; } - const return_inst = try old_instruction.analyze(&ira); + const return_inst = try await (async old_instruction.analyze(&ira) catch unreachable); + assert(return_inst.val != IrVal.Unknown); // at least the type should be known at this point return_inst.linkToParent(old_instruction); // Note: if we ever modify the above to handle error.CompileError by continuing analysis, // then here we want to check if ira.isCompTime() and return early if true diff --git a/src-self-hosted/llvm.zig b/src-self-hosted/llvm.zig index ade2479f41..8bb45ac616 100644 --- a/src-self-hosted/llvm.zig +++ b/src-self-hosted/llvm.zig @@ -23,12 +23,17 @@ pub const TargetMachineRef = removeNullability(c.LLVMTargetMachineRef); pub const TargetDataRef = removeNullability(c.LLVMTargetDataRef); pub const DIBuilder = c.ZigLLVMDIBuilder; +pub const ABIAlignmentOfType = c.LLVMABIAlignmentOfType; pub const AddAttributeAtIndex = c.LLVMAddAttributeAtIndex; pub const AddFunction = c.LLVMAddFunction; +pub const AddGlobal = c.LLVMAddGlobal; pub const AddModuleCodeViewFlag = c.ZigLLVMAddModuleCodeViewFlag; pub const AddModuleDebugInfoFlag = c.ZigLLVMAddModuleDebugInfoFlag; +pub const ArrayType = c.LLVMArrayType; pub const ClearCurrentDebugLocation = c.ZigLLVMClearCurrentDebugLocation; pub const ConstAllOnes = c.LLVMConstAllOnes; +pub const ConstArray = c.LLVMConstArray; +pub const ConstBitCast = c.LLVMConstBitCast; pub const ConstInt = c.LLVMConstInt; pub const ConstIntOfArbitraryPrecision = c.LLVMConstIntOfArbitraryPrecision; pub const ConstNeg = c.LLVMConstNeg; @@ -59,6 +64,7 @@ pub const GetEnumAttributeKindForName = c.LLVMGetEnumAttributeKindForName; pub const GetHostCPUName = c.ZigLLVMGetHostCPUName; pub const GetMDKindIDInContext = c.LLVMGetMDKindIDInContext; pub const GetNativeFeatures = c.ZigLLVMGetNativeFeatures; +pub const GetUndef = c.LLVMGetUndef; pub const HalfTypeInContext = c.LLVMHalfTypeInContext; pub const InitializeAllAsmParsers = c.LLVMInitializeAllAsmParsers; pub const InitializeAllAsmPrinters = c.LLVMInitializeAllAsmPrinters; @@ -81,14 +87,24 @@ pub const MDStringInContext = c.LLVMMDStringInContext; pub const MetadataTypeInContext = c.LLVMMetadataTypeInContext; pub const ModuleCreateWithNameInContext = c.LLVMModuleCreateWithNameInContext; pub const PPCFP128TypeInContext = c.LLVMPPCFP128TypeInContext; +pub const PointerType = c.LLVMPointerType; +pub const SetAlignment = c.LLVMSetAlignment; pub const SetDataLayout = c.LLVMSetDataLayout; +pub const SetGlobalConstant = c.LLVMSetGlobalConstant; +pub const SetInitializer = c.LLVMSetInitializer; +pub const SetLinkage = c.LLVMSetLinkage; pub const SetTarget = c.LLVMSetTarget; +pub const SetUnnamedAddr = c.LLVMSetUnnamedAddr; pub const StructTypeInContext = c.LLVMStructTypeInContext; pub const TokenTypeInContext = c.LLVMTokenTypeInContext; +pub const TypeOf = c.LLVMTypeOf; pub const VoidTypeInContext = c.LLVMVoidTypeInContext; pub const X86FP80TypeInContext = c.LLVMX86FP80TypeInContext; pub const X86MMXTypeInContext = c.LLVMX86MMXTypeInContext; +pub const ConstInBoundsGEP = LLVMConstInBoundsGEP; +pub extern fn LLVMConstInBoundsGEP(ConstantVal: ValueRef, ConstantIndices: [*]ValueRef, NumIndices: c_uint) ?ValueRef; + pub const GetTargetFromTriple = LLVMGetTargetFromTriple; extern fn LLVMGetTargetFromTriple(Triple: [*]const u8, T: *TargetRef, ErrorMessage: ?*[*]u8) Bool; @@ -145,13 +161,28 @@ pub const EmitBinary = EmitOutputType.ZigLLVM_EmitBinary; pub const EmitLLVMIr = EmitOutputType.ZigLLVM_EmitLLVMIr; pub const EmitOutputType = c.ZigLLVM_EmitOutputType; +pub const CCallConv = c.LLVMCCallConv; +pub const FastCallConv = c.LLVMFastCallConv; +pub const ColdCallConv = c.LLVMColdCallConv; +pub const WebKitJSCallConv = c.LLVMWebKitJSCallConv; +pub const AnyRegCallConv = c.LLVMAnyRegCallConv; +pub const X86StdcallCallConv = c.LLVMX86StdcallCallConv; +pub const X86FastcallCallConv = c.LLVMX86FastcallCallConv; +pub const CallConv = c.LLVMCallConv; + +pub const FnInline = extern enum { + Auto, + Always, + Never, +}; + fn removeNullability(comptime T: type) type { comptime assert(@typeId(T) == builtin.TypeId.Optional); return T.Child; } pub const BuildRet = LLVMBuildRet; -extern fn LLVMBuildRet(arg0: BuilderRef, V: ?ValueRef) ValueRef; +extern fn LLVMBuildRet(arg0: BuilderRef, V: ?ValueRef) ?ValueRef; pub const TargetMachineEmitToFile = ZigLLVMTargetMachineEmitToFile; extern fn ZigLLVMTargetMachineEmitToFile( @@ -163,3 +194,8 @@ extern fn ZigLLVMTargetMachineEmitToFile( is_debug: bool, is_small: bool, ) bool; + +pub const BuildCall = ZigLLVMBuildCall; +extern fn ZigLLVMBuildCall(B: BuilderRef, Fn: ValueRef, Args: [*]ValueRef, NumArgs: c_uint, CC: c_uint, fn_inline: FnInline, Name: [*]const u8) ?ValueRef; + +pub const PrivateLinkage = c.LLVMLinkage.LLVMPrivateLinkage; diff --git a/src-self-hosted/scope.zig b/src-self-hosted/scope.zig index 7733e61600..878ddc1495 100644 --- a/src-self-hosted/scope.zig +++ b/src-self-hosted/scope.zig @@ -9,6 +9,7 @@ const Value = @import("value.zig").Value; const ir = @import("ir.zig"); const Span = @import("errmsg.zig").Span; const assert = std.debug.assert; +const event = std.event; pub const Scope = struct { id: Id, @@ -123,7 +124,15 @@ pub const Scope = struct { pub const Decls = struct { base: Scope, - table: Decl.Table, + + /// The lock must be respected for writing. However once name_future resolves, + /// readers can freely access it. + table: event.Locked(Decl.Table), + + /// Once this future is resolved, the table is complete and available for unlocked + /// read-only access. It does not mean all the decls are resolved; it means only that + /// the table has all the names. Each decl in the table has its own resolution state. + name_future: event.Future(void), /// Creates a Decls scope with 1 reference pub fn create(comp: *Compilation, parent: *Scope) !*Decls { @@ -133,15 +142,10 @@ pub const Scope = struct { .parent = parent, .ref_count = 1, }, - .table = undefined, + .table = event.Locked(Decl.Table).init(comp.loop, Decl.Table.init(comp.gpa())), + .name_future = event.Future(void).init(comp.loop), }); - errdefer comp.gpa().destroy(self); - - self.table = Decl.Table.init(comp.gpa()); - errdefer self.table.deinit(); - parent.ref(); - return self; } @@ -149,6 +153,11 @@ pub const Scope = struct { self.table.deinit(); comp.gpa().destroy(self); } + + pub async fn getTableReadOnly(self: *Decls) *Decl.Table { + _ = await (async self.name_future.get() catch unreachable); + return &self.table.private_data; + } }; pub const Block = struct { diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 45e5362124..d3541c73b6 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -14,6 +14,7 @@ test "compile errors" { defer ctx.deinit(); try @import("../test/stage2/compile_errors.zig").addCases(&ctx); + //try @import("../test/stage2/compare_output.zig").addCases(&ctx); try ctx.run(); } diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index fa99586383..217c1d50a7 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -4,12 +4,17 @@ const Scope = @import("scope.zig").Scope; const Compilation = @import("compilation.zig").Compilation; const Value = @import("value.zig").Value; const llvm = @import("llvm.zig"); -const ObjectFile = @import("codegen.zig").ObjectFile; +const event = std.event; +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; pub const Type = struct { base: Value, id: Id, name: []const u8, + abi_alignment: AbiAlignment, + + pub const AbiAlignment = event.Future(error{OutOfMemory}!u32); pub const Id = builtin.TypeId; @@ -43,33 +48,37 @@ pub const Type = struct { } } - pub fn getLlvmType(base: *Type, ofile: *ObjectFile) (error{OutOfMemory}!llvm.TypeRef) { + pub fn getLlvmType( + base: *Type, + allocator: *Allocator, + llvm_context: llvm.ContextRef, + ) (error{OutOfMemory}!llvm.TypeRef) { switch (base.id) { - Id.Struct => return @fieldParentPtr(Struct, "base", base).getLlvmType(ofile), - Id.Fn => return @fieldParentPtr(Fn, "base", base).getLlvmType(ofile), + Id.Struct => return @fieldParentPtr(Struct, "base", base).getLlvmType(allocator, llvm_context), + Id.Fn => return @fieldParentPtr(Fn, "base", base).getLlvmType(allocator, llvm_context), Id.Type => unreachable, Id.Void => unreachable, - Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmType(ofile), + Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmType(allocator, llvm_context), Id.NoReturn => unreachable, - Id.Int => return @fieldParentPtr(Int, "base", base).getLlvmType(ofile), - Id.Float => return @fieldParentPtr(Float, "base", base).getLlvmType(ofile), - Id.Pointer => return @fieldParentPtr(Pointer, "base", base).getLlvmType(ofile), - Id.Array => return @fieldParentPtr(Array, "base", base).getLlvmType(ofile), + Id.Int => return @fieldParentPtr(Int, "base", base).getLlvmType(allocator, llvm_context), + Id.Float => return @fieldParentPtr(Float, "base", base).getLlvmType(allocator, llvm_context), + Id.Pointer => return @fieldParentPtr(Pointer, "base", base).getLlvmType(allocator, llvm_context), + Id.Array => return @fieldParentPtr(Array, "base", base).getLlvmType(allocator, llvm_context), Id.ComptimeFloat => unreachable, Id.ComptimeInt => unreachable, Id.Undefined => unreachable, Id.Null => unreachable, - Id.Optional => return @fieldParentPtr(Optional, "base", base).getLlvmType(ofile), - Id.ErrorUnion => return @fieldParentPtr(ErrorUnion, "base", base).getLlvmType(ofile), - Id.ErrorSet => return @fieldParentPtr(ErrorSet, "base", base).getLlvmType(ofile), - Id.Enum => return @fieldParentPtr(Enum, "base", base).getLlvmType(ofile), - Id.Union => return @fieldParentPtr(Union, "base", base).getLlvmType(ofile), + Id.Optional => return @fieldParentPtr(Optional, "base", base).getLlvmType(allocator, llvm_context), + Id.ErrorUnion => return @fieldParentPtr(ErrorUnion, "base", base).getLlvmType(allocator, llvm_context), + Id.ErrorSet => return @fieldParentPtr(ErrorSet, "base", base).getLlvmType(allocator, llvm_context), + Id.Enum => return @fieldParentPtr(Enum, "base", base).getLlvmType(allocator, llvm_context), + Id.Union => return @fieldParentPtr(Union, "base", base).getLlvmType(allocator, llvm_context), Id.Namespace => unreachable, Id.Block => unreachable, - Id.BoundFn => return @fieldParentPtr(BoundFn, "base", base).getLlvmType(ofile), + Id.BoundFn => return @fieldParentPtr(BoundFn, "base", base).getLlvmType(allocator, llvm_context), Id.ArgTuple => unreachable, - Id.Opaque => return @fieldParentPtr(Opaque, "base", base).getLlvmType(ofile), - Id.Promise => return @fieldParentPtr(Promise, "base", base).getLlvmType(ofile), + Id.Opaque => return @fieldParentPtr(Opaque, "base", base).getLlvmType(allocator, llvm_context), + Id.Promise => return @fieldParentPtr(Promise, "base", base).getLlvmType(allocator, llvm_context), } } @@ -156,16 +165,45 @@ pub const Type = struct { base.* = Type{ .base = Value{ .id = Value.Id.Type, - .typeof = &MetaType.get(comp).base, + .typ = &MetaType.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, .id = id, .name = name, + .abi_alignment = AbiAlignment.init(comp.loop), }; } - pub fn getAbiAlignment(base: *Type, comp: *Compilation) u32 { - @panic("TODO getAbiAlignment"); + /// If you happen to have an llvm context handy, use getAbiAlignmentInContext instead. + /// Otherwise, this one will grab one from the pool and then release it. + pub async fn getAbiAlignment(base: *Type, comp: *Compilation) !u32 { + if (await (async base.abi_alignment.start() catch unreachable)) |ptr| return ptr.*; + + { + const held = try comp.event_loop_local.getAnyLlvmContext(); + defer held.release(comp.event_loop_local); + + const llvm_context = held.node.data; + + base.abi_alignment.data = await (async base.resolveAbiAlignment(comp, llvm_context) catch unreachable); + } + base.abi_alignment.resolve(); + return base.abi_alignment.data; + } + + /// If you have an llvm conext handy, you can use it here. + pub async fn getAbiAlignmentInContext(base: *Type, comp: *Compilation, llvm_context: llvm.ContextRef) !u32 { + if (await (async base.abi_alignment.start() catch unreachable)) |ptr| return ptr.*; + + base.abi_alignment.data = await (async base.resolveAbiAlignment(comp, llvm_context) catch unreachable); + base.abi_alignment.resolve(); + return base.abi_alignment.data; + } + + /// Lower level function that does the work. See getAbiAlignment. + async fn resolveAbiAlignment(base: *Type, comp: *Compilation, llvm_context: llvm.ContextRef) !u32 { + const llvm_type = try base.getLlvmType(comp.gpa(), llvm_context); + return @intCast(u32, llvm.ABIAlignmentOfType(comp.target_data_ref, llvm_type)); } pub const Struct = struct { @@ -176,7 +214,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Struct, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Struct, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -189,7 +227,7 @@ pub const Type = struct { pub const Param = struct { is_noalias: bool, - typeof: *Type, + typ: *Type, }; pub fn create(comp: *Compilation, return_type: *Type, params: []Param, is_var_args: bool) !*Fn { @@ -205,7 +243,7 @@ pub const Type = struct { result.return_type.base.ref(); for (result.params) |param| { - param.typeof.base.ref(); + param.typ.base.ref(); } return result; } @@ -213,20 +251,20 @@ pub const Type = struct { pub fn destroy(self: *Fn, comp: *Compilation) void { self.return_type.base.deref(comp); for (self.params) |param| { - param.typeof.base.deref(comp); + param.typ.base.deref(comp); } comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Fn, ofile: *ObjectFile) !llvm.TypeRef { + pub fn getLlvmType(self: *Fn, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef { const llvm_return_type = switch (self.return_type.id) { - Type.Id.Void => llvm.VoidTypeInContext(ofile.context) orelse return error.OutOfMemory, - else => try self.return_type.getLlvmType(ofile), + Type.Id.Void => llvm.VoidTypeInContext(llvm_context) orelse return error.OutOfMemory, + else => try self.return_type.getLlvmType(allocator, llvm_context), }; - const llvm_param_types = try ofile.gpa().alloc(llvm.TypeRef, self.params.len); - defer ofile.gpa().free(llvm_param_types); + const llvm_param_types = try allocator.alloc(llvm.TypeRef, self.params.len); + defer allocator.free(llvm_param_types); for (llvm_param_types) |*llvm_param_type, i| { - llvm_param_type.* = try self.params[i].typeof.getLlvmType(ofile); + llvm_param_type.* = try self.params[i].typ.getLlvmType(allocator, llvm_context); } return llvm.FunctionType( @@ -280,7 +318,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Bool, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Bool, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -318,6 +356,11 @@ pub const Type = struct { } }; + pub fn get_u8(comp: *Compilation) *Int { + comp.u8_type.base.base.ref(); + return comp.u8_type; + } + pub async fn get(comp: *Compilation, key: Key) !*Int { { const held = await (async comp.int_type_table.acquire() catch unreachable); @@ -371,8 +414,8 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Int, ofile: *ObjectFile) !llvm.TypeRef { - return llvm.IntTypeInContext(ofile.context, self.key.bit_count) orelse return error.OutOfMemory; + pub fn getLlvmType(self: *Int, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef { + return llvm.IntTypeInContext(llvm_context, self.key.bit_count) orelse return error.OutOfMemory; } }; @@ -383,56 +426,236 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Float, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Float, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; pub const Pointer = struct { base: Type, - mut: Mut, - vol: Vol, - size: Size, - alignment: u32, + key: Key, + garbage_node: std.atomic.Stack(*Pointer).Node, + + pub const Key = struct { + child_type: *Type, + mut: Mut, + vol: Vol, + size: Size, + alignment: Align, + + pub fn hash(self: *const Key) u32 { + const align_hash = switch (self.alignment) { + Align.Abi => 0xf201c090, + Align.Override => |x| x, + }; + return hash_usize(@ptrToInt(self.child_type)) *% + hash_enum(self.mut) *% + hash_enum(self.vol) *% + hash_enum(self.size) *% + align_hash; + } + + pub fn eql(self: *const Key, other: *const Key) bool { + if (self.child_type != other.child_type or + self.mut != other.mut or + self.vol != other.vol or + self.size != other.size or + @TagType(Align)(self.alignment) != @TagType(Align)(other.alignment)) + { + return false; + } + switch (self.alignment) { + Align.Abi => return true, + Align.Override => |x| return x == other.alignment.Override, + } + } + }; pub const Mut = enum { Mut, Const, }; + pub const Vol = enum { Non, Volatile, }; + + pub const Align = union(enum) { + Abi, + Override: u32, + }; + pub const Size = builtin.TypeInfo.Pointer.Size; pub fn destroy(self: *Pointer, comp: *Compilation) void { + self.garbage_node = std.atomic.Stack(*Pointer).Node{ + .data = self, + .next = undefined, + }; + comp.registerGarbage(Pointer, &self.garbage_node); + } + + pub async fn gcDestroy(self: *Pointer, comp: *Compilation) void { + { + const held = await (async comp.ptr_type_table.acquire() catch unreachable); + defer held.release(); + + _ = held.value.remove(&self.key).?; + } + self.key.child_type.base.deref(comp); comp.gpa().destroy(self); } - pub fn get( + pub async fn getAlignAsInt(self: *Pointer, comp: *Compilation) u32 { + switch (self.key.alignment) { + Align.Abi => return await (async self.key.child_type.getAbiAlignment(comp) catch unreachable), + Align.Override => |alignment| return alignment, + } + } + + pub async fn get( comp: *Compilation, - elem_type: *Type, - mut: Mut, - vol: Vol, - size: Size, - alignment: u32, - ) *Pointer { - @panic("TODO get pointer"); + key: Key, + ) !*Pointer { + var normal_key = key; + switch (key.alignment) { + Align.Abi => {}, + Align.Override => |alignment| { + const abi_align = try await (async key.child_type.getAbiAlignment(comp) catch unreachable); + if (abi_align == alignment) { + normal_key.alignment = Align.Abi; + } + }, + } + { + const held = await (async comp.ptr_type_table.acquire() catch unreachable); + defer held.release(); + + if (held.value.get(&normal_key)) |entry| { + entry.value.base.base.ref(); + return entry.value; + } + } + + const self = try comp.gpa().create(Pointer{ + .base = undefined, + .key = normal_key, + .garbage_node = undefined, + }); + errdefer comp.gpa().destroy(self); + + const size_str = switch (self.key.size) { + Size.One => "*", + Size.Many => "[*]", + Size.Slice => "[]", + }; + const mut_str = switch (self.key.mut) { + Mut.Const => "const ", + Mut.Mut => "", + }; + const vol_str = switch (self.key.vol) { + Vol.Volatile => "volatile ", + Vol.Non => "", + }; + const name = switch (self.key.alignment) { + Align.Abi => try std.fmt.allocPrint( + comp.gpa(), + "{}{}{}{}", + size_str, + mut_str, + vol_str, + self.key.child_type.name, + ), + Align.Override => |alignment| try std.fmt.allocPrint( + comp.gpa(), + "{}align<{}> {}{}{}", + size_str, + alignment, + mut_str, + vol_str, + self.key.child_type.name, + ), + }; + errdefer comp.gpa().free(name); + + self.base.init(comp, Id.Pointer, name); + + { + const held = await (async comp.ptr_type_table.acquire() catch unreachable); + defer held.release(); + + _ = try held.value.put(&self.key, self); + } + return self; } - pub fn getLlvmType(self: *Pointer, ofile: *ObjectFile) llvm.TypeRef { - @panic("TODO"); + pub fn getLlvmType(self: *Pointer, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef { + const elem_llvm_type = try self.key.child_type.getLlvmType(allocator, llvm_context); + return llvm.PointerType(elem_llvm_type, 0) orelse return error.OutOfMemory; } }; pub const Array = struct { base: Type, + key: Key, + garbage_node: std.atomic.Stack(*Array).Node, + + pub const Key = struct { + elem_type: *Type, + len: usize, + + pub fn hash(self: *const Key) u32 { + return hash_usize(@ptrToInt(self.elem_type)) *% hash_usize(self.len); + } + + pub fn eql(self: *const Key, other: *const Key) bool { + return self.elem_type == other.elem_type and self.len == other.len; + } + }; pub fn destroy(self: *Array, comp: *Compilation) void { + self.key.elem_type.base.deref(comp); comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Array, ofile: *ObjectFile) llvm.TypeRef { - @panic("TODO"); + pub async fn get(comp: *Compilation, key: Key) !*Array { + key.elem_type.base.ref(); + errdefer key.elem_type.base.deref(comp); + + { + const held = await (async comp.array_type_table.acquire() catch unreachable); + defer held.release(); + + if (held.value.get(&key)) |entry| { + entry.value.base.base.ref(); + return entry.value; + } + } + + const self = try comp.gpa().create(Array{ + .base = undefined, + .key = key, + .garbage_node = undefined, + }); + errdefer comp.gpa().destroy(self); + + const name = try std.fmt.allocPrint(comp.gpa(), "[{}]{}", key.len, key.elem_type.name); + errdefer comp.gpa().free(name); + + self.base.init(comp, Id.Array, name); + + { + const held = await (async comp.array_type_table.acquire() catch unreachable); + defer held.release(); + + _ = try held.value.put(&self.key, self); + } + return self; + } + + pub fn getLlvmType(self: *Array, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef { + const elem_llvm_type = try self.key.elem_type.getLlvmType(allocator, llvm_context); + return llvm.ArrayType(elem_llvm_type, @intCast(c_uint, self.key.len)) orelse return error.OutOfMemory; } }; @@ -481,7 +704,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Optional, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Optional, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -493,7 +716,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *ErrorUnion, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *ErrorUnion, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -505,7 +728,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *ErrorSet, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *ErrorSet, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -517,7 +740,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Enum, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Enum, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -529,7 +752,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Union, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Union, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -557,7 +780,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *BoundFn, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *BoundFn, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -577,7 +800,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Opaque, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Opaque, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -589,8 +812,33 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Promise, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Promise, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; }; + +fn hash_usize(x: usize) u32 { + return switch (@sizeOf(usize)) { + 4 => x, + 8 => @truncate(u32, x *% 0xad44ee2d8e3fc13d), + else => @compileError("implement this hash function"), + }; +} + +fn hash_enum(x: var) u32 { + const rands = []u32{ + 0x85ebf64f, + 0x3fcb3211, + 0x240a4e8e, + 0x40bb0e3c, + 0x78be45af, + 0x1ca98e37, + 0xec56053a, + 0x906adc48, + 0xd4fe9763, + 0x54c80dac, + }; + comptime assert(@memberCount(@typeOf(x)) < rands.len); + return rands[@enumToInt(x)]; +} diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index 16c1488838..2005e3c119 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -11,7 +11,7 @@ const assert = std.debug.assert; /// If there is only 1 ref then write need not copy pub const Value = struct { id: Id, - typeof: *Type, + typ: *Type, ref_count: std.atomic.Int(usize), /// Thread-safe @@ -22,23 +22,25 @@ pub const Value = struct { /// Thread-safe pub fn deref(base: *Value, comp: *Compilation) void { if (base.ref_count.decr() == 1) { - base.typeof.base.deref(comp); + base.typ.base.deref(comp); switch (base.id) { Id.Type => @fieldParentPtr(Type, "base", base).destroy(comp), Id.Fn => @fieldParentPtr(Fn, "base", base).destroy(comp), + Id.FnProto => @fieldParentPtr(FnProto, "base", base).destroy(comp), Id.Void => @fieldParentPtr(Void, "base", base).destroy(comp), Id.Bool => @fieldParentPtr(Bool, "base", base).destroy(comp), Id.NoReturn => @fieldParentPtr(NoReturn, "base", base).destroy(comp), Id.Ptr => @fieldParentPtr(Ptr, "base", base).destroy(comp), Id.Int => @fieldParentPtr(Int, "base", base).destroy(comp), + Id.Array => @fieldParentPtr(Array, "base", base).destroy(comp), } } } pub fn setType(base: *Value, new_type: *Type, comp: *Compilation) void { - base.typeof.base.deref(comp); + base.typ.base.deref(comp); new_type.base.ref(); - base.typeof = new_type; + base.typ = new_type; } pub fn getRef(base: *Value) *Value { @@ -59,11 +61,13 @@ pub const Value = struct { switch (base.id) { Id.Type => unreachable, Id.Fn => @panic("TODO"), + Id.FnProto => return @fieldParentPtr(FnProto, "base", base).getLlvmConst(ofile), Id.Void => return null, Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmConst(ofile), Id.NoReturn => unreachable, - Id.Ptr => @panic("TODO"), + Id.Ptr => return @fieldParentPtr(Ptr, "base", base).getLlvmConst(ofile), Id.Int => return @fieldParentPtr(Int, "base", base).getLlvmConst(ofile), + Id.Array => return @fieldParentPtr(Array, "base", base).getLlvmConst(ofile), } } @@ -81,26 +85,87 @@ pub const Value = struct { switch (base.id) { Id.Type => unreachable, Id.Fn => unreachable, + Id.FnProto => unreachable, Id.Void => unreachable, Id.Bool => unreachable, Id.NoReturn => unreachable, Id.Ptr => unreachable, + Id.Array => unreachable, Id.Int => return &(try @fieldParentPtr(Int, "base", base).copy(comp)).base, } } + pub const Parent = union(enum) { + None, + BaseStruct: BaseStruct, + BaseArray: BaseArray, + BaseUnion: *Value, + BaseScalar: *Value, + + pub const BaseStruct = struct { + val: *Value, + field_index: usize, + }; + + pub const BaseArray = struct { + val: *Value, + elem_index: usize, + }; + }; + pub const Id = enum { Type, Fn, Void, Bool, NoReturn, + Array, Ptr, Int, + FnProto, }; pub const Type = @import("type.zig").Type; + pub const FnProto = struct { + base: Value, + + /// The main external name that is used in the .o file. + /// TODO https://github.com/ziglang/zig/issues/265 + symbol_name: Buffer, + + pub fn create(comp: *Compilation, fn_type: *Type.Fn, symbol_name: Buffer) !*FnProto { + const self = try comp.gpa().create(FnProto{ + .base = Value{ + .id = Value.Id.FnProto, + .typ = &fn_type.base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .symbol_name = symbol_name, + }); + fn_type.base.base.ref(); + return self; + } + + pub fn destroy(self: *FnProto, comp: *Compilation) void { + self.symbol_name.deinit(); + comp.gpa().destroy(self); + } + + pub fn getLlvmConst(self: *FnProto, ofile: *ObjectFile) !?llvm.ValueRef { + const llvm_fn_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context); + const llvm_fn = llvm.AddFunction( + ofile.module, + self.symbol_name.ptr(), + llvm_fn_type, + ) orelse return error.OutOfMemory; + + // TODO port more logic from codegen.cpp:fn_llvm_value + + return llvm_fn; + } + }; + pub const Fn = struct { base: Value, @@ -135,7 +200,7 @@ pub const Value = struct { const self = try comp.gpa().create(Fn{ .base = Value{ .id = Value.Id.Fn, - .typeof = &fn_type.base, + .typ = &fn_type.base, .ref_count = std.atomic.Int(usize).init(1), }, .fndef_scope = fndef_scope, @@ -224,6 +289,8 @@ pub const Value = struct { pub const Ptr = struct { base: Value, + special: Special, + mut: Mut, pub const Mut = enum { CompTimeConst, @@ -231,25 +298,210 @@ pub const Value = struct { RunTime, }; + pub const Special = union(enum) { + Scalar: *Value, + BaseArray: BaseArray, + BaseStruct: BaseStruct, + HardCodedAddr: u64, + Discard, + }; + + pub const BaseArray = struct { + val: *Value, + elem_index: usize, + }; + + pub const BaseStruct = struct { + val: *Value, + field_index: usize, + }; + + pub async fn createArrayElemPtr( + comp: *Compilation, + array_val: *Array, + mut: Type.Pointer.Mut, + size: Type.Pointer.Size, + elem_index: usize, + ) !*Ptr { + array_val.base.ref(); + errdefer array_val.base.deref(comp); + + const elem_type = array_val.base.typ.cast(Type.Array).?.key.elem_type; + const ptr_type = try await (async Type.Pointer.get(comp, Type.Pointer.Key{ + .child_type = elem_type, + .mut = mut, + .vol = Type.Pointer.Vol.Non, + .size = size, + .alignment = Type.Pointer.Align.Abi, + }) catch unreachable); + var ptr_type_consumed = false; + errdefer if (!ptr_type_consumed) ptr_type.base.base.deref(comp); + + const self = try comp.gpa().create(Value.Ptr{ + .base = Value{ + .id = Value.Id.Ptr, + .typ = &ptr_type.base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .special = Special{ + .BaseArray = BaseArray{ + .val = &array_val.base, + .elem_index = 0, + }, + }, + .mut = Mut.CompTimeConst, + }); + ptr_type_consumed = true; + errdefer comp.gpa().destroy(self); + + return self; + } + pub fn destroy(self: *Ptr, comp: *Compilation) void { comp.gpa().destroy(self); } + + pub fn getLlvmConst(self: *Ptr, ofile: *ObjectFile) !?llvm.ValueRef { + const llvm_type = self.base.typ.getLlvmType(ofile.arena, ofile.context); + // TODO carefully port the logic from codegen.cpp:gen_const_val_ptr + switch (self.special) { + Special.Scalar => |scalar| @panic("TODO"), + Special.BaseArray => |base_array| { + // TODO put this in one .o file only, and after that, generate extern references to it + const array_llvm_value = (try base_array.val.getLlvmConst(ofile)).?; + const ptr_bit_count = ofile.comp.target_ptr_bits; + const usize_llvm_type = llvm.IntTypeInContext(ofile.context, ptr_bit_count) orelse return error.OutOfMemory; + const indices = []llvm.ValueRef{ + llvm.ConstNull(usize_llvm_type) orelse return error.OutOfMemory, + llvm.ConstInt(usize_llvm_type, base_array.elem_index, 0) orelse return error.OutOfMemory, + }; + return llvm.ConstInBoundsGEP( + array_llvm_value, + &indices, + @intCast(c_uint, indices.len), + ) orelse return error.OutOfMemory; + }, + Special.BaseStruct => |base_struct| @panic("TODO"), + Special.HardCodedAddr => |addr| @panic("TODO"), + Special.Discard => unreachable, + } + } + }; + + pub const Array = struct { + base: Value, + special: Special, + + pub const Special = union(enum) { + Undefined, + OwnedBuffer: []u8, + Explicit: Data, + }; + + pub const Data = struct { + parent: Parent, + elements: []*Value, + }; + + /// Takes ownership of buffer + pub async fn createOwnedBuffer(comp: *Compilation, buffer: []u8) !*Array { + const u8_type = Type.Int.get_u8(comp); + defer u8_type.base.base.deref(comp); + + const array_type = try await (async Type.Array.get(comp, Type.Array.Key{ + .elem_type = &u8_type.base, + .len = buffer.len, + }) catch unreachable); + errdefer array_type.base.base.deref(comp); + + const self = try comp.gpa().create(Value.Array{ + .base = Value{ + .id = Value.Id.Array, + .typ = &array_type.base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .special = Special{ .OwnedBuffer = buffer }, + }); + errdefer comp.gpa().destroy(self); + + return self; + } + + pub fn destroy(self: *Array, comp: *Compilation) void { + switch (self.special) { + Special.Undefined => {}, + Special.OwnedBuffer => |buf| { + comp.gpa().free(buf); + }, + Special.Explicit => {}, + } + comp.gpa().destroy(self); + } + + pub fn getLlvmConst(self: *Array, ofile: *ObjectFile) !?llvm.ValueRef { + switch (self.special) { + Special.Undefined => { + const llvm_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context); + return llvm.GetUndef(llvm_type); + }, + Special.OwnedBuffer => |buf| { + const dont_null_terminate = 1; + const llvm_str_init = llvm.ConstStringInContext( + ofile.context, + buf.ptr, + @intCast(c_uint, buf.len), + dont_null_terminate, + ) orelse return error.OutOfMemory; + const str_init_type = llvm.TypeOf(llvm_str_init); + const global = llvm.AddGlobal(ofile.module, str_init_type, c"") orelse return error.OutOfMemory; + llvm.SetInitializer(global, llvm_str_init); + llvm.SetLinkage(global, llvm.PrivateLinkage); + llvm.SetGlobalConstant(global, 1); + llvm.SetUnnamedAddr(global, 1); + llvm.SetAlignment(global, llvm.ABIAlignmentOfType(ofile.comp.target_data_ref, str_init_type)); + return global; + }, + Special.Explicit => @panic("TODO"), + } + + //{ + // uint64_t len = type_entry->data.array.len; + // if (const_val->data.x_array.special == ConstArraySpecialUndef) { + // return LLVMGetUndef(type_entry->type_ref); + // } + + // LLVMValueRef *values = allocate(len); + // LLVMTypeRef element_type_ref = type_entry->data.array.child_type->type_ref; + // bool make_unnamed_struct = false; + // for (uint64_t i = 0; i < len; i += 1) { + // ConstExprValue *elem_value = &const_val->data.x_array.s_none.elements[i]; + // LLVMValueRef val = gen_const_val(g, elem_value, ""); + // values[i] = val; + // make_unnamed_struct = make_unnamed_struct || is_llvm_value_unnamed_type(elem_value->type, val); + // } + // if (make_unnamed_struct) { + // return LLVMConstStruct(values, len, true); + // } else { + // return LLVMConstArray(element_type_ref, values, (unsigned)len); + // } + //} + } }; pub const Int = struct { base: Value, big_int: std.math.big.Int, - pub fn createFromString(comp: *Compilation, typeof: *Type, base: u8, value: []const u8) !*Int { + pub fn createFromString(comp: *Compilation, typ: *Type, base: u8, value: []const u8) !*Int { const self = try comp.gpa().create(Value.Int{ .base = Value{ .id = Value.Id.Int, - .typeof = typeof, + .typ = typ, .ref_count = std.atomic.Int(usize).init(1), }, .big_int = undefined, }); - typeof.base.ref(); + typ.base.ref(); errdefer comp.gpa().destroy(self); self.big_int = try std.math.big.Int.init(comp.gpa()); @@ -261,9 +513,9 @@ pub const Value = struct { } pub fn getLlvmConst(self: *Int, ofile: *ObjectFile) !?llvm.ValueRef { - switch (self.base.typeof.id) { + switch (self.base.typ.id) { Type.Id.Int => { - const type_ref = try self.base.typeof.getLlvmType(ofile); + const type_ref = try self.base.typ.getLlvmType(ofile.arena, ofile.context); if (self.big_int.len == 0) { return llvm.ConstNull(type_ref); } @@ -286,13 +538,13 @@ pub const Value = struct { } pub fn copy(old: *Int, comp: *Compilation) !*Int { - old.base.typeof.base.ref(); - errdefer old.base.typeof.base.deref(comp); + old.base.typ.base.ref(); + errdefer old.base.typ.base.deref(comp); const new = try comp.gpa().create(Value.Int{ .base = Value{ .id = Value.Id.Int, - .typeof = old.base.typeof, + .typ = old.base.typ, .ref_count = std.atomic.Int(usize).init(1), }, .big_int = undefined, diff --git a/std/event/group.zig b/std/event/group.zig index fee31a56a6..26c098399e 100644 --- a/std/event/group.zig +++ b/std/event/group.zig @@ -76,6 +76,7 @@ pub fn Group(comptime ReturnType: type) type { /// Wait for all the calls and promises of the group to complete. /// Thread-safe. + /// Safe to call any number of times. pub async fn wait(self: *Self) ReturnType { // TODO catch unreachable because the allocation can be grouped with // the coro frame allocation diff --git a/std/zig/index.zig b/std/zig/index.zig index 4dd68fa8b3..da84bc5bb0 100644 --- a/std/zig/index.zig +++ b/std/zig/index.zig @@ -2,6 +2,7 @@ const tokenizer = @import("tokenizer.zig"); pub const Token = tokenizer.Token; pub const Tokenizer = tokenizer.Tokenizer; pub const parse = @import("parse.zig").parse; +pub const parseStringLiteral = @import("parse_string_literal.zig").parseStringLiteral; pub const render = @import("render.zig").render; pub const ast = @import("ast.zig"); @@ -10,4 +11,6 @@ test "std.zig tests" { _ = @import("parse.zig"); _ = @import("render.zig"); _ = @import("tokenizer.zig"); + _ = @import("parse_string_literal.zig"); } + diff --git a/std/zig/parse_string_literal.zig b/std/zig/parse_string_literal.zig new file mode 100644 index 0000000000..00c92a7651 --- /dev/null +++ b/std/zig/parse_string_literal.zig @@ -0,0 +1,76 @@ +const std = @import("../index.zig"); +const assert = std.debug.assert; + +const State = enum { + Start, + Backslash, +}; + +pub const ParseStringLiteralError = error{ + OutOfMemory, + + /// When this is returned, index will be the position of the character. + InvalidCharacter, +}; + +/// caller owns returned memory +pub fn parseStringLiteral( + allocator: *std.mem.Allocator, + bytes: []const u8, + bad_index: *usize, // populated if error.InvalidCharacter is returned +) ParseStringLiteralError![]u8 { + const first_index = if (bytes[0] == 'c') usize(2) else usize(1); + assert(bytes[bytes.len - 1] == '"'); + + var list = std.ArrayList(u8).init(allocator); + errdefer list.deinit(); + + const slice = bytes[first_index..]; + try list.ensureCapacity(slice.len - 1); + + var state = State.Start; + for (slice) |b, index| { + switch (state) { + State.Start => switch (b) { + '\\' => state = State.Backslash, + '\n' => { + bad_index.* = index; + return error.InvalidCharacter; + }, + '"' => return list.toOwnedSlice(), + else => try list.append(b), + }, + State.Backslash => switch (b) { + 'x' => @panic("TODO"), + 'u' => @panic("TODO"), + 'U' => @panic("TODO"), + 'n' => { + try list.append('\n'); + state = State.Start; + }, + 'r' => { + try list.append('\r'); + state = State.Start; + }, + '\\' => { + try list.append('\\'); + state = State.Start; + }, + 't' => { + try list.append('\t'); + state = State.Start; + }, + '"' => { + try list.append('"'); + state = State.Start; + }, + else => { + bad_index.* = index; + return error.InvalidCharacter; + }, + }, + else => unreachable, + } + } + unreachable; +} diff --git a/std/zig/tokenizer.zig b/std/zig/tokenizer.zig index 79f1871b64..3c7ab1f0a8 100644 --- a/std/zig/tokenizer.zig +++ b/std/zig/tokenizer.zig @@ -73,6 +73,7 @@ pub const Token = struct { return null; } + /// TODO remove this enum const StrLitKind = enum { Normal, C, -- cgit v1.2.3