diff --git a/CMakeLists.txt b/CMakeLists.txt index 137cefb5b4..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 @@ -489,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" @@ -566,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" @@ -616,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 99e224c367..6e582a27e7 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 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 f8233bc795..ad3dce061e 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -15,7 +15,7 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) defer fn_val.base.deref(comp); defer code.destroy(comp.gpa()); - var output_path = try await (async comp.createRandomOutputPath(comp.target.oFileExt()) catch unreachable); + var output_path = try await (async comp.createRandomOutputPath(comp.target.objFileExt()) catch unreachable); errdefer output_path.deinit(); const llvm_handle = try comp.event_loop_local.getAnyLlvmContext(); @@ -78,6 +78,7 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) .dibuilder = dibuilder, .context = context, .lock = event.Lock.init(comp.loop), + .arena = &code.arena.allocator, }; try renderToLlvmModule(&ofile, fn_val, code); @@ -139,6 +140,7 @@ pub const ObjectFile = struct { dibuilder: *llvm.DIBuilder, context: llvm.ContextRef, lock: event.Lock, + arena: *std.mem.Allocator, fn gpa(self: *ObjectFile) *std.mem.Allocator { return self.comp.gpa(); @@ -147,7 +149,7 @@ pub const ObjectFile = struct { pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) !void { // TODO audit more of codegen.cpp:fn_llvm_value and port more logic - const llvm_fn_type = try fn_val.base.typeof.getLlvmType(ofile); + const llvm_fn_type = try fn_val.base.typ.getLlvmType(ofile.arena, ofile.context); const llvm_fn = llvm.AddFunction( ofile.module, fn_val.symbol_name.ptr(), @@ -165,7 +167,7 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) // try addLLVMFnAttrInt(ofile, llvm_fn, "alignstack", align_stack); //} - const fn_type = fn_val.base.typeof.cast(Type.Fn).?; + const fn_type = fn_val.base.typ.cast(Type.Fn).?; try addLLVMFnAttr(ofile, llvm_fn, "nounwind"); //add_uwtable_attr(g, fn_table_entry->llvm_value); diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig index 741324c871..093aab21da 100644 --- a/src-self-hosted/compilation.zig +++ b/src-self-hosted/compilation.zig @@ -21,13 +21,15 @@ 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 { @@ -37,6 +39,8 @@ pub const EventLoopLocal = struct { /// 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 { @@ -48,13 +52,16 @@ pub const EventLoopLocal = struct { 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); @@ -78,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 { @@ -108,13 +122,6 @@ pub const Compilation = struct { 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, @@ -179,6 +186,8 @@ 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, @@ -188,6 +197,7 @@ pub const Compilation = struct { target_machine: llvm.TargetMachineRef, target_data_ref: llvm.TargetDataRef, target_layout_str: [*]u8, + target_ptr_bits: u32, /// for allocating things which have the same lifetime as this Compilation arena_allocator: std.heap.ArenaAllocator, @@ -195,7 +205,30 @@ pub const Compilation = struct { root_package: *Package, std_package: *Package, - const CompileErrList = std.ArrayList(*errmsg.Msg); + 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{ @@ -240,12 +273,16 @@ pub const Compilation = struct { 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) { @@ -284,7 +321,6 @@ pub const Compilation = struct { build_mode: builtin.Mode, is_static: bool, zig_lib_dir: []const u8, - cache_dir: []const u8, ) !*Compilation { const loop = event_loop_local.loop; const comp = try event_loop_local.loop.allocator.create(Compilation{ @@ -299,7 +335,6 @@ pub const Compilation = struct { .build_mode = build_mode, .zig_lib_dir = zig_lib_dir, .zig_std_dir = undefined, - .cache_dir = cache_dir, .tmp_dir = event.Future(BuildError![]u8).init(loop), .name = undefined, @@ -318,12 +353,6 @@ 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, @@ -350,7 +379,12 @@ pub const Compilation = struct { .link_out_file = null, .exported_symbol_names = event.Locked(Decl.Table).init(loop, Decl.Table.init(loop.allocator)), .prelink_group = event.Group(BuildError!void).init(loop), + .deinit_group = event.Group(void).init(loop), .compile_errors = event.Locked(CompileErrList).init(loop, CompileErrList.init(loop.allocator)), + .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, @@ -360,15 +394,26 @@ 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); } @@ -378,6 +423,7 @@ pub const Compilation = struct { 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, @@ -431,123 +477,221 @@ pub const Compilation = struct { 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.gpa().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.gpa().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.gpa().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.gpa().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.gpa().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.gpa().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.gpa().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.gpa().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.gpa().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.gpa().destroy(comp.void_value); - comp.true_value = try comp.gpa().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.gpa().destroy(comp.true_value); - comp.false_value = try comp.gpa().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.gpa().destroy(comp.false_value); - comp.noreturn_value = try comp.gpa().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.gpa().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); } - pub fn destroy(self: *Compilation) void { + /// 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.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(); 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 { + resume self.destroy_handle; + } + pub fn build(self: *Compilation) !void { if (self.llvm_argv.len != 0) { var c_compatible_args = try std.cstr.NullTerminated2DArray.fromSlices(self.arena(), [][]const []const u8{ @@ -597,79 +741,103 @@ pub const Compilation = struct { } async fn compileAndLink(self: *Compilation) !void { - const root_src_path = self.root_src_path orelse @panic("TODO handle null root src path"); - // TODO async/await os.path.real - const root_src_real_path = os.path.real(self.gpa(), root_src_path) catch |err| { - try printError("unable to get real path '{}': {}", root_src_path, err); - return err; - }; - errdefer self.gpa().free(root_src_real_path); + 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.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); + // 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.gpa().create(ParsedFile{ - .tree = undefined, - .realpath = root_src_real_path, - }); - errdefer self.gpa().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.gpa(), 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.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), - .resolution_in_progress = 0, - .parsed_file = parsed_file, - .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, &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(); } - try await (async decl_group.wait() catch unreachable); - try await (async self.prelink_group.wait() catch unreachable); + + (await (async self.prelink_group.wait() catch unreachable)) catch |err| switch (err) { + error.SemanticAnalysisFailed => {}, + else => return err, + }; const any_prelink_errors = blk: { const compile_errors = await (async self.compile_errors.acquire() catch unreachable); @@ -679,39 +847,108 @@ pub const Compilation = struct { }; if (!any_prelink_errors) { - try link(self); + try await (async link(self) 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.prelink_group.call(verifyUniqueSymbol, self, decl); try self.prelink_group.call(resolveDecl, self, decl); } + + add_to_table_resolved = true; + try await add_to_table; } - 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); + async fn addDeclToTable(self: *Compilation, decls: *Scope.Decls, decl: *Decl) !void { + const held = await (async decls.table.acquire() catch unreachable); + defer held.release(); - try self.prelink_group.call(addCompileErrorAsync, self, parsed_file, span, text); + if (try held.value.put(decl.name, decl)) |other_decl| { + try self.addCompileError(decls.base.findRoot(), decl.getSpan(), "redefinition of '{}'", decl.name); + // TODO note: other definition here + } + } + + fn addCompileError(self: *Compilation, root: *Scope.Root, span: Span, comptime fmt: []const u8, args: ...) !void { + const text = try std.fmt.allocPrint(self.gpa(), fmt, args); + errdefer self.gpa().free(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(); @@ -725,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, @@ -762,10 +999,22 @@ pub const Compilation = struct { 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; } + /// 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; @@ -831,6 +1080,37 @@ pub const Compilation = struct { 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 { @@ -850,15 +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 (await (async decl.resolution.start() catch unreachable)) |ptr| return ptr.*; - - decl.resolution.data = await (async generateDecl(comp, decl) catch unreachable); - decl.resolution.resolve(); - return decl.resolution.data; -} - /// The function that actually does the generation. async fn generateDecl(comp: *Compilation, decl: *Decl) !void { switch (decl.id) { @@ -872,66 +1143,30 @@ 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.gpa(), fn_decl.base.name); - errdefer symbol_name.deinit(); + var symbol_name_consumed = false; + errdefer if (!symbol_name_consumed) symbol_name.deinit(); // The Decl.Fn owns the initial 1 reference count const fn_val = try Value.Fn.create(comp, fn_type, fndef_scope, symbol_name); - fn_decl.value = Decl.Fn.Val{ .Ok = fn_val }; + fn_decl.value = Decl.Fn.Val{ .Fn = fn_val }; + symbol_name_consumed = true; - const 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.gpa()); - - 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, - }; + body_node, + fn_type.return_type, + ) catch unreachable); errdefer analyzed_code.destroy(comp.gpa()); - if (comp.verbose_ir) { - std.debug.warn("analyzed:\n"); - analyzed_code.dump(); - } - // Kick off rendering to LLVM module, but it doesn't block the fn decl // analysis from being complete. try comp.prelink_group.call(codegen.renderToLlvm, comp, fn_val, analyzed_code); @@ -953,3 +1188,54 @@ async fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) void { fn getZigDir(allocator: *mem.Allocator) ![]u8 { return os.getAppDataDir(allocator, "zig"); } + +async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.FnProto) !*Type.Fn { + const return_type_node = switch (fn_proto.return_type) { + ast.Node.FnProto.ReturnType.Explicit => |n| n, + ast.Node.FnProto.ReturnType.InferErrorSet => |n| n, + }; + const return_type = try await (async comp.analyzeTypeExpr(scope, return_type_node) catch unreachable); + return_type.base.deref(comp); + + var params = ArrayList(Type.Fn.Param).init(comp.gpa()); + var params_consumed = false; + defer if (params_consumed) { + for (params.toSliceConst()) |param| { + param.typ.base.deref(comp); + } + params.deinit(); + }; + + const is_var_args = false; + { + var it = fn_proto.params.iterator(0); + while (it.next()) |param_node_ptr| { + const param_node = param_node_ptr.*.cast(ast.Node.ParamDecl).?; + const param_type = try await (async comp.analyzeTypeExpr(scope, param_node.type_node) catch unreachable); + errdefer param_type.base.deref(comp); + try params.append(Type.Fn.Param{ + .typ = param_type, + .is_noalias = param_node.noalias_token != null, + }); + } + } + const fn_type = try Type.Fn.create(comp, return_type, params.toOwnedSlice(), is_var_args); + params_consumed = true; + errdefer fn_type.base.base.deref(comp); + + return fn_type; +} + +async fn generateDeclFnProto(comp: *Compilation, fn_decl: *Decl.Fn) !void { + const fn_type = try await (async analyzeFnType(comp, fn_decl.base.parent_scope, fn_decl.fn_proto) catch unreachable); + defer fn_type.base.base.deref(comp); + + var symbol_name = try std.Buffer.init(comp.gpa(), fn_decl.base.name); + var symbol_name_consumed = false; + defer if (!symbol_name_consumed) symbol_name.deinit(); + + // The Decl.Fn owns the initial 1 reference count + const fn_proto_val = try Value.FnProto.create(comp, fn_type, symbol_name); + fn_decl.value = Decl.Fn.Val{ .FnProto = fn_proto_val }; + symbol_name_consumed = true; +} diff --git a/src-self-hosted/decl.zig b/src-self-hosted/decl.zig index 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 e0c09b1e4b..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,7 +46,7 @@ pub const IrVal = union(enum) { } }; -pub const Instruction = struct { +pub const Inst = struct { id: Id, scope: *Scope, debug_id: usize, @@ -59,15 +59,15 @@ pub const Instruction = struct { 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); } @@ -77,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(")"); @@ -98,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"), @@ -133,14 +141,22 @@ pub const Instruction = struct { } } - fn ref(base: *Instruction, builder: *Builder) void { + fn ref(base: *Inst, builder: *Builder) void { base.ref_count += 1; if (base.owner_bb != builder.current_basic_block and !base.isCompTime()) { - base.owner_bb.ref(); + base.owner_bb.ref(builder); } } - fn getAsParam(param: *Instruction) !*Instruction { + 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, @@ -148,32 +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 isCompTime(base: *const Instruction) bool { + pub fn isCompTime(base: *const Inst) bool { return base.val == IrVal.KnownValue; } - pub fn linkToParent(self: *Instruction, parent: *Instruction) void { + pub fn linkToParent(self: *Inst, parent: *Inst) void { assert(self.parent == null); assert(parent.child == null); self.parent = parent; @@ -189,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 {}; @@ -209,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; @@ -221,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; @@ -238,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); @@ -247,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, }; @@ -278,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| { @@ -287,7 +422,6 @@ pub const Instruction = struct { Value.Ptr.Mut.CompTimeConst, self.params.mut, self.params.volatility, - val.typeof.getAbiAlignment(ira.irb.comp), ); } @@ -297,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 }; @@ -313,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 { @@ -329,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; @@ -372,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; @@ -394,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; @@ -417,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 { @@ -434,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, @@ -447,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; } @@ -482,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 { @@ -489,12 +876,14 @@ 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 { + 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.gpa()), @@ -505,11 +894,13 @@ pub const Builder = struct { 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, }; } @@ -529,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, @@ -549,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; @@ -622,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; @@ -648,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( @@ -659,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| { @@ -667,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, @@ -678,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) { @@ -686,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 }, ); } } @@ -707,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(), }); @@ -718,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, @@ -755,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, @@ -785,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, @@ -811,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, @@ -838,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(self), - ?*Instruction => if (@field(inst.params, @memberName(I.Params, i))) |other| other.ref(self), - 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)), } } @@ -855,7 +1689,7 @@ pub const Builder = struct { scope: *Scope, span: Span, params: I.Params, - ) !*Instruction { + ) !*Inst { return self.buildExtra(I, scope, span, params, false); } @@ -865,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 { @@ -888,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{ @@ -902,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{ @@ -912,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, }; } @@ -921,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; @@ -981,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( @@ -1004,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; } }; @@ -1014,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; @@ -1064,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 index 915451c931..b9eefa9d4f 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -1,8 +1,12 @@ 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, @@ -12,9 +16,12 @@ const Context = struct { link_err: error{OutOfMemory}!void, link_msg: std.Buffer, + + libc: *LibCInstallation, + out_file_path: std.Buffer, }; -pub fn link(comp: *Compilation) !void { +pub async fn link(comp: *Compilation) !void { var ctx = Context{ .comp = comp, .arena = std.heap.ArenaAllocator.init(comp.gpa()), @@ -22,15 +29,45 @@ pub fn link(comp: *Compilation) !void { .link_in_crt = comp.haveLibC() and comp.kind == Compilation.Kind.Exe, .link_err = {}, .link_msg = undefined, + .libc = undefined, + .out_file_path = undefined, }; defer ctx.arena.deinit(); ctx.args = std.ArrayList([*]const u8).init(&ctx.arena.allocator); ctx.link_msg = std.Buffer.initNull(&ctx.arena.allocator); + if (comp.link_out_file) |out_file| { + ctx.out_file_path = try std.Buffer.init(&ctx.arena.allocator, out_file); + } else { + ctx.out_file_path = try std.Buffer.init(&ctx.arena.allocator, comp.name.toSliceConst()); + switch (comp.kind) { + Compilation.Kind.Exe => { + try ctx.out_file_path.append(comp.target.exeFileExt()); + }, + Compilation.Kind.Lib => { + try ctx.out_file_path.append(comp.target.libFileExt(comp.is_static)); + }, + Compilation.Kind.Obj => { + try ctx.out_file_path.append(comp.target.objFileExt()); + }, + } + } + // even though we're calling LLD as a library it thinks the first // argument is its own exe name try ctx.args.append(c"lld"); + if (comp.haveLibC()) { + ctx.libc = ctx.comp.override_libc orelse blk: { + switch (comp.target) { + Target.Native => { + break :blk (await (async comp.event_loop_local.getNativeLibC() catch unreachable)) catch return error.LibCRequiredButNotProvidedOrFound; + }, + else => return error.LibCRequiredButNotProvidedOrFound, + } + }; + } + try constructLinkerArgs(&ctx); if (comp.verbose_link) { @@ -43,6 +80,7 @@ pub fn link(comp: *Compilation) !void { const extern_ofmt = toExternObjectFormatType(comp.target.getObjectFormat()); const args_slice = ctx.args.toSlice(); + // Not evented I/O. LLD does its own multithreading internally. if (!ZigLLDLink(extern_ofmt, args_slice.ptr, args_slice.len, linkDiagCallback, @ptrCast(*c_void, &ctx))) { if (!ctx.link_msg.isNull()) { // TODO capture these messages and pass them through the system, reporting them through the @@ -95,10 +133,7 @@ fn constructLinkerArgs(ctx: *Context) !void { } fn constructLinkerArgsElf(ctx: *Context) !void { - //if (g->libc_link_lib != nullptr) { - // find_libc_lib_path(g); - //} - + // TODO commented out code in this function //if (g->linker_script) { // lj->args.append("-T"); // lj->args.append(g->linker_script); @@ -107,7 +142,7 @@ fn constructLinkerArgsElf(ctx: *Context) !void { //if (g->no_rosegment_workaround) { // lj->args.append("--no-rosegment"); //} - //lj->args.append("--gc-sections"); + try ctx.args.append(c"--gc-sections"); //lj->args.append("-m"); //lj->args.append(getLDMOption(&g->zig_target)); @@ -115,14 +150,13 @@ fn constructLinkerArgsElf(ctx: *Context) !void { //bool is_lib = g->out_type == OutTypeLib; //bool shared = !g->is_static && is_lib; //Buf *soname = nullptr; - //if (g->is_static) { - // if (g->zig_target.arch.arch == ZigLLVM_arm || g->zig_target.arch.arch == ZigLLVM_armeb || - // g->zig_target.arch.arch == ZigLLVM_thumb || g->zig_target.arch.arch == ZigLLVM_thumbeb) - // { - // lj->args.append("-Bstatic"); - // } else { - // lj->args.append("-static"); - // } + if (ctx.comp.is_static) { + if (ctx.comp.target.isArmOrThumb()) { + try ctx.args.append(c"-Bstatic"); + } else { + try ctx.args.append(c"-static"); + } + } //} else if (shared) { // lj->args.append("-shared"); @@ -133,23 +167,16 @@ fn constructLinkerArgsElf(ctx: *Context) !void { // soname = buf_sprintf("lib%s.so.%" ZIG_PRI_usize "", buf_ptr(g->root_out_name), g->version_major); //} - //lj->args.append("-o"); - //lj->args.append(buf_ptr(&lj->out_file)); + try ctx.args.append(c"-o"); + try ctx.args.append(ctx.out_file_path.ptr()); - //if (lj->link_in_crt) { - // const char *crt1o; - // const char *crtbegino; - // if (g->is_static) { - // crt1o = "crt1.o"; - // crtbegino = "crtbeginT.o"; - // } else { - // crt1o = "Scrt1.o"; - // crtbegino = "crtbegin.o"; - // } - // lj->args.append(get_libc_file(g, crt1o)); - // lj->args.append(get_libc_file(g, "crti.o")); - // lj->args.append(get_libc_static_file(g, crtbegino)); - //} + if (ctx.link_in_crt) { + const crt1o = if (ctx.comp.is_static) "crt1.o" else "Scrt1.o"; + const crtbegino = if (ctx.comp.is_static) "crtbeginT.o" else "crtbegin.o"; + try addPathJoin(ctx, ctx.libc.lib_dir.?, crt1o); + try addPathJoin(ctx, ctx.libc.lib_dir.?, "crti.o"); + try addPathJoin(ctx, ctx.libc.static_lib_dir.?, crtbegino); + } //for (size_t i = 0; i < g->rpath_list.length; i += 1) { // Buf *rpath = g->rpath_list.at(i); @@ -182,25 +209,23 @@ fn constructLinkerArgsElf(ctx: *Context) !void { // lj->args.append(lib_dir); //} - //if (g->libc_link_lib != nullptr) { - // lj->args.append("-L"); - // lj->args.append(buf_ptr(g->libc_lib_dir)); + 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); - // lj->args.append("-L"); - // lj->args.append(buf_ptr(g->libc_static_lib_dir)); - //} + try ctx.args.append(c"-L"); + try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, ctx.libc.static_lib_dir.?)).ptr); - //if (!g->is_static) { - // if (g->dynamic_linker != nullptr) { - // assert(buf_len(g->dynamic_linker) != 0); - // lj->args.append("-dynamic-linker"); - // lj->args.append(buf_ptr(g->dynamic_linker)); - // } else { - // Buf *resolved_dynamic_linker = get_dynamic_linker_path(g); - // lj->args.append("-dynamic-linker"); - // lj->args.append(buf_ptr(resolved_dynamic_linker)); - // } - //} + if (!ctx.comp.is_static) { + const dl = blk: { + if (ctx.libc.dynamic_linker_path) |dl| break :blk dl; + if (ctx.comp.target.getDynamicLinkerPath()) |dl| break :blk dl; + return error.LibCMissingDynamicLinker; + }; + try ctx.args.append(c"-dynamic-linker"); + try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, dl)).ptr); + } + } //if (shared) { // lj->args.append("-soname"); @@ -241,55 +266,358 @@ fn constructLinkerArgsElf(ctx: *Context) !void { // lj->args.append(buf_ptr(arg)); //} - //// libc dep - //if (g->libc_link_lib != nullptr) { - // if (g->is_static) { - // lj->args.append("--start-group"); - // lj->args.append("-lgcc"); - // lj->args.append("-lgcc_eh"); - // lj->args.append("-lc"); - // lj->args.append("-lm"); - // lj->args.append("--end-group"); + // 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 { - // lj->args.append("-lgcc"); - // lj->args.append("--as-needed"); - // lj->args.append("-lgcc_s"); - // lj->args.append("--no-as-needed"); - // lj->args.append("-lc"); - // lj->args.append("-lm"); - // lj->args.append("-lgcc"); - // lj->args.append("--as-needed"); - // lj->args.append("-lgcc_s"); - // lj->args.append("--no-as-needed"); + // 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); + // } // } //} - //// crt end - //if (lj->link_in_crt) { - // lj->args.append(get_libc_static_file(g, "crtend.o")); - // lj->args.append(get_libc_file(g, "crtn.o")); + 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); //} - //if (!g->is_native_target) { - // lj->args.append("--allow-shlib-undefined"); + 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 (g->zig_target.os == OsZen) { - // lj->args.append("-e"); - // lj->args.append("_start"); + 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"); + } - // lj->args.append("--image-base=0x10000000"); + 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 constructLinkerArgsCoff(ctx: *Context) void { - @panic("TODO"); -} - -fn constructLinkerArgsMachO(ctx: *Context) void { - @panic("TODO"); -} - fn constructLinkerArgsWasm(ctx: *Context) void { @panic("TODO"); } @@ -312,3 +640,85 @@ fn addFnObjects(ctx: *Context) !void { 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 b196656367..8bb45ac616 100644 --- a/src-self-hosted/llvm.zig +++ b/src-self-hosted/llvm.zig @@ -23,13 +23,20 @@ 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; @@ -57,6 +64,7 @@ pub const GetEnumAttributeKindForName = c.LLVMGetEnumAttributeKindForName; pub const GetHostCPUName = c.ZigLLVMGetHostCPUName; pub const GetMDKindIDInContext = c.LLVMGetMDKindIDInContext; pub const GetNativeFeatures = c.ZigLLVMGetNativeFeatures; +pub const GetUndef = c.LLVMGetUndef; pub const HalfTypeInContext = c.LLVMHalfTypeInContext; pub const InitializeAllAsmParsers = c.LLVMInitializeAllAsmParsers; pub const InitializeAllAsmPrinters = c.LLVMInitializeAllAsmPrinters; @@ -79,14 +87,24 @@ pub const MDStringInContext = c.LLVMMDStringInContext; pub const MetadataTypeInContext = c.LLVMMetadataTypeInContext; pub const ModuleCreateWithNameInContext = c.LLVMModuleCreateWithNameInContext; pub const PPCFP128TypeInContext = c.LLVMPPCFP128TypeInContext; +pub const PointerType = c.LLVMPointerType; +pub const SetAlignment = c.LLVMSetAlignment; pub const SetDataLayout = c.LLVMSetDataLayout; +pub const SetGlobalConstant = c.LLVMSetGlobalConstant; +pub const SetInitializer = c.LLVMSetInitializer; +pub const SetLinkage = c.LLVMSetLinkage; pub const SetTarget = c.LLVMSetTarget; +pub const SetUnnamedAddr = c.LLVMSetUnnamedAddr; pub const StructTypeInContext = c.LLVMStructTypeInContext; pub const TokenTypeInContext = c.LLVMTokenTypeInContext; +pub const TypeOf = c.LLVMTypeOf; pub const VoidTypeInContext = c.LLVMVoidTypeInContext; pub const X86FP80TypeInContext = c.LLVMX86FP80TypeInContext; pub const X86MMXTypeInContext = c.LLVMX86MMXTypeInContext; +pub const ConstInBoundsGEP = LLVMConstInBoundsGEP; +pub extern fn LLVMConstInBoundsGEP(ConstantVal: ValueRef, ConstantIndices: [*]ValueRef, NumIndices: c_uint) ?ValueRef; + pub const GetTargetFromTriple = LLVMGetTargetFromTriple; extern fn LLVMGetTargetFromTriple(Triple: [*]const u8, T: *TargetRef, ErrorMessage: ?*[*]u8) Bool; @@ -143,13 +161,28 @@ pub const EmitBinary = EmitOutputType.ZigLLVM_EmitBinary; pub const EmitLLVMIr = EmitOutputType.ZigLLVM_EmitLLVMIr; pub const EmitOutputType = c.ZigLLVM_EmitOutputType; +pub const CCallConv = c.LLVMCCallConv; +pub const FastCallConv = c.LLVMFastCallConv; +pub const ColdCallConv = c.LLVMColdCallConv; +pub const WebKitJSCallConv = c.LLVMWebKitJSCallConv; +pub const AnyRegCallConv = c.LLVMAnyRegCallConv; +pub const X86StdcallCallConv = c.LLVMX86StdcallCallConv; +pub const X86FastcallCallConv = c.LLVMX86FastcallCallConv; +pub const CallConv = c.LLVMCallConv; + +pub const FnInline = extern enum { + Auto, + Always, + Never, +}; + fn removeNullability(comptime T: type) type { comptime assert(@typeId(T) == builtin.TypeId.Optional); return T.Child; } pub const BuildRet = LLVMBuildRet; -extern fn LLVMBuildRet(arg0: BuilderRef, V: ?ValueRef) ValueRef; +extern fn LLVMBuildRet(arg0: BuilderRef, V: ?ValueRef) ?ValueRef; pub const TargetMachineEmitToFile = ZigLLVMTargetMachineEmitToFile; extern fn ZigLLVMTargetMachineEmitToFile( @@ -161,3 +194,8 @@ extern fn ZigLLVMTargetMachineEmitToFile( is_debug: bool, is_small: bool, ) bool; + +pub const BuildCall = ZigLLVMBuildCall; +extern fn ZigLLVMBuildCall(B: BuilderRef, Fn: ValueRef, Args: [*]ValueRef, NumArgs: c_uint, CC: c_uint, fn_inline: FnInline, Name: [*]const u8) ?ValueRef; + +pub const PrivateLinkage = c.LLVMLinkage.LLVMPrivateLinkage; diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 8b668e35bd..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), @@ -377,16 +371,11 @@ 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(); @@ -403,10 +392,18 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co 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); @@ -430,25 +427,6 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co comp.strip = flags.present("strip"); - 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"); comp.verbose_ast_fmt = flags.present("verbose-ast-fmt"); @@ -484,7 +462,7 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co comp.emit_file_type = emit_type; 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(); @@ -499,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| { @@ -508,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); } }, } @@ -579,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(); @@ -622,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); @@ -678,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/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 1c519d6c08..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 { + 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.gpa().destroy(self); - - self.table = Decl.Table.init(comp.gpa()); - 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,7 +198,7 @@ pub const Scope = struct { }; /// Creates a Block scope with 1 reference - pub fn create(comp: *Compilation, parent: ?*Scope) !*Block { + pub fn create(comp: *Compilation, parent: *Scope) !*Block { const self = try comp.gpa().create(Block{ .base = Scope{ .id = Id.Block, @@ -140,7 +213,7 @@ pub const Scope = struct { }); errdefer comp.gpa().destroy(self); - if (parent) |p| p.ref(); + parent.ref(); return self; } @@ -157,7 +230,7 @@ pub const Scope = struct { /// Creates a FnDef scope with 1 reference /// Must set the fn_val later - pub fn create(comp: *Compilation, parent: ?*Scope) !*FnDef { + pub fn create(comp: *Compilation, parent: *Scope) !*FnDef { const self = try comp.gpa().create(FnDef{ .base = Scope{ .id = Id.FnDef, @@ -167,7 +240,7 @@ pub const Scope = struct { .fn_val = undefined, }); - if (parent) |p| p.ref(); + parent.ref(); return self; } @@ -181,7 +254,7 @@ pub const Scope = struct { base: Scope, /// Creates a CompTime scope with 1 reference - pub fn create(comp: *Compilation, parent: ?*Scope) !*CompTime { + pub fn create(comp: *Compilation, parent: *Scope) !*CompTime { const self = try comp.gpa().create(CompTime{ .base = Scope{ .id = Id.CompTime, @@ -190,7 +263,7 @@ pub const Scope = struct { }, }); - if (parent) |p| p.ref(); + parent.ref(); return self; } @@ -212,7 +285,7 @@ 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 { @@ -229,7 +302,7 @@ pub const Scope = struct { defer_expr_scope.base.ref(); - if (parent) |p| p.ref(); + parent.ref(); return self; } @@ -242,9 +315,10 @@ pub const Scope = struct { 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 { + pub fn create(comp: *Compilation, parent: *Scope, expr_node: *ast.Node) !*DeferExpr { const self = try comp.gpa().create(DeferExpr{ .base = Scope{ .id = Id.DeferExpr, @@ -252,10 +326,11 @@ pub const Scope = struct { .ref_count = 1, }, .expr_node = expr_node, + .reported_err = false, }); errdefer comp.gpa().destroy(self); - if (parent) |p| p.ref(); + parent.ref(); return self; } diff --git a/src-self-hosted/target.zig b/src-self-hosted/target.zig index db673e421a..0cc8d02a62 100644 --- a/src-self-hosted/target.zig +++ b/src-self-hosted/target.zig @@ -1,6 +1,13 @@ const std = @import("std"); const builtin = @import("builtin"); const llvm = @import("llvm.zig"); +const CInt = @import("c_int.zig").CInt; + +pub const FloatAbi = enum { + Hard, + Soft, + SoftFp, +}; pub const Target = union(enum) { Native, @@ -13,7 +20,7 @@ pub const Target = union(enum) { object_format: builtin.ObjectFormat, }; - pub fn oFileExt(self: Target) []const u8 { + pub fn objFileExt(self: Target) []const u8 { return switch (self.getObjectFormat()) { builtin.ObjectFormat.coff => ".obj", else => ".o", @@ -27,6 +34,13 @@ pub const Target = union(enum) { }; } + pub fn libFileExt(self: Target, is_static: bool) []const u8 { + return switch (self.getOs()) { + builtin.Os.windows => if (is_static) ".lib" else ".dll", + else => if (is_static) ".a" else ".so", + }; + } + pub fn getOs(self: Target) builtin.Os { return switch (self) { Target.Native => builtin.os, @@ -76,6 +90,56 @@ pub const Target = union(enum) { }; } + /// TODO expose the arch and subarch separately + pub fn isArmOrThumb(self: Target) bool { + return switch (self.getArch()) { + builtin.Arch.armv8_3a, + builtin.Arch.armv8_2a, + builtin.Arch.armv8_1a, + builtin.Arch.armv8, + builtin.Arch.armv8r, + builtin.Arch.armv8m_baseline, + builtin.Arch.armv8m_mainline, + builtin.Arch.armv7, + builtin.Arch.armv7em, + builtin.Arch.armv7m, + builtin.Arch.armv7s, + builtin.Arch.armv7k, + builtin.Arch.armv7ve, + builtin.Arch.armv6, + builtin.Arch.armv6m, + builtin.Arch.armv6k, + builtin.Arch.armv6t2, + builtin.Arch.armv5, + builtin.Arch.armv5te, + builtin.Arch.armv4t, + builtin.Arch.armebv8_3a, + builtin.Arch.armebv8_2a, + builtin.Arch.armebv8_1a, + builtin.Arch.armebv8, + builtin.Arch.armebv8r, + builtin.Arch.armebv8m_baseline, + builtin.Arch.armebv8m_mainline, + builtin.Arch.armebv7, + builtin.Arch.armebv7em, + builtin.Arch.armebv7m, + builtin.Arch.armebv7s, + builtin.Arch.armebv7k, + builtin.Arch.armebv7ve, + builtin.Arch.armebv6, + builtin.Arch.armebv6m, + builtin.Arch.armebv6k, + builtin.Arch.armebv6t2, + builtin.Arch.armebv5, + builtin.Arch.armebv5te, + builtin.Arch.armebv4t, + builtin.Arch.thumb, + builtin.Arch.thumbeb, + => true, + else => false, + }; + } + pub fn initializeAll() void { llvm.InitializeAllTargets(); llvm.InitializeAllTargetInfos(); @@ -106,6 +170,257 @@ pub const Target = union(enum) { 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; @@ -115,4 +430,133 @@ pub const Target = union(enum) { } 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 45e5362124..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), }; @@ -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(); @@ -109,7 +105,6 @@ pub const TestContext = struct { builtin.Mode.Debug, true, // is_static self.zig_lib_dir, - self.zig_cache_dir, ); errdefer comp.destroy(); @@ -118,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, @@ -139,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; } @@ -159,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 bb1fb9bb01..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 { @@ -163,7 +214,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Struct, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Struct, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -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.gpa().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, - }, + .base = undefined, .return_type = return_type, .params = params, .is_var_args = is_var_args, }); 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.gpa().destroy(self); } - pub fn getLlvmType(self: *Fn, ofile: *ObjectFile) !llvm.TypeRef { + pub fn getLlvmType(self: *Fn, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef { const llvm_return_type = switch (self.return_type.id) { - Type.Id.Void => llvm.VoidTypeInContext(ofile.context) orelse return error.OutOfMemory, - else => try self.return_type.getLlvmType(ofile), + Type.Id.Void => llvm.VoidTypeInContext(llvm_context) orelse return error.OutOfMemory, + else => try self.return_type.getLlvmType(allocator, llvm_context), }; - const llvm_param_types = try ofile.gpa().alloc(llvm.TypeRef, self.params.len); - defer ofile.gpa().free(llvm_param_types); + const llvm_param_types = try allocator.alloc(llvm.TypeRef, self.params.len); + defer allocator.free(llvm_param_types); for (llvm_param_types) |*llvm_param_type, i| { - llvm_param_type.* = try self.params[i].typeof.getLlvmType(ofile); + llvm_param_type.* = try self.params[i].typ.getLlvmType(allocator, llvm_context); } return llvm.FunctionType( @@ -272,7 +318,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Bool, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Bool, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -293,13 +339,83 @@ pub const Type = struct { pub const Int = struct { base: Type, + key: Key, + garbage_node: std.atomic.Stack(*Int).Node, + + 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 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, ofile: *ObjectFile) llvm.TypeRef { - @panic("TODO"); + 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; } }; @@ -310,56 +426,236 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Float, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Float, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; pub const Pointer = struct { base: Type, - mut: Mut, - vol: Vol, - size: Size, - alignment: u32, + key: Key, + garbage_node: std.atomic.Stack(*Pointer).Node, + + pub const Key = struct { + child_type: *Type, + mut: Mut, + vol: Vol, + size: Size, + alignment: Align, + + pub fn hash(self: *const Key) u32 { + const align_hash = switch (self.alignment) { + Align.Abi => 0xf201c090, + Align.Override => |x| x, + }; + return hash_usize(@ptrToInt(self.child_type)) *% + hash_enum(self.mut) *% + hash_enum(self.vol) *% + hash_enum(self.size) *% + align_hash; + } + + pub fn eql(self: *const Key, other: *const Key) bool { + if (self.child_type != other.child_type or + self.mut != other.mut or + self.vol != other.vol or + self.size != other.size or + @TagType(Align)(self.alignment) != @TagType(Align)(other.alignment)) + { + return false; + } + switch (self.alignment) { + Align.Abi => return true, + Align.Override => |x| return x == other.alignment.Override, + } + } + }; pub const Mut = enum { Mut, Const, }; + pub const Vol = enum { Non, Volatile, }; + + pub const Align = union(enum) { + Abi, + Override: u32, + }; + pub const Size = builtin.TypeInfo.Pointer.Size; pub fn destroy(self: *Pointer, comp: *Compilation) void { + self.garbage_node = std.atomic.Stack(*Pointer).Node{ + .data = self, + .next = undefined, + }; + comp.registerGarbage(Pointer, &self.garbage_node); + } + + pub async fn gcDestroy(self: *Pointer, comp: *Compilation) void { + { + const held = await (async comp.ptr_type_table.acquire() catch unreachable); + defer held.release(); + + _ = held.value.remove(&self.key).?; + } + self.key.child_type.base.deref(comp); comp.gpa().destroy(self); } - pub fn get( - comp: *Compilation, - elem_type: *Type, - mut: Mut, - vol: Vol, - size: Size, - alignment: u32, - ) *Pointer { - @panic("TODO get pointer"); + 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 fn getLlvmType(self: *Pointer, ofile: *ObjectFile) llvm.TypeRef { - @panic("TODO"); + pub async fn get( + comp: *Compilation, + 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, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef { + const elem_llvm_type = try self.key.child_type.getLlvmType(allocator, llvm_context); + return llvm.PointerType(elem_llvm_type, 0) orelse return error.OutOfMemory; } }; pub const Array = struct { base: Type, + key: Key, + garbage_node: std.atomic.Stack(*Array).Node, + + pub const Key = struct { + elem_type: *Type, + len: usize, + + pub fn hash(self: *const Key) u32 { + return hash_usize(@ptrToInt(self.elem_type)) *% hash_usize(self.len); + } + + pub fn eql(self: *const Key, other: *const Key) bool { + return self.elem_type == other.elem_type and self.len == other.len; + } + }; pub fn destroy(self: *Array, comp: *Compilation) void { + self.key.elem_type.base.deref(comp); comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Array, ofile: *ObjectFile) llvm.TypeRef { - @panic("TODO"); + pub async fn get(comp: *Compilation, key: Key) !*Array { + key.elem_type.base.ref(); + errdefer key.elem_type.base.deref(comp); + + { + const held = await (async comp.array_type_table.acquire() catch unreachable); + defer held.release(); + + if (held.value.get(&key)) |entry| { + entry.value.base.base.ref(); + return entry.value; + } + } + + const self = try comp.gpa().create(Array{ + .base = undefined, + .key = key, + .garbage_node = undefined, + }); + errdefer comp.gpa().destroy(self); + + const name = try std.fmt.allocPrint(comp.gpa(), "[{}]{}", key.len, key.elem_type.name); + errdefer comp.gpa().free(name); + + self.base.init(comp, Id.Array, name); + + { + const held = await (async comp.array_type_table.acquire() catch unreachable); + defer held.release(); + + _ = try held.value.put(&self.key, self); + } + return self; + } + + pub fn getLlvmType(self: *Array, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef { + const elem_llvm_type = try self.key.elem_type.getLlvmType(allocator, llvm_context); + return llvm.ArrayType(elem_llvm_type, @intCast(c_uint, self.key.len)) orelse return error.OutOfMemory; } }; @@ -374,6 +670,12 @@ pub const Type = struct { 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.gpa().destroy(self); } @@ -402,7 +704,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Optional, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Optional, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -414,7 +716,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *ErrorUnion, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *ErrorUnion, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -426,7 +728,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *ErrorSet, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *ErrorSet, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -438,7 +740,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Enum, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Enum, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -450,7 +752,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Union, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Union, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -478,7 +780,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *BoundFn, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *BoundFn, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -498,7 +800,7 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Opaque, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Opaque, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; @@ -510,8 +812,33 @@ pub const Type = struct { comp.gpa().destroy(self); } - pub fn getLlvmType(self: *Promise, ofile: *ObjectFile) llvm.TypeRef { + pub fn getLlvmType(self: *Promise, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef { @panic("TODO"); } }; }; + +fn hash_usize(x: usize) u32 { + return switch (@sizeOf(usize)) { + 4 => x, + 8 => @truncate(u32, x *% 0xad44ee2d8e3fc13d), + else => @compileError("implement this hash function"), + }; +} + +fn hash_enum(x: var) u32 { + const rands = []u32{ + 0x85ebf64f, + 0x3fcb3211, + 0x240a4e8e, + 0x40bb0e3c, + 0x78be45af, + 0x1ca98e37, + 0xec56053a, + 0x906adc48, + 0xd4fe9763, + 0x54c80dac, + }; + comptime assert(@memberCount(@typeOf(x)) < rands.len); + return rands[@enumToInt(x)]; +} diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index be19c6bccf..2005e3c119 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -5,12 +5,13 @@ 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 @@ -21,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)); } @@ -46,24 +61,111 @@ 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, @@ -98,7 +200,7 @@ pub const Value = struct { const self = try comp.gpa().create(Fn{ .base = Value{ .id = Value.Id.Fn, - .typeof = &fn_type.base, + .typ = &fn_type.base, .ref_count = std.atomic.Int(usize).init(1), }, .fndef_scope = fndef_scope, @@ -187,6 +289,8 @@ pub const Value = struct { pub const Ptr = struct { base: Value, + special: Special, + mut: Mut, pub const Mut = enum { CompTimeConst, @@ -194,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.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 06d611f80d..6bbe5f6037 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -4379,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); } @@ -4499,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/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/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/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 485a5be19c..cd805f891f 100644 --- a/std/event/loop.zig +++ b/std/event/loop.zig @@ -444,7 +444,7 @@ pub const Loop = struct { .next = undefined, .data = p, }; - loop.onNextTick(&my_tick_node); + self.onNextTick(&my_tick_node); } } 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/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 09bbfe5865..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 { @@ -332,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; @@ -414,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) { 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/file.zig b/std/os/file.zig index 26fd9ca514..6998ba00d1 100644 --- a/std/os/file.zig +++ b/std/os/file.zig @@ -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"); } 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 96c4d3861c..90ccfaf6c5 100644 --- a/std/os/windows/index.zig +++ b/std/os/windows/index.zig @@ -1,190 +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; @@ -206,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; @@ -442,10 +272,6 @@ pub const SYSTEM_INFO = extern struct { wProcessorRevision: WORD, }; -pub extern "ole32.dll" stdcallcc fn CoTaskMemFree(pv: LPVOID) void; - -pub extern "shell32.dll" stdcallcc fn SHGetKnownFolderPath(rfid: *const KNOWNFOLDERID, dwFlags: DWORD, hToken: ?HANDLE, ppszPath: *[*]WCHAR) HRESULT; - pub const HRESULT = c_long; pub const KNOWNFOLDERID = GUID; 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/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/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'"); }