diff --git a/CMakeLists.txt b/CMakeLists.txt index fdedcd5eec..e606855555 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -431,8 +431,8 @@ set(ZIG_CPP_SOURCES set(ZIG_STD_FILES "array_list.zig" "atomic/index.zig" - "atomic/queue_mpmc.zig" - "atomic/queue_mpsc.zig" + "atomic/int.zig" + "atomic/queue.zig" "atomic/stack.zig" "base64.zig" "buf_map.zig" @@ -459,6 +459,8 @@ set(ZIG_STD_FILES "empty.zig" "event.zig" "event/channel.zig" + "event/future.zig" + "event/group.zig" "event/lock.zig" "event/locked.zig" "event/loop.zig" diff --git a/build.zig b/build.zig index fd154c7504..c9e70887e3 100644 --- a/build.zig +++ b/build.zig @@ -35,73 +35,31 @@ pub fn build(b: *Builder) !void { "BUILD_INFO", }); var index: usize = 0; - const cmake_binary_dir = nextValue(&index, build_info); - const cxx_compiler = nextValue(&index, build_info); - const llvm_config_exe = nextValue(&index, build_info); - const lld_include_dir = nextValue(&index, build_info); - const lld_libraries = nextValue(&index, build_info); - const std_files = nextValue(&index, build_info); - const c_header_files = nextValue(&index, build_info); - const dia_guids_lib = nextValue(&index, build_info); + var ctx = Context{ + .cmake_binary_dir = nextValue(&index, build_info), + .cxx_compiler = nextValue(&index, build_info), + .llvm_config_exe = nextValue(&index, build_info), + .lld_include_dir = nextValue(&index, build_info), + .lld_libraries = nextValue(&index, build_info), + .std_files = nextValue(&index, build_info), + .c_header_files = nextValue(&index, build_info), + .dia_guids_lib = nextValue(&index, build_info), + .llvm = undefined, + }; + ctx.llvm = try findLLVM(b, ctx.llvm_config_exe); - const llvm = findLLVM(b, llvm_config_exe) catch unreachable; + var test_stage2 = b.addTest("src-self-hosted/test.zig"); + test_stage2.setBuildMode(builtin.Mode.Debug); var exe = b.addExecutable("zig", "src-self-hosted/main.zig"); exe.setBuildMode(mode); - // This is for finding /lib/libz.a on alpine linux. - // TODO turn this into -Dextra-lib-path=/lib option - exe.addLibPath("/lib"); - - exe.addIncludeDir("src"); - exe.addIncludeDir(cmake_binary_dir); - addCppLib(b, exe, cmake_binary_dir, "zig_cpp"); - if (lld_include_dir.len != 0) { - exe.addIncludeDir(lld_include_dir); - var it = mem.split(lld_libraries, ";"); - while (it.next()) |lib| { - exe.addObjectFile(lib); - } - } else { - addCppLib(b, exe, cmake_binary_dir, "embedded_lld_wasm"); - addCppLib(b, exe, cmake_binary_dir, "embedded_lld_elf"); - addCppLib(b, exe, cmake_binary_dir, "embedded_lld_coff"); - addCppLib(b, exe, cmake_binary_dir, "embedded_lld_lib"); - } - dependOnLib(exe, llvm); - - if (exe.target.getOs() == builtin.Os.linux) { - const libstdcxx_path_padded = try b.exec([][]const u8{ - cxx_compiler, - "-print-file-name=libstdc++.a", - }); - const libstdcxx_path = mem.split(libstdcxx_path_padded, "\r\n").next().?; - if (mem.eql(u8, libstdcxx_path, "libstdc++.a")) { - warn( - \\Unable to determine path to libstdc++.a - \\On Fedora, install libstdc++-static and try again. - \\ - ); - return error.RequiredLibraryNotFound; - } - exe.addObjectFile(libstdcxx_path); - - exe.linkSystemLibrary("pthread"); - } else if (exe.target.isDarwin()) { - exe.linkSystemLibrary("c++"); - } - - if (dia_guids_lib.len != 0) { - exe.addObjectFile(dia_guids_lib); - } - - if (exe.target.getOs() != builtin.Os.windows) { - exe.linkSystemLibrary("xml2"); - } - exe.linkSystemLibrary("c"); + try configureStage2(b, test_stage2, ctx); + try configureStage2(b, exe, ctx); b.default_step.dependOn(&exe.step); + const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false; const skip_self_hosted = b.option(bool, "skip-self-hosted", "Main test suite skips building self hosted compiler") orelse false; if (!skip_self_hosted) { test_step.dependOn(&exe.step); @@ -110,30 +68,40 @@ pub fn build(b: *Builder) !void { exe.setVerboseLink(verbose_link_exe); b.installArtifact(exe); - installStdLib(b, std_files); - installCHeaders(b, c_header_files); + installStdLib(b, ctx.std_files); + installCHeaders(b, ctx.c_header_files); const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter"); - const with_lldb = b.option(bool, "with-lldb", "Run tests in LLDB to get a backtrace if one fails") orelse false; - test_step.dependOn(docs_step); + const test_stage2_step = b.step("test-stage2", "Run the stage2 compiler tests"); + test_stage2_step.dependOn(&test_stage2.step); + test_step.dependOn(test_stage2_step); - test_step.dependOn(tests.addPkgTests(b, test_filter, "test/behavior.zig", "behavior", "Run the behavior tests", with_lldb)); + const all_modes = []builtin.Mode{ + builtin.Mode.Debug, + builtin.Mode.ReleaseSafe, + builtin.Mode.ReleaseFast, + builtin.Mode.ReleaseSmall, + }; + const modes = if (skip_release) []builtin.Mode{builtin.Mode.Debug} else all_modes; - test_step.dependOn(tests.addPkgTests(b, test_filter, "std/index.zig", "std", "Run the standard library tests", with_lldb)); + test_step.dependOn(tests.addPkgTests(b, test_filter, "test/behavior.zig", "behavior", "Run the behavior tests", modes)); - test_step.dependOn(tests.addPkgTests(b, test_filter, "std/special/compiler_rt/index.zig", "compiler-rt", "Run the compiler_rt tests", with_lldb)); + test_step.dependOn(tests.addPkgTests(b, test_filter, "std/index.zig", "std", "Run the standard library tests", modes)); - test_step.dependOn(tests.addCompareOutputTests(b, test_filter)); + test_step.dependOn(tests.addPkgTests(b, test_filter, "std/special/compiler_rt/index.zig", "compiler-rt", "Run the compiler_rt tests", modes)); + + test_step.dependOn(tests.addCompareOutputTests(b, test_filter, modes)); test_step.dependOn(tests.addBuildExampleTests(b, test_filter)); - test_step.dependOn(tests.addCompileErrorTests(b, test_filter)); - test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter)); - test_step.dependOn(tests.addRuntimeSafetyTests(b, test_filter)); + test_step.dependOn(tests.addCompileErrorTests(b, test_filter, modes)); + test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, modes)); + test_step.dependOn(tests.addRuntimeSafetyTests(b, test_filter, modes)); test_step.dependOn(tests.addTranslateCTests(b, test_filter)); test_step.dependOn(tests.addGenHTests(b, test_filter)); + test_step.dependOn(docs_step); } -fn dependOnLib(lib_exe_obj: *std.build.LibExeObjStep, dep: *const LibraryDep) void { +fn dependOnLib(lib_exe_obj: var, dep: *const LibraryDep) void { for (dep.libdirs.toSliceConst()) |lib_dir| { lib_exe_obj.addLibPath(lib_dir); } @@ -148,7 +116,7 @@ fn dependOnLib(lib_exe_obj: *std.build.LibExeObjStep, dep: *const LibraryDep) vo } } -fn addCppLib(b: *Builder, lib_exe_obj: *std.build.LibExeObjStep, cmake_binary_dir: []const u8, lib_name: []const u8) void { +fn addCppLib(b: *Builder, lib_exe_obj: var, cmake_binary_dir: []const u8, lib_name: []const u8) void { const lib_prefix = if (lib_exe_obj.target.isWindows()) "" else "lib"; lib_exe_obj.addObjectFile(os.path.join(b.allocator, cmake_binary_dir, "zig_cpp", b.fmt("{}{}{}", lib_prefix, lib_name, lib_exe_obj.target.libFileExt())) catch unreachable); } @@ -254,3 +222,68 @@ fn nextValue(index: *usize, build_info: []const u8) []const u8 { } } } + +fn configureStage2(b: *Builder, exe: var, ctx: Context) !void { + // This is for finding /lib/libz.a on alpine linux. + // TODO turn this into -Dextra-lib-path=/lib option + exe.addLibPath("/lib"); + + exe.addIncludeDir("src"); + exe.addIncludeDir(ctx.cmake_binary_dir); + addCppLib(b, exe, ctx.cmake_binary_dir, "zig_cpp"); + if (ctx.lld_include_dir.len != 0) { + exe.addIncludeDir(ctx.lld_include_dir); + var it = mem.split(ctx.lld_libraries, ";"); + while (it.next()) |lib| { + exe.addObjectFile(lib); + } + } else { + addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_wasm"); + addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_elf"); + addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_coff"); + addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_lib"); + } + dependOnLib(exe, ctx.llvm); + + if (exe.target.getOs() == builtin.Os.linux) { + const libstdcxx_path_padded = try b.exec([][]const u8{ + ctx.cxx_compiler, + "-print-file-name=libstdc++.a", + }); + const libstdcxx_path = mem.split(libstdcxx_path_padded, "\r\n").next().?; + if (mem.eql(u8, libstdcxx_path, "libstdc++.a")) { + warn( + \\Unable to determine path to libstdc++.a + \\On Fedora, install libstdc++-static and try again. + \\ + ); + return error.RequiredLibraryNotFound; + } + exe.addObjectFile(libstdcxx_path); + + exe.linkSystemLibrary("pthread"); + } else if (exe.target.isDarwin()) { + exe.linkSystemLibrary("c++"); + } + + if (ctx.dia_guids_lib.len != 0) { + exe.addObjectFile(ctx.dia_guids_lib); + } + + if (exe.target.getOs() != builtin.Os.windows) { + exe.linkSystemLibrary("xml2"); + } + exe.linkSystemLibrary("c"); +} + +const Context = struct { + cmake_binary_dir: []const u8, + cxx_compiler: []const u8, + llvm_config_exe: []const u8, + lld_include_dir: []const u8, + lld_libraries: []const u8, + std_files: []const u8, + c_header_files: []const u8, + dia_guids_lib: []const u8, + llvm: LibraryDep, +}; diff --git a/doc/langref.html.in b/doc/langref.html.in index c90c847f92..ea672ccb17 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -2239,7 +2239,7 @@ test "switch inside function" { // On an OS other than fuchsia, block is not even analyzed, // so this compile error is not triggered. // On fuchsia this compile error would be triggered. - @compileError("windows not supported"); + @compileError("fuchsia not supported"); }, else => {}, } @@ -2303,13 +2303,13 @@ test "while continue" { {#code_begin|test|while#} const assert = @import("std").debug.assert; -test "while loop continuation expression" { +test "while loop continue expression" { var i: usize = 0; while (i < 10) : (i += 1) {} assert(i == 10); } -test "while loop continuation expression, more complicated" { +test "while loop continue expression, more complicated" { var i1: usize = 1; var j1: usize = 1; while (i1 * j1 < 2000) : ({ i1 *= 2; j1 *= 3; }) { @@ -7118,10 +7118,16 @@ Environments: opencl

The Zig Standard Library (@import("std")) has architecture, environment, and operating sytsem - abstractions, and thus takes additional work to support more platforms. It currently supports - Linux x86_64. Not all standard library code requires operating system abstractions, however, + abstractions, and thus takes additional work to support more platforms. + Not all standard library code requires operating system abstractions, however, so things such as generic data structures work an all above platforms.

+

The current list of targets supported by the Zig Standard Library is:

+ {#header_close#} {#header_open|Style Guide#}

diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig new file mode 100644 index 0000000000..a07485e74e --- /dev/null +++ b/src-self-hosted/codegen.zig @@ -0,0 +1,59 @@ +const std = @import("std"); +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 ir = @import("ir.zig"); +const Value = @import("value.zig").Value; +const Type = @import("type.zig").Type; +const event = std.event; + +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()); + + 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(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 ofile = ObjectFile{ + .comp = comp, + .module = module, + .builder = builder, + .context = context, + .lock = event.Lock.init(comp.loop), + }; + + try renderToLlvmModule(&ofile, fn_val, code); + + if (comp.verbose_llvm_ir) { + llvm.DumpModule(ofile.module); + } +} + +pub const ObjectFile = struct { + comp: *Compilation, + module: llvm.ModuleRef, + builder: llvm.BuilderRef, + context: llvm.ContextRef, + lock: event.Lock, + + fn a(self: *ObjectFile) *std.mem.Allocator { + return self.comp.a(); + } +}; + +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); +} 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 new file mode 100644 index 0000000000..c0173266ee --- /dev/null +++ b/src-self-hosted/decl.zig @@ -0,0 +1,96 @@ +const std = @import("std"); +const Allocator = mem.Allocator; +const mem = std.mem; +const ast = std.zig.ast; +const Visib = @import("visib.zig").Visib; +const ParsedFile = @import("parsed_file.zig").ParsedFile; +const event = std.event; +const Value = @import("value.zig").Value; +const Token = std.zig.Token; +const errmsg = @import("errmsg.zig"); +const Scope = @import("scope.zig").Scope; +const Compilation = @import("compilation.zig").Compilation; + +pub const Decl = struct { + id: Id, + name: []const u8, + visib: Visib, + resolution: event.Future(Compilation.BuildError!void), + resolution_in_progress: u8, + parsed_file: *ParsedFile, + parent_scope: *Scope, + + pub const Table = std.HashMap([]const u8, *Decl, mem.hash_slice_u8, mem.eql_slice_u8); + + pub fn isExported(base: *const Decl, tree: *ast.Tree) bool { + switch (base.id) { + Id.Fn => { + const fn_decl = @fieldParentPtr(Fn, "base", base); + return fn_decl.isExported(tree); + }, + else => return false, + } + } + + pub fn getSpan(base: *const Decl) errmsg.Span { + switch (base.id) { + Id.Fn => { + const fn_decl = @fieldParentPtr(Fn, "base", base); + const fn_proto = fn_decl.fn_proto; + const start = fn_proto.fn_token; + const end = fn_proto.name_token orelse start; + return errmsg.Span{ + .first = start, + .last = end + 1, + }; + }, + else => @panic("TODO"), + } + } + + pub const Id = enum { + Var, + Fn, + CompTime, + }; + + pub const Var = struct { + base: Decl, + }; + + pub const Fn = struct { + base: Decl, + value: Val, + fn_proto: *const ast.Node.FnProto, + + // TODO https://github.com/ziglang/zig/issues/683 and then make this anonymous + pub const Val = union { + Unresolved: void, + Ok: *Value.Fn, + }; + + pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 { + return if (self.fn_proto.extern_export_inline_token) |tok_index| x: { + const token = tree.tokens.at(tok_index); + break :x switch (token.id) { + Token.Id.Extern => tree.tokenSlicePtr(token), + else => null, + }; + } else null; + } + + pub fn isExported(self: Fn, tree: *ast.Tree) bool { + if (self.fn_proto.extern_export_inline_token) |tok_index| { + const token = tree.tokens.at(tok_index); + return token.id == Token.Id.Keyword_export; + } else { + return false; + } + } + }; + + pub const CompTime = struct { + base: Decl, + }; +}; + diff --git a/src-self-hosted/errmsg.zig b/src-self-hosted/errmsg.zig index b6fd78d8f6..4e353bfb14 100644 --- a/src-self-hosted/errmsg.zig +++ b/src-self-hosted/errmsg.zig @@ -11,11 +11,22 @@ pub const Color = enum { On, }; +pub const Span = struct { + first: ast.TokenIndex, + last: ast.TokenIndex, + + pub fn token(i: TokenIndex) Span { + return Span { + .first = i, + .last = i, + }; + } +}; + pub const Msg = struct { path: []const u8, text: []u8, - first_token: TokenIndex, - last_token: TokenIndex, + span: Span, tree: *ast.Tree, }; @@ -39,8 +50,10 @@ pub fn createFromParseError( .tree = tree, .path = path, .text = text_buf.toOwnedSlice(), - .first_token = loc_token, - .last_token = loc_token, + .span = Span{ + .first = loc_token, + .last = loc_token, + }, }); errdefer allocator.destroy(msg); @@ -48,8 +61,8 @@ pub fn createFromParseError( } pub fn printToStream(stream: var, msg: *const Msg, color_on: bool) !void { - const first_token = msg.tree.tokens.at(msg.first_token); - const last_token = msg.tree.tokens.at(msg.last_token); + const first_token = msg.tree.tokens.at(msg.span.first); + const last_token = msg.tree.tokens.at(msg.span.last); const start_loc = msg.tree.tokenLocationPtr(0, first_token); const end_loc = msg.tree.tokenLocationPtr(first_token.end, last_token); if (!color_on) { diff --git a/src-self-hosted/introspect.zig b/src-self-hosted/introspect.zig index 74084b48c6..ecd04c4467 100644 --- a/src-self-hosted/introspect.zig +++ b/src-self-hosted/introspect.zig @@ -53,3 +53,8 @@ pub fn resolveZigLibDir(allocator: *mem.Allocator) ![]u8 { return error.ZigLibDirNotFound; }; } + +/// Caller must free result +pub fn resolveZigCacheDir(allocator: *mem.Allocator) ![]u8 { + return std.mem.dupe(allocator, u8, "zig-cache"); +} diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 3334d9511b..22d5a067a7 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -1,111 +1,1034 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const Compilation = @import("compilation.zig").Compilation; const Scope = @import("scope.zig").Scope; +const ast = std.zig.ast; +const Allocator = std.mem.Allocator; +const Value = @import("value.zig").Value; +const Type = Value.Type; +const assert = std.debug.assert; +const Token = std.zig.Token; +const ParsedFile = @import("parsed_file.zig").ParsedFile; +const Span = @import("errmsg.zig").Span; + +pub const LVal = enum { + None, + Ptr, +}; + +pub const IrVal = union(enum) { + Unknown, + KnownType: *Type, + KnownValue: *Value, + + const Init = enum { + Unknown, + NoReturn, + Void, + }; + + pub fn dump(self: IrVal) void { + switch (self) { + IrVal.Unknown => typeof.dump(), + IrVal.KnownType => |typeof| { + std.debug.warn("KnownType("); + typeof.dump(); + std.debug.warn(")"); + }, + IrVal.KnownValue => |value| { + std.debug.warn("KnownValue("); + value.dump(); + std.debug.warn(")"); + }, + } + } +}; pub const Instruction = struct { id: Id, scope: *Scope, + debug_id: usize, + val: IrVal, + ref_count: usize, + span: Span, + + /// true if this instruction was generated by zig and not from user code + is_generated: bool, + + /// the instruction that is derived from this one in analysis + child: ?*Instruction, + + /// the instruction that this one derives from in analysis + parent: ?*Instruction, + + pub fn cast(base: *Instruction, comptime T: type) ?*T { + if (base.id == comptime typeToId(T)) { + return @fieldParentPtr(T, "base", base); + } + return null; + } + + pub fn typeToId(comptime T: type) Id { + comptime var i = 0; + inline while (i < @memberCount(Id)) : (i += 1) { + if (T == @field(Instruction, @memberName(Id, i))) { + return @field(Id, @memberName(Id, i)); + } + } + unreachable; + } + + pub fn dump(base: *const Instruction) void { + comptime var i = 0; + inline while (i < @memberCount(Id)) : (i += 1) { + if (base.id == @field(Id, @memberName(Id, i))) { + const T = @field(Instruction, @memberName(Id, i)); + std.debug.warn("#{} = {}(", base.debug_id, @tagName(base.id)); + @fieldParentPtr(T, "base", base).dump(); + std.debug.warn(")"); + return; + } + } + unreachable; + } + + pub fn hasSideEffects(base: *const Instruction) bool { + comptime var i = 0; + inline while (i < @memberCount(Id)) : (i += 1) { + if (base.id == @field(Id, @memberName(Id, i))) { + const T = @field(Instruction, @memberName(Id, i)); + return @fieldParentPtr(T, "base", base).hasSideEffects(); + } + } + unreachable; + } + + pub fn analyze(base: *Instruction, ira: *Analyze) Analyze.Error!*Instruction { + comptime var i = 0; + 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; + } + } + unreachable; + } + + fn getAsParam(param: *Instruction) !*Instruction { + const child = param.child orelse return error.SemanticAnalysisFailed; + switch (child.val) { + IrVal.Unknown => return error.SemanticAnalysisFailed, + else => return child, + } + } + + /// asserts that the type is known + fn getKnownType(self: *Instruction) *Type { + switch (self.val) { + IrVal.KnownType => |typeof| return typeof, + IrVal.KnownValue => |value| return value.typeof, + IrVal.Unknown => unreachable, + } + } + + pub fn setGenerated(base: *Instruction) void { + base.is_generated = true; + } + + pub fn isNoReturn(base: *const Instruction) 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, + } + } + + pub fn linkToParent(self: *Instruction, parent: *Instruction) void { + assert(self.parent == null); + assert(parent.child == null); + self.parent = parent; + parent.child = self; + } pub const Id = enum { - Br, - CondBr, - SwitchBr, - SwitchVar, - SwitchTarget, - Phi, - UnOp, - BinOp, - DeclVar, - LoadPtr, - StorePtr, - FieldPtr, - StructFieldPtr, - UnionFieldPtr, - ElemPtr, - VarPtr, - Call, - Const, Return, - Cast, - ContainerInitList, - ContainerInitFields, - StructInit, - UnionInit, - Unreachable, - TypeOf, - ToPtrType, - PtrTypeChild, - SetRuntimeSafety, - SetFloatMode, - ArrayType, - SliceType, - Asm, - SizeOf, - TestNonNull, - UnwrapMaybe, - MaybeWrap, - UnionTag, - Clz, - Ctz, - Import, - CImport, - CInclude, - CDefine, - CUndef, - ArrayLen, + Const, Ref, - MinValue, - MaxValue, - CompileErr, - CompileLog, - ErrName, - EmbedFile, - Cmpxchg, - Fence, - Truncate, - IntType, - BoolNot, - Memset, - Memcpy, - Slice, - MemberCount, - MemberType, - MemberName, - Breakpoint, - ReturnAddress, - FrameAddress, - AlignOf, - OverflowOp, - TestErr, - UnwrapErrCode, - UnwrapErrPayload, - ErrWrapCode, - ErrWrapPayload, - FnProto, - TestComptime, - PtrCast, - BitCast, - WidenOrShorten, - IntToPtr, - PtrToInt, - IntToEnum, - IntToErr, - ErrToInt, - CheckSwitchProngs, - CheckStatementIsVoid, - TypeName, - CanImplicitCast, - DeclRef, - Panic, - TagName, - TagType, - FieldParentPtr, - OffsetOf, - TypeId, - SetEvalBranchQuota, - PtrTypeOf, - AlignCast, - OpaqueType, - SetAlignStack, - ArgType, - Export, + DeclVar, + CheckVoidStmt, + Phi, + Br, + AddImplicitReturnType, + }; + + pub const Const = struct { + base: Instruction, + params: Params, + + const Params = struct {}; + + // Use Builder.buildConst* methods, or, after building a Const instruction, + // manually set the ir_val field. + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(self: *const Const) void { + self.base.val.KnownValue.dump(); + } + + pub fn hasSideEffects(self: *const Const) bool { + return false; + } + + pub fn analyze(self: *const Const, ira: *Analyze) !*Instruction { + const new_inst = try ira.irb.build(Const, self.base.scope, self.base.span, Params{}); + new_inst.val = IrVal{ .KnownValue = self.base.val.KnownValue.getRef() }; + return new_inst; + } + }; + + pub const Return = struct { + base: Instruction, + params: Params, + + const Params = struct { + return_value: *Instruction, + }; + + const ir_val_init = IrVal.Init.NoReturn; + + pub fn dump(self: *const Return) void { + std.debug.warn("#{}", self.params.return_value.debug_id); + } + + pub fn hasSideEffects(self: *const Return) bool { + return true; + } + + pub fn analyze(self: *const Return, ira: *Analyze) !*Instruction { + const value = try self.params.return_value.getAsParam(); + const casted_value = try ira.implicitCast(value, ira.explicit_return_type); + + // TODO detect returning local variable address + + return ira.irb.build(Return, self.base.scope, self.base.span, Params{ .return_value = casted_value }); + } + }; + + pub const Ref = struct { + base: Instruction, + params: Params, + + const Params = struct { + target: *Instruction, + mut: Type.Pointer.Mut, + volatility: Type.Pointer.Vol, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(inst: *const Ref) void {} + + pub fn hasSideEffects(inst: *const Ref) bool { + return false; + } + + pub fn analyze(self: *const Ref, ira: *Analyze) !*Instruction { + const target = try self.params.target.getAsParam(); + + if (ira.getCompTimeValOrNullUndefOk(target)) |val| { + return ira.getCompTimeRef( + val, + Value.Ptr.Mut.CompTimeConst, + self.params.mut, + self.params.volatility, + val.typeof.getAbiAlignment(ira.irb.comp), + ); + } + + const new_inst = try ira.irb.build(Ref, self.base.scope, self.base.span, Params{ + .target = target, + .mut = self.params.mut, + .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), + ); + // 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 }; + // TODO potentially add an alloca entry here + return new_inst; + } + }; + + pub const DeclVar = struct { + base: Instruction, + params: Params, + + const Params = struct { + variable: *Variable, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(inst: *const DeclVar) void {} + + pub fn hasSideEffects(inst: *const DeclVar) bool { + return true; + } + + pub fn analyze(self: *const DeclVar, ira: *Analyze) !*Instruction { + return error.Unimplemented; // TODO + } + }; + + pub const CheckVoidStmt = struct { + base: Instruction, + params: Params, + + const Params = struct { + target: *Instruction, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(inst: *const CheckVoidStmt) void {} + + pub fn hasSideEffects(inst: *const CheckVoidStmt) bool { + return true; + } + + pub fn analyze(self: *const CheckVoidStmt, ira: *Analyze) !*Instruction { + return error.Unimplemented; // TODO + } + }; + + pub const Phi = struct { + base: Instruction, + params: Params, + + const Params = struct { + incoming_blocks: []*BasicBlock, + incoming_values: []*Instruction, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(inst: *const Phi) void {} + + pub fn hasSideEffects(inst: *const Phi) bool { + return false; + } + + pub fn analyze(self: *const Phi, ira: *Analyze) !*Instruction { + return error.Unimplemented; // TODO + } + }; + + pub const Br = struct { + base: Instruction, + params: Params, + + const Params = struct { + dest_block: *BasicBlock, + is_comptime: *Instruction, + }; + + const ir_val_init = IrVal.Init.NoReturn; + + pub fn dump(inst: *const Br) void {} + + pub fn hasSideEffects(inst: *const Br) bool { + return true; + } + + pub fn analyze(self: *const Br, ira: *Analyze) !*Instruction { + return error.Unimplemented; // TODO + } + }; + + pub const AddImplicitReturnType = struct { + base: Instruction, + params: Params, + + pub const Params = struct { + target: *Instruction, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(inst: *const AddImplicitReturnType) void { + std.debug.warn("#{}", inst.params.target.debug_id); + } + + pub fn hasSideEffects(inst: *const AddImplicitReturnType) bool { + return true; + } + + pub fn analyze(self: *const AddImplicitReturnType, ira: *Analyze) !*Instruction { + const target = try self.params.target.getAsParam(); + try ira.src_implicit_return_type_list.append(target); + return ira.irb.buildConstVoid(self.base.scope, self.base.span, true); + } }; }; + +pub const Variable = struct { + child_scope: *Scope, +}; + +pub const BasicBlock = struct { + ref_count: usize, + name_hint: []const u8, + debug_id: usize, + scope: *Scope, + instruction_list: std.ArrayList(*Instruction), + ref_instruction: ?*Instruction, + + /// the basic block that is derived from this one in analysis + child: ?*BasicBlock, + + /// the basic block that this one derives from in analysis + parent: ?*BasicBlock, + + pub fn ref(self: *BasicBlock) void { + self.ref_count += 1; + } + + pub fn linkToParent(self: *BasicBlock, parent: *BasicBlock) void { + assert(self.parent == null); + assert(parent.child == null); + self.parent = parent; + parent.child = self; + } +}; + +/// Stuff that survives longer than Builder +pub const Code = struct { + basic_block_list: std.ArrayList(*BasicBlock), + arena: std.heap.ArenaAllocator, + return_type: ?*Type, + + /// allocator is comp.a() + pub fn destroy(self: *Code, allocator: *Allocator) void { + self.arena.deinit(); + allocator.destroy(self); + } + + 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); + for (bb.instruction_list.toSliceConst()) |instr| { + std.debug.warn(" "); + instr.dump(); + std.debug.warn("\n"); + } + } + } +}; + +pub const Builder = struct { + comp: *Compilation, + code: *Code, + current_basic_block: *BasicBlock, + next_debug_id: usize, + parsed_file: *ParsedFile, + is_comptime: bool, + + pub const Error = Analyze.Error; + + 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(comp.a()), + .return_type = null, + }); + code.basic_block_list = std.ArrayList(*BasicBlock).init(&code.arena.allocator); + errdefer code.destroy(comp.a()); + + return Builder{ + .comp = comp, + .parsed_file = parsed_file, + .current_basic_block = undefined, + .code = code, + .next_debug_id = 0, + .is_comptime = false, + }; + } + + pub fn abort(self: *Builder) void { + self.code.destroy(self.comp.a()); + } + + /// Call code.destroy() when done + pub fn finish(self: *Builder) *Code { + return self.code; + } + + /// No need to clean up resources thanks to the arena allocator. + 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, + .debug_id = self.next_debug_id, + .scope = scope, + .instruction_list = std.ArrayList(*Instruction).init(self.arena()), + .child = null, + .parent = null, + .ref_instruction = null, + }); + self.next_debug_id += 1; + return basic_block; + } + + pub fn setCursorAtEndAndAppendBlock(self: *Builder, basic_block: *BasicBlock) !void { + try self.code.basic_block_list.append(basic_block); + self.setCursorAtEnd(basic_block); + } + + pub fn setCursorAtEnd(self: *Builder, basic_block: *BasicBlock) void { + self.current_basic_block = basic_block; + } + + pub fn genNode(irb: *Builder, node: *ast.Node, scope: *Scope, lval: LVal) Error!*Instruction { + switch (node.id) { + ast.Node.Id.Root => unreachable, + ast.Node.Id.Use => unreachable, + ast.Node.Id.TestDecl => unreachable, + ast.Node.Id.VarDecl => @panic("TODO"), + ast.Node.Id.Defer => @panic("TODO"), + ast.Node.Id.InfixOp => @panic("TODO"), + ast.Node.Id.PrefixOp => @panic("TODO"), + ast.Node.Id.SuffixOp => @panic("TODO"), + ast.Node.Id.Switch => @panic("TODO"), + ast.Node.Id.While => @panic("TODO"), + ast.Node.Id.For => @panic("TODO"), + ast.Node.Id.If => @panic("TODO"), + ast.Node.Id.ControlFlowExpression => return error.Unimplemented, + ast.Node.Id.Suspend => @panic("TODO"), + ast.Node.Id.VarType => @panic("TODO"), + ast.Node.Id.ErrorType => @panic("TODO"), + ast.Node.Id.FnProto => @panic("TODO"), + ast.Node.Id.PromiseType => @panic("TODO"), + ast.Node.Id.IntegerLiteral => @panic("TODO"), + ast.Node.Id.FloatLiteral => @panic("TODO"), + ast.Node.Id.StringLiteral => @panic("TODO"), + ast.Node.Id.MultilineStringLiteral => @panic("TODO"), + ast.Node.Id.CharLiteral => @panic("TODO"), + ast.Node.Id.BoolLiteral => @panic("TODO"), + ast.Node.Id.NullLiteral => @panic("TODO"), + ast.Node.Id.UndefinedLiteral => @panic("TODO"), + ast.Node.Id.ThisLiteral => @panic("TODO"), + ast.Node.Id.Unreachable => @panic("TODO"), + ast.Node.Id.Identifier => @panic("TODO"), + ast.Node.Id.GroupedExpression => { + const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", node); + return irb.genNode(grouped_expr.expr, scope, lval); + }, + ast.Node.Id.BuiltinCall => @panic("TODO"), + ast.Node.Id.ErrorSetDecl => @panic("TODO"), + ast.Node.Id.ContainerDecl => @panic("TODO"), + ast.Node.Id.Asm => @panic("TODO"), + ast.Node.Id.Comptime => @panic("TODO"), + ast.Node.Id.Block => { + const block = @fieldParentPtr(ast.Node.Block, "base", node); + return irb.lvalWrap(scope, try irb.genBlock(block, scope), lval); + }, + ast.Node.Id.DocComment => @panic("TODO"), + ast.Node.Id.SwitchCase => @panic("TODO"), + ast.Node.Id.SwitchElse => @panic("TODO"), + ast.Node.Id.Else => @panic("TODO"), + ast.Node.Id.Payload => @panic("TODO"), + ast.Node.Id.PointerPayload => @panic("TODO"), + ast.Node.Id.PointerIndexPayload => @panic("TODO"), + ast.Node.Id.StructField => @panic("TODO"), + ast.Node.Id.UnionTag => @panic("TODO"), + ast.Node.Id.EnumTag => @panic("TODO"), + ast.Node.Id.ErrorTag => @panic("TODO"), + ast.Node.Id.AsmInput => @panic("TODO"), + ast.Node.Id.AsmOutput => @panic("TODO"), + ast.Node.Id.AsyncAttribute => @panic("TODO"), + ast.Node.Id.ParamDecl => @panic("TODO"), + ast.Node.Id.FieldInitializer => @panic("TODO"), + } + } + + fn isCompTime(irb: *Builder, target_scope: *Scope) bool { + if (irb.is_comptime) + return true; + + var scope = target_scope; + while (true) { + switch (scope.id) { + Scope.Id.CompTime => return true, + Scope.Id.FnDef => return false, + Scope.Id.Decls => unreachable, + Scope.Id.Block, + Scope.Id.Defer, + Scope.Id.DeferExpr, + => scope = scope.parent orelse return false, + } + } + } + + pub fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Instruction { + const block_scope = try Scope.Block.create(irb.comp, parent_scope); + + const outer_block_scope = &block_scope.base; + 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 (block.statements.len == 0) { + // {} + return irb.buildConstVoid(child_scope, Span.token(block.lbrace), false); + } + + 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.is_comptime = try irb.buildConstBool( + parent_scope, + Span.token(block.lbrace), + irb.isCompTime(parent_scope), + ); + } + + var is_continuation_unreachable = false; + var noreturn_return_value: ?*Instruction = null; + + var stmt_it = block.statements.iterator(0); + while (stmt_it.next()) |statement_node_ptr| { + const statement_node = statement_node_ptr.*; + + if (statement_node.cast(ast.Node.Defer)) |defer_node| { + // defer starts a new scope + const defer_token = irb.parsed_file.tree.tokens.at(defer_node.defer_token); + const kind = switch (defer_token.id) { + Token.Id.Keyword_defer => Scope.Defer.Kind.ScopeExit, + Token.Id.Keyword_errdefer => Scope.Defer.Kind.ErrorExit, + else => unreachable, + }; + 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; + } + const statement_value = try irb.genNode(statement_node, child_scope, LVal.None); + + is_continuation_unreachable = statement_value.isNoReturn(); + if (is_continuation_unreachable) { + // keep the last noreturn statement value around in case we need to return it + noreturn_return_value = statement_value; + } + + if (statement_value.cast(Instruction.DeclVar)) |decl_var| { + // variable declarations start a new scope + child_scope = decl_var.params.variable.child_scope; + } else if (!is_continuation_unreachable) { + // this statement's value must be void + _ = irb.build( + Instruction.CheckVoidStmt, + child_scope, + statement_value.span, + Instruction.CheckVoidStmt.Params{ .target = statement_value }, + ); + } + } + + if (is_continuation_unreachable) { + assert(noreturn_return_value != null); + if (block.label == null or block_scope.incoming_blocks.len == 0) { + return noreturn_return_value.?; + } + + try irb.setCursorAtEndAndAppendBlock(block_scope.end_block); + return irb.build(Instruction.Phi, parent_scope, Span.token(block.rbrace), Instruction.Phi.Params{ + .incoming_blocks = block_scope.incoming_blocks.toOwnedSlice(), + .incoming_values = block_scope.incoming_values.toOwnedSlice(), + }); + } + + if (block.label) |label| { + try block_scope.incoming_blocks.append(irb.current_basic_block); + try block_scope.incoming_values.append( + try irb.buildConstVoid(parent_scope, Span.token(block.rbrace), true), + ); + _ = try irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit); + + _ = try irb.buildGen(Instruction.Br, parent_scope, Span.token(block.rbrace), Instruction.Br.Params{ + .dest_block = block_scope.end_block, + .is_comptime = block_scope.is_comptime, + }); + + try irb.setCursorAtEndAndAppendBlock(block_scope.end_block); + + return irb.build(Instruction.Phi, parent_scope, Span.token(block.rbrace), Instruction.Phi.Params{ + .incoming_blocks = block_scope.incoming_blocks.toOwnedSlice(), + .incoming_values = block_scope.incoming_values.toOwnedSlice(), + }); + } + + _ = try irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit); + return irb.buildConstVoid(child_scope, Span.token(block.rbrace), true); + } + + fn genDefersForBlock( + irb: *Builder, + inner_scope: *Scope, + outer_scope: *Scope, + gen_kind: Scope.Defer.Kind, + ) !bool { + var scope = inner_scope; + var is_noreturn = false; + while (true) { + switch (scope.id) { + Scope.Id.Defer => { + const defer_scope = @fieldParentPtr(Scope.Defer, "base", scope); + const generate = switch (defer_scope.kind) { + Scope.Defer.Kind.ScopeExit => true, + Scope.Defer.Kind.ErrorExit => gen_kind == Scope.Defer.Kind.ErrorExit, + }; + if (generate) { + const defer_expr_scope = defer_scope.defer_expr_scope; + const instruction = try irb.genNode( + defer_expr_scope.expr_node, + &defer_expr_scope.base, + LVal.None, + ); + if (instruction.isNoReturn()) { + is_noreturn = true; + } else { + _ = try irb.build( + Instruction.CheckVoidStmt, + &defer_expr_scope.base, + Span.token(defer_expr_scope.expr_node.lastToken()), + Instruction.CheckVoidStmt.Params{ .target = instruction }, + ); + } + } + }, + Scope.Id.FnDef, + Scope.Id.Decls, + => return is_noreturn, + + Scope.Id.CompTime, + Scope.Id.Block, + => scope = scope.parent orelse return is_noreturn, + + Scope.Id.DeferExpr => unreachable, + } + } + } + + pub fn lvalWrap(irb: *Builder, scope: *Scope, instruction: *Instruction, lval: LVal) !*Instruction { + switch (lval) { + LVal.None => return instruction, + LVal.Ptr => { + // We needed a pointer to a value, but we got a value. So we create + // an instruction which just makes a const pointer of it. + return irb.build(Instruction.Ref, scope, instruction.span, Instruction.Ref.Params{ + .target = instruction, + .mut = Type.Pointer.Mut.Const, + .volatility = Type.Pointer.Vol.Non, + }); + }, + } + } + + fn arena(self: *Builder) *Allocator { + return &self.code.arena.allocator; + } + + fn buildExtra( + self: *Builder, + comptime I: type, + scope: *Scope, + span: Span, + params: I.Params, + is_generated: bool, + ) !*Instruction { + const inst = try self.arena().create(I{ + .base = Instruction{ + .id = Instruction.typeToId(I), + .is_generated = is_generated, + .scope = scope, + .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.comp).base }, + IrVal.Init.Void => IrVal{ .KnownValue = &Value.Void.get(self.comp).base }, + }, + .ref_count = 0, + .span = span, + .child = null, + .parent = null, + }, + .params = params, + }); + + // Look at the params and ref() other instructions + comptime var i = 0; + inline while (i < @memberCount(I.Params)) : (i += 1) { + const FieldType = comptime @typeOf(@field(I.Params(undefined), @memberName(I.Params, i))); + switch (FieldType) { + *Instruction => @field(inst.params, @memberName(I.Params, i)).ref_count += 1, + ?*Instruction => if (@field(inst.params, @memberName(I.Params, i))) |other| other.ref_count += 1, + else => {}, + } + } + + self.next_debug_id += 1; + try self.current_basic_block.instruction_list.append(&inst.base); + return &inst.base; + } + + fn build( + self: *Builder, + comptime I: type, + scope: *Scope, + span: Span, + params: I.Params, + ) !*Instruction { + return self.buildExtra(I, scope, span, params, false); + } + + fn buildGen( + self: *Builder, + comptime I: type, + scope: *Scope, + span: Span, + params: I.Params, + ) !*Instruction { + return self.buildExtra(I, scope, span, params, true); + } + + 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.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.comp).base }; + return inst; + } +}; + +const Analyze = struct { + irb: Builder, + old_bb_index: usize, + const_predecessor_bb: ?*BasicBlock, + parent_basic_block: *BasicBlock, + instruction_index: usize, + src_implicit_return_type_list: std.ArrayList(*Instruction), + explicit_return_type: ?*Type, + + pub const Error = error{ + /// This is only for when we have already reported a compile error. It is the poison value. + SemanticAnalysisFailed, + + /// This is a placeholder - it is useful to use instead of panicking but once the compiler is + /// done this error code will be removed. + Unimplemented, + + OutOfMemory, + }; + + 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{ + .irb = irb, + .old_bb_index = 0, + .const_predecessor_bb = null, + .parent_basic_block = undefined, // initialized with startBasicBlock + .instruction_index = undefined, // initialized with startBasicBlock + .src_implicit_return_type_list = std.ArrayList(*Instruction).init(irb.arena()), + .explicit_return_type = explicit_return_type, + }; + } + + pub fn abort(self: *Analyze) void { + self.irb.abort(); + } + + pub fn getNewBasicBlock(self: *Analyze, old_bb: *BasicBlock, ref_old_instruction: ?*Instruction) !*BasicBlock { + if (old_bb.child) |child| { + if (ref_old_instruction == null or child.ref_instruction != ref_old_instruction) + return child; + } + + const new_bb = try self.irb.createBasicBlock(old_bb.scope, old_bb.name_hint); + new_bb.linkToParent(old_bb); + new_bb.ref_instruction = ref_old_instruction; + return new_bb; + } + + pub fn startBasicBlock(self: *Analyze, old_bb: *BasicBlock, const_predecessor_bb: ?*BasicBlock) void { + self.instruction_index = 0; + self.parent_basic_block = old_bb; + self.const_predecessor_bb = const_predecessor_bb; + } + + pub fn finishBasicBlock(ira: *Analyze, old_code: *Code) !void { + try ira.irb.code.basic_block_list.append(ira.irb.current_basic_block); + ira.instruction_index += 1; + + while (ira.instruction_index < ira.parent_basic_block.instruction_list.len) { + const next_instruction = ira.parent_basic_block.instruction_list.at(ira.instruction_index); + + if (!next_instruction.is_generated) { + try ira.addCompileError(next_instruction.span, "unreachable code"); + break; + } + ira.instruction_index += 1; + } + + ira.old_bb_index += 1; + + var need_repeat = true; + while (true) { + while (ira.old_bb_index < old_code.basic_block_list.len) { + const old_bb = old_code.basic_block_list.at(ira.old_bb_index); + const new_bb = old_bb.child orelse { + ira.old_bb_index += 1; + continue; + }; + if (new_bb.instruction_list.len != 0) { + ira.old_bb_index += 1; + continue; + } + ira.irb.current_basic_block = new_bb; + + ira.startBasicBlock(old_bb, null); + return; + } + if (!need_repeat) + return; + need_repeat = false; + ira.old_bb_index = 0; + continue; + } + } + + fn addCompileError(self: *Analyze, span: Span, comptime fmt: []const u8, args: ...) !void { + 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.comp).base; + } + + fn implicitCast(self: *Analyze, target: *Instruction, optional_dest_type: ?*Type) Analyze.Error!*Instruction { + const dest_type = optional_dest_type orelse return target; + @panic("TODO implicitCast"); + } + + fn getCompTimeValOrNullUndefOk(self: *Analyze, target: *Instruction) ?*Value { + @panic("TODO getCompTimeValOrNullUndefOk"); + } + + fn getCompTimeRef( + self: *Analyze, + value: *Value, + ptr_mut: Value.Ptr.Mut, + mut: Type.Pointer.Mut, + volatility: Type.Pointer.Vol, + ptr_align: u32, + ) Analyze.Error!*Instruction { + @panic("TODO getCompTimeRef"); + } +}; + +pub async fn gen( + comp: *Compilation, + body_node: *ast.Node, + scope: *Scope, + end_span: Span, + parsed_file: *ParsedFile, +) !*Code { + var irb = try Builder.init(comp, parsed_file); + errdefer irb.abort(); + + const entry_block = try irb.createBasicBlock(scope, "Entry"); + entry_block.ref(); // Entry block gets a reference because we enter it to begin. + try irb.setCursorAtEndAndAppendBlock(entry_block); + + const result = try irb.genNode(body_node, scope, LVal.None); + if (!result.isNoReturn()) { + _ = irb.buildGen( + Instruction.AddImplicitReturnType, + scope, + end_span, + Instruction.AddImplicitReturnType.Params{ .target = result }, + ); + _ = irb.buildGen( + Instruction.Return, + scope, + end_span, + Instruction.Return.Params{ .return_value = result }, + ); + } + + return irb.finish(); +} + +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); + + const new_entry_bb = try ira.getNewBasicBlock(old_entry_bb, null); + new_entry_bb.ref(); + + ira.irb.current_basic_block = new_entry_bb; + + ira.startBasicBlock(old_entry_bb, null); + + while (ira.old_bb_index < old_code.basic_block_list.len) { + const old_instruction = ira.parent_basic_block.instruction_list.at(ira.instruction_index); + + if (old_instruction.ref_count == 0 and !old_instruction.hasSideEffects()) { + ira.instruction_index += 1; + continue; + } + + const return_inst = try old_instruction.analyze(&ira); + // 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 + + if (return_inst.isNoReturn()) { + try ira.finishBasicBlock(old_code); + continue; + } + + ira.instruction_index += 1; + } + + if (ira.src_implicit_return_type_list.len == 0) { + ira.irb.code.return_type = &Type.NoReturn.get(comp).base; + return ira.irb.finish(); + } + + ira.irb.code.return_type = try ira.resolvePeerTypes(expected_type, ira.src_implicit_return_type_list.toSliceConst()); + return ira.irb.finish(); +} diff --git a/src-self-hosted/llvm.zig b/src-self-hosted/llvm.zig index 391a92cd63..b815f75b05 100644 --- a/src-self-hosted/llvm.zig +++ b/src-self-hosted/llvm.zig @@ -2,10 +2,27 @@ const builtin = @import("builtin"); const c = @import("c.zig"); const assert = @import("std").debug.assert; -pub const ValueRef = removeNullability(c.LLVMValueRef); -pub const ModuleRef = removeNullability(c.LLVMModuleRef); -pub const ContextRef = removeNullability(c.LLVMContextRef); 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 AddFunction = c.LLVMAddFunction; +pub const CreateBuilderInContext = c.LLVMCreateBuilderInContext; +pub const DisposeBuilder = c.LLVMDisposeBuilder; +pub const DisposeModule = c.LLVMDisposeModule; +pub const DumpModule = c.LLVMDumpModule; +pub const ModuleCreateWithNameInContext = c.LLVMModuleCreateWithNameInContext; +pub const VoidTypeInContext = c.LLVMVoidTypeInContext; + +pub const FunctionType = LLVMFunctionType; +extern fn LLVMFunctionType( + ReturnType: TypeRef, + ParamTypes: [*]TypeRef, + ParamCount: c_uint, + IsVarArg: c_int, +) ?TypeRef; fn removeNullability(comptime T: type) type { comptime assert(@typeId(T) == builtin.TypeId.Optional); diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index fe94a4460a..c9478954c5 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -14,7 +14,8 @@ const c = @import("c.zig"); const introspect = @import("introspect.zig"); const Args = arg.Args; const Flag = arg.Flag; -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"); @@ -257,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(); @@ -299,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; } }; @@ -369,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); } @@ -386,9 +387,13 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Mo var loop: event.Loop = undefined; try loop.initMultiThreaded(allocator); + defer loop.deinit(); - var module = try Module.create( - &loop, + var event_loop_local = EventLoopLocal.init(&loop); + defer event_loop_local.deinit(); + + var comp = try Compilation.create( + &event_loop_local, root_name, root_source_file, Target.Native, @@ -397,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(); @@ -417,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"); @@ -469,54 +474,54 @@ 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, true); + 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, watch: bool) void { - while (watch) { - // TODO directly awaiting async should guarantee memory allocation elision - const build_event = await (async module.events.get() catch unreachable); +async fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void { + // TODO directly awaiting async should guarantee memory allocation elision + const build_event = await (async comp.events.get() catch unreachable); - switch (build_event) { - Module.Event.Ok => { - std.debug.warn("Build succeeded\n"); - return; - }, - Module.Event.Error => |err| { - std.debug.warn("build failed: {}\n", @errorName(err)); - @panic("TODO error return trace"); - }, - Module.Event.Fail => |errs| { - @panic("TODO print compile error messages"); - }, - } + switch (build_event) { + Compilation.Event.Ok => { + std.debug.warn("Build succeeded\n"); + return; + }, + Compilation.Event.Error => |err| { + std.debug.warn("build failed: {}\n", @errorName(err)); + os.exit(1); + }, + Compilation.Event.Fail => |msgs| { + for (msgs) |msg| { + errmsg.printToFile(&stderr_file, msg, color) catch os.exit(1); + } + }, } } 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 = @@ -527,6 +532,7 @@ const usage_fmt = \\Options: \\ --help Print this help and exit \\ --color [auto|off|on] Enable or disable colored error messages + \\ --stdin Format code from stdin \\ \\ ; @@ -538,6 +544,7 @@ const args_fmt_spec = []Flag{ "off", "on", }), + Flag.Bool("--stdin"), }; const Fmt = struct { @@ -579,11 +586,6 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { os.exit(0); } - if (flags.positionals.len == 0) { - try stderr.write("expected at least one source file argument\n"); - os.exit(1); - } - const color = blk: { if (flags.single("color")) |color_flag| { if (mem.eql(u8, color_flag, "auto")) { @@ -598,6 +600,44 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { } }; + if (flags.present("stdin")) { + if (flags.positionals.len != 0) { + try stderr.write("cannot use --stdin with positional arguments\n"); + os.exit(1); + } + + var stdin_file = try io.getStdIn(); + var stdin = io.FileInStream.init(&stdin_file); + + const source_code = try stdin.stream.readAllAlloc(allocator, @maxValue(usize)); + defer allocator.free(source_code); + + var tree = std.zig.parse(allocator, source_code) catch |err| { + try stderr.print("error parsing stdin: {}\n", err); + os.exit(1); + }; + defer tree.deinit(); + + var error_it = tree.errors.iterator(0); + while (error_it.next()) |parse_error| { + const msg = try errmsg.createFromParseError(allocator, parse_error, &tree, ""); + defer allocator.destroy(msg); + + try errmsg.printToFile(&stderr_file, msg, color); + } + if (tree.errors.len != 0) { + os.exit(1); + } + + _ = try std.zig.render(allocator, stdout, &tree); + return; + } + + if (flags.positionals.len == 0) { + try stderr.write("expected at least one source file argument\n"); + os.exit(1); + } + var fmt = Fmt{ .seen = std.HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8).init(allocator), .queue = std.LinkedList([]const u8).init(), diff --git a/src-self-hosted/module.zig b/src-self-hosted/module.zig deleted file mode 100644 index 5ce1a7965a..0000000000 --- a/src-self-hosted/module.zig +++ /dev/null @@ -1,579 +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; - -pub const Module = struct { - loop: *event.Loop, - name: Buffer, - root_src_path: ?[]const u8, - module: llvm.ModuleRef, - context: llvm.ContextRef, - builder: llvm.BuilderRef, - 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), - - // 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, - }; - - pub const Event = union(enum) { - Ok, - Fail: []errmsg.Msg, - Error: BuildError, - }; - - 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( - loop: *event.Loop, - 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 { - var name_buffer = try Buffer.init(loop.allocator, name); - errdefer name_buffer.deinit(); - - const context = c.LLVMContextCreate() orelse return error.OutOfMemory; - errdefer c.LLVMContextDispose(context); - - const module = c.LLVMModuleCreateWithNameInContext(name_buffer.ptr(), context) orelse return error.OutOfMemory; - errdefer c.LLVMDisposeModule(module); - - const builder = c.LLVMCreateBuilderInContext(context) orelse return error.OutOfMemory; - errdefer c.LLVMDisposeBuilder(builder); - - const events = try event.Channel(Event).create(loop, 0); - errdefer events.destroy(); - - return loop.allocator.create(Module{ - .loop = loop, - .events = events, - .name = name_buffer, - .root_src_path = root_src_path, - .module = module, - .context = context, - .builder = builder, - .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)), - }); - } - - fn dump(self: *Module) void { - c.LLVMDumpModule(self.module); - } - - pub fn destroy(self: *Module) void { - self.events.destroy(); - c.LLVMDisposeBuilder(self.builder); - c.LLVMDisposeModule(self.module); - c.LLVMContextDispose(self.context); - 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 - (await (async self.addRootSrc() catch unreachable)) catch |err| { - await (async self.events.put(Event{ .Error = err }) catch unreachable); - return; - }; - await (async self.events.put(Event.Ok) 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); - - var parsed_file = ParsedFile{ - .tree = try std.zig.parse(self.a(), source_code), - .realpath = root_src_real_path, - }; - errdefer parsed_file.tree.deinit(); - - const tree = &parsed_file.tree; - - // create empty struct for it - const decls = try Scope.Decls.create(self.a(), null); - errdefer decls.destroy(); - - 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 { - @panic("TODO add compile error"); - //try self.addCompileError( - // &parsed_file, - // fn_proto.fn_token, - // 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 = Decl.Resolution.Unresolved, - }, - .value = Decl.Fn.Val{ .Unresolved = {} }, - .fn_proto = fn_proto, - }); - errdefer self.a().destroy(fn_decl); - - // TODO make this parallel - try await try async self.addTopLevelDecl(tree, &fn_decl.base); - }, - ast.Node.Id.TestDecl => @panic("TODO"), - else => unreachable, - } - } - } - - async fn addTopLevelDecl(self: *Module, tree: *ast.Tree, decl: *Decl) !void { - const is_export = decl.isExported(tree); - - { - const exported_symbol_names = await try async self.exported_symbol_names.acquire(); - defer exported_symbol_names.release(); - - if (try exported_symbol_names.value.put(decl.name, decl)) |other_decl| { - @panic("TODO report compile error"); - } - } - } - - 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; - } -} - -pub const Scope = struct { - id: Id, - parent: ?*Scope, - - pub const Id = enum { - Decls, - Block, - }; - - pub const Decls = struct { - base: Scope, - table: Decl.Table, - - pub fn create(a: *Allocator, parent: ?*Scope) !*Decls { - const self = try a.create(Decls{ - .base = Scope{ - .id = Id.Decls, - .parent = parent, - }, - .table = undefined, - }); - errdefer a.destroy(self); - - self.table = Decl.Table.init(a); - errdefer self.table.deinit(); - - return self; - } - - pub fn destroy(self: *Decls) void { - self.table.deinit(); - self.table.allocator.destroy(self); - self.* = undefined; - } - }; - - pub const Block = struct { - base: Scope, - }; -}; - -pub const Visib = enum { - Private, - Pub, -}; - -pub const Decl = struct { - id: Id, - name: []const u8, - visib: Visib, - resolution: Resolution, - - pub const Table = std.HashMap([]const u8, *Decl, mem.hash_slice_u8, mem.eql_slice_u8); - - pub fn isExported(base: *const Decl, tree: *ast.Tree) bool { - switch (base.id) { - Id.Fn => { - const fn_decl = @fieldParentPtr(Fn, "base", base); - return fn_decl.isExported(tree); - }, - else => return false, - } - } - - pub const Resolution = enum { - Unresolved, - InProgress, - Invalid, - Ok, - }; - - pub const Id = enum { - Var, - Fn, - CompTime, - }; - - pub const Var = struct { - base: Decl, - }; - - pub const Fn = struct { - base: Decl, - value: Val, - fn_proto: *const ast.Node.FnProto, - - // TODO https://github.com/ziglang/zig/issues/683 and then make this anonymous - pub const Val = union { - Unresolved: void, - Ok: *Value.Fn, - }; - - pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 { - return if (self.fn_proto.extern_export_inline_token) |tok_index| x: { - const token = tree.tokens.at(tok_index); - break :x switch (token.id) { - Token.Id.Extern => tree.tokenSlicePtr(token), - else => null, - }; - } else null; - } - - pub fn isExported(self: Fn, tree: *ast.Tree) bool { - if (self.fn_proto.extern_export_inline_token) |tok_index| { - const token = tree.tokens.at(tok_index); - return token.id == Token.Id.Keyword_export; - } else { - return false; - } - } - }; - - pub const CompTime = struct { - base: Decl, - }; -}; - -pub const Value = struct { - pub const Fn = struct {}; -}; - -pub const Type = struct { - id: Id, - - pub const Id = enum { - Type, - Void, - Bool, - NoReturn, - Int, - Float, - Pointer, - Array, - Struct, - ComptimeFloat, - ComptimeInt, - Undefined, - Null, - Optional, - ErrorUnion, - ErrorSet, - Enum, - Union, - Fn, - Opaque, - Promise, - }; - - pub const Struct = struct { - base: Type, - decls: *Scope.Decls, - }; -}; - -pub const ParsedFile = struct { - tree: ast.Tree, - realpath: []const u8, -}; diff --git a/src-self-hosted/parsed_file.zig b/src-self-hosted/parsed_file.zig new file mode 100644 index 0000000000..d728c2fd18 --- /dev/null +++ b/src-self-hosted/parsed_file.zig @@ -0,0 +1,6 @@ +const ast = @import("std").zig.ast; + +pub const ParsedFile = struct { + tree: ast.Tree, + realpath: []const u8, +}; diff --git a/src-self-hosted/scope.zig b/src-self-hosted/scope.zig index b73dcb4ed3..6fd6456b12 100644 --- a/src-self-hosted/scope.zig +++ b/src-self-hosted/scope.zig @@ -1,16 +1,234 @@ +const std = @import("std"); +const Allocator = mem.Allocator; +const Decl = @import("decl.zig").Decl; +const Compilation = @import("compilation.zig").Compilation; +const mem = std.mem; +const ast = std.zig.ast; +const Value = @import("value.zig").Value; +const ir = @import("ir.zig"); + pub const Scope = struct { id: Id, - parent: *Scope, + parent: ?*Scope, + ref_count: usize, + + pub fn ref(base: *Scope) void { + base.ref_count += 1; + } + + pub fn deref(base: *Scope, comp: *Compilation) void { + base.ref_count -= 1; + if (base.ref_count == 0) { + if (base.parent) |parent| parent.deref(comp); + switch (base.id) { + Id.Decls => @fieldParentPtr(Decls, "base", base).destroy(), + 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), + } + } + } + + pub fn findFnDef(base: *Scope) ?*FnDef { + var scope = base; + while (true) { + switch (scope.id) { + Id.FnDef => return @fieldParentPtr(FnDef, "base", base), + Id.Decls => return null, + + Id.Block, + Id.Defer, + Id.DeferExpr, + Id.CompTime, + => scope = scope.parent orelse return null, + } + } + } pub const Id = enum { Decls, Block, - Defer, - DeferExpr, - VarDecl, - CImport, - Loop, FnDef, CompTime, + Defer, + DeferExpr, + }; + + pub const Decls = struct { + base: Scope, + table: Decl.Table, + + /// Creates a Decls scope with 1 reference + pub fn create(comp: *Compilation, parent: ?*Scope) !*Decls { + const self = try comp.a().create(Decls{ + .base = Scope{ + .id = Id.Decls, + .parent = parent, + .ref_count = 1, + }, + .table = undefined, + }); + errdefer comp.a().destroy(self); + + self.table = Decl.Table.init(comp.a()); + errdefer self.table.deinit(); + + if (parent) |p| p.ref(); + + return self; + } + + pub fn destroy(self: *Decls) void { + self.table.deinit(); + self.table.allocator.destroy(self); + } + }; + + pub const Block = struct { + base: Scope, + incoming_values: std.ArrayList(*ir.Instruction), + incoming_blocks: std.ArrayList(*ir.BasicBlock), + end_block: *ir.BasicBlock, + is_comptime: *ir.Instruction, + + /// Creates a Block scope with 1 reference + pub fn create(comp: *Compilation, parent: ?*Scope) !*Block { + const self = try comp.a().create(Block{ + .base = Scope{ + .id = Id.Block, + .parent = parent, + .ref_count = 1, + }, + .incoming_values = undefined, + .incoming_blocks = undefined, + .end_block = undefined, + .is_comptime = undefined, + }); + errdefer comp.a().destroy(self); + + if (parent) |p| p.ref(); + return self; + } + + pub fn destroy(self: *Block, comp: *Compilation) void { + comp.a().destroy(self); + } + }; + + pub const FnDef = struct { + base: Scope, + + /// This reference is not counted so that the scope can get destroyed with the function + 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.a().create(FnDef{ + .base = Scope{ + .id = Id.FnDef, + .parent = parent, + .ref_count = 1, + }, + .fn_val = undefined, + }); + + if (parent) |p| p.ref(); + + return self; + } + + pub fn destroy(self: *FnDef, comp: *Compilation) void { + comp.a().destroy(self); + } + }; + + pub const CompTime = struct { + base: Scope, + + /// Creates a CompTime scope with 1 reference + pub fn create(comp: *Compilation, parent: ?*Scope) !*CompTime { + const self = try comp.a().create(CompTime{ + .base = Scope{ + .id = Id.CompTime, + .parent = parent, + .ref_count = 1, + }, + }); + + if (parent) |p| p.ref(); + return self; + } + + pub fn destroy(self: *CompTime, comp: *Compilation) void { + comp.a().destroy(self); + } + }; + + pub const Defer = struct { + base: Scope, + defer_expr_scope: *DeferExpr, + kind: Kind, + + pub const Kind = enum { + ScopeExit, + ErrorExit, + }; + + /// Creates a Defer scope with 1 reference + pub fn create( + comp: *Compilation, + parent: ?*Scope, + kind: Kind, + defer_expr_scope: *DeferExpr, + ) !*Defer { + const self = try comp.a().create(Defer{ + .base = Scope{ + .id = Id.Defer, + .parent = parent, + .ref_count = 1, + }, + .defer_expr_scope = defer_expr_scope, + .kind = kind, + }); + errdefer comp.a().destroy(self); + + defer_expr_scope.base.ref(); + + if (parent) |p| p.ref(); + return self; + } + + pub fn destroy(self: *Defer, comp: *Compilation) void { + self.defer_expr_scope.base.deref(comp); + comp.a().destroy(self); + } + }; + + pub const DeferExpr = struct { + base: Scope, + expr_node: *ast.Node, + + /// 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{ + .base = Scope{ + .id = Id.DeferExpr, + .parent = parent, + .ref_count = 1, + }, + .expr_node = expr_node, + }); + errdefer comp.a().destroy(self); + + if (parent) |p| p.ref(); + return 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 new file mode 100644 index 0000000000..3edb267ca9 --- /dev/null +++ b/src-self-hosted/test.zig @@ -0,0 +1,168 @@ +const std = @import("std"); +const mem = std.mem; +const builtin = @import("builtin"); +const Target = @import("target.zig").Target; +const Compilation = @import("compilation.zig").Compilation; +const introspect = @import("introspect.zig"); +const assertOrPanic = std.debug.assertOrPanic; +const errmsg = @import("errmsg.zig"); +const EventLoopLocal = @import("compilation.zig").EventLoopLocal; + +test "compile errors" { + var ctx: TestContext = undefined; + try ctx.init(); + defer ctx.deinit(); + + try @import("../test/stage2/compile_errors.zig").addCases(&ctx); + + try ctx.run(); +} + +const file1 = "1.zig"; +const allocator = std.heap.c_allocator; + +pub const TestContext = struct { + loop: std.event.Loop, + event_loop_local: EventLoopLocal, + zig_lib_dir: []u8, + zig_cache_dir: []u8, + file_index: std.atomic.Int(usize), + group: std.event.Group(error!void), + any_err: error!void, + + const tmp_dir_name = "stage2_test_tmp"; + + fn init(self: *TestContext) !void { + self.* = TestContext{ + .any_err = {}, + .loop = undefined, + .event_loop_local = undefined, + .zig_lib_dir = undefined, + .zig_cache_dir = undefined, + .group = undefined, + .file_index = std.atomic.Int(usize).init(0), + }; + + try self.loop.initMultiThreaded(allocator); + errdefer self.loop.deinit(); + + self.event_loop_local = EventLoopLocal.init(&self.loop); + errdefer self.event_loop_local.deinit(); + + self.group = std.event.Group(error!void).init(&self.loop); + errdefer self.group.cancelAll(); + + self.zig_lib_dir = try introspect.resolveZigLibDir(allocator); + errdefer allocator.free(self.zig_lib_dir); + + self.zig_cache_dir = try introspect.resolveZigCacheDir(allocator); + errdefer allocator.free(self.zig_cache_dir); + + try std.os.makePath(allocator, tmp_dir_name); + errdefer std.os.deleteTree(allocator, tmp_dir_name) catch {}; + } + + fn deinit(self: *TestContext) void { + std.os.deleteTree(allocator, tmp_dir_name) catch {}; + allocator.free(self.zig_cache_dir); + allocator.free(self.zig_lib_dir); + self.event_loop_local.deinit(); + self.loop.deinit(); + } + + fn run(self: *TestContext) !void { + const handle = try self.loop.call(waitForGroup, self); + defer cancel handle; + self.loop.run(); + return self.any_err; + } + + async fn waitForGroup(self: *TestContext) void { + self.any_err = await (async self.group.wait() catch unreachable); + } + + fn testCompileError( + self: *TestContext, + source: []const u8, + path: []const u8, + line: usize, + column: usize, + msg: []const u8, + ) !void { + var file_index_buf: [20]u8 = undefined; + const file_index = try std.fmt.bufPrint(file_index_buf[0..], "{}", self.file_index.incr()); + const file1_path = try std.os.path.join(allocator, tmp_dir_name, file_index, file1); + + if (std.os.path.dirname(file1_path)) |dirname| { + try std.os.makePath(allocator, dirname); + } + + // TODO async I/O + try std.io.writeFile(allocator, file1_path, source); + + var comp = try Compilation.create( + &self.event_loop_local, + "test", + file1_path, + Target.Native, + Compilation.Kind.Obj, + builtin.Mode.Debug, + self.zig_lib_dir, + self.zig_cache_dir, + ); + errdefer comp.destroy(); + + try comp.build(); + + try self.group.call(getModuleEvent, comp, source, path, line, column, msg); + } + + async fn getModuleEvent( + comp: *Compilation, + source: []const u8, + path: []const u8, + line: usize, + column: usize, + text: []const u8, + ) !void { + defer comp.destroy(); + const build_event = await (async comp.events.get() catch unreachable); + + switch (build_event) { + Compilation.Event.Ok => { + @panic("build incorrectly succeeded"); + }, + Compilation.Event.Error => |err| { + @panic("build incorrectly failed"); + }, + 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)) { + const first_token = msg.tree.tokens.at(msg.span.first); + const last_token = msg.tree.tokens.at(msg.span.first); + const start_loc = msg.tree.tokenLocationPtr(0, first_token); + if (start_loc.line + 1 == line and start_loc.column + 1 == column) { + return; + } + } + } + std.debug.warn( + "\n=====source:=======\n{}\n====expected:========\n{}:{}:{}: error: {}\n", + source, + path, + line, + column, + text, + ); + std.debug.warn("\n====found:========\n"); + var stderr = try std.io.getStdErr(); + for (msgs) |msg| { + try errmsg.printToFile(&stderr, msg, errmsg.Color.Auto); + } + std.debug.warn("============\n"); + return error.TestFailed; + }, + } + } +}; diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig new file mode 100644 index 0000000000..8349047749 --- /dev/null +++ b/src-self-hosted/type.zig @@ -0,0 +1,442 @@ +const std = @import("std"); +const builtin = @import("builtin"); +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; + +pub const Type = struct { + base: Value, + id: Id, + + pub const Id = builtin.TypeId; + + pub fn destroy(base: *Type, comp: *Compilation) void { + switch (base.id) { + 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, ofile: *ObjectFile) (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.Type => unreachable, + Id.Void => unreachable, + Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmType(ofile), + 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.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.Namespace => unreachable, + Id.Block => unreachable, + Id.BoundFn => return @fieldParentPtr(BoundFn, "base", base).getLlvmType(ofile), + Id.ArgTuple => unreachable, + Id.Opaque => return @fieldParentPtr(Opaque, "base", base).getLlvmType(ofile), + Id.Promise => return @fieldParentPtr(Promise, "base", base).getLlvmType(ofile), + } + } + + pub fn dump(base: *const Type) void { + std.debug.warn("{}", @tagName(base.id)); + } + + pub fn getAbiAlignment(base: *Type, comp: *Compilation) u32 { + @panic("TODO getAbiAlignment"); + } + + pub const Struct = struct { + base: Type, + decls: *Scope.Decls, + + pub fn destroy(self: *Struct, comp: *Compilation) void { + comp.a().destroy(self); + } + + pub fn getLlvmType(self: *Struct, ofile: *ObjectFile) llvm.TypeRef { + @panic("TODO"); + } + }; + + pub const Fn = struct { + base: Type, + return_type: *Type, + params: []Param, + is_var_args: bool, + + pub const Param = struct { + is_noalias: bool, + typeof: *Type, + }; + + 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(comp).base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .id = builtin.TypeId.Fn, + }, + .return_type = return_type, + .params = params, + .is_var_args = is_var_args, + }); + errdefer comp.a().destroy(result); + + result.return_type.base.ref(); + for (result.params) |param| { + param.typeof.base.ref(); + } + return result; + } + + pub fn destroy(self: *Fn, comp: *Compilation) void { + self.return_type.base.deref(comp); + for (self.params) |param| { + param.typeof.base.deref(comp); + } + comp.a().destroy(self); + } + + pub fn getLlvmType(self: *Fn, ofile: *ObjectFile) !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), + }; + 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(ofile); + } + + return llvm.FunctionType( + llvm_return_type, + llvm_param_types.ptr, + @intCast(c_uint, llvm_param_types.len), + @boolToInt(self.is_var_args), + ) orelse error.OutOfMemory; + } + }; + + pub const MetaType = struct { + base: Type, + value: *Type, + + /// Adds 1 reference to the resulting type + pub fn get(comp: *Compilation) *MetaType { + comp.meta_type.base.base.ref(); + return comp.meta_type; + } + + pub fn destroy(self: *MetaType, comp: *Compilation) void { + comp.a().destroy(self); + } + }; + + pub const Void = struct { + base: Type, + + /// Adds 1 reference to the resulting type + pub fn get(comp: *Compilation) *Void { + comp.void_type.base.base.ref(); + return comp.void_type; + } + + pub fn destroy(self: *Void, comp: *Compilation) void { + comp.a().destroy(self); + } + }; + + pub const Bool = struct { + base: Type, + + /// Adds 1 reference to the resulting type + pub fn get(comp: *Compilation) *Bool { + comp.bool_type.base.base.ref(); + return comp.bool_type; + } + + pub fn destroy(self: *Bool, comp: *Compilation) void { + comp.a().destroy(self); + } + + pub fn getLlvmType(self: *Bool, ofile: *ObjectFile) llvm.TypeRef { + @panic("TODO"); + } + }; + + pub const NoReturn = struct { + base: Type, + + /// Adds 1 reference to the resulting type + pub fn get(comp: *Compilation) *NoReturn { + comp.noreturn_type.base.base.ref(); + return comp.noreturn_type; + } + + pub fn destroy(self: *NoReturn, comp: *Compilation) void { + comp.a().destroy(self); + } + }; + + pub const Int = struct { + base: Type, + + pub fn destroy(self: *Int, comp: *Compilation) void { + comp.a().destroy(self); + } + + pub fn getLlvmType(self: *Int, ofile: *ObjectFile) llvm.TypeRef { + @panic("TODO"); + } + }; + + pub const Float = struct { + base: Type, + + pub fn destroy(self: *Float, comp: *Compilation) void { + comp.a().destroy(self); + } + + pub fn getLlvmType(self: *Float, ofile: *ObjectFile) llvm.TypeRef { + @panic("TODO"); + } + }; + pub const Pointer = struct { + base: Type, + mut: Mut, + vol: Vol, + size: Size, + alignment: u32, + + pub const Mut = enum { + Mut, + Const, + }; + pub const Vol = enum { + Non, + Volatile, + }; + pub const Size = builtin.TypeInfo.Pointer.Size; + + pub fn destroy(self: *Pointer, comp: *Compilation) void { + comp.a().destroy(self); + } + + pub fn get( + comp: *Compilation, + elem_type: *Type, + mut: Mut, + vol: Vol, + size: Size, + alignment: u32, + ) *Pointer { + @panic("TODO get pointer"); + } + + pub fn getLlvmType(self: *Pointer, ofile: *ObjectFile) llvm.TypeRef { + @panic("TODO"); + } + }; + + pub const Array = struct { + base: Type, + + pub fn destroy(self: *Array, comp: *Compilation) void { + comp.a().destroy(self); + } + + pub fn getLlvmType(self: *Array, ofile: *ObjectFile) llvm.TypeRef { + @panic("TODO"); + } + }; + + pub const ComptimeFloat = struct { + base: Type, + + pub fn destroy(self: *ComptimeFloat, comp: *Compilation) void { + comp.a().destroy(self); + } + }; + + pub const ComptimeInt = struct { + base: Type, + + pub fn destroy(self: *ComptimeInt, comp: *Compilation) void { + comp.a().destroy(self); + } + }; + + pub const Undefined = struct { + base: Type, + + pub fn destroy(self: *Undefined, comp: *Compilation) void { + comp.a().destroy(self); + } + }; + + pub const Null = struct { + base: Type, + + pub fn destroy(self: *Null, comp: *Compilation) void { + comp.a().destroy(self); + } + }; + + pub const Optional = struct { + base: Type, + + pub fn destroy(self: *Optional, comp: *Compilation) void { + comp.a().destroy(self); + } + + pub fn getLlvmType(self: *Optional, ofile: *ObjectFile) llvm.TypeRef { + @panic("TODO"); + } + }; + + pub const ErrorUnion = struct { + base: Type, + + pub fn destroy(self: *ErrorUnion, comp: *Compilation) void { + comp.a().destroy(self); + } + + pub fn getLlvmType(self: *ErrorUnion, ofile: *ObjectFile) llvm.TypeRef { + @panic("TODO"); + } + }; + + pub const ErrorSet = struct { + base: Type, + + pub fn destroy(self: *ErrorSet, comp: *Compilation) void { + comp.a().destroy(self); + } + + pub fn getLlvmType(self: *ErrorSet, ofile: *ObjectFile) llvm.TypeRef { + @panic("TODO"); + } + }; + + pub const Enum = struct { + base: Type, + + pub fn destroy(self: *Enum, comp: *Compilation) void { + comp.a().destroy(self); + } + + pub fn getLlvmType(self: *Enum, ofile: *ObjectFile) llvm.TypeRef { + @panic("TODO"); + } + }; + + pub const Union = struct { + base: Type, + + pub fn destroy(self: *Union, comp: *Compilation) void { + comp.a().destroy(self); + } + + pub fn getLlvmType(self: *Union, ofile: *ObjectFile) llvm.TypeRef { + @panic("TODO"); + } + }; + + pub const Namespace = struct { + base: Type, + + pub fn destroy(self: *Namespace, comp: *Compilation) void { + comp.a().destroy(self); + } + }; + + pub const Block = struct { + base: Type, + + pub fn destroy(self: *Block, comp: *Compilation) void { + comp.a().destroy(self); + } + }; + + pub const BoundFn = struct { + base: Type, + + pub fn destroy(self: *BoundFn, comp: *Compilation) void { + comp.a().destroy(self); + } + + pub fn getLlvmType(self: *BoundFn, ofile: *ObjectFile) llvm.TypeRef { + @panic("TODO"); + } + }; + + pub const ArgTuple = struct { + base: Type, + + pub fn destroy(self: *ArgTuple, comp: *Compilation) void { + comp.a().destroy(self); + } + }; + + pub const Opaque = struct { + base: Type, + + pub fn destroy(self: *Opaque, comp: *Compilation) void { + comp.a().destroy(self); + } + + pub fn getLlvmType(self: *Opaque, ofile: *ObjectFile) llvm.TypeRef { + @panic("TODO"); + } + }; + + pub const Promise = struct { + base: Type, + + pub fn destroy(self: *Promise, comp: *Compilation) void { + comp.a().destroy(self); + } + + 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 new file mode 100644 index 0000000000..8c047b1513 --- /dev/null +++ b/src-self-hosted/value.zig @@ -0,0 +1,154 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const Scope = @import("scope.zig").Scope; +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 +pub const Value = struct { + id: Id, + typeof: *Type, + ref_count: std.atomic.Int(usize), + + /// Thread-safe + pub fn ref(base: *Value) void { + _ = base.ref_count.incr(); + } + + /// Thread-safe + pub fn deref(base: *Value, comp: *Compilation) void { + if (base.ref_count.decr() == 1) { + base.typeof.base.deref(comp); + switch (base.id) { + 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), + } + } + } + + pub fn getRef(base: *Value) *Value { + base.ref(); + return base; + } + + pub fn dump(base: *const Value) void { + std.debug.warn("{}", @tagName(base.id)); + } + + pub const Id = enum { + Type, + Fn, + Void, + Bool, + NoReturn, + Ptr, + }; + + pub const Type = @import("type.zig").Type; + + pub const Fn = struct { + base: Value, + + /// The main external name that is used in the .o file. + /// TODO https://github.com/ziglang/zig/issues/265 + symbol_name: std.Buffer, + + /// parent should be the top level decls or container decls + fndef_scope: *Scope.FnDef, + + /// parent is scope for last parameter + child_scope: *Scope, + + /// parent is child_scope + block_scope: *Scope.Block, + + /// 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{ + .base = Value{ + .id = Value.Id.Fn, + .typeof = &fn_type.base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .fndef_scope = fndef_scope, + .child_scope = &fndef_scope.base, + .block_scope = undefined, + .symbol_name = symbol_name, + }); + fn_type.base.base.ref(); + fndef_scope.fn_val = self; + fndef_scope.base.ref(); + return self; + } + + pub fn destroy(self: *Fn, comp: *Compilation) void { + self.fndef_scope.base.deref(comp); + self.symbol_name.deinit(); + comp.a().destroy(self); + } + }; + + pub const Void = struct { + base: Value, + + pub fn get(comp: *Compilation) *Void { + comp.void_value.base.ref(); + return comp.void_value; + } + + pub fn destroy(self: *Void, comp: *Compilation) void { + comp.a().destroy(self); + } + }; + + pub const Bool = struct { + base: Value, + x: bool, + + pub fn get(comp: *Compilation, x: bool) *Bool { + if (x) { + comp.true_value.base.ref(); + return comp.true_value; + } else { + comp.false_value.base.ref(); + return comp.false_value; + } + } + + pub fn destroy(self: *Bool, comp: *Compilation) void { + comp.a().destroy(self); + } + }; + + pub const NoReturn = struct { + base: Value, + + pub fn get(comp: *Compilation) *NoReturn { + comp.noreturn_value.base.ref(); + return comp.noreturn_value; + } + + pub fn destroy(self: *NoReturn, comp: *Compilation) void { + comp.a().destroy(self); + } + }; + + pub const Ptr = struct { + base: Value, + + pub const Mut = enum { + CompTimeConst, + CompTimeVar, + RunTime, + }; + + pub fn destroy(self: *Ptr, comp: *Compilation) void { + comp.a().destroy(self); + } + }; +}; diff --git a/src-self-hosted/visib.zig b/src-self-hosted/visib.zig new file mode 100644 index 0000000000..3704600cca --- /dev/null +++ b/src-self-hosted/visib.zig @@ -0,0 +1,4 @@ +pub const Visib = enum { + Private, + Pub, +}; diff --git a/src/all_types.hpp b/src/all_types.hpp index a18b87e90d..8bb456e848 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -2003,12 +2003,6 @@ struct IrBasicBlock { IrInstruction *must_be_comptime_source_instr; }; -struct LVal { - bool is_ptr; - bool is_const; - bool is_volatile; -}; - enum IrInstructionId { IrInstructionIdInvalid, IrInstructionIdBr, @@ -2970,6 +2964,11 @@ struct IrInstructionTypeName { IrInstruction *type_value; }; +enum LVal { + LValNone, + LValPtr, +}; + struct IrInstructionDeclRef { IrInstruction base; diff --git a/src/analyze.cpp b/src/analyze.cpp index 9b60f7374a..5635cce411 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -1430,10 +1430,10 @@ static bool type_allowed_in_extern(CodeGen *g, TypeTableEntry *type_entry) { case TypeTableEntryIdBoundFn: case TypeTableEntryIdArgTuple: case TypeTableEntryIdPromise: + case TypeTableEntryIdVoid: return false; case TypeTableEntryIdOpaque: case TypeTableEntryIdUnreachable: - case TypeTableEntryIdVoid: case TypeTableEntryIdBool: return true; case TypeTableEntryIdInt: @@ -1460,7 +1460,10 @@ static bool type_allowed_in_extern(CodeGen *g, TypeTableEntry *type_entry) { case TypeTableEntryIdOptional: { TypeTableEntry *child_type = type_entry->data.maybe.child_type; - return child_type->id == TypeTableEntryIdPointer || child_type->id == TypeTableEntryIdFn; + if (child_type->id != TypeTableEntryIdPointer && child_type->id != TypeTableEntryIdFn) { + return false; + } + return type_allowed_in_extern(g, child_type); } case TypeTableEntryIdEnum: return type_entry->data.enumeration.layout == ContainerLayoutExtern || type_entry->data.enumeration.layout == ContainerLayoutPacked; @@ -1637,7 +1640,10 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c fn_type_id.return_type = specified_return_type; } - if (!calling_convention_allows_zig_types(fn_type_id.cc) && !type_allowed_in_extern(g, fn_type_id.return_type)) { + if (!calling_convention_allows_zig_types(fn_type_id.cc) && + fn_type_id.return_type->id != TypeTableEntryIdVoid && + !type_allowed_in_extern(g, fn_type_id.return_type)) + { add_node_error(g, fn_proto->return_type, buf_sprintf("return type '%s' not allowed in function with calling convention '%s'", buf_ptr(&fn_type_id.return_type->name), @@ -1939,6 +1945,17 @@ static void resolve_struct_type(CodeGen *g, TypeTableEntry *struct_type) { break; } + if (struct_type->data.structure.layout == ContainerLayoutExtern) { + if (!type_allowed_in_extern(g, field_type)) { + AstNode *field_source_node = decl_node->data.container_decl.fields.at(i); + add_node_error(g, field_source_node, + buf_sprintf("extern structs cannot contain fields of type '%s'", + buf_ptr(&field_type->name))); + struct_type->data.structure.is_invalid = true; + break; + } + } + if (!type_has_bits(field_type)) continue; diff --git a/src/codegen.cpp b/src/codegen.cpp index 1d51629002..9712388783 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2212,10 +2212,8 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, return LLVMBuildICmp(g->builder, pred, op1_value, op2_value, ""); } else if (type_entry->id == TypeTableEntryIdEnum || type_entry->id == TypeTableEntryIdErrorSet || - type_entry->id == TypeTableEntryIdPointer || type_entry->id == TypeTableEntryIdBool || - type_entry->id == TypeTableEntryIdPromise || - type_entry->id == TypeTableEntryIdFn) + get_codegen_ptr_type(type_entry) != nullptr) { LLVMIntPredicate pred = cmp_op_to_int_predicate(op_id, false); return LLVMBuildICmp(g->builder, pred, op1_value, op2_value, ""); @@ -3103,6 +3101,10 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr return nullptr; } else if (first_arg_ret) { return instruction->tmp_ptr; + } else if (handle_is_ptr(src_return_type)) { + auto store_instr = LLVMBuildStore(g->builder, result, instruction->tmp_ptr); + LLVMSetAlignment(store_instr, LLVMGetAlignment(instruction->tmp_ptr)); + return instruction->tmp_ptr; } else { return result; } diff --git a/src/ir.cpp b/src/ir.cpp index 7f7436010e..3007bbcf64 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -39,9 +39,6 @@ struct IrAnalyze { IrBasicBlock *const_predecessor_bb; }; -static const LVal LVAL_NONE = { false, false, false }; -static const LVal LVAL_PTR = { true, false, false }; - enum ConstCastResultId { ConstCastResultIdOk, ConstCastResultIdErrSet, @@ -249,8 +246,6 @@ static void ir_ref_bb(IrBasicBlock *bb) { static void ir_ref_instruction(IrInstruction *instruction, IrBasicBlock *cur_bb) { assert(instruction->id != IrInstructionIdInvalid); instruction->ref_count += 1; - if (instruction->owner_bb != cur_bb && !instr_is_comptime(instruction)) - ir_ref_bb(instruction->owner_bb); } static void ir_ref_var(VariableTableEntry *var) { @@ -3164,7 +3159,7 @@ static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node, case ReturnKindError: { assert(expr_node); - IrInstruction *err_union_ptr = ir_gen_node_extra(irb, expr_node, scope, LVAL_PTR); + IrInstruction *err_union_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPtr); if (err_union_ptr == irb->codegen->invalid_instruction) return irb->codegen->invalid_instruction; IrInstruction *err_union_val = ir_build_load_ptr(irb, scope, node, err_union_ptr); @@ -3192,7 +3187,7 @@ static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node, ir_set_cursor_at_end_and_append_block(irb, continue_block); IrInstruction *unwrapped_ptr = ir_build_unwrap_err_payload(irb, scope, node, err_union_ptr, false); - if (lval.is_ptr) + if (lval == LValPtr) return unwrapped_ptr; else return ir_build_load_ptr(irb, scope, node, unwrapped_ptr); @@ -3357,7 +3352,7 @@ static IrInstruction *ir_gen_bin_op_id(IrBuilder *irb, Scope *scope, AstNode *no } static IrInstruction *ir_gen_assign(IrBuilder *irb, Scope *scope, AstNode *node) { - IrInstruction *lvalue = ir_gen_node_extra(irb, node->data.bin_op_expr.op1, scope, LVAL_PTR); + IrInstruction *lvalue = ir_gen_node_extra(irb, node->data.bin_op_expr.op1, scope, LValPtr); IrInstruction *rvalue = ir_gen_node(irb, node->data.bin_op_expr.op2, scope); if (lvalue == irb->codegen->invalid_instruction || rvalue == irb->codegen->invalid_instruction) @@ -3368,7 +3363,7 @@ static IrInstruction *ir_gen_assign(IrBuilder *irb, Scope *scope, AstNode *node) } static IrInstruction *ir_gen_assign_op(IrBuilder *irb, Scope *scope, AstNode *node, IrBinOp op_id) { - IrInstruction *lvalue = ir_gen_node_extra(irb, node->data.bin_op_expr.op1, scope, LVAL_PTR); + IrInstruction *lvalue = ir_gen_node_extra(irb, node->data.bin_op_expr.op1, scope, LValPtr); if (lvalue == irb->codegen->invalid_instruction) return lvalue; IrInstruction *op1 = ir_build_load_ptr(irb, scope, node->data.bin_op_expr.op1, lvalue); @@ -3470,7 +3465,7 @@ static IrInstruction *ir_gen_maybe_ok_or(IrBuilder *irb, Scope *parent_scope, As AstNode *op1_node = node->data.bin_op_expr.op1; AstNode *op2_node = node->data.bin_op_expr.op2; - IrInstruction *maybe_ptr = ir_gen_node_extra(irb, op1_node, parent_scope, LVAL_PTR); + IrInstruction *maybe_ptr = ir_gen_node_extra(irb, op1_node, parent_scope, LValPtr); if (maybe_ptr == irb->codegen->invalid_instruction) return irb->codegen->invalid_instruction; @@ -3657,7 +3652,7 @@ static IrInstruction *ir_gen_symbol(IrBuilder *irb, Scope *scope, AstNode *node, Buf *variable_name = node->data.symbol_expr.symbol; - if (buf_eql_str(variable_name, "_") && lval.is_ptr) { + if (buf_eql_str(variable_name, "_") && lval == LValPtr) { IrInstructionConst *const_instruction = ir_build_instruction(irb, scope, node); const_instruction->base.value.type = get_pointer_to_type(irb->codegen, irb->codegen->builtin_types.entry_void, false); @@ -3669,8 +3664,8 @@ static IrInstruction *ir_gen_symbol(IrBuilder *irb, Scope *scope, AstNode *node, auto primitive_table_entry = irb->codegen->primitive_type_table.maybe_get(variable_name); if (primitive_table_entry) { IrInstruction *value = ir_build_const_type(irb, scope, node, primitive_table_entry->value); - if (lval.is_ptr) { - return ir_build_ref(irb, scope, node, value, lval.is_const, lval.is_volatile); + if (lval == LValPtr) { + return ir_build_ref(irb, scope, node, value, false, false); } else { return value; } @@ -3679,7 +3674,7 @@ static IrInstruction *ir_gen_symbol(IrBuilder *irb, Scope *scope, AstNode *node, VariableTableEntry *var = find_variable(irb->codegen, scope, variable_name); if (var) { IrInstruction *var_ptr = ir_build_var_ptr(irb, scope, node, var); - if (lval.is_ptr) + if (lval == LValPtr) return var_ptr; else return ir_build_load_ptr(irb, scope, node, var_ptr); @@ -3705,7 +3700,7 @@ static IrInstruction *ir_gen_array_access(IrBuilder *irb, Scope *scope, AstNode assert(node->type == NodeTypeArrayAccessExpr); AstNode *array_ref_node = node->data.array_access_expr.array_ref_expr; - IrInstruction *array_ref_instruction = ir_gen_node_extra(irb, array_ref_node, scope, LVAL_PTR); + IrInstruction *array_ref_instruction = ir_gen_node_extra(irb, array_ref_node, scope, LValPtr); if (array_ref_instruction == irb->codegen->invalid_instruction) return array_ref_instruction; @@ -3716,7 +3711,7 @@ static IrInstruction *ir_gen_array_access(IrBuilder *irb, Scope *scope, AstNode IrInstruction *ptr_instruction = ir_build_elem_ptr(irb, scope, node, array_ref_instruction, subscript_instruction, true, PtrLenSingle); - if (lval.is_ptr) + if (lval == LValPtr) return ptr_instruction; return ir_build_load_ptr(irb, scope, node, ptr_instruction); @@ -3728,7 +3723,7 @@ static IrInstruction *ir_gen_field_access(IrBuilder *irb, Scope *scope, AstNode AstNode *container_ref_node = node->data.field_access_expr.struct_expr; Buf *field_name = node->data.field_access_expr.field_name; - IrInstruction *container_ref_instruction = ir_gen_node_extra(irb, container_ref_node, scope, LVAL_PTR); + IrInstruction *container_ref_instruction = ir_gen_node_extra(irb, container_ref_node, scope, LValPtr); if (container_ref_instruction == irb->codegen->invalid_instruction) return container_ref_instruction; @@ -4386,7 +4381,7 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo case BuiltinFnIdField: { AstNode *arg0_node = node->data.fn_call_expr.params.at(0); - IrInstruction *arg0_value = ir_gen_node_extra(irb, arg0_node, scope, LVAL_PTR); + IrInstruction *arg0_value = ir_gen_node_extra(irb, arg0_node, scope, LValPtr); if (arg0_value == irb->codegen->invalid_instruction) return arg0_value; @@ -4397,7 +4392,7 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo IrInstruction *ptr_instruction = ir_build_field_ptr_instruction(irb, scope, node, arg0_value, arg1_value); - if (lval.is_ptr) + if (lval == LValPtr) return ptr_instruction; return ir_build_load_ptr(irb, scope, node, ptr_instruction); @@ -4928,18 +4923,18 @@ static IrInstruction *ir_gen_prefix_op_id_lval(IrBuilder *irb, Scope *scope, Ast } static IrInstruction *ir_gen_prefix_op_id(IrBuilder *irb, Scope *scope, AstNode *node, IrUnOp op_id) { - return ir_gen_prefix_op_id_lval(irb, scope, node, op_id, LVAL_NONE); + return ir_gen_prefix_op_id_lval(irb, scope, node, op_id, LValNone); } static IrInstruction *ir_lval_wrap(IrBuilder *irb, Scope *scope, IrInstruction *value, LVal lval) { - if (!lval.is_ptr) + if (lval != LValPtr) return value; if (value == irb->codegen->invalid_instruction) return value; // We needed a pointer to a value, but we got a value. So we create // an instruction which just makes a const pointer of it. - return ir_build_ref(irb, scope, value->source_node, value, lval.is_const, lval.is_volatile); + return ir_build_ref(irb, scope, value->source_node, value, false, false); } static IrInstruction *ir_gen_pointer_type(IrBuilder *irb, Scope *scope, AstNode *node) { @@ -5001,7 +4996,7 @@ static IrInstruction *ir_gen_pointer_type(IrBuilder *irb, Scope *scope, AstNode static IrInstruction *ir_gen_err_assert_ok(IrBuilder *irb, Scope *scope, AstNode *source_node, AstNode *expr_node, LVal lval) { - IrInstruction *err_union_ptr = ir_gen_node_extra(irb, expr_node, scope, LVAL_PTR); + IrInstruction *err_union_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPtr); if (err_union_ptr == irb->codegen->invalid_instruction) return irb->codegen->invalid_instruction; @@ -5009,7 +5004,7 @@ static IrInstruction *ir_gen_err_assert_ok(IrBuilder *irb, Scope *scope, AstNode if (payload_ptr == irb->codegen->invalid_instruction) return irb->codegen->invalid_instruction; - if (lval.is_ptr) + if (lval == LValPtr) return payload_ptr; return ir_build_load_ptr(irb, scope, source_node, payload_ptr); @@ -5046,7 +5041,7 @@ static IrInstruction *ir_gen_prefix_op_expr(IrBuilder *irb, Scope *scope, AstNod return ir_lval_wrap(irb, scope, ir_gen_prefix_op_id(irb, scope, node, IrUnOpOptional), lval); case PrefixOpAddrOf: { AstNode *expr_node = node->data.prefix_op_expr.primary_expr; - return ir_lval_wrap(irb, scope, ir_gen_node_extra(irb, expr_node, scope, LVAL_PTR), lval); + return ir_lval_wrap(irb, scope, ir_gen_node_extra(irb, expr_node, scope, LValPtr), lval); } } zig_unreachable(); @@ -5186,7 +5181,7 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n } else { payload_scope = scope; } - IrInstruction *err_val_ptr = ir_gen_node_extra(irb, node->data.while_expr.condition, scope, LVAL_PTR); + IrInstruction *err_val_ptr = ir_gen_node_extra(irb, node->data.while_expr.condition, scope, LValPtr); if (err_val_ptr == irb->codegen->invalid_instruction) return err_val_ptr; IrInstruction *err_val = ir_build_load_ptr(irb, scope, node->data.while_expr.condition, err_val_ptr); @@ -5269,7 +5264,7 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n VariableTableEntry *payload_var = ir_create_var(irb, symbol_node, scope, var_symbol, true, false, false, is_comptime); Scope *child_scope = payload_var->child_scope; - IrInstruction *maybe_val_ptr = ir_gen_node_extra(irb, node->data.while_expr.condition, scope, LVAL_PTR); + IrInstruction *maybe_val_ptr = ir_gen_node_extra(irb, node->data.while_expr.condition, scope, LValPtr); if (maybe_val_ptr == irb->codegen->invalid_instruction) return maybe_val_ptr; IrInstruction *maybe_val = ir_build_load_ptr(irb, scope, node->data.while_expr.condition, maybe_val_ptr); @@ -5413,7 +5408,7 @@ static IrInstruction *ir_gen_for_expr(IrBuilder *irb, Scope *parent_scope, AstNo } assert(elem_node->type == NodeTypeSymbol); - IrInstruction *array_val_ptr = ir_gen_node_extra(irb, array_node, parent_scope, LVAL_PTR); + IrInstruction *array_val_ptr = ir_gen_node_extra(irb, array_node, parent_scope, LValPtr); if (array_val_ptr == irb->codegen->invalid_instruction) return array_val_ptr; @@ -5700,7 +5695,7 @@ static IrInstruction *ir_gen_test_expr(IrBuilder *irb, Scope *scope, AstNode *no AstNode *else_node = node->data.test_expr.else_node; bool var_is_ptr = node->data.test_expr.var_is_ptr; - IrInstruction *maybe_val_ptr = ir_gen_node_extra(irb, expr_node, scope, LVAL_PTR); + IrInstruction *maybe_val_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPtr); if (maybe_val_ptr == irb->codegen->invalid_instruction) return maybe_val_ptr; @@ -5778,7 +5773,7 @@ static IrInstruction *ir_gen_if_err_expr(IrBuilder *irb, Scope *scope, AstNode * Buf *var_symbol = node->data.if_err_expr.var_symbol; Buf *err_symbol = node->data.if_err_expr.err_symbol; - IrInstruction *err_val_ptr = ir_gen_node_extra(irb, target_node, scope, LVAL_PTR); + IrInstruction *err_val_ptr = ir_gen_node_extra(irb, target_node, scope, LValPtr); if (err_val_ptr == irb->codegen->invalid_instruction) return err_val_ptr; @@ -5904,7 +5899,7 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode * assert(node->type == NodeTypeSwitchExpr); AstNode *target_node = node->data.switch_expr.expr; - IrInstruction *target_value_ptr = ir_gen_node_extra(irb, target_node, scope, LVAL_PTR); + IrInstruction *target_value_ptr = ir_gen_node_extra(irb, target_node, scope, LValPtr); if (target_value_ptr == irb->codegen->invalid_instruction) return target_value_ptr; IrInstruction *target_value = ir_build_switch_target(irb, scope, node, target_value_ptr); @@ -6277,7 +6272,7 @@ static IrInstruction *ir_gen_slice(IrBuilder *irb, Scope *scope, AstNode *node) AstNode *start_node = slice_expr->start; AstNode *end_node = slice_expr->end; - IrInstruction *ptr_value = ir_gen_node_extra(irb, array_node, scope, LVAL_PTR); + IrInstruction *ptr_value = ir_gen_node_extra(irb, array_node, scope, LValPtr); if (ptr_value == irb->codegen->invalid_instruction) return irb->codegen->invalid_instruction; @@ -6311,11 +6306,11 @@ static IrInstruction *ir_gen_err_ok_or(IrBuilder *irb, Scope *parent_scope, AstN add_node_error(irb->codegen, var_node, buf_sprintf("unused variable: '%s'", buf_ptr(var_name))); return irb->codegen->invalid_instruction; } - return ir_gen_err_assert_ok(irb, parent_scope, node, op1_node, LVAL_NONE); + return ir_gen_err_assert_ok(irb, parent_scope, node, op1_node, LValNone); } - IrInstruction *err_union_ptr = ir_gen_node_extra(irb, op1_node, parent_scope, LVAL_PTR); + IrInstruction *err_union_ptr = ir_gen_node_extra(irb, op1_node, parent_scope, LValPtr); if (err_union_ptr == irb->codegen->invalid_instruction) return irb->codegen->invalid_instruction; @@ -6868,7 +6863,7 @@ static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scop IrInstruction *ptr_instruction = ir_gen_field_access(irb, scope, node); if (ptr_instruction == irb->codegen->invalid_instruction) return ptr_instruction; - if (lval.is_ptr) + if (lval == LValPtr) return ptr_instruction; return ir_build_load_ptr(irb, scope, node, ptr_instruction); @@ -6884,12 +6879,12 @@ static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scop case NodeTypeUnwrapOptional: { AstNode *expr_node = node->data.unwrap_optional.expr; - IrInstruction *maybe_ptr = ir_gen_node_extra(irb, expr_node, scope, LVAL_PTR); + IrInstruction *maybe_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPtr); if (maybe_ptr == irb->codegen->invalid_instruction) return irb->codegen->invalid_instruction; IrInstruction *unwrapped_ptr = ir_build_unwrap_maybe(irb, scope, node, maybe_ptr, true); - if (lval.is_ptr) + if (lval == LValPtr) return unwrapped_ptr; return ir_build_load_ptr(irb, scope, node, unwrapped_ptr); @@ -6959,7 +6954,7 @@ static IrInstruction *ir_gen_node_extra(IrBuilder *irb, AstNode *node, Scope *sc } static IrInstruction *ir_gen_node(IrBuilder *irb, AstNode *node, Scope *scope) { - return ir_gen_node_extra(irb, node, scope, LVAL_NONE); + return ir_gen_node_extra(irb, node, scope, LValNone); } static void invalidate_exec(IrExecutable *exec) { @@ -7089,7 +7084,7 @@ bool ir_gen(CodeGen *codegen, AstNode *node, Scope *scope, IrExecutable *ir_exec irb->exec->coro_final_cleanup_block = ir_create_basic_block(irb, scope, "FinalCleanup"); } - IrInstruction *result = ir_gen_node_extra(irb, node, scope, LVAL_NONE); + IrInstruction *result = ir_gen_node_extra(irb, node, scope, LValNone); assert(result); if (irb->exec->invalid) return false; @@ -9242,26 +9237,9 @@ static TypeTableEntry *ir_finish_anal(IrAnalyze *ira, TypeTableEntry *result_typ } static IrInstruction *ir_get_const(IrAnalyze *ira, IrInstruction *old_instruction) { - IrInstruction *new_instruction; - if (old_instruction->id == IrInstructionIdVarPtr) { - IrInstructionVarPtr *old_var_ptr_instruction = (IrInstructionVarPtr *)old_instruction; - IrInstructionVarPtr *var_ptr_instruction = ir_create_instruction(&ira->new_irb, - old_instruction->scope, old_instruction->source_node); - var_ptr_instruction->var = old_var_ptr_instruction->var; - new_instruction = &var_ptr_instruction->base; - } else if (old_instruction->id == IrInstructionIdFieldPtr) { - IrInstructionFieldPtr *field_ptr_instruction = ir_create_instruction(&ira->new_irb, - old_instruction->scope, old_instruction->source_node); - new_instruction = &field_ptr_instruction->base; - } else if (old_instruction->id == IrInstructionIdElemPtr) { - IrInstructionElemPtr *elem_ptr_instruction = ir_create_instruction(&ira->new_irb, - old_instruction->scope, old_instruction->source_node); - new_instruction = &elem_ptr_instruction->base; - } else { - IrInstructionConst *const_instruction = ir_create_instruction(&ira->new_irb, - old_instruction->scope, old_instruction->source_node); - new_instruction = &const_instruction->base; - } + IrInstructionConst *const_instruction = ir_create_instruction(&ira->new_irb, + old_instruction->scope, old_instruction->source_node); + IrInstruction *new_instruction = &const_instruction->base; new_instruction->value.special = ConstValSpecialStatic; return new_instruction; } @@ -9615,23 +9593,6 @@ static IrInstruction *ir_get_ref(IrAnalyze *ira, IrInstruction *source_instructi if (type_is_invalid(value->value.type)) return ira->codegen->invalid_instruction; - if (value->id == IrInstructionIdLoadPtr) { - IrInstructionLoadPtr *load_ptr_inst = (IrInstructionLoadPtr *) value; - - if (load_ptr_inst->ptr->value.type->data.pointer.is_const) { - return load_ptr_inst->ptr; - } - - type_ensure_zero_bits_known(ira->codegen, value->value.type); - if (type_is_invalid(value->value.type)) { - return ira->codegen->invalid_instruction; - } - - if (!type_has_bits(value->value.type)) { - return load_ptr_inst->ptr; - } - } - if (instr_is_comptime(value)) { ConstExprValue *val = ir_resolve_const(ira, value, UndefOk); if (!val) @@ -11150,7 +11111,7 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp if (type_is_invalid(resolved_type)) return resolved_type; - + bool operator_allowed; switch (resolved_type->id) { case TypeTableEntryIdInvalid: zig_unreachable(); // handled above @@ -11159,6 +11120,7 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp case TypeTableEntryIdComptimeInt: case TypeTableEntryIdInt: case TypeTableEntryIdFloat: + operator_allowed = true; break; case TypeTableEntryIdBool: @@ -11173,19 +11135,8 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp case TypeTableEntryIdBoundFn: case TypeTableEntryIdArgTuple: case TypeTableEntryIdPromise: - if (!is_equality_cmp) { - ir_add_error_node(ira, source_node, - buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name))); - return ira->codegen->builtin_types.entry_invalid; - } - break; - case TypeTableEntryIdEnum: - if (!is_equality_cmp) { - ir_add_error_node(ira, source_node, - buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name))); - return ira->codegen->builtin_types.entry_invalid; - } + operator_allowed = is_equality_cmp; break; case TypeTableEntryIdUnreachable: @@ -11193,12 +11144,18 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp case TypeTableEntryIdStruct: case TypeTableEntryIdUndefined: case TypeTableEntryIdNull: - case TypeTableEntryIdOptional: case TypeTableEntryIdErrorUnion: case TypeTableEntryIdUnion: - ir_add_error_node(ira, source_node, - buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name))); - return ira->codegen->builtin_types.entry_invalid; + operator_allowed = false; + break; + case TypeTableEntryIdOptional: + operator_allowed = is_equality_cmp && get_codegen_ptr_type(resolved_type) != nullptr; + break; + } + if (!operator_allowed) { + ir_add_error_node(ira, source_node, + buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name))); + return ira->codegen->builtin_types.entry_invalid; } IrInstruction *casted_op1 = ir_implicit_cast(ira, op1, resolved_type); @@ -19752,7 +19709,7 @@ static TypeTableEntry *ir_analyze_instruction_decl_ref(IrAnalyze *ira, Tld *tld = instruction->tld; LVal lval = instruction->lval; - resolve_top_level_decl(ira->codegen, tld, lval.is_ptr, instruction->base.source_node); + resolve_top_level_decl(ira->codegen, tld, lval == LValPtr, instruction->base.source_node); if (tld->resolution == TldResolutionInvalid) return ira->codegen->builtin_types.entry_invalid; @@ -19773,7 +19730,7 @@ static TypeTableEntry *ir_analyze_instruction_decl_ref(IrAnalyze *ira, add_link_lib_symbol(ira, tld_var->extern_lib_name, &var->name, instruction->base.source_node); } - if (lval.is_ptr) { + if (lval == LValPtr) { ir_link_new_instruction(var_ptr, &instruction->base); return var_ptr->value.type; } else { @@ -19794,7 +19751,7 @@ static TypeTableEntry *ir_analyze_instruction_decl_ref(IrAnalyze *ira, IrInstruction *ref_instruction = ir_create_const_fn(&ira->new_irb, instruction->base.scope, instruction->base.source_node, fn_entry); - if (lval.is_ptr) { + if (lval == LValPtr) { IrInstruction *ptr_instr = ir_get_ref(ira, &instruction->base, ref_instruction, true, false); ir_link_new_instruction(ptr_instr, &instruction->base); return ptr_instr->value.type; diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 780cf9e756..6182958d0a 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -1005,10 +1005,8 @@ static void ir_print_ptr_type(IrPrint *irp, IrInstructionPtrType *instruction) { } static void ir_print_decl_ref(IrPrint *irp, IrInstructionDeclRef *instruction) { - const char *ptr_str = instruction->lval.is_ptr ? "ptr " : ""; - const char *const_str = instruction->lval.is_const ? "const " : ""; - const char *volatile_str = instruction->lval.is_volatile ? "volatile " : ""; - fprintf(irp->f, "declref %s%s%s%s", const_str, volatile_str, ptr_str, buf_ptr(instruction->tld->name)); + const char *ptr_str = (instruction->lval == LValPtr) ? "ptr " : ""; + fprintf(irp->f, "declref %s%s", ptr_str, buf_ptr(instruction->tld->name)); } static void ir_print_panic(IrPrint *irp, IrInstructionPanic *instruction) { diff --git a/src/main.cpp b/src/main.cpp index a409778a78..5f96953f21 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -891,15 +891,19 @@ int main(int argc, char **argv) { add_package(g, cur_pkg, g->root_package); - if (cmd == CmdBuild || cmd == CmdRun) { - codegen_set_emit_file_type(g, emit_file_type); - + if (cmd == CmdBuild || cmd == CmdRun || cmd == CmdTest) { for (size_t i = 0; i < objects.length; i += 1) { codegen_add_object(g, buf_create_from_str(objects.at(i))); } for (size_t i = 0; i < asm_files.length; i += 1) { codegen_add_assembly(g, buf_create_from_str(asm_files.at(i))); } + } + + + if (cmd == CmdBuild || cmd == CmdRun) { + codegen_set_emit_file_type(g, emit_file_type); + codegen_build(g); codegen_link(g, out_file); if (timing_info) diff --git a/std/array_list.zig b/std/array_list.zig index b71f5be6ab..8d7bde46a1 100644 --- a/std/array_list.zig +++ b/std/array_list.zig @@ -41,8 +41,8 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type { return self.items[0..self.len]; } - pub fn at(self: Self, n: usize) T { - return self.toSliceConst()[n]; + pub fn at(self: Self, i: usize) T { + return self.toSliceConst()[i]; } /// Sets the value at index `i`, or returns `error.OutOfBounds` if @@ -85,7 +85,7 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type { try self.ensureCapacity(self.len + 1); self.len += 1; - mem.copy(T, self.items[n + 1 .. self.len], self.items[n .. self.len - 1]); + mem.copyBackwards(T, self.items[n + 1 .. self.len], self.items[n .. self.len - 1]); self.items[n] = item; } @@ -93,7 +93,7 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type { try self.ensureCapacity(self.len + items.len); self.len += items.len; - mem.copy(T, self.items[n + items.len .. self.len], self.items[n .. self.len - items.len]); + mem.copyBackwards(T, self.items[n + items.len .. self.len], self.items[n .. self.len - items.len]); mem.copy(T, self.items[n .. n + items.len], items); } @@ -102,6 +102,26 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type { new_item_ptr.* = item; } + /// Removes the element at the specified index and returns it. + /// The empty slot is filled from the end of the list. + pub fn swapRemove(self: *Self, i: usize) T { + if (self.len - 1 == i) return self.pop(); + + const slice = self.toSlice(); + const old_item = slice[i]; + slice[i] = self.pop(); + return old_item; + } + + pub fn removeOrError(self: *Self, n: usize) !T { + if (n >= self.len) return error.OutOfBounds; + if (self.len - 1 == n) return self.pop(); + + var old_item = self.at(n); + try self.setOrError(n, self.pop()); + return old_item; + } + pub fn appendSlice(self: *Self, items: []align(A) const T) !void { try self.ensureCapacity(self.len + items.len); mem.copy(T, self.items[self.len..], items); @@ -232,6 +252,33 @@ test "basic ArrayList test" { assert(list.pop() == 33); } +test "std.ArrayList.swapRemove" { + var list = ArrayList(i32).init(debug.global_allocator); + defer list.deinit(); + + try list.append(1); + try list.append(2); + try list.append(3); + try list.append(4); + try list.append(5); + try list.append(6); + try list.append(7); + + //remove from middle + assert(list.swapRemove(3) == 4); + assert(list.at(3) == 7); + assert(list.len == 6); + + //remove from end + assert(list.swapRemove(5) == 6); + assert(list.len == 5); + + //remove from front + assert(list.swapRemove(0) == 1); + assert(list.at(0) == 5); + assert(list.len == 4); +} + test "iterator ArrayList test" { var list = ArrayList(i32).init(debug.global_allocator); defer list.deinit(); @@ -266,19 +313,36 @@ test "insert ArrayList test" { defer list.deinit(); try list.append(1); + try list.append(2); + try list.append(3); try list.insert(0, 5); assert(list.items[0] == 5); assert(list.items[1] == 1); + assert(list.items[2] == 2); + assert(list.items[3] == 3); +} +test "insertSlice ArrayList test" { + var list = ArrayList(i32).init(debug.global_allocator); + defer list.deinit(); + + try list.append(1); + try list.append(2); + try list.append(3); + try list.append(4); try list.insertSlice(1, []const i32{ 9, 8, }); - assert(list.items[0] == 5); + assert(list.items[0] == 1); assert(list.items[1] == 9); assert(list.items[2] == 8); + assert(list.items[3] == 2); + assert(list.items[4] == 3); + assert(list.items[5] == 4); const items = []const i32{1}; try list.insertSlice(0, items[0..0]); - assert(list.items[0] == 5); + assert(list.len == 6); + assert(list.items[0] == 1); } diff --git a/std/atomic/index.zig b/std/atomic/index.zig index c0ea5be183..a94cff1973 100644 --- a/std/atomic/index.zig +++ b/std/atomic/index.zig @@ -1,9 +1,9 @@ pub const Stack = @import("stack.zig").Stack; -pub const QueueMpsc = @import("queue_mpsc.zig").QueueMpsc; -pub const QueueMpmc = @import("queue_mpmc.zig").QueueMpmc; +pub const Queue = @import("queue.zig").Queue; +pub const Int = @import("int.zig").Int; test "std.atomic" { _ = @import("stack.zig"); - _ = @import("queue_mpsc.zig"); - _ = @import("queue_mpmc.zig"); + _ = @import("queue.zig"); + _ = @import("int.zig"); } diff --git a/std/atomic/int.zig b/std/atomic/int.zig new file mode 100644 index 0000000000..d51454c673 --- /dev/null +++ b/std/atomic/int.zig @@ -0,0 +1,29 @@ +const builtin = @import("builtin"); +const AtomicOrder = builtin.AtomicOrder; + +/// Thread-safe, lock-free integer +pub fn Int(comptime T: type) type { + return struct { + unprotected_value: T, + + pub const Self = this; + + pub fn init(init_val: T) Self { + return Self{ .unprotected_value = init_val }; + } + + /// Returns previous value + pub fn incr(self: *Self) T { + return @atomicRmw(T, &self.unprotected_value, builtin.AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); + } + + /// Returns previous value + pub fn decr(self: *Self) T { + return @atomicRmw(T, &self.unprotected_value, builtin.AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); + } + + pub fn get(self: *Self) T { + return @atomicLoad(T, &self.unprotected_value, AtomicOrder.SeqCst); + } + }; +} diff --git a/std/atomic/queue_mpmc.zig b/std/atomic/queue.zig similarity index 71% rename from std/atomic/queue_mpmc.zig rename to std/atomic/queue.zig index 7ffc9f9ccb..1fd07714e8 100644 --- a/std/atomic/queue_mpmc.zig +++ b/std/atomic/queue.zig @@ -2,15 +2,13 @@ const builtin = @import("builtin"); const AtomicOrder = builtin.AtomicOrder; const AtomicRmwOp = builtin.AtomicRmwOp; -/// Many producer, many consumer, non-allocating, thread-safe, lock-free -/// This implementation has a crippling limitation - it hangs onto node -/// memory for 1 extra get() and 1 extra put() operation - when get() returns a node, that -/// node must not be freed until both the next get() and the next put() completes. -pub fn QueueMpmc(comptime T: type) type { +/// Many producer, many consumer, non-allocating, thread-safe. +/// Uses a spinlock to protect get() and put(). +pub fn Queue(comptime T: type) type { return struct { - head: *Node, - tail: *Node, - root: Node, + head: ?*Node, + tail: ?*Node, + lock: u8, pub const Self = this; @@ -19,31 +17,48 @@ pub fn QueueMpmc(comptime T: type) type { data: T, }; - /// TODO: well defined copy elision: https://github.com/ziglang/zig/issues/287 - pub fn init(self: *Self) void { - self.root.next = null; - self.head = &self.root; - self.tail = &self.root; + pub fn init() Self { + return Self{ + .head = null, + .tail = null, + .lock = 0, + }; } pub fn put(self: *Self, node: *Node) void { node.next = null; - const tail = @atomicRmw(*Node, &self.tail, AtomicRmwOp.Xchg, node, AtomicOrder.SeqCst); - _ = @atomicRmw(?*Node, &tail.next, AtomicRmwOp.Xchg, node, AtomicOrder.SeqCst); - } + while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {} + defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1); - /// node must not be freed until both the next get() and the next put() complete - pub fn get(self: *Self) ?*Node { - var head = @atomicLoad(*Node, &self.head, AtomicOrder.SeqCst); - while (true) { - const node = head.next orelse return null; - head = @cmpxchgWeak(*Node, &self.head, head, node, AtomicOrder.SeqCst, AtomicOrder.SeqCst) orelse return node; + const opt_tail = self.tail; + self.tail = node; + if (opt_tail) |tail| { + tail.next = node; + } else { + assert(self.head == null); + self.head = node; } } - ///// This is a debug function that is not thread-safe. + pub fn get(self: *Self) ?*Node { + while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {} + defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1); + + const head = self.head orelse return null; + self.head = head.next; + if (head.next == null) self.tail = null; + return head; + } + + pub fn isEmpty(self: *Self) bool { + return @atomicLoad(?*Node, &self.head, builtin.AtomicOrder.SeqCst) != null; + } + pub fn dump(self: *Self) void { + while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {} + defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1); + std.debug.warn("head: "); dumpRecursive(self.head, 0); std.debug.warn("tail: "); @@ -64,12 +79,12 @@ pub fn QueueMpmc(comptime T: type) type { }; } -const std = @import("std"); +const std = @import("../index.zig"); const assert = std.debug.assert; const Context = struct { allocator: *std.mem.Allocator, - queue: *QueueMpmc(i32), + queue: *Queue(i32), put_sum: isize, get_sum: isize, get_count: usize, @@ -84,7 +99,7 @@ const Context = struct { const puts_per_thread = 500; const put_thread_count = 3; -test "std.atomic.queue_mpmc" { +test "std.atomic.Queue" { var direct_allocator = std.heap.DirectAllocator.init(); defer direct_allocator.deinit(); @@ -94,8 +109,7 @@ test "std.atomic.queue_mpmc" { var fixed_buffer_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(plenty_of_memory); var a = &fixed_buffer_allocator.allocator; - var queue: QueueMpmc(i32) = undefined; - queue.init(); + var queue = Queue(i32).init(); var context = Context{ .allocator = a, .queue = &queue, @@ -140,7 +154,7 @@ fn startPuts(ctx: *Context) u8 { while (put_count != 0) : (put_count -= 1) { std.os.time.sleep(0, 1); // let the os scheduler be our fuzz const x = @bitCast(i32, r.random.scalar(u32)); - const node = ctx.allocator.create(QueueMpmc(i32).Node{ + const node = ctx.allocator.create(Queue(i32).Node{ .next = undefined, .data = x, }) catch unreachable; @@ -164,17 +178,16 @@ fn startGets(ctx: *Context) u8 { } } -test "std.atomic.queue_mpmc single-threaded" { - var queue: QueueMpmc(i32) = undefined; - queue.init(); +test "std.atomic.Queue single-threaded" { + var queue = Queue(i32).init(); - var node_0 = QueueMpmc(i32).Node{ + var node_0 = Queue(i32).Node{ .data = 0, .next = undefined, }; queue.put(&node_0); - var node_1 = QueueMpmc(i32).Node{ + var node_1 = Queue(i32).Node{ .data = 1, .next = undefined, }; @@ -182,13 +195,13 @@ test "std.atomic.queue_mpmc single-threaded" { assert(queue.get().?.data == 0); - var node_2 = QueueMpmc(i32).Node{ + var node_2 = Queue(i32).Node{ .data = 2, .next = undefined, }; queue.put(&node_2); - var node_3 = QueueMpmc(i32).Node{ + var node_3 = Queue(i32).Node{ .data = 3, .next = undefined, }; @@ -198,15 +211,14 @@ test "std.atomic.queue_mpmc single-threaded" { assert(queue.get().?.data == 2); - var node_4 = QueueMpmc(i32).Node{ + var node_4 = Queue(i32).Node{ .data = 4, .next = undefined, }; queue.put(&node_4); assert(queue.get().?.data == 3); - // if we were to set node_3.next to null here, it would cause this test - // to fail. this demonstrates the limitation of hanging on to extra memory. + node_3.next = null; assert(queue.get().?.data == 4); diff --git a/std/atomic/queue_mpsc.zig b/std/atomic/queue_mpsc.zig deleted file mode 100644 index 978e189453..0000000000 --- a/std/atomic/queue_mpsc.zig +++ /dev/null @@ -1,185 +0,0 @@ -const std = @import("../index.zig"); -const assert = std.debug.assert; -const builtin = @import("builtin"); -const AtomicOrder = builtin.AtomicOrder; -const AtomicRmwOp = builtin.AtomicRmwOp; - -/// Many producer, single consumer, non-allocating, thread-safe, lock-free -pub fn QueueMpsc(comptime T: type) type { - return struct { - inboxes: [2]std.atomic.Stack(T), - outbox: std.atomic.Stack(T), - inbox_index: usize, - - pub const Self = this; - - pub const Node = std.atomic.Stack(T).Node; - - /// Not thread-safe. The call to init() must complete before any other functions are called. - /// No deinitialization required. - pub fn init() Self { - return Self{ - .inboxes = []std.atomic.Stack(T){ - std.atomic.Stack(T).init(), - std.atomic.Stack(T).init(), - }, - .outbox = std.atomic.Stack(T).init(), - .inbox_index = 0, - }; - } - - /// Fully thread-safe. put() may be called from any thread at any time. - pub fn put(self: *Self, node: *Node) void { - const inbox_index = @atomicLoad(usize, &self.inbox_index, AtomicOrder.SeqCst); - const inbox = &self.inboxes[inbox_index]; - inbox.push(node); - } - - /// Must be called by only 1 consumer at a time. Every call to get() and isEmpty() must complete before - /// the next call to get(). - pub fn get(self: *Self) ?*Node { - if (self.outbox.pop()) |node| { - return node; - } - const prev_inbox_index = @atomicRmw(usize, &self.inbox_index, AtomicRmwOp.Xor, 0x1, AtomicOrder.SeqCst); - const prev_inbox = &self.inboxes[prev_inbox_index]; - while (prev_inbox.pop()) |node| { - self.outbox.push(node); - } - return self.outbox.pop(); - } - - /// Must be called by only 1 consumer at a time. Every call to get() and isEmpty() must complete before - /// the next call to isEmpty(). - pub fn isEmpty(self: *Self) bool { - if (!self.outbox.isEmpty()) return false; - const prev_inbox_index = @atomicRmw(usize, &self.inbox_index, AtomicRmwOp.Xor, 0x1, AtomicOrder.SeqCst); - const prev_inbox = &self.inboxes[prev_inbox_index]; - while (prev_inbox.pop()) |node| { - self.outbox.push(node); - } - return self.outbox.isEmpty(); - } - - /// For debugging only. No API guarantees about what this does. - pub fn dump(self: *Self) void { - { - var it = self.outbox.root; - while (it) |node| { - std.debug.warn("0x{x} -> ", @ptrToInt(node)); - it = node.next; - } - } - const inbox_index = self.inbox_index; - const inboxes = []*std.atomic.Stack(T){ - &self.inboxes[self.inbox_index], - &self.inboxes[1 - self.inbox_index], - }; - for (inboxes) |inbox| { - var it = inbox.root; - while (it) |node| { - std.debug.warn("0x{x} -> ", @ptrToInt(node)); - it = node.next; - } - } - - std.debug.warn("null\n"); - } - }; -} - -const Context = struct { - allocator: *std.mem.Allocator, - queue: *QueueMpsc(i32), - put_sum: isize, - get_sum: isize, - get_count: usize, - puts_done: u8, // TODO make this a bool -}; - -// TODO add lazy evaluated build options and then put puts_per_thread behind -// some option such as: "AggressiveMultithreadedFuzzTest". In the AppVeyor -// CI we would use a less aggressive setting since at 1 core, while we still -// want this test to pass, we need a smaller value since there is so much thrashing -// we would also use a less aggressive setting when running in valgrind -const puts_per_thread = 500; -const put_thread_count = 3; - -test "std.atomic.queue_mpsc" { - var direct_allocator = std.heap.DirectAllocator.init(); - defer direct_allocator.deinit(); - - var plenty_of_memory = try direct_allocator.allocator.alloc(u8, 300 * 1024); - defer direct_allocator.allocator.free(plenty_of_memory); - - var fixed_buffer_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(plenty_of_memory); - var a = &fixed_buffer_allocator.allocator; - - var queue = QueueMpsc(i32).init(); - var context = Context{ - .allocator = a, - .queue = &queue, - .put_sum = 0, - .get_sum = 0, - .puts_done = 0, - .get_count = 0, - }; - - var putters: [put_thread_count]*std.os.Thread = undefined; - for (putters) |*t| { - t.* = try std.os.spawnThread(&context, startPuts); - } - var getters: [1]*std.os.Thread = undefined; - for (getters) |*t| { - t.* = try std.os.spawnThread(&context, startGets); - } - - for (putters) |t| - t.wait(); - _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); - for (getters) |t| - t.wait(); - - if (context.put_sum != context.get_sum) { - std.debug.panic("failure\nput_sum:{} != get_sum:{}", context.put_sum, context.get_sum); - } - - if (context.get_count != puts_per_thread * put_thread_count) { - std.debug.panic( - "failure\nget_count:{} != puts_per_thread:{} * put_thread_count:{}", - context.get_count, - u32(puts_per_thread), - u32(put_thread_count), - ); - } -} - -fn startPuts(ctx: *Context) u8 { - var put_count: usize = puts_per_thread; - var r = std.rand.DefaultPrng.init(0xdeadbeef); - while (put_count != 0) : (put_count -= 1) { - std.os.time.sleep(0, 1); // let the os scheduler be our fuzz - const x = @bitCast(i32, r.random.scalar(u32)); - const node = ctx.allocator.create(QueueMpsc(i32).Node{ - .next = undefined, - .data = x, - }) catch unreachable; - ctx.queue.put(node); - _ = @atomicRmw(isize, &ctx.put_sum, builtin.AtomicRmwOp.Add, x, AtomicOrder.SeqCst); - } - return 0; -} - -fn startGets(ctx: *Context) u8 { - while (true) { - const last = @atomicLoad(u8, &ctx.puts_done, builtin.AtomicOrder.SeqCst) == 1; - - while (ctx.queue.get()) |node| { - std.os.time.sleep(0, 1); // let the os scheduler be our fuzz - _ = @atomicRmw(isize, &ctx.get_sum, builtin.AtomicRmwOp.Add, node.data, builtin.AtomicOrder.SeqCst); - _ = @atomicRmw(usize, &ctx.get_count, builtin.AtomicRmwOp.Add, 1, builtin.AtomicOrder.SeqCst); - } - - if (last) return 0; - } -} diff --git a/std/atomic/stack.zig b/std/atomic/stack.zig index d74bee8e8b..16d5c9503b 100644 --- a/std/atomic/stack.zig +++ b/std/atomic/stack.zig @@ -1,10 +1,13 @@ +const assert = std.debug.assert; const builtin = @import("builtin"); const AtomicOrder = builtin.AtomicOrder; -/// Many reader, many writer, non-allocating, thread-safe, lock-free +/// Many reader, many writer, non-allocating, thread-safe +/// Uses a spinlock to protect push() and pop() pub fn Stack(comptime T: type) type { return struct { root: ?*Node, + lock: u8, pub const Self = this; @@ -14,7 +17,10 @@ pub fn Stack(comptime T: type) type { }; pub fn init() Self { - return Self{ .root = null }; + return Self{ + .root = null, + .lock = 0, + }; } /// push operation, but only if you are the first item in the stack. if you did not succeed in @@ -25,18 +31,20 @@ pub fn Stack(comptime T: type) type { } pub fn push(self: *Self, node: *Node) void { - var root = @atomicLoad(?*Node, &self.root, AtomicOrder.SeqCst); - while (true) { - node.next = root; - root = @cmpxchgWeak(?*Node, &self.root, root, node, AtomicOrder.SeqCst, AtomicOrder.SeqCst) orelse break; - } + while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {} + defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1); + + node.next = self.root; + self.root = node; } pub fn pop(self: *Self) ?*Node { - var root = @atomicLoad(?*Node, &self.root, AtomicOrder.SeqCst); - while (true) { - root = @cmpxchgWeak(?*Node, &self.root, root, (root orelse return null).next, AtomicOrder.SeqCst, AtomicOrder.SeqCst) orelse return root; - } + while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {} + defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1); + + const root = self.root orelse return null; + self.root = root.next; + return root; } pub fn isEmpty(self: *Self) bool { @@ -45,7 +53,7 @@ pub fn Stack(comptime T: type) type { }; } -const std = @import("std"); +const std = @import("../index.zig"); const Context = struct { allocator: *std.mem.Allocator, stack: *Stack(i32), diff --git a/std/build.zig b/std/build.zig index 24fa85383a..cea760e8a2 100644 --- a/std/build.zig +++ b/std/build.zig @@ -1596,6 +1596,8 @@ pub const TestStep = struct { target: Target, exec_cmd_args: ?[]const ?[]const u8, include_dirs: ArrayList([]const u8), + lib_paths: ArrayList([]const u8), + object_files: ArrayList([]const u8), pub fn init(builder: *Builder, root_src: []const u8) TestStep { const step_name = builder.fmt("test {}", root_src); @@ -1611,9 +1613,15 @@ pub const TestStep = struct { .target = Target{ .Native = {} }, .exec_cmd_args = null, .include_dirs = ArrayList([]const u8).init(builder.allocator), + .lib_paths = ArrayList([]const u8).init(builder.allocator), + .object_files = ArrayList([]const u8).init(builder.allocator), }; } + pub fn addLibPath(self: *TestStep, path: []const u8) void { + self.lib_paths.append(path) catch unreachable; + } + pub fn setVerbose(self: *TestStep, value: bool) void { self.verbose = value; } @@ -1638,6 +1646,10 @@ pub const TestStep = struct { self.filter = text; } + pub fn addObjectFile(self: *TestStep, path: []const u8) void { + self.object_files.append(path) catch unreachable; + } + pub fn setTarget(self: *TestStep, target_arch: builtin.Arch, target_os: builtin.Os, target_environ: builtin.Environ) void { self.target = Target{ .Cross = CrossTarget{ @@ -1699,6 +1711,11 @@ pub const TestStep = struct { try zig_args.append(self.name_prefix); } + for (self.object_files.toSliceConst()) |object_file| { + try zig_args.append("--object"); + try zig_args.append(builder.pathFromRoot(object_file)); + } + { var it = self.link_libs.iterator(); while (true) { @@ -1734,6 +1751,11 @@ pub const TestStep = struct { try zig_args.append(rpath); } + for (self.lib_paths.toSliceConst()) |lib_path| { + try zig_args.append("--library-path"); + try zig_args.append(lib_path); + } + for (builder.lib_paths.toSliceConst()) |lib_path| { try zig_args.append("--library-path"); try zig_args.append(lib_path); diff --git a/std/c/darwin.zig b/std/c/darwin.zig index 133ef62f05..4189dfeadc 100644 --- a/std/c/darwin.zig +++ b/std/c/darwin.zig @@ -44,7 +44,7 @@ pub const timezone = extern struct { tz_dsttime: i32, }; -pub const mach_timebase_info_data = struct { +pub const mach_timebase_info_data = extern struct { numer: u32, denom: u32, }; diff --git a/std/event.zig b/std/event.zig index 7e9928b3d7..1e52086286 100644 --- a/std/event.zig +++ b/std/event.zig @@ -3,6 +3,8 @@ pub const Loop = @import("event/loop.zig").Loop; pub const Lock = @import("event/lock.zig").Lock; pub const tcp = @import("event/tcp.zig"); pub const Channel = @import("event/channel.zig").Channel; +pub const Group = @import("event/group.zig").Group; +pub const Future = @import("event/future.zig").Future; test "import event tests" { _ = @import("event/locked.zig"); @@ -10,4 +12,6 @@ test "import event tests" { _ = @import("event/lock.zig"); _ = @import("event/tcp.zig"); _ = @import("event/channel.zig"); + _ = @import("event/group.zig"); + _ = @import("event/future.zig"); } diff --git a/std/event/channel.zig b/std/event/channel.zig index 4b3a7177a2..d4d713bdee 100644 --- a/std/event/channel.zig +++ b/std/event/channel.zig @@ -12,8 +12,8 @@ pub fn Channel(comptime T: type) type { return struct { loop: *Loop, - getters: std.atomic.QueueMpsc(GetNode), - putters: std.atomic.QueueMpsc(PutNode), + getters: std.atomic.Queue(GetNode), + putters: std.atomic.Queue(PutNode), get_count: usize, put_count: usize, dispatch_lock: u8, // TODO make this a bool @@ -46,8 +46,8 @@ pub fn Channel(comptime T: type) type { .buffer_index = 0, .dispatch_lock = 0, .need_dispatch = 0, - .getters = std.atomic.QueueMpsc(GetNode).init(), - .putters = std.atomic.QueueMpsc(PutNode).init(), + .getters = std.atomic.Queue(GetNode).init(), + .putters = std.atomic.Queue(PutNode).init(), .get_count = 0, .put_count = 0, }); @@ -81,7 +81,7 @@ pub fn Channel(comptime T: type) type { .next = undefined, .data = handle, }; - var queue_node = std.atomic.QueueMpsc(PutNode).Node{ + var queue_node = std.atomic.Queue(PutNode).Node{ .data = PutNode{ .tick_node = &my_tick_node, .data = data, @@ -111,7 +111,7 @@ pub fn Channel(comptime T: type) type { .next = undefined, .data = handle, }; - var queue_node = std.atomic.QueueMpsc(GetNode).Node{ + var queue_node = std.atomic.Queue(GetNode).Node{ .data = GetNode{ .ptr = &result, .tick_node = &my_tick_node, diff --git a/std/event/future.zig b/std/event/future.zig new file mode 100644 index 0000000000..23fa570c8f --- /dev/null +++ b/std/event/future.zig @@ -0,0 +1,97 @@ +const std = @import("../index.zig"); +const assert = std.debug.assert; +const builtin = @import("builtin"); +const AtomicRmwOp = builtin.AtomicRmwOp; +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(). +/// 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. +pub fn Future(comptime T: type) type { + return struct { + lock: Lock, + data: T, + available: u8, // TODO make this a bool + + const Self = this; + const Queue = std.atomic.Queue(promise); + + pub fn init(loop: *Loop) Self { + return Self{ + .lock = Lock.initLocked(loop), + .available = 0, + .data = undefined, + }; + } + + /// Obtain the value. If it's not available, wait until it becomes + /// available. + /// Thread-safe. + pub async fn get(self: *Self) *T { + if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 1) { + return &self.data; + } + const held = await (async self.lock.acquire() catch unreachable); + held.release(); + + return &self.data; + } + + /// 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 + Lock.Held.release(Lock.Held{ .lock = &self.lock }); + } + }; +} + +test "std.event.Future" { + var da = std.heap.DirectAllocator.init(); + defer da.deinit(); + + const allocator = &da.allocator; + + var loop: Loop = undefined; + try loop.initMultiThreaded(allocator); + defer loop.deinit(); + + const handle = try async testFuture(&loop); + defer cancel handle; + + loop.run(); +} + +async fn testFuture(loop: *Loop) void { + suspend |p| { + resume p; + } + var future = Future(i32).init(loop); + + const a = async waitOnFuture(&future) catch @panic("memory"); + const b = async waitOnFuture(&future) catch @panic("memory"); + const c = async resolveFuture(&future) catch @panic("memory"); + + const result = (await a) + (await b); + cancel c; + assert(result == 12); +} + +async fn waitOnFuture(future: *Future(i32)) i32 { + suspend |p| { + resume p; + } + return (await (async future.get() catch @panic("memory"))).*; +} + +async fn resolveFuture(future: *Future(i32)) void { + suspend |p| { + resume p; + } + future.data = 6; + future.resolve(); +} diff --git a/std/event/group.zig b/std/event/group.zig new file mode 100644 index 0000000000..c286803b53 --- /dev/null +++ b/std/event/group.zig @@ -0,0 +1,158 @@ +const std = @import("../index.zig"); +const builtin = @import("builtin"); +const Lock = std.event.Lock; +const Loop = std.event.Loop; +const AtomicRmwOp = builtin.AtomicRmwOp; +const AtomicOrder = builtin.AtomicOrder; +const assert = std.debug.assert; + +/// ReturnType should be `void` or `E!void` +pub fn Group(comptime ReturnType: type) type { + return struct { + coro_stack: Stack, + alloc_stack: Stack, + lock: Lock, + + const Self = this; + + const Error = switch (@typeInfo(ReturnType)) { + builtin.TypeId.ErrorUnion => |payload| payload.error_set, + else => void, + }; + const Stack = std.atomic.Stack(promise->ReturnType); + + pub fn init(loop: *Loop) Self { + return Self{ + .coro_stack = Stack.init(), + .alloc_stack = Stack.init(), + .lock = Lock.init(loop), + }; + } + + /// Add a promise to the group. Thread-safe. + pub fn add(self: *Self, handle: promise->ReturnType) (error{OutOfMemory}!void) { + const node = try self.lock.loop.allocator.create(Stack.Node{ + .next = undefined, + .data = handle, + }); + self.alloc_stack.push(node); + } + + /// 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. + /// Thread-safe. + pub fn call(self: *Self, comptime func: var, args: ...) (error{OutOfMemory}!void) { + const S = struct { + async fn asyncFunc(node: **Stack.Node, args2: ...) ReturnType { + // TODO this is a hack to make the memory following be inside the coro frame + suspend |p| { + var my_node: Stack.Node = undefined; + node.* = &my_node; + resume p; + } + + // TODO this allocation elision should be guaranteed because we await it in + // this coro frame + return await (async func(args2) catch unreachable); + } + }; + var node: *Stack.Node = undefined; + const handle = try async S.asyncFunc(&node, args); + node.* = Stack.Node{ + .next = undefined, + .data = handle, + }; + self.coro_stack.push(node); + } + + /// Wait for all the calls and promises of the group to complete. + /// Thread-safe. + pub async fn wait(self: *Self) ReturnType { + // TODO catch unreachable because the allocation can be grouped with + // the coro frame allocation + const held = await (async self.lock.acquire() catch unreachable); + defer held.release(); + + while (self.coro_stack.pop()) |node| { + if (Error == void) { + await node.data; + } else { + (await node.data) catch |err| { + self.cancelAll(); + return err; + }; + } + } + while (self.alloc_stack.pop()) |node| { + const handle = node.data; + self.lock.loop.allocator.destroy(node); + if (Error == void) { + await handle; + } else { + (await handle) catch |err| { + self.cancelAll(); + return err; + }; + } + } + } + + /// Cancel all the outstanding promises. May only be called if wait was never called. + pub fn cancelAll(self: *Self) void { + while (self.coro_stack.pop()) |node| { + cancel node.data; + } + while (self.alloc_stack.pop()) |node| { + cancel node.data; + self.lock.loop.allocator.destroy(node); + } + } + }; +} + +test "std.event.Group" { + var da = std.heap.DirectAllocator.init(); + defer da.deinit(); + + const allocator = &da.allocator; + + var loop: Loop = undefined; + try loop.initMultiThreaded(allocator); + defer loop.deinit(); + + const handle = try async testGroup(&loop); + defer cancel handle; + + loop.run(); +} + +async fn testGroup(loop: *Loop) void { + var count: usize = 0; + var group = Group(void).init(loop); + group.add(async sleepALittle(&count) catch @panic("memory")) catch @panic("memory"); + group.call(increaseByTen, &count) catch @panic("memory"); + await (async group.wait() catch @panic("memory")); + assert(count == 11); + + var another = Group(error!void).init(loop); + another.add(async somethingElse() catch @panic("memory")) catch @panic("memory"); + another.call(doSomethingThatFails) catch @panic("memory"); + std.debug.assertError(await (async another.wait() catch @panic("memory")), error.ItBroke); +} + +async fn sleepALittle(count: *usize) void { + std.os.time.sleep(0, 1000000); + _ = @atomicRmw(usize, count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); +} + +async fn increaseByTen(count: *usize) void { + var i: usize = 0; + while (i < 10) : (i += 1) { + _ = @atomicRmw(usize, count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); + } +} + +async fn doSomethingThatFails() error!void {} +async fn somethingElse() error!void { + return error.ItBroke; +} diff --git a/std/event/lock.zig b/std/event/lock.zig index 2a8d5ada77..2013b5595f 100644 --- a/std/event/lock.zig +++ b/std/event/lock.zig @@ -15,7 +15,7 @@ pub const Lock = struct { queue: Queue, queue_empty_bit: u8, // TODO make this a bool - const Queue = std.atomic.QueueMpsc(promise); + const Queue = std.atomic.Queue(promise); pub const Held = struct { lock: *Lock, @@ -73,6 +73,15 @@ pub const Lock = struct { }; } + pub fn initLocked(loop: *Loop) Lock { + return Lock{ + .loop = loop, + .shared_bit = 1, + .queue = Queue.init(), + .queue_empty_bit = 1, + }; + } + /// Must be called when not locked. Not thread safe. /// All calls to acquire() and release() must complete before calling deinit(). pub fn deinit(self: *Lock) void { @@ -81,7 +90,7 @@ pub const Lock = struct { } pub async fn acquire(self: *Lock) Held { - s: suspend |handle| { + suspend |handle| { // TODO explicitly put this memory in the coroutine frame #1194 var my_tick_node = Loop.NextTickNode{ .data = handle, diff --git a/std/event/loop.zig b/std/event/loop.zig index 646f15875f..fc927592b9 100644 --- a/std/event/loop.zig +++ b/std/event/loop.zig @@ -9,7 +9,7 @@ const AtomicOrder = builtin.AtomicOrder; pub const Loop = struct { allocator: *mem.Allocator, - next_tick_queue: std.atomic.QueueMpsc(promise), + next_tick_queue: std.atomic.Queue(promise), os_data: OsData, final_resume_node: ResumeNode, dispatch_lock: u8, // TODO make this a bool @@ -21,7 +21,7 @@ pub const Loop = struct { available_eventfd_resume_nodes: std.atomic.Stack(ResumeNode.EventFd), eventfd_resume_nodes: []std.atomic.Stack(ResumeNode.EventFd).Node, - pub const NextTickNode = std.atomic.QueueMpsc(promise).Node; + pub const NextTickNode = std.atomic.Queue(promise).Node; pub const ResumeNode = struct { id: Id, @@ -77,7 +77,7 @@ pub const Loop = struct { .pending_event_count = 0, .allocator = allocator, .os_data = undefined, - .next_tick_queue = std.atomic.QueueMpsc(promise).init(), + .next_tick_queue = std.atomic.Queue(promise).init(), .dispatch_lock = 1, // start locked so threads go directly into epoll wait .extra_threads = undefined, .available_eventfd_resume_nodes = std.atomic.Stack(ResumeNode.EventFd).init(), @@ -101,7 +101,6 @@ pub const Loop = struct { errdefer self.deinitOsData(); } - /// must call stop before deinit pub fn deinit(self: *Loop) void { self.deinitOsData(); self.allocator.free(self.extra_threads); @@ -382,6 +381,21 @@ pub const Loop = struct { return async S.asyncFunc(self, &handle, args); } + /// Awaiting a yield lets the event loop run, starting any unstarted async operations. + /// Note that async operations automatically start when a function yields for any other reason, + /// for example, when async I/O is performed. This function is intended to be used only when + /// CPU bound tasks would be waiting in the event loop but never get started because no async I/O + /// is performed. + pub async fn yield(self: *Loop) void { + suspend |p| { + var my_tick_node = Loop.NextTickNode{ + .next = undefined, + .data = p, + }; + loop.onNextTick(&my_tick_node); + } + } + fn workerRun(self: *Loop) void { start_over: while (true) { if (@atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) == 0) { diff --git a/std/heap.zig b/std/heap.zig index ef22c8d0c5..f5e0484b25 100644 --- a/std/heap.zig +++ b/std/heap.zig @@ -302,8 +302,17 @@ pub const FixedBufferAllocator = struct { } fn realloc(allocator: *Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 { + const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator); + assert(old_mem.len <= self.end_index); if (new_size <= old_mem.len) { return old_mem[0..new_size]; + } else if (old_mem.ptr == self.buffer.ptr + self.end_index - old_mem.len) { + const start_index = self.end_index - old_mem.len; + const new_end_index = start_index + new_size; + if (new_end_index > self.buffer.len) return error.OutOfMemory; + const result = self.buffer[start_index..new_end_index]; + self.end_index = new_end_index; + return result; } else { const result = try alloc(allocator, new_size, alignment); mem.copy(u8, result, old_mem); @@ -442,6 +451,7 @@ test "DirectAllocator" { const allocator = &direct_allocator.allocator; try testAllocator(allocator); + try testAllocatorAligned(allocator, 16); try testAllocatorLargeAlignment(allocator); } @@ -453,6 +463,7 @@ test "ArenaAllocator" { defer arena_allocator.deinit(); try testAllocator(&arena_allocator.allocator); + try testAllocatorAligned(&arena_allocator.allocator, 16); try testAllocatorLargeAlignment(&arena_allocator.allocator); } @@ -461,35 +472,98 @@ test "FixedBufferAllocator" { var fixed_buffer_allocator = FixedBufferAllocator.init(test_fixed_buffer_allocator_memory[0..]); try testAllocator(&fixed_buffer_allocator.allocator); + try testAllocatorAligned(&fixed_buffer_allocator.allocator, 16); try testAllocatorLargeAlignment(&fixed_buffer_allocator.allocator); } +test "FixedBufferAllocator Reuse memory on realloc" { + var small_fixed_buffer: [10]u8 = undefined; + // check if we re-use the memory + { + var fixed_buffer_allocator = FixedBufferAllocator.init(small_fixed_buffer[0..]); + + var slice0 = try fixed_buffer_allocator.allocator.alloc(u8, 5); + assert(slice0.len == 5); + var slice1 = try fixed_buffer_allocator.allocator.realloc(u8, slice0, 10); + assert(slice1.ptr == slice0.ptr); + assert(slice1.len == 10); + debug.assertError(fixed_buffer_allocator.allocator.realloc(u8, slice1, 11), error.OutOfMemory); + } + // check that we don't re-use the memory if it's not the most recent block + { + var fixed_buffer_allocator = FixedBufferAllocator.init(small_fixed_buffer[0..]); + + var slice0 = try fixed_buffer_allocator.allocator.alloc(u8, 2); + slice0[0] = 1; + slice0[1] = 2; + var slice1 = try fixed_buffer_allocator.allocator.alloc(u8, 2); + var slice2 = try fixed_buffer_allocator.allocator.realloc(u8, slice0, 4); + assert(slice0.ptr != slice2.ptr); + assert(slice1.ptr != slice2.ptr); + assert(slice2[0] == 1); + assert(slice2[1] == 2); + } +} + test "ThreadSafeFixedBufferAllocator" { var fixed_buffer_allocator = ThreadSafeFixedBufferAllocator.init(test_fixed_buffer_allocator_memory[0..]); try testAllocator(&fixed_buffer_allocator.allocator); + try testAllocatorAligned(&fixed_buffer_allocator.allocator, 16); try testAllocatorLargeAlignment(&fixed_buffer_allocator.allocator); } fn testAllocator(allocator: *mem.Allocator) !void { var slice = try allocator.alloc(*i32, 100); - + assert(slice.len == 100); for (slice) |*item, i| { item.* = try allocator.create(@intCast(i32, i)); } - for (slice) |item, i| { + slice = try allocator.realloc(*i32, slice, 20000); + assert(slice.len == 20000); + + for (slice[0..100]) |item, i| { + assert(item.* == @intCast(i32, i)); allocator.destroy(item); } - slice = try allocator.realloc(*i32, slice, 20000); slice = try allocator.realloc(*i32, slice, 50); + assert(slice.len == 50); slice = try allocator.realloc(*i32, slice, 25); + assert(slice.len == 25); + slice = try allocator.realloc(*i32, slice, 0); + assert(slice.len == 0); slice = try allocator.realloc(*i32, slice, 10); + assert(slice.len == 10); allocator.free(slice); } +fn testAllocatorAligned(allocator: *mem.Allocator, comptime alignment: u29) !void { + // initial + var slice = try allocator.alignedAlloc(u8, alignment, 10); + assert(slice.len == 10); + // grow + slice = try allocator.alignedRealloc(u8, alignment, slice, 100); + assert(slice.len == 100); + // shrink + slice = try allocator.alignedRealloc(u8, alignment, slice, 10); + assert(slice.len == 10); + // go to zero + slice = try allocator.alignedRealloc(u8, alignment, slice, 0); + assert(slice.len == 0); + // realloc from zero + slice = try allocator.alignedRealloc(u8, alignment, slice, 100); + assert(slice.len == 100); + // shrink with shrink + slice = allocator.alignedShrink(u8, alignment, slice, 10); + assert(slice.len == 10); + // shrink to zero + slice = allocator.alignedShrink(u8, alignment, slice, 0); + assert(slice.len == 0); +} + fn testAllocatorLargeAlignment(allocator: *mem.Allocator) mem.Allocator.Error!void { //Maybe a platform's page_size is actually the same as or // very near usize? diff --git a/std/macho.zig b/std/macho.zig index fe5409ad4d..33c170ff43 100644 --- a/std/macho.zig +++ b/std/macho.zig @@ -42,7 +42,7 @@ pub const Symbol = struct { name: []const u8, address: u64, - fn addressLessThan(lhs: *const Symbol, rhs: *const Symbol) bool { + fn addressLessThan(lhs: Symbol, rhs: Symbol) bool { return lhs.address < rhs.address; } }; diff --git a/std/mem.zig b/std/mem.zig index 555e1e249d..2a5b0366a9 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -23,7 +23,10 @@ pub const Allocator = struct { /// * this function must return successfully. /// * alignment <= alignment of old_mem.ptr /// - /// The returned newly allocated memory is undefined. + /// When `reallocFn` returns, + /// `return_value[0..min(old_mem.len, new_byte_count)]` must be the same + /// as `old_mem` was when `reallocFn` is called. The bytes of + /// `return_value[old_mem.len..]` have undefined values. /// `alignment` is guaranteed to be >= 1 /// `alignment` is guaranteed to be a power of 2 reallocFn: fn (self: *Allocator, old_mem: []u8, new_byte_count: usize, alignment: u29) Error![]u8, @@ -71,7 +74,7 @@ pub const Allocator = struct { pub fn alignedRealloc(self: *Allocator, comptime T: type, comptime alignment: u29, old_mem: []align(alignment) T, n: usize) ![]align(alignment) T { if (old_mem.len == 0) { - return self.alloc(T, n); + return self.alignedAlloc(T, alignment, n); } if (n == 0) { self.free(old_mem); @@ -125,6 +128,7 @@ pub const Allocator = struct { /// Copy all of source into dest at position 0. /// dest.len must be >= source.len. +/// dest.ptr must be <= src.ptr. pub fn copy(comptime T: type, dest: []T, source: []const T) void { // TODO instead of manually doing this check for the whole array // and turning off runtime safety, the compiler should detect loops like @@ -135,6 +139,23 @@ pub fn copy(comptime T: type, dest: []T, source: []const T) void { dest[i] = s; } +/// Copy all of source into dest at position 0. +/// dest.len must be >= source.len. +/// dest.ptr must be >= src.ptr. +pub fn copyBackwards(comptime T: type, dest: []T, source: []const T) void { + // TODO instead of manually doing this check for the whole array + // and turning off runtime safety, the compiler should detect loops like + // this and automatically omit safety checks for loops + @setRuntimeSafety(false); + assert(dest.len >= source.len); + var i = source.len; + while(i > 0){ + i -= 1; + dest[i] = source[i]; + } +} + + pub fn set(comptime T: type, dest: []T, value: T) void { for (dest) |*d| d.* = value; diff --git a/std/os/windows/index.zig b/std/os/windows/index.zig index f73b8ec261..d3525247c9 100644 --- a/std/os/windows/index.zig +++ b/std/os/windows/index.zig @@ -59,7 +59,6 @@ pub extern "kernel32" stdcallcc fn CreateSymbolicLinkA( dwFlags: DWORD, ) BOOLEAN; - pub extern "kernel32" stdcallcc fn CreateIoCompletionPort(FileHandle: HANDLE, ExistingCompletionPort: ?HANDLE, CompletionKey: ULONG_PTR, NumberOfConcurrentThreads: DWORD) ?HANDLE; pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?LPDWORD) ?HANDLE; @@ -134,7 +133,6 @@ pub extern "kernel32" stdcallcc fn MoveFileExA( dwFlags: DWORD, ) BOOL; - pub extern "kernel32" stdcallcc fn PostQueuedCompletionStatus(CompletionPort: HANDLE, dwNumberOfBytesTransferred: DWORD, dwCompletionKey: ULONG_PTR, lpOverlapped: ?*OVERLAPPED) BOOL; pub extern "kernel32" stdcallcc fn QueryPerformanceCounter(lpPerformanceCount: *LARGE_INTEGER) BOOL; diff --git a/std/os/windows/util.zig b/std/os/windows/util.zig index b04e8efc4b..dda9ce7a8b 100644 --- a/std/os/windows/util.zig +++ b/std/os/windows/util.zig @@ -215,10 +215,7 @@ pub fn windowsFindNextFile(handle: windows.HANDLE, find_file_data: *windows.WIN3 return true; } - -pub const WindowsCreateIoCompletionPortError = error { - Unexpected, -}; +pub const WindowsCreateIoCompletionPortError = error{Unexpected}; pub fn windowsCreateIoCompletionPort(file_handle: windows.HANDLE, existing_completion_port: ?windows.HANDLE, completion_key: usize, concurrent_thread_count: windows.DWORD) !windows.HANDLE { const handle = windows.CreateIoCompletionPort(file_handle, existing_completion_port, completion_key, concurrent_thread_count) orelse { @@ -230,9 +227,7 @@ pub fn windowsCreateIoCompletionPort(file_handle: windows.HANDLE, existing_compl return handle; } -pub const WindowsPostQueuedCompletionStatusError = error { - Unexpected, -}; +pub const WindowsPostQueuedCompletionStatusError = error{Unexpected}; pub fn windowsPostQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_transferred_count: windows.DWORD, completion_key: usize, lpOverlapped: ?*windows.OVERLAPPED) WindowsPostQueuedCompletionStatusError!void { if (windows.PostQueuedCompletionStatus(completion_port, bytes_transferred_count, completion_key, lpOverlapped) == 0) { @@ -243,7 +238,7 @@ pub fn windowsPostQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_ } } -pub const WindowsWaitResult = error { +pub const WindowsWaitResult = error{ Normal, Aborted, }; diff --git a/std/sort.zig b/std/sort.zig index 1b44c18dd9..f29f51b7cf 100644 --- a/std/sort.zig +++ b/std/sort.zig @@ -5,7 +5,7 @@ const math = std.math; const builtin = @import("builtin"); /// Stable in-place sort. O(n) best case, O(pow(n, 2)) worst case. O(1) memory (no allocator required). -pub fn insertionSort(comptime T: type, items: []T, lessThan: fn (lhs: *const T, rhs: *const T) bool) void { +pub fn insertionSort(comptime T: type, items: []T, lessThan: fn (lhs: T, rhs: T) bool) void { { var i: usize = 1; while (i < items.len) : (i += 1) { @@ -30,7 +30,7 @@ const Range = struct { }; } - fn length(self: *const Range) usize { + fn length(self: Range) usize { return self.end - self.start; } }; @@ -108,7 +108,7 @@ const Pull = struct { /// Stable in-place sort. O(n) best case, O(n*log(n)) worst case and average case. O(1) memory (no allocator required). /// Currently implemented as block sort. -pub fn sort(comptime T: type, items: []T, lessThan: fn (lhs: *const T, rhs: *const T) bool) void { +pub fn sort(comptime T: type, items: []T, lessThan: fn (lhs: T, rhs: T) bool) void { // Implementation ported from https://github.com/BonzaiThePenguin/WikiSort/blob/master/WikiSort.c var cache: [512]T = undefined; @@ -131,16 +131,7 @@ pub fn sort(comptime T: type, items: []T, lessThan: fn (lhs: *const T, rhs: *con // http://pages.ripco.net/~jgamble/nw.html var iterator = Iterator.init(items.len, 4); while (!iterator.finished()) { - var order = []u8{ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - }; + var order = []u8{ 0, 1, 2, 3, 4, 5, 6, 7 }; const range = iterator.nextRange(); const sliced_items = items[range.start..]; @@ -741,7 +732,7 @@ pub fn sort(comptime T: type, items: []T, lessThan: fn (lhs: *const T, rhs: *con } // merge operation without a buffer -fn mergeInPlace(comptime T: type, items: []T, A_arg: *const Range, B_arg: *const Range, lessThan: fn (*const T, *const T) bool) void { +fn mergeInPlace(comptime T: type, items: []T, A_arg: Range, B_arg: Range, lessThan: fn (T, T) bool) void { if (A_arg.length() == 0 or B_arg.length() == 0) return; // this just repeatedly binary searches into B and rotates A into position. @@ -762,8 +753,8 @@ fn mergeInPlace(comptime T: type, items: []T, A_arg: *const Range, B_arg: *const // again, this is NOT a general-purpose solution – it only works well in this case! // kind of like how the O(n^2) insertion sort is used in some places - var A = A_arg.*; - var B = B_arg.*; + var A = A_arg; + var B = B_arg; while (true) { // find the first place in B where the first item in A needs to be inserted @@ -783,7 +774,7 @@ fn mergeInPlace(comptime T: type, items: []T, A_arg: *const Range, B_arg: *const } // merge operation using an internal buffer -fn mergeInternal(comptime T: type, items: []T, A: *const Range, B: *const Range, lessThan: fn (*const T, *const T) bool, buffer: *const Range) void { +fn mergeInternal(comptime T: type, items: []T, A: Range, B: Range, lessThan: fn (T, T) bool, buffer: Range) void { // whenever we find a value to add to the final array, swap it with the value that's already in that spot // when this algorithm is finished, 'buffer' will contain its original contents, but in a different order var A_count: usize = 0; @@ -819,7 +810,7 @@ fn blockSwap(comptime T: type, items: []T, start1: usize, start2: usize, block_s // combine a linear search with a binary search to reduce the number of comparisons in situations // where have some idea as to how many unique values there are and where the next value might be -fn findFirstForward(comptime T: type, items: []T, value: *const T, range: *const Range, lessThan: fn (*const T, *const T) bool, unique: usize) usize { +fn findFirstForward(comptime T: type, items: []T, value: T, range: Range, lessThan: fn (T, T) bool, unique: usize) usize { if (range.length() == 0) return range.start; const skip = math.max(range.length() / unique, usize(1)); @@ -833,7 +824,7 @@ fn findFirstForward(comptime T: type, items: []T, value: *const T, range: *const return binaryFirst(T, items, value, Range.init(index - skip, index), lessThan); } -fn findFirstBackward(comptime T: type, items: []T, value: *const T, range: *const Range, lessThan: fn (*const T, *const T) bool, unique: usize) usize { +fn findFirstBackward(comptime T: type, items: []T, value: T, range: Range, lessThan: fn (T, T) bool, unique: usize) usize { if (range.length() == 0) return range.start; const skip = math.max(range.length() / unique, usize(1)); @@ -847,7 +838,7 @@ fn findFirstBackward(comptime T: type, items: []T, value: *const T, range: *cons return binaryFirst(T, items, value, Range.init(index, index + skip), lessThan); } -fn findLastForward(comptime T: type, items: []T, value: *const T, range: *const Range, lessThan: fn (*const T, *const T) bool, unique: usize) usize { +fn findLastForward(comptime T: type, items: []T, value: T, range: Range, lessThan: fn (T, T) bool, unique: usize) usize { if (range.length() == 0) return range.start; const skip = math.max(range.length() / unique, usize(1)); @@ -861,7 +852,7 @@ fn findLastForward(comptime T: type, items: []T, value: *const T, range: *const return binaryLast(T, items, value, Range.init(index - skip, index), lessThan); } -fn findLastBackward(comptime T: type, items: []T, value: *const T, range: *const Range, lessThan: fn (*const T, *const T) bool, unique: usize) usize { +fn findLastBackward(comptime T: type, items: []T, value: T, range: Range, lessThan: fn (T, T) bool, unique: usize) usize { if (range.length() == 0) return range.start; const skip = math.max(range.length() / unique, usize(1)); @@ -875,7 +866,7 @@ fn findLastBackward(comptime T: type, items: []T, value: *const T, range: *const return binaryLast(T, items, value, Range.init(index, index + skip), lessThan); } -fn binaryFirst(comptime T: type, items: []T, value: *const T, range: *const Range, lessThan: fn (*const T, *const T) bool) usize { +fn binaryFirst(comptime T: type, items: []T, value: T, range: Range, lessThan: fn (T, T) bool) usize { var start = range.start; var end = range.end - 1; if (range.start >= range.end) return range.end; @@ -893,7 +884,7 @@ fn binaryFirst(comptime T: type, items: []T, value: *const T, range: *const Rang return start; } -fn binaryLast(comptime T: type, items: []T, value: *const T, range: *const Range, lessThan: fn (*const T, *const T) bool) usize { +fn binaryLast(comptime T: type, items: []T, value: T, range: Range, lessThan: fn (T, T) bool) usize { var start = range.start; var end = range.end - 1; if (range.start >= range.end) return range.end; @@ -911,7 +902,7 @@ fn binaryLast(comptime T: type, items: []T, value: *const T, range: *const Range return start; } -fn mergeInto(comptime T: type, from: []T, A: *const Range, B: *const Range, lessThan: fn (*const T, *const T) bool, into: []T) void { +fn mergeInto(comptime T: type, from: []T, A: Range, B: Range, lessThan: fn (T, T) bool, into: []T) void { var A_index: usize = A.start; var B_index: usize = B.start; const A_last = A.end; @@ -941,7 +932,7 @@ fn mergeInto(comptime T: type, from: []T, A: *const Range, B: *const Range, less } } -fn mergeExternal(comptime T: type, items: []T, A: *const Range, B: *const Range, lessThan: fn (*const T, *const T) bool, cache: []T) void { +fn mergeExternal(comptime T: type, items: []T, A: Range, B: Range, lessThan: fn (T, T) bool, cache: []T) void { // A fits into the cache, so use that instead of the internal buffer var A_index: usize = 0; var B_index: usize = B.start; @@ -969,27 +960,32 @@ fn mergeExternal(comptime T: type, items: []T, A: *const Range, B: *const Range, mem.copy(T, items[insert_index..], cache[A_index..A_last]); } -fn swap(comptime T: type, items: []T, lessThan: fn (lhs: *const T, rhs: *const T) bool, order: *[8]u8, x: usize, y: usize) void { +fn swap(comptime T: type, items: []T, lessThan: fn (lhs: T, rhs: T) bool, order: *[8]u8, x: usize, y: usize) void { if (lessThan(items[y], items[x]) or ((order.*)[x] > (order.*)[y] and !lessThan(items[x], items[y]))) { mem.swap(T, &items[x], &items[y]); mem.swap(u8, &(order.*)[x], &(order.*)[y]); } } -fn i32asc(lhs: *const i32, rhs: *const i32) bool { - return lhs.* < rhs.*; +// Use these to generate a comparator function for a given type. e.g. `sort(u8, slice, asc(u8))`. +pub fn asc(comptime T: type) fn (T, T) bool { + const impl = struct { + fn inner(a: T, b: T) bool { + return a < b; + } + }; + + return impl.inner; } -fn i32desc(lhs: *const i32, rhs: *const i32) bool { - return rhs.* < lhs.*; -} +pub fn desc(comptime T: type) fn (T, T) bool { + const impl = struct { + fn inner(a: T, b: T) bool { + return a > b; + } + }; -fn u8asc(lhs: *const u8, rhs: *const u8) bool { - return lhs.* < rhs.*; -} - -fn u8desc(lhs: *const u8, rhs: *const u8) bool { - return rhs.* < lhs.*; + return impl.inner; } test "stable sort" { @@ -998,119 +994,38 @@ test "stable sort" { } fn testStableSort() void { var expected = []IdAndValue{ - IdAndValue{ - .id = 0, - .value = 0, - }, - IdAndValue{ - .id = 1, - .value = 0, - }, - IdAndValue{ - .id = 2, - .value = 0, - }, - IdAndValue{ - .id = 0, - .value = 1, - }, - IdAndValue{ - .id = 1, - .value = 1, - }, - IdAndValue{ - .id = 2, - .value = 1, - }, - IdAndValue{ - .id = 0, - .value = 2, - }, - IdAndValue{ - .id = 1, - .value = 2, - }, - IdAndValue{ - .id = 2, - .value = 2, - }, + IdAndValue{ .id = 0, .value = 0 }, + IdAndValue{ .id = 1, .value = 0 }, + IdAndValue{ .id = 2, .value = 0 }, + IdAndValue{ .id = 0, .value = 1 }, + IdAndValue{ .id = 1, .value = 1 }, + IdAndValue{ .id = 2, .value = 1 }, + IdAndValue{ .id = 0, .value = 2 }, + IdAndValue{ .id = 1, .value = 2 }, + IdAndValue{ .id = 2, .value = 2 }, }; var cases = [][9]IdAndValue{ []IdAndValue{ - IdAndValue{ - .id = 0, - .value = 0, - }, - IdAndValue{ - .id = 0, - .value = 1, - }, - IdAndValue{ - .id = 0, - .value = 2, - }, - IdAndValue{ - .id = 1, - .value = 0, - }, - IdAndValue{ - .id = 1, - .value = 1, - }, - IdAndValue{ - .id = 1, - .value = 2, - }, - IdAndValue{ - .id = 2, - .value = 0, - }, - IdAndValue{ - .id = 2, - .value = 1, - }, - IdAndValue{ - .id = 2, - .value = 2, - }, + IdAndValue{ .id = 0, .value = 0 }, + IdAndValue{ .id = 0, .value = 1 }, + IdAndValue{ .id = 0, .value = 2 }, + IdAndValue{ .id = 1, .value = 0 }, + IdAndValue{ .id = 1, .value = 1 }, + IdAndValue{ .id = 1, .value = 2 }, + IdAndValue{ .id = 2, .value = 0 }, + IdAndValue{ .id = 2, .value = 1 }, + IdAndValue{ .id = 2, .value = 2 }, }, []IdAndValue{ - IdAndValue{ - .id = 0, - .value = 2, - }, - IdAndValue{ - .id = 0, - .value = 1, - }, - IdAndValue{ - .id = 0, - .value = 0, - }, - IdAndValue{ - .id = 1, - .value = 2, - }, - IdAndValue{ - .id = 1, - .value = 1, - }, - IdAndValue{ - .id = 1, - .value = 0, - }, - IdAndValue{ - .id = 2, - .value = 2, - }, - IdAndValue{ - .id = 2, - .value = 1, - }, - IdAndValue{ - .id = 2, - .value = 0, - }, + IdAndValue{ .id = 0, .value = 2 }, + IdAndValue{ .id = 0, .value = 1 }, + IdAndValue{ .id = 0, .value = 0 }, + IdAndValue{ .id = 1, .value = 2 }, + IdAndValue{ .id = 1, .value = 1 }, + IdAndValue{ .id = 1, .value = 0 }, + IdAndValue{ .id = 2, .value = 2 }, + IdAndValue{ .id = 2, .value = 1 }, + IdAndValue{ .id = 2, .value = 0 }, }, }; for (cases) |*case| { @@ -1125,8 +1040,8 @@ const IdAndValue = struct { id: usize, value: i32, }; -fn cmpByValue(a: *const IdAndValue, b: *const IdAndValue) bool { - return i32asc(a.value, b.value); +fn cmpByValue(a: IdAndValue, b: IdAndValue) bool { + return asc(i32)(a.value, b.value); } test "std.sort" { @@ -1161,7 +1076,7 @@ test "std.sort" { var buf: [8]u8 = undefined; const slice = buf[0..case[0].len]; mem.copy(u8, slice, case[0]); - sort(u8, slice, u8asc); + sort(u8, slice, asc(u8)); assert(mem.eql(u8, slice, case[1])); } @@ -1175,48 +1090,20 @@ test "std.sort" { []i32{1}, }, [][]const i32{ - []i32{ - 0, - 1, - }, - []i32{ - 0, - 1, - }, + []i32{ 0, 1 }, + []i32{ 0, 1 }, }, [][]const i32{ - []i32{ - 1, - 0, - }, - []i32{ - 0, - 1, - }, + []i32{ 1, 0 }, + []i32{ 0, 1 }, }, [][]const i32{ - []i32{ - 1, - -1, - 0, - }, - []i32{ - -1, - 0, - 1, - }, + []i32{ 1, -1, 0 }, + []i32{ -1, 0, 1 }, }, [][]const i32{ - []i32{ - 2, - 1, - 3, - }, - []i32{ - 1, - 2, - 3, - }, + []i32{ 2, 1, 3 }, + []i32{ 1, 2, 3 }, }, }; @@ -1224,7 +1111,7 @@ test "std.sort" { var buf: [8]i32 = undefined; const slice = buf[0..case[0].len]; mem.copy(i32, slice, case[0]); - sort(i32, slice, i32asc); + sort(i32, slice, asc(i32)); assert(mem.eql(i32, slice, case[1])); } } @@ -1240,48 +1127,20 @@ test "std.sort descending" { []i32{1}, }, [][]const i32{ - []i32{ - 0, - 1, - }, - []i32{ - 1, - 0, - }, + []i32{ 0, 1 }, + []i32{ 1, 0 }, }, [][]const i32{ - []i32{ - 1, - 0, - }, - []i32{ - 1, - 0, - }, + []i32{ 1, 0 }, + []i32{ 1, 0 }, }, [][]const i32{ - []i32{ - 1, - -1, - 0, - }, - []i32{ - 1, - 0, - -1, - }, + []i32{ 1, -1, 0 }, + []i32{ 1, 0, -1 }, }, [][]const i32{ - []i32{ - 2, - 1, - 3, - }, - []i32{ - 3, - 2, - 1, - }, + []i32{ 2, 1, 3 }, + []i32{ 3, 2, 1 }, }, }; @@ -1289,28 +1148,16 @@ test "std.sort descending" { var buf: [8]i32 = undefined; const slice = buf[0..case[0].len]; mem.copy(i32, slice, case[0]); - sort(i32, slice, i32desc); + sort(i32, slice, desc(i32)); assert(mem.eql(i32, slice, case[1])); } } test "another sort case" { - var arr = []i32{ - 5, - 3, - 1, - 2, - 4, - }; - sort(i32, arr[0..], i32asc); + var arr = []i32{ 5, 3, 1, 2, 4 }; + sort(i32, arr[0..], asc(i32)); - assert(mem.eql(i32, arr, []i32{ - 1, - 2, - 3, - 4, - 5, - })); + assert(mem.eql(i32, arr, []i32{ 1, 2, 3, 4, 5 })); } test "sort fuzz testing" { @@ -1345,7 +1192,7 @@ fn fuzzTest(rng: *std.rand.Random) void { } } -pub fn min(comptime T: type, items: []T, lessThan: fn (lhs: *const T, rhs: *const T) bool) T { +pub fn min(comptime T: type, items: []T, lessThan: fn (lhs: T, rhs: T) bool) T { var i: usize = 0; var smallest = items[0]; for (items[1..]) |item| { @@ -1356,7 +1203,7 @@ pub fn min(comptime T: type, items: []T, lessThan: fn (lhs: *const T, rhs: *cons return smallest; } -pub fn max(comptime T: type, items: []T, lessThan: fn (lhs: *const T, rhs: *const T) bool) T { +pub fn max(comptime T: type, items: []T, lessThan: fn (lhs: T, rhs: T) bool) T { var i: usize = 0; var biggest = items[0]; for (items[1..]) |item| { diff --git a/std/zig/ast.zig b/std/zig/ast.zig index 63518c5182..004f9278b9 100644 --- a/std/zig/ast.zig +++ b/std/zig/ast.zig @@ -970,14 +970,8 @@ pub const Node = struct { pub const Defer = struct { base: Node, defer_token: TokenIndex, - kind: Kind, expr: *Node, - const Kind = enum { - Error, - Unconditional, - }; - pub fn iterate(self: *Defer, index: usize) ?*Node { var i = index; diff --git a/std/zig/parse.zig b/std/zig/parse.zig index 9f0371d4da..9842ba2a17 100644 --- a/std/zig/parse.zig +++ b/std/zig/parse.zig @@ -1041,11 +1041,6 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { const node = try arena.create(ast.Node.Defer{ .base = ast.Node{ .id = ast.Node.Id.Defer }, .defer_token = token_index, - .kind = switch (token_ptr.id) { - Token.Id.Keyword_defer => ast.Node.Defer.Kind.Unconditional, - Token.Id.Keyword_errdefer => ast.Node.Defer.Kind.Error, - else => unreachable, - }, .expr = undefined, }); const node_ptr = try block.statements.addOne(); diff --git a/test/behavior.zig b/test/behavior.zig index 450dded56c..21b1c597e1 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -9,6 +9,7 @@ comptime { _ = @import("cases/bitcast.zig"); _ = @import("cases/bool.zig"); _ = @import("cases/bugs/1111.zig"); + _ = @import("cases/bugs/1230.zig"); _ = @import("cases/bugs/394.zig"); _ = @import("cases/bugs/655.zig"); _ = @import("cases/bugs/656.zig"); diff --git a/test/cases/bugs/1230.zig b/test/cases/bugs/1230.zig new file mode 100644 index 0000000000..b782a77f0b --- /dev/null +++ b/test/cases/bugs/1230.zig @@ -0,0 +1,14 @@ +const assert = @import("std").debug.assert; + +const S = extern struct { + x: i32, +}; + +extern fn ret_struct() S { + return S{ .x = 42 }; +} + +test "extern return small struct (bug 1230)" { + const s = ret_struct(); + assert(s.x == 42); +} diff --git a/test/cases/optional.zig b/test/cases/optional.zig index 0129252dab..d43682bbec 100644 --- a/test/cases/optional.zig +++ b/test/cases/optional.zig @@ -7,3 +7,24 @@ test "optional pointer to size zero struct" { var o: ?*EmptyStruct = &e; assert(o != null); } + +test "equality compare nullable pointers" { + testNullPtrsEql(); + comptime testNullPtrsEql(); +} + +fn testNullPtrsEql() void { + var number: i32 = 1234; + + var x: ?*i32 = null; + var y: ?*i32 = null; + assert(x == y); + y = &number; + assert(x != y); + assert(x != &number); + assert(&number != x); + x = &number; + assert(x == y); + assert(x == &number); + assert(&number == x); +} diff --git a/test/compile_errors.zig b/test/compile_errors.zig index a6db8d50b4..58c73b8ae4 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,6 +1,33 @@ const tests = @import("tests.zig"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add( + "optional pointer to void in extern struct", + \\const Foo = extern struct { + \\ x: ?*const void, + \\}; + \\const Bar = extern struct { + \\ foo: Foo, + \\ y: i32, + \\}; + \\export fn entry(bar: *Bar) void {} + , + ".tmp_source.zig:2:5: error: extern structs cannot contain fields of type '?*const void'", + ); + + cases.add( + "use of comptime-known undefined function value", + \\const Cmd = struct { + \\ exec: fn () void, + \\}; + \\export fn entry() void { + \\ const command = Cmd{ .exec = undefined }; + \\ command.exec(); + \\} + , + ".tmp_source.zig:6:12: error: use of undefined value", + ); + cases.add( "use of comptime-known undefined function value", \\const Cmd = struct { diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig new file mode 100644 index 0000000000..1dca908e69 --- /dev/null +++ b/test/stage2/compile_errors.zig @@ -0,0 +1,12 @@ +const TestContext = @import("../../src-self-hosted/test.zig").TestContext; + +pub fn addCases(ctx: *TestContext) !void { + try ctx.testCompileError( + \\export fn entry() void {} + \\export fn entry() void {} + , "1.zig", 2, 8, "exported symbol collision: 'entry'"); + + try ctx.testCompileError( + \\fn() void {} + , "1.zig", 1, 1, "missing function name"); +} diff --git a/test/tests.zig b/test/tests.zig index 66eb2d93a0..3a72f58753 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -47,12 +47,13 @@ const test_targets = []TestTarget{ const max_stdout_size = 1 * 1024 * 1024; // 1 MB -pub fn addCompareOutputTests(b: *build.Builder, test_filter: ?[]const u8) *build.Step { +pub fn addCompareOutputTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step { const cases = b.allocator.create(CompareOutputContext{ .b = b, .step = b.step("test-compare-output", "Run the compare output tests"), .test_index = 0, .test_filter = test_filter, + .modes = modes, }) catch unreachable; compare_output.addCases(cases); @@ -60,12 +61,13 @@ pub fn addCompareOutputTests(b: *build.Builder, test_filter: ?[]const u8) *build return cases.step; } -pub fn addRuntimeSafetyTests(b: *build.Builder, test_filter: ?[]const u8) *build.Step { +pub fn addRuntimeSafetyTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step { const cases = b.allocator.create(CompareOutputContext{ .b = b, .step = b.step("test-runtime-safety", "Run the runtime safety tests"), .test_index = 0, .test_filter = test_filter, + .modes = modes, }) catch unreachable; runtime_safety.addCases(cases); @@ -73,12 +75,13 @@ pub fn addRuntimeSafetyTests(b: *build.Builder, test_filter: ?[]const u8) *build return cases.step; } -pub fn addCompileErrorTests(b: *build.Builder, test_filter: ?[]const u8) *build.Step { +pub fn addCompileErrorTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step { const cases = b.allocator.create(CompileErrorContext{ .b = b, .step = b.step("test-compile-errors", "Run the compile error tests"), .test_index = 0, .test_filter = test_filter, + .modes = modes, }) catch unreachable; compile_errors.addCases(cases); @@ -99,12 +102,13 @@ pub fn addBuildExampleTests(b: *build.Builder, test_filter: ?[]const u8) *build. return cases.step; } -pub fn addAssembleAndLinkTests(b: *build.Builder, test_filter: ?[]const u8) *build.Step { +pub fn addAssembleAndLinkTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step { const cases = b.allocator.create(CompareOutputContext{ .b = b, .step = b.step("test-asm-link", "Run the assemble and link tests"), .test_index = 0, .test_filter = test_filter, + .modes = modes, }) catch unreachable; assemble_and_link.addCases(cases); @@ -138,16 +142,11 @@ pub fn addGenHTests(b: *build.Builder, test_filter: ?[]const u8) *build.Step { return cases.step; } -pub fn addPkgTests(b: *build.Builder, test_filter: ?[]const u8, root_src: []const u8, name: []const u8, desc: []const u8, with_lldb: bool) *build.Step { +pub fn addPkgTests(b: *build.Builder, test_filter: ?[]const u8, root_src: []const u8, name: []const u8, desc: []const u8, modes: []const Mode) *build.Step { const step = b.step(b.fmt("test-{}", name), desc); for (test_targets) |test_target| { const is_native = (test_target.os == builtin.os and test_target.arch == builtin.arch); - for ([]Mode{ - Mode.Debug, - Mode.ReleaseSafe, - Mode.ReleaseFast, - Mode.ReleaseSmall, - }) |mode| { + for (modes) |mode| { for ([]bool{ false, true, @@ -166,18 +165,6 @@ pub fn addPkgTests(b: *build.Builder, test_filter: ?[]const u8, root_src: []cons if (link_libc) { these_tests.linkSystemLibrary("c"); } - if (with_lldb) { - these_tests.setExecCmd([]?[]const u8{ - "lldb", - null, - "-o", - "run", - "-o", - "bt", - "-o", - "exit", - }); - } step.dependOn(&these_tests.step); } } @@ -190,6 +177,7 @@ pub const CompareOutputContext = struct { step: *build.Step, test_index: usize, test_filter: ?[]const u8, + modes: []const Mode, const Special = enum { None, @@ -440,12 +428,7 @@ pub const CompareOutputContext = struct { self.step.dependOn(&run_and_cmp_output.step); }, Special.None => { - for ([]Mode{ - Mode.Debug, - Mode.ReleaseSafe, - Mode.ReleaseFast, - Mode.ReleaseSmall, - }) |mode| { + for (self.modes) |mode| { const annotated_case_name = fmt.allocPrint(self.b.allocator, "{} {} ({})", "compare-output", case.name, @tagName(mode)) catch unreachable; if (self.test_filter) |filter| { if (mem.indexOf(u8, annotated_case_name, filter) == null) continue; @@ -500,6 +483,7 @@ pub const CompileErrorContext = struct { step: *build.Step, test_index: usize, test_filter: ?[]const u8, + modes: []const Mode, const TestCase = struct { name: []const u8, @@ -690,10 +674,7 @@ pub const CompileErrorContext = struct { pub fn addCase(self: *CompileErrorContext, case: *const TestCase) void { const b = self.b; - for ([]Mode{ - Mode.Debug, - Mode.ReleaseFast, - }) |mode| { + for (self.modes) |mode| { const annotated_case_name = fmt.allocPrint(self.b.allocator, "compile-error {} ({})", case.name, @tagName(mode)) catch unreachable; if (self.test_filter) |filter| { if (mem.indexOf(u8, annotated_case_name, filter) == null) continue;