From 278829fc2cc23e55b09915ce07ce1ec2dbf7e68b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 14 Jul 2018 15:45:15 -0400 Subject: self-hosted: adding a fn to an llvm module --- src-self-hosted/codegen.zig | 61 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src-self-hosted/codegen.zig (limited to 'src-self-hosted/codegen.zig') diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig new file mode 100644 index 0000000000..df8f451856 --- /dev/null +++ b/src-self-hosted/codegen.zig @@ -0,0 +1,61 @@ +const std = @import("std"); +// TODO codegen pretends that Module is renamed to Build because I plan to +// do that refactor at some point +const Build = @import("module.zig").Module; +// 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 ir = @import("ir.zig"); +const Value = @import("value.zig").Value; +const Type = @import("type.zig").Type; +const event = std.event; + +pub async fn renderToLlvm(build: *Build, fn_val: *Value.Fn, code: *ir.Code) !void { + fn_val.base.ref(); + defer fn_val.base.deref(build); + defer code.destroy(build.a()); + + const llvm_handle = try build.event_loop_local.getAnyLlvmContext(); + defer llvm_handle.release(build.event_loop_local); + + const context = llvm_handle.node.data; + + const module = llvm.ModuleCreateWithNameInContext(build.name.ptr(), context) orelse return error.OutOfMemory; + defer llvm.DisposeModule(module); + + const builder = llvm.CreateBuilderInContext(context) orelse return error.OutOfMemory; + defer llvm.DisposeBuilder(builder); + + var cunit = CompilationUnit{ + .build = build, + .module = module, + .builder = builder, + .context = context, + .lock = event.Lock.init(build.loop), + }; + + try renderToLlvmModule(&cunit, fn_val, code); + + if (build.verbose_llvm_ir) { + llvm.DumpModule(cunit.module); + } +} + +pub const CompilationUnit = struct { + build: *Build, + module: llvm.ModuleRef, + builder: llvm.BuilderRef, + context: llvm.ContextRef, + lock: event.Lock, + + fn a(self: *CompilationUnit) *std.mem.Allocator { + return self.build.a(); + } +}; + +pub fn renderToLlvmModule(cunit: *CompilationUnit, 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(cunit); + const llvm_fn = llvm.AddFunction(cunit.module, fn_val.symbol_name.ptr(), llvm_fn_type); +} -- cgit v1.2.3 From 28c3d4809bc6d497ac81892bc7eb03b95d8c2b32 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 14 Jul 2018 16:12:41 -0400 Subject: rename Module to Compilation and CompilationUnit to ObjectFile --- src-self-hosted/codegen.zig | 42 ++- src-self-hosted/compilation.zig | 747 ++++++++++++++++++++++++++++++++++++++++ src-self-hosted/decl.zig | 4 +- src-self-hosted/ir.zig | 56 +-- src-self-hosted/main.zig | 114 +++--- src-self-hosted/module.zig | 747 ---------------------------------------- src-self-hosted/scope.zig | 72 ++-- src-self-hosted/test.zig | 26 +- src-self-hosted/type.zig | 268 +++++++------- src-self-hosted/value.zig | 66 ++-- 10 files changed, 1070 insertions(+), 1072 deletions(-) create mode 100644 src-self-hosted/compilation.zig delete mode 100644 src-self-hosted/module.zig (limited to 'src-self-hosted/codegen.zig') diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index df8f451856..a07485e74e 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -1,7 +1,5 @@ const std = @import("std"); -// TODO codegen pretends that Module is renamed to Build because I plan to -// do that refactor at some point -const Build = @import("module.zig").Module; +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 @@ -11,51 +9,51 @@ const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; const event = std.event; -pub async fn renderToLlvm(build: *Build, fn_val: *Value.Fn, code: *ir.Code) !void { +pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) !void { fn_val.base.ref(); - defer fn_val.base.deref(build); - defer code.destroy(build.a()); + defer fn_val.base.deref(comp); + defer code.destroy(comp.a()); - const llvm_handle = try build.event_loop_local.getAnyLlvmContext(); - defer llvm_handle.release(build.event_loop_local); + const llvm_handle = try comp.event_loop_local.getAnyLlvmContext(); + defer llvm_handle.release(comp.event_loop_local); const context = llvm_handle.node.data; - const module = llvm.ModuleCreateWithNameInContext(build.name.ptr(), context) orelse return error.OutOfMemory; + const module = llvm.ModuleCreateWithNameInContext(comp.name.ptr(), context) orelse return error.OutOfMemory; defer llvm.DisposeModule(module); const builder = llvm.CreateBuilderInContext(context) orelse return error.OutOfMemory; defer llvm.DisposeBuilder(builder); - var cunit = CompilationUnit{ - .build = build, + var ofile = ObjectFile{ + .comp = comp, .module = module, .builder = builder, .context = context, - .lock = event.Lock.init(build.loop), + .lock = event.Lock.init(comp.loop), }; - try renderToLlvmModule(&cunit, fn_val, code); + try renderToLlvmModule(&ofile, fn_val, code); - if (build.verbose_llvm_ir) { - llvm.DumpModule(cunit.module); + if (comp.verbose_llvm_ir) { + llvm.DumpModule(ofile.module); } } -pub const CompilationUnit = struct { - build: *Build, +pub const ObjectFile = struct { + comp: *Compilation, module: llvm.ModuleRef, builder: llvm.BuilderRef, context: llvm.ContextRef, lock: event.Lock, - fn a(self: *CompilationUnit) *std.mem.Allocator { - return self.build.a(); + fn a(self: *ObjectFile) *std.mem.Allocator { + return self.comp.a(); } }; -pub fn renderToLlvmModule(cunit: *CompilationUnit, fn_val: *Value.Fn, code: *ir.Code) !void { +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(cunit); - const llvm_fn = llvm.AddFunction(cunit.module, fn_val.symbol_name.ptr(), llvm_fn_type); + const llvm_fn_type = try fn_val.base.typeof.getLlvmType(ofile); + const llvm_fn = llvm.AddFunction(ofile.module, fn_val.symbol_name.ptr(), llvm_fn_type); } diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig new file mode 100644 index 0000000000..cbda7861bc --- /dev/null +++ b/src-self-hosted/compilation.zig @@ -0,0 +1,747 @@ +const std = @import("std"); +const os = std.os; +const io = std.io; +const mem = std.mem; +const Allocator = mem.Allocator; +const Buffer = std.Buffer; +const llvm = @import("llvm.zig"); +const c = @import("c.zig"); +const builtin = @import("builtin"); +const Target = @import("target.zig").Target; +const warn = std.debug.warn; +const Token = std.zig.Token; +const ArrayList = std.ArrayList; +const errmsg = @import("errmsg.zig"); +const ast = std.zig.ast; +const event = std.event; +const assert = std.debug.assert; +const AtomicRmwOp = builtin.AtomicRmwOp; +const AtomicOrder = builtin.AtomicOrder; +const Scope = @import("scope.zig").Scope; +const Decl = @import("decl.zig").Decl; +const ir = @import("ir.zig"); +const Visib = @import("visib.zig").Visib; +const ParsedFile = @import("parsed_file.zig").ParsedFile; +const Value = @import("value.zig").Value; +const Type = Value.Type; +const Span = errmsg.Span; +const codegen = @import("codegen.zig"); + +/// Data that is local to the event loop. +pub const EventLoopLocal = struct { + loop: *event.Loop, + llvm_handle_pool: std.atomic.Stack(llvm.ContextRef), + + fn init(loop: *event.Loop) EventLoopLocal { + return EventLoopLocal{ + .loop = loop, + .llvm_handle_pool = std.atomic.Stack(llvm.ContextRef).init(), + }; + } + + fn deinit(self: *EventLoopLocal) void { + while (self.llvm_handle_pool.pop()) |node| { + c.LLVMContextDispose(node.data); + self.loop.allocator.destroy(node); + } + } + + /// Gets an exclusive handle on any LlvmContext. + /// Caller must release the handle when done. + pub fn getAnyLlvmContext(self: *EventLoopLocal) !LlvmHandle { + if (self.llvm_handle_pool.pop()) |node| return LlvmHandle{ .node = node }; + + const context_ref = c.LLVMContextCreate() orelse return error.OutOfMemory; + errdefer c.LLVMContextDispose(context_ref); + + const node = try self.loop.allocator.create(std.atomic.Stack(llvm.ContextRef).Node{ + .next = undefined, + .data = context_ref, + }); + errdefer self.loop.allocator.destroy(node); + + return LlvmHandle{ .node = node }; + } +}; + +pub const LlvmHandle = struct { + node: *std.atomic.Stack(llvm.ContextRef).Node, + + pub fn release(self: LlvmHandle, event_loop_local: *EventLoopLocal) void { + event_loop_local.llvm_handle_pool.push(self.node); + } +}; + +pub const Compilation = struct { + event_loop_local: *EventLoopLocal, + loop: *event.Loop, + name: Buffer, + root_src_path: ?[]const u8, + target: Target, + build_mode: builtin.Mode, + zig_lib_dir: []const u8, + + version_major: u32, + version_minor: u32, + version_patch: u32, + + linker_script: ?[]const u8, + cache_dir: []const u8, + libc_lib_dir: ?[]const u8, + libc_static_lib_dir: ?[]const u8, + libc_include_dir: ?[]const u8, + msvc_lib_dir: ?[]const u8, + kernel32_lib_dir: ?[]const u8, + dynamic_linker: ?[]const u8, + out_h_path: ?[]const u8, + + is_test: bool, + each_lib_rpath: bool, + strip: bool, + is_static: bool, + linker_rdynamic: bool, + + clang_argv: []const []const u8, + llvm_argv: []const []const u8, + lib_dirs: []const []const u8, + rpath_list: []const []const u8, + assembly_files: []const []const u8, + link_objects: []const []const u8, + + windows_subsystem_windows: bool, + windows_subsystem_console: bool, + + link_libs_list: ArrayList(*LinkLib), + libc_link_lib: ?*LinkLib, + + err_color: errmsg.Color, + + verbose_tokenize: bool, + verbose_ast_tree: bool, + verbose_ast_fmt: bool, + verbose_cimport: bool, + verbose_ir: bool, + verbose_llvm_ir: bool, + verbose_link: bool, + + darwin_frameworks: []const []const u8, + darwin_version_min: DarwinVersionMin, + + test_filters: []const []const u8, + test_name_prefix: ?[]const u8, + + emit_file_type: Emit, + + kind: Kind, + + link_out_file: ?[]const u8, + events: *event.Channel(Event), + + exported_symbol_names: event.Locked(Decl.Table), + + /// Before code generation starts, must wait on this group to make sure + /// the build is complete. + build_group: event.Group(BuildError!void), + + compile_errors: event.Locked(CompileErrList), + + meta_type: *Type.MetaType, + void_type: *Type.Void, + bool_type: *Type.Bool, + noreturn_type: *Type.NoReturn, + + void_value: *Value.Void, + true_value: *Value.Bool, + false_value: *Value.Bool, + noreturn_value: *Value.NoReturn, + + const CompileErrList = std.ArrayList(*errmsg.Msg); + + // TODO handle some of these earlier and report them in a way other than error codes + pub const BuildError = error{ + OutOfMemory, + EndOfStream, + BadFd, + Io, + IsDir, + Unexpected, + SystemResources, + SharingViolation, + PathAlreadyExists, + FileNotFound, + AccessDenied, + PipeBusy, + FileTooBig, + SymLinkLoop, + ProcessFdQuotaExceeded, + NameTooLong, + SystemFdQuotaExceeded, + NoDevice, + PathNotFound, + NoSpaceLeft, + NotDir, + FileSystem, + OperationAborted, + IoPending, + BrokenPipe, + WouldBlock, + FileClosed, + DestinationAddressRequired, + DiskQuota, + InputOutput, + NoStdHandles, + Overflow, + NotSupported, + BufferTooSmall, + Unimplemented, // TODO remove this one + SemanticAnalysisFailed, // TODO remove this one + }; + + pub const Event = union(enum) { + Ok, + Error: BuildError, + Fail: []*errmsg.Msg, + }; + + pub const DarwinVersionMin = union(enum) { + None, + MacOS: []const u8, + Ios: []const u8, + }; + + pub const Kind = enum { + Exe, + Lib, + Obj, + }; + + pub const LinkLib = struct { + name: []const u8, + path: ?[]const u8, + + /// the list of symbols we depend on from this lib + symbols: ArrayList([]u8), + provided_explicitly: bool, + }; + + pub const Emit = enum { + Binary, + Assembly, + LlvmIr, + }; + + pub fn create( + event_loop_local: *EventLoopLocal, + name: []const u8, + root_src_path: ?[]const u8, + target: *const Target, + kind: Kind, + build_mode: builtin.Mode, + zig_lib_dir: []const u8, + cache_dir: []const u8, + ) !*Compilation { + const loop = event_loop_local.loop; + + var name_buffer = try Buffer.init(loop.allocator, name); + errdefer name_buffer.deinit(); + + const events = try event.Channel(Event).create(loop, 0); + errdefer events.destroy(); + + const comp = try loop.allocator.create(Compilation{ + .loop = loop, + .event_loop_local = event_loop_local, + .events = events, + .name = name_buffer, + .root_src_path = root_src_path, + .target = target.*, + .kind = kind, + .build_mode = build_mode, + .zig_lib_dir = zig_lib_dir, + .cache_dir = cache_dir, + + .version_major = 0, + .version_minor = 0, + .version_patch = 0, + + .verbose_tokenize = false, + .verbose_ast_tree = false, + .verbose_ast_fmt = false, + .verbose_cimport = false, + .verbose_ir = false, + .verbose_llvm_ir = false, + .verbose_link = false, + + .linker_script = null, + .libc_lib_dir = null, + .libc_static_lib_dir = null, + .libc_include_dir = null, + .msvc_lib_dir = null, + .kernel32_lib_dir = null, + .dynamic_linker = null, + .out_h_path = null, + .is_test = false, + .each_lib_rpath = false, + .strip = false, + .is_static = false, + .linker_rdynamic = false, + .clang_argv = [][]const u8{}, + .llvm_argv = [][]const u8{}, + .lib_dirs = [][]const u8{}, + .rpath_list = [][]const u8{}, + .assembly_files = [][]const u8{}, + .link_objects = [][]const u8{}, + .windows_subsystem_windows = false, + .windows_subsystem_console = false, + .link_libs_list = ArrayList(*LinkLib).init(loop.allocator), + .libc_link_lib = null, + .err_color = errmsg.Color.Auto, + .darwin_frameworks = [][]const u8{}, + .darwin_version_min = DarwinVersionMin.None, + .test_filters = [][]const u8{}, + .test_name_prefix = null, + .emit_file_type = Emit.Binary, + .link_out_file = null, + .exported_symbol_names = event.Locked(Decl.Table).init(loop, Decl.Table.init(loop.allocator)), + .build_group = event.Group(BuildError!void).init(loop), + .compile_errors = event.Locked(CompileErrList).init(loop, CompileErrList.init(loop.allocator)), + + .meta_type = undefined, + .void_type = undefined, + .void_value = undefined, + .bool_type = undefined, + .true_value = undefined, + .false_value = undefined, + .noreturn_type = undefined, + .noreturn_value = undefined, + }); + try comp.initTypes(); + return comp; + } + + fn initTypes(comp: *Compilation) !void { + comp.meta_type = try comp.a().create(Type.MetaType{ + .base = Type{ + .base = Value{ + .id = Value.Id.Type, + .typeof = undefined, + .ref_count = std.atomic.Int(usize).init(3), // 3 because it references itself twice + }, + .id = builtin.TypeId.Type, + }, + .value = undefined, + }); + comp.meta_type.value = &comp.meta_type.base; + comp.meta_type.base.base.typeof = &comp.meta_type.base; + errdefer comp.a().destroy(comp.meta_type); + + comp.void_type = try comp.a().create(Type.Void{ + .base = Type{ + .base = Value{ + .id = Value.Id.Type, + .typeof = &Type.MetaType.get(comp).base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .id = builtin.TypeId.Void, + }, + }); + errdefer comp.a().destroy(comp.void_type); + + comp.noreturn_type = try comp.a().create(Type.NoReturn{ + .base = Type{ + .base = Value{ + .id = Value.Id.Type, + .typeof = &Type.MetaType.get(comp).base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .id = builtin.TypeId.NoReturn, + }, + }); + errdefer comp.a().destroy(comp.noreturn_type); + + comp.bool_type = try comp.a().create(Type.Bool{ + .base = Type{ + .base = Value{ + .id = Value.Id.Type, + .typeof = &Type.MetaType.get(comp).base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .id = builtin.TypeId.Bool, + }, + }); + errdefer comp.a().destroy(comp.bool_type); + + comp.void_value = try comp.a().create(Value.Void{ + .base = Value{ + .id = Value.Id.Void, + .typeof = &Type.Void.get(comp).base, + .ref_count = std.atomic.Int(usize).init(1), + }, + }); + errdefer comp.a().destroy(comp.void_value); + + comp.true_value = try comp.a().create(Value.Bool{ + .base = Value{ + .id = Value.Id.Bool, + .typeof = &Type.Bool.get(comp).base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .x = true, + }); + errdefer comp.a().destroy(comp.true_value); + + comp.false_value = try comp.a().create(Value.Bool{ + .base = Value{ + .id = Value.Id.Bool, + .typeof = &Type.Bool.get(comp).base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .x = false, + }); + errdefer comp.a().destroy(comp.false_value); + + comp.noreturn_value = try comp.a().create(Value.NoReturn{ + .base = Value{ + .id = Value.Id.NoReturn, + .typeof = &Type.NoReturn.get(comp).base, + .ref_count = std.atomic.Int(usize).init(1), + }, + }); + errdefer comp.a().destroy(comp.noreturn_value); + } + + pub fn destroy(self: *Compilation) void { + self.noreturn_value.base.deref(self); + self.void_value.base.deref(self); + self.false_value.base.deref(self); + self.true_value.base.deref(self); + self.noreturn_type.base.base.deref(self); + self.void_type.base.base.deref(self); + self.meta_type.base.base.deref(self); + + self.events.destroy(); + self.name.deinit(); + + self.a().destroy(self); + } + + pub fn build(self: *Compilation) !void { + if (self.llvm_argv.len != 0) { + var c_compatible_args = try std.cstr.NullTerminated2DArray.fromSlices(self.a(), [][]const []const u8{ + [][]const u8{"zig (LLVM option parsing)"}, + self.llvm_argv, + }); + defer c_compatible_args.deinit(); + // TODO this sets global state + c.ZigLLVMParseCommandLineOptions(self.llvm_argv.len + 1, c_compatible_args.ptr); + } + + _ = try async self.buildAsync(); + } + + 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); + + // this makes a handy error return trace and stack trace in debug mode + if (std.debug.runtime_safety) { + build_result catch unreachable; + } + + const compile_errors = blk: { + const held = await (async self.compile_errors.acquire() catch unreachable); + defer held.release(); + break :blk held.value.toOwnedSlice(); + }; + + if (build_result) |_| { + if (compile_errors.len == 0) { + await (async self.events.put(Event.Ok) catch unreachable); + } else { + await (async self.events.put(Event{ .Fail = compile_errors }) catch unreachable); + } + } else |err| { + // if there's an error then the compile errors have dangling references + self.a().free(compile_errors); + + await (async self.events.put(Event{ .Error = err }) catch unreachable); + } + + // for now we stop after 1 + return; + } + } + + async fn addRootSrc(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.a(), root_src_path) catch |err| { + try printError("unable to get real path '{}': {}", root_src_path, err); + return err; + }; + errdefer self.a().free(root_src_real_path); + + // TODO async/await readFileAlloc() + const source_code = io.readFileAlloc(self.a(), root_src_real_path) catch |err| { + try printError("unable to open '{}': {}", root_src_real_path, err); + return err; + }; + errdefer self.a().free(source_code); + + const parsed_file = try self.a().create(ParsedFile{ + .tree = undefined, + .realpath = root_src_real_path, + }); + errdefer self.a().destroy(parsed_file); + + parsed_file.tree = try std.zig.parse(self.a(), source_code); + errdefer parsed_file.tree.deinit(); + + const tree = &parsed_file.tree; + + // create empty struct for it + const decls = try Scope.Decls.create(self, null); + defer decls.base.deref(self); + + var decl_group = event.Group(BuildError!void).init(self.loop); + errdefer decl_group.cancelAll(); + + var it = tree.root_node.decls.iterator(0); + while (it.next()) |decl_ptr| { + const decl = decl_ptr.*; + switch (decl.id) { + ast.Node.Id.Comptime => @panic("TODO"), + ast.Node.Id.VarDecl => @panic("TODO"), + ast.Node.Id.FnProto => { + const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl); + + const name = if (fn_proto.name_token) |name_token| tree.tokenSlice(name_token) else { + try self.addCompileError(parsed_file, Span{ + .first = fn_proto.fn_token, + .last = fn_proto.fn_token + 1, + }, "missing function name"); + continue; + }; + + const fn_decl = try self.a().create(Decl.Fn{ + .base = Decl{ + .id = Decl.Id.Fn, + .name = name, + .visib = parseVisibToken(tree, fn_proto.visib_token), + .resolution = event.Future(BuildError!void).init(self.loop), + .resolution_in_progress = 0, + .parsed_file = parsed_file, + .parent_scope = &decls.base, + }, + .value = Decl.Fn.Val{ .Unresolved = {} }, + .fn_proto = fn_proto, + }); + errdefer self.a().destroy(fn_decl); + + try decl_group.call(addTopLevelDecl, self, &fn_decl.base); + }, + ast.Node.Id.TestDecl => @panic("TODO"), + else => unreachable, + } + } + try await (async decl_group.wait() catch unreachable); + try await (async self.build_group.wait() catch unreachable); + } + + async fn addTopLevelDecl(self: *Compilation, decl: *Decl) !void { + const is_export = decl.isExported(&decl.parsed_file.tree); + + if (is_export) { + try self.build_group.call(verifyUniqueSymbol, self, decl); + try self.build_group.call(resolveDecl, self, decl); + } + } + + fn addCompileError(self: *Compilation, parsed_file: *ParsedFile, span: Span, comptime fmt: []const u8, args: ...) !void { + const text = try std.fmt.allocPrint(self.loop.allocator, fmt, args); + errdefer self.loop.allocator.free(text); + + try self.build_group.call(addCompileErrorAsync, self, parsed_file, span, text); + } + + async fn addCompileErrorAsync( + self: *Compilation, + parsed_file: *ParsedFile, + span: Span, + text: []u8, + ) !void { + const msg = try self.loop.allocator.create(errmsg.Msg{ + .path = parsed_file.realpath, + .text = text, + .span = span, + .tree = &parsed_file.tree, + }); + errdefer self.loop.allocator.destroy(msg); + + const compile_errors = await (async self.compile_errors.acquire() catch unreachable); + defer compile_errors.release(); + + try compile_errors.value.append(msg); + } + + async fn verifyUniqueSymbol(self: *Compilation, decl: *Decl) !void { + const exported_symbol_names = await (async self.exported_symbol_names.acquire() catch unreachable); + defer exported_symbol_names.release(); + + if (try exported_symbol_names.value.put(decl.name, decl)) |other_decl| { + try self.addCompileError( + decl.parsed_file, + decl.getSpan(), + "exported symbol collision: '{}'", + decl.name, + ); + // TODO add error note showing location of other symbol + } + } + + pub fn link(self: *Compilation, out_file: ?[]const u8) !void { + warn("TODO link"); + return error.Todo; + } + + pub fn addLinkLib(self: *Compilation, name: []const u8, provided_explicitly: bool) !*LinkLib { + const is_libc = mem.eql(u8, name, "c"); + + if (is_libc) { + if (self.libc_link_lib) |libc_link_lib| { + return libc_link_lib; + } + } + + for (self.link_libs_list.toSliceConst()) |existing_lib| { + if (mem.eql(u8, name, existing_lib.name)) { + return existing_lib; + } + } + + const link_lib = try self.a().create(LinkLib{ + .name = name, + .path = null, + .provided_explicitly = provided_explicitly, + .symbols = ArrayList([]u8).init(self.a()), + }); + try self.link_libs_list.append(link_lib); + if (is_libc) { + self.libc_link_lib = link_lib; + } + return link_lib; + } + + fn a(self: Compilation) *mem.Allocator { + return self.loop.allocator; + } +}; + +fn printError(comptime format: []const u8, args: ...) !void { + var stderr_file = try std.io.getStdErr(); + var stderr_file_out_stream = std.io.FileOutStream.init(&stderr_file); + const out_stream = &stderr_file_out_stream.stream; + try out_stream.print(format, args); +} + +fn parseVisibToken(tree: *ast.Tree, optional_token_index: ?ast.TokenIndex) Visib { + if (optional_token_index) |token_index| { + const token = tree.tokens.at(token_index); + assert(token.id == Token.Id.Keyword_pub); + return Visib.Pub; + } else { + return Visib.Private; + } +} + +/// This declaration has been blessed as going into the final code generation. +pub async fn resolveDecl(comp: *Compilation, decl: *Decl) !void { + if (@atomicRmw(u8, &decl.resolution_in_progress, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) == 0) { + decl.resolution.data = await (async generateDecl(comp, decl) catch unreachable); + decl.resolution.resolve(); + return decl.resolution.data; + } else { + return (await (async decl.resolution.get() catch unreachable)).*; + } +} + +/// The function that actually does the generation. +async fn generateDecl(comp: *Compilation, decl: *Decl) !void { + switch (decl.id) { + Decl.Id.Var => @panic("TODO"), + Decl.Id.Fn => { + const fn_decl = @fieldParentPtr(Decl.Fn, "base", decl); + return await (async generateDeclFn(comp, fn_decl) catch unreachable); + }, + Decl.Id.CompTime => @panic("TODO"), + } +} + +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 fndef_scope = try Scope.FnDef.create(comp, fn_decl.base.parent_scope); + defer fndef_scope.base.deref(comp); + + // TODO actually look at the return type of the AST + const return_type = &Type.Void.get(comp).base; + defer 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); + defer fn_type.base.base.deref(comp); + + var symbol_name = try std.Buffer.init(comp.a(), fn_decl.base.name); + errdefer symbol_name.deinit(); + + 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( + comp, + body_node, + &fndef_scope.base, + Span.token(body_node.lastToken()), + fn_decl.base.parsed_file, + ) catch unreachable)) catch |err| switch (err) { + // This poison value should not cause the errdefers to run. It simply means + // that self.compile_errors is populated. + // TODO https://github.com/ziglang/zig/issues/769 + error.SemanticAnalysisFailed => return {}, + else => return err, + }; + defer unanalyzed_code.destroy(comp.a()); + + if (comp.verbose_ir) { + std.debug.warn("unanalyzed:\n"); + unanalyzed_code.dump(); + } + + const analyzed_code = (await (async ir.analyze( + comp, + fn_decl.base.parsed_file, + unanalyzed_code, + null, + ) catch unreachable)) catch |err| switch (err) { + // This poison value should not cause the errdefers to run. It simply means + // that self.compile_errors is populated. + // TODO https://github.com/ziglang/zig/issues/769 + error.SemanticAnalysisFailed => return {}, + else => return err, + }; + errdefer analyzed_code.destroy(comp.a()); + + if (comp.verbose_ir) { + std.debug.warn("analyzed:\n"); + analyzed_code.dump(); + } + + // Kick off rendering to LLVM comp, but it doesn't block the fn decl + // analysis from being complete. + try comp.build_group.call(codegen.renderToLlvm, comp, fn_val, analyzed_code); +} diff --git a/src-self-hosted/decl.zig b/src-self-hosted/decl.zig index 1a75a3249e..c0173266ee 100644 --- a/src-self-hosted/decl.zig +++ b/src-self-hosted/decl.zig @@ -9,13 +9,13 @@ const Value = @import("value.zig").Value; const Token = std.zig.Token; const errmsg = @import("errmsg.zig"); const Scope = @import("scope.zig").Scope; -const Module = @import("module.zig").Module; +const Compilation = @import("compilation.zig").Compilation; pub const Decl = struct { id: Id, name: []const u8, visib: Visib, - resolution: event.Future(Module.BuildError!void), + resolution: event.Future(Compilation.BuildError!void), resolution_in_progress: u8, parsed_file: *ParsedFile, parent_scope: *Scope, diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index f1c395a790..22d5a067a7 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -1,6 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); -const Module = @import("module.zig").Module; +const Compilation = @import("compilation.zig").Compilation; const Scope = @import("scope.zig").Scope; const ast = std.zig.ast; const Allocator = std.mem.Allocator; @@ -243,7 +243,7 @@ pub const Instruction = struct { Value.Ptr.Mut.CompTimeConst, self.params.mut, self.params.volatility, - val.typeof.getAbiAlignment(ira.irb.module), + val.typeof.getAbiAlignment(ira.irb.comp), ); } @@ -254,12 +254,12 @@ pub const Instruction = struct { }); const elem_type = target.getKnownType(); const ptr_type = Type.Pointer.get( - ira.irb.module, + ira.irb.comp, elem_type, self.params.mut, self.params.volatility, Type.Pointer.Size.One, - elem_type.getAbiAlignment(ira.irb.module), + elem_type.getAbiAlignment(ira.irb.comp), ); // 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 @@ -417,7 +417,7 @@ pub const Code = struct { arena: std.heap.ArenaAllocator, return_type: ?*Type, - /// allocator is module.a() + /// allocator is comp.a() pub fn destroy(self: *Code, allocator: *Allocator) void { self.arena.deinit(); allocator.destroy(self); @@ -437,7 +437,7 @@ pub const Code = struct { }; pub const Builder = struct { - module: *Module, + comp: *Compilation, code: *Code, current_basic_block: *BasicBlock, next_debug_id: usize, @@ -446,17 +446,17 @@ pub const Builder = struct { pub const Error = Analyze.Error; - pub fn init(module: *Module, parsed_file: *ParsedFile) !Builder { - const code = try module.a().create(Code{ + pub fn init(comp: *Compilation, parsed_file: *ParsedFile) !Builder { + const code = try comp.a().create(Code{ .basic_block_list = undefined, - .arena = std.heap.ArenaAllocator.init(module.a()), + .arena = std.heap.ArenaAllocator.init(comp.a()), .return_type = null, }); code.basic_block_list = std.ArrayList(*BasicBlock).init(&code.arena.allocator); - errdefer code.destroy(module.a()); + errdefer code.destroy(comp.a()); return Builder{ - .module = module, + .comp = comp, .parsed_file = parsed_file, .current_basic_block = undefined, .code = code, @@ -466,7 +466,7 @@ pub const Builder = struct { } pub fn abort(self: *Builder) void { - self.code.destroy(self.module.a()); + self.code.destroy(self.comp.a()); } /// Call code.destroy() when done @@ -581,7 +581,7 @@ pub const Builder = struct { } pub fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Instruction { - const block_scope = try Scope.Block.create(irb.module, parent_scope); + const block_scope = try Scope.Block.create(irb.comp, parent_scope); const outer_block_scope = &block_scope.base; var child_scope = outer_block_scope; @@ -623,8 +623,8 @@ pub const Builder = struct { Token.Id.Keyword_errdefer => Scope.Defer.Kind.ErrorExit, else => unreachable, }; - const defer_expr_scope = try Scope.DeferExpr.create(irb.module, parent_scope, defer_node.expr); - const defer_child_scope = try Scope.Defer.create(irb.module, parent_scope, kind, defer_expr_scope); + const defer_expr_scope = try Scope.DeferExpr.create(irb.comp, parent_scope, defer_node.expr); + const defer_child_scope = try Scope.Defer.create(irb.comp, parent_scope, kind, defer_expr_scope); child_scope = &defer_child_scope.base; continue; } @@ -770,8 +770,8 @@ pub const Builder = struct { .debug_id = self.next_debug_id, .val = switch (I.ir_val_init) { IrVal.Init.Unknown => IrVal.Unknown, - IrVal.Init.NoReturn => IrVal{ .KnownValue = &Value.NoReturn.get(self.module).base }, - IrVal.Init.Void => IrVal{ .KnownValue = &Value.Void.get(self.module).base }, + IrVal.Init.NoReturn => IrVal{ .KnownValue = &Value.NoReturn.get(self.comp).base }, + IrVal.Init.Void => IrVal{ .KnownValue = &Value.Void.get(self.comp).base }, }, .ref_count = 0, .span = span, @@ -819,13 +819,13 @@ pub const Builder = struct { fn buildConstBool(self: *Builder, scope: *Scope, span: Span, x: bool) !*Instruction { const inst = try self.build(Instruction.Const, scope, span, Instruction.Const.Params{}); - inst.val = IrVal{ .KnownValue = &Value.Bool.get(self.module, x).base }; + inst.val = IrVal{ .KnownValue = &Value.Bool.get(self.comp, x).base }; return inst; } fn buildConstVoid(self: *Builder, scope: *Scope, span: Span, is_generated: bool) !*Instruction { const inst = try self.buildExtra(Instruction.Const, scope, span, Instruction.Const.Params{}, is_generated); - inst.val = IrVal{ .KnownValue = &Value.Void.get(self.module).base }; + inst.val = IrVal{ .KnownValue = &Value.Void.get(self.comp).base }; return inst; } }; @@ -850,8 +850,8 @@ const Analyze = struct { OutOfMemory, }; - pub fn init(module: *Module, parsed_file: *ParsedFile, explicit_return_type: ?*Type) !Analyze { - var irb = try Builder.init(module, parsed_file); + pub fn init(comp: *Compilation, parsed_file: *ParsedFile, explicit_return_type: ?*Type) !Analyze { + var irb = try Builder.init(comp, parsed_file); errdefer irb.abort(); return Analyze{ @@ -929,12 +929,12 @@ const Analyze = struct { } fn addCompileError(self: *Analyze, span: Span, comptime fmt: []const u8, args: ...) !void { - return self.irb.module.addCompileError(self.irb.parsed_file, span, fmt, args); + return self.irb.comp.addCompileError(self.irb.parsed_file, span, fmt, args); } fn resolvePeerTypes(self: *Analyze, expected_type: ?*Type, peers: []const *Instruction) Analyze.Error!*Type { // TODO actual implementation - return &Type.Void.get(self.irb.module).base; + return &Type.Void.get(self.irb.comp).base; } fn implicitCast(self: *Analyze, target: *Instruction, optional_dest_type: ?*Type) Analyze.Error!*Instruction { @@ -959,13 +959,13 @@ const Analyze = struct { }; pub async fn gen( - module: *Module, + comp: *Compilation, body_node: *ast.Node, scope: *Scope, end_span: Span, parsed_file: *ParsedFile, ) !*Code { - var irb = try Builder.init(module, parsed_file); + var irb = try Builder.init(comp, parsed_file); errdefer irb.abort(); const entry_block = try irb.createBasicBlock(scope, "Entry"); @@ -991,8 +991,8 @@ pub async fn gen( return irb.finish(); } -pub async fn analyze(module: *Module, parsed_file: *ParsedFile, old_code: *Code, expected_type: ?*Type) !*Code { - var ira = try Analyze.init(module, parsed_file, expected_type); +pub async fn analyze(comp: *Compilation, parsed_file: *ParsedFile, old_code: *Code, expected_type: ?*Type) !*Code { + var ira = try Analyze.init(comp, parsed_file, expected_type); errdefer ira.abort(); const old_entry_bb = old_code.basic_block_list.at(0); @@ -1025,7 +1025,7 @@ pub async fn analyze(module: *Module, parsed_file: *ParsedFile, old_code: *Code, } if (ira.src_implicit_return_type_list.len == 0) { - ira.irb.code.return_type = &Type.NoReturn.get(module).base; + ira.irb.code.return_type = &Type.NoReturn.get(comp).base; return ira.irb.finish(); } diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 77ec7f6d32..c9478954c5 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -14,8 +14,8 @@ const c = @import("c.zig"); const introspect = @import("introspect.zig"); const Args = arg.Args; const Flag = arg.Flag; -const EventLoopLocal = @import("module.zig").EventLoopLocal; -const Module = @import("module.zig").Module; +const EventLoopLocal = @import("compilation.zig").EventLoopLocal; +const Compilation = @import("compilation.zig").Compilation; const Target = @import("target.zig").Target; const errmsg = @import("errmsg.zig"); @@ -258,7 +258,7 @@ const args_build_generic = []Flag{ Flag.Arg1("--ver-patch"), }; -fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Module.Kind) !void { +fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Compilation.Kind) !void { var flags = try Args.parse(allocator, args_build_generic, args); defer flags.deinit(); @@ -300,14 +300,14 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Mo const emit_type = blk: { if (flags.single("emit")) |emit_flag| { if (mem.eql(u8, emit_flag, "asm")) { - break :blk Module.Emit.Assembly; + break :blk Compilation.Emit.Assembly; } else if (mem.eql(u8, emit_flag, "bin")) { - break :blk Module.Emit.Binary; + break :blk Compilation.Emit.Binary; } else if (mem.eql(u8, emit_flag, "llvm-ir")) { - break :blk Module.Emit.LlvmIr; + break :blk Compilation.Emit.LlvmIr; } else unreachable; } else { - break :blk Module.Emit.Binary; + break :blk Compilation.Emit.Binary; } }; @@ -370,7 +370,7 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Mo os.exit(1); } - if (out_type == Module.Kind.Obj and link_objects.len != 0) { + if (out_type == Compilation.Kind.Obj and link_objects.len != 0) { try stderr.write("When building an object file, --object arguments are invalid\n"); os.exit(1); } @@ -392,7 +392,7 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Mo var event_loop_local = EventLoopLocal.init(&loop); defer event_loop_local.deinit(); - var module = try Module.create( + var comp = try Compilation.create( &event_loop_local, root_name, root_source_file, @@ -402,16 +402,16 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Mo zig_lib_dir, full_cache_dir, ); - defer module.destroy(); + defer comp.destroy(); - module.version_major = try std.fmt.parseUnsigned(u32, flags.single("ver-major") orelse "0", 10); - module.version_minor = try std.fmt.parseUnsigned(u32, flags.single("ver-minor") orelse "0", 10); - module.version_patch = try std.fmt.parseUnsigned(u32, flags.single("ver-patch") orelse "0", 10); + 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); - module.is_test = false; + comp.is_test = false; - module.linker_script = flags.single("linker-script"); - module.each_lib_rpath = flags.present("each-lib-rpath"); + comp.linker_script = flags.single("linker-script"); + comp.each_lib_rpath = flags.present("each-lib-rpath"); var clang_argv_buf = ArrayList([]const u8).init(allocator); defer clang_argv_buf.deinit(); @@ -422,51 +422,51 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Mo try clang_argv_buf.append(mllvm); } - module.llvm_argv = mllvm_flags; - module.clang_argv = clang_argv_buf.toSliceConst(); + comp.llvm_argv = mllvm_flags; + comp.clang_argv = clang_argv_buf.toSliceConst(); - module.strip = flags.present("strip"); - module.is_static = flags.present("static"); + comp.strip = flags.present("strip"); + comp.is_static = flags.present("static"); if (flags.single("libc-lib-dir")) |libc_lib_dir| { - module.libc_lib_dir = libc_lib_dir; + comp.libc_lib_dir = libc_lib_dir; } if (flags.single("libc-static-lib-dir")) |libc_static_lib_dir| { - module.libc_static_lib_dir = libc_static_lib_dir; + comp.libc_static_lib_dir = libc_static_lib_dir; } if (flags.single("libc-include-dir")) |libc_include_dir| { - module.libc_include_dir = libc_include_dir; + comp.libc_include_dir = libc_include_dir; } if (flags.single("msvc-lib-dir")) |msvc_lib_dir| { - module.msvc_lib_dir = msvc_lib_dir; + comp.msvc_lib_dir = msvc_lib_dir; } if (flags.single("kernel32-lib-dir")) |kernel32_lib_dir| { - module.kernel32_lib_dir = kernel32_lib_dir; + comp.kernel32_lib_dir = kernel32_lib_dir; } if (flags.single("dynamic-linker")) |dynamic_linker| { - module.dynamic_linker = dynamic_linker; + comp.dynamic_linker = dynamic_linker; } - module.verbose_tokenize = flags.present("verbose-tokenize"); - module.verbose_ast_tree = flags.present("verbose-ast-tree"); - module.verbose_ast_fmt = flags.present("verbose-ast-fmt"); - module.verbose_link = flags.present("verbose-link"); - module.verbose_ir = flags.present("verbose-ir"); - module.verbose_llvm_ir = flags.present("verbose-llvm-ir"); - module.verbose_cimport = flags.present("verbose-cimport"); + 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"); + comp.verbose_link = flags.present("verbose-link"); + comp.verbose_ir = flags.present("verbose-ir"); + comp.verbose_llvm_ir = flags.present("verbose-llvm-ir"); + comp.verbose_cimport = flags.present("verbose-cimport"); - module.err_color = color; - module.lib_dirs = flags.many("library-path"); - module.darwin_frameworks = flags.many("framework"); - module.rpath_list = flags.many("rpath"); + comp.err_color = color; + comp.lib_dirs = flags.many("library-path"); + comp.darwin_frameworks = flags.many("framework"); + comp.rpath_list = flags.many("rpath"); if (flags.single("output-h")) |output_h| { - module.out_h_path = output_h; + comp.out_h_path = output_h; } - module.windows_subsystem_windows = flags.present("mwindows"); - module.windows_subsystem_console = flags.present("mconsole"); - module.linker_rdynamic = flags.present("rdynamic"); + comp.windows_subsystem_windows = flags.present("mwindows"); + comp.windows_subsystem_console = flags.present("mconsole"); + comp.linker_rdynamic = flags.present("rdynamic"); if (flags.single("mmacosx-version-min") != null and flags.single("mios-version-min") != null) { try stderr.write("-mmacosx-version-min and -mios-version-min options not allowed together\n"); @@ -474,37 +474,37 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Mo } if (flags.single("mmacosx-version-min")) |ver| { - module.darwin_version_min = Module.DarwinVersionMin{ .MacOS = ver }; + comp.darwin_version_min = Compilation.DarwinVersionMin{ .MacOS = ver }; } if (flags.single("mios-version-min")) |ver| { - module.darwin_version_min = Module.DarwinVersionMin{ .Ios = ver }; + comp.darwin_version_min = Compilation.DarwinVersionMin{ .Ios = ver }; } - module.emit_file_type = emit_type; - module.link_objects = link_objects; - module.assembly_files = assembly_files; - module.link_out_file = flags.single("out-file"); + comp.emit_file_type = emit_type; + comp.link_objects = link_objects; + comp.assembly_files = assembly_files; + comp.link_out_file = flags.single("out-file"); - try module.build(); - const process_build_events_handle = try async processBuildEvents(module, color); + try comp.build(); + const process_build_events_handle = try async processBuildEvents(comp, color); defer cancel process_build_events_handle; loop.run(); } -async fn processBuildEvents(module: *Module, color: errmsg.Color) void { +async fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void { // TODO directly awaiting async should guarantee memory allocation elision - const build_event = await (async module.events.get() catch unreachable); + const build_event = await (async comp.events.get() catch unreachable); switch (build_event) { - Module.Event.Ok => { + Compilation.Event.Ok => { std.debug.warn("Build succeeded\n"); return; }, - Module.Event.Error => |err| { + Compilation.Event.Error => |err| { std.debug.warn("build failed: {}\n", @errorName(err)); os.exit(1); }, - Module.Event.Fail => |msgs| { + Compilation.Event.Fail => |msgs| { for (msgs) |msg| { errmsg.printToFile(&stderr_file, msg, color) catch os.exit(1); } @@ -513,15 +513,15 @@ async fn processBuildEvents(module: *Module, color: errmsg.Color) void { } fn cmdBuildExe(allocator: *Allocator, args: []const []const u8) !void { - return buildOutputType(allocator, args, Module.Kind.Exe); + return buildOutputType(allocator, args, Compilation.Kind.Exe); } fn cmdBuildLib(allocator: *Allocator, args: []const []const u8) !void { - return buildOutputType(allocator, args, Module.Kind.Lib); + return buildOutputType(allocator, args, Compilation.Kind.Lib); } fn cmdBuildObj(allocator: *Allocator, args: []const []const u8) !void { - return buildOutputType(allocator, args, Module.Kind.Obj); + return buildOutputType(allocator, args, Compilation.Kind.Obj); } const usage_fmt = diff --git a/src-self-hosted/module.zig b/src-self-hosted/module.zig deleted file mode 100644 index 617bd0d44a..0000000000 --- a/src-self-hosted/module.zig +++ /dev/null @@ -1,747 +0,0 @@ -const std = @import("std"); -const os = std.os; -const io = std.io; -const mem = std.mem; -const Allocator = mem.Allocator; -const Buffer = std.Buffer; -const llvm = @import("llvm.zig"); -const c = @import("c.zig"); -const builtin = @import("builtin"); -const Target = @import("target.zig").Target; -const warn = std.debug.warn; -const Token = std.zig.Token; -const ArrayList = std.ArrayList; -const errmsg = @import("errmsg.zig"); -const ast = std.zig.ast; -const event = std.event; -const assert = std.debug.assert; -const AtomicRmwOp = builtin.AtomicRmwOp; -const AtomicOrder = builtin.AtomicOrder; -const Scope = @import("scope.zig").Scope; -const Decl = @import("decl.zig").Decl; -const ir = @import("ir.zig"); -const Visib = @import("visib.zig").Visib; -const ParsedFile = @import("parsed_file.zig").ParsedFile; -const Value = @import("value.zig").Value; -const Type = Value.Type; -const Span = errmsg.Span; -const codegen = @import("codegen.zig"); - -/// Data that is local to the event loop. -pub const EventLoopLocal = struct { - loop: *event.Loop, - llvm_handle_pool: std.atomic.Stack(llvm.ContextRef), - - fn init(loop: *event.Loop) EventLoopLocal { - return EventLoopLocal{ - .loop = loop, - .llvm_handle_pool = std.atomic.Stack(llvm.ContextRef).init(), - }; - } - - fn deinit(self: *EventLoopLocal) void { - while (self.llvm_handle_pool.pop()) |node| { - c.LLVMContextDispose(node.data); - self.loop.allocator.destroy(node); - } - } - - /// Gets an exclusive handle on any LlvmContext. - /// Caller must release the handle when done. - pub fn getAnyLlvmContext(self: *EventLoopLocal) !LlvmHandle { - if (self.llvm_handle_pool.pop()) |node| return LlvmHandle{ .node = node }; - - const context_ref = c.LLVMContextCreate() orelse return error.OutOfMemory; - errdefer c.LLVMContextDispose(context_ref); - - const node = try self.loop.allocator.create(std.atomic.Stack(llvm.ContextRef).Node{ - .next = undefined, - .data = context_ref, - }); - errdefer self.loop.allocator.destroy(node); - - return LlvmHandle{ .node = node }; - } -}; - -pub const LlvmHandle = struct { - node: *std.atomic.Stack(llvm.ContextRef).Node, - - pub fn release(self: LlvmHandle, event_loop_local: *EventLoopLocal) void { - event_loop_local.llvm_handle_pool.push(self.node); - } -}; - -pub const Module = struct { - event_loop_local: *EventLoopLocal, - loop: *event.Loop, - name: Buffer, - root_src_path: ?[]const u8, - target: Target, - build_mode: builtin.Mode, - zig_lib_dir: []const u8, - - version_major: u32, - version_minor: u32, - version_patch: u32, - - linker_script: ?[]const u8, - cache_dir: []const u8, - libc_lib_dir: ?[]const u8, - libc_static_lib_dir: ?[]const u8, - libc_include_dir: ?[]const u8, - msvc_lib_dir: ?[]const u8, - kernel32_lib_dir: ?[]const u8, - dynamic_linker: ?[]const u8, - out_h_path: ?[]const u8, - - is_test: bool, - each_lib_rpath: bool, - strip: bool, - is_static: bool, - linker_rdynamic: bool, - - clang_argv: []const []const u8, - llvm_argv: []const []const u8, - lib_dirs: []const []const u8, - rpath_list: []const []const u8, - assembly_files: []const []const u8, - link_objects: []const []const u8, - - windows_subsystem_windows: bool, - windows_subsystem_console: bool, - - link_libs_list: ArrayList(*LinkLib), - libc_link_lib: ?*LinkLib, - - err_color: errmsg.Color, - - verbose_tokenize: bool, - verbose_ast_tree: bool, - verbose_ast_fmt: bool, - verbose_cimport: bool, - verbose_ir: bool, - verbose_llvm_ir: bool, - verbose_link: bool, - - darwin_frameworks: []const []const u8, - darwin_version_min: DarwinVersionMin, - - test_filters: []const []const u8, - test_name_prefix: ?[]const u8, - - emit_file_type: Emit, - - kind: Kind, - - link_out_file: ?[]const u8, - events: *event.Channel(Event), - - exported_symbol_names: event.Locked(Decl.Table), - - /// Before code generation starts, must wait on this group to make sure - /// the build is complete. - build_group: event.Group(BuildError!void), - - compile_errors: event.Locked(CompileErrList), - - meta_type: *Type.MetaType, - void_type: *Type.Void, - bool_type: *Type.Bool, - noreturn_type: *Type.NoReturn, - - void_value: *Value.Void, - true_value: *Value.Bool, - false_value: *Value.Bool, - noreturn_value: *Value.NoReturn, - - const CompileErrList = std.ArrayList(*errmsg.Msg); - - // TODO handle some of these earlier and report them in a way other than error codes - pub const BuildError = error{ - OutOfMemory, - EndOfStream, - BadFd, - Io, - IsDir, - Unexpected, - SystemResources, - SharingViolation, - PathAlreadyExists, - FileNotFound, - AccessDenied, - PipeBusy, - FileTooBig, - SymLinkLoop, - ProcessFdQuotaExceeded, - NameTooLong, - SystemFdQuotaExceeded, - NoDevice, - PathNotFound, - NoSpaceLeft, - NotDir, - FileSystem, - OperationAborted, - IoPending, - BrokenPipe, - WouldBlock, - FileClosed, - DestinationAddressRequired, - DiskQuota, - InputOutput, - NoStdHandles, - Overflow, - NotSupported, - BufferTooSmall, - Unimplemented, // TODO remove this one - SemanticAnalysisFailed, // TODO remove this one - }; - - pub const Event = union(enum) { - Ok, - Error: BuildError, - Fail: []*errmsg.Msg, - }; - - pub const DarwinVersionMin = union(enum) { - None, - MacOS: []const u8, - Ios: []const u8, - }; - - pub const Kind = enum { - Exe, - Lib, - Obj, - }; - - pub const LinkLib = struct { - name: []const u8, - path: ?[]const u8, - - /// the list of symbols we depend on from this lib - symbols: ArrayList([]u8), - provided_explicitly: bool, - }; - - pub const Emit = enum { - Binary, - Assembly, - LlvmIr, - }; - - pub fn create( - event_loop_local: *EventLoopLocal, - name: []const u8, - root_src_path: ?[]const u8, - target: *const Target, - kind: Kind, - build_mode: builtin.Mode, - zig_lib_dir: []const u8, - cache_dir: []const u8, - ) !*Module { - const loop = event_loop_local.loop; - - var name_buffer = try Buffer.init(loop.allocator, name); - errdefer name_buffer.deinit(); - - const events = try event.Channel(Event).create(loop, 0); - errdefer events.destroy(); - - const module = try loop.allocator.create(Module{ - .loop = loop, - .event_loop_local = event_loop_local, - .events = events, - .name = name_buffer, - .root_src_path = root_src_path, - .target = target.*, - .kind = kind, - .build_mode = build_mode, - .zig_lib_dir = zig_lib_dir, - .cache_dir = cache_dir, - - .version_major = 0, - .version_minor = 0, - .version_patch = 0, - - .verbose_tokenize = false, - .verbose_ast_tree = false, - .verbose_ast_fmt = false, - .verbose_cimport = false, - .verbose_ir = false, - .verbose_llvm_ir = false, - .verbose_link = false, - - .linker_script = null, - .libc_lib_dir = null, - .libc_static_lib_dir = null, - .libc_include_dir = null, - .msvc_lib_dir = null, - .kernel32_lib_dir = null, - .dynamic_linker = null, - .out_h_path = null, - .is_test = false, - .each_lib_rpath = false, - .strip = false, - .is_static = false, - .linker_rdynamic = false, - .clang_argv = [][]const u8{}, - .llvm_argv = [][]const u8{}, - .lib_dirs = [][]const u8{}, - .rpath_list = [][]const u8{}, - .assembly_files = [][]const u8{}, - .link_objects = [][]const u8{}, - .windows_subsystem_windows = false, - .windows_subsystem_console = false, - .link_libs_list = ArrayList(*LinkLib).init(loop.allocator), - .libc_link_lib = null, - .err_color = errmsg.Color.Auto, - .darwin_frameworks = [][]const u8{}, - .darwin_version_min = DarwinVersionMin.None, - .test_filters = [][]const u8{}, - .test_name_prefix = null, - .emit_file_type = Emit.Binary, - .link_out_file = null, - .exported_symbol_names = event.Locked(Decl.Table).init(loop, Decl.Table.init(loop.allocator)), - .build_group = event.Group(BuildError!void).init(loop), - .compile_errors = event.Locked(CompileErrList).init(loop, CompileErrList.init(loop.allocator)), - - .meta_type = undefined, - .void_type = undefined, - .void_value = undefined, - .bool_type = undefined, - .true_value = undefined, - .false_value = undefined, - .noreturn_type = undefined, - .noreturn_value = undefined, - }); - try module.initTypes(); - return module; - } - - fn initTypes(module: *Module) !void { - module.meta_type = try module.a().create(Type.MetaType{ - .base = Type{ - .base = Value{ - .id = Value.Id.Type, - .typeof = undefined, - .ref_count = std.atomic.Int(usize).init(3), // 3 because it references itself twice - }, - .id = builtin.TypeId.Type, - }, - .value = undefined, - }); - module.meta_type.value = &module.meta_type.base; - module.meta_type.base.base.typeof = &module.meta_type.base; - errdefer module.a().destroy(module.meta_type); - - module.void_type = try module.a().create(Type.Void{ - .base = Type{ - .base = Value{ - .id = Value.Id.Type, - .typeof = &Type.MetaType.get(module).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .id = builtin.TypeId.Void, - }, - }); - errdefer module.a().destroy(module.void_type); - - module.noreturn_type = try module.a().create(Type.NoReturn{ - .base = Type{ - .base = Value{ - .id = Value.Id.Type, - .typeof = &Type.MetaType.get(module).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .id = builtin.TypeId.NoReturn, - }, - }); - errdefer module.a().destroy(module.noreturn_type); - - module.bool_type = try module.a().create(Type.Bool{ - .base = Type{ - .base = Value{ - .id = Value.Id.Type, - .typeof = &Type.MetaType.get(module).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .id = builtin.TypeId.Bool, - }, - }); - errdefer module.a().destroy(module.bool_type); - - module.void_value = try module.a().create(Value.Void{ - .base = Value{ - .id = Value.Id.Void, - .typeof = &Type.Void.get(module).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - }); - errdefer module.a().destroy(module.void_value); - - module.true_value = try module.a().create(Value.Bool{ - .base = Value{ - .id = Value.Id.Bool, - .typeof = &Type.Bool.get(module).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .x = true, - }); - errdefer module.a().destroy(module.true_value); - - module.false_value = try module.a().create(Value.Bool{ - .base = Value{ - .id = Value.Id.Bool, - .typeof = &Type.Bool.get(module).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .x = false, - }); - errdefer module.a().destroy(module.false_value); - - module.noreturn_value = try module.a().create(Value.NoReturn{ - .base = Value{ - .id = Value.Id.NoReturn, - .typeof = &Type.NoReturn.get(module).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - }); - errdefer module.a().destroy(module.noreturn_value); - } - - pub fn destroy(self: *Module) void { - self.noreturn_value.base.deref(self); - self.void_value.base.deref(self); - self.false_value.base.deref(self); - self.true_value.base.deref(self); - self.noreturn_type.base.base.deref(self); - self.void_type.base.base.deref(self); - self.meta_type.base.base.deref(self); - - self.events.destroy(); - self.name.deinit(); - - self.a().destroy(self); - } - - pub fn build(self: *Module) !void { - if (self.llvm_argv.len != 0) { - var c_compatible_args = try std.cstr.NullTerminated2DArray.fromSlices(self.a(), [][]const []const u8{ - [][]const u8{"zig (LLVM option parsing)"}, - self.llvm_argv, - }); - defer c_compatible_args.deinit(); - // TODO this sets global state - c.ZigLLVMParseCommandLineOptions(self.llvm_argv.len + 1, c_compatible_args.ptr); - } - - _ = try async self.buildAsync(); - } - - async fn buildAsync(self: *Module) 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); - - // this makes a handy error return trace and stack trace in debug mode - if (std.debug.runtime_safety) { - build_result catch unreachable; - } - - const compile_errors = blk: { - const held = await (async self.compile_errors.acquire() catch unreachable); - defer held.release(); - break :blk held.value.toOwnedSlice(); - }; - - if (build_result) |_| { - if (compile_errors.len == 0) { - await (async self.events.put(Event.Ok) catch unreachable); - } else { - await (async self.events.put(Event{ .Fail = compile_errors }) catch unreachable); - } - } else |err| { - // if there's an error then the compile errors have dangling references - self.a().free(compile_errors); - - await (async self.events.put(Event{ .Error = err }) catch unreachable); - } - - // for now we stop after 1 - return; - } - } - - async fn addRootSrc(self: *Module) !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.a(), root_src_path) catch |err| { - try printError("unable to get real path '{}': {}", root_src_path, err); - return err; - }; - errdefer self.a().free(root_src_real_path); - - // TODO async/await readFileAlloc() - const source_code = io.readFileAlloc(self.a(), root_src_real_path) catch |err| { - try printError("unable to open '{}': {}", root_src_real_path, err); - return err; - }; - errdefer self.a().free(source_code); - - const parsed_file = try self.a().create(ParsedFile{ - .tree = undefined, - .realpath = root_src_real_path, - }); - errdefer self.a().destroy(parsed_file); - - parsed_file.tree = try std.zig.parse(self.a(), source_code); - errdefer parsed_file.tree.deinit(); - - const tree = &parsed_file.tree; - - // create empty struct for it - const decls = try Scope.Decls.create(self, null); - defer decls.base.deref(self); - - var decl_group = event.Group(BuildError!void).init(self.loop); - errdefer decl_group.cancelAll(); - - var it = tree.root_node.decls.iterator(0); - while (it.next()) |decl_ptr| { - const decl = decl_ptr.*; - switch (decl.id) { - ast.Node.Id.Comptime => @panic("TODO"), - ast.Node.Id.VarDecl => @panic("TODO"), - ast.Node.Id.FnProto => { - const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl); - - const name = if (fn_proto.name_token) |name_token| tree.tokenSlice(name_token) else { - try self.addCompileError(parsed_file, Span{ - .first = fn_proto.fn_token, - .last = fn_proto.fn_token + 1, - }, "missing function name"); - continue; - }; - - const fn_decl = try self.a().create(Decl.Fn{ - .base = Decl{ - .id = Decl.Id.Fn, - .name = name, - .visib = parseVisibToken(tree, fn_proto.visib_token), - .resolution = event.Future(BuildError!void).init(self.loop), - .resolution_in_progress = 0, - .parsed_file = parsed_file, - .parent_scope = &decls.base, - }, - .value = Decl.Fn.Val{ .Unresolved = {} }, - .fn_proto = fn_proto, - }); - errdefer self.a().destroy(fn_decl); - - try decl_group.call(addTopLevelDecl, self, &fn_decl.base); - }, - ast.Node.Id.TestDecl => @panic("TODO"), - else => unreachable, - } - } - try await (async decl_group.wait() catch unreachable); - try await (async self.build_group.wait() catch unreachable); - } - - async fn addTopLevelDecl(self: *Module, decl: *Decl) !void { - const is_export = decl.isExported(&decl.parsed_file.tree); - - if (is_export) { - try self.build_group.call(verifyUniqueSymbol, self, decl); - try self.build_group.call(resolveDecl, self, decl); - } - } - - fn addCompileError(self: *Module, parsed_file: *ParsedFile, span: Span, comptime fmt: []const u8, args: ...) !void { - const text = try std.fmt.allocPrint(self.loop.allocator, fmt, args); - errdefer self.loop.allocator.free(text); - - try self.build_group.call(addCompileErrorAsync, self, parsed_file, span, text); - } - - async fn addCompileErrorAsync( - self: *Module, - parsed_file: *ParsedFile, - span: Span, - text: []u8, - ) !void { - const msg = try self.loop.allocator.create(errmsg.Msg{ - .path = parsed_file.realpath, - .text = text, - .span = span, - .tree = &parsed_file.tree, - }); - errdefer self.loop.allocator.destroy(msg); - - const compile_errors = await (async self.compile_errors.acquire() catch unreachable); - defer compile_errors.release(); - - try compile_errors.value.append(msg); - } - - async fn verifyUniqueSymbol(self: *Module, decl: *Decl) !void { - const exported_symbol_names = await (async self.exported_symbol_names.acquire() catch unreachable); - defer exported_symbol_names.release(); - - if (try exported_symbol_names.value.put(decl.name, decl)) |other_decl| { - try self.addCompileError( - decl.parsed_file, - decl.getSpan(), - "exported symbol collision: '{}'", - decl.name, - ); - // TODO add error note showing location of other symbol - } - } - - pub fn link(self: *Module, out_file: ?[]const u8) !void { - warn("TODO link"); - return error.Todo; - } - - pub fn addLinkLib(self: *Module, name: []const u8, provided_explicitly: bool) !*LinkLib { - const is_libc = mem.eql(u8, name, "c"); - - if (is_libc) { - if (self.libc_link_lib) |libc_link_lib| { - return libc_link_lib; - } - } - - for (self.link_libs_list.toSliceConst()) |existing_lib| { - if (mem.eql(u8, name, existing_lib.name)) { - return existing_lib; - } - } - - const link_lib = try self.a().create(LinkLib{ - .name = name, - .path = null, - .provided_explicitly = provided_explicitly, - .symbols = ArrayList([]u8).init(self.a()), - }); - try self.link_libs_list.append(link_lib); - if (is_libc) { - self.libc_link_lib = link_lib; - } - return link_lib; - } - - fn a(self: Module) *mem.Allocator { - return self.loop.allocator; - } -}; - -fn printError(comptime format: []const u8, args: ...) !void { - var stderr_file = try std.io.getStdErr(); - var stderr_file_out_stream = std.io.FileOutStream.init(&stderr_file); - const out_stream = &stderr_file_out_stream.stream; - try out_stream.print(format, args); -} - -fn parseVisibToken(tree: *ast.Tree, optional_token_index: ?ast.TokenIndex) Visib { - if (optional_token_index) |token_index| { - const token = tree.tokens.at(token_index); - assert(token.id == Token.Id.Keyword_pub); - return Visib.Pub; - } else { - return Visib.Private; - } -} - -/// This declaration has been blessed as going into the final code generation. -pub async fn resolveDecl(module: *Module, decl: *Decl) !void { - if (@atomicRmw(u8, &decl.resolution_in_progress, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) == 0) { - decl.resolution.data = await (async generateDecl(module, decl) catch unreachable); - decl.resolution.resolve(); - return decl.resolution.data; - } else { - return (await (async decl.resolution.get() catch unreachable)).*; - } -} - -/// The function that actually does the generation. -async fn generateDecl(module: *Module, decl: *Decl) !void { - switch (decl.id) { - Decl.Id.Var => @panic("TODO"), - Decl.Id.Fn => { - const fn_decl = @fieldParentPtr(Decl.Fn, "base", decl); - return await (async generateDeclFn(module, fn_decl) catch unreachable); - }, - Decl.Id.CompTime => @panic("TODO"), - } -} - -async fn generateDeclFn(module: *Module, fn_decl: *Decl.Fn) !void { - const body_node = fn_decl.fn_proto.body_node orelse @panic("TODO extern fn proto decl"); - - const fndef_scope = try Scope.FnDef.create(module, fn_decl.base.parent_scope); - defer fndef_scope.base.deref(module); - - // TODO actually look at the return type of the AST - const return_type = &Type.Void.get(module).base; - defer return_type.base.deref(module); - - const is_var_args = false; - const params = ([*]Type.Fn.Param)(undefined)[0..0]; - const fn_type = try Type.Fn.create(module, return_type, params, is_var_args); - defer fn_type.base.base.deref(module); - - var symbol_name = try std.Buffer.init(module.a(), fn_decl.base.name); - errdefer symbol_name.deinit(); - - const fn_val = try Value.Fn.create(module, fn_type, fndef_scope, symbol_name); - defer fn_val.base.deref(module); - - fn_decl.value = Decl.Fn.Val{ .Ok = fn_val }; - - const unanalyzed_code = (await (async ir.gen( - module, - body_node, - &fndef_scope.base, - Span.token(body_node.lastToken()), - fn_decl.base.parsed_file, - ) catch unreachable)) catch |err| switch (err) { - // This poison value should not cause the errdefers to run. It simply means - // that self.compile_errors is populated. - // TODO https://github.com/ziglang/zig/issues/769 - error.SemanticAnalysisFailed => return {}, - else => return err, - }; - defer unanalyzed_code.destroy(module.a()); - - if (module.verbose_ir) { - std.debug.warn("unanalyzed:\n"); - unanalyzed_code.dump(); - } - - const analyzed_code = (await (async ir.analyze( - module, - fn_decl.base.parsed_file, - unanalyzed_code, - null, - ) catch unreachable)) catch |err| switch (err) { - // This poison value should not cause the errdefers to run. It simply means - // that self.compile_errors is populated. - // TODO https://github.com/ziglang/zig/issues/769 - error.SemanticAnalysisFailed => return {}, - else => return err, - }; - errdefer analyzed_code.destroy(module.a()); - - if (module.verbose_ir) { - std.debug.warn("analyzed:\n"); - analyzed_code.dump(); - } - - // Kick off rendering to LLVM module, but it doesn't block the fn decl - // analysis from being complete. - try module.build_group.call(codegen.renderToLlvm, module, fn_val, analyzed_code); -} diff --git a/src-self-hosted/scope.zig b/src-self-hosted/scope.zig index 8f8d016a7c..6fd6456b12 100644 --- a/src-self-hosted/scope.zig +++ b/src-self-hosted/scope.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Allocator = mem.Allocator; const Decl = @import("decl.zig").Decl; -const Module = @import("module.zig").Module; +const Compilation = @import("compilation.zig").Compilation; const mem = std.mem; const ast = std.zig.ast; const Value = @import("value.zig").Value; @@ -16,17 +16,17 @@ pub const Scope = struct { base.ref_count += 1; } - pub fn deref(base: *Scope, module: *Module) void { + pub fn deref(base: *Scope, comp: *Compilation) void { base.ref_count -= 1; if (base.ref_count == 0) { - if (base.parent) |parent| parent.deref(module); + if (base.parent) |parent| parent.deref(comp); switch (base.id) { Id.Decls => @fieldParentPtr(Decls, "base", base).destroy(), - Id.Block => @fieldParentPtr(Block, "base", base).destroy(module), - Id.FnDef => @fieldParentPtr(FnDef, "base", base).destroy(module), - Id.CompTime => @fieldParentPtr(CompTime, "base", base).destroy(module), - Id.Defer => @fieldParentPtr(Defer, "base", base).destroy(module), - Id.DeferExpr => @fieldParentPtr(DeferExpr, "base", base).destroy(module), + Id.Block => @fieldParentPtr(Block, "base", base).destroy(comp), + Id.FnDef => @fieldParentPtr(FnDef, "base", base).destroy(comp), + Id.CompTime => @fieldParentPtr(CompTime, "base", base).destroy(comp), + Id.Defer => @fieldParentPtr(Defer, "base", base).destroy(comp), + Id.DeferExpr => @fieldParentPtr(DeferExpr, "base", base).destroy(comp), } } } @@ -61,8 +61,8 @@ pub const Scope = struct { table: Decl.Table, /// Creates a Decls scope with 1 reference - pub fn create(module: *Module, parent: ?*Scope) !*Decls { - const self = try module.a().create(Decls{ + pub fn create(comp: *Compilation, parent: ?*Scope) !*Decls { + const self = try comp.a().create(Decls{ .base = Scope{ .id = Id.Decls, .parent = parent, @@ -70,9 +70,9 @@ pub const Scope = struct { }, .table = undefined, }); - errdefer module.a().destroy(self); + errdefer comp.a().destroy(self); - self.table = Decl.Table.init(module.a()); + self.table = Decl.Table.init(comp.a()); errdefer self.table.deinit(); if (parent) |p| p.ref(); @@ -94,8 +94,8 @@ pub const Scope = struct { is_comptime: *ir.Instruction, /// Creates a Block scope with 1 reference - pub fn create(module: *Module, parent: ?*Scope) !*Block { - const self = try module.a().create(Block{ + pub fn create(comp: *Compilation, parent: ?*Scope) !*Block { + const self = try comp.a().create(Block{ .base = Scope{ .id = Id.Block, .parent = parent, @@ -106,14 +106,14 @@ pub const Scope = struct { .end_block = undefined, .is_comptime = undefined, }); - errdefer module.a().destroy(self); + errdefer comp.a().destroy(self); if (parent) |p| p.ref(); return self; } - pub fn destroy(self: *Block, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Block, comp: *Compilation) void { + comp.a().destroy(self); } }; @@ -125,8 +125,8 @@ pub const Scope = struct { /// Creates a FnDef scope with 1 reference /// Must set the fn_val later - pub fn create(module: *Module, parent: ?*Scope) !*FnDef { - const self = try module.a().create(FnDef{ + pub fn create(comp: *Compilation, parent: ?*Scope) !*FnDef { + const self = try comp.a().create(FnDef{ .base = Scope{ .id = Id.FnDef, .parent = parent, @@ -140,8 +140,8 @@ pub const Scope = struct { return self; } - pub fn destroy(self: *FnDef, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *FnDef, comp: *Compilation) void { + comp.a().destroy(self); } }; @@ -149,8 +149,8 @@ pub const Scope = struct { base: Scope, /// Creates a CompTime scope with 1 reference - pub fn create(module: *Module, parent: ?*Scope) !*CompTime { - const self = try module.a().create(CompTime{ + pub fn create(comp: *Compilation, parent: ?*Scope) !*CompTime { + const self = try comp.a().create(CompTime{ .base = Scope{ .id = Id.CompTime, .parent = parent, @@ -162,8 +162,8 @@ pub const Scope = struct { return self; } - pub fn destroy(self: *CompTime, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *CompTime, comp: *Compilation) void { + comp.a().destroy(self); } }; @@ -179,12 +179,12 @@ pub const Scope = struct { /// Creates a Defer scope with 1 reference pub fn create( - module: *Module, + comp: *Compilation, parent: ?*Scope, kind: Kind, defer_expr_scope: *DeferExpr, ) !*Defer { - const self = try module.a().create(Defer{ + const self = try comp.a().create(Defer{ .base = Scope{ .id = Id.Defer, .parent = parent, @@ -193,7 +193,7 @@ pub const Scope = struct { .defer_expr_scope = defer_expr_scope, .kind = kind, }); - errdefer module.a().destroy(self); + errdefer comp.a().destroy(self); defer_expr_scope.base.ref(); @@ -201,9 +201,9 @@ pub const Scope = struct { return self; } - pub fn destroy(self: *Defer, module: *Module) void { - self.defer_expr_scope.base.deref(module); - module.a().destroy(self); + pub fn destroy(self: *Defer, comp: *Compilation) void { + self.defer_expr_scope.base.deref(comp); + comp.a().destroy(self); } }; @@ -212,8 +212,8 @@ pub const Scope = struct { expr_node: *ast.Node, /// Creates a DeferExpr scope with 1 reference - pub fn create(module: *Module, parent: ?*Scope, expr_node: *ast.Node) !*DeferExpr { - const self = try module.a().create(DeferExpr{ + pub fn create(comp: *Compilation, parent: ?*Scope, expr_node: *ast.Node) !*DeferExpr { + const self = try comp.a().create(DeferExpr{ .base = Scope{ .id = Id.DeferExpr, .parent = parent, @@ -221,14 +221,14 @@ pub const Scope = struct { }, .expr_node = expr_node, }); - errdefer module.a().destroy(self); + errdefer comp.a().destroy(self); if (parent) |p| p.ref(); return self; } - pub fn destroy(self: *DeferExpr, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *DeferExpr, comp: *Compilation) void { + comp.a().destroy(self); } }; }; diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index e609eb2791..3edb267ca9 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -2,11 +2,11 @@ const std = @import("std"); const mem = std.mem; const builtin = @import("builtin"); const Target = @import("target.zig").Target; -const Module = @import("module.zig").Module; +const Compilation = @import("compilation.zig").Compilation; const introspect = @import("introspect.zig"); const assertOrPanic = std.debug.assertOrPanic; const errmsg = @import("errmsg.zig"); -const EventLoopLocal = @import("module.zig").EventLoopLocal; +const EventLoopLocal = @import("compilation.zig").EventLoopLocal; test "compile errors" { var ctx: TestContext = undefined; @@ -100,42 +100,42 @@ pub const TestContext = struct { // TODO async I/O try std.io.writeFile(allocator, file1_path, source); - var module = try Module.create( + var comp = try Compilation.create( &self.event_loop_local, "test", file1_path, Target.Native, - Module.Kind.Obj, + Compilation.Kind.Obj, builtin.Mode.Debug, self.zig_lib_dir, self.zig_cache_dir, ); - errdefer module.destroy(); + errdefer comp.destroy(); - try module.build(); + try comp.build(); - try self.group.call(getModuleEvent, module, source, path, line, column, msg); + try self.group.call(getModuleEvent, comp, source, path, line, column, msg); } async fn getModuleEvent( - module: *Module, + comp: *Compilation, source: []const u8, path: []const u8, line: usize, column: usize, text: []const u8, ) !void { - defer module.destroy(); - const build_event = await (async module.events.get() catch unreachable); + defer comp.destroy(); + const build_event = await (async comp.events.get() catch unreachable); switch (build_event) { - Module.Event.Ok => { + Compilation.Event.Ok => { @panic("build incorrectly succeeded"); }, - Module.Event.Error => |err| { + Compilation.Event.Error => |err| { @panic("build incorrectly failed"); }, - Module.Event.Fail => |msgs| { + Compilation.Event.Fail => |msgs| { assertOrPanic(msgs.len != 0); for (msgs) |msg| { if (mem.endsWith(u8, msg.path, path) and mem.eql(u8, msg.text, text)) { diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index e4c31018a3..8349047749 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -1,10 +1,10 @@ const std = @import("std"); const builtin = @import("builtin"); const Scope = @import("scope.zig").Scope; -const Module = @import("module.zig").Module; +const Compilation = @import("compilation.zig").Compilation; const Value = @import("value.zig").Value; const llvm = @import("llvm.zig"); -const CompilationUnit = @import("codegen.zig").CompilationUnit; +const ObjectFile = @import("codegen.zig").ObjectFile; pub const Type = struct { base: Value, @@ -12,63 +12,63 @@ pub const Type = struct { pub const Id = builtin.TypeId; - pub fn destroy(base: *Type, module: *Module) void { + pub fn destroy(base: *Type, comp: *Compilation) void { switch (base.id) { - Id.Struct => @fieldParentPtr(Struct, "base", base).destroy(module), - Id.Fn => @fieldParentPtr(Fn, "base", base).destroy(module), - Id.Type => @fieldParentPtr(MetaType, "base", base).destroy(module), - Id.Void => @fieldParentPtr(Void, "base", base).destroy(module), - Id.Bool => @fieldParentPtr(Bool, "base", base).destroy(module), - Id.NoReturn => @fieldParentPtr(NoReturn, "base", base).destroy(module), - Id.Int => @fieldParentPtr(Int, "base", base).destroy(module), - Id.Float => @fieldParentPtr(Float, "base", base).destroy(module), - Id.Pointer => @fieldParentPtr(Pointer, "base", base).destroy(module), - Id.Array => @fieldParentPtr(Array, "base", base).destroy(module), - Id.ComptimeFloat => @fieldParentPtr(ComptimeFloat, "base", base).destroy(module), - Id.ComptimeInt => @fieldParentPtr(ComptimeInt, "base", base).destroy(module), - Id.Undefined => @fieldParentPtr(Undefined, "base", base).destroy(module), - Id.Null => @fieldParentPtr(Null, "base", base).destroy(module), - Id.Optional => @fieldParentPtr(Optional, "base", base).destroy(module), - Id.ErrorUnion => @fieldParentPtr(ErrorUnion, "base", base).destroy(module), - Id.ErrorSet => @fieldParentPtr(ErrorSet, "base", base).destroy(module), - Id.Enum => @fieldParentPtr(Enum, "base", base).destroy(module), - Id.Union => @fieldParentPtr(Union, "base", base).destroy(module), - Id.Namespace => @fieldParentPtr(Namespace, "base", base).destroy(module), - Id.Block => @fieldParentPtr(Block, "base", base).destroy(module), - Id.BoundFn => @fieldParentPtr(BoundFn, "base", base).destroy(module), - Id.ArgTuple => @fieldParentPtr(ArgTuple, "base", base).destroy(module), - Id.Opaque => @fieldParentPtr(Opaque, "base", base).destroy(module), - Id.Promise => @fieldParentPtr(Promise, "base", base).destroy(module), + Id.Struct => @fieldParentPtr(Struct, "base", base).destroy(comp), + Id.Fn => @fieldParentPtr(Fn, "base", base).destroy(comp), + Id.Type => @fieldParentPtr(MetaType, "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.Int => @fieldParentPtr(Int, "base", base).destroy(comp), + Id.Float => @fieldParentPtr(Float, "base", base).destroy(comp), + Id.Pointer => @fieldParentPtr(Pointer, "base", base).destroy(comp), + Id.Array => @fieldParentPtr(Array, "base", base).destroy(comp), + Id.ComptimeFloat => @fieldParentPtr(ComptimeFloat, "base", base).destroy(comp), + Id.ComptimeInt => @fieldParentPtr(ComptimeInt, "base", base).destroy(comp), + Id.Undefined => @fieldParentPtr(Undefined, "base", base).destroy(comp), + Id.Null => @fieldParentPtr(Null, "base", base).destroy(comp), + Id.Optional => @fieldParentPtr(Optional, "base", base).destroy(comp), + Id.ErrorUnion => @fieldParentPtr(ErrorUnion, "base", base).destroy(comp), + Id.ErrorSet => @fieldParentPtr(ErrorSet, "base", base).destroy(comp), + Id.Enum => @fieldParentPtr(Enum, "base", base).destroy(comp), + Id.Union => @fieldParentPtr(Union, "base", base).destroy(comp), + Id.Namespace => @fieldParentPtr(Namespace, "base", base).destroy(comp), + Id.Block => @fieldParentPtr(Block, "base", base).destroy(comp), + Id.BoundFn => @fieldParentPtr(BoundFn, "base", base).destroy(comp), + Id.ArgTuple => @fieldParentPtr(ArgTuple, "base", base).destroy(comp), + Id.Opaque => @fieldParentPtr(Opaque, "base", base).destroy(comp), + Id.Promise => @fieldParentPtr(Promise, "base", base).destroy(comp), } } - pub fn getLlvmType(base: *Type, cunit: *CompilationUnit) (error{OutOfMemory}!llvm.TypeRef) { + pub fn getLlvmType(base: *Type, ofile: *ObjectFile) (error{OutOfMemory}!llvm.TypeRef) { switch (base.id) { - Id.Struct => return @fieldParentPtr(Struct, "base", base).getLlvmType(cunit), - Id.Fn => return @fieldParentPtr(Fn, "base", base).getLlvmType(cunit), + Id.Struct => return @fieldParentPtr(Struct, "base", base).getLlvmType(ofile), + Id.Fn => return @fieldParentPtr(Fn, "base", base).getLlvmType(ofile), Id.Type => unreachable, Id.Void => unreachable, - Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmType(cunit), + Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmType(ofile), Id.NoReturn => unreachable, - Id.Int => return @fieldParentPtr(Int, "base", base).getLlvmType(cunit), - Id.Float => return @fieldParentPtr(Float, "base", base).getLlvmType(cunit), - Id.Pointer => return @fieldParentPtr(Pointer, "base", base).getLlvmType(cunit), - Id.Array => return @fieldParentPtr(Array, "base", base).getLlvmType(cunit), + 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.ComptimeFloat => unreachable, Id.ComptimeInt => unreachable, Id.Undefined => unreachable, Id.Null => unreachable, - Id.Optional => return @fieldParentPtr(Optional, "base", base).getLlvmType(cunit), - Id.ErrorUnion => return @fieldParentPtr(ErrorUnion, "base", base).getLlvmType(cunit), - Id.ErrorSet => return @fieldParentPtr(ErrorSet, "base", base).getLlvmType(cunit), - Id.Enum => return @fieldParentPtr(Enum, "base", base).getLlvmType(cunit), - Id.Union => return @fieldParentPtr(Union, "base", base).getLlvmType(cunit), + 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.Namespace => unreachable, Id.Block => unreachable, - Id.BoundFn => return @fieldParentPtr(BoundFn, "base", base).getLlvmType(cunit), + Id.BoundFn => return @fieldParentPtr(BoundFn, "base", base).getLlvmType(ofile), Id.ArgTuple => unreachable, - Id.Opaque => return @fieldParentPtr(Opaque, "base", base).getLlvmType(cunit), - Id.Promise => return @fieldParentPtr(Promise, "base", base).getLlvmType(cunit), + Id.Opaque => return @fieldParentPtr(Opaque, "base", base).getLlvmType(ofile), + Id.Promise => return @fieldParentPtr(Promise, "base", base).getLlvmType(ofile), } } @@ -76,7 +76,7 @@ pub const Type = struct { std.debug.warn("{}", @tagName(base.id)); } - pub fn getAbiAlignment(base: *Type, module: *Module) u32 { + pub fn getAbiAlignment(base: *Type, comp: *Compilation) u32 { @panic("TODO getAbiAlignment"); } @@ -84,11 +84,11 @@ pub const Type = struct { base: Type, decls: *Scope.Decls, - pub fn destroy(self: *Struct, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Struct, comp: *Compilation) void { + comp.a().destroy(self); } - pub fn getLlvmType(self: *Struct, cunit: *CompilationUnit) llvm.TypeRef { + pub fn getLlvmType(self: *Struct, ofile: *ObjectFile) llvm.TypeRef { @panic("TODO"); } }; @@ -104,12 +104,12 @@ pub const Type = struct { typeof: *Type, }; - pub fn create(module: *Module, return_type: *Type, params: []Param, is_var_args: bool) !*Fn { - const result = try module.a().create(Fn{ + pub fn create(comp: *Compilation, return_type: *Type, params: []Param, is_var_args: bool) !*Fn { + const result = try comp.a().create(Fn{ .base = Type{ .base = Value{ .id = Value.Id.Type, - .typeof = &MetaType.get(module).base, + .typeof = &MetaType.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, .id = builtin.TypeId.Fn, @@ -118,7 +118,7 @@ pub const Type = struct { .params = params, .is_var_args = is_var_args, }); - errdefer module.a().destroy(result); + errdefer comp.a().destroy(result); result.return_type.base.ref(); for (result.params) |param| { @@ -127,23 +127,23 @@ pub const Type = struct { return result; } - pub fn destroy(self: *Fn, module: *Module) void { - self.return_type.base.deref(module); + pub fn destroy(self: *Fn, comp: *Compilation) void { + self.return_type.base.deref(comp); for (self.params) |param| { - param.typeof.base.deref(module); + param.typeof.base.deref(comp); } - module.a().destroy(self); + comp.a().destroy(self); } - pub fn getLlvmType(self: *Fn, cunit: *CompilationUnit) !llvm.TypeRef { + pub fn getLlvmType(self: *Fn, ofile: *ObjectFile) !llvm.TypeRef { const llvm_return_type = switch (self.return_type.id) { - Type.Id.Void => llvm.VoidTypeInContext(cunit.context) orelse return error.OutOfMemory, - else => try self.return_type.getLlvmType(cunit), + Type.Id.Void => llvm.VoidTypeInContext(ofile.context) orelse return error.OutOfMemory, + else => try self.return_type.getLlvmType(ofile), }; - const llvm_param_types = try cunit.a().alloc(llvm.TypeRef, self.params.len); - defer cunit.a().free(llvm_param_types); + const llvm_param_types = try ofile.a().alloc(llvm.TypeRef, self.params.len); + defer ofile.a().free(llvm_param_types); for (llvm_param_types) |*llvm_param_type, i| { - llvm_param_type.* = try self.params[i].typeof.getLlvmType(cunit); + llvm_param_type.* = try self.params[i].typeof.getLlvmType(ofile); } return llvm.FunctionType( @@ -160,13 +160,13 @@ pub const Type = struct { value: *Type, /// Adds 1 reference to the resulting type - pub fn get(module: *Module) *MetaType { - module.meta_type.base.base.ref(); - return module.meta_type; + pub fn get(comp: *Compilation) *MetaType { + comp.meta_type.base.base.ref(); + return comp.meta_type; } - pub fn destroy(self: *MetaType, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *MetaType, comp: *Compilation) void { + comp.a().destroy(self); } }; @@ -174,13 +174,13 @@ pub const Type = struct { base: Type, /// Adds 1 reference to the resulting type - pub fn get(module: *Module) *Void { - module.void_type.base.base.ref(); - return module.void_type; + pub fn get(comp: *Compilation) *Void { + comp.void_type.base.base.ref(); + return comp.void_type; } - pub fn destroy(self: *Void, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Void, comp: *Compilation) void { + comp.a().destroy(self); } }; @@ -188,16 +188,16 @@ pub const Type = struct { base: Type, /// Adds 1 reference to the resulting type - pub fn get(module: *Module) *Bool { - module.bool_type.base.base.ref(); - return module.bool_type; + pub fn get(comp: *Compilation) *Bool { + comp.bool_type.base.base.ref(); + return comp.bool_type; } - pub fn destroy(self: *Bool, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Bool, comp: *Compilation) void { + comp.a().destroy(self); } - pub fn getLlvmType(self: *Bool, cunit: *CompilationUnit) llvm.TypeRef { + pub fn getLlvmType(self: *Bool, ofile: *ObjectFile) llvm.TypeRef { @panic("TODO"); } }; @@ -206,24 +206,24 @@ pub const Type = struct { base: Type, /// Adds 1 reference to the resulting type - pub fn get(module: *Module) *NoReturn { - module.noreturn_type.base.base.ref(); - return module.noreturn_type; + pub fn get(comp: *Compilation) *NoReturn { + comp.noreturn_type.base.base.ref(); + return comp.noreturn_type; } - pub fn destroy(self: *NoReturn, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *NoReturn, comp: *Compilation) void { + comp.a().destroy(self); } }; pub const Int = struct { base: Type, - pub fn destroy(self: *Int, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Int, comp: *Compilation) void { + comp.a().destroy(self); } - pub fn getLlvmType(self: *Int, cunit: *CompilationUnit) llvm.TypeRef { + pub fn getLlvmType(self: *Int, ofile: *ObjectFile) llvm.TypeRef { @panic("TODO"); } }; @@ -231,11 +231,11 @@ pub const Type = struct { pub const Float = struct { base: Type, - pub fn destroy(self: *Float, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Float, comp: *Compilation) void { + comp.a().destroy(self); } - pub fn getLlvmType(self: *Float, cunit: *CompilationUnit) llvm.TypeRef { + pub fn getLlvmType(self: *Float, ofile: *ObjectFile) llvm.TypeRef { @panic("TODO"); } }; @@ -256,12 +256,12 @@ pub const Type = struct { }; pub const Size = builtin.TypeInfo.Pointer.Size; - pub fn destroy(self: *Pointer, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Pointer, comp: *Compilation) void { + comp.a().destroy(self); } pub fn get( - module: *Module, + comp: *Compilation, elem_type: *Type, mut: Mut, vol: Vol, @@ -271,7 +271,7 @@ pub const Type = struct { @panic("TODO get pointer"); } - pub fn getLlvmType(self: *Pointer, cunit: *CompilationUnit) llvm.TypeRef { + pub fn getLlvmType(self: *Pointer, ofile: *ObjectFile) llvm.TypeRef { @panic("TODO"); } }; @@ -279,11 +279,11 @@ pub const Type = struct { pub const Array = struct { base: Type, - pub fn destroy(self: *Array, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Array, comp: *Compilation) void { + comp.a().destroy(self); } - pub fn getLlvmType(self: *Array, cunit: *CompilationUnit) llvm.TypeRef { + pub fn getLlvmType(self: *Array, ofile: *ObjectFile) llvm.TypeRef { @panic("TODO"); } }; @@ -291,43 +291,43 @@ pub const Type = struct { pub const ComptimeFloat = struct { base: Type, - pub fn destroy(self: *ComptimeFloat, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *ComptimeFloat, comp: *Compilation) void { + comp.a().destroy(self); } }; pub const ComptimeInt = struct { base: Type, - pub fn destroy(self: *ComptimeInt, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *ComptimeInt, comp: *Compilation) void { + comp.a().destroy(self); } }; pub const Undefined = struct { base: Type, - pub fn destroy(self: *Undefined, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Undefined, comp: *Compilation) void { + comp.a().destroy(self); } }; pub const Null = struct { base: Type, - pub fn destroy(self: *Null, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Null, comp: *Compilation) void { + comp.a().destroy(self); } }; pub const Optional = struct { base: Type, - pub fn destroy(self: *Optional, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Optional, comp: *Compilation) void { + comp.a().destroy(self); } - pub fn getLlvmType(self: *Optional, cunit: *CompilationUnit) llvm.TypeRef { + pub fn getLlvmType(self: *Optional, ofile: *ObjectFile) llvm.TypeRef { @panic("TODO"); } }; @@ -335,11 +335,11 @@ pub const Type = struct { pub const ErrorUnion = struct { base: Type, - pub fn destroy(self: *ErrorUnion, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *ErrorUnion, comp: *Compilation) void { + comp.a().destroy(self); } - pub fn getLlvmType(self: *ErrorUnion, cunit: *CompilationUnit) llvm.TypeRef { + pub fn getLlvmType(self: *ErrorUnion, ofile: *ObjectFile) llvm.TypeRef { @panic("TODO"); } }; @@ -347,11 +347,11 @@ pub const Type = struct { pub const ErrorSet = struct { base: Type, - pub fn destroy(self: *ErrorSet, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *ErrorSet, comp: *Compilation) void { + comp.a().destroy(self); } - pub fn getLlvmType(self: *ErrorSet, cunit: *CompilationUnit) llvm.TypeRef { + pub fn getLlvmType(self: *ErrorSet, ofile: *ObjectFile) llvm.TypeRef { @panic("TODO"); } }; @@ -359,11 +359,11 @@ pub const Type = struct { pub const Enum = struct { base: Type, - pub fn destroy(self: *Enum, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Enum, comp: *Compilation) void { + comp.a().destroy(self); } - pub fn getLlvmType(self: *Enum, cunit: *CompilationUnit) llvm.TypeRef { + pub fn getLlvmType(self: *Enum, ofile: *ObjectFile) llvm.TypeRef { @panic("TODO"); } }; @@ -371,11 +371,11 @@ pub const Type = struct { pub const Union = struct { base: Type, - pub fn destroy(self: *Union, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Union, comp: *Compilation) void { + comp.a().destroy(self); } - pub fn getLlvmType(self: *Union, cunit: *CompilationUnit) llvm.TypeRef { + pub fn getLlvmType(self: *Union, ofile: *ObjectFile) llvm.TypeRef { @panic("TODO"); } }; @@ -383,27 +383,27 @@ pub const Type = struct { pub const Namespace = struct { base: Type, - pub fn destroy(self: *Namespace, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Namespace, comp: *Compilation) void { + comp.a().destroy(self); } }; pub const Block = struct { base: Type, - pub fn destroy(self: *Block, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Block, comp: *Compilation) void { + comp.a().destroy(self); } }; pub const BoundFn = struct { base: Type, - pub fn destroy(self: *BoundFn, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *BoundFn, comp: *Compilation) void { + comp.a().destroy(self); } - pub fn getLlvmType(self: *BoundFn, cunit: *CompilationUnit) llvm.TypeRef { + pub fn getLlvmType(self: *BoundFn, ofile: *ObjectFile) llvm.TypeRef { @panic("TODO"); } }; @@ -411,19 +411,19 @@ pub const Type = struct { pub const ArgTuple = struct { base: Type, - pub fn destroy(self: *ArgTuple, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *ArgTuple, comp: *Compilation) void { + comp.a().destroy(self); } }; pub const Opaque = struct { base: Type, - pub fn destroy(self: *Opaque, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Opaque, comp: *Compilation) void { + comp.a().destroy(self); } - pub fn getLlvmType(self: *Opaque, cunit: *CompilationUnit) llvm.TypeRef { + pub fn getLlvmType(self: *Opaque, ofile: *ObjectFile) llvm.TypeRef { @panic("TODO"); } }; @@ -431,11 +431,11 @@ pub const Type = struct { pub const Promise = struct { base: Type, - pub fn destroy(self: *Promise, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Promise, comp: *Compilation) void { + comp.a().destroy(self); } - pub fn getLlvmType(self: *Promise, cunit: *CompilationUnit) llvm.TypeRef { + pub fn getLlvmType(self: *Promise, ofile: *ObjectFile) llvm.TypeRef { @panic("TODO"); } }; diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index 779e5c2e45..8c047b1513 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -1,7 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); const Scope = @import("scope.zig").Scope; -const Module = @import("module.zig").Module; +const Compilation = @import("compilation.zig").Compilation; /// Values are ref-counted, heap-allocated, and copy-on-write /// If there is only 1 ref then write need not copy @@ -16,16 +16,16 @@ pub const Value = struct { } /// Thread-safe - pub fn deref(base: *Value, module: *Module) void { + pub fn deref(base: *Value, comp: *Compilation) void { if (base.ref_count.decr() == 1) { - base.typeof.base.deref(module); + base.typeof.base.deref(comp); switch (base.id) { - Id.Type => @fieldParentPtr(Type, "base", base).destroy(module), - Id.Fn => @fieldParentPtr(Fn, "base", base).destroy(module), - Id.Void => @fieldParentPtr(Void, "base", base).destroy(module), - Id.Bool => @fieldParentPtr(Bool, "base", base).destroy(module), - Id.NoReturn => @fieldParentPtr(NoReturn, "base", base).destroy(module), - Id.Ptr => @fieldParentPtr(Ptr, "base", base).destroy(module), + Id.Type => @fieldParentPtr(Type, "base", base).destroy(comp), + Id.Fn => @fieldParentPtr(Fn, "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), } } } @@ -68,8 +68,8 @@ pub const Value = struct { /// Creates a Fn value with 1 ref /// Takes ownership of symbol_name - pub fn create(module: *Module, fn_type: *Type.Fn, fndef_scope: *Scope.FnDef, symbol_name: std.Buffer) !*Fn { - const self = try module.a().create(Fn{ + pub fn create(comp: *Compilation, fn_type: *Type.Fn, fndef_scope: *Scope.FnDef, symbol_name: std.Buffer) !*Fn { + const self = try comp.a().create(Fn{ .base = Value{ .id = Value.Id.Fn, .typeof = &fn_type.base, @@ -86,23 +86,23 @@ pub const Value = struct { return self; } - pub fn destroy(self: *Fn, module: *Module) void { - self.fndef_scope.base.deref(module); + pub fn destroy(self: *Fn, comp: *Compilation) void { + self.fndef_scope.base.deref(comp); self.symbol_name.deinit(); - module.a().destroy(self); + comp.a().destroy(self); } }; pub const Void = struct { base: Value, - pub fn get(module: *Module) *Void { - module.void_value.base.ref(); - return module.void_value; + pub fn get(comp: *Compilation) *Void { + comp.void_value.base.ref(); + return comp.void_value; } - pub fn destroy(self: *Void, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Void, comp: *Compilation) void { + comp.a().destroy(self); } }; @@ -110,31 +110,31 @@ pub const Value = struct { base: Value, x: bool, - pub fn get(module: *Module, x: bool) *Bool { + pub fn get(comp: *Compilation, x: bool) *Bool { if (x) { - module.true_value.base.ref(); - return module.true_value; + comp.true_value.base.ref(); + return comp.true_value; } else { - module.false_value.base.ref(); - return module.false_value; + comp.false_value.base.ref(); + return comp.false_value; } } - pub fn destroy(self: *Bool, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Bool, comp: *Compilation) void { + comp.a().destroy(self); } }; pub const NoReturn = struct { base: Value, - pub fn get(module: *Module) *NoReturn { - module.noreturn_value.base.ref(); - return module.noreturn_value; + pub fn get(comp: *Compilation) *NoReturn { + comp.noreturn_value.base.ref(); + return comp.noreturn_value; } - pub fn destroy(self: *NoReturn, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *NoReturn, comp: *Compilation) void { + comp.a().destroy(self); } }; @@ -147,8 +147,8 @@ pub const Value = struct { RunTime, }; - pub fn destroy(self: *Ptr, module: *Module) void { - module.a().destroy(self); + pub fn destroy(self: *Ptr, comp: *Compilation) void { + comp.a().destroy(self); } }; }; -- cgit v1.2.3 From 363f4facea7fac2d6cfeab9d1d276ecd8e8e4df0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 15 Jul 2018 00:04:12 -0400 Subject: self-hosted: generate LLVM IR for simple function --- src-self-hosted/codegen.zig | 158 +++++++++++++++++++++++++++++++++++++++- src-self-hosted/compilation.zig | 6 +- src-self-hosted/ir.zig | 56 ++++++++++++-- src-self-hosted/llvm.zig | 64 +++++++++++++++- src-self-hosted/scope.zig | 32 ++++++++ src-self-hosted/type.zig | 75 +++++++++++++++++++ src-self-hosted/value.zig | 22 ++++++ std/event/future.zig | 10 +++ 8 files changed, 412 insertions(+), 11 deletions(-) (limited to 'src-self-hosted/codegen.zig') diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index a07485e74e..698f1e5b45 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -8,6 +8,7 @@ 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; pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) !void { fn_val.base.ref(); @@ -35,9 +36,23 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) try renderToLlvmModule(&ofile, fn_val, code); + // TODO module level assembly + //if (buf_len(&g->global_asm) != 0) { + // LLVMSetModuleInlineAsm(g->module, buf_ptr(&g->global_asm)); + //} + + // TODO + //ZigLLVMDIBuilderFinalize(g->dbuilder); + if (comp.verbose_llvm_ir) { llvm.DumpModule(ofile.module); } + + // verify the llvm module when safety is on + if (std.debug.runtime_safety) { + var error_ptr: ?[*]u8 = null; + _ = llvm.VerifyModule(ofile.module, llvm.AbortProcessAction, &error_ptr); + } } pub const ObjectFile = struct { @@ -55,5 +70,146 @@ 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 = llvm.AddFunction(ofile.module, fn_val.symbol_name.ptr(), llvm_fn_type); + const llvm_fn = llvm.AddFunction( + ofile.module, + fn_val.symbol_name.ptr(), + llvm_fn_type, + ) orelse return error.OutOfMemory; + + const want_fn_safety = fn_val.block_scope.safety.get(ofile.comp); + if (want_fn_safety and ofile.comp.haveLibC()) { + try addLLVMFnAttr(ofile, llvm_fn, "sspstrong"); + try addLLVMFnAttrStr(ofile, llvm_fn, "stack-protector-buffer-size", "4"); + } + + // TODO + //if (fn_val.align_stack) |align_stack| { + // try addLLVMFnAttrInt(ofile, llvm_fn, "alignstack", align_stack); + //} + + const fn_type = fn_val.base.typeof.cast(Type.Fn).?; + + try addLLVMFnAttr(ofile, llvm_fn, "nounwind"); + //add_uwtable_attr(g, fn_table_entry->llvm_value); + try addLLVMFnAttr(ofile, llvm_fn, "nobuiltin"); + + //if (g->build_mode == BuildModeDebug && fn_table_entry->fn_inline != FnInlineAlways) { + // ZigLLVMAddFunctionAttr(fn_table_entry->llvm_value, "no-frame-pointer-elim", "true"); + // ZigLLVMAddFunctionAttr(fn_table_entry->llvm_value, "no-frame-pointer-elim-non-leaf", nullptr); + //} + + //if (fn_table_entry->section_name) { + // LLVMSetSection(fn_table_entry->llvm_value, buf_ptr(fn_table_entry->section_name)); + //} + //if (fn_table_entry->align_bytes > 0) { + // LLVMSetAlignment(fn_table_entry->llvm_value, (unsigned)fn_table_entry->align_bytes); + //} else { + // // We'd like to set the best alignment for the function here, but on Darwin LLVM gives + // // "Cannot getTypeInfo() on a type that is unsized!" assertion failure when calling + // // any of the functions for getting alignment. Not specifying the alignment should + // // use the ABI alignment, which is fine. + //} + + //if (!type_has_bits(return_type)) { + // // nothing to do + //} else if (type_is_codegen_pointer(return_type)) { + // addLLVMAttr(fn_table_entry->llvm_value, 0, "nonnull"); + //} else if (handle_is_ptr(return_type) && + // calling_convention_does_first_arg_return(fn_type->data.fn.fn_type_id.cc)) + //{ + // addLLVMArgAttr(fn_table_entry->llvm_value, 0, "sret"); + // addLLVMArgAttr(fn_table_entry->llvm_value, 0, "nonnull"); + //} + + // TODO set parameter attributes + + // TODO + //uint32_t err_ret_trace_arg_index = get_err_ret_trace_arg_index(g, fn_table_entry); + //if (err_ret_trace_arg_index != UINT32_MAX) { + // addLLVMArgAttr(fn_table_entry->llvm_value, (unsigned)err_ret_trace_arg_index, "nonnull"); + //} + + const cur_ret_ptr = if (fn_type.return_type.handleIsPtr()) llvm.GetParam(llvm_fn, 0) else null; + + // build all basic blocks + for (code.basic_block_list.toSlice()) |bb| { + bb.llvm_block = llvm.AppendBasicBlockInContext( + ofile.context, + llvm_fn, + bb.name_hint, + ) orelse return error.OutOfMemory; + } + const entry_bb = code.basic_block_list.at(0); + llvm.PositionBuilderAtEnd(ofile.builder, entry_bb.llvm_block); + + llvm.ClearCurrentDebugLocation(ofile.builder); + + // TODO set up error return tracing + // TODO allocate temporary stack values + // TODO create debug variable declarations for variables and allocate all local variables + // TODO finishing error return trace setup. we have to do this after all the allocas. + // TODO create debug variable declarations for parameters + + for (code.basic_block_list.toSlice()) |current_block| { + llvm.PositionBuilderAtEnd(ofile.builder, current_block.llvm_block); + for (current_block.instruction_list.toSlice()) |instruction| { + if (instruction.ref_count == 0 and !instruction.hasSideEffects()) continue; + + instruction.llvm_value = try instruction.render(ofile, fn_val); + } + current_block.llvm_exit_block = llvm.GetInsertBlock(ofile.builder); + } +} + +fn addLLVMAttr( + ofile: *ObjectFile, + val: llvm.ValueRef, + attr_index: llvm.AttributeIndex, + attr_name: []const u8, +) !void { + const kind_id = llvm.GetEnumAttributeKindForName(attr_name.ptr, attr_name.len); + assert(kind_id != 0); + const llvm_attr = llvm.CreateEnumAttribute(ofile.context, kind_id, 0) orelse return error.OutOfMemory; + llvm.AddAttributeAtIndex(val, attr_index, llvm_attr); +} + +fn addLLVMAttrStr( + ofile: *ObjectFile, + val: llvm.ValueRef, + attr_index: llvm.AttributeIndex, + attr_name: []const u8, + attr_val: []const u8, +) !void { + const llvm_attr = llvm.CreateStringAttribute( + ofile.context, + attr_name.ptr, + @intCast(c_uint, attr_name.len), + attr_val.ptr, + @intCast(c_uint, attr_val.len), + ) orelse return error.OutOfMemory; + llvm.AddAttributeAtIndex(val, attr_index, llvm_attr); +} + +fn addLLVMAttrInt( + val: llvm.ValueRef, + attr_index: llvm.AttributeIndex, + attr_name: []const u8, + attr_val: u64, +) !void { + const kind_id = llvm.GetEnumAttributeKindForName(attr_name.ptr, attr_name.len); + assert(kind_id != 0); + const llvm_attr = llvm.CreateEnumAttribute(ofile.context, kind_id, attr_val) orelse return error.OutOfMemory; + llvm.AddAttributeAtIndex(val, attr_index, llvm_attr); +} + +fn addLLVMFnAttr(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []const u8) !void { + return addLLVMAttr(ofile, fn_val, @maxValue(llvm.AttributeIndex), attr_name); +} + +fn addLLVMFnAttrStr(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []const u8, attr_val: []const u8) !void { + return addLLVMAttrStr(ofile, fn_val, @maxValue(llvm.AttributeIndex), attr_name, attr_val); +} + +fn addLLVMFnAttrInt(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []const u8, attr_val: u64) !void { + return addLLVMAttrInt(ofile, fn_val, @maxValue(llvm.AttributeIndex), attr_name, attr_val); } diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig index cbda7861bc..1dbbf21206 100644 --- a/src-self-hosted/compilation.zig +++ b/src-self-hosted/compilation.zig @@ -606,6 +606,10 @@ pub const Compilation = struct { return error.Todo; } + pub fn haveLibC(self: *Compilation) bool { + return self.libc_link_lib != null; + } + pub fn addLinkLib(self: *Compilation, name: []const u8, provided_explicitly: bool) !*LinkLib { const is_libc = mem.eql(u8, name, "c"); @@ -741,7 +745,7 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { analyzed_code.dump(); } - // Kick off rendering to LLVM comp, but it doesn't block the fn decl + // Kick off rendering to LLVM module, but it doesn't block the fn decl // analysis from being complete. try comp.build_group.call(codegen.renderToLlvm, comp, fn_val, analyzed_code); } diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 22d5a067a7..0e0a4f9bf3 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -10,6 +10,8 @@ const assert = std.debug.assert; const Token = std.zig.Token; const ParsedFile = @import("parsed_file.zig").ParsedFile; const Span = @import("errmsg.zig").Span; +const llvm = @import("llvm.zig"); +const ObjectFile = @import("codegen.zig").ObjectFile; pub const LVal = enum { None, @@ -61,6 +63,9 @@ pub const Instruction = struct { /// the instruction that this one derives from in analysis parent: ?*Instruction, + /// populated durign codegen + llvm_value: ?llvm.ValueRef, + pub fn cast(base: *Instruction, comptime T: type) ?*T { if (base.id == comptime typeToId(T)) { return @fieldParentPtr(T, "base", base); @@ -108,14 +113,25 @@ pub const Instruction = struct { inline while (i < @memberCount(Id)) : (i += 1) { if (base.id == @field(Id, @memberName(Id, i))) { const T = @field(Instruction, @memberName(Id, i)); - const new_inst = try @fieldParentPtr(T, "base", base).analyze(ira); - new_inst.linkToParent(base); - return new_inst; + return @fieldParentPtr(T, "base", base).analyze(ira); } } unreachable; } + pub fn render(base: *Instruction, 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.Ref => @panic("TODO"), + Id.DeclVar => @panic("TODO"), + Id.CheckVoidStmt => @panic("TODO"), + Id.Phi => @panic("TODO"), + Id.Br => @panic("TODO"), + Id.AddImplicitReturnType => @panic("TODO"), + } + } + fn getAsParam(param: *Instruction) !*Instruction { const child = param.child orelse return error.SemanticAnalysisFailed; switch (child.val) { @@ -186,6 +202,10 @@ pub const Instruction = struct { new_inst.val = IrVal{ .KnownValue = self.base.val.KnownValue.getRef() }; return new_inst; } + + pub fn render(self: *Const, ofile: *ObjectFile, fn_val: *Value.Fn) !?llvm.ValueRef { + return self.base.val.KnownValue.getLlvmConst(ofile); + } }; pub const Return = struct { @@ -214,6 +234,18 @@ pub const Instruction = 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 { + 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); + } + return null; + } }; pub const Ref = struct { @@ -387,12 +419,16 @@ pub const Variable = struct { pub const BasicBlock = struct { ref_count: usize, - name_hint: []const u8, + name_hint: [*]const u8, // must be a C string literal debug_id: usize, scope: *Scope, instruction_list: std.ArrayList(*Instruction), ref_instruction: ?*Instruction, + /// for codegen + llvm_block: llvm.BasicBlockRef, + llvm_exit_block: llvm.BasicBlockRef, + /// the basic block that is derived from this one in analysis child: ?*BasicBlock, @@ -426,7 +462,7 @@ pub const Code = struct { pub fn dump(self: *Code) void { var bb_i: usize = 0; for (self.basic_block_list.toSliceConst()) |bb| { - std.debug.warn("{}_{}:\n", bb.name_hint, bb.debug_id); + std.debug.warn("{s}_{}:\n", bb.name_hint, bb.debug_id); for (bb.instruction_list.toSliceConst()) |instr| { std.debug.warn(" "); instr.dump(); @@ -475,7 +511,7 @@ pub const Builder = struct { } /// No need to clean up resources thanks to the arena allocator. - pub fn createBasicBlock(self: *Builder, scope: *Scope, name_hint: []const u8) !*BasicBlock { + pub fn createBasicBlock(self: *Builder, scope: *Scope, name_hint: [*]const u8) !*BasicBlock { const basic_block = try self.arena().create(BasicBlock{ .ref_count = 0, .name_hint = name_hint, @@ -485,6 +521,8 @@ pub const Builder = struct { .child = null, .parent = null, .ref_instruction = null, + .llvm_block = undefined, + .llvm_exit_block = undefined, }); self.next_debug_id += 1; return basic_block; @@ -600,7 +638,7 @@ pub const Builder = struct { if (block.label) |label| { block_scope.incoming_values = std.ArrayList(*Instruction).init(irb.arena()); block_scope.incoming_blocks = std.ArrayList(*BasicBlock).init(irb.arena()); - block_scope.end_block = try irb.createBasicBlock(parent_scope, "BlockEnd"); + block_scope.end_block = try irb.createBasicBlock(parent_scope, c"BlockEnd"); block_scope.is_comptime = try irb.buildConstBool( parent_scope, Span.token(block.lbrace), @@ -777,6 +815,7 @@ pub const Builder = struct { .span = span, .child = null, .parent = null, + .llvm_value = undefined, }, .params = params, }); @@ -968,7 +1007,7 @@ pub async fn gen( var irb = try Builder.init(comp, parsed_file); errdefer irb.abort(); - const entry_block = try irb.createBasicBlock(scope, "Entry"); + const entry_block = try irb.createBasicBlock(scope, c"Entry"); entry_block.ref(); // Entry block gets a reference because we enter it to begin. try irb.setCursorAtEndAndAppendBlock(entry_block); @@ -1013,6 +1052,7 @@ pub async fn analyze(comp: *Compilation, parsed_file: *ParsedFile, old_code: *Co } const return_inst = try old_instruction.analyze(&ira); + 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 b815f75b05..13480dc2c6 100644 --- a/src-self-hosted/llvm.zig +++ b/src-self-hosted/llvm.zig @@ -2,29 +2,91 @@ const builtin = @import("builtin"); const c = @import("c.zig"); const assert = @import("std").debug.assert; +pub const AttributeIndex = c_uint; +pub const Bool = c_int; + pub const BuilderRef = removeNullability(c.LLVMBuilderRef); pub const ContextRef = removeNullability(c.LLVMContextRef); pub const ModuleRef = removeNullability(c.LLVMModuleRef); pub const ValueRef = removeNullability(c.LLVMValueRef); pub const TypeRef = removeNullability(c.LLVMTypeRef); +pub const BasicBlockRef = removeNullability(c.LLVMBasicBlockRef); +pub const AttributeRef = removeNullability(c.LLVMAttributeRef); +pub const AddAttributeAtIndex = c.LLVMAddAttributeAtIndex; pub const AddFunction = c.LLVMAddFunction; +pub const ClearCurrentDebugLocation = c.ZigLLVMClearCurrentDebugLocation; +pub const ConstInt = c.LLVMConstInt; +pub const ConstStringInContext = c.LLVMConstStringInContext; +pub const ConstStructInContext = c.LLVMConstStructInContext; pub const CreateBuilderInContext = c.LLVMCreateBuilderInContext; +pub const CreateEnumAttribute = c.LLVMCreateEnumAttribute; +pub const CreateStringAttribute = c.LLVMCreateStringAttribute; pub const DisposeBuilder = c.LLVMDisposeBuilder; pub const DisposeModule = c.LLVMDisposeModule; +pub const DoubleTypeInContext = c.LLVMDoubleTypeInContext; pub const DumpModule = c.LLVMDumpModule; +pub const FP128TypeInContext = c.LLVMFP128TypeInContext; +pub const FloatTypeInContext = c.LLVMFloatTypeInContext; +pub const GetEnumAttributeKindForName = c.LLVMGetEnumAttributeKindForName; +pub const GetMDKindIDInContext = c.LLVMGetMDKindIDInContext; +pub const HalfTypeInContext = c.LLVMHalfTypeInContext; +pub const InsertBasicBlockInContext = c.LLVMInsertBasicBlockInContext; +pub const Int128TypeInContext = c.LLVMInt128TypeInContext; +pub const Int16TypeInContext = c.LLVMInt16TypeInContext; +pub const Int1TypeInContext = c.LLVMInt1TypeInContext; +pub const Int32TypeInContext = c.LLVMInt32TypeInContext; +pub const Int64TypeInContext = c.LLVMInt64TypeInContext; +pub const Int8TypeInContext = c.LLVMInt8TypeInContext; +pub const IntPtrTypeForASInContext = c.LLVMIntPtrTypeForASInContext; +pub const IntPtrTypeInContext = c.LLVMIntPtrTypeInContext; +pub const IntTypeInContext = c.LLVMIntTypeInContext; +pub const LabelTypeInContext = c.LLVMLabelTypeInContext; +pub const MDNodeInContext = c.LLVMMDNodeInContext; +pub const MDStringInContext = c.LLVMMDStringInContext; +pub const MetadataTypeInContext = c.LLVMMetadataTypeInContext; pub const ModuleCreateWithNameInContext = c.LLVMModuleCreateWithNameInContext; +pub const PPCFP128TypeInContext = c.LLVMPPCFP128TypeInContext; +pub const StructTypeInContext = c.LLVMStructTypeInContext; +pub const TokenTypeInContext = c.LLVMTokenTypeInContext; pub const VoidTypeInContext = c.LLVMVoidTypeInContext; +pub const X86FP80TypeInContext = c.LLVMX86FP80TypeInContext; +pub const X86MMXTypeInContext = c.LLVMX86MMXTypeInContext; +pub const ConstAllOnes = c.LLVMConstAllOnes; +pub const ConstNull = c.LLVMConstNull; + +pub const VerifyModule = LLVMVerifyModule; +extern fn LLVMVerifyModule(M: ModuleRef, Action: VerifierFailureAction, OutMessage: *?[*]u8) Bool; + +pub const GetInsertBlock = LLVMGetInsertBlock; +extern fn LLVMGetInsertBlock(Builder: BuilderRef) BasicBlockRef; pub const FunctionType = LLVMFunctionType; extern fn LLVMFunctionType( ReturnType: TypeRef, ParamTypes: [*]TypeRef, ParamCount: c_uint, - IsVarArg: c_int, + IsVarArg: Bool, ) ?TypeRef; +pub const GetParam = LLVMGetParam; +extern fn LLVMGetParam(Fn: ValueRef, Index: c_uint) ValueRef; + +pub const AppendBasicBlockInContext = LLVMAppendBasicBlockInContext; +extern fn LLVMAppendBasicBlockInContext(C: ContextRef, Fn: ValueRef, Name: [*]const u8) ?BasicBlockRef; + +pub const PositionBuilderAtEnd = LLVMPositionBuilderAtEnd; +extern fn LLVMPositionBuilderAtEnd(Builder: BuilderRef, Block: BasicBlockRef) void; + +pub const AbortProcessAction = VerifierFailureAction.LLVMAbortProcessAction; +pub const PrintMessageAction = VerifierFailureAction.LLVMPrintMessageAction; +pub const ReturnStatusAction = VerifierFailureAction.LLVMReturnStatusAction; +pub const VerifierFailureAction = c.LLVMVerifierFailureAction; + 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; diff --git a/src-self-hosted/scope.zig b/src-self-hosted/scope.zig index 6fd6456b12..4326617fa0 100644 --- a/src-self-hosted/scope.zig +++ b/src-self-hosted/scope.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const Allocator = mem.Allocator; const Decl = @import("decl.zig").Decl; const Compilation = @import("compilation.zig").Compilation; @@ -6,6 +7,7 @@ const mem = std.mem; const ast = std.zig.ast; const Value = @import("value.zig").Value; const ir = @import("ir.zig"); +const Span = @import("errmsg.zig").Span; pub const Scope = struct { id: Id, @@ -93,6 +95,35 @@ pub const Scope = struct { end_block: *ir.BasicBlock, is_comptime: *ir.Instruction, + safety: Safety, + + const Safety = union(enum) { + Auto, + Manual: Manual, + + const Manual = struct { + /// the source span that disabled the safety value + span: Span, + + /// whether safety is enabled + enabled: bool, + }; + + fn get(self: Safety, comp: *Compilation) bool { + return switch (self) { + Safety.Auto => switch (comp.build_mode) { + builtin.Mode.Debug, + builtin.Mode.ReleaseSafe, + => true, + builtin.Mode.ReleaseFast, + builtin.Mode.ReleaseSmall, + => false, + }, + @TagType(Safety).Manual => |man| man.enabled, + }; + } + }; + /// Creates a Block scope with 1 reference pub fn create(comp: *Compilation, parent: ?*Scope) !*Block { const self = try comp.a().create(Block{ @@ -105,6 +136,7 @@ pub const Scope = struct { .incoming_blocks = undefined, .end_block = undefined, .is_comptime = undefined, + .safety = Safety.Auto, }); errdefer comp.a().destroy(self); diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index 8349047749..670547cce2 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -72,6 +72,81 @@ pub const Type = struct { } } + pub fn handleIsPtr(base: *Type) bool { + switch (base.id) { + Id.Type, + Id.ComptimeFloat, + Id.ComptimeInt, + Id.Undefined, + Id.Null, + Id.Namespace, + Id.Block, + Id.BoundFn, + Id.ArgTuple, + Id.Opaque, + => unreachable, + + Id.NoReturn, + Id.Void, + Id.Bool, + Id.Int, + Id.Float, + Id.Pointer, + Id.ErrorSet, + Id.Enum, + Id.Fn, + Id.Promise, + => return false, + + Id.Struct => @panic("TODO"), + Id.Array => @panic("TODO"), + Id.Optional => @panic("TODO"), + Id.ErrorUnion => @panic("TODO"), + Id.Union => @panic("TODO"), + } + } + + pub fn hasBits(base: *Type) bool { + switch (base.id) { + Id.Type, + Id.ComptimeFloat, + Id.ComptimeInt, + Id.Undefined, + Id.Null, + Id.Namespace, + Id.Block, + Id.BoundFn, + Id.ArgTuple, + Id.Opaque, + => unreachable, + + Id.Void, + Id.NoReturn, + => return false, + + Id.Bool, + Id.Int, + Id.Float, + Id.Fn, + Id.Promise, + => return true, + + Id.ErrorSet => @panic("TODO"), + Id.Enum => @panic("TODO"), + Id.Pointer => @panic("TODO"), + Id.Struct => @panic("TODO"), + Id.Array => @panic("TODO"), + Id.Optional => @panic("TODO"), + Id.ErrorUnion => @panic("TODO"), + Id.Union => @panic("TODO"), + } + } + + pub fn cast(base: *Type, comptime T: type) ?*T { + if (base.id != @field(Id, @typeName(T))) return null; + return @fieldParentPtr(T, "base", base); + } + pub fn dump(base: *const Type) void { std.debug.warn("{}", @tagName(base.id)); } diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index 8c047b1513..e3b91d2807 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -2,6 +2,8 @@ const std = @import("std"); const builtin = @import("builtin"); const Scope = @import("scope.zig").Scope; const Compilation = @import("compilation.zig").Compilation; +const ObjectFile = @import("codegen.zig").ObjectFile; +const llvm = @import("llvm.zig"); /// Values are ref-counted, heap-allocated, and copy-on-write /// If there is only 1 ref then write need not copy @@ -39,6 +41,17 @@ pub const Value = struct { std.debug.warn("{}", @tagName(base.id)); } + pub fn getLlvmConst(base: *Value, ofile: *ObjectFile) (error{OutOfMemory}!?llvm.ValueRef) { + switch (base.id) { + Id.Type => unreachable, + Id.Fn => @panic("TODO"), + Id.Void => return null, + Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmConst(ofile), + Id.NoReturn => unreachable, + Id.Ptr => @panic("TODO"), + } + } + pub const Id = enum { Type, Fn, @@ -123,6 +136,15 @@ pub const Value = struct { pub fn destroy(self: *Bool, comp: *Compilation) void { comp.a().destroy(self); } + + pub fn getLlvmConst(self: *Bool, ofile: *ObjectFile) ?llvm.ValueRef { + const llvm_type = llvm.Int1TypeInContext(ofile.context); + if (self.x) { + return llvm.ConstAllOnes(llvm_type); + } else { + return llvm.ConstNull(llvm_type); + } + } }; pub const NoReturn = struct { diff --git a/std/event/future.zig b/std/event/future.zig index 23fa570c8f..0f27b4131b 100644 --- a/std/event/future.zig +++ b/std/event/future.zig @@ -40,6 +40,16 @@ pub fn Future(comptime T: type) type { return &self.data; } + /// Gets the data without waiting for it. If it's available, a pointer is + /// returned. Otherwise, null is returned. + pub fn getOrNull(self: *Self) ?*T { + if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 1) { + return &self.data; + } else { + return null; + } + } + /// Make the data become available. May be called only once. /// Before calling this, modify the `data` property. pub fn resolve(self: *Self) void { -- cgit v1.2.3 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 --- CMakeLists.txt | 1 + src-self-hosted/codegen.zig | 86 ++++++++++- src-self-hosted/compilation.zig | 335 ++++++++++++++++++++++++++++++++-------- src-self-hosted/ir.zig | 10 +- src-self-hosted/llvm.zig | 75 ++++++++- src-self-hosted/main.zig | 8 +- src-self-hosted/package.zig | 29 ++++ src-self-hosted/scope.zig | 32 ++-- src-self-hosted/target.zig | 116 ++++++++++---- src-self-hosted/test.zig | 3 +- src-self-hosted/type.zig | 58 +++---- src-self-hosted/value.zig | 41 ++++- src/zig_llvm.cpp | 5 + src/zig_llvm.h | 3 +- std/atomic/int.zig | 4 + std/buffer.zig | 13 ++ std/dwarf.zig | 37 +++++ std/event/future.zig | 39 ++++- std/index.zig | 3 + std/lazy_init.zig | 85 ++++++++++ 20 files changed, 808 insertions(+), 175 deletions(-) create mode 100644 src-self-hosted/package.zig create mode 100644 std/lazy_init.zig (limited to 'src-self-hosted/codegen.zig') diff --git a/CMakeLists.txt b/CMakeLists.txt index e606855555..0e7c1df350 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -479,6 +479,7 @@ set(ZIG_STD_FILES "index.zig" "io.zig" "json.zig" + "lazy_init.zig" "linked_list.zig" "macho.zig" "math/acos.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(); } }; diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig index 1dbbf21206..d5380a0644 100644 --- a/src-self-hosted/compilation.zig +++ b/src-self-hosted/compilation.zig @@ -26,16 +26,31 @@ const Value = @import("value.zig").Value; const Type = Value.Type; const Span = errmsg.Span; const codegen = @import("codegen.zig"); +const Package = @import("package.zig").Package; /// Data that is local to the event loop. pub const EventLoopLocal = struct { loop: *event.Loop, llvm_handle_pool: std.atomic.Stack(llvm.ContextRef), - fn init(loop: *event.Loop) EventLoopLocal { + /// TODO pool these so that it doesn't have to lock + prng: event.Locked(std.rand.DefaultPrng), + + var lazy_init_targets = std.lazyInit(void); + + fn init(loop: *event.Loop) !EventLoopLocal { + lazy_init_targets.get() orelse { + Target.initializeAll(); + lazy_init_targets.resolve(); + }; + + var seed_bytes: [@sizeOf(u64)]u8 = undefined; + try std.os.getRandomBytes(seed_bytes[0..]); + const seed = std.mem.readInt(seed_bytes, u64, builtin.Endian.Big); return EventLoopLocal{ .loop = loop, .llvm_handle_pool = std.atomic.Stack(llvm.ContextRef).init(), + .prng = event.Locked(std.rand.DefaultPrng).init(loop, std.rand.DefaultPrng.init(seed)), }; } @@ -76,10 +91,16 @@ pub const Compilation = struct { event_loop_local: *EventLoopLocal, loop: *event.Loop, name: Buffer, + llvm_triple: Buffer, root_src_path: ?[]const u8, target: Target, + llvm_target: llvm.TargetRef, build_mode: builtin.Mode, zig_lib_dir: []const u8, + zig_std_dir: []const u8, + + /// lazily created when we need it + tmp_dir: event.Future(BuildError![]u8), version_major: u32, version_minor: u32, @@ -106,8 +127,16 @@ pub const Compilation = struct { lib_dirs: []const []const u8, rpath_list: []const []const u8, assembly_files: []const []const u8, + + /// paths that are explicitly provided by the user to link against link_objects: []const []const u8, + /// functions that have their own objects that we need to link + /// it uses an optional pointer so that tombstone removals are possible + fn_link_set: event.Locked(FnLinkSet), + + pub const FnLinkSet = std.LinkedList(?*Value.Fn); + windows_subsystem_windows: bool, windows_subsystem_console: bool, @@ -141,7 +170,7 @@ pub const Compilation = struct { /// Before code generation starts, must wait on this group to make sure /// the build is complete. - build_group: event.Group(BuildError!void), + prelink_group: event.Group(BuildError!void), compile_errors: event.Locked(CompileErrList), @@ -155,6 +184,16 @@ pub const Compilation = struct { false_value: *Value.Bool, noreturn_value: *Value.NoReturn, + target_machine: llvm.TargetMachineRef, + target_data_ref: llvm.TargetDataRef, + target_layout_str: [*]u8, + + /// for allocating things which have the same lifetime as this Compilation + arena_allocator: std.heap.ArenaAllocator, + + root_package: *Package, + std_package: *Package, + const CompileErrList = std.ArrayList(*errmsg.Msg); // TODO handle some of these earlier and report them in a way other than error codes @@ -195,6 +234,9 @@ pub const Compilation = struct { BufferTooSmall, Unimplemented, // TODO remove this one SemanticAnalysisFailed, // TODO remove this one + ReadOnlyFileSystem, + LinkQuotaExceeded, + EnvironmentVariableNotFound, }; pub const Event = union(enum) { @@ -234,31 +276,31 @@ pub const Compilation = struct { event_loop_local: *EventLoopLocal, name: []const u8, root_src_path: ?[]const u8, - target: *const Target, + target: Target, kind: Kind, build_mode: builtin.Mode, + is_static: bool, zig_lib_dir: []const u8, cache_dir: []const u8, ) !*Compilation { const loop = event_loop_local.loop; - - var name_buffer = try Buffer.init(loop.allocator, name); - errdefer name_buffer.deinit(); - - const events = try event.Channel(Event).create(loop, 0); - errdefer events.destroy(); - - const comp = try loop.allocator.create(Compilation{ + const comp = try event_loop_local.loop.allocator.create(Compilation{ .loop = loop, + .arena_allocator = std.heap.ArenaAllocator.init(loop.allocator), .event_loop_local = event_loop_local, - .events = events, - .name = name_buffer, + .events = undefined, .root_src_path = root_src_path, - .target = target.*, + .target = target, + .llvm_target = undefined, .kind = kind, .build_mode = build_mode, .zig_lib_dir = zig_lib_dir, + .zig_std_dir = undefined, .cache_dir = cache_dir, + .tmp_dir = event.Future(BuildError![]u8).init(loop), + + .name = undefined, + .llvm_triple = undefined, .version_major = 0, .version_minor = 0, @@ -283,7 +325,7 @@ pub const Compilation = struct { .is_test = false, .each_lib_rpath = false, .strip = false, - .is_static = false, + .is_static = is_static, .linker_rdynamic = false, .clang_argv = [][]const u8{}, .llvm_argv = [][]const u8{}, @@ -291,9 +333,10 @@ pub const Compilation = struct { .rpath_list = [][]const u8{}, .assembly_files = [][]const u8{}, .link_objects = [][]const u8{}, + .fn_link_set = event.Locked(FnLinkSet).init(loop, FnLinkSet.init()), .windows_subsystem_windows = false, .windows_subsystem_console = false, - .link_libs_list = ArrayList(*LinkLib).init(loop.allocator), + .link_libs_list = undefined, .libc_link_lib = null, .err_color = errmsg.Color.Auto, .darwin_frameworks = [][]const u8{}, @@ -303,7 +346,7 @@ pub const Compilation = struct { .emit_file_type = Emit.Binary, .link_out_file = null, .exported_symbol_names = event.Locked(Decl.Table).init(loop, Decl.Table.init(loop.allocator)), - .build_group = event.Group(BuildError!void).init(loop), + .prelink_group = event.Group(BuildError!void).init(loop), .compile_errors = event.Locked(CompileErrList).init(loop, CompileErrList.init(loop.allocator)), .meta_type = undefined, @@ -314,13 +357,82 @@ pub const Compilation = struct { .false_value = undefined, .noreturn_type = undefined, .noreturn_value = undefined, + + .target_machine = undefined, + .target_data_ref = undefined, + .target_layout_str = undefined, + + .root_package = undefined, + .std_package = undefined, }); + errdefer { + comp.arena_allocator.deinit(); + comp.loop.allocator.destroy(comp); + } + + comp.name = try Buffer.init(comp.arena(), name); + comp.llvm_triple = try target.getTriple(comp.arena()); + comp.llvm_target = try Target.llvmTargetFromTriple(comp.llvm_triple); + comp.link_libs_list = ArrayList(*LinkLib).init(comp.arena()); + comp.zig_std_dir = try std.os.path.join(comp.arena(), zig_lib_dir, "std"); + + const opt_level = switch (build_mode) { + builtin.Mode.Debug => llvm.CodeGenLevelNone, + else => llvm.CodeGenLevelAggressive, + }; + + const reloc_mode = if (is_static) llvm.RelocStatic else llvm.RelocPIC; + + // LLVM creates invalid binaries on Windows sometimes. + // See https://github.com/ziglang/zig/issues/508 + // As a workaround we do not use target native features on Windows. + var target_specific_cpu_args: ?[*]u8 = null; + var target_specific_cpu_features: ?[*]u8 = null; + errdefer llvm.DisposeMessage(target_specific_cpu_args); + errdefer llvm.DisposeMessage(target_specific_cpu_features); + if (target == Target.Native and !target.isWindows()) { + target_specific_cpu_args = llvm.GetHostCPUName() orelse return error.OutOfMemory; + target_specific_cpu_features = llvm.GetNativeFeatures() orelse return error.OutOfMemory; + } + + comp.target_machine = llvm.CreateTargetMachine( + comp.llvm_target, + comp.llvm_triple.ptr(), + target_specific_cpu_args orelse c"", + target_specific_cpu_features orelse c"", + opt_level, + reloc_mode, + llvm.CodeModelDefault, + ) orelse return error.OutOfMemory; + errdefer llvm.DisposeTargetMachine(comp.target_machine); + + comp.target_data_ref = llvm.CreateTargetDataLayout(comp.target_machine) orelse return error.OutOfMemory; + errdefer llvm.DisposeTargetData(comp.target_data_ref); + + comp.target_layout_str = llvm.CopyStringRepOfTargetData(comp.target_data_ref) orelse return error.OutOfMemory; + errdefer llvm.DisposeMessage(comp.target_layout_str); + + comp.events = try event.Channel(Event).create(comp.loop, 0); + errdefer comp.events.destroy(); + + if (root_src_path) |root_src| { + const dirname = std.os.path.dirname(root_src) orelse "."; + const basename = std.os.path.basename(root_src); + + comp.root_package = try Package.create(comp.arena(), dirname, basename); + comp.std_package = try Package.create(comp.arena(), comp.zig_std_dir, "index.zig"); + try comp.root_package.add("std", comp.std_package); + } else { + comp.root_package = try Package.create(comp.arena(), ".", ""); + } + try comp.initTypes(); + return comp; } fn initTypes(comp: *Compilation) !void { - comp.meta_type = try comp.a().create(Type.MetaType{ + comp.meta_type = try comp.gpa().create(Type.MetaType{ .base = Type{ .base = Value{ .id = Value.Id.Type, @@ -333,9 +445,9 @@ pub const Compilation = struct { }); comp.meta_type.value = &comp.meta_type.base; comp.meta_type.base.base.typeof = &comp.meta_type.base; - errdefer comp.a().destroy(comp.meta_type); + errdefer comp.gpa().destroy(comp.meta_type); - comp.void_type = try comp.a().create(Type.Void{ + comp.void_type = try comp.gpa().create(Type.Void{ .base = Type{ .base = Value{ .id = Value.Id.Type, @@ -345,9 +457,9 @@ pub const Compilation = struct { .id = builtin.TypeId.Void, }, }); - errdefer comp.a().destroy(comp.void_type); + errdefer comp.gpa().destroy(comp.void_type); - comp.noreturn_type = try comp.a().create(Type.NoReturn{ + comp.noreturn_type = try comp.gpa().create(Type.NoReturn{ .base = Type{ .base = Value{ .id = Value.Id.Type, @@ -357,9 +469,9 @@ pub const Compilation = struct { .id = builtin.TypeId.NoReturn, }, }); - errdefer comp.a().destroy(comp.noreturn_type); + errdefer comp.gpa().destroy(comp.noreturn_type); - comp.bool_type = try comp.a().create(Type.Bool{ + comp.bool_type = try comp.gpa().create(Type.Bool{ .base = Type{ .base = Value{ .id = Value.Id.Type, @@ -369,18 +481,18 @@ pub const Compilation = struct { .id = builtin.TypeId.Bool, }, }); - errdefer comp.a().destroy(comp.bool_type); + errdefer comp.gpa().destroy(comp.bool_type); - comp.void_value = try comp.a().create(Value.Void{ + comp.void_value = try comp.gpa().create(Value.Void{ .base = Value{ .id = Value.Id.Void, .typeof = &Type.Void.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, }); - errdefer comp.a().destroy(comp.void_value); + errdefer comp.gpa().destroy(comp.void_value); - comp.true_value = try comp.a().create(Value.Bool{ + comp.true_value = try comp.gpa().create(Value.Bool{ .base = Value{ .id = Value.Id.Bool, .typeof = &Type.Bool.get(comp).base, @@ -388,9 +500,9 @@ pub const Compilation = struct { }, .x = true, }); - errdefer comp.a().destroy(comp.true_value); + errdefer comp.gpa().destroy(comp.true_value); - comp.false_value = try comp.a().create(Value.Bool{ + comp.false_value = try comp.gpa().create(Value.Bool{ .base = Value{ .id = Value.Id.Bool, .typeof = &Type.Bool.get(comp).base, @@ -398,19 +510,23 @@ pub const Compilation = struct { }, .x = false, }); - errdefer comp.a().destroy(comp.false_value); + errdefer comp.gpa().destroy(comp.false_value); - comp.noreturn_value = try comp.a().create(Value.NoReturn{ + comp.noreturn_value = try comp.gpa().create(Value.NoReturn{ .base = Value{ .id = Value.Id.NoReturn, .typeof = &Type.NoReturn.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, }); - errdefer comp.a().destroy(comp.noreturn_value); + 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 |_| {}; + self.noreturn_value.base.deref(self); self.void_value.base.deref(self); self.false_value.base.deref(self); @@ -420,14 +536,18 @@ pub const Compilation = struct { self.meta_type.base.base.deref(self); self.events.destroy(); - self.name.deinit(); - self.a().destroy(self); + llvm.DisposeMessage(self.target_layout_str); + llvm.DisposeTargetData(self.target_data_ref); + llvm.DisposeTargetMachine(self.target_machine); + + self.arena_allocator.deinit(); + self.gpa().destroy(self); } pub fn build(self: *Compilation) !void { if (self.llvm_argv.len != 0) { - var c_compatible_args = try std.cstr.NullTerminated2DArray.fromSlices(self.a(), [][]const []const u8{ + var c_compatible_args = try std.cstr.NullTerminated2DArray.fromSlices(self.arena(), [][]const []const u8{ [][]const u8{"zig (LLVM option parsing)"}, self.llvm_argv, }); @@ -436,7 +556,7 @@ pub const Compilation = struct { c.ZigLLVMParseCommandLineOptions(self.llvm_argv.len + 1, c_compatible_args.ptr); } - _ = try async self.buildAsync(); + _ = try async self.buildAsync(); } async fn buildAsync(self: *Compilation) void { @@ -464,7 +584,7 @@ pub const Compilation = struct { } } else |err| { // if there's an error then the compile errors have dangling references - self.a().free(compile_errors); + self.gpa().free(compile_errors); await (async self.events.put(Event{ .Error = err }) catch unreachable); } @@ -477,26 +597,26 @@ pub const Compilation = struct { async fn addRootSrc(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.a(), root_src_path) catch |err| { + const root_src_real_path = os.path.real(self.gpa(), root_src_path) catch |err| { try printError("unable to get real path '{}': {}", root_src_path, err); return err; }; - errdefer self.a().free(root_src_real_path); + errdefer self.gpa().free(root_src_real_path); // TODO async/await readFileAlloc() - const source_code = io.readFileAlloc(self.a(), root_src_real_path) catch |err| { + const source_code = io.readFileAlloc(self.gpa(), root_src_real_path) catch |err| { try printError("unable to open '{}': {}", root_src_real_path, err); return err; }; - errdefer self.a().free(source_code); + errdefer self.gpa().free(source_code); - const parsed_file = try self.a().create(ParsedFile{ + const parsed_file = try self.gpa().create(ParsedFile{ .tree = undefined, .realpath = root_src_real_path, }); - errdefer self.a().destroy(parsed_file); + errdefer self.gpa().destroy(parsed_file); - parsed_file.tree = try std.zig.parse(self.a(), source_code); + parsed_file.tree = try std.zig.parse(self.gpa(), source_code); errdefer parsed_file.tree.deinit(); const tree = &parsed_file.tree; @@ -525,7 +645,7 @@ pub const Compilation = struct { continue; }; - const fn_decl = try self.a().create(Decl.Fn{ + const fn_decl = try self.gpa().create(Decl.Fn{ .base = Decl{ .id = Decl.Id.Fn, .name = name, @@ -538,7 +658,7 @@ pub const Compilation = struct { .value = Decl.Fn.Val{ .Unresolved = {} }, .fn_proto = fn_proto, }); - errdefer self.a().destroy(fn_decl); + errdefer self.gpa().destroy(fn_decl); try decl_group.call(addTopLevelDecl, self, &fn_decl.base); }, @@ -547,15 +667,15 @@ pub const Compilation = struct { } } try await (async decl_group.wait() catch unreachable); - try await (async self.build_group.wait() catch unreachable); + try await (async self.prelink_group.wait() catch unreachable); } async fn addTopLevelDecl(self: *Compilation, decl: *Decl) !void { const is_export = decl.isExported(&decl.parsed_file.tree); if (is_export) { - try self.build_group.call(verifyUniqueSymbol, self, decl); - try self.build_group.call(resolveDecl, self, decl); + try self.prelink_group.call(verifyUniqueSymbol, self, decl); + try self.prelink_group.call(resolveDecl, self, decl); } } @@ -563,7 +683,7 @@ pub const Compilation = struct { const text = try std.fmt.allocPrint(self.loop.allocator, fmt, args); errdefer self.loop.allocator.free(text); - try self.build_group.call(addCompileErrorAsync, self, parsed_file, span, text); + try self.prelink_group.call(addCompileErrorAsync, self, parsed_file, span, text); } async fn addCompileErrorAsync( @@ -625,11 +745,11 @@ pub const Compilation = struct { } } - const link_lib = try self.a().create(LinkLib{ + const link_lib = try self.gpa().create(LinkLib{ .name = name, .path = null, .provided_explicitly = provided_explicitly, - .symbols = ArrayList([]u8).init(self.a()), + .symbols = ArrayList([]u8).init(self.gpa()), }); try self.link_libs_list.append(link_lib); if (is_libc) { @@ -638,9 +758,71 @@ pub const Compilation = struct { return link_lib; } - fn a(self: Compilation) *mem.Allocator { + /// General Purpose Allocator. Must free when done. + fn gpa(self: Compilation) *mem.Allocator { return self.loop.allocator; } + + /// Arena Allocator. Automatically freed when the Compilation is destroyed. + fn arena(self: *Compilation) *mem.Allocator { + return &self.arena_allocator.allocator; + } + + /// If the temporary directory for this compilation has not been created, it creates it. + /// Then it creates a random file name in that dir and returns it. + pub async fn createRandomOutputPath(self: *Compilation, suffix: []const u8) !Buffer { + const tmp_dir = try await (async self.getTmpDir() catch unreachable); + const file_prefix = await (async self.getRandomFileName() catch unreachable); + + const file_name = try std.fmt.allocPrint(self.gpa(), "{}{}", file_prefix[0..], suffix); + defer self.gpa().free(file_name); + + const full_path = try os.path.join(self.gpa(), tmp_dir, file_name[0..]); + errdefer self.gpa().free(full_path); + + return Buffer.fromOwnedSlice(self.gpa(), full_path); + } + + /// If the temporary directory for this Compilation has not been created, creates it. + /// Then returns it. The directory is unique to this Compilation and cleaned up when + /// the Compilation deinitializes. + async fn getTmpDir(self: *Compilation) ![]const u8 { + if (await (async self.tmp_dir.start() catch unreachable)) |ptr| return ptr.*; + self.tmp_dir.data = await (async self.getTmpDirImpl() catch unreachable); + self.tmp_dir.resolve(); + return self.tmp_dir.data; + } + + async fn getTmpDirImpl(self: *Compilation) ![]u8 { + const comp_dir_name = await (async self.getRandomFileName() catch unreachable); + const zig_dir_path = try getZigDir(self.gpa()); + defer self.gpa().free(zig_dir_path); + + const tmp_dir = try os.path.join(self.arena(), zig_dir_path, comp_dir_name[0..]); + try os.makePath(self.gpa(), tmp_dir); + return tmp_dir; + } + + async fn getRandomFileName(self: *Compilation) [12]u8 { + // here we replace the standard +/ with -_ so that it can be used in a file name + const b64_fs_encoder = std.base64.Base64Encoder.init( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", + std.base64.standard_pad_char, + ); + + var rand_bytes: [9]u8 = undefined; + + { + const held = await (async self.event_loop_local.prng.acquire() catch unreachable); + defer held.release(); + + held.value.random.bytes(rand_bytes[0..]); + } + + var result: [12]u8 = undefined; + b64_fs_encoder.encode(result[0..], rand_bytes); + return result; + } }; fn printError(comptime format: []const u8, args: ...) !void { @@ -662,13 +844,11 @@ 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 (@atomicRmw(u8, &decl.resolution_in_progress, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) == 0) { - decl.resolution.data = await (async generateDecl(comp, decl) catch unreachable); - decl.resolution.resolve(); - return decl.resolution.data; - } else { - return (await (async decl.resolution.get() catch unreachable)).*; - } + if (await (async decl.resolution.start() catch unreachable)) |ptr| return ptr.*; + + decl.resolution.data = await (async generateDecl(comp, decl) catch unreachable); + decl.resolution.resolve(); + return decl.resolution.data; } /// The function that actually does the generation. @@ -698,7 +878,7 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { const fn_type = try Type.Fn.create(comp, return_type, params, is_var_args); defer fn_type.base.base.deref(comp); - var symbol_name = try std.Buffer.init(comp.a(), fn_decl.base.name); + var symbol_name = try std.Buffer.init(comp.gpa(), fn_decl.base.name); errdefer symbol_name.deinit(); const fn_val = try Value.Fn.create(comp, fn_type, fndef_scope, symbol_name); @@ -719,7 +899,7 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { error.SemanticAnalysisFailed => return {}, else => return err, }; - defer unanalyzed_code.destroy(comp.a()); + defer unanalyzed_code.destroy(comp.gpa()); if (comp.verbose_ir) { std.debug.warn("unanalyzed:\n"); @@ -738,7 +918,7 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { error.SemanticAnalysisFailed => return {}, else => return err, }; - errdefer analyzed_code.destroy(comp.a()); + errdefer analyzed_code.destroy(comp.gpa()); if (comp.verbose_ir) { std.debug.warn("analyzed:\n"); @@ -747,5 +927,30 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { // Kick off rendering to LLVM module, but it doesn't block the fn decl // analysis from being complete. - try comp.build_group.call(codegen.renderToLlvm, comp, fn_val, analyzed_code); + try comp.prelink_group.call(codegen.renderToLlvm, comp, fn_val, analyzed_code); + try comp.prelink_group.call(addFnToLinkSet, comp, fn_val); +} + +async fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) void { + fn_val.base.ref(); + defer fn_val.base.deref(comp); + + fn_val.link_set_node.data = fn_val; + + const held = await (async comp.fn_link_set.acquire() catch unreachable); + defer held.release(); + + held.value.append(fn_val.link_set_node); +} + +fn getZigDir(allocator: *mem.Allocator) ![]u8 { + const home_dir = try getHomeDir(allocator); + defer allocator.free(home_dir); + + return os.path.join(allocator, home_dir, ".zig"); +} + +/// TODO move to zig std lib, and make it work for other OSes +fn getHomeDir(allocator: *mem.Allocator) ![]u8 { + return os.getEnvVarOwned(allocator, "HOME"); } diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 0e0a4f9bf3..c1f9c97001 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -453,7 +453,7 @@ pub const Code = struct { arena: std.heap.ArenaAllocator, return_type: ?*Type, - /// allocator is comp.a() + /// allocator is comp.gpa() pub fn destroy(self: *Code, allocator: *Allocator) void { self.arena.deinit(); allocator.destroy(self); @@ -483,13 +483,13 @@ pub const Builder = struct { pub const Error = Analyze.Error; pub fn init(comp: *Compilation, parsed_file: *ParsedFile) !Builder { - const code = try comp.a().create(Code{ + const code = try comp.gpa().create(Code{ .basic_block_list = undefined, - .arena = std.heap.ArenaAllocator.init(comp.a()), + .arena = std.heap.ArenaAllocator.init(comp.gpa()), .return_type = null, }); code.basic_block_list = std.ArrayList(*BasicBlock).init(&code.arena.allocator); - errdefer code.destroy(comp.a()); + errdefer code.destroy(comp.gpa()); return Builder{ .comp = comp, @@ -502,7 +502,7 @@ pub const Builder = struct { } pub fn abort(self: *Builder) void { - self.code.destroy(self.comp.a()); + self.code.destroy(self.comp.gpa()); } /// Call code.destroy() when done diff --git a/src-self-hosted/llvm.zig b/src-self-hosted/llvm.zig index 13480dc2c6..b196656367 100644 --- a/src-self-hosted/llvm.zig +++ b/src-self-hosted/llvm.zig @@ -2,6 +2,12 @@ const builtin = @import("builtin"); const c = @import("c.zig"); const assert = @import("std").debug.assert; +// we wrap the c module for 3 reasons: +// 1. to avoid accidentally calling the non-thread-safe functions +// 2. patch up some of the types to remove nullability +// 3. some functions have been augmented by zig_llvm.cpp to be more powerful, +// such as ZigLLVMTargetMachineEmitToFile + pub const AttributeIndex = c_uint; pub const Bool = c_int; @@ -12,25 +18,51 @@ pub const ValueRef = removeNullability(c.LLVMValueRef); pub const TypeRef = removeNullability(c.LLVMTypeRef); pub const BasicBlockRef = removeNullability(c.LLVMBasicBlockRef); pub const AttributeRef = removeNullability(c.LLVMAttributeRef); +pub const TargetRef = removeNullability(c.LLVMTargetRef); +pub const TargetMachineRef = removeNullability(c.LLVMTargetMachineRef); +pub const TargetDataRef = removeNullability(c.LLVMTargetDataRef); +pub const DIBuilder = c.ZigLLVMDIBuilder; pub const AddAttributeAtIndex = c.LLVMAddAttributeAtIndex; pub const AddFunction = c.LLVMAddFunction; +pub const AddModuleCodeViewFlag = c.ZigLLVMAddModuleCodeViewFlag; +pub const AddModuleDebugInfoFlag = c.ZigLLVMAddModuleDebugInfoFlag; pub const ClearCurrentDebugLocation = c.ZigLLVMClearCurrentDebugLocation; +pub const ConstAllOnes = c.LLVMConstAllOnes; pub const ConstInt = c.LLVMConstInt; +pub const ConstNull = c.LLVMConstNull; pub const ConstStringInContext = c.LLVMConstStringInContext; pub const ConstStructInContext = c.LLVMConstStructInContext; +pub const CopyStringRepOfTargetData = c.LLVMCopyStringRepOfTargetData; pub const CreateBuilderInContext = c.LLVMCreateBuilderInContext; +pub const CreateCompileUnit = c.ZigLLVMCreateCompileUnit; +pub const CreateDIBuilder = c.ZigLLVMCreateDIBuilder; pub const CreateEnumAttribute = c.LLVMCreateEnumAttribute; +pub const CreateFile = c.ZigLLVMCreateFile; pub const CreateStringAttribute = c.LLVMCreateStringAttribute; +pub const CreateTargetDataLayout = c.LLVMCreateTargetDataLayout; +pub const CreateTargetMachine = c.LLVMCreateTargetMachine; +pub const DIBuilderFinalize = c.ZigLLVMDIBuilderFinalize; pub const DisposeBuilder = c.LLVMDisposeBuilder; +pub const DisposeDIBuilder = c.ZigLLVMDisposeDIBuilder; +pub const DisposeMessage = c.LLVMDisposeMessage; pub const DisposeModule = c.LLVMDisposeModule; +pub const DisposeTargetData = c.LLVMDisposeTargetData; +pub const DisposeTargetMachine = c.LLVMDisposeTargetMachine; pub const DoubleTypeInContext = c.LLVMDoubleTypeInContext; pub const DumpModule = c.LLVMDumpModule; pub const FP128TypeInContext = c.LLVMFP128TypeInContext; pub const FloatTypeInContext = c.LLVMFloatTypeInContext; pub const GetEnumAttributeKindForName = c.LLVMGetEnumAttributeKindForName; +pub const GetHostCPUName = c.ZigLLVMGetHostCPUName; pub const GetMDKindIDInContext = c.LLVMGetMDKindIDInContext; +pub const GetNativeFeatures = c.ZigLLVMGetNativeFeatures; pub const HalfTypeInContext = c.LLVMHalfTypeInContext; +pub const InitializeAllAsmParsers = c.LLVMInitializeAllAsmParsers; +pub const InitializeAllAsmPrinters = c.LLVMInitializeAllAsmPrinters; +pub const InitializeAllTargetInfos = c.LLVMInitializeAllTargetInfos; +pub const InitializeAllTargetMCs = c.LLVMInitializeAllTargetMCs; +pub const InitializeAllTargets = c.LLVMInitializeAllTargets; pub const InsertBasicBlockInContext = c.LLVMInsertBasicBlockInContext; pub const Int128TypeInContext = c.LLVMInt128TypeInContext; pub const Int16TypeInContext = c.LLVMInt16TypeInContext; @@ -47,13 +79,16 @@ pub const MDStringInContext = c.LLVMMDStringInContext; pub const MetadataTypeInContext = c.LLVMMetadataTypeInContext; pub const ModuleCreateWithNameInContext = c.LLVMModuleCreateWithNameInContext; pub const PPCFP128TypeInContext = c.LLVMPPCFP128TypeInContext; +pub const SetDataLayout = c.LLVMSetDataLayout; +pub const SetTarget = c.LLVMSetTarget; pub const StructTypeInContext = c.LLVMStructTypeInContext; pub const TokenTypeInContext = c.LLVMTokenTypeInContext; pub const VoidTypeInContext = c.LLVMVoidTypeInContext; pub const X86FP80TypeInContext = c.LLVMX86FP80TypeInContext; pub const X86MMXTypeInContext = c.LLVMX86MMXTypeInContext; -pub const ConstAllOnes = c.LLVMConstAllOnes; -pub const ConstNull = c.LLVMConstNull; + +pub const GetTargetFromTriple = LLVMGetTargetFromTriple; +extern fn LLVMGetTargetFromTriple(Triple: [*]const u8, T: *TargetRef, ErrorMessage: ?*[*]u8) Bool; pub const VerifyModule = LLVMVerifyModule; extern fn LLVMVerifyModule(M: ModuleRef, Action: VerifierFailureAction, OutMessage: *?[*]u8) Bool; @@ -83,6 +118,31 @@ pub const PrintMessageAction = VerifierFailureAction.LLVMPrintMessageAction; pub const ReturnStatusAction = VerifierFailureAction.LLVMReturnStatusAction; pub const VerifierFailureAction = c.LLVMVerifierFailureAction; +pub const CodeGenLevelNone = c.LLVMCodeGenOptLevel.LLVMCodeGenLevelNone; +pub const CodeGenLevelLess = c.LLVMCodeGenOptLevel.LLVMCodeGenLevelLess; +pub const CodeGenLevelDefault = c.LLVMCodeGenOptLevel.LLVMCodeGenLevelDefault; +pub const CodeGenLevelAggressive = c.LLVMCodeGenOptLevel.LLVMCodeGenLevelAggressive; +pub const CodeGenOptLevel = c.LLVMCodeGenOptLevel; + +pub const RelocDefault = c.LLVMRelocMode.LLVMRelocDefault; +pub const RelocStatic = c.LLVMRelocMode.LLVMRelocStatic; +pub const RelocPIC = c.LLVMRelocMode.LLVMRelocPIC; +pub const RelocDynamicNoPic = c.LLVMRelocMode.LLVMRelocDynamicNoPic; +pub const RelocMode = c.LLVMRelocMode; + +pub const CodeModelDefault = c.LLVMCodeModel.LLVMCodeModelDefault; +pub const CodeModelJITDefault = c.LLVMCodeModel.LLVMCodeModelJITDefault; +pub const CodeModelSmall = c.LLVMCodeModel.LLVMCodeModelSmall; +pub const CodeModelKernel = c.LLVMCodeModel.LLVMCodeModelKernel; +pub const CodeModelMedium = c.LLVMCodeModel.LLVMCodeModelMedium; +pub const CodeModelLarge = c.LLVMCodeModel.LLVMCodeModelLarge; +pub const CodeModel = c.LLVMCodeModel; + +pub const EmitAssembly = EmitOutputType.ZigLLVM_EmitAssembly; +pub const EmitBinary = EmitOutputType.ZigLLVM_EmitBinary; +pub const EmitLLVMIr = EmitOutputType.ZigLLVM_EmitLLVMIr; +pub const EmitOutputType = c.ZigLLVM_EmitOutputType; + fn removeNullability(comptime T: type) type { comptime assert(@typeId(T) == builtin.TypeId.Optional); return T.Child; @@ -90,3 +150,14 @@ fn removeNullability(comptime T: type) type { pub const BuildRet = LLVMBuildRet; extern fn LLVMBuildRet(arg0: BuilderRef, V: ?ValueRef) ValueRef; + +pub const TargetMachineEmitToFile = ZigLLVMTargetMachineEmitToFile; +extern fn ZigLLVMTargetMachineEmitToFile( + targ_machine_ref: TargetMachineRef, + module_ref: ModuleRef, + filename: [*]const u8, + output_type: EmitOutputType, + error_message: *[*]u8, + is_debug: bool, + is_small: bool, +) bool; diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index c9478954c5..8b668e35bd 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -363,6 +363,8 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co } }; + const is_static = flags.present("static"); + const assembly_files = flags.many("assembly"); const link_objects = flags.many("object"); if (root_source_file == null and link_objects.len == 0 and assembly_files.len == 0) { @@ -389,7 +391,7 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co try loop.initMultiThreaded(allocator); defer loop.deinit(); - var event_loop_local = EventLoopLocal.init(&loop); + var event_loop_local = try EventLoopLocal.init(&loop); defer event_loop_local.deinit(); var comp = try Compilation.create( @@ -399,6 +401,7 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co Target.Native, out_type, build_mode, + is_static, zig_lib_dir, full_cache_dir, ); @@ -426,7 +429,6 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co comp.clang_argv = clang_argv_buf.toSliceConst(); comp.strip = flags.present("strip"); - comp.is_static = flags.present("static"); if (flags.single("libc-lib-dir")) |libc_lib_dir| { comp.libc_lib_dir = libc_lib_dir; @@ -481,9 +483,9 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co } comp.emit_file_type = emit_type; - comp.link_objects = link_objects; comp.assembly_files = assembly_files; comp.link_out_file = flags.single("out-file"); + comp.link_objects = link_objects; try comp.build(); const process_build_events_handle = try async processBuildEvents(comp, color); diff --git a/src-self-hosted/package.zig b/src-self-hosted/package.zig new file mode 100644 index 0000000000..720b279651 --- /dev/null +++ b/src-self-hosted/package.zig @@ -0,0 +1,29 @@ +const std = @import("std"); +const mem = std.mem; +const assert = std.debug.assert; +const Buffer = std.Buffer; + +pub const Package = struct { + root_src_dir: Buffer, + root_src_path: Buffer, + + /// relative to root_src_dir + table: Table, + + pub const Table = std.HashMap([]const u8, *Package, mem.hash_slice_u8, mem.eql_slice_u8); + + /// makes internal copies of root_src_dir and root_src_path + /// allocator should be an arena allocator because Package never frees anything + pub fn create(allocator: *mem.Allocator, root_src_dir: []const u8, root_src_path: []const u8) !*Package { + return allocator.create(Package{ + .root_src_dir = try Buffer.init(allocator, root_src_dir), + .root_src_path = try Buffer.init(allocator, root_src_path), + .table = Table.init(allocator), + }); + } + + pub fn add(self: *Package, name: []const u8, package: *Package) !void { + const entry = try self.table.put(try mem.dupe(self.table.allocator, u8, name), package); + assert(entry == null); + } +}; diff --git a/src-self-hosted/scope.zig b/src-self-hosted/scope.zig index 4326617fa0..1c519d6c08 100644 --- a/src-self-hosted/scope.zig +++ b/src-self-hosted/scope.zig @@ -64,7 +64,7 @@ pub const Scope = struct { /// Creates a Decls scope with 1 reference pub fn create(comp: *Compilation, parent: ?*Scope) !*Decls { - const self = try comp.a().create(Decls{ + const self = try comp.gpa().create(Decls{ .base = Scope{ .id = Id.Decls, .parent = parent, @@ -72,9 +72,9 @@ pub const Scope = struct { }, .table = undefined, }); - errdefer comp.a().destroy(self); + errdefer comp.gpa().destroy(self); - self.table = Decl.Table.init(comp.a()); + self.table = Decl.Table.init(comp.gpa()); errdefer self.table.deinit(); if (parent) |p| p.ref(); @@ -126,7 +126,7 @@ pub const Scope = struct { /// Creates a Block scope with 1 reference pub fn create(comp: *Compilation, parent: ?*Scope) !*Block { - const self = try comp.a().create(Block{ + const self = try comp.gpa().create(Block{ .base = Scope{ .id = Id.Block, .parent = parent, @@ -138,14 +138,14 @@ pub const Scope = struct { .is_comptime = undefined, .safety = Safety.Auto, }); - errdefer comp.a().destroy(self); + errdefer comp.gpa().destroy(self); if (parent) |p| p.ref(); return self; } pub fn destroy(self: *Block, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -158,7 +158,7 @@ pub const Scope = struct { /// Creates a FnDef scope with 1 reference /// Must set the fn_val later pub fn create(comp: *Compilation, parent: ?*Scope) !*FnDef { - const self = try comp.a().create(FnDef{ + const self = try comp.gpa().create(FnDef{ .base = Scope{ .id = Id.FnDef, .parent = parent, @@ -173,7 +173,7 @@ pub const Scope = struct { } pub fn destroy(self: *FnDef, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -182,7 +182,7 @@ pub const Scope = struct { /// Creates a CompTime scope with 1 reference pub fn create(comp: *Compilation, parent: ?*Scope) !*CompTime { - const self = try comp.a().create(CompTime{ + const self = try comp.gpa().create(CompTime{ .base = Scope{ .id = Id.CompTime, .parent = parent, @@ -195,7 +195,7 @@ pub const Scope = struct { } pub fn destroy(self: *CompTime, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -216,7 +216,7 @@ pub const Scope = struct { kind: Kind, defer_expr_scope: *DeferExpr, ) !*Defer { - const self = try comp.a().create(Defer{ + const self = try comp.gpa().create(Defer{ .base = Scope{ .id = Id.Defer, .parent = parent, @@ -225,7 +225,7 @@ pub const Scope = struct { .defer_expr_scope = defer_expr_scope, .kind = kind, }); - errdefer comp.a().destroy(self); + errdefer comp.gpa().destroy(self); defer_expr_scope.base.ref(); @@ -235,7 +235,7 @@ pub const Scope = struct { pub fn destroy(self: *Defer, comp: *Compilation) void { self.defer_expr_scope.base.deref(comp); - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -245,7 +245,7 @@ pub const Scope = struct { /// Creates a DeferExpr scope with 1 reference pub fn create(comp: *Compilation, parent: ?*Scope, expr_node: *ast.Node) !*DeferExpr { - const self = try comp.a().create(DeferExpr{ + const self = try comp.gpa().create(DeferExpr{ .base = Scope{ .id = Id.DeferExpr, .parent = parent, @@ -253,14 +253,14 @@ pub const Scope = struct { }, .expr_node = expr_node, }); - errdefer comp.a().destroy(self); + errdefer comp.gpa().destroy(self); if (parent) |p| p.ref(); return self; } pub fn destroy(self: *DeferExpr, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; }; diff --git a/src-self-hosted/target.zig b/src-self-hosted/target.zig index 724d99ea23..db673e421a 100644 --- a/src-self-hosted/target.zig +++ b/src-self-hosted/target.zig @@ -1,60 +1,118 @@ +const std = @import("std"); const builtin = @import("builtin"); -const c = @import("c.zig"); - -pub const CrossTarget = struct { - arch: builtin.Arch, - os: builtin.Os, - environ: builtin.Environ, -}; +const llvm = @import("llvm.zig"); pub const Target = union(enum) { Native, - Cross: CrossTarget, + Cross: Cross, - pub fn oFileExt(self: *const Target) []const u8 { - const environ = switch (self.*) { - Target.Native => builtin.environ, - Target.Cross => |t| t.environ, - }; - return switch (environ) { - builtin.Environ.msvc => ".obj", + pub const Cross = struct { + arch: builtin.Arch, + os: builtin.Os, + environ: builtin.Environ, + object_format: builtin.ObjectFormat, + }; + + pub fn oFileExt(self: Target) []const u8 { + return switch (self.getObjectFormat()) { + builtin.ObjectFormat.coff => ".obj", else => ".o", }; } - pub fn exeFileExt(self: *const Target) []const u8 { + pub fn exeFileExt(self: Target) []const u8 { return switch (self.getOs()) { builtin.Os.windows => ".exe", else => "", }; } - pub fn getOs(self: *const Target) builtin.Os { - return switch (self.*) { + pub fn getOs(self: Target) builtin.Os { + return switch (self) { Target.Native => builtin.os, - Target.Cross => |t| t.os, + @TagType(Target).Cross => |t| t.os, + }; + } + + pub fn getArch(self: Target) builtin.Arch { + return switch (self) { + Target.Native => builtin.arch, + @TagType(Target).Cross => |t| t.arch, + }; + } + + pub fn getEnviron(self: Target) builtin.Environ { + return switch (self) { + Target.Native => builtin.environ, + @TagType(Target).Cross => |t| t.environ, + }; + } + + pub fn getObjectFormat(self: Target) builtin.ObjectFormat { + return switch (self) { + Target.Native => builtin.object_format, + @TagType(Target).Cross => |t| t.object_format, }; } - pub fn isDarwin(self: *const Target) bool { + pub fn isWasm(self: Target) bool { + return switch (self.getArch()) { + builtin.Arch.wasm32, builtin.Arch.wasm64 => true, + else => false, + }; + } + + pub fn isDarwin(self: Target) bool { return switch (self.getOs()) { builtin.Os.ios, builtin.Os.macosx => true, else => false, }; } - pub fn isWindows(self: *const Target) bool { + pub fn isWindows(self: Target) bool { return switch (self.getOs()) { builtin.Os.windows => true, else => false, }; } -}; -pub fn initializeAll() void { - c.LLVMInitializeAllTargets(); - c.LLVMInitializeAllTargetInfos(); - c.LLVMInitializeAllTargetMCs(); - c.LLVMInitializeAllAsmPrinters(); - c.LLVMInitializeAllAsmParsers(); -} + pub fn initializeAll() void { + llvm.InitializeAllTargets(); + llvm.InitializeAllTargetInfos(); + llvm.InitializeAllTargetMCs(); + llvm.InitializeAllAsmPrinters(); + llvm.InitializeAllAsmParsers(); + } + + pub fn getTriple(self: Target, allocator: *std.mem.Allocator) !std.Buffer { + var result = try std.Buffer.initSize(allocator, 0); + errdefer result.deinit(); + + // LLVM WebAssembly output support requires the target to be activated at + // build type with -DCMAKE_LLVM_EXPIERMENTAL_TARGETS_TO_BUILD=WebAssembly. + // + // LLVM determines the output format based on the environment suffix, + // defaulting to an object based on the architecture. The default format in + // LLVM 6 sets the wasm arch output incorrectly to ELF. We need to + // explicitly set this ourself in order for it to work. + // + // This is fixed in LLVM 7 and you will be able to get wasm output by + // using the target triple `wasm32-unknown-unknown-unknown`. + const env_name = if (self.isWasm()) "wasm" else @tagName(self.getEnviron()); + + var out = &std.io.BufferOutStream.init(&result).stream; + try out.print("{}-unknown-{}-{}", @tagName(self.getArch()), @tagName(self.getOs()), env_name); + + return result; + } + + pub fn llvmTargetFromTriple(triple: std.Buffer) !llvm.TargetRef { + var result: llvm.TargetRef = undefined; + var err_msg: [*]u8 = undefined; + if (llvm.GetTargetFromTriple(triple.ptr(), &result, &err_msg) != 0) { + std.debug.warn("triple: {s} error: {s}\n", triple.ptr(), err_msg); + return error.UnsupportedTarget; + } + return result; + } +}; diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 3edb267ca9..45e5362124 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -46,7 +46,7 @@ pub const TestContext = struct { try self.loop.initMultiThreaded(allocator); errdefer self.loop.deinit(); - self.event_loop_local = EventLoopLocal.init(&self.loop); + self.event_loop_local = try EventLoopLocal.init(&self.loop); errdefer self.event_loop_local.deinit(); self.group = std.event.Group(error!void).init(&self.loop); @@ -107,6 +107,7 @@ pub const TestContext = struct { Target.Native, Compilation.Kind.Obj, builtin.Mode.Debug, + true, // is_static self.zig_lib_dir, self.zig_cache_dir, ); diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index 670547cce2..bb1fb9bb01 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -160,7 +160,7 @@ pub const Type = struct { decls: *Scope.Decls, pub fn destroy(self: *Struct, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn getLlvmType(self: *Struct, ofile: *ObjectFile) llvm.TypeRef { @@ -180,7 +180,7 @@ pub const Type = struct { }; pub fn create(comp: *Compilation, return_type: *Type, params: []Param, is_var_args: bool) !*Fn { - const result = try comp.a().create(Fn{ + const result = try comp.gpa().create(Fn{ .base = Type{ .base = Value{ .id = Value.Id.Type, @@ -193,7 +193,7 @@ pub const Type = struct { .params = params, .is_var_args = is_var_args, }); - errdefer comp.a().destroy(result); + errdefer comp.gpa().destroy(result); result.return_type.base.ref(); for (result.params) |param| { @@ -207,7 +207,7 @@ pub const Type = struct { for (self.params) |param| { param.typeof.base.deref(comp); } - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn getLlvmType(self: *Fn, ofile: *ObjectFile) !llvm.TypeRef { @@ -215,8 +215,8 @@ pub const Type = struct { Type.Id.Void => llvm.VoidTypeInContext(ofile.context) orelse return error.OutOfMemory, else => try self.return_type.getLlvmType(ofile), }; - const llvm_param_types = try ofile.a().alloc(llvm.TypeRef, self.params.len); - defer ofile.a().free(llvm_param_types); + const llvm_param_types = try ofile.gpa().alloc(llvm.TypeRef, self.params.len); + defer ofile.gpa().free(llvm_param_types); for (llvm_param_types) |*llvm_param_type, i| { llvm_param_type.* = try self.params[i].typeof.getLlvmType(ofile); } @@ -241,7 +241,7 @@ pub const Type = struct { } pub fn destroy(self: *MetaType, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -255,7 +255,7 @@ pub const Type = struct { } pub fn destroy(self: *Void, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -269,7 +269,7 @@ pub const Type = struct { } pub fn destroy(self: *Bool, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn getLlvmType(self: *Bool, ofile: *ObjectFile) llvm.TypeRef { @@ -287,7 +287,7 @@ pub const Type = struct { } pub fn destroy(self: *NoReturn, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -295,7 +295,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Int, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn getLlvmType(self: *Int, ofile: *ObjectFile) llvm.TypeRef { @@ -307,7 +307,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Float, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn getLlvmType(self: *Float, ofile: *ObjectFile) llvm.TypeRef { @@ -332,7 +332,7 @@ pub const Type = struct { pub const Size = builtin.TypeInfo.Pointer.Size; pub fn destroy(self: *Pointer, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn get( @@ -355,7 +355,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Array, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn getLlvmType(self: *Array, ofile: *ObjectFile) llvm.TypeRef { @@ -367,7 +367,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *ComptimeFloat, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -375,7 +375,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *ComptimeInt, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -383,7 +383,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Undefined, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -391,7 +391,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Null, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -399,7 +399,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Optional, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn getLlvmType(self: *Optional, ofile: *ObjectFile) llvm.TypeRef { @@ -411,7 +411,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *ErrorUnion, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn getLlvmType(self: *ErrorUnion, ofile: *ObjectFile) llvm.TypeRef { @@ -423,7 +423,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *ErrorSet, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn getLlvmType(self: *ErrorSet, ofile: *ObjectFile) llvm.TypeRef { @@ -435,7 +435,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Enum, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn getLlvmType(self: *Enum, ofile: *ObjectFile) llvm.TypeRef { @@ -447,7 +447,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Union, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn getLlvmType(self: *Union, ofile: *ObjectFile) llvm.TypeRef { @@ -459,7 +459,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Namespace, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -467,7 +467,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Block, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -475,7 +475,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *BoundFn, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn getLlvmType(self: *BoundFn, ofile: *ObjectFile) llvm.TypeRef { @@ -487,7 +487,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *ArgTuple, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -495,7 +495,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Opaque, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn getLlvmType(self: *Opaque, ofile: *ObjectFile) llvm.TypeRef { @@ -507,7 +507,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Promise, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn getLlvmType(self: *Promise, ofile: *ObjectFile) llvm.TypeRef { diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index e3b91d2807..be19c6bccf 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -4,6 +4,7 @@ const Scope = @import("scope.zig").Scope; const Compilation = @import("compilation.zig").Compilation; const ObjectFile = @import("codegen.zig").ObjectFile; const llvm = @import("llvm.zig"); +const Buffer = std.Buffer; /// Values are ref-counted, heap-allocated, and copy-on-write /// If there is only 1 ref then write need not copy @@ -68,7 +69,7 @@ pub const Value = struct { /// The main external name that is used in the .o file. /// TODO https://github.com/ziglang/zig/issues/265 - symbol_name: std.Buffer, + symbol_name: Buffer, /// parent should be the top level decls or container decls fndef_scope: *Scope.FnDef, @@ -79,10 +80,22 @@ pub const Value = struct { /// parent is child_scope block_scope: *Scope.Block, + /// Path to the object file that contains this function + containing_object: Buffer, + + link_set_node: *std.LinkedList(?*Value.Fn).Node, + /// Creates a Fn value with 1 ref /// Takes ownership of symbol_name - pub fn create(comp: *Compilation, fn_type: *Type.Fn, fndef_scope: *Scope.FnDef, symbol_name: std.Buffer) !*Fn { - const self = try comp.a().create(Fn{ + pub fn create(comp: *Compilation, fn_type: *Type.Fn, fndef_scope: *Scope.FnDef, symbol_name: Buffer) !*Fn { + const link_set_node = try comp.gpa().create(Compilation.FnLinkSet.Node{ + .data = null, + .next = undefined, + .prev = undefined, + }); + errdefer comp.gpa().destroy(link_set_node); + + const self = try comp.gpa().create(Fn{ .base = Value{ .id = Value.Id.Fn, .typeof = &fn_type.base, @@ -92,6 +105,8 @@ pub const Value = struct { .child_scope = &fndef_scope.base, .block_scope = undefined, .symbol_name = symbol_name, + .containing_object = Buffer.initNull(comp.gpa()), + .link_set_node = link_set_node, }); fn_type.base.base.ref(); fndef_scope.fn_val = self; @@ -100,9 +115,19 @@ pub const Value = struct { } pub fn destroy(self: *Fn, comp: *Compilation) void { + // remove with a tombstone so that we do not have to grab a lock + if (self.link_set_node.data != null) { + // it's now the job of the link step to find this tombstone and + // deallocate it. + self.link_set_node.data = null; + } else { + comp.gpa().destroy(self.link_set_node); + } + + self.containing_object.deinit(); self.fndef_scope.base.deref(comp); self.symbol_name.deinit(); - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -115,7 +140,7 @@ pub const Value = struct { } pub fn destroy(self: *Void, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -134,7 +159,7 @@ pub const Value = struct { } pub fn destroy(self: *Bool, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn getLlvmConst(self: *Bool, ofile: *ObjectFile) ?llvm.ValueRef { @@ -156,7 +181,7 @@ pub const Value = struct { } pub fn destroy(self: *NoReturn, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -170,7 +195,7 @@ pub const Value = struct { }; pub fn destroy(self: *Ptr, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; }; diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index 24f2a8a343..a43d2d182c 100644 --- a/src/zig_llvm.cpp +++ b/src/zig_llvm.cpp @@ -440,6 +440,11 @@ ZigLLVMDIBuilder *ZigLLVMCreateDIBuilder(LLVMModuleRef module, bool allow_unreso return reinterpret_cast(di_builder); } +void ZigLLVMDisposeDIBuilder(ZigLLVMDIBuilder *dbuilder) { + DIBuilder *di_builder = reinterpret_cast(dbuilder); + delete di_builder; +} + void ZigLLVMSetCurrentDebugLocation(LLVMBuilderRef builder, int line, int column, ZigLLVMDIScope *scope) { unwrap(builder)->SetCurrentDebugLocation(DebugLoc::get( line, column, reinterpret_cast(scope))); diff --git a/src/zig_llvm.h b/src/zig_llvm.h index d34300b8ae..6f25df8674 100644 --- a/src/zig_llvm.h +++ b/src/zig_llvm.h @@ -39,7 +39,7 @@ struct ZigLLVMInsertionPoint; ZIG_EXTERN_C void ZigLLVMInitializeLoopStrengthReducePass(LLVMPassRegistryRef R); ZIG_EXTERN_C void ZigLLVMInitializeLowerIntrinsicsPass(LLVMPassRegistryRef R); -/// Caller must free memory. +/// Caller must free memory with LLVMDisposeMessage ZIG_EXTERN_C char *ZigLLVMGetHostCPUName(void); ZIG_EXTERN_C char *ZigLLVMGetNativeFeatures(void); @@ -139,6 +139,7 @@ ZIG_EXTERN_C unsigned ZigLLVMTag_DW_enumeration_type(void); ZIG_EXTERN_C unsigned ZigLLVMTag_DW_union_type(void); ZIG_EXTERN_C struct ZigLLVMDIBuilder *ZigLLVMCreateDIBuilder(LLVMModuleRef module, bool allow_unresolved); +ZIG_EXTERN_C void ZigLLVMDisposeDIBuilder(struct ZigLLVMDIBuilder *dbuilder); ZIG_EXTERN_C void ZigLLVMAddModuleDebugInfoFlag(LLVMModuleRef module); ZIG_EXTERN_C void ZigLLVMAddModuleCodeViewFlag(LLVMModuleRef module); diff --git a/std/atomic/int.zig b/std/atomic/int.zig index d51454c673..4103d52719 100644 --- a/std/atomic/int.zig +++ b/std/atomic/int.zig @@ -25,5 +25,9 @@ pub fn Int(comptime T: type) type { pub fn get(self: *Self) T { return @atomicLoad(T, &self.unprotected_value, AtomicOrder.SeqCst); } + + pub fn xchg(self: *Self, new_value: T) T { + return @atomicRmw(T, &self.unprotected_value, builtin.AtomicRmwOp.Xchg, new_value, AtomicOrder.SeqCst); + } }; } diff --git a/std/buffer.zig b/std/buffer.zig index aff7fa86ef..3b58002aba 100644 --- a/std/buffer.zig +++ b/std/buffer.zig @@ -54,6 +54,19 @@ pub const Buffer = struct { return result; } + pub fn allocPrint(allocator: *Allocator, comptime format: []const u8, args: ...) !Buffer { + const countSize = struct { + fn countSize(size: *usize, bytes: []const u8) (error{}!void) { + size.* += bytes.len; + } + }.countSize; + var size: usize = 0; + std.fmt.format(&size, error{}, countSize, format, args) catch |err| switch (err) {}; + var self = try Buffer.initSize(allocator, size); + assert((std.fmt.bufPrint(self.list.items, format, args) catch unreachable).len == size); + return self; + } + pub fn deinit(self: *Buffer) void { self.list.deinit(); } diff --git a/std/dwarf.zig b/std/dwarf.zig index 76ed122447..2cf8ed953e 100644 --- a/std/dwarf.zig +++ b/std/dwarf.zig @@ -639,3 +639,40 @@ pub const LNE_define_file = 0x03; pub const LNE_set_discriminator = 0x04; pub const LNE_lo_user = 0x80; pub const LNE_hi_user = 0xff; + +pub const LANG_C89 = 0x0001; +pub const LANG_C = 0x0002; +pub const LANG_Ada83 = 0x0003; +pub const LANG_C_plus_plus = 0x0004; +pub const LANG_Cobol74 = 0x0005; +pub const LANG_Cobol85 = 0x0006; +pub const LANG_Fortran77 = 0x0007; +pub const LANG_Fortran90 = 0x0008; +pub const LANG_Pascal83 = 0x0009; +pub const LANG_Modula2 = 0x000a; +pub const LANG_Java = 0x000b; +pub const LANG_C99 = 0x000c; +pub const LANG_Ada95 = 0x000d; +pub const LANG_Fortran95 = 0x000e; +pub const LANG_PLI = 0x000f; +pub const LANG_ObjC = 0x0010; +pub const LANG_ObjC_plus_plus = 0x0011; +pub const LANG_UPC = 0x0012; +pub const LANG_D = 0x0013; +pub const LANG_Python = 0x0014; +pub const LANG_Go = 0x0016; +pub const LANG_C_plus_plus_11 = 0x001a; +pub const LANG_Rust = 0x001c; +pub const LANG_C11 = 0x001d; +pub const LANG_C_plus_plus_14 = 0x0021; +pub const LANG_Fortran03 = 0x0022; +pub const LANG_Fortran08 = 0x0023; +pub const LANG_lo_user = 0x8000; +pub const LANG_hi_user = 0xffff; +pub const LANG_Mips_Assembler = 0x8001; +pub const LANG_Upc = 0x8765; +pub const LANG_HP_Bliss = 0x8003; +pub const LANG_HP_Basic91 = 0x8004; +pub const LANG_HP_Pascal91 = 0x8005; +pub const LANG_HP_IMacro = 0x8006; +pub const LANG_HP_Assembler = 0x8007; diff --git a/std/event/future.zig b/std/event/future.zig index 0f27b4131b..f5d14d1ca6 100644 --- a/std/event/future.zig +++ b/std/event/future.zig @@ -6,15 +6,20 @@ const AtomicOrder = builtin.AtomicOrder; const Lock = std.event.Lock; const Loop = std.event.Loop; -/// This is a value that starts out unavailable, until a value is put(). +/// This is a value that starts out unavailable, until resolve() is called /// While it is unavailable, coroutines suspend when they try to get() it, -/// and then are resumed when the value is put(). -/// At this point the value remains forever available, and another put() is not allowed. +/// and then are resumed when resolve() is called. +/// At this point the value remains forever available, and another resolve() is not allowed. pub fn Future(comptime T: type) type { return struct { lock: Lock, data: T, - available: u8, // TODO make this a bool + + /// TODO make this an enum + /// 0 - not started + /// 1 - started + /// 2 - finished + available: u8, const Self = this; const Queue = std.atomic.Queue(promise); @@ -31,7 +36,7 @@ pub fn Future(comptime T: type) type { /// available. /// Thread-safe. pub async fn get(self: *Self) *T { - if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 1) { + if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 2) { return &self.data; } const held = await (async self.lock.acquire() catch unreachable); @@ -43,18 +48,36 @@ pub fn Future(comptime T: type) type { /// Gets the data without waiting for it. If it's available, a pointer is /// returned. Otherwise, null is returned. pub fn getOrNull(self: *Self) ?*T { - if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 1) { + if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 2) { return &self.data; } else { return null; } } + /// If someone else has started working on the data, wait for them to complete + /// and return a pointer to the data. Otherwise, return null, and the caller + /// should start working on the data. + /// It's not required to call start() before resolve() but it can be useful since + /// this method is thread-safe. + pub async fn start(self: *Self) ?*T { + const state = @cmpxchgStrong(u8, &self.available, 0, 1, AtomicOrder.SeqCst, AtomicOrder.SeqCst) orelse return null; + switch (state) { + 1 => { + const held = await (async self.lock.acquire() catch unreachable); + held.release(); + return &self.data; + }, + 2 => return &self.data, + else => unreachable, + } + } + /// Make the data become available. May be called only once. /// Before calling this, modify the `data` property. pub fn resolve(self: *Self) void { - const prev = @atomicRmw(u8, &self.available, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); - assert(prev == 0); // put() called twice + const prev = @atomicRmw(u8, &self.available, AtomicRmwOp.Xchg, 2, AtomicOrder.SeqCst); + assert(prev == 0 or prev == 1); // resolve() called twice Lock.Held.release(Lock.Held{ .lock = &self.lock }); } }; diff --git a/std/index.zig b/std/index.zig index 3b523f519f..2f4cfb7553 100644 --- a/std/index.zig +++ b/std/index.zig @@ -36,6 +36,8 @@ pub const sort = @import("sort.zig"); pub const unicode = @import("unicode.zig"); pub const zig = @import("zig/index.zig"); +pub const lazyInit = @import("lazy_init.zig").lazyInit; + test "std" { // run tests from these _ = @import("atomic/index.zig"); @@ -71,4 +73,5 @@ test "std" { _ = @import("sort.zig"); _ = @import("unicode.zig"); _ = @import("zig/index.zig"); + _ = @import("lazy_init.zig"); } diff --git a/std/lazy_init.zig b/std/lazy_init.zig new file mode 100644 index 0000000000..c46c067810 --- /dev/null +++ b/std/lazy_init.zig @@ -0,0 +1,85 @@ +const std = @import("index.zig"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const AtomicRmwOp = builtin.AtomicRmwOp; +const AtomicOrder = builtin.AtomicOrder; + +/// Thread-safe initialization of global data. +/// TODO use a mutex instead of a spinlock +pub fn lazyInit(comptime T: type) LazyInit(T) { + return LazyInit(T){ + .data = undefined, + .state = 0, + }; +} + +fn LazyInit(comptime T: type) type { + return struct { + state: u8, // TODO make this an enum + data: Data, + + const Self = this; + + // TODO this isn't working for void, investigate and then remove this special case + const Data = if (@sizeOf(T) == 0) u8 else T; + const Ptr = if (T == void) void else *T; + + /// Returns a usable pointer to the initialized data, + /// or returns null, indicating that the caller should + /// perform the initialization and then call resolve(). + pub fn get(self: *Self) ?Ptr { + while (true) { + var state = @cmpxchgWeak(u8, &self.state, 0, 1, AtomicOrder.SeqCst, AtomicOrder.SeqCst) orelse return null; + switch (state) { + 0 => continue, + 1 => { + // TODO mutex instead of a spinlock + continue; + }, + 2 => { + if (@sizeOf(T) == 0) { + return T(undefined); + } else { + return &self.data; + } + }, + else => unreachable, + } + } + } + + pub fn resolve(self: *Self) void { + const prev = @atomicRmw(u8, &self.state, AtomicRmwOp.Xchg, 2, AtomicOrder.SeqCst); + assert(prev == 1); // resolve() called twice + } + }; +} + +var global_number = lazyInit(i32); + +test "std.lazyInit" { + if (global_number.get()) |_| @panic("bad") else { + global_number.data = 1234; + global_number.resolve(); + } + if (global_number.get()) |x| { + assert(x.* == 1234); + } else { + @panic("bad"); + } + if (global_number.get()) |x| { + assert(x.* == 1234); + } else { + @panic("bad"); + } +} + +var global_void = lazyInit(void); + +test "std.lazyInit(void)" { + if (global_void.get()) |_| @panic("bad") else { + global_void.resolve(); + } + assert(global_void.get() != null); + assert(global_void.get() != null); +} -- 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 From 2ea08561cf69dabc99722ffc24cb0e4327605506 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 24 Jul 2018 14:20:49 -0400 Subject: self-hosted: function types use table lookup --- src-self-hosted/codegen.zig | 3 +- src-self-hosted/compilation.zig | 69 +++++++- src-self-hosted/ir.zig | 8 +- src-self-hosted/type.zig | 338 +++++++++++++++++++++++++++++++++------- src/analyze.cpp | 8 +- 5 files changed, 356 insertions(+), 70 deletions(-) (limited to 'src-self-hosted/codegen.zig') diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index ad3dce061e..88293c845e 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -168,6 +168,7 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) //} const fn_type = fn_val.base.typ.cast(Type.Fn).?; + const fn_type_normal = &fn_type.key.data.Normal; try addLLVMFnAttr(ofile, llvm_fn, "nounwind"); //add_uwtable_attr(g, fn_table_entry->llvm_value); @@ -209,7 +210,7 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) // addLLVMArgAttr(fn_table_entry->llvm_value, (unsigned)err_ret_trace_arg_index, "nonnull"); //} - const cur_ret_ptr = if (fn_type.return_type.handleIsPtr()) llvm.GetParam(llvm_fn, 0) else null; + const cur_ret_ptr = if (fn_type_normal.return_type.handleIsPtr()) llvm.GetParam(llvm_fn, 0) else null; // build all basic blocks for (code.basic_block_list.toSlice()) |bb| { diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig index 093aab21da..8d41e2439b 100644 --- a/src-self-hosted/compilation.zig +++ b/src-self-hosted/compilation.zig @@ -220,12 +220,14 @@ pub const Compilation = struct { int_type_table: event.Locked(IntTypeTable), array_type_table: event.Locked(ArrayTypeTable), ptr_type_table: event.Locked(PtrTypeTable), + fn_type_table: event.Locked(FnTypeTable), 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 FnTypeTable = std.HashMap(*const Type.Fn.Key, *Type.Fn, Type.Fn.Key.hash, Type.Fn.Key.eql); const TypeTable = std.HashMap([]const u8, *Type, mem.hash_slice_u8, mem.eql_slice_u8); const CompileErrList = std.ArrayList(*Msg); @@ -384,6 +386,7 @@ pub const Compilation = struct { .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)), + .fn_type_table = event.Locked(FnTypeTable).init(loop, FnTypeTable.init(loop.allocator)), .c_int_types = undefined, .meta_type = undefined, @@ -414,6 +417,7 @@ pub const Compilation = struct { comp.int_type_table.private_data.deinit(); comp.array_type_table.private_data.deinit(); comp.ptr_type_table.private_data.deinit(); + comp.fn_type_table.private_data.deinit(); comp.arena_allocator.deinit(); comp.loop.allocator.destroy(comp); } @@ -1160,10 +1164,47 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { fn_decl.value = Decl.Fn.Val{ .Fn = fn_val }; symbol_name_consumed = true; + // Define local parameter variables + //for (size_t i = 0; i < fn_type_id->param_count; i += 1) { + // FnTypeParamInfo *param_info = &fn_type_id->param_info[i]; + // AstNode *param_decl_node = get_param_decl_node(fn_table_entry, i); + // Buf *param_name; + // bool is_var_args = param_decl_node && param_decl_node->data.param_decl.is_var_args; + // if (param_decl_node && !is_var_args) { + // param_name = param_decl_node->data.param_decl.name; + // } else { + // param_name = buf_sprintf("arg%" ZIG_PRI_usize "", i); + // } + // if (param_name == nullptr) { + // continue; + // } + + // TypeTableEntry *param_type = param_info->type; + // bool is_noalias = param_info->is_noalias; + + // if (is_noalias && get_codegen_ptr_type(param_type) == nullptr) { + // add_node_error(g, param_decl_node, buf_sprintf("noalias on non-pointer parameter")); + // } + + // VariableTableEntry *var = add_variable(g, param_decl_node, fn_table_entry->child_scope, + // param_name, true, create_const_runtime(param_type), nullptr); + // var->src_arg_index = i; + // fn_table_entry->child_scope = var->child_scope; + // var->shadowable = var->shadowable || is_var_args; + + // if (type_has_bits(param_type)) { + // fn_table_entry->variable_list.append(var); + // } + + // if (fn_type->data.fn.gen_param_info) { + // var->gen_arg_index = fn_type->data.fn.gen_param_info[i].gen_index; + // } + //} + const analyzed_code = try await (async comp.genAndAnalyzeCode( &fndef_scope.base, body_node, - fn_type.return_type, + fn_type.key.data.Normal.return_type, ) catch unreachable); errdefer analyzed_code.destroy(comp.gpa()); @@ -1199,14 +1240,13 @@ async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.Fn var params = ArrayList(Type.Fn.Param).init(comp.gpa()); var params_consumed = false; - defer if (params_consumed) { + 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| { @@ -1219,8 +1259,29 @@ async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.Fn }); } } - const fn_type = try Type.Fn.create(comp, return_type, params.toOwnedSlice(), is_var_args); + + const key = Type.Fn.Key{ + .alignment = null, + .data = Type.Fn.Key.Data{ + .Normal = Type.Fn.Normal{ + .return_type = return_type, + .params = params.toOwnedSlice(), + .is_var_args = false, // TODO + .cc = Type.Fn.CallingConvention.Auto, // TODO + }, + }, + }; params_consumed = true; + var key_consumed = false; + defer if (!key_consumed) { + for (key.data.Normal.params) |param| { + param.typ.base.deref(comp); + } + comp.gpa().free(key.data.Normal.params); + }; + + const fn_type = try await (async Type.Fn.get(comp, key) catch unreachable); + key_consumed = true; errdefer fn_type.base.base.deref(comp); return fn_type; diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index c34f06753d..45355bbf2c 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -281,11 +281,13 @@ pub const Inst = struct { return error.SemanticAnalysisFailed; }; - if (fn_type.params.len != self.params.args.len) { + const fn_type_param_count = fn_type.paramCount(); + + if (fn_type_param_count != self.params.args.len) { try ira.addCompileError( self.base.span, "expected {} arguments, found {}", - fn_type.params.len, + fn_type_param_count, self.params.args.len, ); return error.SemanticAnalysisFailed; @@ -299,7 +301,7 @@ pub const Inst = struct { .fn_ref = fn_ref, .args = args, }); - new_inst.val = IrVal{ .KnownType = fn_type.return_type }; + new_inst.val = IrVal{ .KnownType = fn_type.key.data.Normal.return_type }; return new_inst; } diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index 217c1d50a7..3b57260447 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -221,57 +221,267 @@ pub const Type = struct { pub const Fn = struct { base: Type, - return_type: *Type, - params: []Param, - is_var_args: bool, + key: Key, + garbage_node: std.atomic.Stack(*Fn).Node, + + pub const Key = struct { + data: Data, + alignment: ?u32, + + pub const Data = union(enum) { + Generic: Generic, + Normal: Normal, + }; + + pub fn hash(self: *const Key) u32 { + var result: u32 = 0; + result +%= hashAny(self.alignment, 0); + switch (self.data) { + Data.Generic => |generic| { + result +%= hashAny(generic.param_count, 1); + switch (generic.cc) { + CallingConvention.Async => |allocator_type| result +%= hashAny(allocator_type, 2), + else => result +%= hashAny(CallingConvention(generic.cc), 3), + } + }, + Data.Normal => |normal| { + result +%= hashAny(normal.return_type, 4); + result +%= hashAny(normal.is_var_args, 5); + result +%= hashAny(normal.cc, 6); + for (normal.params) |param| { + result +%= hashAny(param.is_noalias, 7); + result +%= hashAny(param.typ, 8); + } + }, + } + return result; + } + + pub fn eql(self: *const Key, other: *const Key) bool { + if ((self.alignment == null) != (other.alignment == null)) return false; + if (self.alignment) |self_align| { + if (self_align != other.alignment.?) return false; + } + if (@TagType(Data)(self.data) != @TagType(Data)(other.data)) return false; + switch (self.data) { + Data.Generic => |*self_generic| { + const other_generic = &other.data.Generic; + if (self_generic.param_count != other_generic.param_count) return false; + if (CallingConvention(self_generic.cc) != CallingConvention(other_generic.cc)) return false; + switch (self_generic.cc) { + CallingConvention.Async => |self_allocator_type| { + const other_allocator_type = other_generic.cc.Async; + if (self_allocator_type != other_allocator_type) return false; + }, + else => {}, + } + }, + Data.Normal => |*self_normal| { + const other_normal = &other.data.Normal; + if (self_normal.cc != other_normal.cc) return false; + if (self_normal.is_var_args != other_normal.is_var_args) return false; + if (self_normal.return_type != other_normal.return_type) return false; + for (self_normal.params) |*self_param, i| { + const other_param = &other_normal.params[i]; + if (self_param.is_noalias != other_param.is_noalias) return false; + if (self_param.typ != other_param.typ) return false; + } + }, + } + return true; + } + + pub fn deref(key: Key, comp: *Compilation) void { + switch (key.data) { + Key.Data.Generic => |generic| { + switch (generic.cc) { + CallingConvention.Async => |allocator_type| allocator_type.base.deref(comp), + else => {}, + } + }, + Key.Data.Normal => |normal| { + normal.return_type.base.deref(comp); + for (normal.params) |param| { + param.typ.base.deref(comp); + } + }, + } + } + + pub fn ref(key: Key) void { + switch (key.data) { + Key.Data.Generic => |generic| { + switch (generic.cc) { + CallingConvention.Async => |allocator_type| allocator_type.base.ref(), + else => {}, + } + }, + Key.Data.Normal => |normal| { + normal.return_type.base.ref(); + for (normal.params) |param| { + param.typ.base.ref(); + } + }, + } + } + }; + + pub const Normal = struct { + params: []Param, + return_type: *Type, + is_var_args: bool, + cc: CallingConvention, + }; + + pub const Generic = struct { + param_count: usize, + cc: CC, + + pub const CC = union(CallingConvention) { + Auto, + C, + Cold, + Naked, + Stdcall, + Async: *Type, // allocator type + }; + }; + + pub const CallingConvention = enum { + Auto, + C, + Cold, + Naked, + Stdcall, + Async, + }; pub const Param = struct { is_noalias: bool, typ: *Type, }; - pub fn create(comp: *Compilation, return_type: *Type, params: []Param, is_var_args: bool) !*Fn { - const result = try comp.gpa().create(Fn{ + fn ccFnTypeStr(cc: CallingConvention) []const u8 { + return switch (cc) { + CallingConvention.Auto => "", + CallingConvention.C => "extern ", + CallingConvention.Cold => "coldcc ", + CallingConvention.Naked => "nakedcc ", + CallingConvention.Stdcall => "stdcallcc ", + CallingConvention.Async => unreachable, + }; + } + + pub fn paramCount(self: *Fn) usize { + return switch (self.key.data) { + Key.Data.Generic => |generic| generic.param_count, + Key.Data.Normal => |normal| normal.params.len, + }; + } + + /// takes ownership of key.Normal.params on success + pub async fn get(comp: *Compilation, key: Key) !*Fn { + { + const held = await (async comp.fn_type_table.acquire() catch unreachable); + defer held.release(); + + if (held.value.get(&key)) |entry| { + entry.value.base.base.ref(); + return entry.value; + } + } + + key.ref(); + errdefer key.deref(comp); + + const self = try comp.gpa().create(Fn{ .base = undefined, - .return_type = return_type, - .params = params, - .is_var_args = is_var_args, + .key = key, + .garbage_node = undefined, }); - errdefer comp.gpa().destroy(result); + errdefer comp.gpa().destroy(self); - result.base.init(comp, Id.Fn, "TODO fn type name"); + var name_buf = try std.Buffer.initSize(comp.gpa(), 0); + defer name_buf.deinit(); + + const name_stream = &std.io.BufferOutStream.init(&name_buf).stream; + + switch (key.data) { + Key.Data.Generic => |generic| { + switch (generic.cc) { + CallingConvention.Async => |async_allocator_type| { + try name_stream.print("async<{}> ", async_allocator_type.name); + }, + else => { + const cc_str = ccFnTypeStr(generic.cc); + try name_stream.write(cc_str); + }, + } + try name_stream.write("fn("); + var param_i: usize = 0; + while (param_i < generic.param_count) : (param_i += 1) { + const arg = if (param_i == 0) "var" else ", var"; + try name_stream.write(arg); + } + try name_stream.write(")"); + if (key.alignment) |alignment| { + try name_stream.print(" align<{}>", alignment); + } + try name_stream.write(" var"); + }, + Key.Data.Normal => |normal| { + const cc_str = ccFnTypeStr(normal.cc); + try name_stream.print("{}fn(", cc_str); + for (normal.params) |param, i| { + if (i != 0) try name_stream.write(", "); + if (param.is_noalias) try name_stream.write("noalias "); + try name_stream.write(param.typ.name); + } + if (normal.is_var_args) { + if (normal.params.len != 0) try name_stream.write(", "); + try name_stream.write("..."); + } + try name_stream.write(")"); + if (key.alignment) |alignment| { + try name_stream.print(" align<{}>", alignment); + } + try name_stream.print(" {}", normal.return_type.name); + }, + } + + self.base.init(comp, Id.Fn, name_buf.toOwnedSlice()); - result.return_type.base.ref(); - for (result.params) |param| { - param.typ.base.ref(); + { + const held = await (async comp.fn_type_table.acquire() catch unreachable); + defer held.release(); + + _ = try held.value.put(&self.key, self); } - return result; + return self; } pub fn destroy(self: *Fn, comp: *Compilation) void { - self.return_type.base.deref(comp); - for (self.params) |param| { - param.typ.base.deref(comp); - } + self.key.deref(comp); comp.gpa().destroy(self); } pub fn getLlvmType(self: *Fn, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef { - const llvm_return_type = switch (self.return_type.id) { + const normal = &self.key.data.Normal; + const llvm_return_type = switch (normal.return_type.id) { Type.Id.Void => llvm.VoidTypeInContext(llvm_context) orelse return error.OutOfMemory, - else => try self.return_type.getLlvmType(allocator, llvm_context), + else => try normal.return_type.getLlvmType(allocator, llvm_context), }; - const llvm_param_types = try allocator.alloc(llvm.TypeRef, self.params.len); + const llvm_param_types = try allocator.alloc(llvm.TypeRef, normal.params.len); defer allocator.free(llvm_param_types); for (llvm_param_types) |*llvm_param_type, i| { - llvm_param_type.* = try self.params[i].typ.getLlvmType(allocator, llvm_context); + llvm_param_type.* = try normal.params[i].typ.getLlvmType(allocator, llvm_context); } return llvm.FunctionType( llvm_return_type, llvm_param_types.ptr, @intCast(c_uint, llvm_param_types.len), - @boolToInt(self.is_var_args), + @boolToInt(normal.is_var_args), ) orelse error.OutOfMemory; } }; @@ -347,8 +557,10 @@ pub const Type = struct { is_signed: bool, pub fn hash(self: *const Key) u32 { - const rands = [2]u32{ 0xa4ba6498, 0x75fc5af7 }; - return rands[@boolToInt(self.is_signed)] *% self.bit_count; + var result: u32 = 0; + result +%= hashAny(self.is_signed, 0); + result +%= hashAny(self.bit_count, 1); + return result; } pub fn eql(self: *const Key, other: *const Key) bool { @@ -443,15 +655,16 @@ pub const Type = struct { alignment: Align, pub fn hash(self: *const Key) u32 { - const align_hash = switch (self.alignment) { + var result: u32 = 0; + result +%= switch (self.alignment) { Align.Abi => 0xf201c090, - Align.Override => |x| x, + Align.Override => |x| hashAny(x, 0), }; - return hash_usize(@ptrToInt(self.child_type)) *% - hash_enum(self.mut) *% - hash_enum(self.vol) *% - hash_enum(self.size) *% - align_hash; + result +%= hashAny(self.child_type, 1); + result +%= hashAny(self.mut, 2); + result +%= hashAny(self.vol, 3); + result +%= hashAny(self.size, 4); + return result; } pub fn eql(self: *const Key, other: *const Key) bool { @@ -605,7 +818,10 @@ pub const Type = struct { len: usize, pub fn hash(self: *const Key) u32 { - return hash_usize(@ptrToInt(self.elem_type)) *% hash_usize(self.len); + var result: u32 = 0; + result +%= hashAny(self.elem_type, 0); + result +%= hashAny(self.len, 1); + return result; } pub fn eql(self: *const Key, other: *const Key) bool { @@ -818,27 +1034,37 @@ pub const Type = struct { }; }; -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)]; +fn hashAny(x: var, comptime seed: u64) u32 { + switch (@typeInfo(@typeOf(x))) { + builtin.TypeId.Int => |info| { + comptime var rng = comptime std.rand.DefaultPrng.init(seed); + const unsigned_x = @bitCast(@IntType(false, info.bits), x); + if (info.bits <= 32) { + return u32(unsigned_x) *% comptime rng.random.scalar(u32); + } else { + return @truncate(u32, unsigned_x *% comptime rng.random.scalar(@typeOf(unsigned_x))); + } + }, + builtin.TypeId.Pointer => |info| { + switch (info.size) { + builtin.TypeInfo.Pointer.Size.One => return hashAny(@ptrToInt(x), seed), + builtin.TypeInfo.Pointer.Size.Many => @compileError("implement hash function"), + builtin.TypeInfo.Pointer.Size.Slice => @compileError("implement hash function"), + } + }, + builtin.TypeId.Enum => return hashAny(@enumToInt(x), seed), + builtin.TypeId.Bool => { + comptime var rng = comptime std.rand.DefaultPrng.init(seed); + const vals = comptime [2]u32{ rng.random.scalar(u32), rng.random.scalar(u32) }; + return vals[@boolToInt(x)]; + }, + builtin.TypeId.Optional => { + if (x) |non_opt| { + return hashAny(non_opt, seed); + } else { + return hashAny(u32(1), seed); + } + }, + else => @compileError("implement hash function for " ++ @typeName(@typeOf(x))), + } } diff --git a/src/analyze.cpp b/src/analyze.cpp index f399ab8305..a4bfff78c3 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -3941,7 +3941,7 @@ AstNode *get_param_decl_node(FnTableEntry *fn_entry, size_t index) { return nullptr; } -static void define_local_param_variables(CodeGen *g, FnTableEntry *fn_table_entry, VariableTableEntry **arg_vars) { +static void define_local_param_variables(CodeGen *g, FnTableEntry *fn_table_entry) { TypeTableEntry *fn_type = fn_table_entry->type_entry; assert(!fn_type->data.fn.is_generic); FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id; @@ -3979,10 +3979,6 @@ static void define_local_param_variables(CodeGen *g, FnTableEntry *fn_table_entr if (fn_type->data.fn.gen_param_info) { var->gen_arg_index = fn_type->data.fn.gen_param_info[i].gen_index; } - - if (arg_vars) { - arg_vars[i] = var; - } } } @@ -4082,7 +4078,7 @@ static void analyze_fn_body(CodeGen *g, FnTableEntry *fn_table_entry) { if (!fn_table_entry->child_scope) fn_table_entry->child_scope = &fn_table_entry->fndef_scope->base; - define_local_param_variables(g, fn_table_entry, nullptr); + define_local_param_variables(g, fn_table_entry); TypeTableEntry *fn_type = fn_table_entry->type_entry; assert(!fn_type->data.fn.is_generic); -- cgit v1.2.3 From adefd1a52b812813dd3e3590d398f927ffc5b9af Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 24 Jul 2018 20:24:05 -0400 Subject: self-hosted: function calling another function --- src-self-hosted/codegen.zig | 160 +++++++++++++++++++++++++++++++- src-self-hosted/compilation.zig | 72 +++++++------- src-self-hosted/ir.zig | 197 ++++++++++++++++++++++++++++++++++----- src-self-hosted/llvm.zig | 15 ++- src-self-hosted/scope.zig | 201 +++++++++++++++++++++++++--------------- src-self-hosted/type.zig | 105 +++++++++++++-------- src-self-hosted/value.zig | 22 ++++- 7 files changed, 597 insertions(+), 175 deletions(-) (limited to 'src-self-hosted/codegen.zig') diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 88293c845e..5ca01ca7e7 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -6,6 +6,7 @@ const c = @import("c.zig"); const ir = @import("ir.zig"); const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; +const Scope = @import("scope.zig").Scope; const event = std.event; const assert = std.debug.assert; const DW = std.dwarf; @@ -156,7 +157,7 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) llvm_fn_type, ) orelse return error.OutOfMemory; - const want_fn_safety = fn_val.block_scope.safety.get(ofile.comp); + const want_fn_safety = fn_val.block_scope.?.safety.get(ofile.comp); if (want_fn_safety and ofile.comp.haveLibC()) { try addLLVMFnAttr(ofile, llvm_fn, "sspstrong"); try addLLVMFnAttrStr(ofile, llvm_fn, "stack-protector-buffer-size", "4"); @@ -227,9 +228,86 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) // TODO set up error return tracing // TODO allocate temporary stack values - // TODO create debug variable declarations for variables and allocate all local variables + + const var_list = fn_type.non_key.Normal.variable_list.toSliceConst(); + // create debug variable declarations for variables and allocate all local variables + for (var_list) |var_scope, i| { + const var_type = switch (var_scope.data) { + Scope.Var.Data.Const => unreachable, + Scope.Var.Data.Param => |param| param.typ, + }; + // if (!type_has_bits(var->value->type)) { + // continue; + // } + // if (ir_get_var_is_comptime(var)) + // continue; + // if (type_requires_comptime(var->value->type)) + // continue; + // if (var->src_arg_index == SIZE_MAX) { + // var->value_ref = build_alloca(g, var->value->type, buf_ptr(&var->name), var->align_bytes); + + // var->di_loc_var = ZigLLVMCreateAutoVariable(g->dbuilder, get_di_scope(g, var->parent_scope), + // buf_ptr(&var->name), import->di_file, (unsigned)(var->decl_node->line + 1), + // var->value->type->di_type, !g->strip_debug_symbols, 0); + + // } else { + // it's a parameter + // assert(var->gen_arg_index != SIZE_MAX); + // TypeTableEntry *gen_type; + // FnGenParamInfo *gen_info = &fn_table_entry->type_entry->data.fn.gen_param_info[var->src_arg_index]; + + if (var_type.handleIsPtr()) { + // if (gen_info->is_byval) { + // gen_type = var->value->type; + // } else { + // gen_type = gen_info->type; + // } + var_scope.data.Param.llvm_value = llvm.GetParam(llvm_fn, @intCast(c_uint, i)); + } else { + // gen_type = var->value->type; + var_scope.data.Param.llvm_value = try renderAlloca(ofile, var_type, var_scope.name, Type.Pointer.Align.Abi); + } + // if (var->decl_node) { + // var->di_loc_var = ZigLLVMCreateParameterVariable(g->dbuilder, get_di_scope(g, var->parent_scope), + // buf_ptr(&var->name), import->di_file, + // (unsigned)(var->decl_node->line + 1), + // gen_type->di_type, !g->strip_debug_symbols, 0, (unsigned)(var->gen_arg_index + 1)); + // } + + // } + } + // TODO finishing error return trace setup. we have to do this after all the allocas. - // TODO create debug variable declarations for parameters + + // create debug variable declarations for parameters + // rely on the first variables in the variable_list being parameters. + //size_t next_var_i = 0; + for (fn_type.key.data.Normal.params) |param, i| { + //FnGenParamInfo *info = &fn_table_entry->type_entry->data.fn.gen_param_info[param_i]; + //if (info->gen_index == SIZE_MAX) + // continue; + const scope_var = var_list[i]; + //assert(variable->src_arg_index != SIZE_MAX); + //next_var_i += 1; + //assert(variable); + //assert(variable->value_ref); + + if (!param.typ.handleIsPtr()) { + //clear_debug_source_node(g); + const llvm_param = llvm.GetParam(llvm_fn, @intCast(c_uint, i)); + _ = renderStoreUntyped( + ofile, + llvm_param, + scope_var.data.Param.llvm_value, + Type.Pointer.Align.Abi, + Type.Pointer.Vol.Non, + ); + } + + //if (variable->decl_node) { + // gen_var_debug_decl(g, variable); + //} + } for (code.basic_block_list.toSlice()) |current_block| { llvm.PositionBuilderAtEnd(ofile.builder, current_block.llvm_block); @@ -294,3 +372,79 @@ fn addLLVMFnAttrStr(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []cons fn addLLVMFnAttrInt(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []const u8, attr_val: u64) !void { return addLLVMAttrInt(ofile, fn_val, @maxValue(llvm.AttributeIndex), attr_name, attr_val); } + +fn renderLoadUntyped( + ofile: *ObjectFile, + ptr: llvm.ValueRef, + alignment: Type.Pointer.Align, + vol: Type.Pointer.Vol, + name: [*]const u8, +) !llvm.ValueRef { + const result = llvm.BuildLoad(ofile.builder, ptr, name) orelse return error.OutOfMemory; + switch (vol) { + Type.Pointer.Vol.Non => {}, + Type.Pointer.Vol.Volatile => llvm.SetVolatile(result, 1), + } + llvm.SetAlignment(result, resolveAlign(ofile, alignment, llvm.GetElementType(llvm.TypeOf(ptr)))); + return result; +} + +fn renderLoad(ofile: *ObjectFile, ptr: llvm.ValueRef, ptr_type: *Type.Pointer, name: [*]const u8) !llvm.ValueRef { + return renderLoadUntyped(ofile, ptr, ptr_type.key.alignment, ptr_type.key.vol, name); +} + +pub fn getHandleValue(ofile: *ObjectFile, ptr: llvm.ValueRef, ptr_type: *Type.Pointer) !?llvm.ValueRef { + const child_type = ptr_type.key.child_type; + if (!child_type.hasBits()) { + return null; + } + if (child_type.handleIsPtr()) { + return ptr; + } + return try renderLoad(ofile, ptr, ptr_type, c""); +} + +pub fn renderStoreUntyped( + ofile: *ObjectFile, + value: llvm.ValueRef, + ptr: llvm.ValueRef, + alignment: Type.Pointer.Align, + vol: Type.Pointer.Vol, +) !llvm.ValueRef { + const result = llvm.BuildStore(ofile.builder, value, ptr) orelse return error.OutOfMemory; + switch (vol) { + Type.Pointer.Vol.Non => {}, + Type.Pointer.Vol.Volatile => llvm.SetVolatile(result, 1), + } + llvm.SetAlignment(result, resolveAlign(ofile, alignment, llvm.TypeOf(value))); + return result; +} + +pub fn renderStore( + ofile: *ObjectFile, + value: llvm.ValueRef, + ptr: llvm.ValueRef, + ptr_type: *Type.Pointer, +) !llvm.ValueRef { + return renderStoreUntyped(ofile, value, ptr, ptr_type.key.alignment, ptr_type.key.vol); +} + +pub fn renderAlloca( + ofile: *ObjectFile, + var_type: *Type, + name: []const u8, + alignment: Type.Pointer.Align, +) !llvm.ValueRef { + const llvm_var_type = try var_type.getLlvmType(ofile.arena, ofile.context); + const name_with_null = try std.cstr.addNullByte(ofile.arena, name); + const result = llvm.BuildAlloca(ofile.builder, llvm_var_type, name_with_null.ptr) orelse return error.OutOfMemory; + llvm.SetAlignment(result, resolveAlign(ofile, alignment, llvm_var_type)); + return result; +} + +pub fn resolveAlign(ofile: *ObjectFile, alignment: Type.Pointer.Align, llvm_type: llvm.TypeRef) u32 { + return switch (alignment) { + Type.Pointer.Align.Abi => return llvm.ABIAlignmentOfType(ofile.comp.target_data_ref, llvm_type), + Type.Pointer.Align.Override => |a| a, + }; +} diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig index 8d41e2439b..1564abfbd3 100644 --- a/src-self-hosted/compilation.zig +++ b/src-self-hosted/compilation.zig @@ -1165,49 +1165,47 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { symbol_name_consumed = true; // Define local parameter variables - //for (size_t i = 0; i < fn_type_id->param_count; i += 1) { - // FnTypeParamInfo *param_info = &fn_type_id->param_info[i]; - // AstNode *param_decl_node = get_param_decl_node(fn_table_entry, i); - // Buf *param_name; - // bool is_var_args = param_decl_node && param_decl_node->data.param_decl.is_var_args; - // if (param_decl_node && !is_var_args) { - // param_name = param_decl_node->data.param_decl.name; - // } else { - // param_name = buf_sprintf("arg%" ZIG_PRI_usize "", i); - // } - // if (param_name == nullptr) { - // continue; - // } - - // TypeTableEntry *param_type = param_info->type; - // bool is_noalias = param_info->is_noalias; - - // if (is_noalias && get_codegen_ptr_type(param_type) == nullptr) { - // add_node_error(g, param_decl_node, buf_sprintf("noalias on non-pointer parameter")); - // } - - // VariableTableEntry *var = add_variable(g, param_decl_node, fn_table_entry->child_scope, - // param_name, true, create_const_runtime(param_type), nullptr); - // var->src_arg_index = i; - // fn_table_entry->child_scope = var->child_scope; - // var->shadowable = var->shadowable || is_var_args; - - // if (type_has_bits(param_type)) { - // fn_table_entry->variable_list.append(var); - // } - - // if (fn_type->data.fn.gen_param_info) { - // var->gen_arg_index = fn_type->data.fn.gen_param_info[i].gen_index; - // } - //} + const root_scope = fn_decl.base.findRootScope(); + for (fn_type.key.data.Normal.params) |param, i| { + //AstNode *param_decl_node = get_param_decl_node(fn_table_entry, i); + const param_decl = @fieldParentPtr(ast.Node.ParamDecl, "base", fn_decl.fn_proto.params.at(i).*); + const name_token = param_decl.name_token orelse { + try comp.addCompileError(root_scope, Span{ + .first = param_decl.firstToken(), + .last = param_decl.type_node.firstToken(), + }, "missing parameter name"); + return error.SemanticAnalysisFailed; + }; + const param_name = root_scope.tree.tokenSlice(name_token); + + // if (is_noalias && get_codegen_ptr_type(param_type) == nullptr) { + // add_node_error(g, param_decl_node, buf_sprintf("noalias on non-pointer parameter")); + // } + + // TODO check for shadowing + + const var_scope = try Scope.Var.createParam( + comp, + fn_val.child_scope, + param_name, + ¶m_decl.base, + i, + param.typ, + ); + fn_val.child_scope = &var_scope.base; + + try fn_type.non_key.Normal.variable_list.append(var_scope); + } const analyzed_code = try await (async comp.genAndAnalyzeCode( - &fndef_scope.base, + fn_val.child_scope, body_node, fn_type.key.data.Normal.return_type, ) catch unreachable); errdefer analyzed_code.destroy(comp.gpa()); + assert(fn_val.block_scope != null); + // Kick off rendering to LLVM module, but it doesn't block the fn decl // analysis from being complete. try comp.prelink_group.call(codegen.renderToLlvm, comp, fn_val, analyzed_code); @@ -1263,7 +1261,7 @@ async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.Fn const key = Type.Fn.Key{ .alignment = null, .data = Type.Fn.Key.Data{ - .Normal = Type.Fn.Normal{ + .Normal = Type.Fn.Key.Normal{ .return_type = return_type, .params = params.toOwnedSlice(), .is_var_args = false, // TODO diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 45355bbf2c..619cd4f330 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -10,8 +10,10 @@ const assert = std.debug.assert; const Token = std.zig.Token; const Span = @import("errmsg.zig").Span; const llvm = @import("llvm.zig"); -const ObjectFile = @import("codegen.zig").ObjectFile; +const codegen = @import("codegen.zig"); +const ObjectFile = codegen.ObjectFile; const Decl = @import("decl.zig").Decl; +const mem = std.mem; pub const LVal = enum { None, @@ -122,6 +124,8 @@ pub const Inst = struct { 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), + Id.VarPtr => return await (async @fieldParentPtr(VarPtr, "base", base).analyze(ira) catch unreachable), + Id.LoadPtr => return await (async @fieldParentPtr(LoadPtr, "base", base).analyze(ira) catch unreachable), } } @@ -130,6 +134,8 @@ pub const Inst = struct { 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.VarPtr => return @fieldParentPtr(VarPtr, "base", base).render(ofile, fn_val), + Id.LoadPtr => return @fieldParentPtr(LoadPtr, "base", base).render(ofile, fn_val), Id.DeclRef => unreachable, Id.PtrType => unreachable, Id.Ref => @panic("TODO"), @@ -248,6 +254,8 @@ pub const Inst = struct { Call, DeclRef, PtrType, + VarPtr, + LoadPtr, }; pub const Call = struct { @@ -491,6 +499,133 @@ pub const Inst = struct { } }; + pub const VarPtr = struct { + base: Inst, + params: Params, + + const Params = struct { + var_scope: *Scope.Var, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(inst: *const VarPtr) void { + std.debug.warn("{}", inst.params.var_scope.name); + } + + pub fn hasSideEffects(inst: *const VarPtr) bool { + return false; + } + + pub async fn analyze(self: *const VarPtr, ira: *Analyze) !*Inst { + switch (self.params.var_scope.data) { + Scope.Var.Data.Const => @panic("TODO"), + Scope.Var.Data.Param => |param| { + const new_inst = try ira.irb.build( + Inst.VarPtr, + self.base.scope, + self.base.span, + Inst.VarPtr.Params{ .var_scope = self.params.var_scope }, + ); + const ptr_type = try await (async Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{ + .child_type = param.typ, + .mut = Type.Pointer.Mut.Const, + .vol = Type.Pointer.Vol.Non, + .size = Type.Pointer.Size.One, + .alignment = Type.Pointer.Align.Abi, + }) catch unreachable); + new_inst.val = IrVal{ .KnownType = &ptr_type.base }; + return new_inst; + }, + } + } + + pub fn render(self: *VarPtr, ofile: *ObjectFile, fn_val: *Value.Fn) llvm.ValueRef { + switch (self.params.var_scope.data) { + Scope.Var.Data.Const => unreachable, // turned into Inst.Const in analyze pass + Scope.Var.Data.Param => |param| return param.llvm_value, + } + } + }; + + pub const LoadPtr = struct { + base: Inst, + params: Params, + + const Params = struct { + target: *Inst, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(inst: *const LoadPtr) void {} + + pub fn hasSideEffects(inst: *const LoadPtr) bool { + return false; + } + + pub async fn analyze(self: *const LoadPtr, ira: *Analyze) !*Inst { + const target = try self.params.target.getAsParam(); + const target_type = target.getKnownType(); + if (target_type.id != Type.Id.Pointer) { + try ira.addCompileError(self.base.span, "dereference of non pointer type '{}'", target_type.name); + return error.SemanticAnalysisFailed; + } + const ptr_type = @fieldParentPtr(Type.Pointer, "base", target_type); + // if (instr_is_comptime(ptr)) { + // if (ptr->value.data.x_ptr.mut == ConstPtrMutComptimeConst || + // ptr->value.data.x_ptr.mut == ConstPtrMutComptimeVar) + // { + // ConstExprValue *pointee = const_ptr_pointee(ira->codegen, &ptr->value); + // if (pointee->special != ConstValSpecialRuntime) { + // IrInstruction *result = ir_create_const(&ira->new_irb, source_instruction->scope, + // source_instruction->source_node, child_type); + // copy_const_val(&result->value, pointee, ptr->value.data.x_ptr.mut == ConstPtrMutComptimeConst); + // result->value.type = child_type; + // return result; + // } + // } + // } + const new_inst = try ira.irb.build( + Inst.LoadPtr, + self.base.scope, + self.base.span, + Inst.LoadPtr.Params{ .target = target }, + ); + new_inst.val = IrVal{ .KnownType = ptr_type.key.child_type }; + return new_inst; + } + + pub fn render(self: *LoadPtr, ofile: *ObjectFile, fn_val: *Value.Fn) !?llvm.ValueRef { + const child_type = self.base.getKnownType(); + if (!child_type.hasBits()) { + return null; + } + const ptr = self.params.target.llvm_value.?; + const ptr_type = self.params.target.getKnownType().cast(Type.Pointer).?; + + return try codegen.getHandleValue(ofile, ptr, ptr_type); + + //uint32_t unaligned_bit_count = ptr_type->data.pointer.unaligned_bit_count; + //if (unaligned_bit_count == 0) + // return get_handle_value(g, ptr, child_type, ptr_type); + + //bool big_endian = g->is_big_endian; + + //assert(!handle_is_ptr(child_type)); + //LLVMValueRef containing_int = gen_load(g, ptr, ptr_type, ""); + + //uint32_t bit_offset = ptr_type->data.pointer.bit_offset; + //uint32_t host_bit_count = LLVMGetIntTypeWidth(LLVMTypeOf(containing_int)); + //uint32_t shift_amt = big_endian ? host_bit_count - bit_offset - unaligned_bit_count : bit_offset; + + //LLVMValueRef shift_amt_val = LLVMConstInt(LLVMTypeOf(containing_int), shift_amt, false); + //LLVMValueRef shifted_value = LLVMBuildLShr(g->builder, containing_int, shift_amt_val, ""); + + //return LLVMBuildTrunc(g->builder, shifted_value, child_type->type_ref, ""); + } + }; + pub const PtrType = struct { base: Inst, params: Params, @@ -1160,6 +1295,7 @@ pub const Builder = struct { Scope.Id.Block, Scope.Id.Defer, Scope.Id.DeferExpr, + Scope.Id.Var, => scope = scope.parent.?, } } @@ -1261,8 +1397,8 @@ pub const Builder = struct { var child_scope = outer_block_scope; if (parent_scope.findFnDef()) |fndef_scope| { - if (fndef_scope.fn_val.child_scope == parent_scope) { - fndef_scope.fn_val.block_scope = block_scope; + if (fndef_scope.fn_val.?.block_scope == null) { + fndef_scope.fn_val.?.block_scope = block_scope; } } @@ -1492,20 +1628,23 @@ pub const Builder = struct { error.OutOfMemory => return error.OutOfMemory, } - //VariableTableEntry *var = find_variable(irb->codegen, scope, variable_name); - //if (var) { - // IrInstruction *var_ptr = ir_build_var_ptr(irb, scope, node, var); - // if (lval == LValPtr) - // return var_ptr; - // else - // return ir_build_load_ptr(irb, scope, node, var_ptr); - //} - - 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, - }); + switch (await (async irb.findIdent(scope, name) catch unreachable)) { + Ident.Decl => |decl| { + return irb.build(Inst.DeclRef, scope, src_span, Inst.DeclRef.Params{ + .decl = decl, + .lval = lval, + }); + }, + Ident.VarScope => |var_scope| { + const var_ptr = try irb.build(Inst.VarPtr, scope, src_span, Inst.VarPtr.Params{ .var_scope = var_scope }); + switch (lval) { + LVal.Ptr => return var_ptr, + LVal.None => { + return irb.build(Inst.LoadPtr, scope, src_span, Inst.LoadPtr.Params{ .target = var_ptr }); + }, + } + }, + Ident.NotFound => {}, } //if (node->owner->any_imports_failed) { @@ -1546,6 +1685,7 @@ pub const Builder = struct { Scope.Id.Block, Scope.Id.Decls, Scope.Id.Root, + Scope.Id.Var, => scope = scope.parent orelse break, Scope.Id.DeferExpr => unreachable, @@ -1596,6 +1736,7 @@ pub const Builder = struct { Scope.Id.CompTime, Scope.Id.Block, + Scope.Id.Var, => scope = scope.parent orelse return is_noreturn, Scope.Id.DeferExpr => unreachable, @@ -1674,8 +1815,10 @@ pub const Builder = struct { Type.Pointer.Size, LVal, *Decl, + *Scope.Var, => {}, - // it's ok to add more types here, just make sure any instructions are ref'd appropriately + // it's ok to add more types here, just make sure that + // any instructions and basic blocks are ref'd appropriately else => @compileError("unrecognized type in Params: " ++ @typeName(FieldType)), } } @@ -1773,18 +1916,30 @@ pub const Builder = struct { //// the above blocks are rendered by ir_gen after the rest of codegen } - async fn findDecl(irb: *Builder, scope: *Scope, name: []const u8) ?*Decl { + const Ident = union(enum) { + NotFound, + Decl: *Decl, + VarScope: *Scope.Var, + }; + + async fn findIdent(irb: *Builder, scope: *Scope, name: []const u8) Ident { var s = scope; while (true) { switch (s.id) { + Scope.Id.Root => return Ident.NotFound, 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; + return Ident{ .Decl = entry.value }; + } + }, + Scope.Id.Var => { + const var_scope = @fieldParentPtr(Scope.Var, "base", s); + if (mem.eql(u8, var_scope.name, name)) { + return Ident{ .VarScope = var_scope }; } }, - Scope.Id.Root => return null, else => {}, } s = s.parent.?; diff --git a/src-self-hosted/llvm.zig b/src-self-hosted/llvm.zig index 8bb45ac616..778d3fae07 100644 --- a/src-self-hosted/llvm.zig +++ b/src-self-hosted/llvm.zig @@ -30,6 +30,7 @@ pub const AddGlobal = c.LLVMAddGlobal; pub const AddModuleCodeViewFlag = c.ZigLLVMAddModuleCodeViewFlag; pub const AddModuleDebugInfoFlag = c.ZigLLVMAddModuleDebugInfoFlag; pub const ArrayType = c.LLVMArrayType; +pub const BuildLoad = c.LLVMBuildLoad; pub const ClearCurrentDebugLocation = c.ZigLLVMClearCurrentDebugLocation; pub const ConstAllOnes = c.LLVMConstAllOnes; pub const ConstArray = c.LLVMConstArray; @@ -95,13 +96,25 @@ pub const SetInitializer = c.LLVMSetInitializer; pub const SetLinkage = c.LLVMSetLinkage; pub const SetTarget = c.LLVMSetTarget; pub const SetUnnamedAddr = c.LLVMSetUnnamedAddr; +pub const SetVolatile = c.LLVMSetVolatile; 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 GetElementType = LLVMGetElementType; +extern fn LLVMGetElementType(Ty: TypeRef) TypeRef; + +pub const TypeOf = LLVMTypeOf; +extern fn LLVMTypeOf(Val: ValueRef) TypeRef; + +pub const BuildStore = LLVMBuildStore; +extern fn LLVMBuildStore(arg0: BuilderRef, Val: ValueRef, Ptr: ValueRef) ?ValueRef; + +pub const BuildAlloca = LLVMBuildAlloca; +extern fn LLVMBuildAlloca(arg0: BuilderRef, Ty: TypeRef, Name: ?[*]const u8) ?ValueRef; + pub const ConstInBoundsGEP = LLVMConstInBoundsGEP; pub extern fn LLVMConstInBoundsGEP(ConstantVal: ValueRef, ConstantIndices: [*]ValueRef, NumIndices: c_uint) ?ValueRef; diff --git a/src-self-hosted/scope.zig b/src-self-hosted/scope.zig index 7a41083f44..a38e765c6e 100644 --- a/src-self-hosted/scope.zig +++ b/src-self-hosted/scope.zig @@ -6,23 +6,26 @@ const Compilation = @import("compilation.zig").Compilation; const mem = std.mem; const ast = std.zig.ast; const Value = @import("value.zig").Value; +const Type = @import("type.zig").Type; const ir = @import("ir.zig"); const Span = @import("errmsg.zig").Span; const assert = std.debug.assert; const event = std.event; +const llvm = @import("llvm.zig"); pub const Scope = struct { id: Id, parent: ?*Scope, - ref_count: usize, + ref_count: std.atomic.Int(usize), + /// Thread-safe pub fn ref(base: *Scope) void { - base.ref_count += 1; + _ = base.ref_count.incr(); } + /// Thread-safe pub fn deref(base: *Scope, comp: *Compilation) void { - base.ref_count -= 1; - if (base.ref_count == 0) { + if (base.ref_count.decr() == 1) { if (base.parent) |parent| parent.deref(comp); switch (base.id) { Id.Root => @fieldParentPtr(Root, "base", base).destroy(comp), @@ -32,6 +35,7 @@ pub const Scope = struct { Id.CompTime => @fieldParentPtr(CompTime, "base", base).destroy(comp), Id.Defer => @fieldParentPtr(Defer, "base", base).destroy(comp), Id.DeferExpr => @fieldParentPtr(DeferExpr, "base", base).destroy(comp), + Id.Var => @fieldParentPtr(Var, "base", base).destroy(comp), } } } @@ -49,15 +53,15 @@ pub const Scope = struct { var scope = base; while (true) { switch (scope.id) { - Id.FnDef => return @fieldParentPtr(FnDef, "base", base), - Id.Decls => return null, + Id.FnDef => return @fieldParentPtr(FnDef, "base", scope), + Id.Root, Id.Decls => return null, Id.Block, Id.Defer, Id.DeferExpr, Id.CompTime, - Id.Root, - => scope = scope.parent orelse return null, + Id.Var, + => scope = scope.parent.?, } } } @@ -66,7 +70,7 @@ pub const Scope = struct { var scope = base; while (true) { switch (scope.id) { - Id.DeferExpr => return @fieldParentPtr(DeferExpr, "base", base), + Id.DeferExpr => return @fieldParentPtr(DeferExpr, "base", scope), Id.FnDef, Id.Decls, @@ -76,11 +80,21 @@ pub const Scope = struct { Id.Defer, Id.CompTime, Id.Root, + Id.Var, => scope = scope.parent orelse return null, } } } + fn init(base: *Scope, id: Id, parent: *Scope) void { + base.* = Scope{ + .id = id, + .parent = parent, + .ref_count = std.atomic.Int(usize).init(1), + }; + parent.ref(); + } + pub const Id = enum { Root, Decls, @@ -89,6 +103,7 @@ pub const Scope = struct { CompTime, Defer, DeferExpr, + Var, }; pub const Root = struct { @@ -100,16 +115,16 @@ pub const Scope = struct { /// Takes ownership of realpath /// Takes ownership of tree, will deinit and destroy when done. pub fn create(comp: *Compilation, tree: *ast.Tree, realpath: []u8) !*Root { - const self = try comp.gpa().create(Root{ + const self = try comp.gpa().createOne(Root); + self.* = Root{ .base = Scope{ .id = Id.Root, .parent = null, - .ref_count = 1, + .ref_count = std.atomic.Int(usize).init(1), }, .tree = tree, .realpath = realpath, - }); - errdefer comp.gpa().destroy(self); + }; return self; } @@ -137,16 +152,13 @@ pub const Scope = struct { /// Creates a Decls scope with 1 reference pub fn create(comp: *Compilation, parent: *Scope) !*Decls { - const self = try comp.gpa().create(Decls{ - .base = Scope{ - .id = Id.Decls, - .parent = parent, - .ref_count = 1, - }, + const self = try comp.gpa().createOne(Decls); + self.* = Decls{ + .base = undefined, .table = event.Locked(Decl.Table).init(comp.loop, Decl.Table.init(comp.gpa())), .name_future = event.Future(void).init(comp.loop), - }); - parent.ref(); + }; + self.base.init(Id.Decls, parent); return self; } @@ -199,21 +211,16 @@ pub const Scope = struct { /// Creates a Block scope with 1 reference pub fn create(comp: *Compilation, parent: *Scope) !*Block { - const self = try comp.gpa().create(Block{ - .base = Scope{ - .id = Id.Block, - .parent = parent, - .ref_count = 1, - }, + const self = try comp.gpa().createOne(Block); + self.* = Block{ + .base = undefined, .incoming_values = undefined, .incoming_blocks = undefined, .end_block = undefined, .is_comptime = undefined, .safety = Safety.Auto, - }); - errdefer comp.gpa().destroy(self); - - parent.ref(); + }; + self.base.init(Id.Block, parent); return self; } @@ -226,22 +233,17 @@ pub const Scope = struct { base: Scope, /// This reference is not counted so that the scope can get destroyed with the function - fn_val: *Value.Fn, + fn_val: ?*Value.Fn, /// Creates a FnDef scope with 1 reference /// Must set the fn_val later pub fn create(comp: *Compilation, parent: *Scope) !*FnDef { - const self = try comp.gpa().create(FnDef{ - .base = Scope{ - .id = Id.FnDef, - .parent = parent, - .ref_count = 1, - }, - .fn_val = undefined, - }); - - parent.ref(); - + const self = try comp.gpa().createOne(FnDef); + self.* = FnDef{ + .base = undefined, + .fn_val = null, + }; + self.base.init(Id.FnDef, parent); return self; } @@ -255,15 +257,9 @@ pub const Scope = struct { /// Creates a CompTime scope with 1 reference pub fn create(comp: *Compilation, parent: *Scope) !*CompTime { - const self = try comp.gpa().create(CompTime{ - .base = Scope{ - .id = Id.CompTime, - .parent = parent, - .ref_count = 1, - }, - }); - - parent.ref(); + const self = try comp.gpa().createOne(CompTime); + self.* = CompTime{ .base = undefined }; + self.base.init(Id.CompTime, parent); return self; } @@ -289,20 +285,14 @@ pub const Scope = struct { kind: Kind, defer_expr_scope: *DeferExpr, ) !*Defer { - const self = try comp.gpa().create(Defer{ - .base = Scope{ - .id = Id.Defer, - .parent = parent, - .ref_count = 1, - }, + const self = try comp.gpa().createOne(Defer); + self.* = Defer{ + .base = undefined, .defer_expr_scope = defer_expr_scope, .kind = kind, - }); - errdefer comp.gpa().destroy(self); - + }; + self.base.init(Id.Defer, parent); defer_expr_scope.base.ref(); - - parent.ref(); return self; } @@ -319,18 +309,13 @@ pub const Scope = struct { /// Creates a DeferExpr scope with 1 reference pub fn create(comp: *Compilation, parent: *Scope, expr_node: *ast.Node) !*DeferExpr { - const self = try comp.gpa().create(DeferExpr{ - .base = Scope{ - .id = Id.DeferExpr, - .parent = parent, - .ref_count = 1, - }, + const self = try comp.gpa().createOne(DeferExpr); + self.* = DeferExpr{ + .base = undefined, .expr_node = expr_node, .reported_err = false, - }); - errdefer comp.gpa().destroy(self); - - parent.ref(); + }; + self.base.init(Id.DeferExpr, parent); return self; } @@ -338,4 +323,74 @@ pub const Scope = struct { comp.gpa().destroy(self); } }; + + pub const Var = struct { + base: Scope, + name: []const u8, + src_node: *ast.Node, + data: Data, + + pub const Data = union(enum) { + Param: Param, + Const: *Value, + }; + + pub const Param = struct { + index: usize, + typ: *Type, + llvm_value: llvm.ValueRef, + }; + + pub fn createParam( + comp: *Compilation, + parent: *Scope, + name: []const u8, + src_node: *ast.Node, + param_index: usize, + param_type: *Type, + ) !*Var { + const self = try create(comp, parent, name, src_node); + self.data = Data{ + .Param = Param{ + .index = param_index, + .typ = param_type, + .llvm_value = undefined, + }, + }; + return self; + } + + pub fn createConst( + comp: *Compilation, + parent: *Scope, + name: []const u8, + src_node: *ast.Node, + value: *Value, + ) !*Var { + const self = try create(comp, parent, name, src_node); + self.data = Data{ .Const = value }; + value.ref(); + return self; + } + + fn create(comp: *Compilation, parent: *Scope, name: []const u8, src_node: *ast.Node) !*Var { + const self = try comp.gpa().createOne(Var); + self.* = Var{ + .base = undefined, + .name = name, + .src_node = src_node, + .data = undefined, + }; + self.base.init(Id.Var, parent); + return self; + } + + pub fn destroy(self: *Var, comp: *Compilation) void { + switch (self.data) { + Data.Param => {}, + Data.Const => |value| value.deref(comp), + } + comp.gpa().destroy(self); + } + }; }; diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index 3b57260447..6783130fc7 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -141,9 +141,13 @@ pub const Type = struct { Id.Promise, => return true, + Id.Pointer => { + const ptr_type = @fieldParentPtr(Pointer, "base", base); + return ptr_type.key.child_type.hasBits(); + }, + Id.ErrorSet => @panic("TODO"), Id.Enum => @panic("TODO"), - Id.Pointer => @panic("TODO"), Id.Struct => @panic("TODO"), Id.Array => @panic("TODO"), Id.Optional => @panic("TODO"), @@ -222,29 +226,65 @@ pub const Type = struct { pub const Fn = struct { base: Type, key: Key, + non_key: NonKey, garbage_node: std.atomic.Stack(*Fn).Node, + pub const Kind = enum { + Normal, + Generic, + }; + + pub const NonKey = union { + Normal: Normal, + Generic: void, + + pub const Normal = struct { + variable_list: std.ArrayList(*Scope.Var), + }; + }; + pub const Key = struct { data: Data, alignment: ?u32, - pub const Data = union(enum) { + pub const Data = union(Kind) { Generic: Generic, Normal: Normal, }; + pub const Normal = struct { + params: []Param, + return_type: *Type, + is_var_args: bool, + cc: CallingConvention, + }; + + pub const Generic = struct { + param_count: usize, + cc: CC, + + pub const CC = union(CallingConvention) { + Auto, + C, + Cold, + Naked, + Stdcall, + Async: *Type, // allocator type + }; + }; + pub fn hash(self: *const Key) u32 { var result: u32 = 0; result +%= hashAny(self.alignment, 0); switch (self.data) { - Data.Generic => |generic| { + Kind.Generic => |generic| { result +%= hashAny(generic.param_count, 1); switch (generic.cc) { CallingConvention.Async => |allocator_type| result +%= hashAny(allocator_type, 2), else => result +%= hashAny(CallingConvention(generic.cc), 3), } }, - Data.Normal => |normal| { + Kind.Normal => |normal| { result +%= hashAny(normal.return_type, 4); result +%= hashAny(normal.is_var_args, 5); result +%= hashAny(normal.cc, 6); @@ -264,7 +304,7 @@ pub const Type = struct { } if (@TagType(Data)(self.data) != @TagType(Data)(other.data)) return false; switch (self.data) { - Data.Generic => |*self_generic| { + Kind.Generic => |*self_generic| { const other_generic = &other.data.Generic; if (self_generic.param_count != other_generic.param_count) return false; if (CallingConvention(self_generic.cc) != CallingConvention(other_generic.cc)) return false; @@ -276,7 +316,7 @@ pub const Type = struct { else => {}, } }, - Data.Normal => |*self_normal| { + Kind.Normal => |*self_normal| { const other_normal = &other.data.Normal; if (self_normal.cc != other_normal.cc) return false; if (self_normal.is_var_args != other_normal.is_var_args) return false; @@ -293,13 +333,13 @@ pub const Type = struct { pub fn deref(key: Key, comp: *Compilation) void { switch (key.data) { - Key.Data.Generic => |generic| { + Kind.Generic => |generic| { switch (generic.cc) { CallingConvention.Async => |allocator_type| allocator_type.base.deref(comp), else => {}, } }, - Key.Data.Normal => |normal| { + Kind.Normal => |normal| { normal.return_type.base.deref(comp); for (normal.params) |param| { param.typ.base.deref(comp); @@ -310,13 +350,13 @@ pub const Type = struct { pub fn ref(key: Key) void { switch (key.data) { - Key.Data.Generic => |generic| { + Kind.Generic => |generic| { switch (generic.cc) { CallingConvention.Async => |allocator_type| allocator_type.base.ref(), else => {}, } }, - Key.Data.Normal => |normal| { + Kind.Normal => |normal| { normal.return_type.base.ref(); for (normal.params) |param| { param.typ.base.ref(); @@ -326,27 +366,6 @@ pub const Type = struct { } }; - pub const Normal = struct { - params: []Param, - return_type: *Type, - is_var_args: bool, - cc: CallingConvention, - }; - - pub const Generic = struct { - param_count: usize, - cc: CC, - - pub const CC = union(CallingConvention) { - Auto, - C, - Cold, - Naked, - Stdcall, - Async: *Type, // allocator type - }; - }; - pub const CallingConvention = enum { Auto, C, @@ -374,8 +393,8 @@ pub const Type = struct { pub fn paramCount(self: *Fn) usize { return switch (self.key.data) { - Key.Data.Generic => |generic| generic.param_count, - Key.Data.Normal => |normal| normal.params.len, + Kind.Generic => |generic| generic.param_count, + Kind.Normal => |normal| normal.params.len, }; } @@ -394,11 +413,13 @@ pub const Type = struct { key.ref(); errdefer key.deref(comp); - const self = try comp.gpa().create(Fn{ + const self = try comp.gpa().createOne(Fn); + self.* = Fn{ .base = undefined, .key = key, + .non_key = undefined, .garbage_node = undefined, - }); + }; errdefer comp.gpa().destroy(self); var name_buf = try std.Buffer.initSize(comp.gpa(), 0); @@ -407,7 +428,8 @@ pub const Type = struct { const name_stream = &std.io.BufferOutStream.init(&name_buf).stream; switch (key.data) { - Key.Data.Generic => |generic| { + Kind.Generic => |generic| { + self.non_key = NonKey{ .Generic = {} }; switch (generic.cc) { CallingConvention.Async => |async_allocator_type| { try name_stream.print("async<{}> ", async_allocator_type.name); @@ -429,7 +451,10 @@ pub const Type = struct { } try name_stream.write(" var"); }, - Key.Data.Normal => |normal| { + Kind.Normal => |normal| { + self.non_key = NonKey{ + .Normal = NonKey.Normal{ .variable_list = std.ArrayList(*Scope.Var).init(comp.gpa()) }, + }; const cc_str = ccFnTypeStr(normal.cc); try name_stream.print("{}fn(", cc_str); for (normal.params) |param, i| { @@ -462,6 +487,12 @@ pub const Type = struct { pub fn destroy(self: *Fn, comp: *Compilation) void { self.key.deref(comp); + switch (self.key.data) { + Kind.Generic => {}, + Kind.Normal => { + self.non_key.Normal.variable_list.deinit(); + }, + } comp.gpa().destroy(self); } diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index 2005e3c119..e6dca4eff7 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -60,7 +60,7 @@ pub const Value = struct { pub fn getLlvmConst(base: *Value, ofile: *ObjectFile) (error{OutOfMemory}!?llvm.ValueRef) { switch (base.id) { Id.Type => unreachable, - Id.Fn => @panic("TODO"), + Id.Fn => return @fieldParentPtr(Fn, "base", base).getLlvmConst(ofile), Id.FnProto => return @fieldParentPtr(FnProto, "base", base).getLlvmConst(ofile), Id.Void => return null, Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmConst(ofile), @@ -180,7 +180,7 @@ pub const Value = struct { child_scope: *Scope, /// parent is child_scope - block_scope: *Scope.Block, + block_scope: ?*Scope.Block, /// Path to the object file that contains this function containing_object: Buffer, @@ -205,7 +205,7 @@ pub const Value = struct { }, .fndef_scope = fndef_scope, .child_scope = &fndef_scope.base, - .block_scope = undefined, + .block_scope = null, .symbol_name = symbol_name, .containing_object = Buffer.initNull(comp.gpa()), .link_set_node = link_set_node, @@ -231,6 +231,22 @@ pub const Value = struct { self.symbol_name.deinit(); comp.gpa().destroy(self); } + + /// We know that the function definition will end up in an .o file somewhere. + /// Here, all we have to do is generate a global prototype. + /// TODO cache the prototype per ObjectFile + pub fn getLlvmConst(self: *Fn, 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 Void = struct { -- cgit v1.2.3