diff --git a/CMakeLists.txt b/CMakeLists.txt index e606855555..867f2684db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -426,6 +426,7 @@ set(ZIG_SOURCES ) set(ZIG_CPP_SOURCES "${CMAKE_SOURCE_DIR}/src/zig_llvm.cpp" + "${CMAKE_SOURCE_DIR}/src/windows_sdk.cpp" ) set(ZIG_STD_FILES @@ -479,6 +480,7 @@ set(ZIG_STD_FILES "index.zig" "io.zig" "json.zig" + "lazy_init.zig" "linked_list.zig" "macho.zig" "math/acos.zig" @@ -488,6 +490,7 @@ set(ZIG_STD_FILES "math/atan.zig" "math/atan2.zig" "math/atanh.zig" + "math/big/index.zig" "math/big/int.zig" "math/cbrt.zig" "math/ceil.zig" @@ -553,9 +556,10 @@ set(ZIG_STD_FILES "net.zig" "os/child_process.zig" "os/darwin.zig" - "os/darwin_errno.zig" + "os/darwin/errno.zig" "os/epoch.zig" "os/file.zig" + "os/get_app_data_dir.zig" "os/get_user_id.zig" "os/index.zig" "os/linux/errno.zig" @@ -564,8 +568,14 @@ set(ZIG_STD_FILES "os/linux/x86_64.zig" "os/path.zig" "os/time.zig" + "os/windows/advapi32.zig" "os/windows/error.zig" "os/windows/index.zig" + "os/windows/kernel32.zig" + "os/windows/ole32.zig" + "os/windows/shell32.zig" + "os/windows/shlwapi.zig" + "os/windows/user32.zig" "os/windows/util.zig" "os/zen.zig" "rand/index.zig" @@ -614,6 +624,7 @@ set(ZIG_STD_FILES "zig/ast.zig" "zig/index.zig" "zig/parse.zig" + "zig/parse_string_literal.zig" "zig/render.zig" "zig/tokenizer.zig" ) diff --git a/README.md b/README.md index 23b8238cd2..3ada62f107 100644 --- a/README.md +++ b/README.md @@ -21,19 +21,19 @@ clarity. * Compatible with C libraries with no wrapper necessary. Directly include C .h files and get access to the functions and symbols therein. * Provides standard library which competes with the C standard library and is - always compiled against statically in source form. Compile units do not + always compiled against statically in source form. Zig binaries do not depend on libc unless explicitly linked. - * Nullable type instead of null pointers. + * Optional type instead of null pointers. * Safe unions, tagged unions, and C ABI compatible unions. * Generics so that one can write efficient data structures that work for any data type. * No header files required. Top level declarations are entirely order-independent. * Compile-time code execution. Compile-time reflection. - * Partial compile-time function evaluation with eliminates the need for + * Partial compile-time function evaluation which eliminates the need for a preprocessor or macros. * The binaries produced by Zig have complete debugging information so you can, - for example, use GDB or MSVC to debug your software. + for example, use GDB, MSVC, or LLDB to debug your software. * Built-in unit tests with `zig test`. * Friendly toward package maintainers. Reproducible build, bootstrapping process carefully documented. Issues filed by package maintainers are @@ -70,7 +70,7 @@ that counts as "freestanding" for the purposes of this table. ## Community - * IRC: `#zig` on Freenode. + * IRC: `#zig` on Freenode ([Channel Logs](https://irclog.whitequark.org/zig/)). * Reddit: [/r/zig](https://www.reddit.com/r/zig) * Email list: [ziglang@googlegroups.com](https://groups.google.com/forum/#!forum/ziglang) diff --git a/build.zig b/build.zig index c9e70887e3..e7a5c5cba1 100644 --- a/build.zig +++ b/build.zig @@ -92,7 +92,7 @@ pub fn build(b: *Builder) !void { 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.addBuildExampleTests(b, test_filter, modes)); 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)); diff --git a/doc/langref.html.in b/doc/langref.html.in index 46b325832b..60ba09d391 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -1087,7 +1087,7 @@ unwrapped == 1234 If a is false, returns false - without evaluating b. Otherwise, retuns b. + without evaluating b. Otherwise, returns b.
false and true == false
@@ -1102,7 +1102,7 @@ unwrapped == 1234 If a is true, returns true - without evaluating b. Otherwise, retuns b. + without evaluating b. Otherwise, returns b.
false or true == true
@@ -1483,7 +1483,7 @@ test "pointer array access" { } test "pointer slicing" { - // In Zig, we prefer using slices over null-terminated pointers. + // In Zig, we prefer slices over pointers to null-terminated arrays. // You can turn an array into a slice using slice syntax: var array = []u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; const slice = array[2..4]; diff --git a/src-self-hosted/c.zig b/src-self-hosted/c.zig index 3912462985..778d851240 100644 --- a/src-self-hosted/c.zig +++ b/src-self-hosted/c.zig @@ -4,4 +4,5 @@ pub use @cImport({ @cInclude("inttypes.h"); @cInclude("config.h"); @cInclude("zig_llvm.h"); + @cInclude("windows_sdk.h"); }); diff --git a/src-self-hosted/c_int.zig b/src-self-hosted/c_int.zig new file mode 100644 index 0000000000..10ce54da05 --- /dev/null +++ b/src-self-hosted/c_int.zig @@ -0,0 +1,68 @@ +pub const CInt = struct { + id: Id, + zig_name: []const u8, + c_name: []const u8, + is_signed: bool, + + pub const Id = enum { + Short, + UShort, + Int, + UInt, + Long, + ULong, + LongLong, + ULongLong, + }; + + pub const list = []CInt{ + CInt{ + .id = Id.Short, + .zig_name = "c_short", + .c_name = "short", + .is_signed = true, + }, + CInt{ + .id = Id.UShort, + .zig_name = "c_ushort", + .c_name = "unsigned short", + .is_signed = false, + }, + CInt{ + .id = Id.Int, + .zig_name = "c_int", + .c_name = "int", + .is_signed = true, + }, + CInt{ + .id = Id.UInt, + .zig_name = "c_uint", + .c_name = "unsigned int", + .is_signed = false, + }, + CInt{ + .id = Id.Long, + .zig_name = "c_long", + .c_name = "long", + .is_signed = true, + }, + CInt{ + .id = Id.ULong, + .zig_name = "c_ulong", + .c_name = "unsigned long", + .is_signed = false, + }, + CInt{ + .id = Id.LongLong, + .zig_name = "c_longlong", + .c_name = "long long", + .is_signed = true, + }, + CInt{ + .id = Id.ULongLong, + .zig_name = "c_ulonglong", + .c_name = "unsigned long long", + .is_signed = false, + }, + }; +}; diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 698f1e5b45..ad3dce061e 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -1,19 +1,22 @@ const std = @import("std"); +const builtin = @import("builtin"); const Compilation = @import("compilation.zig").Compilation; -// we go through llvm instead of c for 2 reasons: -// 1. to avoid accidentally calling the non-thread-safe functions -// 2. patch up some of the types to remove nullability const llvm = @import("llvm.zig"); +const c = @import("c.zig"); const ir = @import("ir.zig"); const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; const event = std.event; const assert = std.debug.assert; +const DW = std.dwarf; pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) !void { fn_val.base.ref(); defer fn_val.base.deref(comp); - defer code.destroy(comp.a()); + defer code.destroy(comp.gpa()); + + var output_path = try await (async comp.createRandomOutputPath(comp.target.objFileExt()) catch unreachable); + errdefer output_path.deinit(); const llvm_handle = try comp.event_loop_local.getAnyLlvmContext(); defer llvm_handle.release(comp.event_loop_local); @@ -23,15 +26,59 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) const module = llvm.ModuleCreateWithNameInContext(comp.name.ptr(), context) orelse return error.OutOfMemory; defer llvm.DisposeModule(module); + llvm.SetTarget(module, comp.llvm_triple.ptr()); + llvm.SetDataLayout(module, comp.target_layout_str); + + if (comp.target.getObjectFormat() == builtin.ObjectFormat.coff) { + llvm.AddModuleCodeViewFlag(module); + } else { + llvm.AddModuleDebugInfoFlag(module); + } + const builder = llvm.CreateBuilderInContext(context) orelse return error.OutOfMemory; defer llvm.DisposeBuilder(builder); + const dibuilder = llvm.CreateDIBuilder(module, true) orelse return error.OutOfMemory; + defer llvm.DisposeDIBuilder(dibuilder); + + // Don't use ZIG_VERSION_STRING here. LLVM misparses it when it includes + // the git revision. + const producer = try std.Buffer.allocPrint( + &code.arena.allocator, + "zig {}.{}.{}", + u32(c.ZIG_VERSION_MAJOR), + u32(c.ZIG_VERSION_MINOR), + u32(c.ZIG_VERSION_PATCH), + ); + const flags = c""; + const runtime_version = 0; + const compile_unit_file = llvm.CreateFile( + dibuilder, + comp.name.ptr(), + comp.root_package.root_src_dir.ptr(), + ) orelse return error.OutOfMemory; + const is_optimized = comp.build_mode != builtin.Mode.Debug; + const compile_unit = llvm.CreateCompileUnit( + dibuilder, + DW.LANG_C99, + compile_unit_file, + producer.ptr(), + is_optimized, + flags, + runtime_version, + c"", + 0, + !comp.strip, + ) orelse return error.OutOfMemory; + var ofile = ObjectFile{ .comp = comp, .module = module, .builder = builder, + .dibuilder = dibuilder, .context = context, .lock = event.Lock.init(comp.loop), + .arena = &code.arena.allocator, }; try renderToLlvmModule(&ofile, fn_val, code); @@ -41,10 +88,10 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) // LLVMSetModuleInlineAsm(g->module, buf_ptr(&g->global_asm)); //} - // TODO - //ZigLLVMDIBuilderFinalize(g->dbuilder); + llvm.DIBuilderFinalize(dibuilder); if (comp.verbose_llvm_ir) { + std.debug.warn("raw module:\n"); llvm.DumpModule(ofile.module); } @@ -53,23 +100,56 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) var error_ptr: ?[*]u8 = null; _ = llvm.VerifyModule(ofile.module, llvm.AbortProcessAction, &error_ptr); } + + assert(comp.emit_file_type == Compilation.Emit.Binary); // TODO support other types + + const is_small = comp.build_mode == builtin.Mode.ReleaseSmall; + const is_debug = comp.build_mode == builtin.Mode.Debug; + + var err_msg: [*]u8 = undefined; + // TODO integrate this with evented I/O + if (llvm.TargetMachineEmitToFile( + comp.target_machine, + module, + output_path.ptr(), + llvm.EmitBinary, + &err_msg, + is_debug, + is_small, + )) { + if (std.debug.runtime_safety) { + std.debug.panic("unable to write object file {}: {s}\n", output_path.toSliceConst(), err_msg); + } + return error.WritingObjectFileFailed; + } + //validate_inline_fns(g); TODO + fn_val.containing_object = output_path; + if (comp.verbose_llvm_ir) { + std.debug.warn("optimized module:\n"); + llvm.DumpModule(ofile.module); + } + if (comp.verbose_link) { + std.debug.warn("created {}\n", output_path.toSliceConst()); + } } pub const ObjectFile = struct { comp: *Compilation, module: llvm.ModuleRef, builder: llvm.BuilderRef, + dibuilder: *llvm.DIBuilder, context: llvm.ContextRef, lock: event.Lock, + arena: *std.mem.Allocator, - fn a(self: *ObjectFile) *std.mem.Allocator { - return self.comp.a(); + fn gpa(self: *ObjectFile) *std.mem.Allocator { + return self.comp.gpa(); } }; pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) !void { // TODO audit more of codegen.cpp:fn_llvm_value and port more logic - const llvm_fn_type = try fn_val.base.typeof.getLlvmType(ofile); + const llvm_fn_type = try fn_val.base.typ.getLlvmType(ofile.arena, ofile.context); const llvm_fn = llvm.AddFunction( ofile.module, fn_val.symbol_name.ptr(), @@ -87,7 +167,7 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) // try addLLVMFnAttrInt(ofile, llvm_fn, "alignstack", align_stack); //} - const fn_type = fn_val.base.typeof.cast(Type.Fn).?; + const fn_type = fn_val.base.typ.cast(Type.Fn).?; try addLLVMFnAttr(ofile, llvm_fn, "nounwind"); //add_uwtable_attr(g, fn_table_entry->llvm_value); diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig index 1dbbf21206..093aab21da 100644 --- a/src-self-hosted/compilation.zig +++ b/src-self-hosted/compilation.zig @@ -21,24 +21,47 @@ 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 Msg = errmsg.Msg; const codegen = @import("codegen.zig"); +const Package = @import("package.zig").Package; +const link = @import("link.zig").link; +const LibCInstallation = @import("libc_installation.zig").LibCInstallation; +const CInt = @import("c_int.zig").CInt; /// Data that is local to the event loop. pub const EventLoopLocal = struct { loop: *event.Loop, llvm_handle_pool: std.atomic.Stack(llvm.ContextRef), - fn init(loop: *event.Loop) EventLoopLocal { + /// TODO pool these so that it doesn't have to lock + prng: event.Locked(std.rand.DefaultPrng), + + native_libc: event.Future(LibCInstallation), + + var lazy_init_targets = std.lazyInit(void); + + fn init(loop: *event.Loop) !EventLoopLocal { + lazy_init_targets.get() orelse { + Target.initializeAll(); + lazy_init_targets.resolve(); + }; + + var seed_bytes: [@sizeOf(u64)]u8 = undefined; + try std.os.getRandomBytes(seed_bytes[0..]); + const seed = std.mem.readInt(seed_bytes, u64, builtin.Endian.Big); + return EventLoopLocal{ .loop = loop, .llvm_handle_pool = std.atomic.Stack(llvm.ContextRef).init(), + .prng = event.Locked(std.rand.DefaultPrng).init(loop, std.rand.DefaultPrng.init(seed)), + .native_libc = event.Future(LibCInstallation).init(loop), }; } + /// Must be called only after EventLoop.run completes. fn deinit(self: *EventLoopLocal) void { while (self.llvm_handle_pool.pop()) |node| { c.LLVMContextDispose(node.data); @@ -62,6 +85,13 @@ pub const EventLoopLocal = struct { return LlvmHandle{ .node = node }; } + + pub async fn getNativeLibC(self: *EventLoopLocal) !*LibCInstallation { + if (await (async self.native_libc.start() catch unreachable)) |ptr| return ptr; + try await (async self.native_libc.data.findNative(self.loop) catch unreachable); + self.native_libc.resolve(); + return &self.native_libc.data; + } }; pub const LlvmHandle = struct { @@ -76,23 +106,22 @@ pub const Compilation = struct { event_loop_local: *EventLoopLocal, loop: *event.Loop, name: Buffer, + llvm_triple: Buffer, root_src_path: ?[]const u8, target: Target, + llvm_target: llvm.TargetRef, build_mode: builtin.Mode, zig_lib_dir: []const u8, + zig_std_dir: []const u8, + + /// lazily created when we need it + tmp_dir: event.Future(BuildError![]u8), version_major: u32, version_minor: u32, 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, @@ -106,8 +135,16 @@ pub const Compilation = struct { lib_dirs: []const []const u8, rpath_list: []const []const u8, assembly_files: []const []const u8, + + /// paths that are explicitly provided by the user to link against link_objects: []const []const u8, + /// functions that have their own objects that we need to link + /// it uses an optional pointer so that tombstone removals are possible + fn_link_set: event.Locked(FnLinkSet), + + pub const FnLinkSet = std.LinkedList(?*Value.Fn); + windows_subsystem_windows: bool, windows_subsystem_console: bool, @@ -141,7 +178,7 @@ pub const Compilation = struct { /// Before code generation starts, must wait on this group to make sure /// the build is complete. - build_group: event.Group(BuildError!void), + prelink_group: event.Group(BuildError!void), compile_errors: event.Locked(CompileErrList), @@ -149,13 +186,49 @@ pub const Compilation = struct { void_type: *Type.Void, bool_type: *Type.Bool, noreturn_type: *Type.NoReturn, + comptime_int_type: *Type.ComptimeInt, + u8_type: *Type.Int, void_value: *Value.Void, true_value: *Value.Bool, false_value: *Value.Bool, noreturn_value: *Value.NoReturn, - const CompileErrList = std.ArrayList(*errmsg.Msg); + target_machine: llvm.TargetMachineRef, + target_data_ref: llvm.TargetDataRef, + target_layout_str: [*]u8, + target_ptr_bits: u32, + + /// for allocating things which have the same lifetime as this Compilation + arena_allocator: std.heap.ArenaAllocator, + + root_package: *Package, + std_package: *Package, + + override_libc: ?*LibCInstallation, + + /// need to wait on this group before deinitializing + deinit_group: event.Group(void), + + destroy_handle: promise, + + have_err_ret_tracing: bool, + + /// not locked because it is read-only + primitive_type_table: TypeTable, + + int_type_table: event.Locked(IntTypeTable), + array_type_table: event.Locked(ArrayTypeTable), + ptr_type_table: event.Locked(PtrTypeTable), + + c_int_types: [CInt.list.len]*Type.Int, + + const IntTypeTable = std.HashMap(*const Type.Int.Key, *Type.Int, Type.Int.Key.hash, Type.Int.Key.eql); + const ArrayTypeTable = std.HashMap(*const Type.Array.Key, *Type.Array, Type.Array.Key.hash, Type.Array.Key.eql); + const PtrTypeTable = std.HashMap(*const Type.Pointer.Key, *Type.Pointer, Type.Pointer.Key.hash, Type.Pointer.Key.eql); + const TypeTable = std.HashMap([]const u8, *Type, mem.hash_slice_u8, mem.eql_slice_u8); + + const CompileErrList = std.ArrayList(*Msg); // TODO handle some of these earlier and report them in a way other than error codes pub const BuildError = error{ @@ -195,12 +268,21 @@ pub const Compilation = struct { BufferTooSmall, Unimplemented, // TODO remove this one SemanticAnalysisFailed, // TODO remove this one + ReadOnlyFileSystem, + LinkQuotaExceeded, + EnvironmentVariableNotFound, + AppDataDirUnavailable, + LinkFailed, + LibCRequiredButNotProvidedOrFound, + LibCMissingDynamicLinker, + InvalidDarwinVersionString, + UnsupportedLinkArchitecture, }; pub const Event = union(enum) { Ok, Error: BuildError, - Fail: []*errmsg.Msg, + Fail: []*Msg, }; pub const DarwinVersionMin = union(enum) { @@ -234,31 +316,29 @@ pub const Compilation = struct { event_loop_local: *EventLoopLocal, name: []const u8, root_src_path: ?[]const u8, - target: *const Target, + target: Target, kind: Kind, build_mode: builtin.Mode, + is_static: bool, zig_lib_dir: []const u8, - cache_dir: []const u8, ) !*Compilation { const loop = event_loop_local.loop; - - var name_buffer = try Buffer.init(loop.allocator, name); - errdefer name_buffer.deinit(); - - const events = try event.Channel(Event).create(loop, 0); - errdefer events.destroy(); - - const comp = try loop.allocator.create(Compilation{ + const comp = try event_loop_local.loop.allocator.create(Compilation{ .loop = loop, + .arena_allocator = std.heap.ArenaAllocator.init(loop.allocator), .event_loop_local = event_loop_local, - .events = events, - .name = name_buffer, + .events = undefined, .root_src_path = root_src_path, - .target = target.*, + .target = target, + .llvm_target = undefined, .kind = kind, .build_mode = build_mode, .zig_lib_dir = zig_lib_dir, - .cache_dir = cache_dir, + .zig_std_dir = undefined, + .tmp_dir = event.Future(BuildError![]u8).init(loop), + + .name = undefined, + .llvm_triple = undefined, .version_major = 0, .version_minor = 0, @@ -273,17 +353,11 @@ pub const Compilation = struct { .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, + .is_static = is_static, .linker_rdynamic = false, .clang_argv = [][]const u8{}, .llvm_argv = [][]const u8{}, @@ -291,9 +365,10 @@ pub const Compilation = struct { .rpath_list = [][]const u8{}, .assembly_files = [][]const u8{}, .link_objects = [][]const u8{}, + .fn_link_set = event.Locked(FnLinkSet).init(loop, FnLinkSet.init()), .windows_subsystem_windows = false, .windows_subsystem_console = false, - .link_libs_list = ArrayList(*LinkLib).init(loop.allocator), + .link_libs_list = undefined, .libc_link_lib = null, .err_color = errmsg.Color.Auto, .darwin_frameworks = [][]const u8{}, @@ -303,8 +378,13 @@ pub const Compilation = struct { .emit_file_type = Emit.Binary, .link_out_file = null, .exported_symbol_names = event.Locked(Decl.Table).init(loop, Decl.Table.init(loop.allocator)), - .build_group = event.Group(BuildError!void).init(loop), + .prelink_group = event.Group(BuildError!void).init(loop), + .deinit_group = event.Group(void).init(loop), .compile_errors = event.Locked(CompileErrList).init(loop, CompileErrList.init(loop.allocator)), + .int_type_table = event.Locked(IntTypeTable).init(loop, IntTypeTable.init(loop.allocator)), + .array_type_table = event.Locked(ArrayTypeTable).init(loop, ArrayTypeTable.init(loop.allocator)), + .ptr_type_table = event.Locked(PtrTypeTable).init(loop, PtrTypeTable.init(loop.allocator)), + .c_int_types = undefined, .meta_type = undefined, .void_type = undefined, @@ -314,120 +394,307 @@ pub const Compilation = struct { .false_value = undefined, .noreturn_type = undefined, .noreturn_value = undefined, + .comptime_int_type = undefined, + .u8_type = undefined, + + .target_machine = undefined, + .target_data_ref = undefined, + .target_layout_str = undefined, + .target_ptr_bits = target.getArchPtrBitWidth(), + + .root_package = undefined, + .std_package = undefined, + + .override_libc = null, + .destroy_handle = undefined, + .have_err_ret_tracing = false, + .primitive_type_table = undefined, }); + errdefer { + comp.int_type_table.private_data.deinit(); + comp.array_type_table.private_data.deinit(); + comp.ptr_type_table.private_data.deinit(); + comp.arena_allocator.deinit(); + comp.loop.allocator.destroy(comp); + } + + comp.name = try Buffer.init(comp.arena(), name); + comp.llvm_triple = try target.getTriple(comp.arena()); + comp.llvm_target = try Target.llvmTargetFromTriple(comp.llvm_triple); + comp.link_libs_list = ArrayList(*LinkLib).init(comp.arena()); + comp.zig_std_dir = try std.os.path.join(comp.arena(), zig_lib_dir, "std"); + comp.primitive_type_table = TypeTable.init(comp.arena()); + + const opt_level = switch (build_mode) { + builtin.Mode.Debug => llvm.CodeGenLevelNone, + else => llvm.CodeGenLevelAggressive, + }; + + const reloc_mode = if (is_static) llvm.RelocStatic else llvm.RelocPIC; + + // LLVM creates invalid binaries on Windows sometimes. + // See https://github.com/ziglang/zig/issues/508 + // As a workaround we do not use target native features on Windows. + var target_specific_cpu_args: ?[*]u8 = null; + var target_specific_cpu_features: ?[*]u8 = null; + errdefer llvm.DisposeMessage(target_specific_cpu_args); + errdefer llvm.DisposeMessage(target_specific_cpu_features); + if (target == Target.Native and !target.isWindows()) { + target_specific_cpu_args = llvm.GetHostCPUName() orelse return error.OutOfMemory; + target_specific_cpu_features = llvm.GetNativeFeatures() orelse return error.OutOfMemory; + } + + comp.target_machine = llvm.CreateTargetMachine( + comp.llvm_target, + comp.llvm_triple.ptr(), + target_specific_cpu_args orelse c"", + target_specific_cpu_features orelse c"", + opt_level, + reloc_mode, + llvm.CodeModelDefault, + ) orelse return error.OutOfMemory; + errdefer llvm.DisposeTargetMachine(comp.target_machine); + + comp.target_data_ref = llvm.CreateTargetDataLayout(comp.target_machine) orelse return error.OutOfMemory; + errdefer llvm.DisposeTargetData(comp.target_data_ref); + + comp.target_layout_str = llvm.CopyStringRepOfTargetData(comp.target_data_ref) orelse return error.OutOfMemory; + errdefer llvm.DisposeMessage(comp.target_layout_str); + + comp.events = try event.Channel(Event).create(comp.loop, 0); + errdefer comp.events.destroy(); + + if (root_src_path) |root_src| { + const dirname = std.os.path.dirname(root_src) orelse "."; + const basename = std.os.path.basename(root_src); + + comp.root_package = try Package.create(comp.arena(), dirname, basename); + comp.std_package = try Package.create(comp.arena(), comp.zig_std_dir, "index.zig"); + try comp.root_package.add("std", comp.std_package); + } else { + comp.root_package = try Package.create(comp.arena(), ".", ""); + } + try comp.initTypes(); + + comp.destroy_handle = try async comp.internalDeinit(); + return comp; } + /// it does ref the result because it could be an arbitrary integer size + pub async fn getPrimitiveType(comp: *Compilation, name: []const u8) !?*Type { + if (name.len >= 2) { + switch (name[0]) { + 'i', 'u' => blk: { + for (name[1..]) |byte| + switch (byte) { + '0'...'9' => {}, + else => break :blk, + }; + const is_signed = name[0] == 'i'; + const bit_count = std.fmt.parseUnsigned(u32, name[1..], 10) catch |err| switch (err) { + error.Overflow => return error.Overflow, + error.InvalidCharacter => unreachable, // we just checked the characters above + }; + const int_type = try await (async Type.Int.get(comp, Type.Int.Key{ + .bit_count = bit_count, + .is_signed = is_signed, + }) catch unreachable); + errdefer int_type.base.base.deref(); + return &int_type.base; + }, + else => {}, + } + } + + if (comp.primitive_type_table.get(name)) |entry| { + entry.value.base.ref(); + return entry.value; + } + + return null; + } + fn initTypes(comp: *Compilation) !void { - comp.meta_type = try comp.a().create(Type.MetaType{ + comp.meta_type = try comp.arena().create(Type.MetaType{ .base = Type{ + .name = "type", .base = Value{ .id = Value.Id.Type, - .typeof = undefined, + .typ = undefined, .ref_count = std.atomic.Int(usize).init(3), // 3 because it references itself twice }, .id = builtin.TypeId.Type, + .abi_alignment = Type.AbiAlignment.init(comp.loop), }, .value = undefined, }); comp.meta_type.value = &comp.meta_type.base; - comp.meta_type.base.base.typeof = &comp.meta_type.base; - errdefer comp.a().destroy(comp.meta_type); + comp.meta_type.base.base.typ = &comp.meta_type.base; + assert((try comp.primitive_type_table.put(comp.meta_type.base.name, &comp.meta_type.base)) == null); - comp.void_type = try comp.a().create(Type.Void{ + comp.void_type = try comp.arena().create(Type.Void{ .base = Type{ + .name = "void", .base = Value{ .id = Value.Id.Type, - .typeof = &Type.MetaType.get(comp).base, + .typ = &Type.MetaType.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, .id = builtin.TypeId.Void, + .abi_alignment = Type.AbiAlignment.init(comp.loop), }, }); - errdefer comp.a().destroy(comp.void_type); + assert((try comp.primitive_type_table.put(comp.void_type.base.name, &comp.void_type.base)) == null); - comp.noreturn_type = try comp.a().create(Type.NoReturn{ + comp.noreturn_type = try comp.arena().create(Type.NoReturn{ .base = Type{ + .name = "noreturn", .base = Value{ .id = Value.Id.Type, - .typeof = &Type.MetaType.get(comp).base, + .typ = &Type.MetaType.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, .id = builtin.TypeId.NoReturn, + .abi_alignment = Type.AbiAlignment.init(comp.loop), }, }); - errdefer comp.a().destroy(comp.noreturn_type); + assert((try comp.primitive_type_table.put(comp.noreturn_type.base.name, &comp.noreturn_type.base)) == null); - comp.bool_type = try comp.a().create(Type.Bool{ + comp.comptime_int_type = try comp.arena().create(Type.ComptimeInt{ .base = Type{ + .name = "comptime_int", .base = Value{ .id = Value.Id.Type, - .typeof = &Type.MetaType.get(comp).base, + .typ = &Type.MetaType.get(comp).base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .id = builtin.TypeId.ComptimeInt, + .abi_alignment = Type.AbiAlignment.init(comp.loop), + }, + }); + assert((try comp.primitive_type_table.put(comp.comptime_int_type.base.name, &comp.comptime_int_type.base)) == null); + + comp.bool_type = try comp.arena().create(Type.Bool{ + .base = Type{ + .name = "bool", + .base = Value{ + .id = Value.Id.Type, + .typ = &Type.MetaType.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, .id = builtin.TypeId.Bool, + .abi_alignment = Type.AbiAlignment.init(comp.loop), }, }); - errdefer comp.a().destroy(comp.bool_type); + assert((try comp.primitive_type_table.put(comp.bool_type.base.name, &comp.bool_type.base)) == null); - comp.void_value = try comp.a().create(Value.Void{ + comp.void_value = try comp.arena().create(Value.Void{ .base = Value{ .id = Value.Id.Void, - .typeof = &Type.Void.get(comp).base, + .typ = &Type.Void.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, }); - errdefer comp.a().destroy(comp.void_value); - comp.true_value = try comp.a().create(Value.Bool{ + comp.true_value = try comp.arena().create(Value.Bool{ .base = Value{ .id = Value.Id.Bool, - .typeof = &Type.Bool.get(comp).base, + .typ = &Type.Bool.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, .x = true, }); - errdefer comp.a().destroy(comp.true_value); - comp.false_value = try comp.a().create(Value.Bool{ + comp.false_value = try comp.arena().create(Value.Bool{ .base = Value{ .id = Value.Id.Bool, - .typeof = &Type.Bool.get(comp).base, + .typ = &Type.Bool.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, .x = false, }); - errdefer comp.a().destroy(comp.false_value); - comp.noreturn_value = try comp.a().create(Value.NoReturn{ + comp.noreturn_value = try comp.arena().create(Value.NoReturn{ .base = Value{ .id = Value.Id.NoReturn, - .typeof = &Type.NoReturn.get(comp).base, + .typ = &Type.NoReturn.get(comp).base, .ref_count = std.atomic.Int(usize).init(1), }, }); - errdefer comp.a().destroy(comp.noreturn_value); + + for (CInt.list) |cint, i| { + const c_int_type = try comp.arena().create(Type.Int{ + .base = Type{ + .name = cint.zig_name, + .base = Value{ + .id = Value.Id.Type, + .typ = &Type.MetaType.get(comp).base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .id = builtin.TypeId.Int, + .abi_alignment = Type.AbiAlignment.init(comp.loop), + }, + .key = Type.Int.Key{ + .is_signed = cint.is_signed, + .bit_count = comp.target.cIntTypeSizeInBits(cint.id), + }, + .garbage_node = undefined, + }); + comp.c_int_types[i] = c_int_type; + assert((try comp.primitive_type_table.put(cint.zig_name, &c_int_type.base)) == null); + } + comp.u8_type = try comp.arena().create(Type.Int{ + .base = Type{ + .name = "u8", + .base = Value{ + .id = Value.Id.Type, + .typ = &Type.MetaType.get(comp).base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .id = builtin.TypeId.Int, + .abi_alignment = Type.AbiAlignment.init(comp.loop), + }, + .key = Type.Int.Key{ + .is_signed = false, + .bit_count = 8, + }, + .garbage_node = undefined, + }); + assert((try comp.primitive_type_table.put(comp.u8_type.base.name, &comp.u8_type.base)) == null); + } + + /// This function can safely use async/await, because it manages Compilation's lifetime, + /// and EventLoopLocal.deinit will not be called until the event.Loop.run() completes. + async fn internalDeinit(self: *Compilation) void { + suspend; + + await (async self.deinit_group.wait() catch unreachable); + if (self.tmp_dir.getOrNull()) |tmp_dir_result| if (tmp_dir_result.*) |tmp_dir| { + // TODO evented I/O? + os.deleteTree(self.arena(), tmp_dir) catch {}; + } else |_| {}; + + self.events.destroy(); + + llvm.DisposeMessage(self.target_layout_str); + llvm.DisposeTargetData(self.target_data_ref); + llvm.DisposeTargetMachine(self.target_machine); + + self.primitive_type_table.deinit(); + + self.arena_allocator.deinit(); + self.gpa().destroy(self); } 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); + resume self.destroy_handle; } pub fn build(self: *Compilation) !void { if (self.llvm_argv.len != 0) { - var c_compatible_args = try std.cstr.NullTerminated2DArray.fromSlices(self.a(), [][]const []const u8{ + var c_compatible_args = try std.cstr.NullTerminated2DArray.fromSlices(self.arena(), [][]const []const u8{ [][]const u8{"zig (LLVM option parsing)"}, self.llvm_argv, }); @@ -436,14 +703,13 @@ pub const Compilation = struct { c.ZigLLVMParseCommandLineOptions(self.llvm_argv.len + 1, c_compatible_args.ptr); } - _ = try async self.buildAsync(); + _ = try async self.buildAsync(); } async fn buildAsync(self: *Compilation) void { while (true) { // TODO directly awaiting async should guarantee memory allocation elision - // TODO also async before suspending should guarantee memory allocation elision - const build_result = await (async self.addRootSrc() catch unreachable); + const build_result = await (async self.compileAndLink() catch unreachable); // this makes a handy error return trace and stack trace in debug mode if (std.debug.runtime_safety) { @@ -464,7 +730,7 @@ pub const Compilation = struct { } } else |err| { // if there's an error then the compile errors have dangling references - self.a().free(compile_errors); + self.gpa().free(compile_errors); await (async self.events.put(Event{ .Error = err }) catch unreachable); } @@ -474,111 +740,215 @@ pub const Compilation = struct { } } - async fn addRootSrc(self: *Compilation) !void { - const root_src_path = self.root_src_path orelse @panic("TODO handle null root src path"); - // TODO async/await os.path.real - const root_src_real_path = os.path.real(self.a(), root_src_path) catch |err| { - try printError("unable to get real path '{}': {}", root_src_path, err); - return err; - }; - errdefer self.a().free(root_src_real_path); + async fn compileAndLink(self: *Compilation) !void { + if (self.root_src_path) |root_src_path| { + // TODO async/await os.path.real + const root_src_real_path = os.path.real(self.gpa(), root_src_path) catch |err| { + try printError("unable to get real path '{}': {}", root_src_path, err); + return err; + }; + const root_scope = blk: { + errdefer self.gpa().free(root_src_real_path); - // TODO async/await readFileAlloc() - const source_code = io.readFileAlloc(self.a(), root_src_real_path) catch |err| { - try printError("unable to open '{}': {}", root_src_real_path, err); - return err; - }; - errdefer self.a().free(source_code); + // TODO async/await readFileAlloc() + const source_code = io.readFileAlloc(self.gpa(), root_src_real_path) catch |err| { + try printError("unable to open '{}': {}", root_src_real_path, err); + return err; + }; + errdefer self.gpa().free(source_code); - const parsed_file = try self.a().create(ParsedFile{ - .tree = undefined, - .realpath = root_src_real_path, - }); - errdefer self.a().destroy(parsed_file); + const tree = try self.gpa().createOne(ast.Tree); + tree.* = try std.zig.parse(self.gpa(), source_code); + errdefer { + tree.deinit(); + self.gpa().destroy(tree); + } - parsed_file.tree = try std.zig.parse(self.a(), source_code); - errdefer parsed_file.tree.deinit(); + break :blk try Scope.Root.create(self, tree, root_src_real_path); + }; + defer root_scope.base.deref(self); + const tree = root_scope.tree; - const tree = &parsed_file.tree; + var error_it = tree.errors.iterator(0); + while (error_it.next()) |parse_error| { + const msg = try Msg.createFromParseErrorAndScope(self, root_scope, parse_error); + errdefer msg.destroy(); - // 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 self.addCompileErrorAsync(msg) catch unreachable); } + if (tree.errors.len != 0) { + return; + } + + const decls = try Scope.Decls.create(self, &root_scope.base); + defer decls.base.deref(self); + + var decl_group = event.Group(BuildError!void).init(self.loop); + var decl_group_consumed = false; + errdefer if (!decl_group_consumed) 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 => { + const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", decl); + + try self.prelink_group.call(addCompTimeBlock, self, &decls.base, comptime_node); + }, + 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(root_scope, Span{ + .first = fn_proto.fn_token, + .last = fn_proto.fn_token + 1, + }, "missing function name"); + continue; + }; + + const fn_decl = try self.gpa().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), + .parent_scope = &decls.base, + }, + .value = Decl.Fn.Val{ .Unresolved = {} }, + .fn_proto = fn_proto, + }); + errdefer self.gpa().destroy(fn_decl); + + try decl_group.call(addTopLevelDecl, self, decls, &fn_decl.base); + }, + ast.Node.Id.TestDecl => @panic("TODO"), + else => unreachable, + } + } + decl_group_consumed = true; + try await (async decl_group.wait() catch unreachable); + + // Now other code can rely on the decls scope having a complete list of names. + decls.name_future.resolve(); + } + + (await (async self.prelink_group.wait() catch unreachable)) catch |err| switch (err) { + error.SemanticAnalysisFailed => {}, + else => return err, + }; + + const any_prelink_errors = blk: { + const compile_errors = await (async self.compile_errors.acquire() catch unreachable); + defer compile_errors.release(); + + break :blk compile_errors.value.len != 0; + }; + + if (!any_prelink_errors) { + try await (async link(self) catch 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); + /// caller takes ownership of resulting Code + async fn genAndAnalyzeCode( + comp: *Compilation, + scope: *Scope, + node: *ast.Node, + expected_type: ?*Type, + ) !*ir.Code { + const unanalyzed_code = try await (async ir.gen( + comp, + node, + scope, + ) catch unreachable); + defer unanalyzed_code.destroy(comp.gpa()); + + if (comp.verbose_ir) { + std.debug.warn("unanalyzed:\n"); + unanalyzed_code.dump(); + } + + const analyzed_code = try await (async ir.analyze( + comp, + unanalyzed_code, + expected_type, + ) catch unreachable); + errdefer analyzed_code.destroy(comp.gpa()); + + if (comp.verbose_ir) { + std.debug.warn("analyzed:\n"); + analyzed_code.dump(); + } + + return analyzed_code; + } + + async fn addCompTimeBlock( + comp: *Compilation, + scope: *Scope, + comptime_node: *ast.Node.Comptime, + ) !void { + const void_type = Type.Void.get(comp); + defer void_type.base.base.deref(comp); + + const analyzed_code = (await (async genAndAnalyzeCode( + comp, + scope, + comptime_node.expr, + &void_type.base, + ) catch unreachable)) catch |err| switch (err) { + // This poison value should not cause the errdefers to run. It simply means + // that comp.compile_errors is populated. + error.SemanticAnalysisFailed => return {}, + else => return err, + }; + analyzed_code.destroy(comp.gpa()); + } + + async fn addTopLevelDecl(self: *Compilation, decls: *Scope.Decls, decl: *Decl) !void { + const tree = decl.findRootScope().tree; + const is_export = decl.isExported(tree); + + var add_to_table_resolved = false; + const add_to_table = async self.addDeclToTable(decls, decl) catch unreachable; + errdefer if (!add_to_table_resolved) cancel add_to_table; // TODO https://github.com/ziglang/zig/issues/1261 if (is_export) { - try self.build_group.call(verifyUniqueSymbol, self, decl); - try self.build_group.call(resolveDecl, self, decl); + try self.prelink_group.call(verifyUniqueSymbol, self, decl); + try self.prelink_group.call(resolveDecl, self, decl); + } + + add_to_table_resolved = true; + try await add_to_table; + } + + async fn addDeclToTable(self: *Compilation, decls: *Scope.Decls, decl: *Decl) !void { + const held = await (async decls.table.acquire() catch unreachable); + defer held.release(); + + if (try held.value.put(decl.name, decl)) |other_decl| { + try self.addCompileError(decls.base.findRoot(), decl.getSpan(), "redefinition of '{}'", decl.name); + // TODO note: other definition here } } - fn addCompileError(self: *Compilation, 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); + fn addCompileError(self: *Compilation, root: *Scope.Root, span: Span, comptime fmt: []const u8, args: ...) !void { + const text = try std.fmt.allocPrint(self.gpa(), fmt, args); + errdefer self.gpa().free(text); - try self.build_group.call(addCompileErrorAsync, self, parsed_file, span, text); + const msg = try Msg.createFromScope(self, root, span, text); + errdefer msg.destroy(); + + try self.prelink_group.call(addCompileErrorAsync, self, msg); } async fn addCompileErrorAsync( self: *Compilation, - parsed_file: *ParsedFile, - span: Span, - text: []u8, + msg: *Msg, ) !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); + errdefer msg.destroy(); const compile_errors = await (async self.compile_errors.acquire() catch unreachable); defer compile_errors.release(); @@ -592,7 +962,7 @@ pub const Compilation = struct { if (try exported_symbol_names.value.put(decl.name, decl)) |other_decl| { try self.addCompileError( - decl.parsed_file, + decl.findRootScope(), decl.getSpan(), "exported symbol collision: '{}'", decl.name, @@ -601,11 +971,6 @@ pub const Compilation = struct { } } - pub fn link(self: *Compilation, out_file: ?[]const u8) !void { - warn("TODO link"); - return error.Todo; - } - pub fn haveLibC(self: *Compilation) bool { return self.libc_link_lib != null; } @@ -625,22 +990,127 @@ pub const Compilation = struct { } } - const link_lib = try self.a().create(LinkLib{ + const link_lib = try self.gpa().create(LinkLib{ .name = name, .path = null, .provided_explicitly = provided_explicitly, - .symbols = ArrayList([]u8).init(self.a()), + .symbols = ArrayList([]u8).init(self.gpa()), }); try self.link_libs_list.append(link_lib); if (is_libc) { self.libc_link_lib = link_lib; + + // get a head start on looking for the native libc + if (self.target == Target.Native and self.override_libc == null) { + try self.deinit_group.call(startFindingNativeLibC, self); + } } return link_lib; } - fn a(self: Compilation) *mem.Allocator { + /// cancels itself so no need to await or cancel the promise. + async fn startFindingNativeLibC(self: *Compilation) void { + await (async self.loop.yield() catch unreachable); + // we don't care if it fails, we're just trying to kick off the future resolution + _ = (await (async self.event_loop_local.getNativeLibC() catch unreachable)) catch return; + } + + /// General Purpose Allocator. Must free when done. + fn gpa(self: Compilation) *mem.Allocator { return self.loop.allocator; } + + /// Arena Allocator. Automatically freed when the Compilation is destroyed. + fn arena(self: *Compilation) *mem.Allocator { + return &self.arena_allocator.allocator; + } + + /// If the temporary directory for this compilation has not been created, it creates it. + /// Then it creates a random file name in that dir and returns it. + pub async fn createRandomOutputPath(self: *Compilation, suffix: []const u8) !Buffer { + const tmp_dir = try await (async self.getTmpDir() catch unreachable); + const file_prefix = await (async self.getRandomFileName() catch unreachable); + + const file_name = try std.fmt.allocPrint(self.gpa(), "{}{}", file_prefix[0..], suffix); + defer self.gpa().free(file_name); + + const full_path = try os.path.join(self.gpa(), tmp_dir, file_name[0..]); + errdefer self.gpa().free(full_path); + + return Buffer.fromOwnedSlice(self.gpa(), full_path); + } + + /// If the temporary directory for this Compilation has not been created, creates it. + /// Then returns it. The directory is unique to this Compilation and cleaned up when + /// the Compilation deinitializes. + async fn getTmpDir(self: *Compilation) ![]const u8 { + if (await (async self.tmp_dir.start() catch unreachable)) |ptr| return ptr.*; + self.tmp_dir.data = await (async self.getTmpDirImpl() catch unreachable); + self.tmp_dir.resolve(); + return self.tmp_dir.data; + } + + async fn getTmpDirImpl(self: *Compilation) ![]u8 { + const comp_dir_name = await (async self.getRandomFileName() catch unreachable); + const zig_dir_path = try getZigDir(self.gpa()); + defer self.gpa().free(zig_dir_path); + + const tmp_dir = try os.path.join(self.arena(), zig_dir_path, comp_dir_name[0..]); + try os.makePath(self.gpa(), tmp_dir); + return tmp_dir; + } + + async fn getRandomFileName(self: *Compilation) [12]u8 { + // here we replace the standard +/ with -_ so that it can be used in a file name + const b64_fs_encoder = std.base64.Base64Encoder.init( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", + std.base64.standard_pad_char, + ); + + var rand_bytes: [9]u8 = undefined; + + { + const held = await (async self.event_loop_local.prng.acquire() catch unreachable); + defer held.release(); + + held.value.random.bytes(rand_bytes[0..]); + } + + var result: [12]u8 = undefined; + b64_fs_encoder.encode(result[0..], rand_bytes); + return result; + } + + fn registerGarbage(comp: *Compilation, comptime T: type, node: *std.atomic.Stack(*T).Node) void { + // TODO put the garbage somewhere + } + + /// Returns a value which has been ref()'d once + async fn analyzeConstValue(comp: *Compilation, scope: *Scope, node: *ast.Node, expected_type: *Type) !*Value { + const analyzed_code = try await (async comp.genAndAnalyzeCode(scope, node, expected_type) catch unreachable); + defer analyzed_code.destroy(comp.gpa()); + + return analyzed_code.getCompTimeResult(comp); + } + + async fn analyzeTypeExpr(comp: *Compilation, scope: *Scope, node: *ast.Node) !*Type { + const meta_type = &Type.MetaType.get(comp).base; + defer meta_type.base.deref(comp); + + const result_val = try await (async comp.analyzeConstValue(scope, node, meta_type) catch unreachable); + errdefer result_val.base.deref(comp); + + return result_val.cast(Type).?; + } + + /// This declaration has been blessed as going into the final code generation. + pub async fn resolveDecl(comp: *Compilation, decl: *Decl) !void { + if (await (async decl.resolution.start() catch unreachable)) |ptr| return ptr.*; + + decl.resolution.data = try await (async generateDecl(comp, decl) catch unreachable); + decl.resolution.resolve(); + return decl.resolution.data; + } }; fn printError(comptime format: []const u8, args: ...) !void { @@ -660,17 +1130,6 @@ fn parseVisibToken(tree: *ast.Tree, optional_token_index: ?ast.TokenIndex) Visib } } -/// This declaration has been blessed as going into the final code generation. -pub async fn resolveDecl(comp: *Compilation, decl: *Decl) !void { - if (@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) { @@ -684,68 +1143,99 @@ async fn generateDecl(comp: *Compilation, decl: *Decl) !void { } async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { - const body_node = fn_decl.fn_proto.body_node orelse @panic("TODO extern fn proto decl"); + const body_node = fn_decl.fn_proto.body_node orelse return await (async generateDeclFnProto(comp, fn_decl) catch unreachable); const fndef_scope = try Scope.FnDef.create(comp, fn_decl.base.parent_scope); defer fndef_scope.base.deref(comp); - // 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); + const fn_type = try await (async analyzeFnType(comp, fn_decl.base.parent_scope, fn_decl.fn_proto) catch unreachable); defer fn_type.base.base.deref(comp); - var symbol_name = try std.Buffer.init(comp.a(), fn_decl.base.name); - errdefer symbol_name.deinit(); + var symbol_name = try std.Buffer.init(comp.gpa(), fn_decl.base.name); + var symbol_name_consumed = false; + errdefer if (!symbol_name_consumed) symbol_name.deinit(); + // The Decl.Fn owns the initial 1 reference count const fn_val = try Value.Fn.create(comp, fn_type, fndef_scope, symbol_name); - defer fn_val.base.deref(comp); + fn_decl.value = Decl.Fn.Val{ .Fn = fn_val }; + symbol_name_consumed = true; - fn_decl.value = Decl.Fn.Val{ .Ok = fn_val }; - - const unanalyzed_code = (await (async ir.gen( - comp, - body_node, + const analyzed_code = try await (async comp.genAndAnalyzeCode( &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(); - } + body_node, + fn_type.return_type, + ) catch unreachable); + errdefer analyzed_code.destroy(comp.gpa()); // Kick off rendering to LLVM module, but it doesn't block the fn decl // analysis from being complete. - try comp.build_group.call(codegen.renderToLlvm, comp, fn_val, analyzed_code); + try comp.prelink_group.call(codegen.renderToLlvm, comp, fn_val, analyzed_code); + try comp.prelink_group.call(addFnToLinkSet, comp, fn_val); +} + +async fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) void { + fn_val.base.ref(); + defer fn_val.base.deref(comp); + + fn_val.link_set_node.data = fn_val; + + const held = await (async comp.fn_link_set.acquire() catch unreachable); + defer held.release(); + + held.value.append(fn_val.link_set_node); +} + +fn getZigDir(allocator: *mem.Allocator) ![]u8 { + return os.getAppDataDir(allocator, "zig"); +} + +async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.FnProto) !*Type.Fn { + const return_type_node = switch (fn_proto.return_type) { + ast.Node.FnProto.ReturnType.Explicit => |n| n, + ast.Node.FnProto.ReturnType.InferErrorSet => |n| n, + }; + const return_type = try await (async comp.analyzeTypeExpr(scope, return_type_node) catch unreachable); + return_type.base.deref(comp); + + var params = ArrayList(Type.Fn.Param).init(comp.gpa()); + var params_consumed = false; + defer if (params_consumed) { + for (params.toSliceConst()) |param| { + param.typ.base.deref(comp); + } + params.deinit(); + }; + + const is_var_args = false; + { + var it = fn_proto.params.iterator(0); + while (it.next()) |param_node_ptr| { + const param_node = param_node_ptr.*.cast(ast.Node.ParamDecl).?; + const param_type = try await (async comp.analyzeTypeExpr(scope, param_node.type_node) catch unreachable); + errdefer param_type.base.deref(comp); + try params.append(Type.Fn.Param{ + .typ = param_type, + .is_noalias = param_node.noalias_token != null, + }); + } + } + const fn_type = try Type.Fn.create(comp, return_type, params.toOwnedSlice(), is_var_args); + params_consumed = true; + errdefer fn_type.base.base.deref(comp); + + return fn_type; +} + +async fn generateDeclFnProto(comp: *Compilation, fn_decl: *Decl.Fn) !void { + const fn_type = try await (async analyzeFnType(comp, fn_decl.base.parent_scope, fn_decl.fn_proto) catch unreachable); + defer fn_type.base.base.deref(comp); + + var symbol_name = try std.Buffer.init(comp.gpa(), fn_decl.base.name); + var symbol_name_consumed = false; + defer if (!symbol_name_consumed) symbol_name.deinit(); + + // The Decl.Fn owns the initial 1 reference count + const fn_proto_val = try Value.FnProto.create(comp, fn_type, symbol_name); + fn_decl.value = Decl.Fn.Val{ .FnProto = fn_proto_val }; + symbol_name_consumed = true; } diff --git a/src-self-hosted/decl.zig b/src-self-hosted/decl.zig index c0173266ee..6e80243038 100644 --- a/src-self-hosted/decl.zig +++ b/src-self-hosted/decl.zig @@ -3,7 +3,6 @@ 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; @@ -16,8 +15,6 @@ pub const Decl = struct { 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); @@ -48,6 +45,10 @@ pub const Decl = struct { } } + pub fn findRootScope(base: *const Decl) *Scope.Root { + return base.parent_scope.findRoot(); + } + pub const Id = enum { Var, Fn, @@ -61,12 +62,13 @@ pub const Decl = struct { pub const Fn = struct { base: Decl, value: Val, - fn_proto: *const ast.Node.FnProto, + fn_proto: *ast.Node.FnProto, // TODO https://github.com/ziglang/zig/issues/683 and then make this anonymous - pub const Val = union { + pub const Val = union(enum) { Unresolved: void, - Ok: *Value.Fn, + Fn: *Value.Fn, + FnProto: *Value.FnProto, }; pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 { diff --git a/src-self-hosted/errmsg.zig b/src-self-hosted/errmsg.zig index 4e353bfb14..51e135686a 100644 --- a/src-self-hosted/errmsg.zig +++ b/src-self-hosted/errmsg.zig @@ -4,6 +4,8 @@ const os = std.os; const Token = std.zig.Token; const ast = std.zig.ast; const TokenIndex = std.zig.ast.TokenIndex; +const Compilation = @import("compilation.zig").Compilation; +const Scope = @import("scope.zig").Scope; pub const Color = enum { Auto, @@ -16,85 +18,220 @@ pub const Span = struct { last: ast.TokenIndex, pub fn token(i: TokenIndex) Span { - return Span { + return Span{ .first = i, .last = i, }; } + + pub fn node(n: *ast.Node) Span { + return Span{ + .first = n.firstToken(), + .last = n.lastToken(), + }; + } }; pub const Msg = struct { - path: []const u8, - text: []u8, span: Span, - tree: *ast.Tree, -}; + text: []u8, + data: Data, -/// `path` must outlive the returned Msg -/// `tree` must outlive the returned Msg -/// Caller owns returned Msg and must free with `allocator` -pub fn createFromParseError( - allocator: *mem.Allocator, - parse_error: *const ast.Error, - tree: *ast.Tree, - path: []const u8, -) !*Msg { - const loc_token = parse_error.loc(); - var text_buf = try std.Buffer.initSize(allocator, 0); - defer text_buf.deinit(); + const Data = union(enum) { + PathAndTree: PathAndTree, + ScopeAndComp: ScopeAndComp, + }; - var out_stream = &std.io.BufferOutStream.init(&text_buf).stream; - try parse_error.render(&tree.tokens, out_stream); + const PathAndTree = struct { + realpath: []const u8, + tree: *ast.Tree, + allocator: *mem.Allocator, + }; - const msg = try allocator.create(Msg{ - .tree = tree, - .path = path, - .text = text_buf.toOwnedSlice(), - .span = Span{ - .first = loc_token, - .last = loc_token, - }, - }); - errdefer allocator.destroy(msg); + const ScopeAndComp = struct { + root_scope: *Scope.Root, + compilation: *Compilation, + }; - return msg; -} + pub fn destroy(self: *Msg) void { + switch (self.data) { + Data.PathAndTree => |path_and_tree| { + path_and_tree.allocator.free(self.text); + path_and_tree.allocator.destroy(self); + }, + Data.ScopeAndComp => |scope_and_comp| { + scope_and_comp.root_scope.base.deref(scope_and_comp.compilation); + scope_and_comp.compilation.gpa().free(self.text); + scope_and_comp.compilation.gpa().destroy(self); + }, + } + } + + fn getAllocator(self: *const Msg) *mem.Allocator { + switch (self.data) { + Data.PathAndTree => |path_and_tree| { + return path_and_tree.allocator; + }, + Data.ScopeAndComp => |scope_and_comp| { + return scope_and_comp.compilation.gpa(); + }, + } + } + + pub fn getRealPath(self: *const Msg) []const u8 { + switch (self.data) { + Data.PathAndTree => |path_and_tree| { + return path_and_tree.realpath; + }, + Data.ScopeAndComp => |scope_and_comp| { + return scope_and_comp.root_scope.realpath; + }, + } + } + + pub fn getTree(self: *const Msg) *ast.Tree { + switch (self.data) { + Data.PathAndTree => |path_and_tree| { + return path_and_tree.tree; + }, + Data.ScopeAndComp => |scope_and_comp| { + return scope_and_comp.root_scope.tree; + }, + } + } + + /// Takes ownership of text + /// References root_scope, and derefs when the msg is freed + pub fn createFromScope(comp: *Compilation, root_scope: *Scope.Root, span: Span, text: []u8) !*Msg { + const msg = try comp.gpa().create(Msg{ + .text = text, + .span = span, + .data = Data{ + .ScopeAndComp = ScopeAndComp{ + .root_scope = root_scope, + .compilation = comp, + }, + }, + }); + root_scope.base.ref(); + return msg; + } + + pub fn createFromParseErrorAndScope( + comp: *Compilation, + root_scope: *Scope.Root, + parse_error: *const ast.Error, + ) !*Msg { + const loc_token = parse_error.loc(); + var text_buf = try std.Buffer.initSize(comp.gpa(), 0); + defer text_buf.deinit(); + + var out_stream = &std.io.BufferOutStream.init(&text_buf).stream; + try parse_error.render(&root_scope.tree.tokens, out_stream); + + const msg = try comp.gpa().create(Msg{ + .text = undefined, + .span = Span{ + .first = loc_token, + .last = loc_token, + }, + .data = Data{ + .ScopeAndComp = ScopeAndComp{ + .root_scope = root_scope, + .compilation = comp, + }, + }, + }); + root_scope.base.ref(); + msg.text = text_buf.toOwnedSlice(); + return msg; + } + + /// `realpath` must outlive the returned Msg + /// `tree` must outlive the returned Msg + /// Caller owns returned Msg and must free with `allocator` + /// allocator will additionally be used for printing messages later. + pub fn createFromParseError( + allocator: *mem.Allocator, + parse_error: *const ast.Error, + tree: *ast.Tree, + realpath: []const u8, + ) !*Msg { + const loc_token = parse_error.loc(); + var text_buf = try std.Buffer.initSize(allocator, 0); + defer text_buf.deinit(); + + var out_stream = &std.io.BufferOutStream.init(&text_buf).stream; + try parse_error.render(&tree.tokens, out_stream); + + const msg = try allocator.create(Msg{ + .text = undefined, + .data = Data{ + .PathAndTree = PathAndTree{ + .allocator = allocator, + .realpath = realpath, + .tree = tree, + }, + }, + .span = Span{ + .first = loc_token, + .last = loc_token, + }, + }); + msg.text = text_buf.toOwnedSlice(); + errdefer allocator.destroy(msg); + + return msg; + } + + pub fn printToStream(msg: *const Msg, stream: var, color_on: bool) !void { + const allocator = msg.getAllocator(); + const realpath = msg.getRealPath(); + const tree = msg.getTree(); + + const cwd = try os.getCwd(allocator); + defer allocator.free(cwd); + + const relpath = try os.path.relative(allocator, cwd, realpath); + defer allocator.free(relpath); + + const path = if (relpath.len < realpath.len) relpath else realpath; + + const first_token = tree.tokens.at(msg.span.first); + const last_token = tree.tokens.at(msg.span.last); + const start_loc = tree.tokenLocationPtr(0, first_token); + const end_loc = tree.tokenLocationPtr(first_token.end, last_token); + if (!color_on) { + try stream.print( + "{}:{}:{}: error: {}\n", + path, + start_loc.line + 1, + start_loc.column + 1, + msg.text, + ); + return; + } -pub fn printToStream(stream: var, msg: *const Msg, color_on: bool) !void { - 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) { try stream.print( - "{}:{}:{}: error: {}\n", - msg.path, + "{}:{}:{}: error: {}\n{}\n", + path, start_loc.line + 1, start_loc.column + 1, msg.text, + tree.source[start_loc.line_start..start_loc.line_end], ); - return; + try stream.writeByteNTimes(' ', start_loc.column); + try stream.writeByteNTimes('~', last_token.end - first_token.start); + try stream.write("\n"); } - try stream.print( - "{}:{}:{}: error: {}\n{}\n", - msg.path, - start_loc.line + 1, - start_loc.column + 1, - msg.text, - msg.tree.source[start_loc.line_start..start_loc.line_end], - ); - try stream.writeByteNTimes(' ', start_loc.column); - try stream.writeByteNTimes('~', last_token.end - first_token.start); - try stream.write("\n"); -} - -pub fn printToFile(file: *os.File, msg: *const Msg, color: Color) !void { - const color_on = switch (color) { - Color.Auto => file.isTty(), - Color.On => true, - Color.Off => false, - }; - var stream = &std.io.FileOutStream.init(file).stream; - return printToStream(stream, msg, color_on); -} + pub fn printToFile(msg: *const Msg, file: *os.File, color: Color) !void { + const color_on = switch (color) { + Color.Auto => file.isTty(), + Color.On => true, + Color.Off => false, + }; + var stream = &std.io.FileOutStream.init(file).stream; + return msg.printToStream(stream, color_on); + } +}; diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 0e0a4f9bf3..c34f06753d 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -8,10 +8,10 @@ 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; const llvm = @import("llvm.zig"); const ObjectFile = @import("codegen.zig").ObjectFile; +const Decl = @import("decl.zig").Decl; pub const LVal = enum { None, @@ -31,10 +31,10 @@ pub const IrVal = union(enum) { pub fn dump(self: IrVal) void { switch (self) { - IrVal.Unknown => typeof.dump(), - IrVal.KnownType => |typeof| { + IrVal.Unknown => std.debug.warn("Unknown"), + IrVal.KnownType => |typ| { std.debug.warn("KnownType("); - typeof.dump(); + typ.dump(); std.debug.warn(")"); }, IrVal.KnownValue => |value| { @@ -46,27 +46,28 @@ pub const IrVal = union(enum) { } }; -pub const Instruction = struct { +pub const Inst = struct { id: Id, scope: *Scope, debug_id: usize, val: IrVal, ref_count: usize, span: Span, + owner_bb: *BasicBlock, /// 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, + child: ?*Inst, /// the instruction that this one derives from in analysis - parent: ?*Instruction, + parent: ?*Inst, /// populated durign codegen llvm_value: ?llvm.ValueRef, - pub fn cast(base: *Instruction, comptime T: type) ?*T { + pub fn cast(base: *Inst, comptime T: type) ?*T { if (base.id == comptime typeToId(T)) { return @fieldParentPtr(T, "base", base); } @@ -76,18 +77,18 @@ pub const Instruction = struct { 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))) { + if (T == @field(Inst, @memberName(Id, i))) { return @field(Id, @memberName(Id, i)); } } unreachable; } - pub fn dump(base: *const Instruction) void { + pub fn dump(base: *const Inst) 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)); + const T = @field(Inst, @memberName(Id, i)); std.debug.warn("#{} = {}(", base.debug_id, @tagName(base.id)); @fieldParentPtr(T, "base", base).dump(); std.debug.warn(")"); @@ -97,32 +98,40 @@ pub const Instruction = struct { unreachable; } - pub fn hasSideEffects(base: *const Instruction) bool { + pub fn hasSideEffects(base: *const Inst) 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)); + const T = @field(Inst, @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)); - return @fieldParentPtr(T, "base", base).analyze(ira); - } + pub async fn analyze(base: *Inst, ira: *Analyze) Analyze.Error!*Inst { + switch (base.id) { + Id.Return => return @fieldParentPtr(Return, "base", base).analyze(ira), + Id.Const => return @fieldParentPtr(Const, "base", base).analyze(ira), + Id.Call => return @fieldParentPtr(Call, "base", base).analyze(ira), + Id.DeclRef => return await (async @fieldParentPtr(DeclRef, "base", base).analyze(ira) catch unreachable), + Id.Ref => return await (async @fieldParentPtr(Ref, "base", base).analyze(ira) catch unreachable), + Id.DeclVar => return @fieldParentPtr(DeclVar, "base", base).analyze(ira), + Id.CheckVoidStmt => return @fieldParentPtr(CheckVoidStmt, "base", base).analyze(ira), + Id.Phi => return @fieldParentPtr(Phi, "base", base).analyze(ira), + Id.Br => return @fieldParentPtr(Br, "base", base).analyze(ira), + Id.AddImplicitReturnType => return @fieldParentPtr(AddImplicitReturnType, "base", base).analyze(ira), + Id.PtrType => return await (async @fieldParentPtr(PtrType, "base", base).analyze(ira) catch unreachable), } - unreachable; } - pub fn render(base: *Instruction, ofile: *ObjectFile, fn_val: *Value.Fn) (error{OutOfMemory}!?llvm.ValueRef) { + pub fn render(base: *Inst, ofile: *ObjectFile, fn_val: *Value.Fn) (error{OutOfMemory}!?llvm.ValueRef) { switch (base.id) { Id.Return => return @fieldParentPtr(Return, "base", base).render(ofile, fn_val), Id.Const => return @fieldParentPtr(Const, "base", base).render(ofile, fn_val), + Id.Call => return @fieldParentPtr(Call, "base", base).render(ofile, fn_val), + Id.DeclRef => unreachable, + Id.PtrType => unreachable, Id.Ref => @panic("TODO"), Id.DeclVar => @panic("TODO"), Id.CheckVoidStmt => @panic("TODO"), @@ -132,7 +141,22 @@ pub const Instruction = struct { } } - fn getAsParam(param: *Instruction) !*Instruction { + fn ref(base: *Inst, builder: *Builder) void { + base.ref_count += 1; + if (base.owner_bb != builder.current_basic_block and !base.isCompTime()) { + base.owner_bb.ref(builder); + } + } + + fn copyVal(base: *Inst, comp: *Compilation) !*Value { + if (base.parent.?.ref_count == 0) { + return base.val.KnownValue.derefAndCopy(comp); + } + return base.val.KnownValue.copy(comp); + } + + fn getAsParam(param: *Inst) !*Inst { + param.ref_count -= 1; const child = param.child orelse return error.SemanticAnalysisFailed; switch (child.val) { IrVal.Unknown => return error.SemanticAnalysisFailed, @@ -140,28 +164,72 @@ pub const Instruction = struct { } } + fn getConstVal(self: *Inst, ira: *Analyze) !*Value { + if (self.isCompTime()) { + return self.val.KnownValue; + } else { + try ira.addCompileError(self.span, "unable to evaluate constant expression"); + return error.SemanticAnalysisFailed; + } + } + + fn getAsConstType(param: *Inst, ira: *Analyze) !*Type { + const meta_type = Type.MetaType.get(ira.irb.comp); + meta_type.base.base.deref(ira.irb.comp); + + const inst = try param.getAsParam(); + const casted = try ira.implicitCast(inst, &meta_type.base); + const val = try casted.getConstVal(ira); + return val.cast(Value.Type).?; + } + + fn getAsConstAlign(param: *Inst, ira: *Analyze) !u32 { + return error.Unimplemented; + //const align_type = Type.Int.get_align(ira.irb.comp); + //align_type.base.base.deref(ira.irb.comp); + + //const inst = try param.getAsParam(); + //const casted = try ira.implicitCast(inst, align_type); + //const val = try casted.getConstVal(ira); + + //uint32_t align_bytes = bigint_as_unsigned(&const_val->data.x_bigint); + //if (align_bytes == 0) { + // ir_add_error(ira, value, buf_sprintf("alignment must be >= 1")); + // return false; + //} + + //if (!is_power_of_2(align_bytes)) { + // ir_add_error(ira, value, buf_sprintf("alignment value %" PRIu32 " is not a power of 2", align_bytes)); + // return false; + //} + } + /// asserts that the type is known - fn getKnownType(self: *Instruction) *Type { + fn getKnownType(self: *Inst) *Type { switch (self.val) { - IrVal.KnownType => |typeof| return typeof, - IrVal.KnownValue => |value| return value.typeof, + IrVal.KnownType => |typ| return typ, + IrVal.KnownValue => |value| return value.typ, IrVal.Unknown => unreachable, } } - pub fn setGenerated(base: *Instruction) void { + pub fn setGenerated(base: *Inst) void { base.is_generated = true; } - pub fn isNoReturn(base: *const Instruction) bool { + pub fn isNoReturn(base: *const Inst) bool { switch (base.val) { IrVal.Unknown => return false, - IrVal.KnownValue => |x| return x.typeof.id == Type.Id.NoReturn, - IrVal.KnownType => |typeof| return typeof.id == Type.Id.NoReturn, + IrVal.KnownValue => |x| return x.typ.id == Type.Id.NoReturn, + IrVal.KnownType => |typ| return typ.id == Type.Id.NoReturn, } } - pub fn linkToParent(self: *Instruction, parent: *Instruction) void { + pub fn isCompTime(base: *const Inst) bool { + return base.val == IrVal.KnownValue; + } + + pub fn linkToParent(self: *Inst, parent: *Inst) void { assert(self.parent == null); assert(parent.child == null); self.parent = parent; @@ -177,10 +245,89 @@ pub const Instruction = struct { Phi, Br, AddImplicitReturnType, + Call, + DeclRef, + PtrType, + }; + + pub const Call = struct { + base: Inst, + params: Params, + + const Params = struct { + fn_ref: *Inst, + args: []*Inst, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(self: *const Call) void { + std.debug.warn("#{}(", self.params.fn_ref.debug_id); + for (self.params.args) |arg| { + std.debug.warn("#{},", arg.debug_id); + } + std.debug.warn(")"); + } + + pub fn hasSideEffects(self: *const Call) bool { + return true; + } + + pub fn analyze(self: *const Call, ira: *Analyze) !*Inst { + const fn_ref = try self.params.fn_ref.getAsParam(); + const fn_ref_type = fn_ref.getKnownType(); + const fn_type = fn_ref_type.cast(Type.Fn) orelse { + try ira.addCompileError(fn_ref.span, "type '{}' not a function", fn_ref_type.name); + return error.SemanticAnalysisFailed; + }; + + if (fn_type.params.len != self.params.args.len) { + try ira.addCompileError( + self.base.span, + "expected {} arguments, found {}", + fn_type.params.len, + self.params.args.len, + ); + return error.SemanticAnalysisFailed; + } + + const args = try ira.irb.arena().alloc(*Inst, self.params.args.len); + for (self.params.args) |arg, i| { + args[i] = try arg.getAsParam(); + } + const new_inst = try ira.irb.build(Call, self.base.scope, self.base.span, Params{ + .fn_ref = fn_ref, + .args = args, + }); + new_inst.val = IrVal{ .KnownType = fn_type.return_type }; + return new_inst; + } + + pub fn render(self: *Call, ofile: *ObjectFile, fn_val: *Value.Fn) !?llvm.ValueRef { + const fn_ref = self.params.fn_ref.llvm_value.?; + + const args = try ofile.arena.alloc(llvm.ValueRef, self.params.args.len); + for (self.params.args) |arg, i| { + args[i] = arg.llvm_value.?; + } + + const llvm_cc = llvm.CCallConv; + const fn_inline = llvm.FnInline.Auto; + + return llvm.BuildCall( + ofile.builder, + fn_ref, + args.ptr, + @intCast(c_uint, args.len), + llvm_cc, + fn_inline, + c"", + ) orelse error.OutOfMemory; + } }; pub const Const = struct { - base: Instruction, + base: Inst, params: Params, const Params = struct {}; @@ -197,7 +344,7 @@ pub const Instruction = struct { return false; } - pub fn analyze(self: *const Const, ira: *Analyze) !*Instruction { + pub fn analyze(self: *const Const, ira: *Analyze) !*Inst { 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; @@ -209,11 +356,11 @@ pub const Instruction = struct { }; pub const Return = struct { - base: Instruction, + base: Inst, params: Params, const Params = struct { - return_value: *Instruction, + return_value: *Inst, }; const ir_val_init = IrVal.Init.NoReturn; @@ -226,7 +373,7 @@ pub const Instruction = struct { return true; } - pub fn analyze(self: *const Return, ira: *Analyze) !*Instruction { + pub fn analyze(self: *const Return, ira: *Analyze) !*Inst { const value = try self.params.return_value.getAsParam(); const casted_value = try ira.implicitCast(value, ira.explicit_return_type); @@ -235,25 +382,25 @@ pub const Instruction = struct { return ira.irb.build(Return, self.base.scope, self.base.span, Params{ .return_value = casted_value }); } - pub fn render(self: *Return, ofile: *ObjectFile, fn_val: *Value.Fn) ?llvm.ValueRef { + pub fn render(self: *Return, ofile: *ObjectFile, fn_val: *Value.Fn) !?llvm.ValueRef { const value = self.params.return_value.llvm_value; const return_type = self.params.return_value.getKnownType(); if (return_type.handleIsPtr()) { @panic("TODO"); } else { - _ = llvm.BuildRet(ofile.builder, value); + _ = llvm.BuildRet(ofile.builder, value) orelse return error.OutOfMemory; } return null; } }; pub const Ref = struct { - base: Instruction, + base: Inst, params: Params, const Params = struct { - target: *Instruction, + target: *Inst, mut: Type.Pointer.Mut, volatility: Type.Pointer.Vol, }; @@ -266,7 +413,7 @@ pub const Instruction = struct { return false; } - pub fn analyze(self: *const Ref, ira: *Analyze) !*Instruction { + pub async fn analyze(self: *const Ref, ira: *Analyze) !*Inst { const target = try self.params.target.getAsParam(); if (ira.getCompTimeValOrNullUndefOk(target)) |val| { @@ -275,7 +422,6 @@ pub const Instruction = struct { Value.Ptr.Mut.CompTimeConst, self.params.mut, self.params.volatility, - val.typeof.getAbiAlignment(ira.irb.comp), ); } @@ -285,14 +431,13 @@ pub const Instruction = struct { .volatility = self.params.volatility, }); const elem_type = target.getKnownType(); - const ptr_type = Type.Pointer.get( - ira.irb.comp, - elem_type, - self.params.mut, - self.params.volatility, - Type.Pointer.Size.One, - elem_type.getAbiAlignment(ira.irb.comp), - ); + const ptr_type = try await (async Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{ + .child_type = elem_type, + .mut = self.params.mut, + .vol = self.params.volatility, + .size = Type.Pointer.Size.One, + .alignment = Type.Pointer.Align.Abi, + }) catch unreachable); // TODO: potentially set the hint that this is a stack pointer. But it might not be - this // could be a ref of a global, for example new_inst.val = IrVal{ .KnownType = &ptr_type.base }; @@ -301,8 +446,99 @@ pub const Instruction = struct { } }; + pub const DeclRef = struct { + base: Inst, + params: Params, + + const Params = struct { + decl: *Decl, + lval: LVal, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(inst: *const DeclRef) void {} + + pub fn hasSideEffects(inst: *const DeclRef) bool { + return false; + } + + pub async fn analyze(self: *const DeclRef, ira: *Analyze) !*Inst { + (await (async ira.irb.comp.resolveDecl(self.params.decl) catch unreachable)) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => return error.SemanticAnalysisFailed, + }; + switch (self.params.decl.id) { + Decl.Id.CompTime => unreachable, + Decl.Id.Var => return error.Unimplemented, + Decl.Id.Fn => { + const fn_decl = @fieldParentPtr(Decl.Fn, "base", self.params.decl); + const decl_val = switch (fn_decl.value) { + Decl.Fn.Val.Unresolved => unreachable, + Decl.Fn.Val.Fn => |fn_val| &fn_val.base, + Decl.Fn.Val.FnProto => |fn_proto| &fn_proto.base, + }; + switch (self.params.lval) { + LVal.None => { + return ira.irb.buildConstValue(self.base.scope, self.base.span, decl_val); + }, + LVal.Ptr => return error.Unimplemented, + } + }, + } + } + }; + + pub const PtrType = struct { + base: Inst, + params: Params, + + const Params = struct { + child_type: *Inst, + mut: Type.Pointer.Mut, + vol: Type.Pointer.Vol, + size: Type.Pointer.Size, + alignment: ?*Inst, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(inst: *const PtrType) void {} + + pub fn hasSideEffects(inst: *const PtrType) bool { + return false; + } + + pub async fn analyze(self: *const PtrType, ira: *Analyze) !*Inst { + const child_type = try self.params.child_type.getAsConstType(ira); + // if (child_type->id == TypeTableEntryIdUnreachable) { + // ir_add_error(ira, &instruction->base, buf_sprintf("pointer to noreturn not allowed")); + // return ira->codegen->builtin_types.entry_invalid; + // } else if (child_type->id == TypeTableEntryIdOpaque && instruction->ptr_len == PtrLenUnknown) { + // ir_add_error(ira, &instruction->base, buf_sprintf("unknown-length pointer to opaque")); + // return ira->codegen->builtin_types.entry_invalid; + // } + const alignment = if (self.params.alignment) |align_inst| blk: { + const amt = try align_inst.getAsConstAlign(ira); + break :blk Type.Pointer.Align{ .Override = amt }; + } else blk: { + break :blk Type.Pointer.Align{ .Abi = {} }; + }; + const ptr_type = try await (async Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{ + .child_type = child_type, + .mut = self.params.mut, + .vol = self.params.vol, + .size = self.params.size, + .alignment = alignment, + }) catch unreachable); + ptr_type.base.base.deref(ira.irb.comp); + + return ira.irb.buildConstValue(self.base.scope, self.base.span, &ptr_type.base.base); + } + }; + pub const DeclVar = struct { - base: Instruction, + base: Inst, params: Params, const Params = struct { @@ -317,39 +553,46 @@ pub const Instruction = struct { return true; } - pub fn analyze(self: *const DeclVar, ira: *Analyze) !*Instruction { + pub fn analyze(self: *const DeclVar, ira: *Analyze) !*Inst { return error.Unimplemented; // TODO } }; pub const CheckVoidStmt = struct { - base: Instruction, + base: Inst, params: Params, const Params = struct { - target: *Instruction, + target: *Inst, }; const ir_val_init = IrVal.Init.Unknown; - pub fn dump(inst: *const CheckVoidStmt) void {} + pub fn dump(self: *const CheckVoidStmt) void { + std.debug.warn("#{}", self.params.target.debug_id); + } pub fn hasSideEffects(inst: *const CheckVoidStmt) bool { return true; } - pub fn analyze(self: *const CheckVoidStmt, ira: *Analyze) !*Instruction { - return error.Unimplemented; // TODO + pub fn analyze(self: *const CheckVoidStmt, ira: *Analyze) !*Inst { + const target = try self.params.target.getAsParam(); + if (target.getKnownType().id != Type.Id.Void) { + try ira.addCompileError(self.base.span, "expression value is ignored"); + return error.SemanticAnalysisFailed; + } + return ira.irb.buildConstVoid(self.base.scope, self.base.span, true); } }; pub const Phi = struct { - base: Instruction, + base: Inst, params: Params, const Params = struct { incoming_blocks: []*BasicBlock, - incoming_values: []*Instruction, + incoming_values: []*Inst, }; const ir_val_init = IrVal.Init.Unknown; @@ -360,18 +603,18 @@ pub const Instruction = struct { return false; } - pub fn analyze(self: *const Phi, ira: *Analyze) !*Instruction { + pub fn analyze(self: *const Phi, ira: *Analyze) !*Inst { return error.Unimplemented; // TODO } }; pub const Br = struct { - base: Instruction, + base: Inst, params: Params, const Params = struct { dest_block: *BasicBlock, - is_comptime: *Instruction, + is_comptime: *Inst, }; const ir_val_init = IrVal.Init.NoReturn; @@ -382,17 +625,41 @@ pub const Instruction = struct { return true; } - pub fn analyze(self: *const Br, ira: *Analyze) !*Instruction { + pub fn analyze(self: *const Br, ira: *Analyze) !*Inst { + return error.Unimplemented; // TODO + } + }; + + pub const CondBr = struct { + base: Inst, + params: Params, + + const Params = struct { + condition: *Inst, + then_block: *BasicBlock, + else_block: *BasicBlock, + is_comptime: *Inst, + }; + + const ir_val_init = IrVal.Init.NoReturn; + + pub fn dump(inst: *const CondBr) void {} + + pub fn hasSideEffects(inst: *const CondBr) bool { + return true; + } + + pub fn analyze(self: *const CondBr, ira: *Analyze) !*Inst { return error.Unimplemented; // TODO } }; pub const AddImplicitReturnType = struct { - base: Instruction, + base: Inst, params: Params, pub const Params = struct { - target: *Instruction, + target: *Inst, }; const ir_val_init = IrVal.Init.Unknown; @@ -405,12 +672,117 @@ pub const Instruction = struct { return true; } - pub fn analyze(self: *const AddImplicitReturnType, ira: *Analyze) !*Instruction { + pub fn analyze(self: *const AddImplicitReturnType, ira: *Analyze) !*Inst { 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 TestErr = struct { + base: Inst, + params: Params, + + pub const Params = struct { + target: *Inst, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(inst: *const TestErr) void { + std.debug.warn("#{}", inst.params.target.debug_id); + } + + pub fn hasSideEffects(inst: *const TestErr) bool { + return false; + } + + pub fn analyze(self: *const TestErr, ira: *Analyze) !*Inst { + const target = try self.params.target.getAsParam(); + const target_type = target.getKnownType(); + switch (target_type.id) { + Type.Id.ErrorUnion => { + return error.Unimplemented; + // if (instr_is_comptime(value)) { + // ConstExprValue *err_union_val = ir_resolve_const(ira, value, UndefBad); + // if (!err_union_val) + // return ira->codegen->builtin_types.entry_invalid; + + // if (err_union_val->special != ConstValSpecialRuntime) { + // ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); + // out_val->data.x_bool = (err_union_val->data.x_err_union.err != nullptr); + // return ira->codegen->builtin_types.entry_bool; + // } + // } + + // TypeTableEntry *err_set_type = type_entry->data.error_union.err_set_type; + // if (!resolve_inferred_error_set(ira->codegen, err_set_type, instruction->base.source_node)) { + // return ira->codegen->builtin_types.entry_invalid; + // } + // if (!type_is_global_error_set(err_set_type) && + // err_set_type->data.error_set.err_count == 0) + // { + // assert(err_set_type->data.error_set.infer_fn == nullptr); + // ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); + // out_val->data.x_bool = false; + // return ira->codegen->builtin_types.entry_bool; + // } + + // ir_build_test_err_from(&ira->new_irb, &instruction->base, value); + // return ira->codegen->builtin_types.entry_bool; + }, + Type.Id.ErrorSet => { + return ira.irb.buildConstBool(self.base.scope, self.base.span, true); + }, + else => { + return ira.irb.buildConstBool(self.base.scope, self.base.span, false); + }, + } + } + }; + + pub const TestCompTime = struct { + base: Inst, + params: Params, + + pub const Params = struct { + target: *Inst, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(inst: *const TestCompTime) void { + std.debug.warn("#{}", inst.params.target.debug_id); + } + + pub fn hasSideEffects(inst: *const TestCompTime) bool { + return false; + } + + pub fn analyze(self: *const TestCompTime, ira: *Analyze) !*Inst { + const target = try self.params.target.getAsParam(); + return ira.irb.buildConstBool(self.base.scope, self.base.span, target.isCompTime()); + } + }; + + pub const SaveErrRetAddr = struct { + base: Inst, + params: Params, + + const Params = struct {}; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(inst: *const SaveErrRetAddr) void {} + + pub fn hasSideEffects(inst: *const SaveErrRetAddr) bool { + return true; + } + + pub fn analyze(self: *const SaveErrRetAddr, ira: *Analyze) !*Inst { + return ira.irb.build(Inst.SaveErrRetAddr, self.base.scope, self.base.span, Params{}); + } + }; }; pub const Variable = struct { @@ -422,8 +794,8 @@ pub const BasicBlock = struct { name_hint: [*]const u8, // must be a C string literal debug_id: usize, scope: *Scope, - instruction_list: std.ArrayList(*Instruction), - ref_instruction: ?*Instruction, + instruction_list: std.ArrayList(*Inst), + ref_instruction: ?*Inst, /// for codegen llvm_block: llvm.BasicBlockRef, @@ -435,7 +807,7 @@ pub const BasicBlock = struct { /// the basic block that this one derives from in analysis parent: ?*BasicBlock, - pub fn ref(self: *BasicBlock) void { + pub fn ref(self: *BasicBlock, builder: *Builder) void { self.ref_count += 1; } @@ -453,7 +825,7 @@ pub const Code = struct { arena: std.heap.ArenaAllocator, return_type: ?*Type, - /// allocator is comp.a() + /// allocator is comp.gpa() pub fn destroy(self: *Code, allocator: *Allocator) void { self.arena.deinit(); allocator.destroy(self); @@ -470,6 +842,33 @@ pub const Code = struct { } } } + + /// returns a ref-incremented value, or adds a compile error + pub fn getCompTimeResult(self: *Code, comp: *Compilation) !*Value { + const bb = self.basic_block_list.at(0); + for (bb.instruction_list.toSliceConst()) |inst| { + if (inst.cast(Inst.Return)) |ret_inst| { + const ret_value = ret_inst.params.return_value; + if (ret_value.isCompTime()) { + return ret_value.val.KnownValue.getRef(); + } + try comp.addCompileError( + ret_value.scope.findRoot(), + ret_value.span, + "unable to evaluate constant expression", + ); + return error.SemanticAnalysisFailed; + } else if (inst.hasSideEffects()) { + try comp.addCompileError( + inst.scope.findRoot(), + inst.span, + "unable to evaluate constant expression", + ); + return error.SemanticAnalysisFailed; + } + } + unreachable; + } }; pub const Builder = struct { @@ -477,32 +876,36 @@ pub const Builder = struct { code: *Code, current_basic_block: *BasicBlock, next_debug_id: usize, - parsed_file: *ParsedFile, + root_scope: *Scope.Root, is_comptime: bool, + is_async: bool, + begin_scope: ?*Scope, pub const Error = Analyze.Error; - pub fn init(comp: *Compilation, parsed_file: *ParsedFile) !Builder { - const code = try comp.a().create(Code{ + pub fn init(comp: *Compilation, root_scope: *Scope.Root, begin_scope: ?*Scope) !Builder { + const code = try comp.gpa().create(Code{ .basic_block_list = undefined, - .arena = std.heap.ArenaAllocator.init(comp.a()), + .arena = std.heap.ArenaAllocator.init(comp.gpa()), .return_type = null, }); code.basic_block_list = std.ArrayList(*BasicBlock).init(&code.arena.allocator); - errdefer code.destroy(comp.a()); + errdefer code.destroy(comp.gpa()); return Builder{ .comp = comp, - .parsed_file = parsed_file, + .root_scope = root_scope, .current_basic_block = undefined, .code = code, .next_debug_id = 0, .is_comptime = false, + .is_async = false, + .begin_scope = begin_scope, }; } pub fn abort(self: *Builder) void { - self.code.destroy(self.comp.a()); + self.code.destroy(self.comp.gpa()); } /// Call code.destroy() when done @@ -517,7 +920,7 @@ pub const Builder = struct { .name_hint = name_hint, .debug_id = self.next_debug_id, .scope = scope, - .instruction_list = std.ArrayList(*Instruction).init(self.arena()), + .instruction_list = std.ArrayList(*Inst).init(self.arena()), .child = null, .parent = null, .ref_instruction = null, @@ -537,69 +940,210 @@ pub const Builder = struct { self.current_basic_block = basic_block; } - pub fn genNode(irb: *Builder, node: *ast.Node, scope: *Scope, lval: LVal) Error!*Instruction { + pub async fn genNode(irb: *Builder, node: *ast.Node, scope: *Scope, lval: LVal) Error!*Inst { 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.VarDecl => return error.Unimplemented, + ast.Node.Id.Defer => return error.Unimplemented, + ast.Node.Id.InfixOp => return error.Unimplemented, + ast.Node.Id.PrefixOp => { + const prefix_op = @fieldParentPtr(ast.Node.PrefixOp, "base", node); + switch (prefix_op.op) { + ast.Node.PrefixOp.Op.AddressOf => return error.Unimplemented, + ast.Node.PrefixOp.Op.ArrayType => |n| return error.Unimplemented, + ast.Node.PrefixOp.Op.Await => return error.Unimplemented, + ast.Node.PrefixOp.Op.BitNot => return error.Unimplemented, + ast.Node.PrefixOp.Op.BoolNot => return error.Unimplemented, + ast.Node.PrefixOp.Op.Cancel => return error.Unimplemented, + ast.Node.PrefixOp.Op.OptionalType => return error.Unimplemented, + ast.Node.PrefixOp.Op.Negation => return error.Unimplemented, + ast.Node.PrefixOp.Op.NegationWrap => return error.Unimplemented, + ast.Node.PrefixOp.Op.Resume => return error.Unimplemented, + ast.Node.PrefixOp.Op.PtrType => |ptr_info| { + const inst = try await (async irb.genPtrType(prefix_op, ptr_info, scope) catch unreachable); + return irb.lvalWrap(scope, inst, lval); + }, + ast.Node.PrefixOp.Op.SliceType => |ptr_info| return error.Unimplemented, + ast.Node.PrefixOp.Op.Try => return error.Unimplemented, + } + }, + ast.Node.Id.SuffixOp => { + const suffix_op = @fieldParentPtr(ast.Node.SuffixOp, "base", node); + switch (suffix_op.op) { + @TagType(ast.Node.SuffixOp.Op).Call => |*call| { + const inst = try await (async irb.genCall(suffix_op, call, scope) catch unreachable); + return irb.lvalWrap(scope, inst, lval); + }, + @TagType(ast.Node.SuffixOp.Op).ArrayAccess => |n| return error.Unimplemented, + @TagType(ast.Node.SuffixOp.Op).Slice => |slice| return error.Unimplemented, + @TagType(ast.Node.SuffixOp.Op).ArrayInitializer => |init_list| return error.Unimplemented, + @TagType(ast.Node.SuffixOp.Op).StructInitializer => |init_list| return error.Unimplemented, + @TagType(ast.Node.SuffixOp.Op).Deref => return error.Unimplemented, + @TagType(ast.Node.SuffixOp.Op).UnwrapOptional => return error.Unimplemented, + } + }, + ast.Node.Id.Switch => return error.Unimplemented, + ast.Node.Id.While => return error.Unimplemented, + ast.Node.Id.For => return error.Unimplemented, + ast.Node.Id.If => return error.Unimplemented, + ast.Node.Id.ControlFlowExpression => { + const control_flow_expr = @fieldParentPtr(ast.Node.ControlFlowExpression, "base", node); + return await (async irb.genControlFlowExpr(control_flow_expr, scope, lval) catch unreachable); + }, + ast.Node.Id.Suspend => return error.Unimplemented, + ast.Node.Id.VarType => return error.Unimplemented, + ast.Node.Id.ErrorType => return error.Unimplemented, + ast.Node.Id.FnProto => return error.Unimplemented, + ast.Node.Id.PromiseType => return error.Unimplemented, + ast.Node.Id.IntegerLiteral => { + const int_lit = @fieldParentPtr(ast.Node.IntegerLiteral, "base", node); + return irb.lvalWrap(scope, try irb.genIntLit(int_lit, scope), lval); + }, + ast.Node.Id.FloatLiteral => return error.Unimplemented, + ast.Node.Id.StringLiteral => { + const str_lit = @fieldParentPtr(ast.Node.StringLiteral, "base", node); + const inst = try await (async irb.genStrLit(str_lit, scope) catch unreachable); + return irb.lvalWrap(scope, inst, lval); + }, + ast.Node.Id.MultilineStringLiteral => return error.Unimplemented, + ast.Node.Id.CharLiteral => return error.Unimplemented, + ast.Node.Id.BoolLiteral => return error.Unimplemented, + ast.Node.Id.NullLiteral => return error.Unimplemented, + ast.Node.Id.UndefinedLiteral => return error.Unimplemented, + ast.Node.Id.ThisLiteral => return error.Unimplemented, + ast.Node.Id.Unreachable => return error.Unimplemented, + ast.Node.Id.Identifier => { + const identifier = @fieldParentPtr(ast.Node.Identifier, "base", node); + return await (async irb.genIdentifier(identifier, scope, lval) catch unreachable); + }, ast.Node.Id.GroupedExpression => { const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", node); - return irb.genNode(grouped_expr.expr, scope, lval); + return await (async irb.genNode(grouped_expr.expr, scope, lval) catch unreachable); }, - 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.BuiltinCall => return error.Unimplemented, + ast.Node.Id.ErrorSetDecl => return error.Unimplemented, + ast.Node.Id.ContainerDecl => return error.Unimplemented, + ast.Node.Id.Asm => return error.Unimplemented, + ast.Node.Id.Comptime => return error.Unimplemented, ast.Node.Id.Block => { const block = @fieldParentPtr(ast.Node.Block, "base", node); - return irb.lvalWrap(scope, try irb.genBlock(block, scope), lval); + const inst = try await (async irb.genBlock(block, scope) catch unreachable); + return irb.lvalWrap(scope, inst, 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"), + ast.Node.Id.DocComment => return error.Unimplemented, + ast.Node.Id.SwitchCase => return error.Unimplemented, + ast.Node.Id.SwitchElse => return error.Unimplemented, + ast.Node.Id.Else => return error.Unimplemented, + ast.Node.Id.Payload => return error.Unimplemented, + ast.Node.Id.PointerPayload => return error.Unimplemented, + ast.Node.Id.PointerIndexPayload => return error.Unimplemented, + ast.Node.Id.StructField => return error.Unimplemented, + ast.Node.Id.UnionTag => return error.Unimplemented, + ast.Node.Id.EnumTag => return error.Unimplemented, + ast.Node.Id.ErrorTag => return error.Unimplemented, + ast.Node.Id.AsmInput => return error.Unimplemented, + ast.Node.Id.AsmOutput => return error.Unimplemented, + ast.Node.Id.AsyncAttribute => return error.Unimplemented, + ast.Node.Id.ParamDecl => return error.Unimplemented, + ast.Node.Id.FieldInitializer => return error.Unimplemented, } } + async fn genCall(irb: *Builder, suffix_op: *ast.Node.SuffixOp, call: *ast.Node.SuffixOp.Op.Call, scope: *Scope) !*Inst { + const fn_ref = try await (async irb.genNode(suffix_op.lhs, scope, LVal.None) catch unreachable); + + const args = try irb.arena().alloc(*Inst, call.params.len); + var it = call.params.iterator(0); + var i: usize = 0; + while (it.next()) |arg_node_ptr| : (i += 1) { + args[i] = try await (async irb.genNode(arg_node_ptr.*, scope, LVal.None) catch unreachable); + } + + //bool is_async = node->data.fn_call_expr.is_async; + //IrInstruction *async_allocator = nullptr; + //if (is_async) { + // if (node->data.fn_call_expr.async_allocator) { + // async_allocator = ir_gen_node(irb, node->data.fn_call_expr.async_allocator, scope); + // if (async_allocator == irb->codegen->invalid_instruction) + // return async_allocator; + // } + //} + + return irb.build(Inst.Call, scope, Span.token(suffix_op.rtoken), Inst.Call.Params{ + .fn_ref = fn_ref, + .args = args, + }); + //IrInstruction *fn_call = ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, FnInlineAuto, is_async, async_allocator, nullptr); + //return ir_lval_wrap(irb, scope, fn_call, lval); + } + + async fn genPtrType( + irb: *Builder, + prefix_op: *ast.Node.PrefixOp, + ptr_info: ast.Node.PrefixOp.PtrInfo, + scope: *Scope, + ) !*Inst { + // TODO port more logic + + //assert(node->type == NodeTypePointerType); + //PtrLen ptr_len = (node->data.pointer_type.star_token->id == TokenIdStar || + // node->data.pointer_type.star_token->id == TokenIdStarStar) ? PtrLenSingle : PtrLenUnknown; + //bool is_const = node->data.pointer_type.is_const; + //bool is_volatile = node->data.pointer_type.is_volatile; + //AstNode *expr_node = node->data.pointer_type.op_expr; + //AstNode *align_expr = node->data.pointer_type.align_expr; + + //IrInstruction *align_value; + //if (align_expr != nullptr) { + // align_value = ir_gen_node(irb, align_expr, scope); + // if (align_value == irb->codegen->invalid_instruction) + // return align_value; + //} else { + // align_value = nullptr; + //} + const child_type = try await (async irb.genNode(prefix_op.rhs, scope, LVal.None) catch unreachable); + + //uint32_t bit_offset_start = 0; + //if (node->data.pointer_type.bit_offset_start != nullptr) { + // if (!bigint_fits_in_bits(node->data.pointer_type.bit_offset_start, 32, false)) { + // Buf *val_buf = buf_alloc(); + // bigint_append_buf(val_buf, node->data.pointer_type.bit_offset_start, 10); + // exec_add_error_node(irb->codegen, irb->exec, node, + // buf_sprintf("value %s too large for u32 bit offset", buf_ptr(val_buf))); + // return irb->codegen->invalid_instruction; + // } + // bit_offset_start = bigint_as_unsigned(node->data.pointer_type.bit_offset_start); + //} + + //uint32_t bit_offset_end = 0; + //if (node->data.pointer_type.bit_offset_end != nullptr) { + // if (!bigint_fits_in_bits(node->data.pointer_type.bit_offset_end, 32, false)) { + // Buf *val_buf = buf_alloc(); + // bigint_append_buf(val_buf, node->data.pointer_type.bit_offset_end, 10); + // exec_add_error_node(irb->codegen, irb->exec, node, + // buf_sprintf("value %s too large for u32 bit offset", buf_ptr(val_buf))); + // return irb->codegen->invalid_instruction; + // } + // bit_offset_end = bigint_as_unsigned(node->data.pointer_type.bit_offset_end); + //} + + //if ((bit_offset_start != 0 || bit_offset_end != 0) && bit_offset_start >= bit_offset_end) { + // exec_add_error_node(irb->codegen, irb->exec, node, + // buf_sprintf("bit offset start must be less than bit offset end")); + // return irb->codegen->invalid_instruction; + //} + + return irb.build(Inst.PtrType, scope, Span.node(&prefix_op.base), Inst.PtrType.Params{ + .child_type = child_type, + .mut = Type.Pointer.Mut.Mut, + .vol = Type.Pointer.Vol.Non, + .size = Type.Pointer.Size.Many, + .alignment = null, + }); + } + fn isCompTime(irb: *Builder, target_scope: *Scope) bool { if (irb.is_comptime) return true; @@ -610,15 +1154,105 @@ pub const Builder = struct { Scope.Id.CompTime => return true, Scope.Id.FnDef => return false, Scope.Id.Decls => unreachable, + Scope.Id.Root => unreachable, Scope.Id.Block, Scope.Id.Defer, Scope.Id.DeferExpr, - => scope = scope.parent orelse return false, + => scope = scope.parent.?, } } } - pub fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Instruction { + pub fn genIntLit(irb: *Builder, int_lit: *ast.Node.IntegerLiteral, scope: *Scope) !*Inst { + const int_token = irb.root_scope.tree.tokenSlice(int_lit.token); + + var base: u8 = undefined; + var rest: []const u8 = undefined; + if (int_token.len >= 3 and int_token[0] == '0') { + base = switch (int_token[1]) { + 'b' => u8(2), + 'o' => u8(8), + 'x' => u8(16), + else => unreachable, + }; + rest = int_token[2..]; + } else { + base = 10; + rest = int_token; + } + + const comptime_int_type = Type.ComptimeInt.get(irb.comp); + defer comptime_int_type.base.base.deref(irb.comp); + + const int_val = Value.Int.createFromString( + irb.comp, + &comptime_int_type.base, + base, + rest, + ) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.InvalidBase => unreachable, + error.InvalidCharForDigit => unreachable, + error.DigitTooLargeForBase => unreachable, + }; + errdefer int_val.base.deref(irb.comp); + + const inst = try irb.build(Inst.Const, scope, Span.token(int_lit.token), Inst.Const.Params{}); + inst.val = IrVal{ .KnownValue = &int_val.base }; + return inst; + } + + pub async fn genStrLit(irb: *Builder, str_lit: *ast.Node.StringLiteral, scope: *Scope) !*Inst { + const str_token = irb.root_scope.tree.tokenSlice(str_lit.token); + const src_span = Span.token(str_lit.token); + + var bad_index: usize = undefined; + var buf = std.zig.parseStringLiteral(irb.comp.gpa(), str_token, &bad_index) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.InvalidCharacter => { + try irb.comp.addCompileError( + irb.root_scope, + src_span, + "invalid character in string literal: '{c}'", + str_token[bad_index], + ); + return error.SemanticAnalysisFailed; + }, + }; + var buf_cleaned = false; + errdefer if (!buf_cleaned) irb.comp.gpa().free(buf); + + if (str_token[0] == 'c') { + // first we add a null + buf = try irb.comp.gpa().realloc(u8, buf, buf.len + 1); + buf[buf.len - 1] = 0; + + // next make an array value + const array_val = try await (async Value.Array.createOwnedBuffer(irb.comp, buf) catch unreachable); + buf_cleaned = true; + defer array_val.base.deref(irb.comp); + + // then make a pointer value pointing at the first element + const ptr_val = try await (async Value.Ptr.createArrayElemPtr( + irb.comp, + array_val, + Type.Pointer.Mut.Const, + Type.Pointer.Size.Many, + 0, + ) catch unreachable); + defer ptr_val.base.deref(irb.comp); + + return irb.buildConstValue(scope, src_span, &ptr_val.base); + } else { + const array_val = try await (async Value.Array.createOwnedBuffer(irb.comp, buf) catch unreachable); + buf_cleaned = true; + defer array_val.base.deref(irb.comp); + + return irb.buildConstValue(scope, src_span, &array_val.base); + } + } + + pub async fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Inst { const block_scope = try Scope.Block.create(irb.comp, parent_scope); const outer_block_scope = &block_scope.base; @@ -636,7 +1270,7 @@ pub const Builder = struct { } if (block.label) |label| { - block_scope.incoming_values = std.ArrayList(*Instruction).init(irb.arena()); + block_scope.incoming_values = std.ArrayList(*Inst).init(irb.arena()); block_scope.incoming_blocks = std.ArrayList(*BasicBlock).init(irb.arena()); block_scope.end_block = try irb.createBasicBlock(parent_scope, c"BlockEnd"); block_scope.is_comptime = try irb.buildConstBool( @@ -647,7 +1281,7 @@ pub const Builder = struct { } var is_continuation_unreachable = false; - var noreturn_return_value: ?*Instruction = null; + var noreturn_return_value: ?*Inst = null; var stmt_it = block.statements.iterator(0); while (stmt_it.next()) |statement_node_ptr| { @@ -655,7 +1289,7 @@ pub const Builder = struct { 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 defer_token = irb.root_scope.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, @@ -666,7 +1300,7 @@ pub const Builder = struct { child_scope = &defer_child_scope.base; continue; } - const statement_value = try irb.genNode(statement_node, child_scope, LVal.None); + const statement_value = try await (async irb.genNode(statement_node, child_scope, LVal.None) catch unreachable); is_continuation_unreachable = statement_value.isNoReturn(); if (is_continuation_unreachable) { @@ -674,16 +1308,19 @@ pub const Builder = struct { noreturn_return_value = statement_value; } - if (statement_value.cast(Instruction.DeclVar)) |decl_var| { + if (statement_value.cast(Inst.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, + Inst.CheckVoidStmt, child_scope, - statement_value.span, - Instruction.CheckVoidStmt.Params{ .target = statement_value }, + Span{ + .first = statement_node.firstToken(), + .last = statement_node.lastToken(), + }, + Inst.CheckVoidStmt.Params{ .target = statement_value }, ); } } @@ -695,7 +1332,7 @@ pub const Builder = struct { } try irb.setCursorAtEndAndAppendBlock(block_scope.end_block); - return irb.build(Instruction.Phi, parent_scope, Span.token(block.rbrace), Instruction.Phi.Params{ + return irb.build(Inst.Phi, parent_scope, Span.token(block.rbrace), Inst.Phi.Params{ .incoming_blocks = block_scope.incoming_blocks.toOwnedSlice(), .incoming_values = block_scope.incoming_values.toOwnedSlice(), }); @@ -706,26 +1343,216 @@ pub const Builder = struct { 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 await (async irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit) catch unreachable); - _ = try irb.buildGen(Instruction.Br, parent_scope, Span.token(block.rbrace), Instruction.Br.Params{ + _ = try irb.buildGen(Inst.Br, parent_scope, Span.token(block.rbrace), Inst.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{ + return irb.build(Inst.Phi, parent_scope, Span.token(block.rbrace), Inst.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); + _ = try await (async irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit) catch unreachable); return irb.buildConstVoid(child_scope, Span.token(block.rbrace), true); } - fn genDefersForBlock( + pub async fn genControlFlowExpr( + irb: *Builder, + control_flow_expr: *ast.Node.ControlFlowExpression, + scope: *Scope, + lval: LVal, + ) !*Inst { + switch (control_flow_expr.kind) { + ast.Node.ControlFlowExpression.Kind.Break => |arg| return error.Unimplemented, + ast.Node.ControlFlowExpression.Kind.Continue => |arg| return error.Unimplemented, + ast.Node.ControlFlowExpression.Kind.Return => { + const src_span = Span.token(control_flow_expr.ltoken); + if (scope.findFnDef() == null) { + try irb.comp.addCompileError( + irb.root_scope, + src_span, + "return expression outside function definition", + ); + return error.SemanticAnalysisFailed; + } + + if (scope.findDeferExpr()) |scope_defer_expr| { + if (!scope_defer_expr.reported_err) { + try irb.comp.addCompileError( + irb.root_scope, + src_span, + "cannot return from defer expression", + ); + scope_defer_expr.reported_err = true; + } + return error.SemanticAnalysisFailed; + } + + const outer_scope = irb.begin_scope.?; + const return_value = if (control_flow_expr.rhs) |rhs| blk: { + break :blk try await (async irb.genNode(rhs, scope, LVal.None) catch unreachable); + } else blk: { + break :blk try irb.buildConstVoid(scope, src_span, true); + }; + + const defer_counts = irb.countDefers(scope, outer_scope); + const have_err_defers = defer_counts.error_exit != 0; + if (have_err_defers or irb.comp.have_err_ret_tracing) { + const err_block = try irb.createBasicBlock(scope, c"ErrRetErr"); + const ok_block = try irb.createBasicBlock(scope, c"ErrRetOk"); + if (!have_err_defers) { + _ = try await (async irb.genDefersForBlock(scope, outer_scope, Scope.Defer.Kind.ScopeExit) catch unreachable); + } + + const is_err = try irb.build( + Inst.TestErr, + scope, + src_span, + Inst.TestErr.Params{ .target = return_value }, + ); + + const err_is_comptime = try irb.buildTestCompTime(scope, src_span, is_err); + + _ = try irb.buildGen(Inst.CondBr, scope, src_span, Inst.CondBr.Params{ + .condition = is_err, + .then_block = err_block, + .else_block = ok_block, + .is_comptime = err_is_comptime, + }); + + const ret_stmt_block = try irb.createBasicBlock(scope, c"RetStmt"); + + try irb.setCursorAtEndAndAppendBlock(err_block); + if (have_err_defers) { + _ = try await (async irb.genDefersForBlock(scope, outer_scope, Scope.Defer.Kind.ErrorExit) catch unreachable); + } + if (irb.comp.have_err_ret_tracing and !irb.isCompTime(scope)) { + _ = try irb.build(Inst.SaveErrRetAddr, scope, src_span, Inst.SaveErrRetAddr.Params{}); + } + _ = try irb.build(Inst.Br, scope, src_span, Inst.Br.Params{ + .dest_block = ret_stmt_block, + .is_comptime = err_is_comptime, + }); + + try irb.setCursorAtEndAndAppendBlock(ok_block); + if (have_err_defers) { + _ = try await (async irb.genDefersForBlock(scope, outer_scope, Scope.Defer.Kind.ScopeExit) catch unreachable); + } + _ = try irb.build(Inst.Br, scope, src_span, Inst.Br.Params{ + .dest_block = ret_stmt_block, + .is_comptime = err_is_comptime, + }); + + try irb.setCursorAtEndAndAppendBlock(ret_stmt_block); + return irb.genAsyncReturn(scope, src_span, return_value, false); + } else { + _ = try await (async irb.genDefersForBlock(scope, outer_scope, Scope.Defer.Kind.ScopeExit) catch unreachable); + return irb.genAsyncReturn(scope, src_span, return_value, false); + } + }, + } + } + + pub async fn genIdentifier(irb: *Builder, identifier: *ast.Node.Identifier, scope: *Scope, lval: LVal) !*Inst { + const src_span = Span.token(identifier.token); + const name = irb.root_scope.tree.tokenSlice(identifier.token); + + //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); + // const_instruction->base.value.special = ConstValSpecialStatic; + // const_instruction->base.value.data.x_ptr.special = ConstPtrSpecialDiscard; + // return &const_instruction->base; + //} + + if (await (async irb.comp.getPrimitiveType(name) catch unreachable)) |result| { + if (result) |primitive_type| { + defer primitive_type.base.deref(irb.comp); + switch (lval) { + // if (lval == LValPtr) { + // return ir_build_ref(irb, scope, node, value, false, false); + LVal.Ptr => return error.Unimplemented, + LVal.None => return irb.buildConstValue(scope, src_span, &primitive_type.base), + } + } + } else |err| switch (err) { + error.Overflow => { + try irb.comp.addCompileError(irb.root_scope, src_span, "integer too large"); + return error.SemanticAnalysisFailed; + }, + error.OutOfMemory => return error.OutOfMemory, + } + + //VariableTableEntry *var = find_variable(irb->codegen, scope, variable_name); + //if (var) { + // IrInstruction *var_ptr = ir_build_var_ptr(irb, scope, node, var); + // if (lval == LValPtr) + // return var_ptr; + // else + // return ir_build_load_ptr(irb, scope, node, var_ptr); + //} + + if (await (async irb.findDecl(scope, name) catch unreachable)) |decl| { + return irb.build(Inst.DeclRef, scope, src_span, Inst.DeclRef.Params{ + .decl = decl, + .lval = lval, + }); + } + + //if (node->owner->any_imports_failed) { + // // skip the error message since we had a failing import in this file + // // if an import breaks we don't need redundant undeclared identifier errors + // return irb->codegen->invalid_instruction; + //} + + // TODO put a variable of same name with invalid type in global scope + // so that future references to this same name will find a variable with an invalid type + + try irb.comp.addCompileError(irb.root_scope, src_span, "unknown identifier '{}'", name); + return error.SemanticAnalysisFailed; + } + + const DeferCounts = struct { + scope_exit: usize, + error_exit: usize, + }; + + fn countDefers(irb: *Builder, inner_scope: *Scope, outer_scope: *Scope) DeferCounts { + var result = DeferCounts{ .scope_exit = 0, .error_exit = 0 }; + + var scope = inner_scope; + while (scope != outer_scope) { + switch (scope.id) { + Scope.Id.Defer => { + const defer_scope = @fieldParentPtr(Scope.Defer, "base", scope); + switch (defer_scope.kind) { + Scope.Defer.Kind.ScopeExit => result.scope_exit += 1, + Scope.Defer.Kind.ErrorExit => result.error_exit += 1, + } + scope = scope.parent orelse break; + }, + Scope.Id.FnDef => break, + + Scope.Id.CompTime, + Scope.Id.Block, + Scope.Id.Decls, + Scope.Id.Root, + => scope = scope.parent orelse break, + + Scope.Id.DeferExpr => unreachable, + } + } + return result; + } + + async fn genDefersForBlock( irb: *Builder, inner_scope: *Scope, outer_scope: *Scope, @@ -743,25 +1570,26 @@ pub const Builder = struct { }; if (generate) { const defer_expr_scope = defer_scope.defer_expr_scope; - const instruction = try irb.genNode( + const instruction = try await (async irb.genNode( defer_expr_scope.expr_node, &defer_expr_scope.base, LVal.None, - ); + ) catch unreachable); if (instruction.isNoReturn()) { is_noreturn = true; } else { _ = try irb.build( - Instruction.CheckVoidStmt, + Inst.CheckVoidStmt, &defer_expr_scope.base, Span.token(defer_expr_scope.expr_node.lastToken()), - Instruction.CheckVoidStmt.Params{ .target = instruction }, + Inst.CheckVoidStmt.Params{ .target = instruction }, ); } } }, Scope.Id.FnDef, Scope.Id.Decls, + Scope.Id.Root, => return is_noreturn, Scope.Id.CompTime, @@ -773,13 +1601,13 @@ pub const Builder = struct { } } - pub fn lvalWrap(irb: *Builder, scope: *Scope, instruction: *Instruction, lval: LVal) !*Instruction { + pub fn lvalWrap(irb: *Builder, scope: *Scope, instruction: *Inst, lval: LVal) !*Inst { 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{ + return irb.build(Inst.Ref, scope, instruction.span, Inst.Ref.Params{ .target = instruction, .mut = Type.Pointer.Mut.Const, .volatility = Type.Pointer.Vol.Non, @@ -799,10 +1627,10 @@ pub const Builder = struct { span: Span, params: I.Params, is_generated: bool, - ) !*Instruction { + ) !*Inst { const inst = try self.arena().create(I{ - .base = Instruction{ - .id = Instruction.typeToId(I), + .base = Inst{ + .id = Inst.typeToId(I), .is_generated = is_generated, .scope = scope, .debug_id = self.next_debug_id, @@ -816,6 +1644,7 @@ pub const Builder = struct { .child = null, .parent = null, .llvm_value = undefined, + .owner_bb = self.current_basic_block, }, .params = params, }); @@ -825,9 +1654,27 @@ pub const Builder = struct { 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 => {}, + *Inst => @field(inst.params, @memberName(I.Params, i)).ref(self), + *BasicBlock => @field(inst.params, @memberName(I.Params, i)).ref(self), + ?*Inst => if (@field(inst.params, @memberName(I.Params, i))) |other| other.ref(self), + []*Inst => { + // TODO https://github.com/ziglang/zig/issues/1269 + for (@field(inst.params, @memberName(I.Params, i))) |other| + other.ref(self); + }, + []*BasicBlock => { + // TODO https://github.com/ziglang/zig/issues/1269 + for (@field(inst.params, @memberName(I.Params, i))) |other| + other.ref(self); + }, + Type.Pointer.Mut, + Type.Pointer.Vol, + Type.Pointer.Size, + LVal, + *Decl, + => {}, + // it's ok to add more types here, just make sure any instructions are ref'd appropriately + else => @compileError("unrecognized type in Params: " ++ @typeName(FieldType)), } } @@ -842,7 +1689,7 @@ pub const Builder = struct { scope: *Scope, span: Span, params: I.Params, - ) !*Instruction { + ) !*Inst { return self.buildExtra(I, scope, span, params, false); } @@ -852,21 +1699,95 @@ pub const Builder = struct { scope: *Scope, span: Span, params: I.Params, - ) !*Instruction { + ) !*Inst { 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{}); + fn buildConstBool(self: *Builder, scope: *Scope, span: Span, x: bool) !*Inst { + const inst = try self.build(Inst.Const, scope, span, Inst.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); + fn buildConstVoid(self: *Builder, scope: *Scope, span: Span, is_generated: bool) !*Inst { + const inst = try self.buildExtra(Inst.Const, scope, span, Inst.Const.Params{}, is_generated); inst.val = IrVal{ .KnownValue = &Value.Void.get(self.comp).base }; return inst; } + + fn buildConstValue(self: *Builder, scope: *Scope, span: Span, v: *Value) !*Inst { + const inst = try self.build(Inst.Const, scope, span, Inst.Const.Params{}); + inst.val = IrVal{ .KnownValue = v.getRef() }; + return inst; + } + + /// If the code is explicitly set to be comptime, then builds a const bool, + /// otherwise builds a TestCompTime instruction. + fn buildTestCompTime(self: *Builder, scope: *Scope, span: Span, target: *Inst) !*Inst { + if (self.isCompTime(scope)) { + return self.buildConstBool(scope, span, true); + } else { + return self.build( + Inst.TestCompTime, + scope, + span, + Inst.TestCompTime.Params{ .target = target }, + ); + } + } + + fn genAsyncReturn(irb: *Builder, scope: *Scope, span: Span, result: *Inst, is_gen: bool) !*Inst { + _ = irb.buildGen( + Inst.AddImplicitReturnType, + scope, + span, + Inst.AddImplicitReturnType.Params{ .target = result }, + ); + + if (!irb.is_async) { + return irb.buildExtra( + Inst.Return, + scope, + span, + Inst.Return.Params{ .return_value = result }, + is_gen, + ); + } + return error.Unimplemented; + + //ir_build_store_ptr(irb, scope, node, irb->exec->coro_result_field_ptr, return_value); + //IrInstruction *promise_type_val = ir_build_const_type(irb, scope, node, + // get_optional_type(irb->codegen, irb->codegen->builtin_types.entry_promise)); + //// TODO replace replacement_value with @intToPtr(?promise, 0x1) when it doesn't crash zig + //IrInstruction *replacement_value = irb->exec->coro_handle; + //IrInstruction *maybe_await_handle = ir_build_atomic_rmw(irb, scope, node, + // promise_type_val, irb->exec->coro_awaiter_field_ptr, nullptr, replacement_value, nullptr, + // AtomicRmwOp_xchg, AtomicOrderSeqCst); + //ir_build_store_ptr(irb, scope, node, irb->exec->await_handle_var_ptr, maybe_await_handle); + //IrInstruction *is_non_null = ir_build_test_nonnull(irb, scope, node, maybe_await_handle); + //IrInstruction *is_comptime = ir_build_const_bool(irb, scope, node, false); + //return ir_build_cond_br(irb, scope, node, is_non_null, irb->exec->coro_normal_final, irb->exec->coro_early_final, + // is_comptime); + //// the above blocks are rendered by ir_gen after the rest of codegen + } + + async fn findDecl(irb: *Builder, scope: *Scope, name: []const u8) ?*Decl { + var s = scope; + while (true) { + switch (s.id) { + Scope.Id.Decls => { + const decls = @fieldParentPtr(Scope.Decls, "base", s); + const table = await (async decls.getTableReadOnly() catch unreachable); + if (table.get(name)) |entry| { + return entry.value; + } + }, + Scope.Id.Root => return null, + else => {}, + } + s = s.parent.?; + } + } }; const Analyze = struct { @@ -875,7 +1796,7 @@ const Analyze = struct { const_predecessor_bb: ?*BasicBlock, parent_basic_block: *BasicBlock, instruction_index: usize, - src_implicit_return_type_list: std.ArrayList(*Instruction), + src_implicit_return_type_list: std.ArrayList(*Inst), explicit_return_type: ?*Type, pub const Error = error{ @@ -889,8 +1810,8 @@ const Analyze = struct { OutOfMemory, }; - pub fn init(comp: *Compilation, parsed_file: *ParsedFile, explicit_return_type: ?*Type) !Analyze { - var irb = try Builder.init(comp, parsed_file); + pub fn init(comp: *Compilation, root_scope: *Scope.Root, explicit_return_type: ?*Type) !Analyze { + var irb = try Builder.init(comp, root_scope, null); errdefer irb.abort(); return Analyze{ @@ -899,7 +1820,7 @@ const Analyze = struct { .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()), + .src_implicit_return_type_list = std.ArrayList(*Inst).init(irb.arena()), .explicit_return_type = explicit_return_type, }; } @@ -908,7 +1829,7 @@ const Analyze = struct { self.irb.abort(); } - pub fn getNewBasicBlock(self: *Analyze, old_bb: *BasicBlock, ref_old_instruction: ?*Instruction) !*BasicBlock { + pub fn getNewBasicBlock(self: *Analyze, old_bb: *BasicBlock, ref_old_instruction: ?*Inst) !*BasicBlock { if (old_bb.child) |child| { if (ref_old_instruction == null or child.ref_instruction != ref_old_instruction) return child; @@ -968,21 +1889,478 @@ const Analyze = struct { } fn addCompileError(self: *Analyze, span: Span, comptime fmt: []const u8, args: ...) !void { - return self.irb.comp.addCompileError(self.irb.parsed_file, span, fmt, args); + return self.irb.comp.addCompileError(self.irb.root_scope, span, fmt, args); } - fn resolvePeerTypes(self: *Analyze, expected_type: ?*Type, peers: []const *Instruction) Analyze.Error!*Type { + fn resolvePeerTypes(self: *Analyze, expected_type: ?*Type, peers: []const *Inst) 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 { + fn implicitCast(self: *Analyze, target: *Inst, optional_dest_type: ?*Type) Analyze.Error!*Inst { const dest_type = optional_dest_type orelse return target; - @panic("TODO implicitCast"); + const from_type = target.getKnownType(); + if (from_type == dest_type or from_type.id == Type.Id.NoReturn) return target; + return self.analyzeCast(target, target, dest_type); } - fn getCompTimeValOrNullUndefOk(self: *Analyze, target: *Instruction) ?*Value { - @panic("TODO getCompTimeValOrNullUndefOk"); + fn analyzeCast(ira: *Analyze, source_instr: *Inst, target: *Inst, dest_type: *Type) !*Inst { + const from_type = target.getKnownType(); + + //if (type_is_invalid(wanted_type) || type_is_invalid(actual_type)) { + // return ira->codegen->invalid_instruction; + //} + + //// perfect match or non-const to const + //ConstCastOnly const_cast_result = types_match_const_cast_only(ira, wanted_type, actual_type, + // source_node, false); + //if (const_cast_result.id == ConstCastResultIdOk) { + // return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpNoop, false); + //} + + //// widening conversion + //if (wanted_type->id == TypeTableEntryIdInt && + // actual_type->id == TypeTableEntryIdInt && + // wanted_type->data.integral.is_signed == actual_type->data.integral.is_signed && + // wanted_type->data.integral.bit_count >= actual_type->data.integral.bit_count) + //{ + // return ir_analyze_widen_or_shorten(ira, source_instr, value, wanted_type); + //} + + //// small enough unsigned ints can get casted to large enough signed ints + //if (wanted_type->id == TypeTableEntryIdInt && wanted_type->data.integral.is_signed && + // actual_type->id == TypeTableEntryIdInt && !actual_type->data.integral.is_signed && + // wanted_type->data.integral.bit_count > actual_type->data.integral.bit_count) + //{ + // return ir_analyze_widen_or_shorten(ira, source_instr, value, wanted_type); + //} + + //// float widening conversion + //if (wanted_type->id == TypeTableEntryIdFloat && + // actual_type->id == TypeTableEntryIdFloat && + // wanted_type->data.floating.bit_count >= actual_type->data.floating.bit_count) + //{ + // return ir_analyze_widen_or_shorten(ira, source_instr, value, wanted_type); + //} + + //// cast from [N]T to []const T + //if (is_slice(wanted_type) && actual_type->id == TypeTableEntryIdArray) { + // TypeTableEntry *ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry; + // assert(ptr_type->id == TypeTableEntryIdPointer); + // if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) && + // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type, + // source_node, false).id == ConstCastResultIdOk) + // { + // return ir_analyze_array_to_slice(ira, source_instr, value, wanted_type); + // } + //} + + //// cast from *const [N]T to []const T + //if (is_slice(wanted_type) && + // actual_type->id == TypeTableEntryIdPointer && + // actual_type->data.pointer.is_const && + // actual_type->data.pointer.child_type->id == TypeTableEntryIdArray) + //{ + // TypeTableEntry *ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry; + // assert(ptr_type->id == TypeTableEntryIdPointer); + + // TypeTableEntry *array_type = actual_type->data.pointer.child_type; + + // if ((ptr_type->data.pointer.is_const || array_type->data.array.len == 0) && + // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, array_type->data.array.child_type, + // source_node, false).id == ConstCastResultIdOk) + // { + // return ir_analyze_array_to_slice(ira, source_instr, value, wanted_type); + // } + //} + + //// cast from [N]T to *const []const T + //if (wanted_type->id == TypeTableEntryIdPointer && + // wanted_type->data.pointer.is_const && + // is_slice(wanted_type->data.pointer.child_type) && + // actual_type->id == TypeTableEntryIdArray) + //{ + // TypeTableEntry *ptr_type = + // wanted_type->data.pointer.child_type->data.structure.fields[slice_ptr_index].type_entry; + // assert(ptr_type->id == TypeTableEntryIdPointer); + // if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) && + // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type, + // source_node, false).id == ConstCastResultIdOk) + // { + // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.pointer.child_type, value); + // if (type_is_invalid(cast1->value.type)) + // return ira->codegen->invalid_instruction; + + // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); + // if (type_is_invalid(cast2->value.type)) + // return ira->codegen->invalid_instruction; + + // return cast2; + // } + //} + + //// cast from [N]T to ?[]const T + //if (wanted_type->id == TypeTableEntryIdOptional && + // is_slice(wanted_type->data.maybe.child_type) && + // actual_type->id == TypeTableEntryIdArray) + //{ + // TypeTableEntry *ptr_type = + // wanted_type->data.maybe.child_type->data.structure.fields[slice_ptr_index].type_entry; + // assert(ptr_type->id == TypeTableEntryIdPointer); + // if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) && + // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type, + // source_node, false).id == ConstCastResultIdOk) + // { + // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.maybe.child_type, value); + // if (type_is_invalid(cast1->value.type)) + // return ira->codegen->invalid_instruction; + + // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); + // if (type_is_invalid(cast2->value.type)) + // return ira->codegen->invalid_instruction; + + // return cast2; + // } + //} + + //// *[N]T to [*]T + //if (wanted_type->id == TypeTableEntryIdPointer && + // wanted_type->data.pointer.ptr_len == PtrLenUnknown && + // actual_type->id == TypeTableEntryIdPointer && + // actual_type->data.pointer.ptr_len == PtrLenSingle && + // actual_type->data.pointer.child_type->id == TypeTableEntryIdArray && + // actual_type->data.pointer.alignment >= wanted_type->data.pointer.alignment && + // types_match_const_cast_only(ira, wanted_type->data.pointer.child_type, + // actual_type->data.pointer.child_type->data.array.child_type, source_node, + // !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk) + //{ + // return ir_resolve_ptr_of_array_to_unknown_len_ptr(ira, source_instr, value, wanted_type); + //} + + //// *[N]T to []T + //if (is_slice(wanted_type) && + // actual_type->id == TypeTableEntryIdPointer && + // actual_type->data.pointer.ptr_len == PtrLenSingle && + // actual_type->data.pointer.child_type->id == TypeTableEntryIdArray) + //{ + // TypeTableEntry *slice_ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry; + // assert(slice_ptr_type->id == TypeTableEntryIdPointer); + // if (types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type, + // actual_type->data.pointer.child_type->data.array.child_type, source_node, + // !slice_ptr_type->data.pointer.is_const).id == ConstCastResultIdOk) + // { + // return ir_resolve_ptr_of_array_to_slice(ira, source_instr, value, wanted_type); + // } + //} + + //// cast from T to ?T + //// note that the *T to ?*T case is handled via the "ConstCastOnly" mechanism + //if (wanted_type->id == TypeTableEntryIdOptional) { + // TypeTableEntry *wanted_child_type = wanted_type->data.maybe.child_type; + // if (types_match_const_cast_only(ira, wanted_child_type, actual_type, source_node, + // false).id == ConstCastResultIdOk) + // { + // return ir_analyze_maybe_wrap(ira, source_instr, value, wanted_type); + // } else if (actual_type->id == TypeTableEntryIdComptimeInt || + // actual_type->id == TypeTableEntryIdComptimeFloat) + // { + // if (ir_num_lit_fits_in_other_type(ira, value, wanted_child_type, true)) { + // return ir_analyze_maybe_wrap(ira, source_instr, value, wanted_type); + // } else { + // return ira->codegen->invalid_instruction; + // } + // } else if (wanted_child_type->id == TypeTableEntryIdPointer && + // wanted_child_type->data.pointer.is_const && + // (actual_type->id == TypeTableEntryIdPointer || is_container(actual_type))) + // { + // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_child_type, value); + // if (type_is_invalid(cast1->value.type)) + // return ira->codegen->invalid_instruction; + + // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); + // if (type_is_invalid(cast2->value.type)) + // return ira->codegen->invalid_instruction; + + // return cast2; + // } + //} + + //// cast from null literal to maybe type + //if (wanted_type->id == TypeTableEntryIdOptional && + // actual_type->id == TypeTableEntryIdNull) + //{ + // return ir_analyze_null_to_maybe(ira, source_instr, value, wanted_type); + //} + + //// cast from child type of error type to error type + //if (wanted_type->id == TypeTableEntryIdErrorUnion) { + // if (types_match_const_cast_only(ira, wanted_type->data.error_union.payload_type, actual_type, + // source_node, false).id == ConstCastResultIdOk) + // { + // return ir_analyze_err_wrap_payload(ira, source_instr, value, wanted_type); + // } else if (actual_type->id == TypeTableEntryIdComptimeInt || + // actual_type->id == TypeTableEntryIdComptimeFloat) + // { + // if (ir_num_lit_fits_in_other_type(ira, value, wanted_type->data.error_union.payload_type, true)) { + // return ir_analyze_err_wrap_payload(ira, source_instr, value, wanted_type); + // } else { + // return ira->codegen->invalid_instruction; + // } + // } + //} + + //// cast from [N]T to E![]const T + //if (wanted_type->id == TypeTableEntryIdErrorUnion && + // is_slice(wanted_type->data.error_union.payload_type) && + // actual_type->id == TypeTableEntryIdArray) + //{ + // TypeTableEntry *ptr_type = + // wanted_type->data.error_union.payload_type->data.structure.fields[slice_ptr_index].type_entry; + // assert(ptr_type->id == TypeTableEntryIdPointer); + // if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) && + // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type, + // source_node, false).id == ConstCastResultIdOk) + // { + // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.error_union.payload_type, value); + // if (type_is_invalid(cast1->value.type)) + // return ira->codegen->invalid_instruction; + + // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); + // if (type_is_invalid(cast2->value.type)) + // return ira->codegen->invalid_instruction; + + // return cast2; + // } + //} + + //// cast from error set to error union type + //if (wanted_type->id == TypeTableEntryIdErrorUnion && + // actual_type->id == TypeTableEntryIdErrorSet) + //{ + // return ir_analyze_err_wrap_code(ira, source_instr, value, wanted_type); + //} + + //// cast from T to E!?T + //if (wanted_type->id == TypeTableEntryIdErrorUnion && + // wanted_type->data.error_union.payload_type->id == TypeTableEntryIdOptional && + // actual_type->id != TypeTableEntryIdOptional) + //{ + // TypeTableEntry *wanted_child_type = wanted_type->data.error_union.payload_type->data.maybe.child_type; + // if (types_match_const_cast_only(ira, wanted_child_type, actual_type, source_node, false).id == ConstCastResultIdOk || + // actual_type->id == TypeTableEntryIdNull || + // actual_type->id == TypeTableEntryIdComptimeInt || + // actual_type->id == TypeTableEntryIdComptimeFloat) + // { + // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.error_union.payload_type, value); + // if (type_is_invalid(cast1->value.type)) + // return ira->codegen->invalid_instruction; + + // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); + // if (type_is_invalid(cast2->value.type)) + // return ira->codegen->invalid_instruction; + + // return cast2; + // } + //} + + // cast from comptime-known integer to another integer where the value fits + if (target.isCompTime() and (from_type.id == Type.Id.Int or from_type.id == Type.Id.ComptimeInt)) cast: { + const target_val = target.val.KnownValue; + const from_int = &target_val.cast(Value.Int).?.big_int; + const fits = fits: { + if (dest_type.cast(Type.ComptimeInt)) |ctint| { + break :fits true; + } + if (dest_type.cast(Type.Int)) |int| { + break :fits from_int.fitsInTwosComp(int.key.is_signed, int.key.bit_count); + } + break :cast; + }; + if (!fits) { + try ira.addCompileError( + source_instr.span, + "integer value '{}' cannot be stored in type '{}'", + from_int, + dest_type.name, + ); + return error.SemanticAnalysisFailed; + } + + const new_val = try target.copyVal(ira.irb.comp); + new_val.setType(dest_type, ira.irb.comp); + return ira.irb.buildConstValue(source_instr.scope, source_instr.span, new_val); + } + + // cast from number literal to another type + // cast from number literal to *const integer + //if (actual_type->id == TypeTableEntryIdComptimeFloat || + // actual_type->id == TypeTableEntryIdComptimeInt) + //{ + // ensure_complete_type(ira->codegen, wanted_type); + // if (type_is_invalid(wanted_type)) + // return ira->codegen->invalid_instruction; + // if (wanted_type->id == TypeTableEntryIdEnum) { + // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.enumeration.tag_int_type, value); + // if (type_is_invalid(cast1->value.type)) + // return ira->codegen->invalid_instruction; + + // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); + // if (type_is_invalid(cast2->value.type)) + // return ira->codegen->invalid_instruction; + + // return cast2; + // } else if (wanted_type->id == TypeTableEntryIdPointer && + // wanted_type->data.pointer.is_const) + // { + // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.pointer.child_type, value); + // if (type_is_invalid(cast1->value.type)) + // return ira->codegen->invalid_instruction; + + // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); + // if (type_is_invalid(cast2->value.type)) + // return ira->codegen->invalid_instruction; + + // return cast2; + // } else if (ir_num_lit_fits_in_other_type(ira, value, wanted_type, true)) { + // CastOp op; + // if ((actual_type->id == TypeTableEntryIdComptimeFloat && + // wanted_type->id == TypeTableEntryIdFloat) || + // (actual_type->id == TypeTableEntryIdComptimeInt && + // wanted_type->id == TypeTableEntryIdInt)) + // { + // op = CastOpNumLitToConcrete; + // } else if (wanted_type->id == TypeTableEntryIdInt) { + // op = CastOpFloatToInt; + // } else if (wanted_type->id == TypeTableEntryIdFloat) { + // op = CastOpIntToFloat; + // } else { + // zig_unreachable(); + // } + // return ir_resolve_cast(ira, source_instr, value, wanted_type, op, false); + // } else { + // return ira->codegen->invalid_instruction; + // } + //} + + //// cast from typed number to integer or float literal. + //// works when the number is known at compile time + //if (instr_is_comptime(value) && + // ((actual_type->id == TypeTableEntryIdInt && wanted_type->id == TypeTableEntryIdComptimeInt) || + // (actual_type->id == TypeTableEntryIdFloat && wanted_type->id == TypeTableEntryIdComptimeFloat))) + //{ + // return ir_analyze_number_to_literal(ira, source_instr, value, wanted_type); + //} + + //// cast from union to the enum type of the union + //if (actual_type->id == TypeTableEntryIdUnion && wanted_type->id == TypeTableEntryIdEnum) { + // type_ensure_zero_bits_known(ira->codegen, actual_type); + // if (type_is_invalid(actual_type)) + // return ira->codegen->invalid_instruction; + + // if (actual_type->data.unionation.tag_type == wanted_type) { + // return ir_analyze_union_to_tag(ira, source_instr, value, wanted_type); + // } + //} + + //// enum to union which has the enum as the tag type + //if (wanted_type->id == TypeTableEntryIdUnion && actual_type->id == TypeTableEntryIdEnum && + // (wanted_type->data.unionation.decl_node->data.container_decl.auto_enum || + // wanted_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr)) + //{ + // type_ensure_zero_bits_known(ira->codegen, wanted_type); + // if (wanted_type->data.unionation.tag_type == actual_type) { + // return ir_analyze_enum_to_union(ira, source_instr, value, wanted_type); + // } + //} + + //// enum to &const union which has the enum as the tag type + //if (actual_type->id == TypeTableEntryIdEnum && wanted_type->id == TypeTableEntryIdPointer) { + // TypeTableEntry *union_type = wanted_type->data.pointer.child_type; + // if (union_type->data.unionation.decl_node->data.container_decl.auto_enum || + // union_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr) + // { + // type_ensure_zero_bits_known(ira->codegen, union_type); + // if (union_type->data.unionation.tag_type == actual_type) { + // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, union_type, value); + // if (type_is_invalid(cast1->value.type)) + // return ira->codegen->invalid_instruction; + + // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); + // if (type_is_invalid(cast2->value.type)) + // return ira->codegen->invalid_instruction; + + // return cast2; + // } + // } + //} + + //// cast from *T to *[1]T + //if (wanted_type->id == TypeTableEntryIdPointer && wanted_type->data.pointer.ptr_len == PtrLenSingle && + // actual_type->id == TypeTableEntryIdPointer && actual_type->data.pointer.ptr_len == PtrLenSingle) + //{ + // TypeTableEntry *array_type = wanted_type->data.pointer.child_type; + // if (array_type->id == TypeTableEntryIdArray && array_type->data.array.len == 1 && + // types_match_const_cast_only(ira, array_type->data.array.child_type, + // actual_type->data.pointer.child_type, source_node, + // !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk) + // { + // if (wanted_type->data.pointer.alignment > actual_type->data.pointer.alignment) { + // ErrorMsg *msg = ir_add_error(ira, source_instr, buf_sprintf("cast increases pointer alignment")); + // add_error_note(ira->codegen, msg, value->source_node, + // buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&actual_type->name), + // actual_type->data.pointer.alignment)); + // add_error_note(ira->codegen, msg, source_instr->source_node, + // buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&wanted_type->name), + // wanted_type->data.pointer.alignment)); + // return ira->codegen->invalid_instruction; + // } + // return ir_analyze_ptr_to_array(ira, source_instr, value, wanted_type); + // } + //} + + //// cast from T to *T where T is zero bits + //if (wanted_type->id == TypeTableEntryIdPointer && wanted_type->data.pointer.ptr_len == PtrLenSingle && + // types_match_const_cast_only(ira, wanted_type->data.pointer.child_type, + // actual_type, source_node, !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk) + //{ + // type_ensure_zero_bits_known(ira->codegen, actual_type); + // if (type_is_invalid(actual_type)) { + // return ira->codegen->invalid_instruction; + // } + // if (!type_has_bits(actual_type)) { + // return ir_get_ref(ira, source_instr, value, false, false); + // } + //} + + //// cast from undefined to anything + //if (actual_type->id == TypeTableEntryIdUndefined) { + // return ir_analyze_undefined_to_anything(ira, source_instr, value, wanted_type); + //} + + //// cast from something to const pointer of it + //if (!type_requires_comptime(actual_type)) { + // TypeTableEntry *const_ptr_actual = get_pointer_to_type(ira->codegen, actual_type, true); + // if (types_match_const_cast_only(ira, wanted_type, const_ptr_actual, source_node, false).id == ConstCastResultIdOk) { + // return ir_analyze_cast_ref(ira, source_instr, value, wanted_type); + // } + //} + + try ira.addCompileError( + source_instr.span, + "expected type '{}', found '{}'", + dest_type.name, + from_type.name, + ); + //ErrorMsg *parent_msg = ir_add_error_node(ira, source_instr->source_node, + // buf_sprintf("expected type '%s', found '%s'", + // buf_ptr(&wanted_type->name), + // buf_ptr(&actual_type->name))); + //report_recursive_error(ira, source_instr->source_node, &const_cast_result, parent_msg); + return error.SemanticAnalysisFailed; + } + + fn getCompTimeValOrNullUndefOk(self: *Analyze, target: *Inst) ?*Value { + @panic("TODO"); } fn getCompTimeRef( @@ -991,9 +2369,8 @@ const Analyze = struct { ptr_mut: Value.Ptr.Mut, mut: Type.Pointer.Mut, volatility: Type.Pointer.Vol, - ptr_align: u32, - ) Analyze.Error!*Instruction { - @panic("TODO getCompTimeRef"); + ) Analyze.Error!*Inst { + return error.Unimplemented; } }; @@ -1001,43 +2378,32 @@ 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); + var irb = try Builder.init(comp, scope.findRoot(), scope); errdefer irb.abort(); const entry_block = try irb.createBasicBlock(scope, c"Entry"); - entry_block.ref(); // Entry block gets a reference because we enter it to begin. + entry_block.ref(&irb); // Entry block gets a reference because we enter it to begin. try irb.setCursorAtEndAndAppendBlock(entry_block); - const result = try irb.genNode(body_node, scope, LVal.None); + const result = try await (async irb.genNode(body_node, scope, LVal.None) catch unreachable); 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 }, - ); + // no need for save_err_ret_addr because this cannot return error + _ = try irb.genAsyncReturn(scope, Span.token(body_node.lastToken()), result, true); } 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); +pub async fn analyze(comp: *Compilation, old_code: *Code, expected_type: ?*Type) !*Code { + const old_entry_bb = old_code.basic_block_list.at(0); + const root_scope = old_entry_bb.scope.findRoot(); + + var ira = try Analyze.init(comp, root_scope, 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(); + new_entry_bb.ref(&ira.irb); ira.irb.current_basic_block = new_entry_bb; @@ -1051,7 +2417,8 @@ pub async fn analyze(comp: *Compilation, parsed_file: *ParsedFile, old_code: *Co continue; } - const return_inst = try old_instruction.analyze(&ira); + const return_inst = try await (async old_instruction.analyze(&ira) catch unreachable); + assert(return_inst.val != IrVal.Unknown); // at least the type should be known at this point return_inst.linkToParent(old_instruction); // Note: if we ever modify the above to handle error.CompileError by continuing analysis, // then here we want to check if ira.isCompTime() and return early if true diff --git a/src-self-hosted/libc_installation.zig b/src-self-hosted/libc_installation.zig new file mode 100644 index 0000000000..3938c0d90c --- /dev/null +++ b/src-self-hosted/libc_installation.zig @@ -0,0 +1,462 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const event = std.event; +const Target = @import("target.zig").Target; +const c = @import("c.zig"); + +/// See the render function implementation for documentation of the fields. +pub const LibCInstallation = struct { + include_dir: []const u8, + lib_dir: ?[]const u8, + static_lib_dir: ?[]const u8, + msvc_lib_dir: ?[]const u8, + kernel32_lib_dir: ?[]const u8, + dynamic_linker_path: ?[]const u8, + + pub const FindError = error{ + OutOfMemory, + FileSystem, + UnableToSpawnCCompiler, + CCompilerExitCode, + CCompilerCrashed, + CCompilerCannotFindHeaders, + LibCRuntimeNotFound, + LibCStdLibHeaderNotFound, + LibCKernel32LibNotFound, + UnsupportedArchitecture, + }; + + pub fn parse( + self: *LibCInstallation, + allocator: *std.mem.Allocator, + libc_file: []const u8, + stderr: *std.io.OutStream(std.io.FileOutStream.Error), + ) !void { + self.initEmpty(); + + const keys = []const []const u8{ + "include_dir", + "lib_dir", + "static_lib_dir", + "msvc_lib_dir", + "kernel32_lib_dir", + "dynamic_linker_path", + }; + const FoundKey = struct { + found: bool, + allocated: ?[]u8, + }; + var found_keys = [1]FoundKey{FoundKey{ .found = false, .allocated = null }} ** keys.len; + errdefer { + self.initEmpty(); + for (found_keys) |found_key| { + if (found_key.allocated) |s| allocator.free(s); + } + } + + const contents = try std.io.readFileAlloc(allocator, libc_file); + defer allocator.free(contents); + + var it = std.mem.split(contents, "\n"); + while (it.next()) |line| { + if (line.len == 0 or line[0] == '#') continue; + var line_it = std.mem.split(line, "="); + const name = line_it.next() orelse { + try stderr.print("missing equal sign after field name\n"); + return error.ParseError; + }; + const value = line_it.rest(); + inline for (keys) |key, i| { + if (std.mem.eql(u8, name, key)) { + found_keys[i].found = true; + switch (@typeInfo(@typeOf(@field(self, key)))) { + builtin.TypeId.Optional => { + if (value.len == 0) { + @field(self, key) = null; + } else { + found_keys[i].allocated = try std.mem.dupe(allocator, u8, value); + @field(self, key) = found_keys[i].allocated; + } + }, + else => { + if (value.len == 0) { + try stderr.print("field cannot be empty: {}\n", key); + return error.ParseError; + } + const dupe = try std.mem.dupe(allocator, u8, value); + found_keys[i].allocated = dupe; + @field(self, key) = dupe; + }, + } + break; + } + } + } + for (found_keys) |found_key, i| { + if (!found_key.found) { + try stderr.print("missing field: {}\n", keys[i]); + return error.ParseError; + } + } + } + + pub fn render(self: *const LibCInstallation, out: *std.io.OutStream(std.io.FileOutStream.Error)) !void { + @setEvalBranchQuota(4000); + try out.print( + \\# The directory that contains `stdlib.h`. + \\# On Linux, can be found with: `cc -E -Wp,-v -xc /dev/null` + \\include_dir={} + \\ + \\# The directory that contains `crt1.o`. + \\# On Linux, can be found with `cc -print-file-name=crt1.o`. + \\# Not needed when targeting MacOS. + \\lib_dir={} + \\ + \\# The directory that contains `crtbegin.o`. + \\# On Linux, can be found with `cc -print-file-name=crtbegin.o`. + \\# Not needed when targeting MacOS or Windows. + \\static_lib_dir={} + \\ + \\# The directory that contains `vcruntime.lib`. + \\# Only needed when targeting Windows. + \\msvc_lib_dir={} + \\ + \\# The directory that contains `kernel32.lib`. + \\# Only needed when targeting Windows. + \\kernel32_lib_dir={} + \\ + \\# The full path to the dynamic linker, on the target system. + \\# Only needed when targeting Linux. + \\dynamic_linker_path={} + \\ + , + self.include_dir, + self.lib_dir orelse "", + self.static_lib_dir orelse "", + self.msvc_lib_dir orelse "", + self.kernel32_lib_dir orelse "", + self.dynamic_linker_path orelse Target(Target.Native).getDynamicLinkerPath(), + ); + } + + /// Finds the default, native libc. + pub async fn findNative(self: *LibCInstallation, loop: *event.Loop) !void { + self.initEmpty(); + var group = event.Group(FindError!void).init(loop); + errdefer group.cancelAll(); + var windows_sdk: ?*c.ZigWindowsSDK = null; + errdefer if (windows_sdk) |sdk| c.zig_free_windows_sdk(@ptrCast(?[*]c.ZigWindowsSDK, sdk)); + + switch (builtin.os) { + builtin.Os.windows => { + var sdk: *c.ZigWindowsSDK = undefined; + switch (c.zig_find_windows_sdk(@ptrCast(?[*]?[*]c.ZigWindowsSDK, &sdk))) { + c.ZigFindWindowsSdkError.None => { + windows_sdk = sdk; + + if (sdk.msvc_lib_dir_ptr) |ptr| { + self.msvc_lib_dir = try std.mem.dupe(loop.allocator, u8, ptr[0..sdk.msvc_lib_dir_len]); + } + try group.call(findNativeKernel32LibDir, self, loop, sdk); + try group.call(findNativeIncludeDirWindows, self, loop, sdk); + try group.call(findNativeLibDirWindows, self, loop, sdk); + }, + c.ZigFindWindowsSdkError.OutOfMemory => return error.OutOfMemory, + c.ZigFindWindowsSdkError.NotFound => return error.NotFound, + c.ZigFindWindowsSdkError.PathTooLong => return error.NotFound, + } + }, + builtin.Os.linux => { + try group.call(findNativeIncludeDirLinux, self, loop); + try group.call(findNativeLibDirLinux, self, loop); + try group.call(findNativeStaticLibDir, self, loop); + try group.call(findNativeDynamicLinker, self, loop); + }, + builtin.Os.macosx => { + self.include_dir = try std.mem.dupe(loop.allocator, u8, "/usr/include"); + }, + else => @compileError("unimplemented: find libc for this OS"), + } + return await (async group.wait() catch unreachable); + } + + async fn findNativeIncludeDirLinux(self: *LibCInstallation, loop: *event.Loop) !void { + const cc_exe = std.os.getEnvPosix("CC") orelse "cc"; + const argv = []const []const u8{ + cc_exe, + "-E", + "-Wp,-v", + "-xc", + "/dev/null", + }; + // TODO make this use event loop + const errorable_result = std.os.ChildProcess.exec(loop.allocator, argv, null, null, 1024 * 1024); + const exec_result = if (std.debug.runtime_safety) blk: { + break :blk errorable_result catch unreachable; + } else blk: { + break :blk errorable_result catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => return error.UnableToSpawnCCompiler, + }; + }; + defer { + loop.allocator.free(exec_result.stdout); + loop.allocator.free(exec_result.stderr); + } + + switch (exec_result.term) { + std.os.ChildProcess.Term.Exited => |code| { + if (code != 0) return error.CCompilerExitCode; + }, + else => { + return error.CCompilerCrashed; + }, + } + + var it = std.mem.split(exec_result.stderr, "\n\r"); + var search_paths = std.ArrayList([]const u8).init(loop.allocator); + defer search_paths.deinit(); + while (it.next()) |line| { + if (line.len != 0 and line[0] == ' ') { + try search_paths.append(line); + } + } + if (search_paths.len == 0) { + return error.CCompilerCannotFindHeaders; + } + + // search in reverse order + var path_i: usize = 0; + while (path_i < search_paths.len) : (path_i += 1) { + const search_path_untrimmed = search_paths.at(search_paths.len - path_i - 1); + const search_path = std.mem.trimLeft(u8, search_path_untrimmed, " "); + const stdlib_path = try std.os.path.join(loop.allocator, search_path, "stdlib.h"); + defer loop.allocator.free(stdlib_path); + + if (try fileExists(loop.allocator, stdlib_path)) { + self.include_dir = try std.mem.dupe(loop.allocator, u8, search_path); + return; + } + } + + return error.LibCStdLibHeaderNotFound; + } + + async fn findNativeIncludeDirWindows(self: *LibCInstallation, loop: *event.Loop, sdk: *c.ZigWindowsSDK) !void { + var search_buf: [2]Search = undefined; + const searches = fillSearch(&search_buf, sdk); + + var result_buf = try std.Buffer.initSize(loop.allocator, 0); + defer result_buf.deinit(); + + for (searches) |search| { + result_buf.shrink(0); + const stream = &std.io.BufferOutStream.init(&result_buf).stream; + try stream.print("{}\\Include\\{}\\ucrt", search.path, search.version); + + const stdlib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "stdlib.h"); + defer loop.allocator.free(stdlib_path); + + if (try fileExists(loop.allocator, stdlib_path)) { + self.include_dir = result_buf.toOwnedSlice(); + return; + } + } + + return error.LibCStdLibHeaderNotFound; + } + + async fn findNativeLibDirWindows(self: *LibCInstallation, loop: *event.Loop, sdk: *c.ZigWindowsSDK) FindError!void { + var search_buf: [2]Search = undefined; + const searches = fillSearch(&search_buf, sdk); + + var result_buf = try std.Buffer.initSize(loop.allocator, 0); + defer result_buf.deinit(); + + for (searches) |search| { + result_buf.shrink(0); + const stream = &std.io.BufferOutStream.init(&result_buf).stream; + try stream.print("{}\\Lib\\{}\\ucrt\\", search.path, search.version); + switch (builtin.arch) { + builtin.Arch.i386 => try stream.write("x86"), + builtin.Arch.x86_64 => try stream.write("x64"), + builtin.Arch.aarch64 => try stream.write("arm"), + else => return error.UnsupportedArchitecture, + } + const ucrt_lib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "ucrt.lib"); + defer loop.allocator.free(ucrt_lib_path); + if (try fileExists(loop.allocator, ucrt_lib_path)) { + self.lib_dir = result_buf.toOwnedSlice(); + return; + } + } + return error.LibCRuntimeNotFound; + } + + async fn findNativeLibDirLinux(self: *LibCInstallation, loop: *event.Loop) FindError!void { + self.lib_dir = try await (async ccPrintFileName(loop, "crt1.o", true) catch unreachable); + } + + async fn findNativeStaticLibDir(self: *LibCInstallation, loop: *event.Loop) FindError!void { + self.static_lib_dir = try await (async ccPrintFileName(loop, "crtbegin.o", true) catch unreachable); + } + + async fn findNativeDynamicLinker(self: *LibCInstallation, loop: *event.Loop) FindError!void { + var dyn_tests = []DynTest{ + DynTest{ + .name = "ld-linux-x86-64.so.2", + .result = null, + }, + DynTest{ + .name = "ld-musl-x86_64.so.1", + .result = null, + }, + }; + var group = event.Group(FindError!void).init(loop); + errdefer group.cancelAll(); + for (dyn_tests) |*dyn_test| { + try group.call(testNativeDynamicLinker, self, loop, dyn_test); + } + try await (async group.wait() catch unreachable); + for (dyn_tests) |*dyn_test| { + if (dyn_test.result) |result| { + self.dynamic_linker_path = result; + return; + } + } + } + + const DynTest = struct { + name: []const u8, + result: ?[]const u8, + }; + + async fn testNativeDynamicLinker(self: *LibCInstallation, loop: *event.Loop, dyn_test: *DynTest) FindError!void { + if (await (async ccPrintFileName(loop, dyn_test.name, false) catch unreachable)) |result| { + dyn_test.result = result; + return; + } else |err| switch (err) { + error.LibCRuntimeNotFound => return, + else => return err, + } + } + + + async fn findNativeKernel32LibDir(self: *LibCInstallation, loop: *event.Loop, sdk: *c.ZigWindowsSDK) FindError!void { + var search_buf: [2]Search = undefined; + const searches = fillSearch(&search_buf, sdk); + + var result_buf = try std.Buffer.initSize(loop.allocator, 0); + defer result_buf.deinit(); + + for (searches) |search| { + result_buf.shrink(0); + const stream = &std.io.BufferOutStream.init(&result_buf).stream; + try stream.print("{}\\Lib\\{}\\um\\", search.path, search.version); + switch (builtin.arch) { + builtin.Arch.i386 => try stream.write("x86\\"), + builtin.Arch.x86_64 => try stream.write("x64\\"), + builtin.Arch.aarch64 => try stream.write("arm\\"), + else => return error.UnsupportedArchitecture, + } + const kernel32_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "kernel32.lib"); + defer loop.allocator.free(kernel32_path); + if (try fileExists(loop.allocator, kernel32_path)) { + self.kernel32_lib_dir = result_buf.toOwnedSlice(); + return; + } + } + return error.LibCKernel32LibNotFound; + } + + fn initEmpty(self: *LibCInstallation) void { + self.* = LibCInstallation{ + .include_dir = ([*]const u8)(undefined)[0..0], + .lib_dir = null, + .static_lib_dir = null, + .msvc_lib_dir = null, + .kernel32_lib_dir = null, + .dynamic_linker_path = null, + }; + } +}; + +/// caller owns returned memory +async fn ccPrintFileName(loop: *event.Loop, o_file: []const u8, want_dirname: bool) ![]u8 { + const cc_exe = std.os.getEnvPosix("CC") orelse "cc"; + const arg1 = try std.fmt.allocPrint(loop.allocator, "-print-file-name={}", o_file); + defer loop.allocator.free(arg1); + const argv = []const []const u8{ cc_exe, arg1 }; + + // TODO This simulates evented I/O for the child process exec + await (async loop.yield() catch unreachable); + const errorable_result = std.os.ChildProcess.exec(loop.allocator, argv, null, null, 1024 * 1024); + const exec_result = if (std.debug.runtime_safety) blk: { + break :blk errorable_result catch unreachable; + } else blk: { + break :blk errorable_result catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => return error.UnableToSpawnCCompiler, + }; + }; + defer { + loop.allocator.free(exec_result.stdout); + loop.allocator.free(exec_result.stderr); + } + switch (exec_result.term) { + std.os.ChildProcess.Term.Exited => |code| { + if (code != 0) return error.CCompilerExitCode; + }, + else => { + return error.CCompilerCrashed; + }, + } + var it = std.mem.split(exec_result.stdout, "\n\r"); + const line = it.next() orelse return error.LibCRuntimeNotFound; + const dirname = std.os.path.dirname(line) orelse return error.LibCRuntimeNotFound; + + if (want_dirname) { + return std.mem.dupe(loop.allocator, u8, dirname); + } else { + return std.mem.dupe(loop.allocator, u8, line); + } +} + +const Search = struct { + path: []const u8, + version: []const u8, +}; + +fn fillSearch(search_buf: *[2]Search, sdk: *c.ZigWindowsSDK) []Search { + var search_end: usize = 0; + if (sdk.path10_ptr) |path10_ptr| { + if (sdk.version10_ptr) |ver10_ptr| { + search_buf[search_end] = Search{ + .path = path10_ptr[0..sdk.path10_len], + .version = ver10_ptr[0..sdk.version10_len], + }; + search_end += 1; + } + } + if (sdk.path81_ptr) |path81_ptr| { + if (sdk.version81_ptr) |ver81_ptr| { + search_buf[search_end] = Search{ + .path = path81_ptr[0..sdk.path81_len], + .version = ver81_ptr[0..sdk.version81_len], + }; + search_end += 1; + } + } + return search_buf[0..search_end]; +} + + +fn fileExists(allocator: *std.mem.Allocator, path: []const u8) !bool { + if (std.os.File.access(allocator, path)) |_| { + return true; + } else |err| switch (err) { + error.NotFound, error.PermissionDenied => return false, + error.OutOfMemory => return error.OutOfMemory, + else => return error.FileSystem, + } +} diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig new file mode 100644 index 0000000000..b9eefa9d4f --- /dev/null +++ b/src-self-hosted/link.zig @@ -0,0 +1,724 @@ +const std = @import("std"); +const mem = std.mem; +const c = @import("c.zig"); +const builtin = @import("builtin"); +const ObjectFormat = builtin.ObjectFormat; +const Compilation = @import("compilation.zig").Compilation; +const Target = @import("target.zig").Target; +const LibCInstallation = @import("libc_installation.zig").LibCInstallation; +const assert = std.debug.assert; + +const Context = struct { + comp: *Compilation, + arena: std.heap.ArenaAllocator, + args: std.ArrayList([*]const u8), + link_in_crt: bool, + + link_err: error{OutOfMemory}!void, + link_msg: std.Buffer, + + libc: *LibCInstallation, + out_file_path: std.Buffer, +}; + +pub async fn link(comp: *Compilation) !void { + var ctx = Context{ + .comp = comp, + .arena = std.heap.ArenaAllocator.init(comp.gpa()), + .args = undefined, + .link_in_crt = comp.haveLibC() and comp.kind == Compilation.Kind.Exe, + .link_err = {}, + .link_msg = undefined, + .libc = undefined, + .out_file_path = undefined, + }; + defer ctx.arena.deinit(); + ctx.args = std.ArrayList([*]const u8).init(&ctx.arena.allocator); + ctx.link_msg = std.Buffer.initNull(&ctx.arena.allocator); + + if (comp.link_out_file) |out_file| { + ctx.out_file_path = try std.Buffer.init(&ctx.arena.allocator, out_file); + } else { + ctx.out_file_path = try std.Buffer.init(&ctx.arena.allocator, comp.name.toSliceConst()); + switch (comp.kind) { + Compilation.Kind.Exe => { + try ctx.out_file_path.append(comp.target.exeFileExt()); + }, + Compilation.Kind.Lib => { + try ctx.out_file_path.append(comp.target.libFileExt(comp.is_static)); + }, + Compilation.Kind.Obj => { + try ctx.out_file_path.append(comp.target.objFileExt()); + }, + } + } + + // even though we're calling LLD as a library it thinks the first + // argument is its own exe name + try ctx.args.append(c"lld"); + + if (comp.haveLibC()) { + ctx.libc = ctx.comp.override_libc orelse blk: { + switch (comp.target) { + Target.Native => { + break :blk (await (async comp.event_loop_local.getNativeLibC() catch unreachable)) catch return error.LibCRequiredButNotProvidedOrFound; + }, + else => return error.LibCRequiredButNotProvidedOrFound, + } + }; + } + + try constructLinkerArgs(&ctx); + + if (comp.verbose_link) { + for (ctx.args.toSliceConst()) |arg, i| { + const space = if (i == 0) "" else " "; + std.debug.warn("{}{s}", space, arg); + } + std.debug.warn("\n"); + } + + const extern_ofmt = toExternObjectFormatType(comp.target.getObjectFormat()); + const args_slice = ctx.args.toSlice(); + // Not evented I/O. LLD does its own multithreading internally. + if (!ZigLLDLink(extern_ofmt, args_slice.ptr, args_slice.len, linkDiagCallback, @ptrCast(*c_void, &ctx))) { + if (!ctx.link_msg.isNull()) { + // TODO capture these messages and pass them through the system, reporting them through the + // event system instead of printing them directly here. + // perhaps try to parse and understand them. + std.debug.warn("{}\n", ctx.link_msg.toSliceConst()); + } + return error.LinkFailed; + } +} + +extern fn ZigLLDLink( + oformat: c.ZigLLVM_ObjectFormatType, + args: [*]const [*]const u8, + arg_count: usize, + append_diagnostic: extern fn (*c_void, [*]const u8, usize) void, + context: *c_void, +) bool; + +extern fn linkDiagCallback(context: *c_void, ptr: [*]const u8, len: usize) void { + const ctx = @ptrCast(*Context, @alignCast(@alignOf(Context), context)); + ctx.link_err = linkDiagCallbackErrorable(ctx, ptr[0..len]); +} + +fn linkDiagCallbackErrorable(ctx: *Context, msg: []const u8) !void { + if (ctx.link_msg.isNull()) { + try ctx.link_msg.resize(0); + } + try ctx.link_msg.append(msg); +} + +fn toExternObjectFormatType(ofmt: ObjectFormat) c.ZigLLVM_ObjectFormatType { + return switch (ofmt) { + ObjectFormat.unknown => c.ZigLLVM_UnknownObjectFormat, + ObjectFormat.coff => c.ZigLLVM_COFF, + ObjectFormat.elf => c.ZigLLVM_ELF, + ObjectFormat.macho => c.ZigLLVM_MachO, + ObjectFormat.wasm => c.ZigLLVM_Wasm, + }; +} + +fn constructLinkerArgs(ctx: *Context) !void { + switch (ctx.comp.target.getObjectFormat()) { + ObjectFormat.unknown => unreachable, + ObjectFormat.coff => return constructLinkerArgsCoff(ctx), + ObjectFormat.elf => return constructLinkerArgsElf(ctx), + ObjectFormat.macho => return constructLinkerArgsMachO(ctx), + ObjectFormat.wasm => return constructLinkerArgsWasm(ctx), + } +} + +fn constructLinkerArgsElf(ctx: *Context) !void { + // TODO commented out code in this function + //if (g->linker_script) { + // lj->args.append("-T"); + // lj->args.append(g->linker_script); + //} + + //if (g->no_rosegment_workaround) { + // lj->args.append("--no-rosegment"); + //} + try ctx.args.append(c"--gc-sections"); + + //lj->args.append("-m"); + //lj->args.append(getLDMOption(&g->zig_target)); + + //bool is_lib = g->out_type == OutTypeLib; + //bool shared = !g->is_static && is_lib; + //Buf *soname = nullptr; + if (ctx.comp.is_static) { + if (ctx.comp.target.isArmOrThumb()) { + try ctx.args.append(c"-Bstatic"); + } else { + try ctx.args.append(c"-static"); + } + } + //} else if (shared) { + // lj->args.append("-shared"); + + // if (buf_len(&lj->out_file) == 0) { + // buf_appendf(&lj->out_file, "lib%s.so.%" ZIG_PRI_usize ".%" ZIG_PRI_usize ".%" ZIG_PRI_usize "", + // buf_ptr(g->root_out_name), g->version_major, g->version_minor, g->version_patch); + // } + // soname = buf_sprintf("lib%s.so.%" ZIG_PRI_usize "", buf_ptr(g->root_out_name), g->version_major); + //} + + try ctx.args.append(c"-o"); + try ctx.args.append(ctx.out_file_path.ptr()); + + if (ctx.link_in_crt) { + const crt1o = if (ctx.comp.is_static) "crt1.o" else "Scrt1.o"; + const crtbegino = if (ctx.comp.is_static) "crtbeginT.o" else "crtbegin.o"; + try addPathJoin(ctx, ctx.libc.lib_dir.?, crt1o); + try addPathJoin(ctx, ctx.libc.lib_dir.?, "crti.o"); + try addPathJoin(ctx, ctx.libc.static_lib_dir.?, crtbegino); + } + + //for (size_t i = 0; i < g->rpath_list.length; i += 1) { + // Buf *rpath = g->rpath_list.at(i); + // add_rpath(lj, rpath); + //} + //if (g->each_lib_rpath) { + // for (size_t i = 0; i < g->lib_dirs.length; i += 1) { + // const char *lib_dir = g->lib_dirs.at(i); + // for (size_t i = 0; i < g->link_libs_list.length; i += 1) { + // LinkLib *link_lib = g->link_libs_list.at(i); + // if (buf_eql_str(link_lib->name, "c")) { + // continue; + // } + // bool does_exist; + // Buf *test_path = buf_sprintf("%s/lib%s.so", lib_dir, buf_ptr(link_lib->name)); + // if (os_file_exists(test_path, &does_exist) != ErrorNone) { + // zig_panic("link: unable to check if file exists: %s", buf_ptr(test_path)); + // } + // if (does_exist) { + // add_rpath(lj, buf_create_from_str(lib_dir)); + // break; + // } + // } + // } + //} + + //for (size_t i = 0; i < g->lib_dirs.length; i += 1) { + // const char *lib_dir = g->lib_dirs.at(i); + // lj->args.append("-L"); + // lj->args.append(lib_dir); + //} + + if (ctx.comp.haveLibC()) { + try ctx.args.append(c"-L"); + try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, ctx.libc.lib_dir.?)).ptr); + + try ctx.args.append(c"-L"); + try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, ctx.libc.static_lib_dir.?)).ptr); + + if (!ctx.comp.is_static) { + const dl = blk: { + if (ctx.libc.dynamic_linker_path) |dl| break :blk dl; + if (ctx.comp.target.getDynamicLinkerPath()) |dl| break :blk dl; + return error.LibCMissingDynamicLinker; + }; + try ctx.args.append(c"-dynamic-linker"); + try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, dl)).ptr); + } + } + + //if (shared) { + // lj->args.append("-soname"); + // lj->args.append(buf_ptr(soname)); + //} + + // .o files + for (ctx.comp.link_objects) |link_object| { + const link_obj_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, link_object); + try ctx.args.append(link_obj_with_null.ptr); + } + try addFnObjects(ctx); + + //if (g->out_type == OutTypeExe || g->out_type == OutTypeLib) { + // if (g->libc_link_lib == nullptr) { + // Buf *builtin_o_path = build_o(g, "builtin"); + // lj->args.append(buf_ptr(builtin_o_path)); + // } + + // // sometimes libgcc is missing stuff, so we still build compiler_rt and rely on weak linkage + // Buf *compiler_rt_o_path = build_compiler_rt(g); + // lj->args.append(buf_ptr(compiler_rt_o_path)); + //} + + //for (size_t i = 0; i < g->link_libs_list.length; i += 1) { + // LinkLib *link_lib = g->link_libs_list.at(i); + // if (buf_eql_str(link_lib->name, "c")) { + // continue; + // } + // Buf *arg; + // if (buf_starts_with_str(link_lib->name, "/") || buf_ends_with_str(link_lib->name, ".a") || + // buf_ends_with_str(link_lib->name, ".so")) + // { + // arg = link_lib->name; + // } else { + // arg = buf_sprintf("-l%s", buf_ptr(link_lib->name)); + // } + // lj->args.append(buf_ptr(arg)); + //} + + // libc dep + if (ctx.comp.haveLibC()) { + if (ctx.comp.is_static) { + try ctx.args.append(c"--start-group"); + try ctx.args.append(c"-lgcc"); + try ctx.args.append(c"-lgcc_eh"); + try ctx.args.append(c"-lc"); + try ctx.args.append(c"-lm"); + try ctx.args.append(c"--end-group"); + } else { + try ctx.args.append(c"-lgcc"); + try ctx.args.append(c"--as-needed"); + try ctx.args.append(c"-lgcc_s"); + try ctx.args.append(c"--no-as-needed"); + try ctx.args.append(c"-lc"); + try ctx.args.append(c"-lm"); + try ctx.args.append(c"-lgcc"); + try ctx.args.append(c"--as-needed"); + try ctx.args.append(c"-lgcc_s"); + try ctx.args.append(c"--no-as-needed"); + } + } + + // crt end + if (ctx.link_in_crt) { + try addPathJoin(ctx, ctx.libc.static_lib_dir.?, "crtend.o"); + try addPathJoin(ctx, ctx.libc.lib_dir.?, "crtn.o"); + } + + if (ctx.comp.target != Target.Native) { + try ctx.args.append(c"--allow-shlib-undefined"); + } + + if (ctx.comp.target.getOs() == builtin.Os.zen) { + try ctx.args.append(c"-e"); + try ctx.args.append(c"_start"); + + try ctx.args.append(c"--image-base=0x10000000"); + } +} + +fn addPathJoin(ctx: *Context, dirname: []const u8, basename: []const u8) !void { + const full_path = try std.os.path.join(&ctx.arena.allocator, dirname, basename); + const full_path_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, full_path); + try ctx.args.append(full_path_with_null.ptr); +} + +fn constructLinkerArgsCoff(ctx: *Context) !void { + try ctx.args.append(c"-NOLOGO"); + + if (!ctx.comp.strip) { + try ctx.args.append(c"-DEBUG"); + } + + switch (ctx.comp.target.getArch()) { + builtin.Arch.i386 => try ctx.args.append(c"-MACHINE:X86"), + builtin.Arch.x86_64 => try ctx.args.append(c"-MACHINE:X64"), + builtin.Arch.aarch64 => try ctx.args.append(c"-MACHINE:ARM"), + else => return error.UnsupportedLinkArchitecture, + } + + if (ctx.comp.windows_subsystem_windows) { + try ctx.args.append(c"/SUBSYSTEM:windows"); + } else if (ctx.comp.windows_subsystem_console) { + try ctx.args.append(c"/SUBSYSTEM:console"); + } + + const is_library = ctx.comp.kind == Compilation.Kind.Lib; + + const out_arg = try std.fmt.allocPrint(&ctx.arena.allocator, "-OUT:{}\x00", ctx.out_file_path.toSliceConst()); + try ctx.args.append(out_arg.ptr); + + if (ctx.comp.haveLibC()) { + try ctx.args.append((try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", ctx.libc.msvc_lib_dir.?)).ptr); + try ctx.args.append((try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", ctx.libc.kernel32_lib_dir.?)).ptr); + try ctx.args.append((try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", ctx.libc.lib_dir.?)).ptr); + } + + if (ctx.link_in_crt) { + const lib_str = if (ctx.comp.is_static) "lib" else ""; + const d_str = if (ctx.comp.build_mode == builtin.Mode.Debug) "d" else ""; + + if (ctx.comp.is_static) { + const cmt_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "libcmt{}.lib\x00", d_str); + try ctx.args.append(cmt_lib_name.ptr); + } else { + const msvcrt_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "msvcrt{}.lib\x00", d_str); + try ctx.args.append(msvcrt_lib_name.ptr); + } + + const vcruntime_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "{}vcruntime{}.lib\x00", lib_str, d_str); + try ctx.args.append(vcruntime_lib_name.ptr); + + const crt_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "{}ucrt{}.lib\x00", lib_str, d_str); + try ctx.args.append(crt_lib_name.ptr); + + // Visual C++ 2015 Conformance Changes + // https://msdn.microsoft.com/en-us/library/bb531344.aspx + try ctx.args.append(c"legacy_stdio_definitions.lib"); + + // msvcrt depends on kernel32 + try ctx.args.append(c"kernel32.lib"); + } else { + try ctx.args.append(c"-NODEFAULTLIB"); + if (!is_library) { + try ctx.args.append(c"-ENTRY:WinMainCRTStartup"); + // TODO + //if (g->have_winmain) { + // lj->args.append("-ENTRY:WinMain"); + //} else { + // lj->args.append("-ENTRY:WinMainCRTStartup"); + //} + } + } + + if (is_library and !ctx.comp.is_static) { + try ctx.args.append(c"-DLL"); + } + + //for (size_t i = 0; i < g->lib_dirs.length; i += 1) { + // const char *lib_dir = g->lib_dirs.at(i); + // lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", lib_dir))); + //} + + for (ctx.comp.link_objects) |link_object| { + const link_obj_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, link_object); + try ctx.args.append(link_obj_with_null.ptr); + } + try addFnObjects(ctx); + + switch (ctx.comp.kind) { + Compilation.Kind.Exe, Compilation.Kind.Lib => { + if (!ctx.comp.haveLibC()) { + @panic("TODO"); + //Buf *builtin_o_path = build_o(g, "builtin"); + //lj->args.append(buf_ptr(builtin_o_path)); + } + + // msvc compiler_rt is missing some stuff, so we still build it and rely on weak linkage + // TODO + //Buf *compiler_rt_o_path = build_compiler_rt(g); + //lj->args.append(buf_ptr(compiler_rt_o_path)); + }, + Compilation.Kind.Obj => {}, + } + + //Buf *def_contents = buf_alloc(); + //ZigList gen_lib_args = {0}; + //for (size_t lib_i = 0; lib_i < g->link_libs_list.length; lib_i += 1) { + // LinkLib *link_lib = g->link_libs_list.at(lib_i); + // if (buf_eql_str(link_lib->name, "c")) { + // continue; + // } + // if (link_lib->provided_explicitly) { + // if (lj->codegen->zig_target.env_type == ZigLLVM_GNU) { + // Buf *arg = buf_sprintf("-l%s", buf_ptr(link_lib->name)); + // lj->args.append(buf_ptr(arg)); + // } + // else { + // lj->args.append(buf_ptr(link_lib->name)); + // } + // } else { + // buf_resize(def_contents, 0); + // buf_appendf(def_contents, "LIBRARY %s\nEXPORTS\n", buf_ptr(link_lib->name)); + // for (size_t exp_i = 0; exp_i < link_lib->symbols.length; exp_i += 1) { + // Buf *symbol_name = link_lib->symbols.at(exp_i); + // buf_appendf(def_contents, "%s\n", buf_ptr(symbol_name)); + // } + // buf_appendf(def_contents, "\n"); + + // Buf *def_path = buf_alloc(); + // os_path_join(g->cache_dir, buf_sprintf("%s.def", buf_ptr(link_lib->name)), def_path); + // os_write_file(def_path, def_contents); + + // Buf *generated_lib_path = buf_alloc(); + // os_path_join(g->cache_dir, buf_sprintf("%s.lib", buf_ptr(link_lib->name)), generated_lib_path); + + // gen_lib_args.resize(0); + // gen_lib_args.append("link"); + + // coff_append_machine_arg(g, &gen_lib_args); + // gen_lib_args.append(buf_ptr(buf_sprintf("-DEF:%s", buf_ptr(def_path)))); + // gen_lib_args.append(buf_ptr(buf_sprintf("-OUT:%s", buf_ptr(generated_lib_path)))); + // Buf diag = BUF_INIT; + // if (!zig_lld_link(g->zig_target.oformat, gen_lib_args.items, gen_lib_args.length, &diag)) { + // fprintf(stderr, "%s\n", buf_ptr(&diag)); + // exit(1); + // } + // lj->args.append(buf_ptr(generated_lib_path)); + // } + //} +} + +fn constructLinkerArgsMachO(ctx: *Context) !void { + try ctx.args.append(c"-demangle"); + + if (ctx.comp.linker_rdynamic) { + try ctx.args.append(c"-export_dynamic"); + } + + const is_lib = ctx.comp.kind == Compilation.Kind.Lib; + const shared = !ctx.comp.is_static and is_lib; + if (ctx.comp.is_static) { + try ctx.args.append(c"-static"); + } else { + try ctx.args.append(c"-dynamic"); + } + + //if (is_lib) { + // if (!g->is_static) { + // lj->args.append("-dylib"); + + // Buf *compat_vers = buf_sprintf("%" ZIG_PRI_usize ".0.0", g->version_major); + // lj->args.append("-compatibility_version"); + // lj->args.append(buf_ptr(compat_vers)); + + // Buf *cur_vers = buf_sprintf("%" ZIG_PRI_usize ".%" ZIG_PRI_usize ".%" ZIG_PRI_usize, + // g->version_major, g->version_minor, g->version_patch); + // lj->args.append("-current_version"); + // lj->args.append(buf_ptr(cur_vers)); + + // // TODO getting an error when running an executable when doing this rpath thing + // //Buf *dylib_install_name = buf_sprintf("@rpath/lib%s.%" ZIG_PRI_usize ".dylib", + // // buf_ptr(g->root_out_name), g->version_major); + // //lj->args.append("-install_name"); + // //lj->args.append(buf_ptr(dylib_install_name)); + + // if (buf_len(&lj->out_file) == 0) { + // buf_appendf(&lj->out_file, "lib%s.%" ZIG_PRI_usize ".%" ZIG_PRI_usize ".%" ZIG_PRI_usize ".dylib", + // buf_ptr(g->root_out_name), g->version_major, g->version_minor, g->version_patch); + // } + // } + //} + + try ctx.args.append(c"-arch"); + const darwin_arch_str = try std.cstr.addNullByte( + &ctx.arena.allocator, + ctx.comp.target.getDarwinArchString(), + ); + try ctx.args.append(darwin_arch_str.ptr); + + const platform = try DarwinPlatform.get(ctx.comp); + switch (platform.kind) { + DarwinPlatform.Kind.MacOS => try ctx.args.append(c"-macosx_version_min"), + DarwinPlatform.Kind.IPhoneOS => try ctx.args.append(c"-iphoneos_version_min"), + DarwinPlatform.Kind.IPhoneOSSimulator => try ctx.args.append(c"-ios_simulator_version_min"), + } + const ver_str = try std.fmt.allocPrint(&ctx.arena.allocator, "{}.{}.{}\x00", platform.major, platform.minor, platform.micro); + try ctx.args.append(ver_str.ptr); + + if (ctx.comp.kind == Compilation.Kind.Exe) { + if (ctx.comp.is_static) { + try ctx.args.append(c"-no_pie"); + } else { + try ctx.args.append(c"-pie"); + } + } + + try ctx.args.append(c"-o"); + try ctx.args.append(ctx.out_file_path.ptr()); + + //for (size_t i = 0; i < g->rpath_list.length; i += 1) { + // Buf *rpath = g->rpath_list.at(i); + // add_rpath(lj, rpath); + //} + //add_rpath(lj, &lj->out_file); + + if (shared) { + try ctx.args.append(c"-headerpad_max_install_names"); + } else if (ctx.comp.is_static) { + try ctx.args.append(c"-lcrt0.o"); + } else { + switch (platform.kind) { + DarwinPlatform.Kind.MacOS => { + if (platform.versionLessThan(10, 5)) { + try ctx.args.append(c"-lcrt1.o"); + } else if (platform.versionLessThan(10, 6)) { + try ctx.args.append(c"-lcrt1.10.5.o"); + } else if (platform.versionLessThan(10, 8)) { + try ctx.args.append(c"-lcrt1.10.6.o"); + } + }, + DarwinPlatform.Kind.IPhoneOS => { + if (ctx.comp.target.getArch() == builtin.Arch.aarch64) { + // iOS does not need any crt1 files for arm64 + } else if (platform.versionLessThan(3, 1)) { + try ctx.args.append(c"-lcrt1.o"); + } else if (platform.versionLessThan(6, 0)) { + try ctx.args.append(c"-lcrt1.3.1.o"); + } + }, + DarwinPlatform.Kind.IPhoneOSSimulator => {}, // no crt1.o needed + } + } + + //for (size_t i = 0; i < g->lib_dirs.length; i += 1) { + // const char *lib_dir = g->lib_dirs.at(i); + // lj->args.append("-L"); + // lj->args.append(lib_dir); + //} + + for (ctx.comp.link_objects) |link_object| { + const link_obj_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, link_object); + try ctx.args.append(link_obj_with_null.ptr); + } + try addFnObjects(ctx); + + //// compiler_rt on darwin is missing some stuff, so we still build it and rely on LinkOnce + //if (g->out_type == OutTypeExe || g->out_type == OutTypeLib) { + // Buf *compiler_rt_o_path = build_compiler_rt(g); + // lj->args.append(buf_ptr(compiler_rt_o_path)); + //} + + if (ctx.comp.target == Target.Native) { + for (ctx.comp.link_libs_list.toSliceConst()) |lib| { + if (mem.eql(u8, lib.name, "c")) { + // on Darwin, libSystem has libc in it, but also you have to use it + // to make syscalls because the syscall numbers are not documented + // and change between versions. + // so we always link against libSystem + try ctx.args.append(c"-lSystem"); + } else { + if (mem.indexOfScalar(u8, lib.name, '/') == null) { + const arg = try std.fmt.allocPrint(&ctx.arena.allocator, "-l{}\x00", lib.name); + try ctx.args.append(arg.ptr); + } else { + const arg = try std.cstr.addNullByte(&ctx.arena.allocator, lib.name); + try ctx.args.append(arg.ptr); + } + } + } + } else { + try ctx.args.append(c"-undefined"); + try ctx.args.append(c"dynamic_lookup"); + } + + if (platform.kind == DarwinPlatform.Kind.MacOS) { + if (platform.versionLessThan(10, 5)) { + try ctx.args.append(c"-lgcc_s.10.4"); + } else if (platform.versionLessThan(10, 6)) { + try ctx.args.append(c"-lgcc_s.10.5"); + } + } else { + @panic("TODO"); + } + + //for (size_t i = 0; i < g->darwin_frameworks.length; i += 1) { + // lj->args.append("-framework"); + // lj->args.append(buf_ptr(g->darwin_frameworks.at(i))); + //} +} + +fn constructLinkerArgsWasm(ctx: *Context) void { + @panic("TODO"); +} + +fn addFnObjects(ctx: *Context) !void { + // at this point it's guaranteed nobody else has this lock, so we circumvent it + // and avoid having to be a coroutine + const fn_link_set = &ctx.comp.fn_link_set.private_data; + + var it = fn_link_set.first; + while (it) |node| { + const fn_val = node.data orelse { + // handle the tombstone. See Value.Fn.destroy. + it = node.next; + fn_link_set.remove(node); + ctx.comp.gpa().destroy(node); + continue; + }; + try ctx.args.append(fn_val.containing_object.ptr()); + it = node.next; + } +} + +const DarwinPlatform = struct { + kind: Kind, + major: u32, + minor: u32, + micro: u32, + + const Kind = enum { + MacOS, + IPhoneOS, + IPhoneOSSimulator, + }; + + fn get(comp: *Compilation) !DarwinPlatform { + var result: DarwinPlatform = undefined; + const ver_str = switch (comp.darwin_version_min) { + Compilation.DarwinVersionMin.MacOS => |ver| blk: { + result.kind = Kind.MacOS; + break :blk ver; + }, + Compilation.DarwinVersionMin.Ios => |ver| blk: { + result.kind = Kind.IPhoneOS; + break :blk ver; + }, + Compilation.DarwinVersionMin.None => blk: { + assert(comp.target.getOs() == builtin.Os.macosx); + result.kind = Kind.MacOS; + break :blk "10.10"; + }, + }; + + var had_extra: bool = undefined; + try darwinGetReleaseVersion(ver_str, &result.major, &result.minor, &result.micro, &had_extra,); + if (had_extra or result.major != 10 or result.minor >= 100 or result.micro >= 100) { + return error.InvalidDarwinVersionString; + } + + if (result.kind == Kind.IPhoneOS) { + switch (comp.target.getArch()) { + builtin.Arch.i386, + builtin.Arch.x86_64, + => result.kind = Kind.IPhoneOSSimulator, + else => {}, + } + } + return result; + } + + fn versionLessThan(self: DarwinPlatform, major: u32, minor: u32) bool { + if (self.major < major) + return true; + if (self.major > major) + return false; + if (self.minor < minor) + return true; + return false; + } +}; + +/// Parse (([0-9]+)(.([0-9]+)(.([0-9]+)?))?)? and return the +/// grouped values as integers. Numbers which are not provided are set to 0. +/// return true if the entire string was parsed (9.2), or all groups were +/// parsed (10.3.5extrastuff). +fn darwinGetReleaseVersion(str: []const u8, major: *u32, minor: *u32, micro: *u32, had_extra: *bool) !void { + major.* = 0; + minor.* = 0; + micro.* = 0; + had_extra.* = false; + + if (str.len == 0) + return error.InvalidDarwinVersionString; + + var start_pos: usize = 0; + for ([]*u32{major, minor, micro}) |v| { + const dot_pos = mem.indexOfScalarPos(u8, str, start_pos, '.'); + const end_pos = dot_pos orelse str.len; + v.* = std.fmt.parseUnsigned(u32, str[start_pos..end_pos], 10) catch return error.InvalidDarwinVersionString; + start_pos = (dot_pos orelse return) + 1; + if (start_pos == str.len) return; + } + had_extra.* = true; +} diff --git a/src-self-hosted/llvm.zig b/src-self-hosted/llvm.zig index 13480dc2c6..8bb45ac616 100644 --- a/src-self-hosted/llvm.zig +++ b/src-self-hosted/llvm.zig @@ -2,6 +2,12 @@ const builtin = @import("builtin"); const c = @import("c.zig"); const assert = @import("std").debug.assert; +// we wrap the c module for 3 reasons: +// 1. to avoid accidentally calling the non-thread-safe functions +// 2. patch up some of the types to remove nullability +// 3. some functions have been augmented by zig_llvm.cpp to be more powerful, +// such as ZigLLVMTargetMachineEmitToFile + pub const AttributeIndex = c_uint; pub const Bool = c_int; @@ -12,25 +18,59 @@ pub const ValueRef = removeNullability(c.LLVMValueRef); pub const TypeRef = removeNullability(c.LLVMTypeRef); pub const BasicBlockRef = removeNullability(c.LLVMBasicBlockRef); pub const AttributeRef = removeNullability(c.LLVMAttributeRef); +pub const TargetRef = removeNullability(c.LLVMTargetRef); +pub const TargetMachineRef = removeNullability(c.LLVMTargetMachineRef); +pub const TargetDataRef = removeNullability(c.LLVMTargetDataRef); +pub const DIBuilder = c.ZigLLVMDIBuilder; +pub const ABIAlignmentOfType = c.LLVMABIAlignmentOfType; pub const AddAttributeAtIndex = c.LLVMAddAttributeAtIndex; pub const AddFunction = c.LLVMAddFunction; +pub const AddGlobal = c.LLVMAddGlobal; +pub const AddModuleCodeViewFlag = c.ZigLLVMAddModuleCodeViewFlag; +pub const AddModuleDebugInfoFlag = c.ZigLLVMAddModuleDebugInfoFlag; +pub const ArrayType = c.LLVMArrayType; pub const ClearCurrentDebugLocation = c.ZigLLVMClearCurrentDebugLocation; +pub const ConstAllOnes = c.LLVMConstAllOnes; +pub const ConstArray = c.LLVMConstArray; +pub const ConstBitCast = c.LLVMConstBitCast; pub const ConstInt = c.LLVMConstInt; +pub const ConstIntOfArbitraryPrecision = c.LLVMConstIntOfArbitraryPrecision; +pub const ConstNeg = c.LLVMConstNeg; +pub const ConstNull = c.LLVMConstNull; pub const ConstStringInContext = c.LLVMConstStringInContext; pub const ConstStructInContext = c.LLVMConstStructInContext; +pub const CopyStringRepOfTargetData = c.LLVMCopyStringRepOfTargetData; pub const CreateBuilderInContext = c.LLVMCreateBuilderInContext; +pub const CreateCompileUnit = c.ZigLLVMCreateCompileUnit; +pub const CreateDIBuilder = c.ZigLLVMCreateDIBuilder; pub const CreateEnumAttribute = c.LLVMCreateEnumAttribute; +pub const CreateFile = c.ZigLLVMCreateFile; pub const CreateStringAttribute = c.LLVMCreateStringAttribute; +pub const CreateTargetDataLayout = c.LLVMCreateTargetDataLayout; +pub const CreateTargetMachine = c.LLVMCreateTargetMachine; +pub const DIBuilderFinalize = c.ZigLLVMDIBuilderFinalize; pub const DisposeBuilder = c.LLVMDisposeBuilder; +pub const DisposeDIBuilder = c.ZigLLVMDisposeDIBuilder; +pub const DisposeMessage = c.LLVMDisposeMessage; pub const DisposeModule = c.LLVMDisposeModule; +pub const DisposeTargetData = c.LLVMDisposeTargetData; +pub const DisposeTargetMachine = c.LLVMDisposeTargetMachine; pub const DoubleTypeInContext = c.LLVMDoubleTypeInContext; pub const DumpModule = c.LLVMDumpModule; pub const FP128TypeInContext = c.LLVMFP128TypeInContext; pub const FloatTypeInContext = c.LLVMFloatTypeInContext; pub const GetEnumAttributeKindForName = c.LLVMGetEnumAttributeKindForName; +pub const GetHostCPUName = c.ZigLLVMGetHostCPUName; pub const GetMDKindIDInContext = c.LLVMGetMDKindIDInContext; +pub const GetNativeFeatures = c.ZigLLVMGetNativeFeatures; +pub const GetUndef = c.LLVMGetUndef; pub const HalfTypeInContext = c.LLVMHalfTypeInContext; +pub const InitializeAllAsmParsers = c.LLVMInitializeAllAsmParsers; +pub const InitializeAllAsmPrinters = c.LLVMInitializeAllAsmPrinters; +pub const InitializeAllTargetInfos = c.LLVMInitializeAllTargetInfos; +pub const InitializeAllTargetMCs = c.LLVMInitializeAllTargetMCs; +pub const InitializeAllTargets = c.LLVMInitializeAllTargets; pub const InsertBasicBlockInContext = c.LLVMInsertBasicBlockInContext; pub const Int128TypeInContext = c.LLVMInt128TypeInContext; pub const Int16TypeInContext = c.LLVMInt16TypeInContext; @@ -47,13 +87,26 @@ pub const MDStringInContext = c.LLVMMDStringInContext; pub const MetadataTypeInContext = c.LLVMMetadataTypeInContext; pub const ModuleCreateWithNameInContext = c.LLVMModuleCreateWithNameInContext; pub const PPCFP128TypeInContext = c.LLVMPPCFP128TypeInContext; +pub const PointerType = c.LLVMPointerType; +pub const SetAlignment = c.LLVMSetAlignment; +pub const SetDataLayout = c.LLVMSetDataLayout; +pub const SetGlobalConstant = c.LLVMSetGlobalConstant; +pub const SetInitializer = c.LLVMSetInitializer; +pub const SetLinkage = c.LLVMSetLinkage; +pub const SetTarget = c.LLVMSetTarget; +pub const SetUnnamedAddr = c.LLVMSetUnnamedAddr; pub const StructTypeInContext = c.LLVMStructTypeInContext; pub const TokenTypeInContext = c.LLVMTokenTypeInContext; +pub const TypeOf = c.LLVMTypeOf; pub const VoidTypeInContext = c.LLVMVoidTypeInContext; pub const X86FP80TypeInContext = c.LLVMX86FP80TypeInContext; pub const X86MMXTypeInContext = c.LLVMX86MMXTypeInContext; -pub const ConstAllOnes = c.LLVMConstAllOnes; -pub const ConstNull = c.LLVMConstNull; + +pub const ConstInBoundsGEP = LLVMConstInBoundsGEP; +pub extern fn LLVMConstInBoundsGEP(ConstantVal: ValueRef, ConstantIndices: [*]ValueRef, NumIndices: c_uint) ?ValueRef; + +pub const GetTargetFromTriple = LLVMGetTargetFromTriple; +extern fn LLVMGetTargetFromTriple(Triple: [*]const u8, T: *TargetRef, ErrorMessage: ?*[*]u8) Bool; pub const VerifyModule = LLVMVerifyModule; extern fn LLVMVerifyModule(M: ModuleRef, Action: VerifierFailureAction, OutMessage: *?[*]u8) Bool; @@ -83,10 +136,66 @@ pub const PrintMessageAction = VerifierFailureAction.LLVMPrintMessageAction; pub const ReturnStatusAction = VerifierFailureAction.LLVMReturnStatusAction; pub const VerifierFailureAction = c.LLVMVerifierFailureAction; +pub const CodeGenLevelNone = c.LLVMCodeGenOptLevel.LLVMCodeGenLevelNone; +pub const CodeGenLevelLess = c.LLVMCodeGenOptLevel.LLVMCodeGenLevelLess; +pub const CodeGenLevelDefault = c.LLVMCodeGenOptLevel.LLVMCodeGenLevelDefault; +pub const CodeGenLevelAggressive = c.LLVMCodeGenOptLevel.LLVMCodeGenLevelAggressive; +pub const CodeGenOptLevel = c.LLVMCodeGenOptLevel; + +pub const RelocDefault = c.LLVMRelocMode.LLVMRelocDefault; +pub const RelocStatic = c.LLVMRelocMode.LLVMRelocStatic; +pub const RelocPIC = c.LLVMRelocMode.LLVMRelocPIC; +pub const RelocDynamicNoPic = c.LLVMRelocMode.LLVMRelocDynamicNoPic; +pub const RelocMode = c.LLVMRelocMode; + +pub const CodeModelDefault = c.LLVMCodeModel.LLVMCodeModelDefault; +pub const CodeModelJITDefault = c.LLVMCodeModel.LLVMCodeModelJITDefault; +pub const CodeModelSmall = c.LLVMCodeModel.LLVMCodeModelSmall; +pub const CodeModelKernel = c.LLVMCodeModel.LLVMCodeModelKernel; +pub const CodeModelMedium = c.LLVMCodeModel.LLVMCodeModelMedium; +pub const CodeModelLarge = c.LLVMCodeModel.LLVMCodeModelLarge; +pub const CodeModel = c.LLVMCodeModel; + +pub const EmitAssembly = EmitOutputType.ZigLLVM_EmitAssembly; +pub const EmitBinary = EmitOutputType.ZigLLVM_EmitBinary; +pub const EmitLLVMIr = EmitOutputType.ZigLLVM_EmitLLVMIr; +pub const EmitOutputType = c.ZigLLVM_EmitOutputType; + +pub const CCallConv = c.LLVMCCallConv; +pub const FastCallConv = c.LLVMFastCallConv; +pub const ColdCallConv = c.LLVMColdCallConv; +pub const WebKitJSCallConv = c.LLVMWebKitJSCallConv; +pub const AnyRegCallConv = c.LLVMAnyRegCallConv; +pub const X86StdcallCallConv = c.LLVMX86StdcallCallConv; +pub const X86FastcallCallConv = c.LLVMX86FastcallCallConv; +pub const CallConv = c.LLVMCallConv; + +pub const FnInline = extern enum { + Auto, + Always, + Never, +}; + fn removeNullability(comptime T: type) type { comptime assert(@typeId(T) == builtin.TypeId.Optional); return T.Child; } pub const BuildRet = LLVMBuildRet; -extern fn LLVMBuildRet(arg0: BuilderRef, V: ?ValueRef) ValueRef; +extern fn LLVMBuildRet(arg0: BuilderRef, V: ?ValueRef) ?ValueRef; + +pub const TargetMachineEmitToFile = ZigLLVMTargetMachineEmitToFile; +extern fn ZigLLVMTargetMachineEmitToFile( + targ_machine_ref: TargetMachineRef, + module_ref: ModuleRef, + filename: [*]const u8, + output_type: EmitOutputType, + error_message: *[*]u8, + is_debug: bool, + is_small: bool, +) bool; + +pub const BuildCall = ZigLLVMBuildCall; +extern fn ZigLLVMBuildCall(B: BuilderRef, Fn: ValueRef, Args: [*]ValueRef, NumArgs: c_uint, CC: c_uint, fn_inline: FnInline, Name: [*]const u8) ?ValueRef; + +pub const PrivateLinkage = c.LLVMLinkage.LLVMPrivateLinkage; diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index c9478954c5..37bb435c1b 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -18,6 +18,7 @@ const EventLoopLocal = @import("compilation.zig").EventLoopLocal; const Compilation = @import("compilation.zig").Compilation; const Target = @import("target.zig").Target; const errmsg = @import("errmsg.zig"); +const LibCInstallation = @import("libc_installation.zig").LibCInstallation; var stderr_file: os.File = undefined; var stderr: *io.OutStream(io.FileOutStream.Error) = undefined; @@ -28,13 +29,14 @@ const usage = \\ \\Commands: \\ - \\ build-exe [source] Create executable from source or object files - \\ build-lib [source] Create library from source or object files - \\ build-obj [source] Create object from source or assembly - \\ fmt [source] Parse file and render in canonical zig format - \\ targets List available compilation targets - \\ version Print version number and exit - \\ zen Print zen of zig and exit + \\ build-exe [source] Create executable from source or object files + \\ build-lib [source] Create library from source or object files + \\ build-obj [source] Create object from source or assembly + \\ fmt [source] Parse file and render in canonical zig format + \\ libc [paths_file] Display native libc paths file or validate one + \\ targets List available compilation targets + \\ version Print version number and exit + \\ zen Print zen of zig and exit \\ \\ ; @@ -85,6 +87,10 @@ pub fn main() !void { .name = "fmt", .exec = cmdFmt, }, + Command{ + .name = "libc", + .exec = cmdLibC, + }, Command{ .name = "targets", .exec = cmdTargets, @@ -130,11 +136,10 @@ const usage_build_generic = \\ --color [auto|off|on] Enable or disable colored error messages \\ \\Compile Options: + \\ --libc [file] Provide a file which specifies libc paths \\ --assembly [source] Add assembly file to build - \\ --cache-dir [path] Override the cache directory \\ --emit [filetype] Emit a specific file format as compilation output \\ --enable-timing-info Print timing diagnostics - \\ --libc-include-dir [path] Directory where libc stdlib.h resides \\ --name [name] Override output name \\ --output [file] Override destination path \\ --output-h [file] Override generated header file path @@ -163,12 +168,7 @@ const usage_build_generic = \\ \\Link Options: \\ --ar-path [path] Set the path to ar - \\ --dynamic-linker [path] Set the path to ld.so \\ --each-lib-rpath Add rpath for each used dynamic library - \\ --libc-lib-dir [path] Directory where libc crt1.o resides - \\ --libc-static-lib-dir [path] Directory where libc crtbegin.o resides - \\ --msvc-lib-dir [path] (windows) directory where vcruntime.lib resides - \\ --kernel32-lib-dir [path] (windows) directory where kernel32.lib resides \\ --library [lib] Link against lib \\ --forbid-library [lib] Make it an error to link against lib \\ --library-path [dir] Add a directory to the library search path @@ -203,14 +203,13 @@ const args_build_generic = []Flag{ }), Flag.ArgMergeN("--assembly", 1), - Flag.Arg1("--cache-dir"), Flag.Option("--emit", []const []const u8{ "asm", "bin", "llvm-ir", }), Flag.Bool("--enable-timing-info"), - Flag.Arg1("--libc-include-dir"), + Flag.Arg1("--libc"), Flag.Arg1("--name"), Flag.Arg1("--output"), Flag.Arg1("--output-h"), @@ -234,12 +233,7 @@ const args_build_generic = []Flag{ Flag.Arg1("-mllvm"), Flag.Arg1("--ar-path"), - Flag.Arg1("--dynamic-linker"), Flag.Bool("--each-lib-rpath"), - Flag.Arg1("--libc-lib-dir"), - Flag.Arg1("--libc-static-lib-dir"), - Flag.Arg1("--msvc-lib-dir"), - Flag.Arg1("--kernel32-lib-dir"), Flag.ArgMergeN("--library", 1), Flag.ArgMergeN("--forbid-library", 1), Flag.ArgMergeN("--library-path", 1), @@ -363,6 +357,8 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co } }; + const is_static = flags.present("static"); + const assembly_files = flags.many("assembly"); const link_objects = flags.many("object"); if (root_source_file == null and link_objects.len == 0 and assembly_files.len == 0) { @@ -375,21 +371,16 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co os.exit(1); } - const rel_cache_dir = flags.single("cache-dir") orelse "zig-cache"[0..]; - const full_cache_dir = os.path.resolve(allocator, ".", rel_cache_dir) catch { - try stderr.print("invalid cache dir: {}\n", rel_cache_dir); - os.exit(1); - }; - defer allocator.free(full_cache_dir); - const zig_lib_dir = introspect.resolveZigLibDir(allocator) catch os.exit(1); defer allocator.free(zig_lib_dir); + var override_libc: LibCInstallation = undefined; + var loop: event.Loop = undefined; try loop.initMultiThreaded(allocator); defer loop.deinit(); - var event_loop_local = EventLoopLocal.init(&loop); + var event_loop_local = try EventLoopLocal.init(&loop); defer event_loop_local.deinit(); var comp = try Compilation.create( @@ -399,11 +390,20 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co Target.Native, out_type, build_mode, + is_static, zig_lib_dir, - full_cache_dir, ); defer comp.destroy(); + if (flags.single("libc")) |libc_path| { + parseLibcPaths(loop.allocator, &override_libc, libc_path); + comp.override_libc = &override_libc; + } + + for (flags.many("library")) |lib| { + _ = try comp.addLinkLib(lib, true); + } + comp.version_major = try std.fmt.parseUnsigned(u32, flags.single("ver-major") orelse "0", 10); comp.version_minor = try std.fmt.parseUnsigned(u32, flags.single("ver-minor") orelse "0", 10); comp.version_patch = try std.fmt.parseUnsigned(u32, flags.single("ver-patch") orelse "0", 10); @@ -426,26 +426,6 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co comp.clang_argv = clang_argv_buf.toSliceConst(); comp.strip = flags.present("strip"); - comp.is_static = flags.present("static"); - - if (flags.single("libc-lib-dir")) |libc_lib_dir| { - comp.libc_lib_dir = libc_lib_dir; - } - if (flags.single("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| { - comp.libc_include_dir = libc_include_dir; - } - if (flags.single("msvc-lib-dir")) |msvc_lib_dir| { - comp.msvc_lib_dir = msvc_lib_dir; - } - if (flags.single("kernel32-lib-dir")) |kernel32_lib_dir| { - comp.kernel32_lib_dir = kernel32_lib_dir; - } - if (flags.single("dynamic-linker")) |dynamic_linker| { - comp.dynamic_linker = dynamic_linker; - } comp.verbose_tokenize = flags.present("verbose-tokenize"); comp.verbose_ast_tree = flags.present("verbose-ast-tree"); @@ -481,9 +461,9 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co } comp.emit_file_type = emit_type; - comp.link_objects = link_objects; comp.assembly_files = assembly_files; - comp.link_out_file = flags.single("out-file"); + comp.link_out_file = flags.single("output"); + comp.link_objects = link_objects; try comp.build(); const process_build_events_handle = try async processBuildEvents(comp, color); @@ -497,7 +477,6 @@ async fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void { switch (build_event) { Compilation.Event.Ok => { - std.debug.warn("Build succeeded\n"); return; }, Compilation.Event.Error => |err| { @@ -506,7 +485,8 @@ async fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void { }, Compilation.Event.Fail => |msgs| { for (msgs) |msg| { - errmsg.printToFile(&stderr_file, msg, color) catch os.exit(1); + defer msg.destroy(); + msg.printToFile(&stderr_file, color) catch os.exit(1); } }, } @@ -577,6 +557,53 @@ const Fmt = struct { } }; +fn parseLibcPaths(allocator: *Allocator, libc: *LibCInstallation, libc_paths_file: []const u8) void { + libc.parse(allocator, libc_paths_file, stderr) catch |err| { + stderr.print( + "Unable to parse libc path file '{}': {}.\n" ++ + "Try running `zig libc` to see an example for the native target.\n", + libc_paths_file, + @errorName(err), + ) catch os.exit(1); + os.exit(1); + }; +} + +fn cmdLibC(allocator: *Allocator, args: []const []const u8) !void { + switch (args.len) { + 0 => {}, + 1 => { + var libc_installation: LibCInstallation = undefined; + parseLibcPaths(allocator, &libc_installation, args[0]); + return; + }, + else => { + try stderr.print("unexpected extra parameter: {}\n", args[1]); + os.exit(1); + }, + } + + var loop: event.Loop = undefined; + try loop.initMultiThreaded(allocator); + defer loop.deinit(); + + var event_loop_local = try EventLoopLocal.init(&loop); + defer event_loop_local.deinit(); + + const handle = try async findLibCAsync(&event_loop_local); + defer cancel handle; + + loop.run(); +} + +async fn findLibCAsync(event_loop_local: *EventLoopLocal) void { + const libc = (await (async event_loop_local.getNativeLibC() catch unreachable)) catch |err| { + stderr.print("unable to find libc: {}\n", @errorName(err)) catch os.exit(1); + os.exit(1); + }; + libc.render(stdout) catch os.exit(1); +} + fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { var flags = try Args.parse(allocator, args_fmt_spec, args); defer flags.deinit(); @@ -620,10 +647,10 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { 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); + const msg = try errmsg.Msg.createFromParseError(allocator, parse_error, &tree, ""); + defer msg.destroy(); - try errmsg.printToFile(&stderr_file, msg, color); + try msg.printToFile(&stderr_file, color); } if (tree.errors.len != 0) { os.exit(1); @@ -676,10 +703,10 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { var error_it = tree.errors.iterator(0); while (error_it.next()) |parse_error| { - const msg = try errmsg.createFromParseError(allocator, parse_error, &tree, file_path); - defer allocator.destroy(msg); + const msg = try errmsg.Msg.createFromParseError(allocator, parse_error, &tree, file_path); + defer msg.destroy(); - try errmsg.printToFile(&stderr_file, msg, color); + try msg.printToFile(&stderr_file, color); } if (tree.errors.len != 0) { fmt.any_error = true; diff --git a/src-self-hosted/package.zig b/src-self-hosted/package.zig new file mode 100644 index 0000000000..720b279651 --- /dev/null +++ b/src-self-hosted/package.zig @@ -0,0 +1,29 @@ +const std = @import("std"); +const mem = std.mem; +const assert = std.debug.assert; +const Buffer = std.Buffer; + +pub const Package = struct { + root_src_dir: Buffer, + root_src_path: Buffer, + + /// relative to root_src_dir + table: Table, + + pub const Table = std.HashMap([]const u8, *Package, mem.hash_slice_u8, mem.eql_slice_u8); + + /// makes internal copies of root_src_dir and root_src_path + /// allocator should be an arena allocator because Package never frees anything + pub fn create(allocator: *mem.Allocator, root_src_dir: []const u8, root_src_path: []const u8) !*Package { + return allocator.create(Package{ + .root_src_dir = try Buffer.init(allocator, root_src_dir), + .root_src_path = try Buffer.init(allocator, root_src_path), + .table = Table.init(allocator), + }); + } + + pub fn add(self: *Package, name: []const u8, package: *Package) !void { + const entry = try self.table.put(try mem.dupe(self.table.allocator, u8, name), package); + assert(entry == null); + } +}; diff --git a/src-self-hosted/parsed_file.zig b/src-self-hosted/parsed_file.zig deleted file mode 100644 index d728c2fd18..0000000000 --- a/src-self-hosted/parsed_file.zig +++ /dev/null @@ -1,6 +0,0 @@ -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 4326617fa0..7a41083f44 100644 --- a/src-self-hosted/scope.zig +++ b/src-self-hosted/scope.zig @@ -8,6 +8,8 @@ const ast = std.zig.ast; const Value = @import("value.zig").Value; const ir = @import("ir.zig"); const Span = @import("errmsg.zig").Span; +const assert = std.debug.assert; +const event = std.event; pub const Scope = struct { id: Id, @@ -23,7 +25,8 @@ pub const Scope = struct { if (base.ref_count == 0) { if (base.parent) |parent| parent.deref(comp); switch (base.id) { - Id.Decls => @fieldParentPtr(Decls, "base", base).destroy(), + Id.Root => @fieldParentPtr(Root, "base", base).destroy(comp), + Id.Decls => @fieldParentPtr(Decls, "base", base).destroy(comp), Id.Block => @fieldParentPtr(Block, "base", base).destroy(comp), Id.FnDef => @fieldParentPtr(FnDef, "base", base).destroy(comp), Id.CompTime => @fieldParentPtr(CompTime, "base", base).destroy(comp), @@ -33,6 +36,15 @@ pub const Scope = struct { } } + pub fn findRoot(base: *Scope) *Root { + var scope = base; + while (scope.parent) |parent| { + scope = parent; + } + assert(scope.id == Id.Root); + return @fieldParentPtr(Root, "base", scope); + } + pub fn findFnDef(base: *Scope) ?*FnDef { var scope = base; while (true) { @@ -44,12 +56,33 @@ pub const Scope = struct { Id.Defer, Id.DeferExpr, Id.CompTime, + Id.Root, + => scope = scope.parent orelse return null, + } + } + } + + pub fn findDeferExpr(base: *Scope) ?*DeferExpr { + var scope = base; + while (true) { + switch (scope.id) { + Id.DeferExpr => return @fieldParentPtr(DeferExpr, "base", base), + + Id.FnDef, + Id.Decls, + => return null, + + Id.Block, + Id.Defer, + Id.CompTime, + Id.Root, => scope = scope.parent orelse return null, } } } pub const Id = enum { + Root, Decls, Block, FnDef, @@ -58,42 +91,82 @@ pub const Scope = struct { DeferExpr, }; + pub const Root = struct { + base: Scope, + tree: *ast.Tree, + realpath: []const u8, + + /// Creates a Root scope with 1 reference + /// Takes ownership of realpath + /// Takes ownership of tree, will deinit and destroy when done. + pub fn create(comp: *Compilation, tree: *ast.Tree, realpath: []u8) !*Root { + const self = try comp.gpa().create(Root{ + .base = Scope{ + .id = Id.Root, + .parent = null, + .ref_count = 1, + }, + .tree = tree, + .realpath = realpath, + }); + errdefer comp.gpa().destroy(self); + + return self; + } + + pub fn destroy(self: *Root, comp: *Compilation) void { + comp.gpa().free(self.tree.source); + self.tree.deinit(); + comp.gpa().destroy(self.tree); + comp.gpa().free(self.realpath); + comp.gpa().destroy(self); + } + }; + pub const Decls = struct { base: Scope, - table: Decl.Table, + + /// The lock must be respected for writing. However once name_future resolves, + /// readers can freely access it. + table: event.Locked(Decl.Table), + + /// Once this future is resolved, the table is complete and available for unlocked + /// read-only access. It does not mean all the decls are resolved; it means only that + /// the table has all the names. Each decl in the table has its own resolution state. + name_future: event.Future(void), /// Creates a Decls scope with 1 reference - pub fn create(comp: *Compilation, parent: ?*Scope) !*Decls { - const self = try comp.a().create(Decls{ + pub fn create(comp: *Compilation, parent: *Scope) !*Decls { + const self = try comp.gpa().create(Decls{ .base = Scope{ .id = Id.Decls, .parent = parent, .ref_count = 1, }, - .table = undefined, + .table = event.Locked(Decl.Table).init(comp.loop, Decl.Table.init(comp.gpa())), + .name_future = event.Future(void).init(comp.loop), }); - errdefer comp.a().destroy(self); - - self.table = Decl.Table.init(comp.a()); - errdefer self.table.deinit(); - - if (parent) |p| p.ref(); - + parent.ref(); return self; } - pub fn destroy(self: *Decls) void { + pub fn destroy(self: *Decls, comp: *Compilation) void { self.table.deinit(); - self.table.allocator.destroy(self); + comp.gpa().destroy(self); + } + + pub async fn getTableReadOnly(self: *Decls) *Decl.Table { + _ = await (async self.name_future.get() catch unreachable); + return &self.table.private_data; } }; pub const Block = struct { base: Scope, - incoming_values: std.ArrayList(*ir.Instruction), + incoming_values: std.ArrayList(*ir.Inst), incoming_blocks: std.ArrayList(*ir.BasicBlock), end_block: *ir.BasicBlock, - is_comptime: *ir.Instruction, + is_comptime: *ir.Inst, safety: Safety, @@ -125,8 +198,8 @@ pub const Scope = struct { }; /// Creates a Block scope with 1 reference - pub fn create(comp: *Compilation, parent: ?*Scope) !*Block { - const self = try comp.a().create(Block{ + pub fn create(comp: *Compilation, parent: *Scope) !*Block { + const self = try comp.gpa().create(Block{ .base = Scope{ .id = Id.Block, .parent = parent, @@ -138,14 +211,14 @@ pub const Scope = struct { .is_comptime = undefined, .safety = Safety.Auto, }); - errdefer comp.a().destroy(self); + errdefer comp.gpa().destroy(self); - if (parent) |p| p.ref(); + parent.ref(); return self; } pub fn destroy(self: *Block, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -157,8 +230,8 @@ pub const Scope = struct { /// Creates a FnDef scope with 1 reference /// Must set the fn_val later - pub fn create(comp: *Compilation, parent: ?*Scope) !*FnDef { - const self = try comp.a().create(FnDef{ + pub fn create(comp: *Compilation, parent: *Scope) !*FnDef { + const self = try comp.gpa().create(FnDef{ .base = Scope{ .id = Id.FnDef, .parent = parent, @@ -167,13 +240,13 @@ pub const Scope = struct { .fn_val = undefined, }); - if (parent) |p| p.ref(); + parent.ref(); return self; } pub fn destroy(self: *FnDef, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -181,8 +254,8 @@ pub const Scope = 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{ + pub fn create(comp: *Compilation, parent: *Scope) !*CompTime { + const self = try comp.gpa().create(CompTime{ .base = Scope{ .id = Id.CompTime, .parent = parent, @@ -190,12 +263,12 @@ pub const Scope = struct { }, }); - if (parent) |p| p.ref(); + parent.ref(); return self; } pub fn destroy(self: *CompTime, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -212,11 +285,11 @@ pub const Scope = struct { /// Creates a Defer scope with 1 reference pub fn create( comp: *Compilation, - parent: ?*Scope, + parent: *Scope, kind: Kind, defer_expr_scope: *DeferExpr, ) !*Defer { - const self = try comp.a().create(Defer{ + const self = try comp.gpa().create(Defer{ .base = Scope{ .id = Id.Defer, .parent = parent, @@ -225,42 +298,44 @@ pub const Scope = struct { .defer_expr_scope = defer_expr_scope, .kind = kind, }); - errdefer comp.a().destroy(self); + errdefer comp.gpa().destroy(self); defer_expr_scope.base.ref(); - if (parent) |p| p.ref(); + parent.ref(); return self; } pub fn destroy(self: *Defer, comp: *Compilation) void { self.defer_expr_scope.base.deref(comp); - comp.a().destroy(self); + comp.gpa().destroy(self); } }; pub const DeferExpr = struct { base: Scope, expr_node: *ast.Node, + reported_err: bool, /// 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{ + pub fn create(comp: *Compilation, parent: *Scope, expr_node: *ast.Node) !*DeferExpr { + const self = try comp.gpa().create(DeferExpr{ .base = Scope{ .id = Id.DeferExpr, .parent = parent, .ref_count = 1, }, .expr_node = expr_node, + .reported_err = false, }); - errdefer comp.a().destroy(self); + errdefer comp.gpa().destroy(self); - if (parent) |p| p.ref(); + parent.ref(); return self; } pub fn destroy(self: *DeferExpr, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; }; diff --git a/src-self-hosted/target.zig b/src-self-hosted/target.zig index 724d99ea23..0cc8d02a62 100644 --- a/src-self-hosted/target.zig +++ b/src-self-hosted/target.zig @@ -1,60 +1,562 @@ +const std = @import("std"); const builtin = @import("builtin"); -const c = @import("c.zig"); +const llvm = @import("llvm.zig"); +const CInt = @import("c_int.zig").CInt; -pub const CrossTarget = struct { - arch: builtin.Arch, - os: builtin.Os, - environ: builtin.Environ, +pub const FloatAbi = enum { + Hard, + Soft, + SoftFp, }; pub const Target = union(enum) { Native, - Cross: CrossTarget, + Cross: Cross, - pub fn oFileExt(self: *const Target) []const u8 { - const environ = switch (self.*) { - Target.Native => builtin.environ, - Target.Cross => |t| t.environ, - }; - return switch (environ) { - builtin.Environ.msvc => ".obj", + pub const Cross = struct { + arch: builtin.Arch, + os: builtin.Os, + environ: builtin.Environ, + object_format: builtin.ObjectFormat, + }; + + pub fn objFileExt(self: Target) []const u8 { + return switch (self.getObjectFormat()) { + builtin.ObjectFormat.coff => ".obj", else => ".o", }; } - pub fn exeFileExt(self: *const Target) []const u8 { + pub fn exeFileExt(self: Target) []const u8 { return switch (self.getOs()) { builtin.Os.windows => ".exe", else => "", }; } - pub fn getOs(self: *const Target) builtin.Os { - return switch (self.*) { - Target.Native => builtin.os, - Target.Cross => |t| t.os, + pub fn libFileExt(self: Target, is_static: bool) []const u8 { + return switch (self.getOs()) { + builtin.Os.windows => if (is_static) ".lib" else ".dll", + else => if (is_static) ".a" else ".so", }; } - pub fn isDarwin(self: *const Target) bool { + pub fn getOs(self: Target) builtin.Os { + return switch (self) { + Target.Native => builtin.os, + @TagType(Target).Cross => |t| t.os, + }; + } + + pub fn getArch(self: Target) builtin.Arch { + return switch (self) { + Target.Native => builtin.arch, + @TagType(Target).Cross => |t| t.arch, + }; + } + + pub fn getEnviron(self: Target) builtin.Environ { + return switch (self) { + Target.Native => builtin.environ, + @TagType(Target).Cross => |t| t.environ, + }; + } + + pub fn getObjectFormat(self: Target) builtin.ObjectFormat { + return switch (self) { + Target.Native => builtin.object_format, + @TagType(Target).Cross => |t| t.object_format, + }; + } + + pub fn isWasm(self: Target) bool { + return switch (self.getArch()) { + builtin.Arch.wasm32, builtin.Arch.wasm64 => true, + else => false, + }; + } + + pub fn isDarwin(self: Target) bool { return switch (self.getOs()) { builtin.Os.ios, builtin.Os.macosx => true, else => false, }; } - pub fn isWindows(self: *const Target) bool { + pub fn isWindows(self: Target) bool { return switch (self.getOs()) { builtin.Os.windows => true, else => false, }; } -}; -pub fn initializeAll() void { - c.LLVMInitializeAllTargets(); - c.LLVMInitializeAllTargetInfos(); - c.LLVMInitializeAllTargetMCs(); - c.LLVMInitializeAllAsmPrinters(); - c.LLVMInitializeAllAsmParsers(); -} + /// TODO expose the arch and subarch separately + pub fn isArmOrThumb(self: Target) bool { + return switch (self.getArch()) { + builtin.Arch.armv8_3a, + builtin.Arch.armv8_2a, + builtin.Arch.armv8_1a, + builtin.Arch.armv8, + builtin.Arch.armv8r, + builtin.Arch.armv8m_baseline, + builtin.Arch.armv8m_mainline, + builtin.Arch.armv7, + builtin.Arch.armv7em, + builtin.Arch.armv7m, + builtin.Arch.armv7s, + builtin.Arch.armv7k, + builtin.Arch.armv7ve, + builtin.Arch.armv6, + builtin.Arch.armv6m, + builtin.Arch.armv6k, + builtin.Arch.armv6t2, + builtin.Arch.armv5, + builtin.Arch.armv5te, + builtin.Arch.armv4t, + builtin.Arch.armebv8_3a, + builtin.Arch.armebv8_2a, + builtin.Arch.armebv8_1a, + builtin.Arch.armebv8, + builtin.Arch.armebv8r, + builtin.Arch.armebv8m_baseline, + builtin.Arch.armebv8m_mainline, + builtin.Arch.armebv7, + builtin.Arch.armebv7em, + builtin.Arch.armebv7m, + builtin.Arch.armebv7s, + builtin.Arch.armebv7k, + builtin.Arch.armebv7ve, + builtin.Arch.armebv6, + builtin.Arch.armebv6m, + builtin.Arch.armebv6k, + builtin.Arch.armebv6t2, + builtin.Arch.armebv5, + builtin.Arch.armebv5te, + builtin.Arch.armebv4t, + builtin.Arch.thumb, + builtin.Arch.thumbeb, + => true, + else => false, + }; + } + + pub fn initializeAll() void { + llvm.InitializeAllTargets(); + llvm.InitializeAllTargetInfos(); + llvm.InitializeAllTargetMCs(); + llvm.InitializeAllAsmPrinters(); + llvm.InitializeAllAsmParsers(); + } + + pub fn getTriple(self: Target, allocator: *std.mem.Allocator) !std.Buffer { + var result = try std.Buffer.initSize(allocator, 0); + errdefer result.deinit(); + + // LLVM WebAssembly output support requires the target to be activated at + // build type with -DCMAKE_LLVM_EXPIERMENTAL_TARGETS_TO_BUILD=WebAssembly. + // + // LLVM determines the output format based on the environment suffix, + // defaulting to an object based on the architecture. The default format in + // LLVM 6 sets the wasm arch output incorrectly to ELF. We need to + // explicitly set this ourself in order for it to work. + // + // This is fixed in LLVM 7 and you will be able to get wasm output by + // using the target triple `wasm32-unknown-unknown-unknown`. + const env_name = if (self.isWasm()) "wasm" else @tagName(self.getEnviron()); + + var out = &std.io.BufferOutStream.init(&result).stream; + try out.print("{}-unknown-{}-{}", @tagName(self.getArch()), @tagName(self.getOs()), env_name); + + return result; + } + + pub fn is64bit(self: Target) bool { + return self.getArchPtrBitWidth() == 64; + } + + pub fn getArchPtrBitWidth(self: Target) u32 { + switch (self.getArch()) { + builtin.Arch.avr, + builtin.Arch.msp430, + => return 16, + + builtin.Arch.arc, + builtin.Arch.armv8_3a, + builtin.Arch.armv8_2a, + builtin.Arch.armv8_1a, + builtin.Arch.armv8, + builtin.Arch.armv8r, + builtin.Arch.armv8m_baseline, + builtin.Arch.armv8m_mainline, + builtin.Arch.armv7, + builtin.Arch.armv7em, + builtin.Arch.armv7m, + builtin.Arch.armv7s, + builtin.Arch.armv7k, + builtin.Arch.armv7ve, + builtin.Arch.armv6, + builtin.Arch.armv6m, + builtin.Arch.armv6k, + builtin.Arch.armv6t2, + builtin.Arch.armv5, + builtin.Arch.armv5te, + builtin.Arch.armv4t, + builtin.Arch.armebv8_3a, + builtin.Arch.armebv8_2a, + builtin.Arch.armebv8_1a, + builtin.Arch.armebv8, + builtin.Arch.armebv8r, + builtin.Arch.armebv8m_baseline, + builtin.Arch.armebv8m_mainline, + builtin.Arch.armebv7, + builtin.Arch.armebv7em, + builtin.Arch.armebv7m, + builtin.Arch.armebv7s, + builtin.Arch.armebv7k, + builtin.Arch.armebv7ve, + builtin.Arch.armebv6, + builtin.Arch.armebv6m, + builtin.Arch.armebv6k, + builtin.Arch.armebv6t2, + builtin.Arch.armebv5, + builtin.Arch.armebv5te, + builtin.Arch.armebv4t, + builtin.Arch.hexagon, + builtin.Arch.le32, + builtin.Arch.mips, + builtin.Arch.mipsel, + builtin.Arch.nios2, + builtin.Arch.powerpc, + builtin.Arch.r600, + builtin.Arch.riscv32, + builtin.Arch.sparc, + builtin.Arch.sparcel, + builtin.Arch.tce, + builtin.Arch.tcele, + builtin.Arch.thumb, + builtin.Arch.thumbeb, + builtin.Arch.i386, + builtin.Arch.xcore, + builtin.Arch.nvptx, + builtin.Arch.amdil, + builtin.Arch.hsail, + builtin.Arch.spir, + builtin.Arch.kalimbav3, + builtin.Arch.kalimbav4, + builtin.Arch.kalimbav5, + builtin.Arch.shave, + builtin.Arch.lanai, + builtin.Arch.wasm32, + builtin.Arch.renderscript32, + => return 32, + + builtin.Arch.aarch64, + builtin.Arch.aarch64_be, + builtin.Arch.mips64, + builtin.Arch.mips64el, + builtin.Arch.powerpc64, + builtin.Arch.powerpc64le, + builtin.Arch.riscv64, + builtin.Arch.x86_64, + builtin.Arch.nvptx64, + builtin.Arch.le64, + builtin.Arch.amdil64, + builtin.Arch.hsail64, + builtin.Arch.spir64, + builtin.Arch.wasm64, + builtin.Arch.renderscript64, + builtin.Arch.amdgcn, + builtin.Arch.bpfel, + builtin.Arch.bpfeb, + builtin.Arch.sparcv9, + builtin.Arch.s390x, + => return 64, + } + } + + pub fn getFloatAbi(self: Target) FloatAbi { + return switch (self.getEnviron()) { + builtin.Environ.gnueabihf, + builtin.Environ.eabihf, + builtin.Environ.musleabihf, + => FloatAbi.Hard, + else => FloatAbi.Soft, + }; + } + + pub fn getDynamicLinkerPath(self: Target) ?[]const u8 { + const env = self.getEnviron(); + const arch = self.getArch(); + switch (env) { + builtin.Environ.android => { + if (self.is64bit()) { + return "/system/bin/linker64"; + } else { + return "/system/bin/linker"; + } + }, + builtin.Environ.gnux32 => { + if (arch == builtin.Arch.x86_64) { + return "/libx32/ld-linux-x32.so.2"; + } + }, + builtin.Environ.musl, + builtin.Environ.musleabi, + builtin.Environ.musleabihf, + => { + if (arch == builtin.Arch.x86_64) { + return "/lib/ld-musl-x86_64.so.1"; + } + }, + else => {}, + } + switch (arch) { + builtin.Arch.i386, + builtin.Arch.sparc, + builtin.Arch.sparcel, + => return "/lib/ld-linux.so.2", + + builtin.Arch.aarch64 => return "/lib/ld-linux-aarch64.so.1", + builtin.Arch.aarch64_be => return "/lib/ld-linux-aarch64_be.so.1", + + builtin.Arch.armv8_3a, + builtin.Arch.armv8_2a, + builtin.Arch.armv8_1a, + builtin.Arch.armv8, + builtin.Arch.armv8r, + builtin.Arch.armv8m_baseline, + builtin.Arch.armv8m_mainline, + builtin.Arch.armv7, + builtin.Arch.armv7em, + builtin.Arch.armv7m, + builtin.Arch.armv7s, + builtin.Arch.armv7k, + builtin.Arch.armv7ve, + builtin.Arch.armv6, + builtin.Arch.armv6m, + builtin.Arch.armv6k, + builtin.Arch.armv6t2, + builtin.Arch.armv5, + builtin.Arch.armv5te, + builtin.Arch.armv4t, + builtin.Arch.thumb, + => return switch (self.getFloatAbi()) { + FloatAbi.Hard => return "/lib/ld-linux-armhf.so.3", + else => return "/lib/ld-linux.so.3", + }, + + builtin.Arch.armebv8_3a, + builtin.Arch.armebv8_2a, + builtin.Arch.armebv8_1a, + builtin.Arch.armebv8, + builtin.Arch.armebv8r, + builtin.Arch.armebv8m_baseline, + builtin.Arch.armebv8m_mainline, + builtin.Arch.armebv7, + builtin.Arch.armebv7em, + builtin.Arch.armebv7m, + builtin.Arch.armebv7s, + builtin.Arch.armebv7k, + builtin.Arch.armebv7ve, + builtin.Arch.armebv6, + builtin.Arch.armebv6m, + builtin.Arch.armebv6k, + builtin.Arch.armebv6t2, + builtin.Arch.armebv5, + builtin.Arch.armebv5te, + builtin.Arch.armebv4t, + builtin.Arch.thumbeb, + => return switch (self.getFloatAbi()) { + FloatAbi.Hard => return "/lib/ld-linux-armhf.so.3", + else => return "/lib/ld-linux.so.3", + }, + + builtin.Arch.mips, + builtin.Arch.mipsel, + builtin.Arch.mips64, + builtin.Arch.mips64el, + => return null, + + builtin.Arch.powerpc => return "/lib/ld.so.1", + builtin.Arch.powerpc64 => return "/lib64/ld64.so.2", + builtin.Arch.powerpc64le => return "/lib64/ld64.so.2", + builtin.Arch.s390x => return "/lib64/ld64.so.1", + builtin.Arch.sparcv9 => return "/lib64/ld-linux.so.2", + builtin.Arch.x86_64 => return "/lib64/ld-linux-x86-64.so.2", + + builtin.Arch.arc, + builtin.Arch.avr, + builtin.Arch.bpfel, + builtin.Arch.bpfeb, + builtin.Arch.hexagon, + builtin.Arch.msp430, + builtin.Arch.nios2, + builtin.Arch.r600, + builtin.Arch.amdgcn, + builtin.Arch.riscv32, + builtin.Arch.riscv64, + builtin.Arch.tce, + builtin.Arch.tcele, + builtin.Arch.xcore, + builtin.Arch.nvptx, + builtin.Arch.nvptx64, + builtin.Arch.le32, + builtin.Arch.le64, + builtin.Arch.amdil, + builtin.Arch.amdil64, + builtin.Arch.hsail, + builtin.Arch.hsail64, + builtin.Arch.spir, + builtin.Arch.spir64, + builtin.Arch.kalimbav3, + builtin.Arch.kalimbav4, + builtin.Arch.kalimbav5, + builtin.Arch.shave, + builtin.Arch.lanai, + builtin.Arch.wasm32, + builtin.Arch.wasm64, + builtin.Arch.renderscript32, + builtin.Arch.renderscript64, + => return null, + } + } + + pub fn llvmTargetFromTriple(triple: std.Buffer) !llvm.TargetRef { + var result: llvm.TargetRef = undefined; + var err_msg: [*]u8 = undefined; + if (llvm.GetTargetFromTriple(triple.ptr(), &result, &err_msg) != 0) { + std.debug.warn("triple: {s} error: {s}\n", triple.ptr(), err_msg); + return error.UnsupportedTarget; + } + return result; + } + + pub fn cIntTypeSizeInBits(self: Target, id: CInt.Id) u32 { + const arch = self.getArch(); + switch (self.getOs()) { + builtin.Os.freestanding => switch (self.getArch()) { + builtin.Arch.msp430 => switch (id) { + CInt.Id.Short, + CInt.Id.UShort, + CInt.Id.Int, + CInt.Id.UInt, + => return 16, + CInt.Id.Long, + CInt.Id.ULong, + => return 32, + CInt.Id.LongLong, + CInt.Id.ULongLong, + => return 64, + }, + else => switch (id) { + CInt.Id.Short, + CInt.Id.UShort, + => return 16, + CInt.Id.Int, + CInt.Id.UInt, + => return 32, + CInt.Id.Long, + CInt.Id.ULong, + => return self.getArchPtrBitWidth(), + CInt.Id.LongLong, + CInt.Id.ULongLong, + => return 64, + }, + }, + + builtin.Os.linux, + builtin.Os.macosx, + builtin.Os.openbsd, + builtin.Os.zen, + => switch (id) { + CInt.Id.Short, + CInt.Id.UShort, + => return 16, + CInt.Id.Int, + CInt.Id.UInt, + => return 32, + CInt.Id.Long, + CInt.Id.ULong, + => return self.getArchPtrBitWidth(), + CInt.Id.LongLong, + CInt.Id.ULongLong, + => return 64, + }, + + builtin.Os.windows => switch (id) { + CInt.Id.Short, + CInt.Id.UShort, + => return 16, + CInt.Id.Int, + CInt.Id.UInt, + => return 32, + CInt.Id.Long, + CInt.Id.ULong, + CInt.Id.LongLong, + CInt.Id.ULongLong, + => return 64, + }, + + builtin.Os.ananas, + builtin.Os.cloudabi, + builtin.Os.dragonfly, + builtin.Os.freebsd, + builtin.Os.fuchsia, + builtin.Os.ios, + builtin.Os.kfreebsd, + builtin.Os.lv2, + builtin.Os.netbsd, + builtin.Os.solaris, + builtin.Os.haiku, + builtin.Os.minix, + builtin.Os.rtems, + builtin.Os.nacl, + builtin.Os.cnk, + builtin.Os.aix, + builtin.Os.cuda, + builtin.Os.nvcl, + builtin.Os.amdhsa, + builtin.Os.ps4, + builtin.Os.elfiamcu, + builtin.Os.tvos, + builtin.Os.watchos, + builtin.Os.mesa3d, + builtin.Os.contiki, + builtin.Os.amdpal, + => @panic("TODO specify the C integer type sizes for this OS"), + } + } + + pub fn getDarwinArchString(self: Target) []const u8 { + const arch = self.getArch(); + switch (arch) { + builtin.Arch.aarch64 => return "arm64", + builtin.Arch.thumb, + builtin.Arch.armv8_3a, + builtin.Arch.armv8_2a, + builtin.Arch.armv8_1a, + builtin.Arch.armv8, + builtin.Arch.armv8r, + builtin.Arch.armv8m_baseline, + builtin.Arch.armv8m_mainline, + builtin.Arch.armv7, + builtin.Arch.armv7em, + builtin.Arch.armv7m, + builtin.Arch.armv7s, + builtin.Arch.armv7k, + builtin.Arch.armv7ve, + builtin.Arch.armv6, + builtin.Arch.armv6m, + builtin.Arch.armv6k, + builtin.Arch.armv6t2, + builtin.Arch.armv5, + builtin.Arch.armv5te, + builtin.Arch.armv4t, + => return "arm", + builtin.Arch.powerpc => return "ppc", + builtin.Arch.powerpc64 => return "ppc64", + builtin.Arch.powerpc64le => return "ppc64le", + else => return @tagName(arch), + } + } +}; diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 3edb267ca9..47e45d1bb0 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -8,12 +8,14 @@ const assertOrPanic = std.debug.assertOrPanic; const errmsg = @import("errmsg.zig"); const EventLoopLocal = @import("compilation.zig").EventLoopLocal; -test "compile errors" { - var ctx: TestContext = undefined; +var ctx: TestContext = undefined; + +test "stage2" { try ctx.init(); defer ctx.deinit(); try @import("../test/stage2/compile_errors.zig").addCases(&ctx); + try @import("../test/stage2/compare_output.zig").addCases(&ctx); try ctx.run(); } @@ -25,7 +27,6 @@ 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, @@ -38,7 +39,6 @@ pub const TestContext = struct { .loop = undefined, .event_loop_local = undefined, .zig_lib_dir = undefined, - .zig_cache_dir = undefined, .group = undefined, .file_index = std.atomic.Int(usize).init(0), }; @@ -46,7 +46,7 @@ pub const TestContext = struct { try self.loop.initMultiThreaded(allocator); errdefer self.loop.deinit(); - self.event_loop_local = EventLoopLocal.init(&self.loop); + self.event_loop_local = try EventLoopLocal.init(&self.loop); errdefer self.event_loop_local.deinit(); self.group = std.event.Group(error!void).init(&self.loop); @@ -55,16 +55,12 @@ pub const TestContext = struct { 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(); @@ -107,8 +103,8 @@ pub const TestContext = struct { Target.Native, Compilation.Kind.Obj, builtin.Mode.Debug, + true, // is_static self.zig_lib_dir, - self.zig_cache_dir, ); errdefer comp.destroy(); @@ -117,6 +113,84 @@ pub const TestContext = struct { try self.group.call(getModuleEvent, comp, source, path, line, column, msg); } + fn testCompareOutputLibC( + self: *TestContext, + source: []const u8, + expected_output: []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); + + const output_file = try std.fmt.allocPrint(allocator, "{}-out{}", file1_path, Target(Target.Native).exeFileExt()); + 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.Exe, + builtin.Mode.Debug, + false, + self.zig_lib_dir, + ); + errdefer comp.destroy(); + + _ = try comp.addLinkLib("c", true); + comp.link_out_file = output_file; + try comp.build(); + + try self.group.call(getModuleEventSuccess, comp, output_file, expected_output); + } + + async fn getModuleEventSuccess( + comp: *Compilation, + exe_file: []const u8, + expected_output: []const u8, + ) !void { + // TODO this should not be necessary + const exe_file_2 = try std.mem.dupe(allocator, u8, exe_file); + + defer comp.destroy(); + const build_event = await (async comp.events.get() catch unreachable); + + switch (build_event) { + Compilation.Event.Ok => { + const argv = []const []const u8{exe_file_2}; + // TODO use event loop + const child = try std.os.ChildProcess.exec(allocator, argv, null, null, 1024 * 1024); + switch (child.term) { + std.os.ChildProcess.Term.Exited => |code| { + if (code != 0) { + return error.BadReturnCode; + } + }, + else => { + return error.Crashed; + }, + } + if (!mem.eql(u8, child.stdout, expected_output)) { + return error.OutputMismatch; + } + }, + Compilation.Event.Error => |err| return err, + Compilation.Event.Fail => |msgs| { + var stderr = try std.io.getStdErr(); + try stderr.write("build incorrectly failed:\n"); + for (msgs) |msg| { + defer msg.destroy(); + try msg.printToFile(&stderr, errmsg.Color.Auto); + } + }, + } + } + async fn getModuleEvent( comp: *Compilation, source: []const u8, @@ -138,10 +212,10 @@ pub const TestContext = struct { 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 (mem.endsWith(u8, msg.getRealPath(), path) and mem.eql(u8, msg.text, text)) { + const first_token = msg.getTree().tokens.at(msg.span.first); + const last_token = msg.getTree().tokens.at(msg.span.first); + const start_loc = msg.getTree().tokenLocationPtr(0, first_token); if (start_loc.line + 1 == line and start_loc.column + 1 == column) { return; } @@ -158,7 +232,8 @@ pub const TestContext = struct { std.debug.warn("\n====found:========\n"); var stderr = try std.io.getStdErr(); for (msgs) |msg| { - try errmsg.printToFile(&stderr, msg, errmsg.Color.Auto); + defer msg.destroy(); + try msg.printToFile(&stderr, errmsg.Color.Auto); } std.debug.warn("============\n"); return error.TestFailed; diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index 670547cce2..217c1d50a7 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -4,11 +4,17 @@ const Scope = @import("scope.zig").Scope; const Compilation = @import("compilation.zig").Compilation; const Value = @import("value.zig").Value; const llvm = @import("llvm.zig"); -const ObjectFile = @import("codegen.zig").ObjectFile; +const event = std.event; +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; pub const Type = struct { base: Value, id: Id, + name: []const u8, + abi_alignment: AbiAlignment, + + pub const AbiAlignment = event.Future(error{OutOfMemory}!u32); pub const Id = builtin.TypeId; @@ -42,33 +48,37 @@ pub const Type = struct { } } - pub fn getLlvmType(base: *Type, ofile: *ObjectFile) (error{OutOfMemory}!llvm.TypeRef) { + pub fn getLlvmType( + base: *Type, + allocator: *Allocator, + llvm_context: llvm.ContextRef, + ) (error{OutOfMemory}!llvm.TypeRef) { switch (base.id) { - Id.Struct => return @fieldParentPtr(Struct, "base", base).getLlvmType(ofile), - Id.Fn => return @fieldParentPtr(Fn, "base", base).getLlvmType(ofile), + Id.Struct => return @fieldParentPtr(Struct, "base", base).getLlvmType(allocator, llvm_context), + Id.Fn => return @fieldParentPtr(Fn, "base", base).getLlvmType(allocator, llvm_context), Id.Type => unreachable, Id.Void => unreachable, - Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmType(ofile), + Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmType(allocator, llvm_context), Id.NoReturn => unreachable, - Id.Int => return @fieldParentPtr(Int, "base", base).getLlvmType(ofile), - Id.Float => return @fieldParentPtr(Float, "base", base).getLlvmType(ofile), - Id.Pointer => return @fieldParentPtr(Pointer, "base", base).getLlvmType(ofile), - Id.Array => return @fieldParentPtr(Array, "base", base).getLlvmType(ofile), + Id.Int => return @fieldParentPtr(Int, "base", base).getLlvmType(allocator, llvm_context), + Id.Float => return @fieldParentPtr(Float, "base", base).getLlvmType(allocator, llvm_context), + Id.Pointer => return @fieldParentPtr(Pointer, "base", base).getLlvmType(allocator, llvm_context), + Id.Array => return @fieldParentPtr(Array, "base", base).getLlvmType(allocator, llvm_context), Id.ComptimeFloat => unreachable, Id.ComptimeInt => unreachable, Id.Undefined => unreachable, Id.Null => unreachable, - Id.Optional => return @fieldParentPtr(Optional, "base", base).getLlvmType(ofile), - Id.ErrorUnion => return @fieldParentPtr(ErrorUnion, "base", base).getLlvmType(ofile), - Id.ErrorSet => return @fieldParentPtr(ErrorSet, "base", base).getLlvmType(ofile), - Id.Enum => return @fieldParentPtr(Enum, "base", base).getLlvmType(ofile), - Id.Union => return @fieldParentPtr(Union, "base", base).getLlvmType(ofile), + Id.Optional => return @fieldParentPtr(Optional, "base", base).getLlvmType(allocator, llvm_context), + Id.ErrorUnion => return @fieldParentPtr(ErrorUnion, "base", base).getLlvmType(allocator, llvm_context), + Id.ErrorSet => return @fieldParentPtr(ErrorSet, "base", base).getLlvmType(allocator, llvm_context), + Id.Enum => return @fieldParentPtr(Enum, "base", base).getLlvmType(allocator, llvm_context), + Id.Union => return @fieldParentPtr(Union, "base", base).getLlvmType(allocator, llvm_context), Id.Namespace => unreachable, Id.Block => unreachable, - Id.BoundFn => return @fieldParentPtr(BoundFn, "base", base).getLlvmType(ofile), + Id.BoundFn => return @fieldParentPtr(BoundFn, "base", base).getLlvmType(allocator, llvm_context), Id.ArgTuple => unreachable, - Id.Opaque => return @fieldParentPtr(Opaque, "base", base).getLlvmType(ofile), - Id.Promise => return @fieldParentPtr(Promise, "base", base).getLlvmType(ofile), + Id.Opaque => return @fieldParentPtr(Opaque, "base", base).getLlvmType(allocator, llvm_context), + Id.Promise => return @fieldParentPtr(Promise, "base", base).getLlvmType(allocator, llvm_context), } } @@ -151,8 +161,49 @@ pub const Type = struct { std.debug.warn("{}", @tagName(base.id)); } - pub fn getAbiAlignment(base: *Type, comp: *Compilation) u32 { - @panic("TODO getAbiAlignment"); + fn init(base: *Type, comp: *Compilation, id: Id, name: []const u8) void { + base.* = Type{ + .base = Value{ + .id = Value.Id.Type, + .typ = &MetaType.get(comp).base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .id = id, + .name = name, + .abi_alignment = AbiAlignment.init(comp.loop), + }; + } + + /// If you happen to have an llvm context handy, use getAbiAlignmentInContext instead. + /// Otherwise, this one will grab one from the pool and then release it. + pub async fn getAbiAlignment(base: *Type, comp: *Compilation) !u32 { + if (await (async base.abi_alignment.start() catch unreachable)) |ptr| return ptr.*; + + { + const held = try comp.event_loop_local.getAnyLlvmContext(); + defer held.release(comp.event_loop_local); + + const llvm_context = held.node.data; + + base.abi_alignment.data = await (async base.resolveAbiAlignment(comp, llvm_context) catch unreachable); + } + base.abi_alignment.resolve(); + return base.abi_alignment.data; + } + + /// If you have an llvm conext handy, you can use it here. + pub async fn getAbiAlignmentInContext(base: *Type, comp: *Compilation, llvm_context: llvm.ContextRef) !u32 { + if (await (async base.abi_alignment.start() catch unreachable)) |ptr| return ptr.*; + + base.abi_alignment.data = await (async base.resolveAbiAlignment(comp, llvm_context) catch unreachable); + base.abi_alignment.resolve(); + return base.abi_alignment.data; + } + + /// Lower level function that does the work. See getAbiAlignment. + async fn resolveAbiAlignment(base: *Type, comp: *Compilation, llvm_context: llvm.ContextRef) !u32 { + const llvm_type = try base.getLlvmType(comp.gpa(), llvm_context); + return @intCast(u32, llvm.ABIAlignmentOfType(comp.target_data_ref, llvm_type)); } pub const Struct = struct { @@ -160,10 +211,10 @@ pub const Type = struct { decls: *Scope.Decls, pub fn destroy(self: *Struct, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Struct, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Struct, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -176,28 +227,23 @@ pub const Type = struct { pub const Param = struct { is_noalias: bool, - typeof: *Type, + typ: *Type, }; pub fn create(comp: *Compilation, return_type: *Type, params: []Param, is_var_args: bool) !*Fn { - 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, - }, + const result = try comp.gpa().create(Fn{ + .base = undefined, .return_type = return_type, .params = params, .is_var_args = is_var_args, }); - errdefer comp.a().destroy(result); + errdefer comp.gpa().destroy(result); + + result.base.init(comp, Id.Fn, "TODO fn type name"); result.return_type.base.ref(); for (result.params) |param| { - param.typeof.base.ref(); + param.typ.base.ref(); } return result; } @@ -205,20 +251,20 @@ pub const Type = struct { pub fn destroy(self: *Fn, comp: *Compilation) void { self.return_type.base.deref(comp); for (self.params) |param| { - param.typeof.base.deref(comp); + param.typ.base.deref(comp); } - comp.a().destroy(self); + comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Fn, ofile: *ObjectFile) !llvm.TypeRef { + pub fn getLlvmType(self: *Fn, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef { const llvm_return_type = switch (self.return_type.id) { - Type.Id.Void => llvm.VoidTypeInContext(ofile.context) orelse return error.OutOfMemory, - else => try self.return_type.getLlvmType(ofile), + Type.Id.Void => llvm.VoidTypeInContext(llvm_context) orelse return error.OutOfMemory, + else => try self.return_type.getLlvmType(allocator, llvm_context), }; - const llvm_param_types = try ofile.a().alloc(llvm.TypeRef, self.params.len); - defer ofile.a().free(llvm_param_types); + const llvm_param_types = try allocator.alloc(llvm.TypeRef, self.params.len); + defer allocator.free(llvm_param_types); for (llvm_param_types) |*llvm_param_type, i| { - llvm_param_type.* = try self.params[i].typeof.getLlvmType(ofile); + llvm_param_type.* = try self.params[i].typ.getLlvmType(allocator, llvm_context); } return llvm.FunctionType( @@ -241,7 +287,7 @@ pub const Type = struct { } pub fn destroy(self: *MetaType, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -255,7 +301,7 @@ pub const Type = struct { } pub fn destroy(self: *Void, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -269,10 +315,10 @@ pub const Type = struct { } pub fn destroy(self: *Bool, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Bool, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Bool, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -287,19 +333,89 @@ pub const Type = struct { } pub fn destroy(self: *NoReturn, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; pub const Int = struct { base: Type, + key: Key, + garbage_node: std.atomic.Stack(*Int).Node, - pub fn destroy(self: *Int, comp: *Compilation) void { - comp.a().destroy(self); + pub const Key = struct { + bit_count: u32, + is_signed: bool, + + pub fn hash(self: *const Key) u32 { + const rands = [2]u32{ 0xa4ba6498, 0x75fc5af7 }; + return rands[@boolToInt(self.is_signed)] *% self.bit_count; + } + + pub fn eql(self: *const Key, other: *const Key) bool { + return self.bit_count == other.bit_count and self.is_signed == other.is_signed; + } + }; + + pub fn get_u8(comp: *Compilation) *Int { + comp.u8_type.base.base.ref(); + return comp.u8_type; } - pub fn getLlvmType(self: *Int, ofile: *ObjectFile) llvm.TypeRef { - @panic("TODO"); + pub async fn get(comp: *Compilation, key: Key) !*Int { + { + const held = await (async comp.int_type_table.acquire() catch unreachable); + defer held.release(); + + if (held.value.get(&key)) |entry| { + entry.value.base.base.ref(); + return entry.value; + } + } + + const self = try comp.gpa().create(Int{ + .base = undefined, + .key = key, + .garbage_node = undefined, + }); + errdefer comp.gpa().destroy(self); + + const u_or_i = "ui"[@boolToInt(key.is_signed)]; + const name = try std.fmt.allocPrint(comp.gpa(), "{c}{}", u_or_i, key.bit_count); + errdefer comp.gpa().free(name); + + self.base.init(comp, Id.Int, name); + + { + const held = await (async comp.int_type_table.acquire() catch unreachable); + defer held.release(); + + _ = try held.value.put(&self.key, self); + } + return self; + } + + pub fn destroy(self: *Int, comp: *Compilation) void { + self.garbage_node = std.atomic.Stack(*Int).Node{ + .data = self, + .next = undefined, + }; + comp.registerGarbage(Int, &self.garbage_node); + } + + pub async fn gcDestroy(self: *Int, comp: *Compilation) void { + { + const held = await (async comp.int_type_table.acquire() catch unreachable); + defer held.release(); + + _ = held.value.remove(&self.key).?; + } + // we allocated the name + comp.gpa().free(self.base.name); + comp.gpa().destroy(self); + } + + pub fn getLlvmType(self: *Int, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef { + return llvm.IntTypeInContext(llvm_context, self.key.bit_count) orelse return error.OutOfMemory; } }; @@ -307,59 +423,239 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Float, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Float, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Float, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; pub const Pointer = struct { base: Type, - mut: Mut, - vol: Vol, - size: Size, - alignment: u32, + key: Key, + garbage_node: std.atomic.Stack(*Pointer).Node, + + pub const Key = struct { + child_type: *Type, + mut: Mut, + vol: Vol, + size: Size, + alignment: Align, + + pub fn hash(self: *const Key) u32 { + const align_hash = switch (self.alignment) { + Align.Abi => 0xf201c090, + Align.Override => |x| x, + }; + return hash_usize(@ptrToInt(self.child_type)) *% + hash_enum(self.mut) *% + hash_enum(self.vol) *% + hash_enum(self.size) *% + align_hash; + } + + pub fn eql(self: *const Key, other: *const Key) bool { + if (self.child_type != other.child_type or + self.mut != other.mut or + self.vol != other.vol or + self.size != other.size or + @TagType(Align)(self.alignment) != @TagType(Align)(other.alignment)) + { + return false; + } + switch (self.alignment) { + Align.Abi => return true, + Align.Override => |x| return x == other.alignment.Override, + } + } + }; pub const Mut = enum { Mut, Const, }; + pub const Vol = enum { Non, Volatile, }; + + pub const Align = union(enum) { + Abi, + Override: u32, + }; + pub const Size = builtin.TypeInfo.Pointer.Size; pub fn destroy(self: *Pointer, comp: *Compilation) void { - comp.a().destroy(self); + self.garbage_node = std.atomic.Stack(*Pointer).Node{ + .data = self, + .next = undefined, + }; + comp.registerGarbage(Pointer, &self.garbage_node); } - pub fn get( + pub async fn gcDestroy(self: *Pointer, comp: *Compilation) void { + { + const held = await (async comp.ptr_type_table.acquire() catch unreachable); + defer held.release(); + + _ = held.value.remove(&self.key).?; + } + self.key.child_type.base.deref(comp); + comp.gpa().destroy(self); + } + + pub async fn getAlignAsInt(self: *Pointer, comp: *Compilation) u32 { + switch (self.key.alignment) { + Align.Abi => return await (async self.key.child_type.getAbiAlignment(comp) catch unreachable), + Align.Override => |alignment| return alignment, + } + } + + pub async fn get( comp: *Compilation, - elem_type: *Type, - mut: Mut, - vol: Vol, - size: Size, - alignment: u32, - ) *Pointer { - @panic("TODO get pointer"); + key: Key, + ) !*Pointer { + var normal_key = key; + switch (key.alignment) { + Align.Abi => {}, + Align.Override => |alignment| { + const abi_align = try await (async key.child_type.getAbiAlignment(comp) catch unreachable); + if (abi_align == alignment) { + normal_key.alignment = Align.Abi; + } + }, + } + { + const held = await (async comp.ptr_type_table.acquire() catch unreachable); + defer held.release(); + + if (held.value.get(&normal_key)) |entry| { + entry.value.base.base.ref(); + return entry.value; + } + } + + const self = try comp.gpa().create(Pointer{ + .base = undefined, + .key = normal_key, + .garbage_node = undefined, + }); + errdefer comp.gpa().destroy(self); + + const size_str = switch (self.key.size) { + Size.One => "*", + Size.Many => "[*]", + Size.Slice => "[]", + }; + const mut_str = switch (self.key.mut) { + Mut.Const => "const ", + Mut.Mut => "", + }; + const vol_str = switch (self.key.vol) { + Vol.Volatile => "volatile ", + Vol.Non => "", + }; + const name = switch (self.key.alignment) { + Align.Abi => try std.fmt.allocPrint( + comp.gpa(), + "{}{}{}{}", + size_str, + mut_str, + vol_str, + self.key.child_type.name, + ), + Align.Override => |alignment| try std.fmt.allocPrint( + comp.gpa(), + "{}align<{}> {}{}{}", + size_str, + alignment, + mut_str, + vol_str, + self.key.child_type.name, + ), + }; + errdefer comp.gpa().free(name); + + self.base.init(comp, Id.Pointer, name); + + { + const held = await (async comp.ptr_type_table.acquire() catch unreachable); + defer held.release(); + + _ = try held.value.put(&self.key, self); + } + return self; } - pub fn getLlvmType(self: *Pointer, ofile: *ObjectFile) llvm.TypeRef { - @panic("TODO"); + pub fn getLlvmType(self: *Pointer, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef { + const elem_llvm_type = try self.key.child_type.getLlvmType(allocator, llvm_context); + return llvm.PointerType(elem_llvm_type, 0) orelse return error.OutOfMemory; } }; pub const Array = struct { base: Type, + key: Key, + garbage_node: std.atomic.Stack(*Array).Node, + + pub const Key = struct { + elem_type: *Type, + len: usize, + + pub fn hash(self: *const Key) u32 { + return hash_usize(@ptrToInt(self.elem_type)) *% hash_usize(self.len); + } + + pub fn eql(self: *const Key, other: *const Key) bool { + return self.elem_type == other.elem_type and self.len == other.len; + } + }; pub fn destroy(self: *Array, comp: *Compilation) void { - comp.a().destroy(self); + self.key.elem_type.base.deref(comp); + comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Array, ofile: *ObjectFile) llvm.TypeRef { - @panic("TODO"); + pub async fn get(comp: *Compilation, key: Key) !*Array { + key.elem_type.base.ref(); + errdefer key.elem_type.base.deref(comp); + + { + const held = await (async comp.array_type_table.acquire() catch unreachable); + defer held.release(); + + if (held.value.get(&key)) |entry| { + entry.value.base.base.ref(); + return entry.value; + } + } + + const self = try comp.gpa().create(Array{ + .base = undefined, + .key = key, + .garbage_node = undefined, + }); + errdefer comp.gpa().destroy(self); + + const name = try std.fmt.allocPrint(comp.gpa(), "[{}]{}", key.len, key.elem_type.name); + errdefer comp.gpa().free(name); + + self.base.init(comp, Id.Array, name); + + { + const held = await (async comp.array_type_table.acquire() catch unreachable); + defer held.release(); + + _ = try held.value.put(&self.key, self); + } + return self; + } + + pub fn getLlvmType(self: *Array, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef { + const elem_llvm_type = try self.key.elem_type.getLlvmType(allocator, llvm_context); + return llvm.ArrayType(elem_llvm_type, @intCast(c_uint, self.key.len)) orelse return error.OutOfMemory; } }; @@ -367,15 +663,21 @@ pub const Type = struct { base: Type, pub fn destroy(self: *ComptimeFloat, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; pub const ComptimeInt = struct { base: Type, + /// Adds 1 reference to the resulting type + pub fn get(comp: *Compilation) *ComptimeInt { + comp.comptime_int_type.base.base.ref(); + return comp.comptime_int_type; + } + pub fn destroy(self: *ComptimeInt, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -383,7 +685,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Undefined, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -391,7 +693,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Null, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -399,10 +701,10 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Optional, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Optional, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Optional, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -411,10 +713,10 @@ pub const Type = struct { base: Type, pub fn destroy(self: *ErrorUnion, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } - pub fn getLlvmType(self: *ErrorUnion, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *ErrorUnion, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -423,10 +725,10 @@ pub const Type = struct { base: Type, pub fn destroy(self: *ErrorSet, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } - pub fn getLlvmType(self: *ErrorSet, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *ErrorSet, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -435,10 +737,10 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Enum, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Enum, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Enum, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -447,10 +749,10 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Union, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Union, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Union, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -459,7 +761,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Namespace, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -467,7 +769,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Block, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -475,10 +777,10 @@ pub const Type = struct { base: Type, pub fn destroy(self: *BoundFn, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } - pub fn getLlvmType(self: *BoundFn, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *BoundFn, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -487,7 +789,7 @@ pub const Type = struct { base: Type, pub fn destroy(self: *ArgTuple, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -495,10 +797,10 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Opaque, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Opaque, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Opaque, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -507,11 +809,36 @@ pub const Type = struct { base: Type, pub fn destroy(self: *Promise, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Promise, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Promise, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; }; + +fn hash_usize(x: usize) u32 { + return switch (@sizeOf(usize)) { + 4 => x, + 8 => @truncate(u32, x *% 0xad44ee2d8e3fc13d), + else => @compileError("implement this hash function"), + }; +} + +fn hash_enum(x: var) u32 { + const rands = []u32{ + 0x85ebf64f, + 0x3fcb3211, + 0x240a4e8e, + 0x40bb0e3c, + 0x78be45af, + 0x1ca98e37, + 0xec56053a, + 0x906adc48, + 0xd4fe9763, + 0x54c80dac, + }; + comptime assert(@memberCount(@typeOf(x)) < rands.len); + return rands[@enumToInt(x)]; +} diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index e3b91d2807..2005e3c119 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -4,12 +4,14 @@ const Scope = @import("scope.zig").Scope; const Compilation = @import("compilation.zig").Compilation; const ObjectFile = @import("codegen.zig").ObjectFile; const llvm = @import("llvm.zig"); +const Buffer = std.Buffer; +const assert = std.debug.assert; /// 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, + typ: *Type, ref_count: std.atomic.Int(usize), /// Thread-safe @@ -20,23 +22,37 @@ pub const Value = struct { /// Thread-safe pub fn deref(base: *Value, comp: *Compilation) void { if (base.ref_count.decr() == 1) { - base.typeof.base.deref(comp); + base.typ.base.deref(comp); switch (base.id) { Id.Type => @fieldParentPtr(Type, "base", base).destroy(comp), Id.Fn => @fieldParentPtr(Fn, "base", base).destroy(comp), + Id.FnProto => @fieldParentPtr(FnProto, "base", base).destroy(comp), Id.Void => @fieldParentPtr(Void, "base", base).destroy(comp), Id.Bool => @fieldParentPtr(Bool, "base", base).destroy(comp), Id.NoReturn => @fieldParentPtr(NoReturn, "base", base).destroy(comp), Id.Ptr => @fieldParentPtr(Ptr, "base", base).destroy(comp), + Id.Int => @fieldParentPtr(Int, "base", base).destroy(comp), + Id.Array => @fieldParentPtr(Array, "base", base).destroy(comp), } } } + pub fn setType(base: *Value, new_type: *Type, comp: *Compilation) void { + base.typ.base.deref(comp); + new_type.base.ref(); + base.typ = new_type; + } + pub fn getRef(base: *Value) *Value { base.ref(); return base; } + pub fn cast(base: *Value, comptime T: type) ?*T { + if (base.id != @field(Id, @typeName(T))) return null; + return @fieldParentPtr(T, "base", base); + } + pub fn dump(base: *const Value) void { std.debug.warn("{}", @tagName(base.id)); } @@ -45,30 +61,117 @@ pub const Value = struct { switch (base.id) { Id.Type => unreachable, Id.Fn => @panic("TODO"), + Id.FnProto => return @fieldParentPtr(FnProto, "base", base).getLlvmConst(ofile), Id.Void => return null, Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmConst(ofile), Id.NoReturn => unreachable, - Id.Ptr => @panic("TODO"), + Id.Ptr => return @fieldParentPtr(Ptr, "base", base).getLlvmConst(ofile), + Id.Int => return @fieldParentPtr(Int, "base", base).getLlvmConst(ofile), + Id.Array => return @fieldParentPtr(Array, "base", base).getLlvmConst(ofile), } } + pub fn derefAndCopy(self: *Value, comp: *Compilation) (error{OutOfMemory}!*Value) { + if (self.ref_count.get() == 1) { + // ( ͡° ͜ʖ ͡°) + return self; + } + + assert(self.ref_count.decr() != 1); + return self.copy(comp); + } + + pub fn copy(base: *Value, comp: *Compilation) (error{OutOfMemory}!*Value) { + switch (base.id) { + Id.Type => unreachable, + Id.Fn => unreachable, + Id.FnProto => unreachable, + Id.Void => unreachable, + Id.Bool => unreachable, + Id.NoReturn => unreachable, + Id.Ptr => unreachable, + Id.Array => unreachable, + Id.Int => return &(try @fieldParentPtr(Int, "base", base).copy(comp)).base, + } + } + + pub const Parent = union(enum) { + None, + BaseStruct: BaseStruct, + BaseArray: BaseArray, + BaseUnion: *Value, + BaseScalar: *Value, + + pub const BaseStruct = struct { + val: *Value, + field_index: usize, + }; + + pub const BaseArray = struct { + val: *Value, + elem_index: usize, + }; + }; + pub const Id = enum { Type, Fn, Void, Bool, NoReturn, + Array, Ptr, + Int, + FnProto, }; pub const Type = @import("type.zig").Type; + pub const FnProto = struct { + base: Value, + + /// The main external name that is used in the .o file. + /// TODO https://github.com/ziglang/zig/issues/265 + symbol_name: Buffer, + + pub fn create(comp: *Compilation, fn_type: *Type.Fn, symbol_name: Buffer) !*FnProto { + const self = try comp.gpa().create(FnProto{ + .base = Value{ + .id = Value.Id.FnProto, + .typ = &fn_type.base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .symbol_name = symbol_name, + }); + fn_type.base.base.ref(); + return self; + } + + pub fn destroy(self: *FnProto, comp: *Compilation) void { + self.symbol_name.deinit(); + comp.gpa().destroy(self); + } + + pub fn getLlvmConst(self: *FnProto, ofile: *ObjectFile) !?llvm.ValueRef { + const llvm_fn_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context); + const llvm_fn = llvm.AddFunction( + ofile.module, + self.symbol_name.ptr(), + llvm_fn_type, + ) orelse return error.OutOfMemory; + + // TODO port more logic from codegen.cpp:fn_llvm_value + + return llvm_fn; + } + }; + pub const Fn = struct { base: Value, /// The main external name that is used in the .o file. /// TODO https://github.com/ziglang/zig/issues/265 - symbol_name: std.Buffer, + symbol_name: Buffer, /// parent should be the top level decls or container decls fndef_scope: *Scope.FnDef, @@ -79,19 +182,33 @@ pub const Value = struct { /// parent is child_scope block_scope: *Scope.Block, + /// Path to the object file that contains this function + containing_object: Buffer, + + link_set_node: *std.LinkedList(?*Value.Fn).Node, + /// Creates a Fn value with 1 ref /// Takes ownership of symbol_name - pub fn create(comp: *Compilation, fn_type: *Type.Fn, fndef_scope: *Scope.FnDef, symbol_name: std.Buffer) !*Fn { - const self = try comp.a().create(Fn{ + pub fn create(comp: *Compilation, fn_type: *Type.Fn, fndef_scope: *Scope.FnDef, symbol_name: Buffer) !*Fn { + const link_set_node = try comp.gpa().create(Compilation.FnLinkSet.Node{ + .data = null, + .next = undefined, + .prev = undefined, + }); + errdefer comp.gpa().destroy(link_set_node); + + const self = try comp.gpa().create(Fn{ .base = Value{ .id = Value.Id.Fn, - .typeof = &fn_type.base, + .typ = &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, + .containing_object = Buffer.initNull(comp.gpa()), + .link_set_node = link_set_node, }); fn_type.base.base.ref(); fndef_scope.fn_val = self; @@ -100,9 +217,19 @@ pub const Value = struct { } pub fn destroy(self: *Fn, comp: *Compilation) void { + // remove with a tombstone so that we do not have to grab a lock + if (self.link_set_node.data != null) { + // it's now the job of the link step to find this tombstone and + // deallocate it. + self.link_set_node.data = null; + } else { + comp.gpa().destroy(self.link_set_node); + } + + self.containing_object.deinit(); self.fndef_scope.base.deref(comp); self.symbol_name.deinit(); - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -115,7 +242,7 @@ pub const Value = struct { } pub fn destroy(self: *Void, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; @@ -134,7 +261,7 @@ pub const Value = struct { } pub fn destroy(self: *Bool, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } pub fn getLlvmConst(self: *Bool, ofile: *ObjectFile) ?llvm.ValueRef { @@ -156,12 +283,14 @@ pub const Value = struct { } pub fn destroy(self: *NoReturn, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); } }; pub const Ptr = struct { base: Value, + special: Special, + mut: Mut, pub const Mut = enum { CompTimeConst, @@ -169,8 +298,268 @@ pub const Value = struct { RunTime, }; + pub const Special = union(enum) { + Scalar: *Value, + BaseArray: BaseArray, + BaseStruct: BaseStruct, + HardCodedAddr: u64, + Discard, + }; + + pub const BaseArray = struct { + val: *Value, + elem_index: usize, + }; + + pub const BaseStruct = struct { + val: *Value, + field_index: usize, + }; + + pub async fn createArrayElemPtr( + comp: *Compilation, + array_val: *Array, + mut: Type.Pointer.Mut, + size: Type.Pointer.Size, + elem_index: usize, + ) !*Ptr { + array_val.base.ref(); + errdefer array_val.base.deref(comp); + + const elem_type = array_val.base.typ.cast(Type.Array).?.key.elem_type; + const ptr_type = try await (async Type.Pointer.get(comp, Type.Pointer.Key{ + .child_type = elem_type, + .mut = mut, + .vol = Type.Pointer.Vol.Non, + .size = size, + .alignment = Type.Pointer.Align.Abi, + }) catch unreachable); + var ptr_type_consumed = false; + errdefer if (!ptr_type_consumed) ptr_type.base.base.deref(comp); + + const self = try comp.gpa().create(Value.Ptr{ + .base = Value{ + .id = Value.Id.Ptr, + .typ = &ptr_type.base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .special = Special{ + .BaseArray = BaseArray{ + .val = &array_val.base, + .elem_index = 0, + }, + }, + .mut = Mut.CompTimeConst, + }); + ptr_type_consumed = true; + errdefer comp.gpa().destroy(self); + + return self; + } + pub fn destroy(self: *Ptr, comp: *Compilation) void { - comp.a().destroy(self); + comp.gpa().destroy(self); + } + + pub fn getLlvmConst(self: *Ptr, ofile: *ObjectFile) !?llvm.ValueRef { + const llvm_type = self.base.typ.getLlvmType(ofile.arena, ofile.context); + // TODO carefully port the logic from codegen.cpp:gen_const_val_ptr + switch (self.special) { + Special.Scalar => |scalar| @panic("TODO"), + Special.BaseArray => |base_array| { + // TODO put this in one .o file only, and after that, generate extern references to it + const array_llvm_value = (try base_array.val.getLlvmConst(ofile)).?; + const ptr_bit_count = ofile.comp.target_ptr_bits; + const usize_llvm_type = llvm.IntTypeInContext(ofile.context, ptr_bit_count) orelse return error.OutOfMemory; + const indices = []llvm.ValueRef{ + llvm.ConstNull(usize_llvm_type) orelse return error.OutOfMemory, + llvm.ConstInt(usize_llvm_type, base_array.elem_index, 0) orelse return error.OutOfMemory, + }; + return llvm.ConstInBoundsGEP( + array_llvm_value, + &indices, + @intCast(c_uint, indices.len), + ) orelse return error.OutOfMemory; + }, + Special.BaseStruct => |base_struct| @panic("TODO"), + Special.HardCodedAddr => |addr| @panic("TODO"), + Special.Discard => unreachable, + } + } + }; + + pub const Array = struct { + base: Value, + special: Special, + + pub const Special = union(enum) { + Undefined, + OwnedBuffer: []u8, + Explicit: Data, + }; + + pub const Data = struct { + parent: Parent, + elements: []*Value, + }; + + /// Takes ownership of buffer + pub async fn createOwnedBuffer(comp: *Compilation, buffer: []u8) !*Array { + const u8_type = Type.Int.get_u8(comp); + defer u8_type.base.base.deref(comp); + + const array_type = try await (async Type.Array.get(comp, Type.Array.Key{ + .elem_type = &u8_type.base, + .len = buffer.len, + }) catch unreachable); + errdefer array_type.base.base.deref(comp); + + const self = try comp.gpa().create(Value.Array{ + .base = Value{ + .id = Value.Id.Array, + .typ = &array_type.base, + .ref_count = std.atomic.Int(usize).init(1), + }, + .special = Special{ .OwnedBuffer = buffer }, + }); + errdefer comp.gpa().destroy(self); + + return self; + } + + pub fn destroy(self: *Array, comp: *Compilation) void { + switch (self.special) { + Special.Undefined => {}, + Special.OwnedBuffer => |buf| { + comp.gpa().free(buf); + }, + Special.Explicit => {}, + } + comp.gpa().destroy(self); + } + + pub fn getLlvmConst(self: *Array, ofile: *ObjectFile) !?llvm.ValueRef { + switch (self.special) { + Special.Undefined => { + const llvm_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context); + return llvm.GetUndef(llvm_type); + }, + Special.OwnedBuffer => |buf| { + const dont_null_terminate = 1; + const llvm_str_init = llvm.ConstStringInContext( + ofile.context, + buf.ptr, + @intCast(c_uint, buf.len), + dont_null_terminate, + ) orelse return error.OutOfMemory; + const str_init_type = llvm.TypeOf(llvm_str_init); + const global = llvm.AddGlobal(ofile.module, str_init_type, c"") orelse return error.OutOfMemory; + llvm.SetInitializer(global, llvm_str_init); + llvm.SetLinkage(global, llvm.PrivateLinkage); + llvm.SetGlobalConstant(global, 1); + llvm.SetUnnamedAddr(global, 1); + llvm.SetAlignment(global, llvm.ABIAlignmentOfType(ofile.comp.target_data_ref, str_init_type)); + return global; + }, + Special.Explicit => @panic("TODO"), + } + + //{ + // uint64_t len = type_entry->data.array.len; + // if (const_val->data.x_array.special == ConstArraySpecialUndef) { + // return LLVMGetUndef(type_entry->type_ref); + // } + + // LLVMValueRef *values = allocate(len); + // LLVMTypeRef element_type_ref = type_entry->data.array.child_type->type_ref; + // bool make_unnamed_struct = false; + // for (uint64_t i = 0; i < len; i += 1) { + // ConstExprValue *elem_value = &const_val->data.x_array.s_none.elements[i]; + // LLVMValueRef val = gen_const_val(g, elem_value, ""); + // values[i] = val; + // make_unnamed_struct = make_unnamed_struct || is_llvm_value_unnamed_type(elem_value->type, val); + // } + // if (make_unnamed_struct) { + // return LLVMConstStruct(values, len, true); + // } else { + // return LLVMConstArray(element_type_ref, values, (unsigned)len); + // } + //} + } + }; + + pub const Int = struct { + base: Value, + big_int: std.math.big.Int, + + pub fn createFromString(comp: *Compilation, typ: *Type, base: u8, value: []const u8) !*Int { + const self = try comp.gpa().create(Value.Int{ + .base = Value{ + .id = Value.Id.Int, + .typ = typ, + .ref_count = std.atomic.Int(usize).init(1), + }, + .big_int = undefined, + }); + typ.base.ref(); + errdefer comp.gpa().destroy(self); + + self.big_int = try std.math.big.Int.init(comp.gpa()); + errdefer self.big_int.deinit(); + + try self.big_int.setString(base, value); + + return self; + } + + pub fn getLlvmConst(self: *Int, ofile: *ObjectFile) !?llvm.ValueRef { + switch (self.base.typ.id) { + Type.Id.Int => { + const type_ref = try self.base.typ.getLlvmType(ofile.arena, ofile.context); + if (self.big_int.len == 0) { + return llvm.ConstNull(type_ref); + } + const unsigned_val = if (self.big_int.len == 1) blk: { + break :blk llvm.ConstInt(type_ref, self.big_int.limbs[0], @boolToInt(false)); + } else if (@sizeOf(std.math.big.Limb) == @sizeOf(u64)) blk: { + break :blk llvm.ConstIntOfArbitraryPrecision( + type_ref, + @intCast(c_uint, self.big_int.len), + @ptrCast([*]u64, self.big_int.limbs.ptr), + ); + } else { + @compileError("std.math.Big.Int.Limb size does not match LLVM"); + }; + return if (self.big_int.positive) unsigned_val else llvm.ConstNeg(unsigned_val); + }, + Type.Id.ComptimeInt => unreachable, + else => unreachable, + } + } + + pub fn copy(old: *Int, comp: *Compilation) !*Int { + old.base.typ.base.ref(); + errdefer old.base.typ.base.deref(comp); + + const new = try comp.gpa().create(Value.Int{ + .base = Value{ + .id = Value.Id.Int, + .typ = old.base.typ, + .ref_count = std.atomic.Int(usize).init(1), + }, + .big_int = undefined, + }); + errdefer comp.gpa().destroy(new); + + new.big_int = try old.big_int.clone(); + errdefer new.big_int.deinit(); + + return new; + } + + pub fn destroy(self: *Int, comp: *Compilation) void { + self.big_int.deinit(); + comp.gpa().destroy(self); } }; }; diff --git a/src/analyze.cpp b/src/analyze.cpp index 2ace893508..6bbe5f6037 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -1454,7 +1454,9 @@ static bool type_allowed_in_extern(CodeGen *g, TypeTableEntry *type_entry) { case TypeTableEntryIdFn: return type_entry->data.fn.fn_type_id.cc == CallingConventionC; case TypeTableEntryIdPointer: - return type_allowed_in_extern(g, type_entry->data.pointer.child_type); + if (type_size(g, type_entry) == 0) + return false; + return true; case TypeTableEntryIdStruct: return type_entry->data.structure.layout == ContainerLayoutExtern || type_entry->data.structure.layout == ContainerLayoutPacked; case TypeTableEntryIdOptional: @@ -4377,7 +4379,7 @@ bool handle_is_ptr(TypeTableEntry *type_entry) { static ZigWindowsSDK *get_windows_sdk(CodeGen *g) { if (g->win_sdk == nullptr) { - if (os_find_windows_sdk(&g->win_sdk)) { + if (zig_find_windows_sdk(&g->win_sdk)) { fprintf(stderr, "unable to determine windows sdk path\n"); exit(1); } @@ -4497,12 +4499,11 @@ void find_libc_lib_path(CodeGen *g) { ZigWindowsSDK *sdk = get_windows_sdk(g); if (g->msvc_lib_dir == nullptr) { - Buf* vc_lib_dir = buf_alloc(); - if (os_get_win32_vcruntime_path(vc_lib_dir, g->zig_target.arch.arch)) { + if (sdk->msvc_lib_dir_ptr == nullptr) { fprintf(stderr, "Unable to determine vcruntime path. --msvc-lib-dir"); exit(1); } - g->msvc_lib_dir = vc_lib_dir; + g->msvc_lib_dir = buf_create_from_mem(sdk->msvc_lib_dir_ptr, sdk->msvc_lib_dir_len); } if (g->libc_lib_dir == nullptr) { diff --git a/src/codegen.cpp b/src/codegen.cpp index 6cb89e6f7e..c288af4273 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -60,6 +60,33 @@ PackageTableEntry *new_anonymous_package(void) { return new_package("", ""); } +static const char *symbols_that_llvm_depends_on[] = { + "memcpy", + "memset", + "sqrt", + "powi", + "sin", + "cos", + "pow", + "exp", + "exp2", + "log", + "log10", + "log2", + "fma", + "fabs", + "minnum", + "maxnum", + "copysign", + "floor", + "ceil", + "trunc", + "rint", + "nearbyint", + "round", + // TODO probably all of compiler-rt needs to go here +}; + CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out_type, BuildMode build_mode, Buf *zig_lib_dir) { @@ -94,6 +121,10 @@ CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out g->want_h_file = (out_type == OutTypeObj || out_type == OutTypeLib); buf_resize(&g->global_asm, 0); + for (size_t i = 0; i < array_length(symbols_that_llvm_depends_on); i += 1) { + g->external_prototypes.put(buf_create_from_str(symbols_that_llvm_depends_on[i]), nullptr); + } + if (root_src_path) { Buf *src_basename = buf_alloc(); Buf *src_dir = buf_alloc(); @@ -7419,51 +7450,60 @@ static void gen_h_file(CodeGen *g) { case TypeTableEntryIdPromise: zig_unreachable(); case TypeTableEntryIdEnum: - assert(type_entry->data.enumeration.layout == ContainerLayoutExtern); - fprintf(out_h, "enum %s {\n", buf_ptr(&type_entry->name)); - for (uint32_t field_i = 0; field_i < type_entry->data.enumeration.src_field_count; field_i += 1) { - TypeEnumField *enum_field = &type_entry->data.enumeration.fields[field_i]; - Buf *value_buf = buf_alloc(); - bigint_append_buf(value_buf, &enum_field->value, 10); - fprintf(out_h, " %s = %s", buf_ptr(enum_field->name), buf_ptr(value_buf)); - if (field_i != type_entry->data.enumeration.src_field_count - 1) { - fprintf(out_h, ","); + if (type_entry->data.enumeration.layout == ContainerLayoutExtern) { + fprintf(out_h, "enum %s {\n", buf_ptr(&type_entry->name)); + for (uint32_t field_i = 0; field_i < type_entry->data.enumeration.src_field_count; field_i += 1) { + TypeEnumField *enum_field = &type_entry->data.enumeration.fields[field_i]; + Buf *value_buf = buf_alloc(); + bigint_append_buf(value_buf, &enum_field->value, 10); + fprintf(out_h, " %s = %s", buf_ptr(enum_field->name), buf_ptr(value_buf)); + if (field_i != type_entry->data.enumeration.src_field_count - 1) { + fprintf(out_h, ","); + } + fprintf(out_h, "\n"); } - fprintf(out_h, "\n"); + fprintf(out_h, "};\n\n"); + } else { + fprintf(out_h, "enum %s;\n", buf_ptr(&type_entry->name)); } - fprintf(out_h, "};\n\n"); break; case TypeTableEntryIdStruct: - assert(type_entry->data.structure.layout == ContainerLayoutExtern); - fprintf(out_h, "struct %s {\n", buf_ptr(&type_entry->name)); - for (uint32_t field_i = 0; field_i < type_entry->data.structure.src_field_count; field_i += 1) { - TypeStructField *struct_field = &type_entry->data.structure.fields[field_i]; + if (type_entry->data.structure.layout == ContainerLayoutExtern) { + fprintf(out_h, "struct %s {\n", buf_ptr(&type_entry->name)); + for (uint32_t field_i = 0; field_i < type_entry->data.structure.src_field_count; field_i += 1) { + TypeStructField *struct_field = &type_entry->data.structure.fields[field_i]; - Buf *type_name_buf = buf_alloc(); - get_c_type(g, gen_h, struct_field->type_entry, type_name_buf); + Buf *type_name_buf = buf_alloc(); + get_c_type(g, gen_h, struct_field->type_entry, type_name_buf); + + if (struct_field->type_entry->id == TypeTableEntryIdArray) { + fprintf(out_h, " %s %s[%" ZIG_PRI_u64 "];\n", buf_ptr(type_name_buf), + buf_ptr(struct_field->name), + struct_field->type_entry->data.array.len); + } else { + fprintf(out_h, " %s %s;\n", buf_ptr(type_name_buf), buf_ptr(struct_field->name)); + } - if (struct_field->type_entry->id == TypeTableEntryIdArray) { - fprintf(out_h, " %s %s[%" ZIG_PRI_u64 "];\n", buf_ptr(type_name_buf), - buf_ptr(struct_field->name), - struct_field->type_entry->data.array.len); - } else { - fprintf(out_h, " %s %s;\n", buf_ptr(type_name_buf), buf_ptr(struct_field->name)); } - + fprintf(out_h, "};\n\n"); + } else { + fprintf(out_h, "struct %s;\n", buf_ptr(&type_entry->name)); } - fprintf(out_h, "};\n\n"); break; case TypeTableEntryIdUnion: - assert(type_entry->data.unionation.layout == ContainerLayoutExtern); - fprintf(out_h, "union %s {\n", buf_ptr(&type_entry->name)); - for (uint32_t field_i = 0; field_i < type_entry->data.unionation.src_field_count; field_i += 1) { - TypeUnionField *union_field = &type_entry->data.unionation.fields[field_i]; + if (type_entry->data.unionation.layout == ContainerLayoutExtern) { + fprintf(out_h, "union %s {\n", buf_ptr(&type_entry->name)); + for (uint32_t field_i = 0; field_i < type_entry->data.unionation.src_field_count; field_i += 1) { + TypeUnionField *union_field = &type_entry->data.unionation.fields[field_i]; - Buf *type_name_buf = buf_alloc(); - get_c_type(g, gen_h, union_field->type_entry, type_name_buf); - fprintf(out_h, " %s %s;\n", buf_ptr(type_name_buf), buf_ptr(union_field->name)); + Buf *type_name_buf = buf_alloc(); + get_c_type(g, gen_h, union_field->type_entry, type_name_buf); + fprintf(out_h, " %s %s;\n", buf_ptr(type_name_buf), buf_ptr(union_field->name)); + } + fprintf(out_h, "};\n\n"); + } else { + fprintf(out_h, "union %s;\n", buf_ptr(&type_entry->name)); } - fprintf(out_h, "};\n\n"); break; case TypeTableEntryIdOpaque: fprintf(out_h, "struct %s;\n\n", buf_ptr(&type_entry->name)); diff --git a/src/ir.cpp b/src/ir.cpp index 0804134d2a..fe5fb77085 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -246,6 +246,8 @@ 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) { @@ -2959,16 +2961,34 @@ static void ir_count_defers(IrBuilder *irb, Scope *inner_scope, Scope *outer_sco results[ReturnKindUnconditional] = 0; results[ReturnKindError] = 0; - while (inner_scope != outer_scope) { - assert(inner_scope); - if (inner_scope->id == ScopeIdDefer) { - AstNode *defer_node = inner_scope->source_node; - assert(defer_node->type == NodeTypeDefer); - ReturnKind defer_kind = defer_node->data.defer.kind; - results[defer_kind] += 1; + Scope *scope = inner_scope; + while (scope != outer_scope) { + assert(scope); + switch (scope->id) { + case ScopeIdDefer: { + AstNode *defer_node = scope->source_node; + assert(defer_node->type == NodeTypeDefer); + ReturnKind defer_kind = defer_node->data.defer.kind; + results[defer_kind] += 1; + scope = scope->parent; + continue; + } + case ScopeIdDecls: + case ScopeIdFnDef: + return; + case ScopeIdBlock: + case ScopeIdVarDecl: + case ScopeIdLoop: + case ScopeIdSuspend: + case ScopeIdCompTime: + scope = scope->parent; + continue; + case ScopeIdDeferExpr: + case ScopeIdCImport: + case ScopeIdCoroPrelude: + zig_unreachable(); } - inner_scope = inner_scope->parent; } } @@ -2984,27 +3004,43 @@ static bool ir_gen_defers_for_block(IrBuilder *irb, Scope *inner_scope, Scope *o if (!scope) return is_noreturn; - if (scope->id == ScopeIdDefer) { - AstNode *defer_node = scope->source_node; - assert(defer_node->type == NodeTypeDefer); - ReturnKind defer_kind = defer_node->data.defer.kind; - if (defer_kind == ReturnKindUnconditional || - (gen_error_defers && defer_kind == ReturnKindError)) - { - AstNode *defer_expr_node = defer_node->data.defer.expr; - Scope *defer_expr_scope = defer_node->data.defer.expr_scope; - IrInstruction *defer_expr_value = ir_gen_node(irb, defer_expr_node, defer_expr_scope); - if (defer_expr_value != irb->codegen->invalid_instruction) { - if (defer_expr_value->value.type != nullptr && defer_expr_value->value.type->id == TypeTableEntryIdUnreachable) { - is_noreturn = true; - } else { - ir_mark_gen(ir_build_check_statement_is_void(irb, defer_expr_scope, defer_expr_node, defer_expr_value)); + switch (scope->id) { + case ScopeIdDefer: { + AstNode *defer_node = scope->source_node; + assert(defer_node->type == NodeTypeDefer); + ReturnKind defer_kind = defer_node->data.defer.kind; + if (defer_kind == ReturnKindUnconditional || + (gen_error_defers && defer_kind == ReturnKindError)) + { + AstNode *defer_expr_node = defer_node->data.defer.expr; + Scope *defer_expr_scope = defer_node->data.defer.expr_scope; + IrInstruction *defer_expr_value = ir_gen_node(irb, defer_expr_node, defer_expr_scope); + if (defer_expr_value != irb->codegen->invalid_instruction) { + if (defer_expr_value->value.type != nullptr && defer_expr_value->value.type->id == TypeTableEntryIdUnreachable) { + is_noreturn = true; + } else { + ir_mark_gen(ir_build_check_statement_is_void(irb, defer_expr_scope, defer_expr_node, defer_expr_value)); + } } } + scope = scope->parent; + continue; } - + case ScopeIdDecls: + case ScopeIdFnDef: + return is_noreturn; + case ScopeIdBlock: + case ScopeIdVarDecl: + case ScopeIdLoop: + case ScopeIdSuspend: + case ScopeIdCompTime: + scope = scope->parent; + continue; + case ScopeIdDeferExpr: + case ScopeIdCImport: + case ScopeIdCoroPrelude: + zig_unreachable(); } - scope = scope->parent; } return is_noreturn; } @@ -9408,7 +9444,7 @@ static IrInstruction *ir_analyze_maybe_wrap(IrAnalyze *ira, IrInstruction *sourc if (type_is_invalid(casted_payload->value.type)) return ira->codegen->invalid_instruction; - ConstExprValue *val = ir_resolve_const(ira, casted_payload, UndefBad); + ConstExprValue *val = ir_resolve_const(ira, casted_payload, UndefOk); if (!val) return ira->codegen->invalid_instruction; @@ -13090,6 +13126,7 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal impl_fn->ir_executable.parent_exec = ira->new_irb.exec; impl_fn->analyzed_executable.source_node = call_instruction->base.source_node; impl_fn->analyzed_executable.parent_exec = ira->new_irb.exec; + impl_fn->analyzed_executable.backward_branch_quota = ira->new_irb.exec->backward_branch_quota; impl_fn->analyzed_executable.is_generic_instantiation = true; ira->codegen->fn_defs.append(impl_fn); diff --git a/src/link.cpp b/src/link.cpp index 2d9a79585f..f65c072bac 100644 --- a/src/link.cpp +++ b/src/link.cpp @@ -901,7 +901,7 @@ static void construct_linker_job_macho(LinkJob *lj) { if (strchr(buf_ptr(link_lib->name), '/') == nullptr) { Buf *arg = buf_sprintf("-l%s", buf_ptr(link_lib->name)); lj->args.append(buf_ptr(arg)); - } else { + } else { lj->args.append(buf_ptr(link_lib->name)); } } diff --git a/src/os.cpp b/src/os.cpp index d52295950d..91a591a7b6 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -26,7 +26,6 @@ #include #include #include -#include "windows_com.hpp" typedef SSIZE_T ssize_t; #else @@ -1115,249 +1114,10 @@ void os_stderr_set_color(TermColor color) { #endif } -int os_find_windows_sdk(ZigWindowsSDK **out_sdk) { -#if defined(ZIG_OS_WINDOWS) - ZigWindowsSDK *result_sdk = allocate(1); - buf_resize(&result_sdk->path10, 0); - buf_resize(&result_sdk->path81, 0); - - HKEY key; - HRESULT rc; - rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY | KEY_ENUMERATE_SUB_KEYS, &key); - if (rc != ERROR_SUCCESS) { - return ErrorFileNotFound; - } - - { - DWORD tmp_buf_len = MAX_PATH; - buf_resize(&result_sdk->path10, tmp_buf_len); - rc = RegQueryValueEx(key, "KitsRoot10", NULL, NULL, (LPBYTE)buf_ptr(&result_sdk->path10), &tmp_buf_len); - if (rc == ERROR_FILE_NOT_FOUND) { - buf_resize(&result_sdk->path10, 0); - } else { - buf_resize(&result_sdk->path10, tmp_buf_len); - } - } - { - DWORD tmp_buf_len = MAX_PATH; - buf_resize(&result_sdk->path81, tmp_buf_len); - rc = RegQueryValueEx(key, "KitsRoot81", NULL, NULL, (LPBYTE)buf_ptr(&result_sdk->path81), &tmp_buf_len); - if (rc == ERROR_FILE_NOT_FOUND) { - buf_resize(&result_sdk->path81, 0); - } else { - buf_resize(&result_sdk->path81, tmp_buf_len); - } - } - - if (buf_len(&result_sdk->path10) != 0) { - Buf *sdk_lib_dir = buf_sprintf("%s\\Lib\\*", buf_ptr(&result_sdk->path10)); - - // enumerate files in sdk path looking for latest version - WIN32_FIND_DATA ffd; - HANDLE hFind = FindFirstFileA(buf_ptr(sdk_lib_dir), &ffd); - if (hFind == INVALID_HANDLE_VALUE) { - return ErrorFileNotFound; - } - int v0 = 0, v1 = 0, v2 = 0, v3 = 0; - bool found_version_dir = false; - for (;;) { - if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - int c0 = 0, c1 = 0, c2 = 0, c3 = 0; - sscanf(ffd.cFileName, "%d.%d.%d.%d", &c0, &c1, &c2, &c3); - if (c0 == 10 && c1 == 0 && c2 == 10240 && c3 == 0) { - // Microsoft released 26624 as 10240 accidentally. - // https://developer.microsoft.com/en-us/windows/downloads/sdk-archive - c2 = 26624; - } - if ((c0 > v0) || (c1 > v1) || (c2 > v2) || (c3 > v3)) { - v0 = c0, v1 = c1, v2 = c2, v3 = c3; - buf_init_from_str(&result_sdk->version10, ffd.cFileName); - found_version_dir = true; - } - } - if (FindNextFile(hFind, &ffd) == 0) { - FindClose(hFind); - break; - } - } - if (!found_version_dir) { - buf_resize(&result_sdk->path10, 0); - } - } - - if (buf_len(&result_sdk->path81) != 0) { - Buf *sdk_lib_dir = buf_sprintf("%s\\Lib\\winv*", buf_ptr(&result_sdk->path81)); - - // enumerate files in sdk path looking for latest version - WIN32_FIND_DATA ffd; - HANDLE hFind = FindFirstFileA(buf_ptr(sdk_lib_dir), &ffd); - if (hFind == INVALID_HANDLE_VALUE) { - return ErrorFileNotFound; - } - int v0 = 0, v1 = 0; - bool found_version_dir = false; - for (;;) { - if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - int c0 = 0, c1 = 0; - sscanf(ffd.cFileName, "winv%d.%d", &c0, &c1); - if ((c0 > v0) || (c1 > v1)) { - v0 = c0, v1 = c1; - buf_init_from_str(&result_sdk->version81, ffd.cFileName); - found_version_dir = true; - } - } - if (FindNextFile(hFind, &ffd) == 0) { - FindClose(hFind); - break; - } - } - if (!found_version_dir) { - buf_resize(&result_sdk->path81, 0); - } - } - - *out_sdk = result_sdk; - return 0; -#else - return ErrorFileNotFound; -#endif -} - -int os_get_win32_vcruntime_path(Buf* output_buf, ZigLLVM_ArchType platform_type) { -#if defined(ZIG_OS_WINDOWS) - buf_resize(output_buf, 0); - //COM Smart Pointerse requires explicit scope - { - HRESULT rc; - rc = CoInitializeEx(NULL, COINIT_MULTITHREADED); - if (rc != S_OK) { - goto com_done; - } - - //This COM class is installed when a VS2017 - ISetupConfigurationPtr setup_config; - rc = setup_config.CreateInstance(__uuidof(SetupConfiguration)); - if (rc != S_OK) { - goto com_done; - } - - IEnumSetupInstancesPtr all_instances; - rc = setup_config->EnumInstances(&all_instances); - if (rc != S_OK) { - goto com_done; - } - - ISetupInstance* curr_instance; - ULONG found_inst; - while ((rc = all_instances->Next(1, &curr_instance, &found_inst) == S_OK)) { - BSTR bstr_inst_path; - rc = curr_instance->GetInstallationPath(&bstr_inst_path); - if (rc != S_OK) { - goto com_done; - } - //BSTRs are UTF-16 encoded, so we need to convert the string & adjust the length - UINT bstr_path_len = *((UINT*)bstr_inst_path - 1); - ULONG tmp_path_len = bstr_path_len / 2 + 1; - char* conv_path = (char*)bstr_inst_path; - char *tmp_path = (char*)alloca(tmp_path_len); - memset(tmp_path, 0, tmp_path_len); - uint32_t c = 0; - for (uint32_t i = 0; i < bstr_path_len; i += 2) { - tmp_path[c] = conv_path[i]; - ++c; - assert(c != tmp_path_len); - } - - buf_append_str(output_buf, tmp_path); - buf_append_char(output_buf, '\\'); - - Buf* tmp_buf = buf_alloc(); - buf_append_buf(tmp_buf, output_buf); - buf_append_str(tmp_buf, "VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"); - FILE* tools_file = fopen(buf_ptr(tmp_buf), "r"); - if (!tools_file) { - goto com_done; - } - memset(tmp_path, 0, tmp_path_len); - fgets(tmp_path, tmp_path_len, tools_file); - strtok(tmp_path, " \r\n"); - fclose(tools_file); - buf_appendf(output_buf, "VC\\Tools\\MSVC\\%s\\lib\\", tmp_path); - switch (platform_type) { - case ZigLLVM_x86: - buf_append_str(output_buf, "x86\\"); - break; - case ZigLLVM_x86_64: - buf_append_str(output_buf, "x64\\"); - break; - case ZigLLVM_arm: - buf_append_str(output_buf, "arm\\"); - break; - default: - zig_panic("Attemped to use vcruntime for non-supported platform."); - } - buf_resize(tmp_buf, 0); - buf_append_buf(tmp_buf, output_buf); - buf_append_str(tmp_buf, "vcruntime.lib"); - - if (GetFileAttributesA(buf_ptr(tmp_buf)) != INVALID_FILE_ATTRIBUTES) { - return 0; - } - } - } - -com_done:; - HKEY key; - HRESULT rc; - rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &key); - if (rc != ERROR_SUCCESS) { - return ErrorFileNotFound; - } - - DWORD dw_type = 0; - DWORD cb_data = 0; - rc = RegQueryValueEx(key, "14.0", NULL, &dw_type, NULL, &cb_data); - if ((rc == ERROR_FILE_NOT_FOUND) || (REG_SZ != dw_type)) { - return ErrorFileNotFound; - } - - Buf* tmp_buf = buf_alloc_fixed(cb_data); - RegQueryValueExA(key, "14.0", NULL, NULL, (LPBYTE)buf_ptr(tmp_buf), &cb_data); - //RegQueryValueExA returns the length of the string INCLUDING the null terminator - buf_resize(tmp_buf, cb_data-1); - buf_append_str(tmp_buf, "VC\\Lib\\"); - switch (platform_type) { - case ZigLLVM_x86: - //x86 is in the root of the Lib folder - break; - case ZigLLVM_x86_64: - buf_append_str(tmp_buf, "amd64\\"); - break; - case ZigLLVM_arm: - buf_append_str(tmp_buf, "arm\\"); - break; - default: - zig_panic("Attemped to use vcruntime for non-supported platform."); - } - - buf_append_buf(output_buf, tmp_buf); - buf_append_str(tmp_buf, "vcruntime.lib"); - - if (GetFileAttributesA(buf_ptr(tmp_buf)) != INVALID_FILE_ATTRIBUTES) { - return 0; - } else { - buf_resize(output_buf, 0); - return ErrorFileNotFound; - } -#else - return ErrorFileNotFound; -#endif -} - int os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_ArchType platform_type) { #if defined(ZIG_OS_WINDOWS) buf_resize(output_buf, 0); - buf_appendf(output_buf, "%s\\Lib\\%s\\ucrt\\", buf_ptr(&sdk->path10), buf_ptr(&sdk->version10)); + buf_appendf(output_buf, "%s\\Lib\\%s\\ucrt\\", sdk->path10_ptr, sdk->version10_ptr); switch (platform_type) { case ZigLLVM_x86: buf_append_str(output_buf, "x86\\"); @@ -1389,7 +1149,7 @@ int os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_Arch int os_get_win32_ucrt_include_path(ZigWindowsSDK *sdk, Buf* output_buf) { #if defined(ZIG_OS_WINDOWS) buf_resize(output_buf, 0); - buf_appendf(output_buf, "%s\\Include\\%s\\ucrt", buf_ptr(&sdk->path10), buf_ptr(&sdk->version10)); + buf_appendf(output_buf, "%s\\Include\\%s\\ucrt", sdk->path10_ptr, sdk->version10_ptr); if (GetFileAttributesA(buf_ptr(output_buf)) != INVALID_FILE_ATTRIBUTES) { return 0; } @@ -1406,7 +1166,7 @@ int os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_ArchTy #if defined(ZIG_OS_WINDOWS) { buf_resize(output_buf, 0); - buf_appendf(output_buf, "%s\\Lib\\%s\\um\\", buf_ptr(&sdk->path10), buf_ptr(&sdk->version10)); + buf_appendf(output_buf, "%s\\Lib\\%s\\um\\", sdk->path10_ptr, sdk->version10_ptr); switch (platform_type) { case ZigLLVM_x86: buf_append_str(output_buf, "x86\\"); @@ -1429,7 +1189,7 @@ int os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_ArchTy } { buf_resize(output_buf, 0); - buf_appendf(output_buf, "%s\\Lib\\%s\\um\\", buf_ptr(&sdk->path81), buf_ptr(&sdk->version81)); + buf_appendf(output_buf, "%s\\Lib\\%s\\um\\", sdk->path81_ptr, sdk->version81_ptr); switch (platform_type) { case ZigLLVM_x86: buf_append_str(output_buf, "x86\\"); diff --git a/src/os.hpp b/src/os.hpp index b94e98ec3d..cfe4e8f3a2 100644 --- a/src/os.hpp +++ b/src/os.hpp @@ -12,6 +12,7 @@ #include "buffer.hpp" #include "error.hpp" #include "zig_llvm.h" +#include "windows_sdk.h" #include #include @@ -79,15 +80,6 @@ bool os_is_sep(uint8_t c); int os_self_exe_path(Buf *out_path); -struct ZigWindowsSDK { - Buf path10; - Buf version10; - Buf path81; - Buf version81; -}; - -int os_find_windows_sdk(ZigWindowsSDK **out_sdk); -int os_get_win32_vcruntime_path(Buf *output_buf, ZigLLVM_ArchType platform_type); int os_get_win32_ucrt_include_path(ZigWindowsSDK *sdk, Buf *output_buf); int os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf *output_buf, ZigLLVM_ArchType platform_type); int os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf *output_buf, ZigLLVM_ArchType platform_type); diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index f7f41af8a6..1d3db5567a 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -460,16 +460,21 @@ static const char* get_escape_shorthand(uint8_t c) { static void invalid_char_error(Tokenize *t, uint8_t c) { if (c == '\r') { tokenize_error(t, "invalid carriage return, only '\\n' line endings are supported"); - } else if (isprint(c)) { - tokenize_error(t, "invalid character: '%c'", c); - } else { - const char *sh = get_escape_shorthand(c); - if (sh) { - tokenize_error(t, "invalid character: '%s'", sh); - } else { - tokenize_error(t, "invalid character: '\\x%x'", c); - } + return; } + + const char *sh = get_escape_shorthand(c); + if (sh) { + tokenize_error(t, "invalid character: '%s'", sh); + return; + } + + if (isprint(c)) { + tokenize_error(t, "invalid character: '%c'", c); + return; + } + + tokenize_error(t, "invalid character: '\\x%02x'", c); } void tokenize(Buf *buf, Tokenization *out) { diff --git a/src/windows_sdk.cpp b/src/windows_sdk.cpp new file mode 100644 index 0000000000..0f9d0fc301 --- /dev/null +++ b/src/windows_sdk.cpp @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2018 Andrew Kelley + * + * This file is part of zig, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#include "windows_sdk.h" + +#if defined(_WIN32) + +#include "windows_com.hpp" +#include +#include + +struct ZigWindowsSDKPrivate { + ZigWindowsSDK base; +}; + +enum NativeArch { + NativeArchArm, + NativeArchi386, + NativeArchx86_64, +}; + +#if defined(_M_ARM) || defined(__arm_) +static const NativeArch native_arch = NativeArchArm; +#endif +#if defined(_M_IX86) || defined(__i386__) +static const NativeArch native_arch = NativeArchi386; +#endif +#if defined(_M_X64) || defined(__x86_64__) +static const NativeArch native_arch = NativeArchx86_64; +#endif + +void zig_free_windows_sdk(struct ZigWindowsSDK *sdk) { + if (sdk == nullptr) { + return; + } + free((void*)sdk->path10_ptr); + free((void*)sdk->version10_ptr); + free((void*)sdk->path81_ptr); + free((void*)sdk->version81_ptr); + free((void*)sdk->msvc_lib_dir_ptr); +} + +static ZigFindWindowsSdkError find_msvc_lib_dir(ZigWindowsSDKPrivate *priv) { + //COM Smart Pointers requires explicit scope + { + HRESULT rc = CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (rc != S_OK && rc != S_FALSE) { + goto com_done; + } + + //This COM class is installed when a VS2017 + ISetupConfigurationPtr setup_config; + rc = setup_config.CreateInstance(__uuidof(SetupConfiguration)); + if (rc != S_OK) { + goto com_done; + } + + IEnumSetupInstancesPtr all_instances; + rc = setup_config->EnumInstances(&all_instances); + if (rc != S_OK) { + goto com_done; + } + + ISetupInstance* curr_instance; + ULONG found_inst; + while ((rc = all_instances->Next(1, &curr_instance, &found_inst) == S_OK)) { + BSTR bstr_inst_path; + rc = curr_instance->GetInstallationPath(&bstr_inst_path); + if (rc != S_OK) { + goto com_done; + } + //BSTRs are UTF-16 encoded, so we need to convert the string & adjust the length + //TODO call an actual function to do this + UINT bstr_path_len = *((UINT*)bstr_inst_path - 1); + ULONG tmp_path_len = bstr_path_len / 2 + 1; + char* conv_path = (char*)bstr_inst_path; + // TODO don't use alloca + char *tmp_path = (char*)alloca(tmp_path_len); + memset(tmp_path, 0, tmp_path_len); + uint32_t c = 0; + for (uint32_t i = 0; i < bstr_path_len; i += 2) { + tmp_path[c] = conv_path[i]; + ++c; + assert(c != tmp_path_len); + } + char output_path[4096]; + output_path[0] = 0; + char *out_append_ptr = output_path; + + out_append_ptr += sprintf(out_append_ptr, "%s\\", tmp_path); + + char tmp_buf[4096]; + sprintf(tmp_buf, "%s%s", output_path, "VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"); + FILE* tools_file = fopen(tmp_buf, "rb"); + if (!tools_file) { + goto com_done; + } + memset(tmp_path, 0, tmp_path_len); + fgets(tmp_path, tmp_path_len, tools_file); + strtok(tmp_path, " \r\n"); + fclose(tools_file); + out_append_ptr += sprintf(out_append_ptr, "VC\\Tools\\MSVC\\%s\\lib\\", tmp_path); + switch (native_arch) { + case NativeArchi386: + out_append_ptr += sprintf(out_append_ptr, "x86\\"); + break; + case NativeArchx86_64: + out_append_ptr += sprintf(out_append_ptr, "x64\\"); + break; + case NativeArchArm: + out_append_ptr += sprintf(out_append_ptr, "arm\\"); + break; + } + sprintf(tmp_buf, "%s%s", output_path, "vcruntime.lib"); + + if (GetFileAttributesA(tmp_buf) != INVALID_FILE_ATTRIBUTES) { + priv->base.msvc_lib_dir_ptr = strdup(output_path); + if (priv->base.msvc_lib_dir_ptr == nullptr) { + return ZigFindWindowsSdkErrorOutOfMemory; + } + priv->base.msvc_lib_dir_len = strlen(priv->base.msvc_lib_dir_ptr); + return ZigFindWindowsSdkErrorNone; + } + } + } + +com_done:; + HKEY key; + HRESULT rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", 0, + KEY_QUERY_VALUE | KEY_WOW64_32KEY, &key); + if (rc != ERROR_SUCCESS) { + return ZigFindWindowsSdkErrorNotFound; + } + + DWORD dw_type = 0; + DWORD cb_data = 0; + rc = RegQueryValueEx(key, "14.0", NULL, &dw_type, NULL, &cb_data); + if ((rc == ERROR_FILE_NOT_FOUND) || (REG_SZ != dw_type)) { + return ZigFindWindowsSdkErrorNotFound; + } + + char tmp_buf[4096]; + + RegQueryValueExA(key, "14.0", NULL, NULL, (LPBYTE)tmp_buf, &cb_data); + // RegQueryValueExA returns the length of the string INCLUDING the null terminator + char *tmp_buf_append_ptr = tmp_buf + (cb_data - 1); + tmp_buf_append_ptr += sprintf(tmp_buf_append_ptr, "VC\\Lib\\"); + switch (native_arch) { + case NativeArchi386: + //x86 is in the root of the Lib folder + break; + case NativeArchx86_64: + tmp_buf_append_ptr += sprintf(tmp_buf_append_ptr, "amd64\\"); + break; + case NativeArchArm: + tmp_buf_append_ptr += sprintf(tmp_buf_append_ptr, "arm\\"); + break; + } + + char *output_path = strdup(tmp_buf); + if (output_path == nullptr) { + return ZigFindWindowsSdkErrorOutOfMemory; + } + + tmp_buf_append_ptr += sprintf(tmp_buf_append_ptr, "vcruntime.lib"); + + if (GetFileAttributesA(tmp_buf) != INVALID_FILE_ATTRIBUTES) { + priv->base.msvc_lib_dir_ptr = output_path; + priv->base.msvc_lib_dir_len = strlen(output_path); + return ZigFindWindowsSdkErrorNone; + } else { + free(output_path); + return ZigFindWindowsSdkErrorNotFound; + } +} + +static ZigFindWindowsSdkError find_10_version(ZigWindowsSDKPrivate *priv) { + if (priv->base.path10_ptr == nullptr) + return ZigFindWindowsSdkErrorNone; + + char sdk_lib_dir[4096]; + int n = snprintf(sdk_lib_dir, 4096, "%s\\Lib\\*", priv->base.path10_ptr); + if (n < 0 || n >= 4096) { + return ZigFindWindowsSdkErrorPathTooLong; + } + + // enumerate files in sdk path looking for latest version + WIN32_FIND_DATA ffd; + HANDLE hFind = FindFirstFileA(sdk_lib_dir, &ffd); + if (hFind == INVALID_HANDLE_VALUE) { + return ZigFindWindowsSdkErrorNotFound; + } + int v0 = 0, v1 = 0, v2 = 0, v3 = 0; + for (;;) { + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + int c0 = 0, c1 = 0, c2 = 0, c3 = 0; + sscanf(ffd.cFileName, "%d.%d.%d.%d", &c0, &c1, &c2, &c3); + if (c0 == 10 && c1 == 0 && c2 == 10240 && c3 == 0) { + // Microsoft released 26624 as 10240 accidentally. + // https://developer.microsoft.com/en-us/windows/downloads/sdk-archive + c2 = 26624; + } + if ((c0 > v0) || (c1 > v1) || (c2 > v2) || (c3 > v3)) { + v0 = c0, v1 = c1, v2 = c2, v3 = c3; + free((void*)priv->base.version10_ptr); + priv->base.version10_ptr = strdup(ffd.cFileName); + if (priv->base.version10_ptr == nullptr) { + FindClose(hFind); + return ZigFindWindowsSdkErrorOutOfMemory; + } + } + } + if (FindNextFile(hFind, &ffd) == 0) { + FindClose(hFind); + break; + } + } + priv->base.version10_len = strlen(priv->base.version10_ptr); + return ZigFindWindowsSdkErrorNone; +} + +static ZigFindWindowsSdkError find_81_version(ZigWindowsSDKPrivate *priv) { + if (priv->base.path81_ptr == nullptr) + return ZigFindWindowsSdkErrorNone; + + char sdk_lib_dir[4096]; + int n = snprintf(sdk_lib_dir, 4096, "%s\\Lib\\winv*", priv->base.path81_ptr); + if (n < 0 || n >= 4096) { + return ZigFindWindowsSdkErrorPathTooLong; + } + + // enumerate files in sdk path looking for latest version + WIN32_FIND_DATA ffd; + HANDLE hFind = FindFirstFileA(sdk_lib_dir, &ffd); + if (hFind == INVALID_HANDLE_VALUE) { + return ZigFindWindowsSdkErrorNotFound; + } + int v0 = 0, v1 = 0; + for (;;) { + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + int c0 = 0, c1 = 0; + sscanf(ffd.cFileName, "winv%d.%d", &c0, &c1); + if ((c0 > v0) || (c1 > v1)) { + v0 = c0, v1 = c1; + free((void*)priv->base.version81_ptr); + priv->base.version81_ptr = strdup(ffd.cFileName); + if (priv->base.version81_ptr == nullptr) { + FindClose(hFind); + return ZigFindWindowsSdkErrorOutOfMemory; + } + } + } + if (FindNextFile(hFind, &ffd) == 0) { + FindClose(hFind); + break; + } + } + priv->base.version81_len = strlen(priv->base.version81_ptr); + return ZigFindWindowsSdkErrorNone; +} + +ZigFindWindowsSdkError zig_find_windows_sdk(struct ZigWindowsSDK **out_sdk) { + ZigWindowsSDKPrivate *priv = (ZigWindowsSDKPrivate*)calloc(1, sizeof(ZigWindowsSDKPrivate)); + if (priv == nullptr) { + return ZigFindWindowsSdkErrorOutOfMemory; + } + + HKEY key; + HRESULT rc; + rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", 0, + KEY_QUERY_VALUE | KEY_WOW64_32KEY | KEY_ENUMERATE_SUB_KEYS, &key); + if (rc != ERROR_SUCCESS) { + zig_free_windows_sdk(&priv->base); + return ZigFindWindowsSdkErrorNotFound; + } + + { + DWORD tmp_buf_len = MAX_PATH; + priv->base.path10_ptr = (const char *)calloc(tmp_buf_len, 1); + if (priv->base.path10_ptr == nullptr) { + zig_free_windows_sdk(&priv->base); + return ZigFindWindowsSdkErrorOutOfMemory; + } + rc = RegQueryValueEx(key, "KitsRoot10", NULL, NULL, (LPBYTE)priv->base.path10_ptr, &tmp_buf_len); + if (rc == ERROR_SUCCESS) { + priv->base.path10_len = tmp_buf_len - 1; + if (priv->base.path10_ptr[priv->base.path10_len - 1] == '\\') { + priv->base.path10_len -= 1; + } + } else { + free((void*)priv->base.path10_ptr); + priv->base.path10_ptr = nullptr; + } + } + { + DWORD tmp_buf_len = MAX_PATH; + priv->base.path81_ptr = (const char *)calloc(tmp_buf_len, 1); + if (priv->base.path81_ptr == nullptr) { + zig_free_windows_sdk(&priv->base); + return ZigFindWindowsSdkErrorOutOfMemory; + } + rc = RegQueryValueEx(key, "KitsRoot81", NULL, NULL, (LPBYTE)priv->base.path81_ptr, &tmp_buf_len); + if (rc == ERROR_SUCCESS) { + priv->base.path81_len = tmp_buf_len - 1; + if (priv->base.path81_ptr[priv->base.path81_len - 1] == '\\') { + priv->base.path81_len -= 1; + } + } else { + free((void*)priv->base.path81_ptr); + priv->base.path81_ptr = nullptr; + } + } + + { + ZigFindWindowsSdkError err = find_10_version(priv); + if (err == ZigFindWindowsSdkErrorOutOfMemory) { + zig_free_windows_sdk(&priv->base); + return err; + } + } + { + ZigFindWindowsSdkError err = find_81_version(priv); + if (err == ZigFindWindowsSdkErrorOutOfMemory) { + zig_free_windows_sdk(&priv->base); + return err; + } + } + + { + ZigFindWindowsSdkError err = find_msvc_lib_dir(priv); + if (err == ZigFindWindowsSdkErrorOutOfMemory) { + zig_free_windows_sdk(&priv->base); + return err; + } + } + + *out_sdk = &priv->base; + return ZigFindWindowsSdkErrorNone; +} + +#else + +void zig_free_windows_sdk(struct ZigWindowsSDK *sdk) {} +ZigFindWindowsSdkError zig_find_windows_sdk(struct ZigWindowsSDK **out_sdk) { + return ZigFindWindowsSdkErrorNotFound; +} + +#endif diff --git a/src/windows_sdk.h b/src/windows_sdk.h new file mode 100644 index 0000000000..2d531ad372 --- /dev/null +++ b/src/windows_sdk.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Andrew Kelley + * + * This file is part of zig, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#ifndef ZIG_WINDOWS_SDK_H +#define ZIG_WINDOWS_SDK_H + +#ifdef __cplusplus +#define ZIG_EXTERN_C extern "C" +#else +#define ZIG_EXTERN_C +#endif + +#include + +struct ZigWindowsSDK { + const char *path10_ptr; + size_t path10_len; + + const char *version10_ptr; + size_t version10_len; + + const char *path81_ptr; + size_t path81_len; + + const char *version81_ptr; + size_t version81_len; + + const char *msvc_lib_dir_ptr; + size_t msvc_lib_dir_len; +}; + +enum ZigFindWindowsSdkError { + ZigFindWindowsSdkErrorNone, + ZigFindWindowsSdkErrorOutOfMemory, + ZigFindWindowsSdkErrorNotFound, + ZigFindWindowsSdkErrorPathTooLong, +}; + +ZIG_EXTERN_C enum ZigFindWindowsSdkError zig_find_windows_sdk(struct ZigWindowsSDK **out_sdk); + +ZIG_EXTERN_C void zig_free_windows_sdk(struct ZigWindowsSDK *sdk); + +#endif diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index f53e097577..ec46c37d96 100644 --- a/src/zig_llvm.cpp +++ b/src/zig_llvm.cpp @@ -455,6 +455,11 @@ ZigLLVMDIBuilder *ZigLLVMCreateDIBuilder(LLVMModuleRef module, bool allow_unreso return reinterpret_cast(di_builder); } +void ZigLLVMDisposeDIBuilder(ZigLLVMDIBuilder *dbuilder) { + DIBuilder *di_builder = reinterpret_cast(dbuilder); + delete di_builder; +} + void ZigLLVMSetCurrentDebugLocation(LLVMBuilderRef builder, int line, int column, ZigLLVMDIScope *scope) { unwrap(builder)->SetCurrentDebugLocation(DebugLoc::get( line, column, reinterpret_cast(scope))); diff --git a/src/zig_llvm.h b/src/zig_llvm.h index 5e404183b7..d026ae61a3 100644 --- a/src/zig_llvm.h +++ b/src/zig_llvm.h @@ -22,6 +22,9 @@ #define ZIG_EXTERN_C #endif +// ATTENTION: If you modify this file, be sure to update the corresponding +// extern function declarations in the self-hosted compiler. + struct ZigLLVMDIType; struct ZigLLVMDIBuilder; struct ZigLLVMDICompileUnit; @@ -39,7 +42,7 @@ struct ZigLLVMInsertionPoint; ZIG_EXTERN_C void ZigLLVMInitializeLoopStrengthReducePass(LLVMPassRegistryRef R); ZIG_EXTERN_C void ZigLLVMInitializeLowerIntrinsicsPass(LLVMPassRegistryRef R); -/// Caller must free memory. +/// Caller must free memory with LLVMDisposeMessage ZIG_EXTERN_C char *ZigLLVMGetHostCPUName(void); ZIG_EXTERN_C char *ZigLLVMGetNativeFeatures(void); @@ -145,6 +148,7 @@ ZIG_EXTERN_C unsigned ZigLLVMTag_DW_enumeration_type(void); ZIG_EXTERN_C unsigned ZigLLVMTag_DW_union_type(void); ZIG_EXTERN_C struct ZigLLVMDIBuilder *ZigLLVMCreateDIBuilder(LLVMModuleRef module, bool allow_unresolved); +ZIG_EXTERN_C void ZigLLVMDisposeDIBuilder(struct ZigLLVMDIBuilder *dbuilder); ZIG_EXTERN_C void ZigLLVMAddModuleDebugInfoFlag(LLVMModuleRef module); ZIG_EXTERN_C void ZigLLVMAddModuleCodeViewFlag(LLVMModuleRef module); diff --git a/std/array_list.zig b/std/array_list.zig index 8d7bde46a1..298026d11c 100644 --- a/std/array_list.zig +++ b/std/array_list.zig @@ -113,13 +113,12 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type { 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; + /// Removes the element at the specified index and returns it + /// or an error.OutOfBounds is returned. If no error then + /// the empty slot is filled from the end of the list. + pub fn swapRemoveOrError(self: *Self, i: usize) !T { + if (i >= self.len) return error.OutOfBounds; + return self.swapRemove(i); } pub fn appendSlice(self: *Self, items: []align(A) const T) !void { @@ -192,7 +191,7 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type { }; } -test "basic ArrayList test" { +test "std.ArrayList.basic" { var bytes: [1024]u8 = undefined; const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator; @@ -279,7 +278,35 @@ test "std.ArrayList.swapRemove" { assert(list.len == 4); } -test "iterator ArrayList test" { +test "std.ArrayList.swapRemoveOrError" { + var list = ArrayList(i32).init(debug.global_allocator); + defer list.deinit(); + + // Test just after initialization + assertError(list.swapRemoveOrError(0), error.OutOfBounds); + + // Test after adding one item and remote it + try list.append(1); + assert((try list.swapRemoveOrError(0)) == 1); + assertError(list.swapRemoveOrError(0), error.OutOfBounds); + + // Test after adding two items and remote both + try list.append(1); + try list.append(2); + assert((try list.swapRemoveOrError(1)) == 2); + assert((try list.swapRemoveOrError(0)) == 1); + assertError(list.swapRemoveOrError(0), error.OutOfBounds); + + // Test out of bounds with one item + try list.append(1); + assertError(list.swapRemoveOrError(1), error.OutOfBounds); + + // Test out of bounds with two items + try list.append(2); + assertError(list.swapRemoveOrError(2), error.OutOfBounds); +} + +test "std.ArrayList.iterator" { var list = ArrayList(i32).init(debug.global_allocator); defer list.deinit(); @@ -308,7 +335,7 @@ test "iterator ArrayList test" { assert(it.next().? == 1); } -test "insert ArrayList test" { +test "std.ArrayList.insert" { var list = ArrayList(i32).init(debug.global_allocator); defer list.deinit(); @@ -322,7 +349,7 @@ test "insert ArrayList test" { assert(list.items[3] == 3); } -test "insertSlice ArrayList test" { +test "std.ArrayList.insertSlice" { var list = ArrayList(i32).init(debug.global_allocator); defer list.deinit(); diff --git a/std/atomic/int.zig b/std/atomic/int.zig index d51454c673..4103d52719 100644 --- a/std/atomic/int.zig +++ b/std/atomic/int.zig @@ -25,5 +25,9 @@ pub fn Int(comptime T: type) type { pub fn get(self: *Self) T { return @atomicLoad(T, &self.unprotected_value, AtomicOrder.SeqCst); } + + pub fn xchg(self: *Self, new_value: T) T { + return @atomicRmw(T, &self.unprotected_value, builtin.AtomicRmwOp.Xchg, new_value, AtomicOrder.SeqCst); + } }; } diff --git a/std/atomic/queue.zig b/std/atomic/queue.zig index 1fd07714e8..df31c88d2a 100644 --- a/std/atomic/queue.zig +++ b/std/atomic/queue.zig @@ -51,6 +51,20 @@ pub fn Queue(comptime T: type) type { return head; } + pub fn unget(self: *Self, node: *Node) 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); + + const opt_head = self.head; + self.head = node; + if (opt_head) |head| { + head.next = node; + } else { + assert(self.tail == null); + self.tail = node; + } + } + pub fn isEmpty(self: *Self) bool { return @atomicLoad(?*Node, &self.head, builtin.AtomicOrder.SeqCst) != null; } diff --git a/std/buffer.zig b/std/buffer.zig index aff7fa86ef..3b58002aba 100644 --- a/std/buffer.zig +++ b/std/buffer.zig @@ -54,6 +54,19 @@ pub const Buffer = struct { return result; } + pub fn allocPrint(allocator: *Allocator, comptime format: []const u8, args: ...) !Buffer { + const countSize = struct { + fn countSize(size: *usize, bytes: []const u8) (error{}!void) { + size.* += bytes.len; + } + }.countSize; + var size: usize = 0; + std.fmt.format(&size, error{}, countSize, format, args) catch |err| switch (err) {}; + var self = try Buffer.initSize(allocator, size); + assert((std.fmt.bufPrint(self.list.items, format, args) catch unreachable).len == size); + return self; + } + pub fn deinit(self: *Buffer) void { self.list.deinit(); } diff --git a/std/c/darwin.zig b/std/c/darwin.zig index 4189dfeadc..1bd1d6c4c9 100644 --- a/std/c/darwin.zig +++ b/std/c/darwin.zig @@ -30,7 +30,7 @@ pub extern "c" fn sysctl(name: [*]c_int, namelen: c_uint, oldp: ?*c_void, oldlen pub extern "c" fn sysctlbyname(name: [*]const u8, oldp: ?*c_void, oldlenp: ?*usize, newp: ?*c_void, newlen: usize) c_int; pub extern "c" fn sysctlnametomib(name: [*]const u8, mibp: ?*c_int, sizep: ?*usize) c_int; -pub use @import("../os/darwin_errno.zig"); +pub use @import("../os/darwin/errno.zig"); pub const _errno = __error; diff --git a/std/dwarf.zig b/std/dwarf.zig index 76ed122447..2cf8ed953e 100644 --- a/std/dwarf.zig +++ b/std/dwarf.zig @@ -639,3 +639,40 @@ pub const LNE_define_file = 0x03; pub const LNE_set_discriminator = 0x04; pub const LNE_lo_user = 0x80; pub const LNE_hi_user = 0xff; + +pub const LANG_C89 = 0x0001; +pub const LANG_C = 0x0002; +pub const LANG_Ada83 = 0x0003; +pub const LANG_C_plus_plus = 0x0004; +pub const LANG_Cobol74 = 0x0005; +pub const LANG_Cobol85 = 0x0006; +pub const LANG_Fortran77 = 0x0007; +pub const LANG_Fortran90 = 0x0008; +pub const LANG_Pascal83 = 0x0009; +pub const LANG_Modula2 = 0x000a; +pub const LANG_Java = 0x000b; +pub const LANG_C99 = 0x000c; +pub const LANG_Ada95 = 0x000d; +pub const LANG_Fortran95 = 0x000e; +pub const LANG_PLI = 0x000f; +pub const LANG_ObjC = 0x0010; +pub const LANG_ObjC_plus_plus = 0x0011; +pub const LANG_UPC = 0x0012; +pub const LANG_D = 0x0013; +pub const LANG_Python = 0x0014; +pub const LANG_Go = 0x0016; +pub const LANG_C_plus_plus_11 = 0x001a; +pub const LANG_Rust = 0x001c; +pub const LANG_C11 = 0x001d; +pub const LANG_C_plus_plus_14 = 0x0021; +pub const LANG_Fortran03 = 0x0022; +pub const LANG_Fortran08 = 0x0023; +pub const LANG_lo_user = 0x8000; +pub const LANG_hi_user = 0xffff; +pub const LANG_Mips_Assembler = 0x8001; +pub const LANG_Upc = 0x8765; +pub const LANG_HP_Bliss = 0x8003; +pub const LANG_HP_Basic91 = 0x8004; +pub const LANG_HP_Pascal91 = 0x8005; +pub const LANG_HP_IMacro = 0x8006; +pub const LANG_HP_Assembler = 0x8007; diff --git a/std/event/future.zig b/std/event/future.zig index 0f27b4131b..f5d14d1ca6 100644 --- a/std/event/future.zig +++ b/std/event/future.zig @@ -6,15 +6,20 @@ const AtomicOrder = builtin.AtomicOrder; const Lock = std.event.Lock; const Loop = std.event.Loop; -/// This is a value that starts out unavailable, until a value is put(). +/// This is a value that starts out unavailable, until resolve() is called /// While it is unavailable, coroutines suspend when they try to get() it, -/// and then are resumed when the value is put(). -/// At this point the value remains forever available, and another put() is not allowed. +/// and then are resumed when resolve() is called. +/// At this point the value remains forever available, and another resolve() is not allowed. pub fn Future(comptime T: type) type { return struct { lock: Lock, data: T, - available: u8, // TODO make this a bool + + /// TODO make this an enum + /// 0 - not started + /// 1 - started + /// 2 - finished + available: u8, const Self = this; const Queue = std.atomic.Queue(promise); @@ -31,7 +36,7 @@ pub fn Future(comptime T: type) type { /// available. /// Thread-safe. pub async fn get(self: *Self) *T { - if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 1) { + if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 2) { return &self.data; } const held = await (async self.lock.acquire() catch unreachable); @@ -43,18 +48,36 @@ pub fn Future(comptime T: type) type { /// Gets the data without waiting for it. If it's available, a pointer is /// returned. Otherwise, null is returned. pub fn getOrNull(self: *Self) ?*T { - if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 1) { + if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 2) { return &self.data; } else { return null; } } + /// If someone else has started working on the data, wait for them to complete + /// and return a pointer to the data. Otherwise, return null, and the caller + /// should start working on the data. + /// It's not required to call start() before resolve() but it can be useful since + /// this method is thread-safe. + pub async fn start(self: *Self) ?*T { + const state = @cmpxchgStrong(u8, &self.available, 0, 1, AtomicOrder.SeqCst, AtomicOrder.SeqCst) orelse return null; + switch (state) { + 1 => { + const held = await (async self.lock.acquire() catch unreachable); + held.release(); + return &self.data; + }, + 2 => return &self.data, + else => unreachable, + } + } + /// Make the data become available. May be called only once. /// Before calling this, modify the `data` property. pub fn resolve(self: *Self) void { - const prev = @atomicRmw(u8, &self.available, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); - assert(prev == 0); // put() called twice + const prev = @atomicRmw(u8, &self.available, AtomicRmwOp.Xchg, 2, AtomicOrder.SeqCst); + assert(prev == 0 or prev == 1); // resolve() called twice Lock.Held.release(Lock.Held{ .lock = &self.lock }); } }; diff --git a/std/event/group.zig b/std/event/group.zig index c286803b53..26c098399e 100644 --- a/std/event/group.zig +++ b/std/event/group.zig @@ -6,7 +6,7 @@ const AtomicRmwOp = builtin.AtomicRmwOp; const AtomicOrder = builtin.AtomicOrder; const assert = std.debug.assert; -/// ReturnType should be `void` or `E!void` +/// ReturnType must be `void` or `E!void` pub fn Group(comptime ReturnType: type) type { return struct { coro_stack: Stack, @@ -38,8 +38,17 @@ pub fn Group(comptime ReturnType: type) type { self.alloc_stack.push(node); } + /// Add a node to the group. Thread-safe. Cannot fail. + /// `node.data` should be the promise handle to add to the group. + /// The node's memory should be in the coroutine frame of + /// the handle that is in the node, or somewhere guaranteed to live + /// at least as long. + pub fn addNode(self: *Self, node: *Stack.Node) void { + self.coro_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. + /// of returning a promise. func must be async and have return type ReturnType. /// Thread-safe. pub fn call(self: *Self, comptime func: var, args: ...) (error{OutOfMemory}!void) { const S = struct { @@ -67,6 +76,7 @@ pub fn Group(comptime ReturnType: type) type { /// Wait for all the calls and promises of the group to complete. /// Thread-safe. + /// Safe to call any number of times. pub async fn wait(self: *Self) ReturnType { // TODO catch unreachable because the allocation can be grouped with // the coro frame allocation @@ -98,6 +108,8 @@ pub fn Group(comptime ReturnType: type) type { } /// Cancel all the outstanding promises. May only be called if wait was never called. + /// TODO These should be `cancelasync` not `cancel`. + /// See https://github.com/ziglang/zig/issues/1261 pub fn cancelAll(self: *Self) void { while (self.coro_stack.pop()) |node| { cancel node.data; diff --git a/std/event/loop.zig b/std/event/loop.zig index fc927592b9..cd805f891f 100644 --- a/std/event/loop.zig +++ b/std/event/loop.zig @@ -12,7 +12,6 @@ pub const Loop = struct { next_tick_queue: std.atomic.Queue(promise), os_data: OsData, final_resume_node: ResumeNode, - dispatch_lock: u8, // TODO make this a bool pending_event_count: usize, extra_threads: []*std.os.Thread, @@ -74,11 +73,10 @@ pub const Loop = struct { /// max(thread_count - 1, 0) fn initInternal(self: *Loop, allocator: *mem.Allocator, thread_count: usize) !void { self.* = Loop{ - .pending_event_count = 0, + .pending_event_count = 1, .allocator = allocator, .os_data = undefined, .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(), .eventfd_resume_nodes = undefined, @@ -235,8 +233,6 @@ pub const Loop = struct { } }, builtin.Os.windows => { - self.os_data.extra_thread_count = extra_thread_count; - self.os_data.io_port = try std.os.windowsCreateIoCompletionPort( windows.INVALID_HANDLE_VALUE, null, @@ -306,7 +302,7 @@ pub const Loop = struct { pub fn addFd(self: *Loop, fd: i32, resume_node: *ResumeNode) !void { _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); errdefer { - _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); + self.finishOneEvent(); } try self.modFd( fd, @@ -326,7 +322,7 @@ pub const Loop = struct { pub fn removeFd(self: *Loop, fd: i32) void { self.removeFdNoCounter(fd); - _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); + self.finishOneEvent(); } fn removeFdNoCounter(self: *Loop, fd: i32) void { @@ -345,14 +341,70 @@ pub const Loop = struct { } } + fn dispatch(self: *Loop) void { + while (self.available_eventfd_resume_nodes.pop()) |resume_stack_node| { + const next_tick_node = self.next_tick_queue.get() orelse { + self.available_eventfd_resume_nodes.push(resume_stack_node); + return; + }; + const eventfd_node = &resume_stack_node.data; + eventfd_node.base.handle = next_tick_node.data; + switch (builtin.os) { + builtin.Os.macosx => { + const kevent_array = (*[1]posix.Kevent)(&eventfd_node.kevent); + const eventlist = ([*]posix.Kevent)(undefined)[0..0]; + _ = std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null) catch { + self.next_tick_queue.unget(next_tick_node); + self.available_eventfd_resume_nodes.push(resume_stack_node); + return; + }; + }, + builtin.Os.linux => { + // the pending count is already accounted for + const epoll_events = posix.EPOLLONESHOT | std.os.linux.EPOLLIN | std.os.linux.EPOLLOUT | + std.os.linux.EPOLLET; + self.modFd( + eventfd_node.eventfd, + eventfd_node.epoll_op, + epoll_events, + &eventfd_node.base, + ) catch { + self.next_tick_queue.unget(next_tick_node); + self.available_eventfd_resume_nodes.push(resume_stack_node); + return; + }; + }, + builtin.Os.windows => { + // this value is never dereferenced but we need it to be non-null so that + // the consumer code can decide whether to read the completion key. + // it has to do this for normal I/O, so we match that behavior here. + const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1); + std.os.windowsPostQueuedCompletionStatus( + self.os_data.io_port, + undefined, + eventfd_node.completion_key, + overlapped, + ) catch { + self.next_tick_queue.unget(next_tick_node); + self.available_eventfd_resume_nodes.push(resume_stack_node); + return; + }; + }, + else => @compileError("unsupported OS"), + } + } + } + /// Bring your own linked list node. This means it can't fail. pub fn onNextTick(self: *Loop, node: *NextTickNode) void { _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); self.next_tick_queue.put(node); + self.dispatch(); } pub fn run(self: *Loop) void { - _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); + self.finishOneEvent(); // the reference we start with + self.workerRun(); for (self.extra_threads) |extra_thread| { extra_thread.wait(); @@ -392,110 +444,49 @@ pub const Loop = struct { .next = undefined, .data = p, }; - loop.onNextTick(&my_tick_node); + self.onNextTick(&my_tick_node); + } + } + + fn finishOneEvent(self: *Loop) void { + if (@atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst) == 1) { + // cause all the threads to stop + switch (builtin.os) { + builtin.Os.linux => { + // writing 8 bytes to an eventfd cannot fail + std.os.posixWrite(self.os_data.final_eventfd, wakeup_bytes) catch unreachable; + return; + }, + builtin.Os.macosx => { + const final_kevent = (*[1]posix.Kevent)(&self.os_data.final_kevent); + const eventlist = ([*]posix.Kevent)(undefined)[0..0]; + // cannot fail because we already added it and this just enables it + _ = std.os.bsdKEvent(self.os_data.kqfd, final_kevent, eventlist, null) catch unreachable; + return; + }, + builtin.Os.windows => { + var i: usize = 0; + while (i < self.extra_threads.len + 1) : (i += 1) { + while (true) { + const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1); + std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue; + break; + } + } + return; + }, + else => @compileError("unsupported OS"), + } } } fn workerRun(self: *Loop) void { - start_over: while (true) { - if (@atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) == 0) { - while (self.next_tick_queue.get()) |next_tick_node| { - const handle = next_tick_node.data; - if (self.next_tick_queue.isEmpty()) { - // last node, just resume it - _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); - resume handle; - _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); - continue :start_over; - } - - // non-last node, stick it in the epoll/kqueue set so that - // other threads can get to it - if (self.available_eventfd_resume_nodes.pop()) |resume_stack_node| { - const eventfd_node = &resume_stack_node.data; - eventfd_node.base.handle = handle; - switch (builtin.os) { - builtin.Os.macosx => { - const kevent_array = (*[1]posix.Kevent)(&eventfd_node.kevent); - const eventlist = ([*]posix.Kevent)(undefined)[0..0]; - _ = std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null) catch { - // fine, we didn't need it anyway - _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); - self.available_eventfd_resume_nodes.push(resume_stack_node); - resume handle; - _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); - continue :start_over; - }; - }, - builtin.Os.linux => { - // the pending count is already accounted for - const epoll_events = posix.EPOLLONESHOT | std.os.linux.EPOLLIN | std.os.linux.EPOLLOUT | std.os.linux.EPOLLET; - self.modFd(eventfd_node.eventfd, eventfd_node.epoll_op, epoll_events, &eventfd_node.base) catch { - // fine, we didn't need it anyway - _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); - self.available_eventfd_resume_nodes.push(resume_stack_node); - resume handle; - _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); - continue :start_over; - }; - }, - builtin.Os.windows => { - // this value is never dereferenced but we need it to be non-null so that - // the consumer code can decide whether to read the completion key. - // it has to do this for normal I/O, so we match that behavior here. - const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1); - std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, eventfd_node.completion_key, overlapped) catch { - // fine, we didn't need it anyway - _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); - self.available_eventfd_resume_nodes.push(resume_stack_node); - resume handle; - _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); - continue :start_over; - }; - }, - else => @compileError("unsupported OS"), - } - } else { - // threads are too busy, can't add another eventfd to wake one up - _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); - resume handle; - _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); - continue :start_over; - } - } - - const pending_event_count = @atomicLoad(usize, &self.pending_event_count, AtomicOrder.SeqCst); - if (pending_event_count == 0) { - // cause all the threads to stop - switch (builtin.os) { - builtin.Os.linux => { - // writing 8 bytes to an eventfd cannot fail - std.os.posixWrite(self.os_data.final_eventfd, wakeup_bytes) catch unreachable; - return; - }, - builtin.Os.macosx => { - const final_kevent = (*[1]posix.Kevent)(&self.os_data.final_kevent); - const eventlist = ([*]posix.Kevent)(undefined)[0..0]; - // cannot fail because we already added it and this just enables it - _ = std.os.bsdKEvent(self.os_data.kqfd, final_kevent, eventlist, null) catch unreachable; - return; - }, - builtin.Os.windows => { - var i: usize = 0; - while (i < self.os_data.extra_thread_count) : (i += 1) { - while (true) { - const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1); - std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue; - break; - } - } - return; - }, - else => @compileError("unsupported OS"), - } - } - - _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); + while (true) { + while (true) { + const next_tick_node = self.next_tick_queue.get() orelse break; + self.dispatch(); + resume next_tick_node.data; + self.finishOneEvent(); } switch (builtin.os) { @@ -519,7 +510,7 @@ pub const Loop = struct { } resume handle; if (resume_node_id == ResumeNode.Id.EventFd) { - _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); + self.finishOneEvent(); } } }, @@ -541,7 +532,7 @@ pub const Loop = struct { } resume handle; if (resume_node_id == ResumeNode.Id.EventFd) { - _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); + self.finishOneEvent(); } } }, @@ -570,7 +561,7 @@ pub const Loop = struct { } resume handle; if (resume_node_id == ResumeNode.Id.EventFd) { - _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); + self.finishOneEvent(); } }, else => @compileError("unsupported OS"), diff --git a/std/event/tcp.zig b/std/event/tcp.zig index 5151ecf934..416a8c07dc 100644 --- a/std/event/tcp.zig +++ b/std/event/tcp.zig @@ -125,8 +125,9 @@ pub async fn connect(loop: *Loop, _address: *const std.net.Address) !std.os.File test "listen on a port, send bytes, receive bytes" { if (builtin.os != builtin.Os.linux) { // TODO build abstractions for other operating systems - return; + return error.SkipZigTest; } + const MyServer = struct { tcp_server: Server, diff --git a/std/fmt/index.zig b/std/fmt/index.zig index c3c17f5322..2188cc5803 100644 --- a/std/fmt/index.zig +++ b/std/fmt/index.zig @@ -785,11 +785,15 @@ pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: ...) ![]u8 { return buf[0 .. buf.len - context.remaining.len]; } -pub fn allocPrint(allocator: *mem.Allocator, comptime fmt: []const u8, args: ...) ![]u8 { +pub const AllocPrintError = error{OutOfMemory}; + +pub fn allocPrint(allocator: *mem.Allocator, comptime fmt: []const u8, args: ...) AllocPrintError![]u8 { var size: usize = 0; format(&size, error{}, countSize, fmt, args) catch |err| switch (err) {}; const buf = try allocator.alloc(u8, size); - return bufPrint(buf, fmt, args); + return bufPrint(buf, fmt, args) catch |err| switch (err) { + error.BufferTooSmall => unreachable, // we just counted the size above + }; } fn countSize(size: *usize, bytes: []const u8) (error{}!void) { diff --git a/std/index.zig b/std/index.zig index 3b523f519f..2f4cfb7553 100644 --- a/std/index.zig +++ b/std/index.zig @@ -36,6 +36,8 @@ pub const sort = @import("sort.zig"); pub const unicode = @import("unicode.zig"); pub const zig = @import("zig/index.zig"); +pub const lazyInit = @import("lazy_init.zig").lazyInit; + test "std" { // run tests from these _ = @import("atomic/index.zig"); @@ -71,4 +73,5 @@ test "std" { _ = @import("sort.zig"); _ = @import("unicode.zig"); _ = @import("zig/index.zig"); + _ = @import("lazy_init.zig"); } diff --git a/std/lazy_init.zig b/std/lazy_init.zig new file mode 100644 index 0000000000..c46c067810 --- /dev/null +++ b/std/lazy_init.zig @@ -0,0 +1,85 @@ +const std = @import("index.zig"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const AtomicRmwOp = builtin.AtomicRmwOp; +const AtomicOrder = builtin.AtomicOrder; + +/// Thread-safe initialization of global data. +/// TODO use a mutex instead of a spinlock +pub fn lazyInit(comptime T: type) LazyInit(T) { + return LazyInit(T){ + .data = undefined, + .state = 0, + }; +} + +fn LazyInit(comptime T: type) type { + return struct { + state: u8, // TODO make this an enum + data: Data, + + const Self = this; + + // TODO this isn't working for void, investigate and then remove this special case + const Data = if (@sizeOf(T) == 0) u8 else T; + const Ptr = if (T == void) void else *T; + + /// Returns a usable pointer to the initialized data, + /// or returns null, indicating that the caller should + /// perform the initialization and then call resolve(). + pub fn get(self: *Self) ?Ptr { + while (true) { + var state = @cmpxchgWeak(u8, &self.state, 0, 1, AtomicOrder.SeqCst, AtomicOrder.SeqCst) orelse return null; + switch (state) { + 0 => continue, + 1 => { + // TODO mutex instead of a spinlock + continue; + }, + 2 => { + if (@sizeOf(T) == 0) { + return T(undefined); + } else { + return &self.data; + } + }, + else => unreachable, + } + } + } + + pub fn resolve(self: *Self) void { + const prev = @atomicRmw(u8, &self.state, AtomicRmwOp.Xchg, 2, AtomicOrder.SeqCst); + assert(prev == 1); // resolve() called twice + } + }; +} + +var global_number = lazyInit(i32); + +test "std.lazyInit" { + if (global_number.get()) |_| @panic("bad") else { + global_number.data = 1234; + global_number.resolve(); + } + if (global_number.get()) |x| { + assert(x.* == 1234); + } else { + @panic("bad"); + } + if (global_number.get()) |x| { + assert(x.* == 1234); + } else { + @panic("bad"); + } +} + +var global_void = lazyInit(void); + +test "std.lazyInit(void)" { + if (global_void.get()) |_| @panic("bad") else { + global_void.resolve(); + } + assert(global_void.get() != null); + assert(global_void.get() != null); +} diff --git a/std/macho.zig b/std/macho.zig index 33c170ff43..ddc4d334e4 100644 --- a/std/macho.zig +++ b/std/macho.zig @@ -141,7 +141,7 @@ pub fn loadSymbols(allocator: *mem.Allocator, in: *io.FileInStream) !SymbolTable } // Effectively a no-op, lld emits symbols in ascending order. - std.sort.insertionSort(Symbol, symbols[0..nsyms], Symbol.addressLessThan); + std.sort.sort(Symbol, symbols[0..nsyms], Symbol.addressLessThan); // Insert the sentinel. Since we don't know where the last function ends, // we arbitrarily limit it to the start address + 4 KB. diff --git a/std/math/big/int.zig b/std/math/big/int.zig index caa9d0a7ed..41e1503d49 100644 --- a/std/math/big/int.zig +++ b/std/math/big/int.zig @@ -60,8 +60,9 @@ pub const Int = struct { self.limbs = try self.allocator.realloc(Limb, self.limbs, capacity); } - pub fn deinit(self: Int) void { + pub fn deinit(self: *Int) void { self.allocator.free(self.limbs); + self.* = undefined; } pub fn clone(other: Int) !Int { @@ -115,13 +116,63 @@ pub const Int = struct { return !r.isOdd(); } - fn bitcount(self: Int) usize { - const u_bit_count = (self.len - 1) * Limb.bit_count + (Limb.bit_count - @clz(self.limbs[self.len - 1])); - return usize(@boolToInt(!self.positive)) + u_bit_count; + // Returns the number of bits required to represent the absolute value of self. + fn bitCountAbs(self: Int) usize { + return (self.len - 1) * Limb.bit_count + (Limb.bit_count - @clz(self.limbs[self.len - 1])); } + // Returns the number of bits required to represent the integer in twos-complement form. + // + // If the integer is negative the value returned is the number of bits needed by a signed + // integer to represent the value. If positive the value is the number of bits for an + // unsigned integer. Any unsigned integer will fit in the signed integer with bitcount + // one greater than the returned value. + // + // e.g. -127 returns 8 as it will fit in an i8. 127 returns 7 since it fits in a u7. + fn bitCountTwosComp(self: Int) usize { + var bits = self.bitCountAbs(); + + // If the entire value has only one bit set (e.g. 0b100000000) then the negation in twos + // complement requires one less bit. + if (!self.positive) block: { + bits += 1; + + if (@popCount(self.limbs[self.len - 1]) == 1) { + for (self.limbs[0 .. self.len - 1]) |limb| { + if (@popCount(limb) != 0) { + break :block; + } + } + + bits -= 1; + } + } + + return bits; + } + + pub fn fitsInTwosComp(self: Int, is_signed: bool, bit_count: usize) bool { + if (self.eqZero()) { + return true; + } + if (!is_signed and !self.positive) { + return false; + } + + const req_bits = self.bitCountTwosComp() + @boolToInt(self.positive and is_signed); + return bit_count >= req_bits; + } + + pub fn fits(self: Int, comptime T: type) bool { + return self.fitsInTwosComp(T.is_signed, T.bit_count); + } + + // Returns the approximate size of the integer in the given base. Negative values accomodate for + // the minus sign. This is used for determining the number of characters needed to print the + // value. It is inexact and will exceed the given value by 1-2 digits. pub fn sizeInBase(self: Int, base: usize) usize { - return (self.bitcount() / math.log2(base)) + 1; + const bit_count = usize(@boolToInt(!self.positive)) + self.bitCountAbs(); + return (bit_count / math.log2(base)) + 1; } pub fn set(self: *Int, value: var) Allocator.Error!void { @@ -189,9 +240,9 @@ pub const Int = struct { pub fn to(self: Int, comptime T: type) ConvertError!T { switch (@typeId(T)) { TypeId.Int => { - const UT = if (T.is_signed) @IntType(false, T.bit_count - 1) else T; + const UT = @IntType(false, T.bit_count); - if (self.bitcount() > 8 * @sizeOf(UT)) { + if (self.bitCountTwosComp() > T.bit_count) { return error.TargetTooSmall; } @@ -208,9 +259,17 @@ pub const Int = struct { } if (!T.is_signed) { - return if (self.positive) r else error.NegativeIntoUnsigned; + return if (self.positive) @intCast(T, r) else error.NegativeIntoUnsigned; } else { - return if (self.positive) @intCast(T, r) else -@intCast(T, r); + if (self.positive) { + return @intCast(T, r); + } else { + if (math.cast(T, r)) |ok| { + return -ok; + } else |_| { + return @minValue(T); + } + } } }, else => { @@ -274,6 +333,7 @@ pub const Int = struct { self.positive = positive; } + /// TODO make this call format instead of the other way around pub fn toString(self: Int, allocator: *Allocator, base: u8) ![]const u8 { if (base < 2 or base > 16) { return error.InvalidBase; @@ -356,6 +416,21 @@ pub const Int = struct { return s; } + /// for the std lib format function + /// TODO make this non-allocating + pub fn format( + self: Int, + comptime fmt: []const u8, + context: var, + comptime FmtError: type, + output: fn (@typeOf(context), []const u8) FmtError!void, + ) FmtError!void { + // TODO look at fmt and support other bases + const str = self.toString(self.allocator, 10) catch @panic("TODO make this non allocating"); + defer self.allocator.free(str); + return output(context, str); + } + // returns -1, 0, 1 if |a| < |b|, |a| == |b| or |a| > |b| respectively. pub fn cmpAbs(a: Int, b: Int) i8 { if (a.len < b.len) { @@ -1120,24 +1195,88 @@ test "big.int bitcount + sizeInBase" { var a = try Int.init(al); try a.set(0b100); - debug.assert(a.bitcount() == 3); + debug.assert(a.bitCountAbs() == 3); debug.assert(a.sizeInBase(2) >= 3); debug.assert(a.sizeInBase(10) >= 1); + a.negate(); + debug.assert(a.bitCountAbs() == 3); + debug.assert(a.sizeInBase(2) >= 4); + debug.assert(a.sizeInBase(10) >= 2); + try a.set(0xffffffff); - debug.assert(a.bitcount() == 32); + debug.assert(a.bitCountAbs() == 32); debug.assert(a.sizeInBase(2) >= 32); debug.assert(a.sizeInBase(10) >= 10); try a.shiftLeft(a, 5000); - debug.assert(a.bitcount() == 5032); + debug.assert(a.bitCountAbs() == 5032); debug.assert(a.sizeInBase(2) >= 5032); a.positive = false; - debug.assert(a.bitcount() == 5033); + debug.assert(a.bitCountAbs() == 5032); debug.assert(a.sizeInBase(2) >= 5033); } +test "big.int bitcount/to" { + var a = try Int.init(al); + + try a.set(0); + debug.assert(a.bitCountTwosComp() == 0); + + // TODO: stack smashing + // debug.assert((try a.to(u0)) == 0); + // TODO: sigsegv + // debug.assert((try a.to(i0)) == 0); + + try a.set(-1); + debug.assert(a.bitCountTwosComp() == 1); + debug.assert((try a.to(i1)) == -1); + + try a.set(-8); + debug.assert(a.bitCountTwosComp() == 4); + debug.assert((try a.to(i4)) == -8); + + try a.set(127); + debug.assert(a.bitCountTwosComp() == 7); + debug.assert((try a.to(u7)) == 127); + + try a.set(-128); + debug.assert(a.bitCountTwosComp() == 8); + debug.assert((try a.to(i8)) == -128); + + try a.set(-129); + debug.assert(a.bitCountTwosComp() == 9); + debug.assert((try a.to(i9)) == -129); +} + +test "big.int fits" { + var a = try Int.init(al); + + try a.set(0); + debug.assert(a.fits(u0)); + debug.assert(a.fits(i0)); + + try a.set(255); + debug.assert(!a.fits(u0)); + debug.assert(!a.fits(u1)); + debug.assert(!a.fits(i8)); + debug.assert(a.fits(u8)); + debug.assert(a.fits(u9)); + debug.assert(a.fits(i9)); + + try a.set(-128); + debug.assert(!a.fits(i7)); + debug.assert(a.fits(i8)); + debug.assert(a.fits(i9)); + debug.assert(!a.fits(u9)); + + try a.set(0x1ffffffffeeeeeeee); + debug.assert(!a.fits(u32)); + debug.assert(!a.fits(u64)); + debug.assert(a.fits(u65)); +} + test "big.int string set" { var a = try Int.init(al); try a.setString(10, "120317241209124781241290847124"); diff --git a/std/mem.zig b/std/mem.zig index 2a5b0366a9..43961a6d14 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -35,6 +35,7 @@ pub const Allocator = struct { freeFn: fn (self: *Allocator, old_mem: []u8) void, /// Call `destroy` with the result + /// TODO this is deprecated. use createOne instead pub fn create(self: *Allocator, init: var) Error!*@typeOf(init) { const T = @typeOf(init); if (@sizeOf(T) == 0) return &(T{}); @@ -44,6 +45,14 @@ pub const Allocator = struct { return ptr; } + /// Call `destroy` with the result. + /// Returns undefined memory. + pub fn createOne(self: *Allocator, comptime T: type) Error!*T { + if (@sizeOf(T) == 0) return &(T{}); + const slice = try self.alloc(T, 1); + return &slice[0]; + } + /// `ptr` should be the return value of `create` pub fn destroy(self: *Allocator, ptr: var) void { const non_const_ptr = @intToPtr([*]u8, @ptrToInt(ptr)); @@ -149,13 +158,12 @@ pub fn copyBackwards(comptime T: type, dest: []T, source: []const T) void { @setRuntimeSafety(false); assert(dest.len >= source.len); var i = source.len; - while(i > 0){ + 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/darwin.zig b/std/os/darwin.zig index 4134e382fc..cf67b01d5a 100644 --- a/std/os/darwin.zig +++ b/std/os/darwin.zig @@ -2,7 +2,7 @@ const std = @import("../index.zig"); const c = std.c; const assert = std.debug.assert; -pub use @import("darwin_errno.zig"); +pub use @import("darwin/errno.zig"); pub const PATH_MAX = 1024; @@ -482,6 +482,92 @@ pub const NOTE_MACH_CONTINUOUS_TIME = 0x00000080; /// data is mach absolute time units pub const NOTE_MACHTIME = 0x00000100; +pub const AF_UNSPEC: c_int = 0; +pub const AF_LOCAL: c_int = 1; +pub const AF_UNIX: c_int = AF_LOCAL; +pub const AF_INET: c_int = 2; +pub const AF_SYS_CONTROL: c_int = 2; +pub const AF_IMPLINK: c_int = 3; +pub const AF_PUP: c_int = 4; +pub const AF_CHAOS: c_int = 5; +pub const AF_NS: c_int = 6; +pub const AF_ISO: c_int = 7; +pub const AF_OSI: c_int = AF_ISO; +pub const AF_ECMA: c_int = 8; +pub const AF_DATAKIT: c_int = 9; +pub const AF_CCITT: c_int = 10; +pub const AF_SNA: c_int = 11; +pub const AF_DECnet: c_int = 12; +pub const AF_DLI: c_int = 13; +pub const AF_LAT: c_int = 14; +pub const AF_HYLINK: c_int = 15; +pub const AF_APPLETALK: c_int = 16; +pub const AF_ROUTE: c_int = 17; +pub const AF_LINK: c_int = 18; +pub const AF_XTP: c_int = 19; +pub const AF_COIP: c_int = 20; +pub const AF_CNT: c_int = 21; +pub const AF_RTIP: c_int = 22; +pub const AF_IPX: c_int = 23; +pub const AF_SIP: c_int = 24; +pub const AF_PIP: c_int = 25; +pub const AF_ISDN: c_int = 28; +pub const AF_E164: c_int = AF_ISDN; +pub const AF_KEY: c_int = 29; +pub const AF_INET6: c_int = 30; +pub const AF_NATM: c_int = 31; +pub const AF_SYSTEM: c_int = 32; +pub const AF_NETBIOS: c_int = 33; +pub const AF_PPP: c_int = 34; +pub const AF_MAX: c_int = 40; + +pub const PF_UNSPEC: c_int = AF_UNSPEC; +pub const PF_LOCAL: c_int = AF_LOCAL; +pub const PF_UNIX: c_int = PF_LOCAL; +pub const PF_INET: c_int = AF_INET; +pub const PF_IMPLINK: c_int = AF_IMPLINK; +pub const PF_PUP: c_int = AF_PUP; +pub const PF_CHAOS: c_int = AF_CHAOS; +pub const PF_NS: c_int = AF_NS; +pub const PF_ISO: c_int = AF_ISO; +pub const PF_OSI: c_int = AF_ISO; +pub const PF_ECMA: c_int = AF_ECMA; +pub const PF_DATAKIT: c_int = AF_DATAKIT; +pub const PF_CCITT: c_int = AF_CCITT; +pub const PF_SNA: c_int = AF_SNA; +pub const PF_DECnet: c_int = AF_DECnet; +pub const PF_DLI: c_int = AF_DLI; +pub const PF_LAT: c_int = AF_LAT; +pub const PF_HYLINK: c_int = AF_HYLINK; +pub const PF_APPLETALK: c_int = AF_APPLETALK; +pub const PF_ROUTE: c_int = AF_ROUTE; +pub const PF_LINK: c_int = AF_LINK; +pub const PF_XTP: c_int = AF_XTP; +pub const PF_COIP: c_int = AF_COIP; +pub const PF_CNT: c_int = AF_CNT; +pub const PF_SIP: c_int = AF_SIP; +pub const PF_IPX: c_int = AF_IPX; +pub const PF_RTIP: c_int = AF_RTIP; +pub const PF_PIP: c_int = AF_PIP; +pub const PF_ISDN: c_int = AF_ISDN; +pub const PF_KEY: c_int = AF_KEY; +pub const PF_INET6: c_int = AF_INET6; +pub const PF_NATM: c_int = AF_NATM; +pub const PF_SYSTEM: c_int = AF_SYSTEM; +pub const PF_NETBIOS: c_int = AF_NETBIOS; +pub const PF_PPP: c_int = AF_PPP; +pub const PF_MAX: c_int = AF_MAX; + +pub const SYSPROTO_EVENT: c_int = 1; +pub const SYSPROTO_CONTROL: c_int = 2; + +pub const SOCK_STREAM: c_int = 1; +pub const SOCK_DGRAM: c_int = 2; +pub const SOCK_RAW: c_int = 3; +pub const SOCK_RDM: c_int = 4; +pub const SOCK_SEQPACKET: c_int = 5; +pub const SOCK_MAXADDRLEN: c_int = 255; + fn wstatus(x: i32) i32 { return x & 0o177; } diff --git a/std/os/darwin_errno.zig b/std/os/darwin/errno.zig similarity index 100% rename from std/os/darwin_errno.zig rename to std/os/darwin/errno.zig diff --git a/std/os/file.zig b/std/os/file.zig index 055f185121..6998ba00d1 100644 --- a/std/os/file.zig +++ b/std/os/file.zig @@ -15,7 +15,7 @@ pub const File = struct { /// The OS-specific file descriptor or file handle. handle: os.FileHandle, - const OpenError = os.WindowsOpenError || os.PosixOpenError; + pub const OpenError = os.WindowsOpenError || os.PosixOpenError; /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator. /// Call close to clean up. @@ -109,43 +109,42 @@ pub const File = struct { Unexpected, }; - pub fn access(allocator: *mem.Allocator, path: []const u8, file_mode: os.FileMode) AccessError!bool { + pub fn access(allocator: *mem.Allocator, path: []const u8) AccessError!void { const path_with_null = try std.cstr.addNullByte(allocator, path); defer allocator.free(path_with_null); if (is_posix) { - // mode is ignored and is always F_OK for now const result = posix.access(path_with_null.ptr, posix.F_OK); const err = posix.getErrno(result); - if (err > 0) { - return switch (err) { - posix.EACCES => error.PermissionDenied, - posix.EROFS => error.PermissionDenied, - posix.ELOOP => error.PermissionDenied, - posix.ETXTBSY => error.PermissionDenied, - posix.ENOTDIR => error.NotFound, - posix.ENOENT => error.NotFound, + switch (err) { + 0 => return, + posix.EACCES => return error.PermissionDenied, + posix.EROFS => return error.PermissionDenied, + posix.ELOOP => return error.PermissionDenied, + posix.ETXTBSY => return error.PermissionDenied, + posix.ENOTDIR => return error.NotFound, + posix.ENOENT => return error.NotFound, - posix.ENAMETOOLONG => error.NameTooLong, - posix.EINVAL => error.BadMode, - posix.EFAULT => error.BadPathName, - posix.EIO => error.Io, - posix.ENOMEM => error.SystemResources, - else => os.unexpectedErrorPosix(err), - }; + posix.ENAMETOOLONG => return error.NameTooLong, + posix.EINVAL => unreachable, + posix.EFAULT => return error.BadPathName, + posix.EIO => return error.Io, + posix.ENOMEM => return error.SystemResources, + else => return os.unexpectedErrorPosix(err), } - return true; } else if (is_windows) { if (os.windows.GetFileAttributesA(path_with_null.ptr) != os.windows.INVALID_FILE_ATTRIBUTES) { - return true; + return; } const err = windows.GetLastError(); - return switch (err) { - windows.ERROR.FILE_NOT_FOUND => error.NotFound, - windows.ERROR.ACCESS_DENIED => error.PermissionDenied, - else => os.unexpectedErrorWindows(err), - }; + switch (err) { + windows.ERROR.FILE_NOT_FOUND, + windows.ERROR.PATH_NOT_FOUND, + => return error.NotFound, + windows.ERROR.ACCESS_DENIED => return error.PermissionDenied, + else => return os.unexpectedErrorWindows(err), + } } else { @compileError("TODO implement access for this OS"); } @@ -242,7 +241,7 @@ pub const File = struct { }, Os.windows => { var pos: windows.LARGE_INTEGER = undefined; - if (windows.SetFilePointerEx(self.handle, 0, *pos, windows.FILE_CURRENT) == 0) { + if (windows.SetFilePointerEx(self.handle, 0, &pos, windows.FILE_CURRENT) == 0) { const err = windows.GetLastError(); return switch (err) { windows.ERROR.INVALID_PARAMETER => error.BadFd, @@ -251,13 +250,7 @@ pub const File = struct { } assert(pos >= 0); - if (@sizeOf(@typeOf(pos)) > @sizeOf(usize)) { - if (pos > @maxValue(usize)) { - return error.FilePosLargerThanPointerRange; - } - } - - return usize(pos); + return math.cast(usize, pos) catch error.FilePosLargerThanPointerRange; }, else => @compileError("unsupported OS"), } @@ -289,7 +282,7 @@ pub const File = struct { Unexpected, }; - fn mode(self: *File) ModeError!os.FileMode { + pub fn mode(self: *File) ModeError!os.FileMode { if (is_posix) { var stat: posix.Stat = undefined; const err = posix.getErrno(posix.fstat(self.handle, &stat)); @@ -364,7 +357,7 @@ pub const File = struct { pub const WriteError = os.WindowsWriteError || os.PosixWriteError; - fn write(self: *File, bytes: []const u8) WriteError!void { + pub fn write(self: *File, bytes: []const u8) WriteError!void { if (is_posix) { try os.posixWrite(self.handle, bytes); } else if (is_windows) { diff --git a/std/os/get_app_data_dir.zig b/std/os/get_app_data_dir.zig new file mode 100644 index 0000000000..e8ae5dd490 --- /dev/null +++ b/std/os/get_app_data_dir.zig @@ -0,0 +1,69 @@ +const std = @import("../index.zig"); +const builtin = @import("builtin"); +const unicode = std.unicode; +const mem = std.mem; +const os = std.os; + +pub const GetAppDataDirError = error{ + OutOfMemory, + AppDataDirUnavailable, +}; + +/// Caller owns returned memory. +pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 { + switch (builtin.os) { + builtin.Os.windows => { + var dir_path_ptr: [*]u16 = undefined; + switch (os.windows.SHGetKnownFolderPath( + &os.windows.FOLDERID_LocalAppData, + os.windows.KF_FLAG_CREATE, + null, + &dir_path_ptr, + )) { + os.windows.S_OK => { + defer os.windows.CoTaskMemFree(@ptrCast(*c_void, dir_path_ptr)); + const global_dir = unicode.utf16leToUtf8(allocator, utf16lePtrSlice(dir_path_ptr)) catch |err| switch (err) { + error.UnexpectedSecondSurrogateHalf => return error.AppDataDirUnavailable, + error.ExpectedSecondSurrogateHalf => return error.AppDataDirUnavailable, + error.DanglingSurrogateHalf => return error.AppDataDirUnavailable, + error.OutOfMemory => return error.OutOfMemory, + }; + defer allocator.free(global_dir); + return os.path.join(allocator, global_dir, appname); + }, + os.windows.E_OUTOFMEMORY => return error.OutOfMemory, + else => return error.AppDataDirUnavailable, + } + }, + builtin.Os.macosx => { + const home_dir = os.getEnvPosix("HOME") orelse { + // TODO look in /etc/passwd + return error.AppDataDirUnavailable; + }; + return os.path.join(allocator, home_dir, "Library", "Application Support", appname); + }, + builtin.Os.linux => { + const home_dir = os.getEnvPosix("HOME") orelse { + // TODO look in /etc/passwd + return error.AppDataDirUnavailable; + }; + return os.path.join(allocator, home_dir, ".local", "share", appname); + }, + else => @compileError("Unsupported OS"), + } +} + +fn utf16lePtrSlice(ptr: [*]const u16) []const u16 { + var index: usize = 0; + while (ptr[index] != 0) : (index += 1) {} + return ptr[0..index]; +} + +test "std.os.getAppDataDir" { + var buf: [512]u8 = undefined; + const allocator = &std.heap.FixedBufferAllocator.init(buf[0..]).allocator; + + // We can't actually validate the result + _ = getAppDataDir(allocator, "zig") catch return; +} + diff --git a/std/os/index.zig b/std/os/index.zig index 79b2d2ff53..77fd2a78ad 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -11,13 +11,14 @@ const os = this; test "std.os" { _ = @import("child_process.zig"); _ = @import("darwin.zig"); - _ = @import("darwin_errno.zig"); + _ = @import("darwin/errno.zig"); _ = @import("get_user_id.zig"); _ = @import("linux/index.zig"); _ = @import("path.zig"); _ = @import("test.zig"); _ = @import("time.zig"); _ = @import("windows/index.zig"); + _ = @import("get_app_data_dir.zig"); } pub const windows = @import("windows/index.zig"); @@ -76,6 +77,9 @@ pub const WindowsWriteError = windows_util.WriteError; pub const FileHandle = if (is_windows) windows.HANDLE else i32; +pub const getAppDataDir = @import("get_app_data_dir.zig").getAppDataDir; +pub const GetAppDataDirError = @import("get_app_data_dir.zig").GetAppDataDirError; + const debug = std.debug; const assert = debug.assert; @@ -494,6 +498,7 @@ pub var linux_aux_raw = []usize{0} ** 38; pub var posix_environ_raw: [][*]u8 = undefined; /// Caller must free result when done. +/// TODO make this go through libc when we have it pub fn getEnvMap(allocator: *Allocator) !BufMap { var result = BufMap.init(allocator); errdefer result.deinit(); @@ -537,6 +542,7 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap { } } +/// TODO make this go through libc when we have it pub fn getEnvPosix(key: []const u8) ?[]const u8 { for (posix_environ_raw) |ptr| { var line_i: usize = 0; @@ -559,6 +565,7 @@ pub const GetEnvVarOwnedError = error{ }; /// Caller must free returned memory. +/// TODO make this go through libc when we have it pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { if (is_windows) { const key_with_null = try cstr.addNullByte(allocator, key); diff --git a/std/os/test.zig b/std/os/test.zig index 52e6ffdc1c..9e795e8ad2 100644 --- a/std/os/test.zig +++ b/std/os/test.zig @@ -23,14 +23,14 @@ test "makePath, put some files in it, deleteTree" { test "access file" { try os.makePath(a, "os_test_tmp"); - if (os.File.access(a, "os_test_tmp/file.txt", os.default_file_mode)) |ok| { - unreachable; + if (os.File.access(a, "os_test_tmp/file.txt")) |ok| { + @panic("expected error"); } else |err| { assert(err == error.NotFound); } try io.writeFile(a, "os_test_tmp/file.txt", ""); - assert((try os.File.access(a, "os_test_tmp/file.txt", os.default_file_mode)) == true); + try os.File.access(a, "os_test_tmp/file.txt"); try os.deleteTree(a, "os_test_tmp"); } diff --git a/std/os/windows/advapi32.zig b/std/os/windows/advapi32.zig new file mode 100644 index 0000000000..dcb5a636ea --- /dev/null +++ b/std/os/windows/advapi32.zig @@ -0,0 +1,30 @@ +use @import("index.zig"); + +pub const PROV_RSA_FULL = 1; + +pub const REGSAM = ACCESS_MASK; +pub const ACCESS_MASK = DWORD; +pub const PHKEY = &HKEY; +pub const HKEY = &HKEY__; +pub const HKEY__ = extern struct { + unused: c_int, +}; +pub const LSTATUS = LONG; + +pub extern "advapi32" stdcallcc fn CryptAcquireContextA( + phProv: *HCRYPTPROV, + pszContainer: ?LPCSTR, + pszProvider: ?LPCSTR, + dwProvType: DWORD, + dwFlags: DWORD, +) BOOL; + +pub extern "advapi32" stdcallcc fn CryptReleaseContext(hProv: HCRYPTPROV, dwFlags: DWORD) BOOL; + +pub extern "advapi32" stdcallcc fn CryptGenRandom(hProv: HCRYPTPROV, dwLen: DWORD, pbBuffer: [*]BYTE) BOOL; + +pub extern "advapi32" stdcallcc fn RegOpenKeyExW(hKey: HKEY, lpSubKey: LPCWSTR, ulOptions: DWORD, samDesired: REGSAM, + phkResult: &HKEY,) LSTATUS; + +pub extern "advapi32" stdcallcc fn RegQueryValueExW(hKey: HKEY, lpValueName: LPCWSTR, lpReserved: LPDWORD, + lpType: LPDWORD, lpData: LPBYTE, lpcbData: LPDWORD,) LSTATUS; diff --git a/std/os/windows/index.zig b/std/os/windows/index.zig index d3525247c9..90ccfaf6c5 100644 --- a/std/os/windows/index.zig +++ b/std/os/windows/index.zig @@ -1,188 +1,19 @@ +const std = @import("../../index.zig"); +const assert = std.debug.assert; + +pub use @import("advapi32.zig"); +pub use @import("kernel32.zig"); +pub use @import("ole32.zig"); +pub use @import("shell32.zig"); +pub use @import("shlwapi.zig"); +pub use @import("user32.zig"); + test "import" { _ = @import("util.zig"); } pub const ERROR = @import("error.zig"); -pub extern "advapi32" stdcallcc fn CryptAcquireContextA( - phProv: *HCRYPTPROV, - pszContainer: ?LPCSTR, - pszProvider: ?LPCSTR, - dwProvType: DWORD, - dwFlags: DWORD, -) BOOL; - -pub extern "advapi32" stdcallcc fn CryptReleaseContext(hProv: HCRYPTPROV, dwFlags: DWORD) BOOL; - -pub extern "advapi32" stdcallcc fn CryptGenRandom(hProv: HCRYPTPROV, dwLen: DWORD, pbBuffer: [*]BYTE) BOOL; - -pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL; - -pub extern "kernel32" stdcallcc fn CreateDirectoryA( - lpPathName: LPCSTR, - lpSecurityAttributes: ?*SECURITY_ATTRIBUTES, -) BOOL; - -pub extern "kernel32" stdcallcc fn CreateFileA( - lpFileName: LPCSTR, - dwDesiredAccess: DWORD, - dwShareMode: DWORD, - lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES, - dwCreationDisposition: DWORD, - dwFlagsAndAttributes: DWORD, - hTemplateFile: ?HANDLE, -) HANDLE; - -pub extern "kernel32" stdcallcc fn CreatePipe( - hReadPipe: *HANDLE, - hWritePipe: *HANDLE, - lpPipeAttributes: *const SECURITY_ATTRIBUTES, - nSize: DWORD, -) BOOL; - -pub extern "kernel32" stdcallcc fn CreateProcessA( - lpApplicationName: ?LPCSTR, - lpCommandLine: LPSTR, - lpProcessAttributes: ?*SECURITY_ATTRIBUTES, - lpThreadAttributes: ?*SECURITY_ATTRIBUTES, - bInheritHandles: BOOL, - dwCreationFlags: DWORD, - lpEnvironment: ?*c_void, - lpCurrentDirectory: ?LPCSTR, - lpStartupInfo: *STARTUPINFOA, - lpProcessInformation: *PROCESS_INFORMATION, -) BOOL; - -pub extern "kernel32" stdcallcc fn CreateSymbolicLinkA( - lpSymlinkFileName: LPCSTR, - lpTargetFileName: LPCSTR, - 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; - -pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL; - -pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn; - -pub extern "kernel32" stdcallcc fn FindFirstFileA(lpFileName: LPCSTR, lpFindFileData: *WIN32_FIND_DATAA) HANDLE; -pub extern "kernel32" stdcallcc fn FindClose(hFindFile: HANDLE) BOOL; -pub extern "kernel32" stdcallcc fn FindNextFileA(hFindFile: HANDLE, lpFindFileData: *WIN32_FIND_DATAA) BOOL; - -pub extern "kernel32" stdcallcc fn FreeEnvironmentStringsA(penv: [*]u8) BOOL; - -pub extern "kernel32" stdcallcc fn GetCommandLineA() LPSTR; - -pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) BOOL; - -pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?LPSTR) DWORD; - -pub extern "kernel32" stdcallcc fn GetEnvironmentStringsA() ?[*]u8; - -pub extern "kernel32" stdcallcc fn GetEnvironmentVariableA(lpName: LPCSTR, lpBuffer: LPSTR, nSize: DWORD) DWORD; - -pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCode: *DWORD) BOOL; - -pub extern "kernel32" stdcallcc fn GetFileSizeEx(hFile: HANDLE, lpFileSize: *LARGE_INTEGER) BOOL; - -pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: LPCSTR) DWORD; - -pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: LPSTR, nSize: DWORD) DWORD; - -pub extern "kernel32" stdcallcc fn GetLastError() DWORD; - -pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx( - in_hFile: HANDLE, - in_FileInformationClass: FILE_INFO_BY_HANDLE_CLASS, - out_lpFileInformation: *c_void, - in_dwBufferSize: DWORD, -) BOOL; - -pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA( - hFile: HANDLE, - lpszFilePath: LPSTR, - cchFilePath: DWORD, - dwFlags: DWORD, -) DWORD; - -pub extern "kernel32" stdcallcc fn GetProcessHeap() ?HANDLE; -pub extern "kernel32" stdcallcc fn GetQueuedCompletionStatus(CompletionPort: HANDLE, lpNumberOfBytesTransferred: LPDWORD, lpCompletionKey: *ULONG_PTR, lpOverlapped: *?*OVERLAPPED, dwMilliseconds: DWORD) BOOL; - -pub extern "kernel32" stdcallcc fn GetSystemInfo(lpSystemInfo: *SYSTEM_INFO) void; -pub extern "kernel32" stdcallcc fn GetSystemTimeAsFileTime(*FILETIME) void; - -pub extern "kernel32" stdcallcc fn HeapCreate(flOptions: DWORD, dwInitialSize: SIZE_T, dwMaximumSize: SIZE_T) ?HANDLE; -pub extern "kernel32" stdcallcc fn HeapDestroy(hHeap: HANDLE) BOOL; -pub extern "kernel32" stdcallcc fn HeapReAlloc(hHeap: HANDLE, dwFlags: DWORD, lpMem: *c_void, dwBytes: SIZE_T) ?*c_void; -pub extern "kernel32" stdcallcc fn HeapSize(hHeap: HANDLE, dwFlags: DWORD, lpMem: *const c_void) SIZE_T; -pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: *const c_void) BOOL; -pub extern "kernel32" stdcallcc fn HeapCompact(hHeap: HANDLE, dwFlags: DWORD) SIZE_T; -pub extern "kernel32" stdcallcc fn HeapSummary(hHeap: HANDLE, dwFlags: DWORD, lpSummary: LPHEAP_SUMMARY) BOOL; - -pub extern "kernel32" stdcallcc fn GetStdHandle(in_nStdHandle: DWORD) ?HANDLE; - -pub extern "kernel32" stdcallcc fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) ?*c_void; - -pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: *c_void) BOOL; - -pub extern "kernel32" stdcallcc fn MoveFileExA( - lpExistingFileName: LPCSTR, - lpNewFileName: LPCSTR, - 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; - -pub extern "kernel32" stdcallcc fn QueryPerformanceFrequency(lpFrequency: *LARGE_INTEGER) BOOL; - -pub extern "kernel32" stdcallcc fn ReadFile( - in_hFile: HANDLE, - out_lpBuffer: *c_void, - in_nNumberOfBytesToRead: DWORD, - out_lpNumberOfBytesRead: *DWORD, - in_out_lpOverlapped: ?*OVERLAPPED, -) BOOL; - -pub extern "kernel32" stdcallcc fn RemoveDirectoryA(lpPathName: LPCSTR) BOOL; - -pub extern "kernel32" stdcallcc fn SetFilePointerEx( - in_fFile: HANDLE, - in_liDistanceToMove: LARGE_INTEGER, - out_opt_ldNewFilePointer: ?*LARGE_INTEGER, - in_dwMoveMethod: DWORD, -) BOOL; - -pub extern "kernel32" stdcallcc fn SetHandleInformation(hObject: HANDLE, dwMask: DWORD, dwFlags: DWORD) BOOL; - -pub extern "kernel32" stdcallcc fn Sleep(dwMilliseconds: DWORD) void; - -pub extern "kernel32" stdcallcc fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) BOOL; - -pub extern "kernel32" stdcallcc fn WaitForSingleObject(hHandle: HANDLE, dwMilliseconds: DWORD) DWORD; - -pub extern "kernel32" stdcallcc fn WriteFile( - in_hFile: HANDLE, - in_lpBuffer: *const c_void, - in_nNumberOfBytesToWrite: DWORD, - out_lpNumberOfBytesWritten: ?*DWORD, - in_out_lpOverlapped: ?*OVERLAPPED, -) BOOL; - -//TODO: call unicode versions instead of relying on ANSI code page -pub extern "kernel32" stdcallcc fn LoadLibraryA(lpLibFileName: LPCSTR) ?HMODULE; - -pub extern "kernel32" stdcallcc fn FreeLibrary(hModule: HMODULE) BOOL; - -pub extern "user32" stdcallcc fn MessageBoxA(hWnd: ?HANDLE, lpText: ?LPCTSTR, lpCaption: ?LPCTSTR, uType: UINT) c_int; - -pub extern "shlwapi" stdcallcc fn PathFileExistsA(pszPath: ?LPCTSTR) BOOL; - -pub const PROV_RSA_FULL = 1; - pub const BOOL = c_int; pub const BOOLEAN = BYTE; pub const BYTE = u8; @@ -204,6 +35,7 @@ pub const LPSTR = [*]CHAR; pub const LPTSTR = if (UNICODE) LPWSTR else LPSTR; pub const LPVOID = *c_void; pub const LPWSTR = [*]WCHAR; +pub const LPCWSTR = [*]const WCHAR; pub const PVOID = *c_void; pub const PWSTR = [*]WCHAR; pub const SIZE_T = usize; @@ -439,3 +271,82 @@ pub const SYSTEM_INFO = extern struct { wProcessorLevel: WORD, wProcessorRevision: WORD, }; + +pub const HRESULT = c_long; + +pub const KNOWNFOLDERID = GUID; +pub const GUID = extern struct { + Data1: c_ulong, + Data2: c_ushort, + Data3: c_ushort, + Data4: [8]u8, + + pub fn parse(str: []const u8) GUID { + var guid: GUID = undefined; + var index: usize = 0; + assert(str[index] == '{'); + index += 1; + + guid.Data1 = std.fmt.parseUnsigned(c_ulong, str[index..index + 8], 16) catch unreachable; + index += 8; + + assert(str[index] == '-'); + index += 1; + + guid.Data2 = std.fmt.parseUnsigned(c_ushort, str[index..index + 4], 16) catch unreachable; + index += 4; + + assert(str[index] == '-'); + index += 1; + + guid.Data3 = std.fmt.parseUnsigned(c_ushort, str[index..index + 4], 16) catch unreachable; + index += 4; + + assert(str[index] == '-'); + index += 1; + + guid.Data4[0] = std.fmt.parseUnsigned(u8, str[index..index + 2], 16) catch unreachable; + index += 2; + guid.Data4[1] = std.fmt.parseUnsigned(u8, str[index..index + 2], 16) catch unreachable; + index += 2; + + assert(str[index] == '-'); + index += 1; + + var i: usize = 2; + while (i < guid.Data4.len) : (i += 1) { + guid.Data4[i] = std.fmt.parseUnsigned(u8, str[index..index + 2], 16) catch unreachable; + index += 2; + } + + assert(str[index] == '}'); + index += 1; + return guid; + } +}; + +pub const FOLDERID_LocalAppData = GUID.parse("{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}"); + +pub const KF_FLAG_DEFAULT = 0; +pub const KF_FLAG_NO_APPCONTAINER_REDIRECTION = 65536; +pub const KF_FLAG_CREATE = 32768; +pub const KF_FLAG_DONT_VERIFY = 16384; +pub const KF_FLAG_DONT_UNEXPAND = 8192; +pub const KF_FLAG_NO_ALIAS = 4096; +pub const KF_FLAG_INIT = 2048; +pub const KF_FLAG_DEFAULT_PATH = 1024; +pub const KF_FLAG_NOT_PARENT_RELATIVE = 512; +pub const KF_FLAG_SIMPLE_IDLIST = 256; +pub const KF_FLAG_ALIAS_ONLY = -2147483648; + +pub const S_OK = 0; +pub const E_NOTIMPL = @bitCast(c_long, c_ulong(0x80004001)); +pub const E_NOINTERFACE = @bitCast(c_long, c_ulong(0x80004002)); +pub const E_POINTER = @bitCast(c_long, c_ulong(0x80004003)); +pub const E_ABORT = @bitCast(c_long, c_ulong(0x80004004)); +pub const E_FAIL = @bitCast(c_long, c_ulong(0x80004005)); +pub const E_UNEXPECTED = @bitCast(c_long, c_ulong(0x8000FFFF)); +pub const E_ACCESSDENIED = @bitCast(c_long, c_ulong(0x80070005)); +pub const E_HANDLE = @bitCast(c_long, c_ulong(0x80070006)); +pub const E_OUTOFMEMORY = @bitCast(c_long, c_ulong(0x8007000E)); +pub const E_INVALIDARG = @bitCast(c_long, c_ulong(0x80070057)); diff --git a/std/os/windows/kernel32.zig b/std/os/windows/kernel32.zig new file mode 100644 index 0000000000..fa3473ad05 --- /dev/null +++ b/std/os/windows/kernel32.zig @@ -0,0 +1,162 @@ +use @import("index.zig"); + +pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL; + +pub extern "kernel32" stdcallcc fn CreateDirectoryA( + lpPathName: LPCSTR, + lpSecurityAttributes: ?*SECURITY_ATTRIBUTES, +) BOOL; + +pub extern "kernel32" stdcallcc fn CreateFileA( + lpFileName: LPCSTR, + dwDesiredAccess: DWORD, + dwShareMode: DWORD, + lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES, + dwCreationDisposition: DWORD, + dwFlagsAndAttributes: DWORD, + hTemplateFile: ?HANDLE, +) HANDLE; + +pub extern "kernel32" stdcallcc fn CreatePipe( + hReadPipe: *HANDLE, + hWritePipe: *HANDLE, + lpPipeAttributes: *const SECURITY_ATTRIBUTES, + nSize: DWORD, +) BOOL; + +pub extern "kernel32" stdcallcc fn CreateProcessA( + lpApplicationName: ?LPCSTR, + lpCommandLine: LPSTR, + lpProcessAttributes: ?*SECURITY_ATTRIBUTES, + lpThreadAttributes: ?*SECURITY_ATTRIBUTES, + bInheritHandles: BOOL, + dwCreationFlags: DWORD, + lpEnvironment: ?*c_void, + lpCurrentDirectory: ?LPCSTR, + lpStartupInfo: *STARTUPINFOA, + lpProcessInformation: *PROCESS_INFORMATION, +) BOOL; + +pub extern "kernel32" stdcallcc fn CreateSymbolicLinkA( + lpSymlinkFileName: LPCSTR, + lpTargetFileName: LPCSTR, + 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; + +pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL; + +pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn; + +pub extern "kernel32" stdcallcc fn FindFirstFileA(lpFileName: LPCSTR, lpFindFileData: *WIN32_FIND_DATAA) HANDLE; +pub extern "kernel32" stdcallcc fn FindClose(hFindFile: HANDLE) BOOL; +pub extern "kernel32" stdcallcc fn FindNextFileA(hFindFile: HANDLE, lpFindFileData: *WIN32_FIND_DATAA) BOOL; + +pub extern "kernel32" stdcallcc fn FreeEnvironmentStringsA(penv: [*]u8) BOOL; + +pub extern "kernel32" stdcallcc fn GetCommandLineA() LPSTR; + +pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) BOOL; + +pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?LPSTR) DWORD; + +pub extern "kernel32" stdcallcc fn GetEnvironmentStringsA() ?[*]u8; + +pub extern "kernel32" stdcallcc fn GetEnvironmentVariableA(lpName: LPCSTR, lpBuffer: LPSTR, nSize: DWORD) DWORD; + +pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCode: *DWORD) BOOL; + +pub extern "kernel32" stdcallcc fn GetFileSizeEx(hFile: HANDLE, lpFileSize: *LARGE_INTEGER) BOOL; + +pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: LPCSTR) DWORD; + +pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: LPSTR, nSize: DWORD) DWORD; + +pub extern "kernel32" stdcallcc fn GetLastError() DWORD; + +pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx( + in_hFile: HANDLE, + in_FileInformationClass: FILE_INFO_BY_HANDLE_CLASS, + out_lpFileInformation: *c_void, + in_dwBufferSize: DWORD, +) BOOL; + +pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA( + hFile: HANDLE, + lpszFilePath: LPSTR, + cchFilePath: DWORD, + dwFlags: DWORD, +) DWORD; + +pub extern "kernel32" stdcallcc fn GetProcessHeap() ?HANDLE; +pub extern "kernel32" stdcallcc fn GetQueuedCompletionStatus(CompletionPort: HANDLE, lpNumberOfBytesTransferred: LPDWORD, lpCompletionKey: *ULONG_PTR, lpOverlapped: *?*OVERLAPPED, dwMilliseconds: DWORD) BOOL; + +pub extern "kernel32" stdcallcc fn GetSystemInfo(lpSystemInfo: *SYSTEM_INFO) void; +pub extern "kernel32" stdcallcc fn GetSystemTimeAsFileTime(*FILETIME) void; + +pub extern "kernel32" stdcallcc fn HeapCreate(flOptions: DWORD, dwInitialSize: SIZE_T, dwMaximumSize: SIZE_T) ?HANDLE; +pub extern "kernel32" stdcallcc fn HeapDestroy(hHeap: HANDLE) BOOL; +pub extern "kernel32" stdcallcc fn HeapReAlloc(hHeap: HANDLE, dwFlags: DWORD, lpMem: *c_void, dwBytes: SIZE_T) ?*c_void; +pub extern "kernel32" stdcallcc fn HeapSize(hHeap: HANDLE, dwFlags: DWORD, lpMem: *const c_void) SIZE_T; +pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: *const c_void) BOOL; +pub extern "kernel32" stdcallcc fn HeapCompact(hHeap: HANDLE, dwFlags: DWORD) SIZE_T; +pub extern "kernel32" stdcallcc fn HeapSummary(hHeap: HANDLE, dwFlags: DWORD, lpSummary: LPHEAP_SUMMARY) BOOL; + +pub extern "kernel32" stdcallcc fn GetStdHandle(in_nStdHandle: DWORD) ?HANDLE; + +pub extern "kernel32" stdcallcc fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) ?*c_void; + +pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: *c_void) BOOL; + +pub extern "kernel32" stdcallcc fn MoveFileExA( + lpExistingFileName: LPCSTR, + lpNewFileName: LPCSTR, + 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; + +pub extern "kernel32" stdcallcc fn QueryPerformanceFrequency(lpFrequency: *LARGE_INTEGER) BOOL; + +pub extern "kernel32" stdcallcc fn ReadFile( + in_hFile: HANDLE, + out_lpBuffer: *c_void, + in_nNumberOfBytesToRead: DWORD, + out_lpNumberOfBytesRead: *DWORD, + in_out_lpOverlapped: ?*OVERLAPPED, +) BOOL; + +pub extern "kernel32" stdcallcc fn RemoveDirectoryA(lpPathName: LPCSTR) BOOL; + +pub extern "kernel32" stdcallcc fn SetFilePointerEx( + in_fFile: HANDLE, + in_liDistanceToMove: LARGE_INTEGER, + out_opt_ldNewFilePointer: ?*LARGE_INTEGER, + in_dwMoveMethod: DWORD, +) BOOL; + +pub extern "kernel32" stdcallcc fn SetHandleInformation(hObject: HANDLE, dwMask: DWORD, dwFlags: DWORD) BOOL; + +pub extern "kernel32" stdcallcc fn Sleep(dwMilliseconds: DWORD) void; + +pub extern "kernel32" stdcallcc fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) BOOL; + +pub extern "kernel32" stdcallcc fn WaitForSingleObject(hHandle: HANDLE, dwMilliseconds: DWORD) DWORD; + +pub extern "kernel32" stdcallcc fn WriteFile( + in_hFile: HANDLE, + in_lpBuffer: *const c_void, + in_nNumberOfBytesToWrite: DWORD, + out_lpNumberOfBytesWritten: ?*DWORD, + in_out_lpOverlapped: ?*OVERLAPPED, +) BOOL; + +//TODO: call unicode versions instead of relying on ANSI code page +pub extern "kernel32" stdcallcc fn LoadLibraryA(lpLibFileName: LPCSTR) ?HMODULE; + +pub extern "kernel32" stdcallcc fn FreeLibrary(hModule: HMODULE) BOOL; diff --git a/std/os/windows/ole32.zig b/std/os/windows/ole32.zig new file mode 100644 index 0000000000..84d8089d07 --- /dev/null +++ b/std/os/windows/ole32.zig @@ -0,0 +1,18 @@ +use @import("index.zig"); + +pub extern "ole32.dll" stdcallcc fn CoTaskMemFree(pv: LPVOID) void; +pub extern "ole32.dll" stdcallcc fn CoUninitialize() void; +pub extern "ole32.dll" stdcallcc fn CoGetCurrentProcess() DWORD; +pub extern "ole32.dll" stdcallcc fn CoInitializeEx(pvReserved: LPVOID, dwCoInit: DWORD) HRESULT; + + +pub const COINIT_APARTMENTTHREADED = COINIT.COINIT_APARTMENTTHREADED; +pub const COINIT_MULTITHREADED = COINIT.COINIT_MULTITHREADED; +pub const COINIT_DISABLE_OLE1DDE = COINIT.COINIT_DISABLE_OLE1DDE; +pub const COINIT_SPEED_OVER_MEMORY = COINIT.COINIT_SPEED_OVER_MEMORY; +pub const COINIT = extern enum { + COINIT_APARTMENTTHREADED = 2, + COINIT_MULTITHREADED = 0, + COINIT_DISABLE_OLE1DDE = 4, + COINIT_SPEED_OVER_MEMORY = 8, +}; diff --git a/std/os/windows/shell32.zig b/std/os/windows/shell32.zig new file mode 100644 index 0000000000..f10466add3 --- /dev/null +++ b/std/os/windows/shell32.zig @@ -0,0 +1,4 @@ +use @import("index.zig"); + +pub extern "shell32.dll" stdcallcc fn SHGetKnownFolderPath(rfid: *const KNOWNFOLDERID, dwFlags: DWORD, hToken: ?HANDLE, ppszPath: *[*]WCHAR) HRESULT; + diff --git a/std/os/windows/shlwapi.zig b/std/os/windows/shlwapi.zig new file mode 100644 index 0000000000..6bccefaf98 --- /dev/null +++ b/std/os/windows/shlwapi.zig @@ -0,0 +1,4 @@ +use @import("index.zig"); + +pub extern "shlwapi" stdcallcc fn PathFileExistsA(pszPath: ?LPCTSTR) BOOL; + diff --git a/std/os/windows/user32.zig b/std/os/windows/user32.zig new file mode 100644 index 0000000000..37f9f6f3b8 --- /dev/null +++ b/std/os/windows/user32.zig @@ -0,0 +1,4 @@ +use @import("index.zig"); + +pub extern "user32" stdcallcc fn MessageBoxA(hWnd: ?HANDLE, lpText: ?LPCTSTR, lpCaption: ?LPCTSTR, uType: UINT) c_int; + diff --git a/std/special/test_runner.zig b/std/special/test_runner.zig index 76a54a5018..857739e82d 100644 --- a/std/special/test_runner.zig +++ b/std/special/test_runner.zig @@ -5,11 +5,25 @@ const test_fn_list = builtin.__zig_test_fn_slice; const warn = std.debug.warn; pub fn main() !void { + var ok_count: usize = 0; + var skip_count: usize = 0; for (test_fn_list) |test_fn, i| { warn("Test {}/{} {}...", i + 1, test_fn_list.len, test_fn.name); - try test_fn.func(); - - warn("OK\n"); + if (test_fn.func()) |_| { + ok_count += 1; + warn("OK\n"); + } else |err| switch (err) { + error.SkipZigTest => { + skip_count += 1; + warn("SKIP\n"); + }, + else => return err, + } + } + if (ok_count == test_fn_list.len) { + warn("All tests passed.\n"); + } else { + warn("{} passed; {} skipped.\n", ok_count, skip_count); } } diff --git a/std/unicode.zig b/std/unicode.zig index 9c329acc68..8a9d4a9214 100644 --- a/std/unicode.zig +++ b/std/unicode.zig @@ -1,5 +1,8 @@ const std = @import("./index.zig"); +const builtin = @import("builtin"); const debug = std.debug; +const assert = std.debug.assert; +const mem = std.mem; /// Returns how many bytes the UTF-8 representation would require /// for the given codepoint. @@ -441,3 +444,89 @@ fn testDecode(bytes: []const u8) !u32 { debug.assert(bytes.len == length); return utf8Decode(bytes); } + +// TODO: make this API on top of a non-allocating Utf16LeView +pub fn utf16leToUtf8(allocator: *mem.Allocator, utf16le: []const u16) ![]u8 { + var result = std.ArrayList(u8).init(allocator); + // optimistically guess that it will all be ascii. + try result.ensureCapacity(utf16le.len); + + const utf16le_as_bytes = @sliceToBytes(utf16le); + var i: usize = 0; + var out_index: usize = 0; + while (i < utf16le_as_bytes.len) : (i += 2) { + // decode + const c0: u32 = mem.readIntLE(u16, utf16le_as_bytes[i..i + 2]); + var codepoint: u32 = undefined; + if (c0 & ~u32(0x03ff) == 0xd800) { + // surrogate pair + i += 2; + if (i >= utf16le_as_bytes.len) return error.DanglingSurrogateHalf; + const c1: u32 = mem.readIntLE(u16, utf16le_as_bytes[i..i + 2]); + if (c1 & ~u32(0x03ff) != 0xdc00) return error.ExpectedSecondSurrogateHalf; + codepoint = 0x10000 + (((c0 & 0x03ff) << 10) | (c1 & 0x03ff)); + } else if (c0 & ~u32(0x03ff) == 0xdc00) { + return error.UnexpectedSecondSurrogateHalf; + } else { + codepoint = c0; + } + + // encode + const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable; + try result.resize(result.len + utf8_len); + _ = utf8Encode(codepoint, result.items[out_index..]) catch unreachable; + out_index += utf8_len; + } + + return result.toOwnedSlice(); +} + +test "utf16leToUtf8" { + var utf16le: [2]u16 = undefined; + const utf16le_as_bytes = @sliceToBytes(utf16le[0..]); + + { + mem.writeInt(utf16le_as_bytes[0..], u16('A'), builtin.Endian.Little); + mem.writeInt(utf16le_as_bytes[2..], u16('a'), builtin.Endian.Little); + const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); + assert(mem.eql(u8, utf8, "Aa")); + } + + { + mem.writeInt(utf16le_as_bytes[0..], u16(0x80), builtin.Endian.Little); + mem.writeInt(utf16le_as_bytes[2..], u16(0xffff), builtin.Endian.Little); + const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); + assert(mem.eql(u8, utf8, "\xc2\x80" ++ "\xef\xbf\xbf")); + } + + { + // the values just outside the surrogate half range + mem.writeInt(utf16le_as_bytes[0..], u16(0xd7ff), builtin.Endian.Little); + mem.writeInt(utf16le_as_bytes[2..], u16(0xe000), builtin.Endian.Little); + const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); + assert(mem.eql(u8, utf8, "\xed\x9f\xbf" ++ "\xee\x80\x80")); + } + + { + // smallest surrogate pair + mem.writeInt(utf16le_as_bytes[0..], u16(0xd800), builtin.Endian.Little); + mem.writeInt(utf16le_as_bytes[2..], u16(0xdc00), builtin.Endian.Little); + const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); + assert(mem.eql(u8, utf8, "\xf0\x90\x80\x80")); + } + + { + // largest surrogate pair + mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little); + mem.writeInt(utf16le_as_bytes[2..], u16(0xdfff), builtin.Endian.Little); + const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); + assert(mem.eql(u8, utf8, "\xf4\x8f\xbf\xbf")); + } + + { + mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little); + mem.writeInt(utf16le_as_bytes[2..], u16(0xdc00), builtin.Endian.Little); + const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); + assert(mem.eql(u8, utf8, "\xf4\x8f\xb0\x80")); + } +} diff --git a/std/zig/index.zig b/std/zig/index.zig index 4dd68fa8b3..da84bc5bb0 100644 --- a/std/zig/index.zig +++ b/std/zig/index.zig @@ -2,6 +2,7 @@ const tokenizer = @import("tokenizer.zig"); pub const Token = tokenizer.Token; pub const Tokenizer = tokenizer.Tokenizer; pub const parse = @import("parse.zig").parse; +pub const parseStringLiteral = @import("parse_string_literal.zig").parseStringLiteral; pub const render = @import("render.zig").render; pub const ast = @import("ast.zig"); @@ -10,4 +11,6 @@ test "std.zig tests" { _ = @import("parse.zig"); _ = @import("render.zig"); _ = @import("tokenizer.zig"); + _ = @import("parse_string_literal.zig"); } + diff --git a/std/zig/parse.zig b/std/zig/parse.zig index 9842ba2a17..73d51e7870 100644 --- a/std/zig/parse.zig +++ b/std/zig/parse.zig @@ -2356,7 +2356,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { const token = nextToken(&tok_it, &tree); switch (token.ptr.id) { Token.Id.IntegerLiteral => { - _ = try createToCtxLiteral(arena, opt_ctx, ast.Node.StringLiteral, token.index); + _ = try createToCtxLiteral(arena, opt_ctx, ast.Node.IntegerLiteral, token.index); continue; }, Token.Id.FloatLiteral => { diff --git a/std/zig/parse_string_literal.zig b/std/zig/parse_string_literal.zig new file mode 100644 index 0000000000..00c92a7651 --- /dev/null +++ b/std/zig/parse_string_literal.zig @@ -0,0 +1,76 @@ +const std = @import("../index.zig"); +const assert = std.debug.assert; + +const State = enum { + Start, + Backslash, +}; + +pub const ParseStringLiteralError = error{ + OutOfMemory, + + /// When this is returned, index will be the position of the character. + InvalidCharacter, +}; + +/// caller owns returned memory +pub fn parseStringLiteral( + allocator: *std.mem.Allocator, + bytes: []const u8, + bad_index: *usize, // populated if error.InvalidCharacter is returned +) ParseStringLiteralError![]u8 { + const first_index = if (bytes[0] == 'c') usize(2) else usize(1); + assert(bytes[bytes.len - 1] == '"'); + + var list = std.ArrayList(u8).init(allocator); + errdefer list.deinit(); + + const slice = bytes[first_index..]; + try list.ensureCapacity(slice.len - 1); + + var state = State.Start; + for (slice) |b, index| { + switch (state) { + State.Start => switch (b) { + '\\' => state = State.Backslash, + '\n' => { + bad_index.* = index; + return error.InvalidCharacter; + }, + '"' => return list.toOwnedSlice(), + else => try list.append(b), + }, + State.Backslash => switch (b) { + 'x' => @panic("TODO"), + 'u' => @panic("TODO"), + 'U' => @panic("TODO"), + 'n' => { + try list.append('\n'); + state = State.Start; + }, + 'r' => { + try list.append('\r'); + state = State.Start; + }, + '\\' => { + try list.append('\\'); + state = State.Start; + }, + 't' => { + try list.append('\t'); + state = State.Start; + }, + '"' => { + try list.append('"'); + state = State.Start; + }, + else => { + bad_index.* = index; + return error.InvalidCharacter; + }, + }, + else => unreachable, + } + } + unreachable; +} diff --git a/std/zig/tokenizer.zig b/std/zig/tokenizer.zig index 79f1871b64..3c7ab1f0a8 100644 --- a/std/zig/tokenizer.zig +++ b/std/zig/tokenizer.zig @@ -73,6 +73,7 @@ pub const Token = struct { return null; } + /// TODO remove this enum const StrLitKind = enum { Normal, C, diff --git a/test/cases/cast.zig b/test/cases/cast.zig index 5688d90e11..63cc6313e1 100644 --- a/test/cases/cast.zig +++ b/test/cases/cast.zig @@ -468,3 +468,20 @@ test "@intCast i32 to u7" { var z = x >> @intCast(u7, y); assert(z == 0xff); } + +test "implicit cast undefined to optional" { + assert(MakeType(void).getNull() == null); + assert(MakeType(void).getNonNull() != null); +} + +fn MakeType(comptime T: type) type { + return struct { + fn getNull() ?T { + return null; + } + + fn getNonNull() ?T { + return T(undefined); + } + }; +} diff --git a/test/cases/defer.zig b/test/cases/defer.zig index d2b00d1f91..7d4d1bc3d8 100644 --- a/test/cases/defer.zig +++ b/test/cases/defer.zig @@ -61,3 +61,18 @@ test "defer and labeled break" { assert(i == 1); } + +test "errdefer does not apply to fn inside fn" { + if (testNestedFnErrDefer()) |_| @panic("expected error") else |e| assert(e == error.Bad); +} + +fn testNestedFnErrDefer() error!void { + var a: i32 = 0; + errdefer a += 1; + const S = struct { + fn baz() error { + return error.Bad; + } + }; + return S.baz(); +} diff --git a/test/cases/eval.zig b/test/cases/eval.zig index 83d2e80176..9da475994d 100644 --- a/test/cases/eval.zig +++ b/test/cases/eval.zig @@ -642,3 +642,13 @@ test "@tagName of @typeId" { const str = @tagName(@typeId(u8)); assert(std.mem.eql(u8, str, "Int")); } + +test "setting backward branch quota just before a generic fn call" { + @setEvalBranchQuota(1001); + loopNTimes(1001); +} + +fn loopNTimes(comptime n: usize) void { + comptime var i = 0; + inline while (i < n) : (i += 1) {} +} diff --git a/test/gen_h.zig b/test/gen_h.zig index e6a757ea6d..b3aaa263d6 100644 --- a/test/gen_h.zig +++ b/test/gen_h.zig @@ -76,4 +76,51 @@ pub fn addCases(cases: *tests.GenHContext) void { \\TEST_EXPORT void entry(struct Foo foo, uint8_t bar[]); \\ ); + + cases.add("ptr to zig struct", + \\const S = struct { + \\ a: u8, + \\}; + \\ + \\export fn a(s: *S) u8 { + \\ return s.a; + \\} + + , + \\struct S; + \\TEST_EXPORT uint8_t a(struct S * s); + \\ + ); + + cases.add("ptr to zig union", + \\const U = union(enum) { + \\ A: u8, + \\ B: u16, + \\}; + \\ + \\export fn a(s: *U) u8 { + \\ return s.A; + \\} + + , + \\union U; + \\TEST_EXPORT uint8_t a(union U * s); + \\ + ); + + cases.add("ptr to zig enum", + \\const E = enum(u8) { + \\ A, + \\ B, + \\}; + \\ + \\export fn a(s: *E) u8 { + \\ return @enumToInt(s.*); + \\} + + , + \\enum E; + \\TEST_EXPORT uint8_t a(enum E * s); + \\ + ); } diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig new file mode 100644 index 0000000000..35adcbb96b --- /dev/null +++ b/test/stage2/compare_output.zig @@ -0,0 +1,12 @@ +const std = @import("std"); +const TestContext = @import("../../src-self-hosted/test.zig").TestContext; + +pub fn addCases(ctx: *TestContext) !void { + try ctx.testCompareOutputLibC( + \\extern fn puts([*]const u8) void; + \\export fn main() c_int { + \\ puts(c"Hello, world!"); + \\ return 0; + \\} + , "Hello, world!" ++ std.cstr.line_sep); +} diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 1dca908e69..2cecd78653 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -9,4 +9,22 @@ pub fn addCases(ctx: *TestContext) !void { try ctx.testCompileError( \\fn() void {} , "1.zig", 1, 1, "missing function name"); + + try ctx.testCompileError( + \\comptime { + \\ return; + \\} + , "1.zig", 2, 5, "return expression outside function definition"); + + try ctx.testCompileError( + \\export fn entry() void { + \\ defer return; + \\} + , "1.zig", 2, 11, "cannot return from defer expression"); + + try ctx.testCompileError( + \\export fn entry() c_int { + \\ return 36893488147419103232; + \\} + , "1.zig", 2, 12, "integer value '36893488147419103232' cannot be stored in type 'c_int'"); } diff --git a/test/tests.zig b/test/tests.zig index 3a72f58753..aa5eed17ee 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -89,12 +89,13 @@ pub fn addCompileErrorTests(b: *build.Builder, test_filter: ?[]const u8, modes: return cases.step; } -pub fn addBuildExampleTests(b: *build.Builder, test_filter: ?[]const u8) *build.Step { +pub fn addBuildExampleTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step { const cases = b.allocator.create(BuildExamplesContext{ .b = b, .step = b.step("test-build-examples", "Build the examples"), .test_index = 0, .test_filter = test_filter, + .modes = modes, }) catch unreachable; build_examples.addCases(cases); @@ -697,6 +698,7 @@ pub const BuildExamplesContext = struct { step: *build.Step, test_index: usize, test_filter: ?[]const u8, + modes: []const Mode, pub fn addC(self: *BuildExamplesContext, root_src: []const u8) void { self.addAllArgs(root_src, true); @@ -739,12 +741,7 @@ pub const BuildExamplesContext = struct { pub fn addAllArgs(self: *BuildExamplesContext, root_src: []const u8, link_libc: bool) void { const b = self.b; - for ([]Mode{ - Mode.Debug, - Mode.ReleaseSafe, - Mode.ReleaseFast, - Mode.ReleaseSmall, - }) |mode| { + for (self.modes) |mode| { const annotated_case_name = fmt.allocPrint(self.b.allocator, "build {} ({})", root_src, @tagName(mode)) catch unreachable; if (self.test_filter) |filter| { if (mem.indexOf(u8, annotated_case_name, filter) == null) continue;