From cd8070f94f6865959949dbcec6e0e32cd88bb544 Mon Sep 17 00:00:00 2001 From: antlilja Date: Sat, 30 Jul 2022 11:39:49 +0200 Subject: [PATCH 001/290] Removed param_names from Fn inside Module.zig Removed the copy of param_names inside of Fn and changed to implementation of getParamName to fetch to parameter name from the ZIR. The signature of getParamName was also changed to take an additional *Module argument. --- src/Module.zig | 38 +++++++++++++++++++----------------- src/Sema.zig | 6 ------ src/Zir.zig | 21 ++++++++++++++++++++ src/arch/arm/CodeGen.zig | 2 +- src/arch/riscv64/CodeGen.zig | 2 +- src/arch/sparc64/CodeGen.zig | 2 +- src/arch/wasm/CodeGen.zig | 2 +- src/arch/x86_64/CodeGen.zig | 2 +- src/codegen/llvm.zig | 2 +- 9 files changed, 47 insertions(+), 30 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 4ac2775515..b80e00ad59 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1471,14 +1471,6 @@ pub const Fn = struct { /// TODO apply the same enhancement for param_names below to this field. anytype_args: [*]bool, - /// Prefer to use `getParamName` to access this because of the future improvement - /// we want to do mentioned in the TODO below. - /// Stored in gpa. - /// TODO: change param ZIR instructions to be embedded inside the function - /// ZIR instruction instead of before it, so that `zir_body_inst` can be used to - /// determine param names rather than redundantly storing them here. - param_names: []const [:0]const u8, - /// Precomputed hash for monomorphed_funcs. /// This is important because it may be accessed when resizing monomorphed_funcs /// while this Fn has already been added to the set, but does not have the @@ -1590,18 +1582,28 @@ pub const Fn = struct { gpa.destroy(node); it = next; } - - for (func.param_names) |param_name| { - gpa.free(param_name); - } - gpa.free(func.param_names); } - pub fn getParamName(func: Fn, index: u32) [:0]const u8 { - // TODO rework ZIR of parameters so that this function looks up - // param names in ZIR instead of redundantly saving them into Fn. - // const zir = func.owner_decl.getFileScope().zir; - return func.param_names[index]; + pub fn getParamName(func: Fn, mod: *Module, index: u32) [:0]const u8 { + const file = mod.declPtr(func.owner_decl).getFileScope(); + + const tags = file.zir.instructions.items(.tag); + const data = file.zir.instructions.items(.data); + + const param_body = file.zir.getParamBody(func.zir_body_inst); + const param = param_body[index]; + + return switch (tags[param]) { + .param, .param_comptime => blk: { + const extra = file.zir.extraData(Zir.Inst.Param, data[param].pl_tok.payload_index); + break :blk file.zir.nullTerminatedString(extra.data.name); + }, + .param_anytype, .param_anytype_comptime => blk: { + const param_data = data[param].str_tok; + break :blk param_data.get(file.zir); + }, + else => unreachable, + }; } pub fn hasInferredErrorSet(func: Fn, mod: *Module) bool { diff --git a/src/Sema.zig b/src/Sema.zig index a0829d6eb7..07e81dc5be 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -7753,11 +7753,6 @@ fn funcCommon( break :blk if (sema.comptime_args.len == 0) null else sema.comptime_args.ptr; } else null; - const param_names = try sema.gpa.alloc([:0]const u8, block.params.items.len); - for (param_names) |*param_name, i| { - param_name.* = try sema.gpa.dupeZ(u8, block.params.items[i].name); - } - const hash = new_func.hash; const fn_payload = try sema.arena.create(Value.Payload.Function); new_func.* = .{ @@ -7771,7 +7766,6 @@ fn funcCommon( .rbrace_line = src_locs.rbrace_line, .lbrace_column = @truncate(u16, src_locs.columns), .rbrace_column = @truncate(u16, src_locs.columns >> 16), - .param_names = param_names, .branch_quota = default_branch_quota, .is_noinline = is_noinline, }; diff --git a/src/Zir.zig b/src/Zir.zig index ccd677df0b..5af3c0038d 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -3909,6 +3909,27 @@ pub const FnInfo = struct { total_params_len: u32, }; +pub fn getParamBody(zir: Zir, fn_inst: Inst.Index) []const u32 { + const tags = zir.instructions.items(.tag); + const datas = zir.instructions.items(.data); + const inst_data = datas[fn_inst].pl_node; + + const param_block_index = switch (tags[fn_inst]) { + .func, .func_inferred => blk: { + const extra = zir.extraData(Inst.Func, inst_data.payload_index); + break :blk extra.data.param_block; + }, + .func_fancy => blk: { + const extra = zir.extraData(Inst.FuncFancy, inst_data.payload_index); + break :blk extra.data.param_block; + }, + else => unreachable, + }; + + const param_block = zir.extraData(Inst.Block, datas[param_block_index].pl_node.payload_index); + return zir.extra[param_block.end..][0..param_block.data.body_len]; +} + pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { const tags = zir.instructions.items(.tag); const datas = zir.instructions.items(.data); diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 93d98c41d3..a603f72d53 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -3372,7 +3372,7 @@ fn genArgDbgInfo(self: *Self, inst: Air.Inst.Index, arg_index: u32, stack_byte_c const mcv = self.args[arg_index]; const ty = self.air.instructions.items(.data)[inst].ty; - const name = self.mod_fn.getParamName(arg_index); + const name = self.mod_fn.getParamName(self.bin_file.options.module.?, arg_index); const name_with_null = name.ptr[0 .. name.len + 1]; switch (mcv) { diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 220fb18699..d09c6278b5 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -1619,7 +1619,7 @@ fn airFieldParentPtr(self: *Self, inst: Air.Inst.Index) !void { fn genArgDbgInfo(self: *Self, inst: Air.Inst.Index, mcv: MCValue, arg_index: u32) !void { const ty = self.air.instructions.items(.data)[inst].ty; - const name = self.mod_fn.getParamName(arg_index); + const name = self.mod_fn.getParamName(self.bin_file.options.module.?, arg_index); const name_with_null = name.ptr[0 .. name.len + 1]; switch (mcv) { diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index 2c6a322fca..bc9fc115b2 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -2959,7 +2959,7 @@ fn finishAir(self: *Self, inst: Air.Inst.Index, result: MCValue, operands: [Live fn genArgDbgInfo(self: *Self, inst: Air.Inst.Index, mcv: MCValue, arg_index: u32) !void { const ty = self.air.instructions.items(.data)[inst].ty; - const name = self.mod_fn.getParamName(arg_index); + const name = self.mod_fn.getParamName(self.bin_file.options.module.?, arg_index); const name_with_null = name.ptr[0 .. name.len + 1]; switch (mcv) { diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 91072d0b4c..dd9cfba548 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1991,7 +1991,7 @@ fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!WValue { switch (self.debug_output) { .dwarf => |dwarf| { // TODO: Get the original arg index rather than wasm arg index - const name = self.mod_fn.getParamName(arg_index); + const name = self.mod_fn.getParamName(self.bin_file.base.options.module.?, arg_index); const leb_size = link.File.Wasm.getULEB128Size(arg.local); const dbg_info = &dwarf.dbg_info; try dbg_info.ensureUnusedCapacity(3 + leb_size + 5 + name.len + 1); diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 09721c661f..d59a114e51 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -3789,7 +3789,7 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void { const ty = self.air.typeOfIndex(inst); const mcv = self.args[arg_index]; - const name = self.mod_fn.getParamName(arg_index); + const name = self.mod_fn.getParamName(self.bin_file.options.module.?, arg_index); const name_with_null = name.ptr[0 .. name.len + 1]; if (self.liveness.isUnused(inst)) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 664edb0304..5ea7ffad67 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -7313,7 +7313,7 @@ pub const FuncGen = struct { const lbrace_col = func.lbrace_column + 1; const di_local_var = dib.createParameterVariable( self.di_scope.?, - func.getParamName(src_index).ptr, // TODO test 0 bit args + func.getParamName(self.dg.module, src_index).ptr, // TODO test 0 bit args self.di_file.?, lbrace_line, try self.dg.object.lowerDebugType(inst_ty, .full), From ab3b614a335ffac9eac4f824ee18fba262ad988e Mon Sep 17 00:00:00 2001 From: antlilja Date: Sat, 30 Jul 2022 18:43:15 +0200 Subject: [PATCH 002/290] Removed anytype_args field from Fn anytype_args field was replaced with isAnytypeParam function. --- src/Module.zig | 21 ++++++++++++++++----- src/Sema.zig | 11 ++--------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index b80e00ad59..625af7ca07 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1464,12 +1464,8 @@ pub const Fn = struct { /// These never have .generic_poison for the Type /// because the Type is needed to pass to `Type.eql` and for inserting comptime arguments /// into the inst_map when analyzing the body of a generic function instantiation. - /// Instead, the is_anytype knowledge is communicated via `anytype_args`. + /// Instead, the is_anytype knowledge is communicated via `isAnytypeParam`. comptime_args: ?[*]TypedValue, - /// When comptime_args is null, this is undefined. Otherwise, this flags each - /// parameter and tells whether it is anytype. - /// TODO apply the same enhancement for param_names below to this field. - anytype_args: [*]bool, /// Precomputed hash for monomorphed_funcs. /// This is important because it may be accessed when resizing monomorphed_funcs @@ -1584,6 +1580,21 @@ pub const Fn = struct { } } + pub fn isAnytypeParam(func: Fn, mod: *Module, index: u32) bool { + const file = mod.declPtr(func.owner_decl).getFileScope(); + + const tags = file.zir.instructions.items(.tag); + + const param_body = file.zir.getParamBody(func.zir_body_inst); + const param = param_body[index]; + + return switch (tags[param]) { + .param, .param_comptime => false, + .param_anytype, .param_anytype_comptime => true, + else => unreachable, + }; + } + pub fn getParamName(func: Fn, mod: *Module, index: u32) [:0]const u8 { const file = mod.declPtr(func.owner_decl).getFileScope(); diff --git a/src/Sema.zig b/src/Sema.zig index 07e81dc5be..1629b7711a 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5498,7 +5498,7 @@ const GenericCallAdapter = struct { const this_is_comptime = this_arg.val.tag() != .generic_poison; const other_is_comptime = other_arg.val.tag() != .generic_poison; const this_is_anytype = this_arg.ty.tag() != .generic_poison; - const other_is_anytype = other_key.anytype_args[i]; + const other_is_anytype = other_key.isAnytypeParam(ctx.module, @intCast(u32, i)); if (other_is_anytype != this_is_anytype) return false; if (other_is_comptime != this_is_comptime) return false; @@ -6379,12 +6379,9 @@ fn instantiateGenericCall( errdefer new_func.deinit(gpa); assert(new_func == new_module_func); - const anytype_args = try new_decl_arena_allocator.alloc(bool, func_ty_info.param_types.len); - new_func.anytype_args = anytype_args.ptr; arg_i = 0; for (fn_info.param_body) |inst| { var is_comptime = false; - var is_anytype = false; switch (zir_tags[inst]) { .param => { is_comptime = func_ty_info.paramIsComptime(arg_i); @@ -6393,11 +6390,9 @@ fn instantiateGenericCall( is_comptime = true; }, .param_anytype => { - is_anytype = true; is_comptime = func_ty_info.paramIsComptime(arg_i); }, .param_anytype_comptime => { - is_anytype = true; is_comptime = true; }, else => continue, @@ -6405,10 +6400,9 @@ fn instantiateGenericCall( // We populate the Type here regardless because it is needed by // `GenericCallAdapter.eql` as well as function body analysis. - // Whether it is anytype is communicated by `anytype_args`. + // Whether it is anytype is communicated by `isAnytypeParam`. const arg = child_sema.inst_map.get(inst).?; const copied_arg_ty = try child_sema.typeOf(arg).copy(new_decl_arena_allocator); - anytype_args[arg_i] = is_anytype; if (try sema.typeRequiresComptime(block, .unneeded, copied_arg_ty)) { is_comptime = true; @@ -7760,7 +7754,6 @@ fn funcCommon( .zir_body_inst = func_inst, .owner_decl = sema.owner_decl_index, .comptime_args = comptime_args, - .anytype_args = undefined, .hash = hash, .lbrace_line = src_locs.lbrace_line, .rbrace_line = src_locs.rbrace_line, From b3950d4a88425719a642fe71d4739185a8632f89 Mon Sep 17 00:00:00 2001 From: Stephen Gregoratto Date: Mon, 1 Aug 2022 22:53:46 +1000 Subject: [PATCH 003/290] Update Linux syscall list for 5.19 New changes: memfd_secret implemented for RISC-V. --- lib/std/os/linux/syscalls.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/os/linux/syscalls.zig b/lib/std/os/linux/syscalls.zig index fb0993afe5..6e8cee7b84 100644 --- a/lib/std/os/linux/syscalls.zig +++ b/lib/std/os/linux/syscalls.zig @@ -3485,6 +3485,7 @@ pub const RiscV64 = enum(usize) { landlock_create_ruleset = 444, landlock_add_rule = 445, landlock_restrict_self = 446, + memfd_secret = 447, process_mrelease = 448, futex_waitv = 449, set_mempolicy_home_node = 450, From fd3415ad5e007005f7d568c41967c563e2066e95 Mon Sep 17 00:00:00 2001 From: Meredith Oleander <33614480+em-dash@users.noreply.github.com> Date: Fri, 5 Aug 2022 22:38:48 +1000 Subject: [PATCH 004/290] translate-c: fix alignment in pointer casts --- src/translate_c.zig | 3 +-- test/translate_c.zig | 30 +++++++++++++++++++++--------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/translate_c.zig b/src/translate_c.zig index 97e47d84f3..fa23b861e3 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -4001,8 +4001,7 @@ fn transCPtrCast( // For opaque types a ptrCast is enough expr else blk: { - const child_type_node = try transQualType(c, scope, child_type, loc); - const alignof = try Tag.std_meta_alignment.create(c.arena, child_type_node); + const alignof = try Tag.std_meta_alignment.create(c.arena, dst_type_node); const align_cast = try Tag.align_cast.create(c.arena, .{ .lhs = alignof, .rhs = expr }); break :blk align_cast; }; diff --git a/test/translate_c.zig b/test/translate_c.zig index 5a640c5b4b..540c560839 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -1485,7 +1485,19 @@ pub fn addCases(cases: *tests.TranslateCContext) void { , &[_][]const u8{ \\pub export fn ptrcast() [*c]f32 { \\ var a: [*c]c_int = undefined; - \\ return @ptrCast([*c]f32, @alignCast(@import("std").meta.alignment(f32), a)); + \\ return @ptrCast([*c]f32, @alignCast(@import("std").meta.alignment([*c]f32), a)); + \\} + }); + + cases.add("casting pointer to pointer", + \\float **ptrptrcast() { + \\ int **a; + \\ return (float **)a; + \\} + , &[_][]const u8{ + \\pub export fn ptrptrcast() [*c][*c]f32 { + \\ var a: [*c][*c]c_int = undefined; + \\ return @ptrCast([*c][*c]f32, @alignCast(@import("std").meta.alignment([*c][*c]f32), a)); \\} }); @@ -1509,23 +1521,23 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub export fn test_ptr_cast() void { \\ var p: ?*anyopaque = undefined; \\ { - \\ var to_char: [*c]u8 = @ptrCast([*c]u8, @alignCast(@import("std").meta.alignment(u8), p)); + \\ var to_char: [*c]u8 = @ptrCast([*c]u8, @alignCast(@import("std").meta.alignment([*c]u8), p)); \\ _ = to_char; - \\ var to_short: [*c]c_short = @ptrCast([*c]c_short, @alignCast(@import("std").meta.alignment(c_short), p)); + \\ var to_short: [*c]c_short = @ptrCast([*c]c_short, @alignCast(@import("std").meta.alignment([*c]c_short), p)); \\ _ = to_short; - \\ var to_int: [*c]c_int = @ptrCast([*c]c_int, @alignCast(@import("std").meta.alignment(c_int), p)); + \\ var to_int: [*c]c_int = @ptrCast([*c]c_int, @alignCast(@import("std").meta.alignment([*c]c_int), p)); \\ _ = to_int; - \\ var to_longlong: [*c]c_longlong = @ptrCast([*c]c_longlong, @alignCast(@import("std").meta.alignment(c_longlong), p)); + \\ var to_longlong: [*c]c_longlong = @ptrCast([*c]c_longlong, @alignCast(@import("std").meta.alignment([*c]c_longlong), p)); \\ _ = to_longlong; \\ } \\ { - \\ var to_char: [*c]u8 = @ptrCast([*c]u8, @alignCast(@import("std").meta.alignment(u8), p)); + \\ var to_char: [*c]u8 = @ptrCast([*c]u8, @alignCast(@import("std").meta.alignment([*c]u8), p)); \\ _ = to_char; - \\ var to_short: [*c]c_short = @ptrCast([*c]c_short, @alignCast(@import("std").meta.alignment(c_short), p)); + \\ var to_short: [*c]c_short = @ptrCast([*c]c_short, @alignCast(@import("std").meta.alignment([*c]c_short), p)); \\ _ = to_short; - \\ var to_int: [*c]c_int = @ptrCast([*c]c_int, @alignCast(@import("std").meta.alignment(c_int), p)); + \\ var to_int: [*c]c_int = @ptrCast([*c]c_int, @alignCast(@import("std").meta.alignment([*c]c_int), p)); \\ _ = to_int; - \\ var to_longlong: [*c]c_longlong = @ptrCast([*c]c_longlong, @alignCast(@import("std").meta.alignment(c_longlong), p)); + \\ var to_longlong: [*c]c_longlong = @ptrCast([*c]c_longlong, @alignCast(@import("std").meta.alignment([*c]c_longlong), p)); \\ _ = to_longlong; \\ } \\} From 263b5933d2fd63ae4052c7a29288968cbd8505bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Wed, 3 Aug 2022 15:17:21 +0200 Subject: [PATCH 005/290] Makes std.meta.Tuple and std.meta.ArgsTuple generate a unique type instead of generating one per invocation. --- lib/std/meta.zig | 51 +++++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/lib/std/meta.zig b/lib/std/meta.zig index c6717ad1c0..d5006bf81f 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -1024,28 +1024,13 @@ pub fn ArgsTuple(comptime Function: type) type { if (function_info.is_var_args) @compileError("Cannot create ArgsTuple for variadic function"); - var argument_field_list: [function_info.args.len]std.builtin.Type.StructField = undefined; + var argument_field_list: [function_info.args.len]type = undefined; inline for (function_info.args) |arg, i| { const T = arg.arg_type.?; - @setEvalBranchQuota(10_000); - var num_buf: [128]u8 = undefined; - argument_field_list[i] = .{ - .name = std.fmt.bufPrint(&num_buf, "{d}", .{i}) catch unreachable, - .field_type = T, - .default_value = null, - .is_comptime = false, - .alignment = if (@sizeOf(T) > 0) @alignOf(T) else 0, - }; + argument_field_list[i] = T; } - return @Type(.{ - .Struct = .{ - .is_tuple = true, - .layout = .Auto, - .decls = &.{}, - .fields = &argument_field_list, - }, - }); + return CreateUniqueTuple(argument_field_list.len, argument_field_list); } /// For a given anonymous list of types, returns a new tuple type @@ -1056,6 +1041,10 @@ pub fn ArgsTuple(comptime Function: type) type { /// - `Tuple(&[_]type {f32})` ⇒ `tuple { f32 }` /// - `Tuple(&[_]type {f32,u32})` ⇒ `tuple { f32, u32 }` pub fn Tuple(comptime types: []const type) type { + return CreateUniqueTuple(types.len, types[0..types.len].*); +} + +fn CreateUniqueTuple(comptime N: comptime_int, comptime types: [N]type) type { var tuple_fields: [types.len]std.builtin.Type.StructField = undefined; inline for (types) |T, i| { @setEvalBranchQuota(10_000); @@ -1118,6 +1107,32 @@ test "Tuple" { TupleTester.assertTuple(.{ u32, f16, []const u8, void }, Tuple(&[_]type{ u32, f16, []const u8, void })); } +test "Tuple deduplication" { + const T1 = std.meta.Tuple(&.{ u32, f32, i8 }); + const T2 = std.meta.Tuple(&.{ u32, f32, i8 }); + const T3 = std.meta.Tuple(&.{ u32, f32, i7 }); + + if (T1 != T2) { + @compileError("std.meta.Tuple doesn't deduplicate tuple types."); + } + if (T1 == T3) { + @compileError("std.meta.Tuple fails to generate different types."); + } +} + +test "ArgsTuple forwarding" { + const T1 = std.meta.Tuple(&.{ u32, f32, i8 }); + const T2 = std.meta.ArgsTuple(fn (u32, f32, i8) void); + const T3 = std.meta.ArgsTuple(fn (u32, f32, i8) callconv(.C) noreturn); + + if (T1 != T2) { + @compileError("std.meta.ArgsTuple produces different types than std.meta.Tuple"); + } + if (T1 != T3) { + @compileError("std.meta.ArgsTuple produces different types for the same argument lists."); + } +} + /// TODO: https://github.com/ziglang/zig/issues/425 pub fn globalOption(comptime name: []const u8, comptime T: type) ?T { if (!@hasDecl(root, name)) From 44c321c05e0f5755bcb9f4b1a33dc23aeef880aa Mon Sep 17 00:00:00 2001 From: Allan Regush <17693494+AllanRegush@users.noreply.github.com> Date: Wed, 27 Jul 2022 20:49:06 -0600 Subject: [PATCH 006/290] std.enums: make directEnumArrayLen public --- lib/std/enums.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/enums.zig b/lib/std/enums.zig index 31bc367e64..08781767de 100644 --- a/lib/std/enums.zig +++ b/lib/std/enums.zig @@ -57,7 +57,7 @@ pub fn values(comptime E: type) []const E { /// the total number of items which have no matching enum key (holes in the enum /// numbering). So for example, if an enum has values 1, 2, 5, and 6, max_unused_slots /// must be at least 3, to allow unused slots 0, 3, and 4. -fn directEnumArrayLen(comptime E: type, comptime max_unused_slots: comptime_int) comptime_int { +pub fn directEnumArrayLen(comptime E: type, comptime max_unused_slots: comptime_int) comptime_int { var max_value: comptime_int = -1; const max_usize: comptime_int = ~@as(usize, 0); const fields = std.meta.fields(E); From 18440cb239755c983f672b4902147c12e819e029 Mon Sep 17 00:00:00 2001 From: N00byEdge Date: Fri, 5 Aug 2022 14:47:52 +0200 Subject: [PATCH 007/290] std.mem.zeroes: Zero sized structs with uninitialized members (#12246) `std.mem.zeroes(struct{handle: void})` Failed with the following error before: ``` /nix/store/l6v4359wc9xrxxmvvp3rynsb5s3d78xf-zig-0.9.1/lib/zig/std/mem.zig:270:42: error: missing field: 'handle' if (@sizeOf(T) == 0) return T{}; ^ ``` --- lib/std/mem.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 5decb88ff3..aef0e0eb19 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -267,7 +267,7 @@ pub fn zeroes(comptime T: type) T { return null; }, .Struct => |struct_info| { - if (@sizeOf(T) == 0) return T{}; + if (@sizeOf(T) == 0) return undefined; if (struct_info.layout == .Extern) { var item: T = undefined; set(u8, asBytes(&item), 0); @@ -424,6 +424,9 @@ test "zeroes" { comptime var comptime_union = zeroes(C_union); try testing.expectEqual(@as(u8, 0), comptime_union.a); + + // Ensure zero sized struct with fields is initialized correctly. + _ = zeroes(struct { handle: void }); } /// Initializes all fields of the struct with their default value, or zero values if no default value is present. From 423bef4dfc635a3ca0144cac95384984857a8519 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Fri, 29 Jul 2022 00:55:00 +0200 Subject: [PATCH 008/290] stage2 AArch64: Fix struct_field_val for register_with_overflow Now mirrors the behavior of the native ARM backend --- src/arch/aarch64/CodeGen.zig | 38 +++++++++++++++++------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index a8bafee4f8..b68ae283b5 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -3016,29 +3016,27 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { break :result MCValue{ .memory = addr + struct_field_offset }; }, .register_with_overflow => |rwo| { - switch (index) { - 0 => { - // get wrapped value: return register - break :result MCValue{ .register = rwo.reg }; - }, - 1 => { - // TODO return special MCValue condition flags - // get overflow bit: set register to C flag - // resp. V flag - const raw_dest_reg = try self.register_manager.allocReg(null, gp); - const dest_reg = raw_dest_reg.to32(); + const reg_lock = self.register_manager.lockRegAssumeUnused(rwo.reg); + defer self.register_manager.unlockReg(reg_lock); - _ = try self.addInst(.{ - .tag = .cset, - .data = .{ .r_cond = .{ - .rd = dest_reg, - .cond = rwo.flag, - } }, - }); + const field: MCValue = switch (index) { + // get wrapped value: return register + 0 => MCValue{ .register = rwo.reg }, + + // get overflow bit: return C or V flag + 1 => MCValue{ .condition_flags = rwo.flag }, - break :result MCValue{ .register = dest_reg }; - }, else => unreachable, + }; + + if (self.reuseOperand(inst, operand, 0, field)) { + break :result field; + } else { + // Copy to new register + const dest_reg = try self.register_manager.allocReg(null, gp); + try self.genSetReg(struct_ty.structFieldType(index), dest_reg, field); + + break :result MCValue{ .register = dest_reg }; } }, else => return self.fail("TODO implement codegen struct_field_val for {}", .{mcv}), From cf3aaceed9f2a9e1872bdd8b2cccecd1766e2419 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Sat, 30 Jul 2022 23:17:15 +0200 Subject: [PATCH 009/290] stage2 AArch64: introduce MCValue.stack_argument_offset This new MCValue union member shares the same semantics as the MCValue type of the same name in the ARM backend. --- src/arch/aarch64/CodeGen.zig | 210 +++++++++++++++++++++++++++++++++-- src/arch/aarch64/Emit.zig | 64 +++++++++++ src/arch/aarch64/Mir.zig | 10 ++ 3 files changed, 275 insertions(+), 9 deletions(-) diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index b68ae283b5..63be9a2220 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -166,10 +166,12 @@ const MCValue = union(enum) { /// the type is u1) or true (if the type in bool) iff the /// specified condition is true. condition_flags: Condition, + /// The value is a function argument passed via the stack. + stack_argument_offset: u32, fn isMemory(mcv: MCValue) bool { return switch (mcv) { - .memory, .stack_offset => true, + .memory, .stack_offset, .stack_argument_offset => true, else => false, }; } @@ -192,6 +194,7 @@ const MCValue = union(enum) { .condition_flags, .ptr_stack_offset, .undef, + .stack_argument_offset, => false, .register, @@ -337,6 +340,7 @@ pub fn generate( .prev_di_line = module_fn.lbrace_line, .prev_di_column = module_fn.lbrace_column, .stack_size = mem.alignForwardGeneric(u32, function.max_end_stack, function.stack_align), + .prologue_stack_space = call_info.stack_byte_count + function.saved_regs_stack_space, }; defer emit.deinit(); @@ -2726,6 +2730,7 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo }, .memory, .stack_offset, + .stack_argument_offset, .got_load, .direct_load, => { @@ -2927,6 +2932,7 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type }, .memory, .stack_offset, + .stack_argument_offset, .got_load, .direct_load, => { @@ -3009,6 +3015,9 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { switch (mcv) { .dead, .unreach => unreachable, + .stack_argument_offset => |off| { + break :result MCValue{ .stack_argument_offset = off - struct_field_offset }; + }, .stack_offset => |off| { break :result MCValue{ .stack_offset = off - struct_field_offset }; }, @@ -3152,12 +3161,12 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. try self.register_manager.getReg(reg, null); try self.genSetReg(arg_ty, reg, arg_mcv); }, - .stack_offset => { - return self.fail("TODO implement calling with parameters in memory", .{}); - }, - .ptr_stack_offset => { - return self.fail("TODO implement calling with MCValue.ptr_stack_offset arg", .{}); - }, + .stack_offset => unreachable, + .stack_argument_offset => |offset| try self.genSetStackArgument( + arg_ty, + info.stack_byte_count - offset, + arg_mcv, + ), else => unreachable, } } @@ -3884,7 +3893,7 @@ fn br(self: *Self, block: Air.Inst.Index, operand: Air.Inst.Ref) !void { block_data.mcv = switch (operand_mcv) { .none, .dead, .unreach => unreachable, .register, .stack_offset, .memory => operand_mcv, - .immediate, .condition_flags => blk: { + .immediate, .stack_argument_offset, .condition_flags => blk: { const new_mcv = try self.allocRegOrMem(block, true); try self.setRegOrMem(self.air.typeOfIndex(block), new_mcv, operand_mcv); break :blk new_mcv; @@ -4126,6 +4135,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro .got_load, .direct_load, .memory, + .stack_argument_offset, .stack_offset, => { switch (mcv) { @@ -4328,6 +4338,188 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void else => unreachable, } }, + .stack_argument_offset => |off| { + const abi_size = ty.abiSize(self.target.*); + + switch (abi_size) { + 1, 2, 4, 8 => { + const tag: Mir.Inst.Tag = switch (abi_size) { + 1 => if (ty.isSignedInt()) Mir.Inst.Tag.ldrsb_stack_argument else .ldrb_stack_argument, + 2 => if (ty.isSignedInt()) Mir.Inst.Tag.ldrsh_stack_argument else .ldrh_stack_argument, + 4, 8 => .ldr_stack_argument, + else => unreachable, // unexpected abi size + }; + + _ = try self.addInst(.{ + .tag = tag, + .data = .{ .load_store_stack = .{ + .rt = reg, + .offset = @intCast(u32, off), + } }, + }); + }, + 3, 5, 6, 7 => return self.fail("TODO implement genSetReg types size {}", .{abi_size}), + else => unreachable, + } + }, + } +} + +fn genSetStackArgument(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void { + const abi_size = @intCast(u32, ty.abiSize(self.target.*)); + switch (mcv) { + .dead => unreachable, + .none, .unreach => return, + .undef => { + if (!self.wantSafety()) + return; // The already existing value will do just fine. + // TODO Upgrade this to a memset call when we have that available. + switch (ty.abiSize(self.target.*)) { + 1 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaa }), + 2 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaa }), + 4 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaa }), + 8 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }), + else => return self.fail("TODO implement memset", .{}), + } + }, + .register => |reg| { + switch (abi_size) { + 1, 2, 4, 8 => { + const tag: Mir.Inst.Tag = switch (abi_size) { + 1 => .strb_immediate, + 2 => .strh_immediate, + 4, 8 => .str_immediate, + else => unreachable, // unexpected abi size + }; + const rt = registerAlias(reg, abi_size); + const offset = switch (abi_size) { + 1 => blk: { + if (math.cast(u12, stack_offset)) |imm| { + break :blk Instruction.LoadStoreOffset.imm(imm); + } else { + return self.fail("TODO genSetStackArgument byte with larger offset", .{}); + } + }, + 2 => blk: { + assert(std.mem.isAlignedGeneric(u32, stack_offset, 2)); // misaligned stack entry + if (math.cast(u12, @divExact(stack_offset, 2))) |imm| { + break :blk Instruction.LoadStoreOffset.imm(imm); + } else { + return self.fail("TODO getSetStackArgument halfword with larger offset", .{}); + } + }, + 4, 8 => blk: { + const alignment = abi_size; + assert(std.mem.isAlignedGeneric(u32, stack_offset, alignment)); // misaligned stack entry + if (math.cast(u12, @divExact(stack_offset, alignment))) |imm| { + break :blk Instruction.LoadStoreOffset.imm(imm); + } else { + return self.fail("TODO genSetStackArgument with larger offset", .{}); + } + }, + else => unreachable, + }; + + _ = try self.addInst(.{ + .tag = tag, + .data = .{ .load_store_register_immediate = .{ + .rt = rt, + .rn = .sp, + .offset = offset.immediate, + } }, + }); + }, + else => return self.fail("TODO genSetStackArgument other types abi_size={}", .{abi_size}), + } + }, + .register_with_overflow => { + return self.fail("TODO implement genSetStack {}", .{mcv}); + }, + .got_load, + .direct_load, + .memory, + .stack_argument_offset, + .stack_offset, + => { + if (abi_size <= 4) { + const reg = try self.copyToTmpRegister(ty, mcv); + return self.genSetStackArgument(ty, stack_offset, MCValue{ .register = reg }); + } else { + var ptr_ty_payload: Type.Payload.ElemType = .{ + .base = .{ .tag = .single_mut_pointer }, + .data = ty, + }; + const ptr_ty = Type.initPayload(&ptr_ty_payload.base); + + // TODO call extern memcpy + const regs = try self.register_manager.allocRegs(5, .{ null, null, null, null, null }, gp); + const regs_locks = self.register_manager.lockRegsAssumeUnused(5, regs); + defer for (regs_locks) |reg| { + self.register_manager.unlockReg(reg); + }; + + const src_reg = regs[0]; + const dst_reg = regs[1]; + const len_reg = regs[2]; + const count_reg = regs[3]; + const tmp_reg = regs[4]; + + switch (mcv) { + .stack_offset => |off| { + // sub src_reg, fp, #off + try self.genSetReg(ptr_ty, src_reg, .{ .ptr_stack_offset = off }); + }, + .memory => |addr| try self.genSetReg(ptr_ty, src_reg, .{ .immediate = @intCast(u32, addr) }), + .got_load, + .direct_load, + => |sym_index| { + const tag: Mir.Inst.Tag = switch (mcv) { + .got_load => .load_memory_ptr_got, + .direct_load => .load_memory_ptr_direct, + else => unreachable, + }; + const mod = self.bin_file.options.module.?; + _ = try self.addInst(.{ + .tag = tag, + .data = .{ + .payload = try self.addExtra(Mir.LoadMemoryPie{ + .register = @enumToInt(src_reg), + .atom_index = mod.declPtr(self.mod_fn.owner_decl).link.macho.sym_index, + .sym_index = sym_index, + }), + }, + }); + }, + .stack_argument_offset => return self.fail("TODO load {}", .{mcv}), + else => unreachable, + } + + // add dst_reg, sp, #stack_offset + _ = try self.addInst(.{ + .tag = .add_immediate, + .data = .{ .rr_imm12_sh = .{ + .rd = dst_reg, + .rn = .sp, + .imm12 = math.cast(u12, stack_offset) orelse { + return self.fail("TODO load: set reg to stack offset with all possible offsets", .{}); + }, + } }, + }); + + // mov len, #abi_size + try self.genSetReg(Type.usize, len_reg, .{ .immediate = abi_size }); + + // memcpy(src, dst, len) + try self.genInlineMemcpy(src_reg, dst_reg, len_reg, count_reg, tmp_reg); + } + }, + .condition_flags, + .immediate, + .ptr_stack_offset, + => { + const reg = try self.copyToTmpRegister(ty, mcv); + return self.genSetStackArgument(ty, stack_offset, MCValue{ .register = reg }); + }, } } @@ -4835,8 +5027,8 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { } } - result.args[i] = .{ .stack_offset = nsaa }; nsaa += param_size; + result.args[i] = .{ .stack_argument_offset = nsaa }; } } diff --git a/src/arch/aarch64/Emit.zig b/src/arch/aarch64/Emit.zig index 47a0c08893..9320138f65 100644 --- a/src/arch/aarch64/Emit.zig +++ b/src/arch/aarch64/Emit.zig @@ -27,14 +27,21 @@ code: *std.ArrayList(u8), prev_di_line: u32, prev_di_column: u32, + /// Relative to the beginning of `code`. prev_di_pc: usize, +/// The amount of stack space consumed by all stack arguments as well +/// as the saved callee-saved registers +prologue_stack_space: u32, + /// The branch type of every branch branch_types: std.AutoHashMapUnmanaged(Mir.Inst.Index, BranchType) = .{}, + /// For every forward branch, maps the target instruction to a list of /// branches which branch to this target instruction branch_forward_origins: std.AutoHashMapUnmanaged(Mir.Inst.Index, std.ArrayListUnmanaged(Mir.Inst.Index)) = .{}, + /// For backward branches: stores the code offset of the target /// instruction /// @@ -42,6 +49,8 @@ branch_forward_origins: std.AutoHashMapUnmanaged(Mir.Inst.Index, std.ArrayListUn /// instruction code_offset_mapping: std.AutoHashMapUnmanaged(Mir.Inst.Index, usize) = .{}, +/// The final stack frame size of the function (already aligned to the +/// respective stack alignment). Does not include prologue stack space. stack_size: u32, const InnerError = error{ @@ -148,6 +157,12 @@ pub fn emitMir( .strb_stack => try emit.mirLoadStoreStack(inst), .strh_stack => try emit.mirLoadStoreStack(inst), + .ldr_stack_argument => try emit.mirLoadStackArgument(inst), + .ldrb_stack_argument => try emit.mirLoadStackArgument(inst), + .ldrh_stack_argument => try emit.mirLoadStackArgument(inst), + .ldrsb_stack_argument => try emit.mirLoadStackArgument(inst), + .ldrsh_stack_argument => try emit.mirLoadStackArgument(inst), + .ldr_register => try emit.mirLoadStoreRegisterRegister(inst), .ldrb_register => try emit.mirLoadStoreRegisterRegister(inst), .ldrh_register => try emit.mirLoadStoreRegisterRegister(inst), @@ -920,6 +935,55 @@ fn mirLoadStoreRegisterPair(emit: *Emit, inst: Mir.Inst.Index) !void { } } +fn mirLoadStackArgument(emit: *Emit, inst: Mir.Inst.Index) !void { + const tag = emit.mir.instructions.items(.tag)[inst]; + const load_store_stack = emit.mir.instructions.items(.data)[inst].load_store_stack; + const rt = load_store_stack.rt; + + const raw_offset = emit.stack_size + emit.prologue_stack_space - load_store_stack.offset; + const offset = switch (tag) { + .ldrb_stack_argument, .ldrsb_stack_argument => blk: { + if (math.cast(u12, raw_offset)) |imm| { + break :blk Instruction.LoadStoreOffset.imm(imm); + } else { + return emit.fail("TODO load stack argument byte with larger offset", .{}); + } + }, + .ldrh_stack_argument, .ldrsh_stack_argument => blk: { + assert(std.mem.isAlignedGeneric(u32, raw_offset, 2)); // misaligned stack entry + if (math.cast(u12, @divExact(raw_offset, 2))) |imm| { + break :blk Instruction.LoadStoreOffset.imm(imm); + } else { + return emit.fail("TODO load stack argument halfword with larger offset", .{}); + } + }, + .ldr_stack_argument => blk: { + const alignment: u32 = switch (rt.size()) { + 32 => 4, + 64 => 8, + else => unreachable, + }; + + assert(std.mem.isAlignedGeneric(u32, raw_offset, alignment)); // misaligned stack entry + if (math.cast(u12, @divExact(raw_offset, alignment))) |imm| { + break :blk Instruction.LoadStoreOffset.imm(imm); + } else { + return emit.fail("TODO load stack argument with larger offset", .{}); + } + }, + else => unreachable, + }; + + switch (tag) { + .ldr_stack_argument => try emit.writeInstruction(Instruction.ldr(rt, .sp, offset)), + .ldrb_stack_argument => try emit.writeInstruction(Instruction.ldrb(rt, .sp, offset)), + .ldrh_stack_argument => try emit.writeInstruction(Instruction.ldrh(rt, .sp, offset)), + .ldrsb_stack_argument => try emit.writeInstruction(Instruction.ldrsb(rt, .sp, offset)), + .ldrsh_stack_argument => try emit.writeInstruction(Instruction.ldrsh(rt, .sp, offset)), + else => unreachable, + } +} + fn mirLoadStoreStack(emit: *Emit, inst: Mir.Inst.Index) !void { const tag = emit.mir.instructions.items(.tag)[inst]; const load_store_stack = emit.mir.instructions.items(.data)[inst].load_store_stack; diff --git a/src/arch/aarch64/Mir.zig b/src/arch/aarch64/Mir.zig index 2fef069f7a..6242026b66 100644 --- a/src/arch/aarch64/Mir.zig +++ b/src/arch/aarch64/Mir.zig @@ -94,18 +94,24 @@ pub const Inst = struct { ldp, /// Pseudo-instruction: Load from stack ldr_stack, + /// Pseudo-instruction: Load from stack argument + ldr_stack_argument, /// Load Register (immediate) ldr_immediate, /// Load Register (register) ldr_register, /// Pseudo-instruction: Load byte from stack ldrb_stack, + /// Pseudo-instruction: Load byte from stack argument + ldrb_stack_argument, /// Load Register Byte (immediate) ldrb_immediate, /// Load Register Byte (register) ldrb_register, /// Pseudo-instruction: Load halfword from stack ldrh_stack, + /// Pseudo-instruction: Load halfword from stack argument + ldrh_stack_argument, /// Load Register Halfword (immediate) ldrh_immediate, /// Load Register Halfword (register) @@ -114,10 +120,14 @@ pub const Inst = struct { ldrsb_immediate, /// Pseudo-instruction: Load signed byte from stack ldrsb_stack, + /// Pseudo-instruction: Load signed byte from stack argument + ldrsb_stack_argument, /// Load Register Signed Halfword (immediate) ldrsh_immediate, /// Pseudo-instruction: Load signed halfword from stack ldrsh_stack, + /// Pseudo-instruction: Load signed halfword from stack argument + ldrsh_stack_argument, /// Load Register Signed Word (immediate) ldrsw_immediate, /// Logical Shift Left (immediate) From 65b3c27f2457f3d957d83edf13e20e41e84f6dd4 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Tue, 2 Aug 2022 21:04:54 +0200 Subject: [PATCH 010/290] stage2 AArch64: all arguments passed via stack from now on Only in the Undefined calling convention, not in other calling conventions --- src/arch/aarch64/CodeGen.zig | 142 +++++++++++++++++++++-------------- src/arch/aarch64/Emit.zig | 65 +++++++++------- src/arch/aarch64/Mir.zig | 2 + 3 files changed, 128 insertions(+), 81 deletions(-) diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 63be9a2220..f3a6ad84ed 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -340,7 +340,7 @@ pub fn generate( .prev_di_line = module_fn.lbrace_line, .prev_di_column = module_fn.lbrace_column, .stack_size = mem.alignForwardGeneric(u32, function.max_end_stack, function.stack_align), - .prologue_stack_space = call_info.stack_byte_count + function.saved_regs_stack_space, + .saved_regs_stack_space = function.saved_regs_stack_space, }; defer emit.deinit(); @@ -2317,6 +2317,9 @@ fn errUnionErr(self: *Self, error_union_mcv: MCValue, error_union_ty: Type) !MCV const err_offset = @intCast(u32, errUnionErrorOffset(payload_ty, self.target.*)); switch (error_union_mcv) { .register => return self.fail("TODO errUnionErr for registers", .{}), + .stack_argument_offset => |off| { + return MCValue{ .stack_argument_offset = off + err_offset }; + }, .stack_offset => |off| { return MCValue{ .stack_offset = off - err_offset }; }, @@ -2351,6 +2354,9 @@ fn errUnionPayload(self: *Self, error_union_mcv: MCValue, error_union_ty: Type) const payload_offset = @intCast(u32, errUnionPayloadOffset(payload_ty, self.target.*)); switch (error_union_mcv) { .register => return self.fail("TODO errUnionPayload for registers", .{}), + .stack_argument_offset => |off| { + return MCValue{ .stack_argument_offset = off + payload_offset }; + }, .stack_offset => |off| { return MCValue{ .stack_offset = off - payload_offset }; }, @@ -3016,7 +3022,7 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { switch (mcv) { .dead, .unreach => unreachable, .stack_argument_offset => |off| { - break :result MCValue{ .stack_argument_offset = off - struct_field_offset }; + break :result MCValue{ .stack_argument_offset = off + struct_field_offset }; }, .stack_offset => |off| { break :result MCValue{ .stack_offset = off - struct_field_offset }; @@ -3150,6 +3156,9 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. // saving compare flags may require a new caller-saved register try self.spillCompareFlagsIfOccupied(); + // Make space for the arguments passed via the stack + self.max_end_stack += info.stack_byte_count; + for (info.args) |mc_arg, arg_i| { const arg = args[arg_i]; const arg_ty = self.air.typeOf(arg); @@ -3164,7 +3173,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. .stack_offset => unreachable, .stack_argument_offset => |offset| try self.genSetStackArgument( arg_ty, - info.stack_byte_count - offset, + offset, arg_mcv, ), else => unreachable, @@ -3642,40 +3651,14 @@ fn isNonNull(self: *Self, operand: MCValue) !MCValue { fn isErr(self: *Self, ty: Type, operand: MCValue) !MCValue { const error_type = ty.errorUnionSet(); - const payload_type = ty.errorUnionPayload(); + const error_int_type = Type.initTag(.u16); if (error_type.errorSetIsEmpty()) { return MCValue{ .immediate = 0 }; // always false } - const err_off = errUnionErrorOffset(payload_type, self.target.*); - switch (operand) { - .stack_offset => |off| { - const offset = off - @intCast(u32, err_off); - const tmp_reg = try self.copyToTmpRegister(Type.anyerror, .{ .stack_offset = offset }); - _ = try self.addInst(.{ - .tag = .cmp_immediate, - .data = .{ .r_imm12_sh = .{ - .rn = tmp_reg, - .imm12 = 0, - } }, - }); - }, - .register => |reg| { - if (err_off > 0 or payload_type.hasRuntimeBitsIgnoreComptime()) { - return self.fail("TODO implement isErr for register operand with payload bits", .{}); - } - _ = try self.addInst(.{ - .tag = .cmp_immediate, - .data = .{ .r_imm12_sh = .{ - .rn = reg, - .imm12 = 0, - } }, - }); - }, - else => return self.fail("TODO implement isErr for {}", .{operand}), - } - + const error_mcv = try self.errUnionErr(operand, ty); + _ = try self.binOp(.cmp_eq, error_mcv, .{ .immediate = 0 }, error_int_type, error_int_type, null); return MCValue{ .condition_flags = .hi }; } @@ -4174,6 +4157,15 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro // sub src_reg, fp, #off try self.genSetReg(ptr_ty, src_reg, .{ .ptr_stack_offset = off }); }, + .stack_argument_offset => |off| { + _ = try self.addInst(.{ + .tag = .ldr_ptr_stack_argument, + .data = .{ .load_store_stack = .{ + .rt = src_reg, + .offset = off, + } }, + }); + }, .memory => |addr| try self.genSetReg(Type.usize, src_reg, .{ .immediate = addr }), .got_load, .direct_load, @@ -4433,7 +4425,7 @@ fn genSetStackArgument(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) I } }, .register_with_overflow => { - return self.fail("TODO implement genSetStack {}", .{mcv}); + return self.fail("TODO implement genSetStackArgument {}", .{mcv}); }, .got_load, .direct_load, @@ -4469,6 +4461,15 @@ fn genSetStackArgument(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) I // sub src_reg, fp, #off try self.genSetReg(ptr_ty, src_reg, .{ .ptr_stack_offset = off }); }, + .stack_argument_offset => |off| { + _ = try self.addInst(.{ + .tag = .ldr_ptr_stack_argument, + .data = .{ .load_store_stack = .{ + .rt = src_reg, + .offset = off, + } }, + }); + }, .memory => |addr| try self.genSetReg(ptr_ty, src_reg, .{ .immediate = @intCast(u32, addr) }), .got_load, .direct_load, @@ -4490,7 +4491,6 @@ fn genSetStackArgument(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) I }, }); }, - .stack_argument_offset => return self.fail("TODO load {}", .{mcv}), else => unreachable, } @@ -4989,11 +4989,27 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { result.stack_align = 1; return result; }, - .Unspecified, .C => { + .C => { // ARM64 Procedure Call Standard var ncrn: usize = 0; // Next Core Register Number var nsaa: u32 = 0; // Next stacked argument address + if (ret_ty.zigTypeTag() == .NoReturn) { + result.return_value = .{ .unreach = {} }; + } else if (!ret_ty.hasRuntimeBitsIgnoreComptime() and !ret_ty.isError()) { + result.return_value = .{ .none = {} }; + } else { + const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*)); + if (ret_ty_size == 0) { + assert(ret_ty.isError()); + result.return_value = .{ .immediate = 0 }; + } else if (ret_ty_size <= 8) { + result.return_value = .{ .register = registerAlias(c_abi_int_return_regs[0], ret_ty_size) }; + } else { + return self.fail("TODO support more return types for ARM backend", .{}); + } + } + for (param_types) |ty, i| { const param_size = @intCast(u32, ty.abiSize(self.target.*)); if (param_size == 0) { @@ -5027,36 +5043,52 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { } } - nsaa += param_size; result.args[i] = .{ .stack_argument_offset = nsaa }; + nsaa += param_size; } } result.stack_byte_count = nsaa; result.stack_align = 16; }, + .Unspecified => { + if (ret_ty.zigTypeTag() == .NoReturn) { + result.return_value = .{ .unreach = {} }; + } else if (!ret_ty.hasRuntimeBitsIgnoreComptime() and !ret_ty.isError()) { + result.return_value = .{ .none = {} }; + } else { + const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*)); + if (ret_ty_size == 0) { + assert(ret_ty.isError()); + result.return_value = .{ .immediate = 0 }; + } else if (ret_ty_size <= 8) { + result.return_value = .{ .register = registerAlias(c_abi_int_return_regs[0], ret_ty_size) }; + } else { + return self.fail("TODO support more return types for ARM backend", .{}); + } + } + + var stack_offset: u32 = 0; + + for (param_types) |ty, i| { + if (ty.abiSize(self.target.*) > 0) { + const param_size = @intCast(u32, ty.abiSize(self.target.*)); + const param_alignment = ty.abiAlignment(self.target.*); + + stack_offset = std.mem.alignForwardGeneric(u32, stack_offset, param_alignment); + result.args[i] = .{ .stack_argument_offset = stack_offset }; + stack_offset += param_size; + } else { + result.args[i] = .{ .none = {} }; + } + } + + result.stack_byte_count = stack_offset; + result.stack_align = 16; + }, else => return self.fail("TODO implement function parameters for {} on aarch64", .{cc}), } - if (ret_ty.zigTypeTag() == .NoReturn) { - result.return_value = .{ .unreach = {} }; - } else if (!ret_ty.hasRuntimeBitsIgnoreComptime() and !ret_ty.isError()) { - result.return_value = .{ .none = {} }; - } else switch (cc) { - .Naked => unreachable, - .Unspecified, .C => { - const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*)); - if (ret_ty_size == 0) { - assert(ret_ty.isError()); - result.return_value = .{ .immediate = 0 }; - } else if (ret_ty_size <= 8) { - result.return_value = .{ .register = registerAlias(c_abi_int_return_regs[0], ret_ty_size) }; - } else { - return self.fail("TODO support more return types for ARM backend", .{}); - } - }, - else => return self.fail("TODO implement function return values for {}", .{cc}), - } return result; } diff --git a/src/arch/aarch64/Emit.zig b/src/arch/aarch64/Emit.zig index 9320138f65..1ca198ccd8 100644 --- a/src/arch/aarch64/Emit.zig +++ b/src/arch/aarch64/Emit.zig @@ -31,9 +31,9 @@ prev_di_column: u32, /// Relative to the beginning of `code`. prev_di_pc: usize, -/// The amount of stack space consumed by all stack arguments as well -/// as the saved callee-saved registers -prologue_stack_space: u32, +/// The amount of stack space consumed by the saved callee-saved +/// registers in bytes +saved_regs_stack_space: u32, /// The branch type of every branch branch_types: std.AutoHashMapUnmanaged(Mir.Inst.Index, BranchType) = .{}, @@ -158,6 +158,7 @@ pub fn emitMir( .strh_stack => try emit.mirLoadStoreStack(inst), .ldr_stack_argument => try emit.mirLoadStackArgument(inst), + .ldr_ptr_stack_argument => try emit.mirLoadStackArgument(inst), .ldrb_stack_argument => try emit.mirLoadStackArgument(inst), .ldrh_stack_argument => try emit.mirLoadStackArgument(inst), .ldrsb_stack_argument => try emit.mirLoadStackArgument(inst), @@ -940,24 +941,42 @@ fn mirLoadStackArgument(emit: *Emit, inst: Mir.Inst.Index) !void { const load_store_stack = emit.mir.instructions.items(.data)[inst].load_store_stack; const rt = load_store_stack.rt; - const raw_offset = emit.stack_size + emit.prologue_stack_space - load_store_stack.offset; - const offset = switch (tag) { - .ldrb_stack_argument, .ldrsb_stack_argument => blk: { - if (math.cast(u12, raw_offset)) |imm| { - break :blk Instruction.LoadStoreOffset.imm(imm); - } else { + const raw_offset = emit.stack_size + emit.saved_regs_stack_space + load_store_stack.offset; + switch (tag) { + .ldr_ptr_stack_argument => { + const offset = if (math.cast(u12, raw_offset)) |imm| imm else { + return emit.fail("TODO load stack argument ptr with larger offset", .{}); + }; + + switch (tag) { + .ldr_ptr_stack_argument => try emit.writeInstruction(Instruction.add(rt, .sp, offset, false)), + else => unreachable, + } + }, + .ldrb_stack_argument, .ldrsb_stack_argument => { + const offset = if (math.cast(u12, raw_offset)) |imm| Instruction.LoadStoreOffset.imm(imm) else { return emit.fail("TODO load stack argument byte with larger offset", .{}); + }; + + switch (tag) { + .ldrb_stack_argument => try emit.writeInstruction(Instruction.ldrb(rt, .sp, offset)), + .ldrsb_stack_argument => try emit.writeInstruction(Instruction.ldrsb(rt, .sp, offset)), + else => unreachable, } }, - .ldrh_stack_argument, .ldrsh_stack_argument => blk: { + .ldrh_stack_argument, .ldrsh_stack_argument => { assert(std.mem.isAlignedGeneric(u32, raw_offset, 2)); // misaligned stack entry - if (math.cast(u12, @divExact(raw_offset, 2))) |imm| { - break :blk Instruction.LoadStoreOffset.imm(imm); - } else { + const offset = if (math.cast(u12, @divExact(raw_offset, 2))) |imm| Instruction.LoadStoreOffset.imm(imm) else { return emit.fail("TODO load stack argument halfword with larger offset", .{}); + }; + + switch (tag) { + .ldrh_stack_argument => try emit.writeInstruction(Instruction.ldrh(rt, .sp, offset)), + .ldrsh_stack_argument => try emit.writeInstruction(Instruction.ldrsh(rt, .sp, offset)), + else => unreachable, } }, - .ldr_stack_argument => blk: { + .ldr_stack_argument => { const alignment: u32 = switch (rt.size()) { 32 => 4, 64 => 8, @@ -965,22 +984,16 @@ fn mirLoadStackArgument(emit: *Emit, inst: Mir.Inst.Index) !void { }; assert(std.mem.isAlignedGeneric(u32, raw_offset, alignment)); // misaligned stack entry - if (math.cast(u12, @divExact(raw_offset, alignment))) |imm| { - break :blk Instruction.LoadStoreOffset.imm(imm); - } else { + const offset = if (math.cast(u12, @divExact(raw_offset, alignment))) |imm| Instruction.LoadStoreOffset.imm(imm) else { return emit.fail("TODO load stack argument with larger offset", .{}); + }; + + switch (tag) { + .ldr_stack_argument => try emit.writeInstruction(Instruction.ldr(rt, .sp, offset)), + else => unreachable, } }, else => unreachable, - }; - - switch (tag) { - .ldr_stack_argument => try emit.writeInstruction(Instruction.ldr(rt, .sp, offset)), - .ldrb_stack_argument => try emit.writeInstruction(Instruction.ldrb(rt, .sp, offset)), - .ldrh_stack_argument => try emit.writeInstruction(Instruction.ldrh(rt, .sp, offset)), - .ldrsb_stack_argument => try emit.writeInstruction(Instruction.ldrsb(rt, .sp, offset)), - .ldrsh_stack_argument => try emit.writeInstruction(Instruction.ldrsh(rt, .sp, offset)), - else => unreachable, } } diff --git a/src/arch/aarch64/Mir.zig b/src/arch/aarch64/Mir.zig index 6242026b66..c4d6af9db4 100644 --- a/src/arch/aarch64/Mir.zig +++ b/src/arch/aarch64/Mir.zig @@ -92,6 +92,8 @@ pub const Inst = struct { load_memory_ptr_direct, /// Load Pair of Registers ldp, + /// Pseudo-instruction: Load pointer to stack argument + ldr_ptr_stack_argument, /// Pseudo-instruction: Load from stack ldr_stack, /// Pseudo-instruction: Load from stack argument From 02738228f24b807c45f9ae7b3b1f3657668aed27 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Thu, 4 Aug 2022 09:33:04 +0200 Subject: [PATCH 011/290] stage2 AArch64: support returning values by reference also adds some more support for slices passed as stack arguments --- src/arch/aarch64/CodeGen.zig | 153 ++++++++++++++++++++++++++++------- 1 file changed, 124 insertions(+), 29 deletions(-) diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index f3a6ad84ed..c5b71657aa 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -418,6 +418,23 @@ fn gen(self: *Self) !void { // sub sp, sp, #reloc const backpatch_reloc = try self.addNop(); + if (self.ret_mcv == .stack_offset) { + // The address of where to store the return value is in x0 + // (or w0 when pointer size is 32 bits). As this register + // might get overwritten along the way, save the address + // to the stack. + const ptr_bits = self.target.cpu.arch.ptrBitWidth(); + const ptr_bytes = @divExact(ptr_bits, 8); + const ret_ptr_reg = registerAlias(.x0, ptr_bytes); + + const stack_offset = mem.alignForwardGeneric(u32, self.next_stack_offset, ptr_bytes) + ptr_bytes; + self.next_stack_offset = stack_offset; + self.max_end_stack = @maximum(self.max_end_stack, self.next_stack_offset); + + try self.genSetStack(Type.usize, stack_offset, MCValue{ .register = ret_ptr_reg }); + self.ret_mcv = MCValue{ .stack_offset = stack_offset }; + } + _ = try self.addInst(.{ .tag = .dbg_prologue_end, .data = .{ .nop = {} }, @@ -2446,21 +2463,28 @@ fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } +fn slicePtr(mcv: MCValue) MCValue { + switch (mcv) { + .dead, .unreach, .none => unreachable, + .register => unreachable, // a slice doesn't fit in one register + .stack_argument_offset => |off| { + return MCValue{ .stack_argument_offset = off }; + }, + .stack_offset => |off| { + return MCValue{ .stack_offset = off }; + }, + .memory => |addr| { + return MCValue{ .memory = addr }; + }, + else => unreachable, // invalid MCValue for a slice + } +} + fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void { const ty_op = self.air.instructions.items(.data)[inst].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const mcv = try self.resolveInst(ty_op.operand); - switch (mcv) { - .dead, .unreach, .none => unreachable, - .register => unreachable, // a slice doesn't fit in one register - .stack_offset => |off| { - break :result MCValue{ .stack_offset = off }; - }, - .memory => |addr| { - break :result MCValue{ .memory = addr }; - }, - else => return self.fail("TODO implement slice_len for {}", .{mcv}), - } + break :result slicePtr(mcv); }; return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } @@ -2474,6 +2498,9 @@ fn airSliceLen(self: *Self, inst: Air.Inst.Index) !void { switch (mcv) { .dead, .unreach, .none => unreachable, .register => unreachable, // a slice doesn't fit in one register + .stack_argument_offset => |off| { + break :result MCValue{ .stack_argument_offset = off + ptr_bytes }; + }, .stack_offset => |off| { break :result MCValue{ .stack_offset = off - ptr_bytes }; }, @@ -2524,6 +2551,9 @@ fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void { if (!is_volatile and self.liveness.isUnused(inst)) return self.finishAir(inst, .dead, .{ bin_op.lhs, bin_op.rhs, .none }); const result: MCValue = result: { + const slice_ty = self.air.typeOf(bin_op.lhs); + const elem_ty = slice_ty.childType(); + const elem_size = elem_ty.abiSize(self.target.*); const slice_mcv = try self.resolveInst(bin_op.lhs); // TODO optimize for the case where the index is a constant, @@ -2531,10 +2561,6 @@ fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void { const index_mcv = try self.resolveInst(bin_op.rhs); const index_is_register = index_mcv == .register; - const slice_ty = self.air.typeOf(bin_op.lhs); - const elem_ty = slice_ty.childType(); - const elem_size = elem_ty.abiSize(self.target.*); - var buf: Type.SlicePtrFieldTypeBuffer = undefined; const slice_ptr_field_type = slice_ty.slicePtrFieldType(&buf); @@ -2544,15 +2570,17 @@ fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void { null; defer if (index_lock) |reg| self.register_manager.unlockReg(reg); - const base_mcv: MCValue = switch (slice_mcv) { - .stack_offset => |off| .{ .register = try self.copyToTmpRegister(slice_ptr_field_type, .{ .stack_offset = off }) }, - else => return self.fail("TODO slice_elem_val when slice is {}", .{slice_mcv}), - }; - const base_lock = self.register_manager.lockRegAssumeUnused(base_mcv.register); - defer self.register_manager.unlockReg(base_lock); + const base_mcv = slicePtr(slice_mcv); switch (elem_size) { else => { + const base_reg = switch (base_mcv) { + .register => |r| r, + else => try self.copyToTmpRegister(slice_ptr_field_type, base_mcv), + }; + const base_reg_lock = self.register_manager.lockRegAssumeUnused(base_reg); + defer self.register_manager.unlockReg(base_reg_lock); + const dest = try self.allocRegOrMem(inst, true); const addr = try self.binOp(.ptr_add, base_mcv, index_mcv, slice_ptr_field_type, Type.usize, null); try self.load(dest, addr, slice_ptr_field_type); @@ -2567,7 +2595,16 @@ fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void { fn airSliceElemPtr(self: *Self, inst: Air.Inst.Index) !void { const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement slice_elem_ptr for {}", .{self.target.cpu.arch}); + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { + const slice_mcv = try self.resolveInst(extra.lhs); + const index_mcv = try self.resolveInst(extra.rhs); + const base_mcv = slicePtr(slice_mcv); + + const slice_ty = self.air.typeOf(extra.lhs); + + const addr = try self.binOp(.ptr_add, base_mcv, index_mcv, slice_ty, Type.usize, null); + break :result addr; + }; return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none }); } @@ -3156,6 +3193,28 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. // saving compare flags may require a new caller-saved register try self.spillCompareFlagsIfOccupied(); + if (info.return_value == .stack_offset) { + log.debug("airCall: return by reference", .{}); + const ret_ty = fn_ty.fnReturnType(); + const ret_abi_size = @intCast(u32, ret_ty.abiSize(self.target.*)); + const ret_abi_align = @intCast(u32, ret_ty.abiAlignment(self.target.*)); + const stack_offset = try self.allocMem(inst, ret_abi_size, ret_abi_align); + + const ptr_bits = self.target.cpu.arch.ptrBitWidth(); + const ptr_bytes = @divExact(ptr_bits, 8); + const ret_ptr_reg = registerAlias(.x0, ptr_bytes); + + var ptr_ty_payload: Type.Payload.ElemType = .{ + .base = .{ .tag = .single_mut_pointer }, + .data = ret_ty, + }; + const ptr_ty = Type.initPayload(&ptr_ty_payload.base); + try self.register_manager.getReg(ret_ptr_reg, null); + try self.genSetReg(ptr_ty, ret_ptr_reg, .{ .ptr_stack_offset = stack_offset }); + + info.return_value = .{ .stack_offset = stack_offset }; + } + // Make space for the arguments passed via the stack self.max_end_stack += info.stack_byte_count; @@ -3319,8 +3378,15 @@ fn airRet(self: *Self, inst: Air.Inst.Index) !void { }, .stack_offset => { // Return result by reference - // TODO - return self.fail("TODO implement airRet for {}", .{self.ret_mcv}); + // + // self.ret_mcv is an address to where this function + // should store its result into + var ptr_ty_payload: Type.Payload.ElemType = .{ + .base = .{ .tag = .single_mut_pointer }, + .data = ret_ty, + }; + const ptr_ty = Type.initPayload(&ptr_ty_payload.base); + try self.store(self.ret_mcv, operand, ptr_ty, ret_ty); }, else => unreachable, } @@ -3346,10 +3412,34 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void { }, .stack_offset => { // Return result by reference - // TODO - return self.fail("TODO implement airRetLoad for {}", .{self.ret_mcv}); + // + // self.ret_mcv is an address to where this function + // should store its result into + // + // If the operand is a ret_ptr instruction, we are done + // here. Else we need to load the result from the location + // pointed to by the operand and store it to the result + // location. + const op_inst = Air.refToIndex(un_op).?; + if (self.air.instructions.items(.tag)[op_inst] != .ret_ptr) { + const abi_size = @intCast(u32, ret_ty.abiSize(self.target.*)); + const abi_align = ret_ty.abiAlignment(self.target.*); + + // This is essentially allocMem without the + // instruction tracking + if (abi_align > self.stack_align) + self.stack_align = abi_align; + // TODO find a free slot instead of always appending + const offset = mem.alignForwardGeneric(u32, self.next_stack_offset, abi_align) + abi_size; + self.next_stack_offset = offset; + self.max_end_stack = @maximum(self.max_end_stack, self.next_stack_offset); + + const tmp_mcv = MCValue{ .stack_offset = offset }; + try self.load(tmp_mcv, ptr, ptr_ty); + try self.store(self.ret_mcv, tmp_mcv, ptr_ty, ret_ty); + } }, - else => unreachable, + else => unreachable, // invalid return result } try self.exitlude_jump_relocs.append(self.gpa, try self.addNop()); @@ -5062,9 +5152,14 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { assert(ret_ty.isError()); result.return_value = .{ .immediate = 0 }; } else if (ret_ty_size <= 8) { - result.return_value = .{ .register = registerAlias(c_abi_int_return_regs[0], ret_ty_size) }; + result.return_value = .{ .register = registerAlias(.x0, ret_ty_size) }; } else { - return self.fail("TODO support more return types for ARM backend", .{}); + // The result is returned by reference, not by + // value. This means that x0 (or w0 when pointer + // size is 32 bits) will contain the address of + // where this function should write the result + // into. + result.return_value = .{ .stack_offset = 0 }; } } From dcb236acf432c1d772b2f7d65074735eeae4c4c2 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Thu, 4 Aug 2022 17:51:15 +0200 Subject: [PATCH 012/290] stage2 AArch64: memcpy support in store and more complete intcast --- src/arch/aarch64/CodeGen.zig | 80 +++++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 10 deletions(-) diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index c5b71657aa..c30b8d97b3 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -1029,17 +1029,37 @@ fn airIntCast(self: *Self, inst: Air.Inst.Index) !void { if (self.liveness.isUnused(inst)) return self.finishAir(inst, .dead, .{ ty_op.operand, .none, .none }); - const operand_ty = self.air.typeOf(ty_op.operand); - const operand = try self.resolveInst(ty_op.operand); - const info_a = operand_ty.intInfo(self.target.*); - const info_b = self.air.typeOfIndex(inst).intInfo(self.target.*); - if (info_a.signedness != info_b.signedness) - return self.fail("TODO gen intcast sign safety in semantic analysis", .{}); + const operand = ty_op.operand; + const operand_mcv = try self.resolveInst(operand); + const operand_ty = self.air.typeOf(operand); + const operand_info = operand_ty.intInfo(self.target.*); - if (info_a.bits == info_b.bits) - return self.finishAir(inst, operand, .{ ty_op.operand, .none, .none }); + const dest_ty = self.air.typeOfIndex(inst); + const dest_info = dest_ty.intInfo(self.target.*); - return self.fail("TODO implement intCast for {}", .{self.target.cpu.arch}); + const result: MCValue = result: { + const operand_lock: ?RegisterLock = switch (operand_mcv) { + .register => |reg| self.register_manager.lockRegAssumeUnused(reg), + else => null, + }; + defer if (operand_lock) |lock| self.register_manager.unlockReg(lock); + + if (dest_info.bits > operand_info.bits) { + const dest_mcv = try self.allocRegOrMem(inst, true); + try self.setRegOrMem(self.air.typeOfIndex(inst), dest_mcv, operand_mcv); + break :result dest_mcv; + } else { + if (self.reuseOperand(inst, operand, 0, operand_mcv)) { + break :result operand_mcv; + } else { + const dest_mcv = try self.allocRegOrMem(inst, true); + try self.setRegOrMem(self.air.typeOfIndex(inst), dest_mcv, operand_mcv); + break :result dest_mcv; + } + } + }; + + return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } fn truncRegister( @@ -1065,6 +1085,8 @@ fn truncRegister( }); }, 32, 64 => { + assert(dest_reg.size() == operand_reg.size()); + _ = try self.addInst(.{ .tag = .mov_register, .data = .{ .rr = .{ @@ -2955,6 +2977,8 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type defer if (addr_reg_lock) |reg| self.register_manager.unlockReg(reg); switch (value) { + .dead => unreachable, + .undef => unreachable, .register => |value_reg| { try self.genStrRegister(value_reg, addr_reg, value_ty); }, @@ -2968,7 +2992,41 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type try self.genSetReg(value_ty, tmp_reg, value); try self.store(ptr, .{ .register = tmp_reg }, ptr_ty, value_ty); } else { - return self.fail("TODO implement memcpy", .{}); + const regs = try self.register_manager.allocRegs(4, .{ null, null, null, null }, gp); + const regs_locks = self.register_manager.lockRegsAssumeUnused(4, regs); + defer for (regs_locks) |reg| { + self.register_manager.unlockReg(reg); + }; + + const src_reg = addr_reg; + const dst_reg = regs[0]; + const len_reg = regs[1]; + const count_reg = regs[2]; + const tmp_reg = regs[3]; + + switch (value) { + .stack_offset => |off| { + // sub src_reg, fp, #off + try self.genSetReg(ptr_ty, src_reg, .{ .ptr_stack_offset = off }); + }, + .memory => |addr| try self.genSetReg(Type.usize, src_reg, .{ .immediate = @intCast(u32, addr) }), + .stack_argument_offset => |off| { + _ = try self.addInst(.{ + .tag = .ldr_ptr_stack_argument, + .data = .{ .load_store_stack = .{ + .rt = src_reg, + .offset = off, + } }, + }); + }, + else => return self.fail("TODO store {} to register", .{value}), + } + + // mov len, #abi_size + try self.genSetReg(Type.usize, len_reg, .{ .immediate = abi_size }); + + // memcpy(src, dst, len) + try self.genInlineMemcpy(src_reg, dst_reg, len_reg, count_reg, tmp_reg); } }, } @@ -4359,6 +4417,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void } }, .register => |src_reg| { + assert(src_reg.size() == reg.size()); + // If the registers are the same, nothing to do. if (src_reg.id() == reg.id()) return; From 8b24c783c5bd417f84beeb2f3736a78c3f595d22 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Thu, 4 Aug 2022 21:05:11 +0200 Subject: [PATCH 013/290] stage2 AArch64: implement basic integer division --- src/arch/aarch64/CodeGen.zig | 130 ++++++++++++++++++++++++----------- src/arch/aarch64/Emit.zig | 12 ++-- src/arch/aarch64/Mir.zig | 4 ++ src/arch/aarch64/bits.zig | 8 +++ 4 files changed, 109 insertions(+), 45 deletions(-) diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index c30b8d97b3..37c3721709 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -561,33 +561,38 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { switch (air_tags[inst]) { // zig fmt: off - .add => try self.airBinOp(inst, .add), - .addwrap => try self.airBinOp(inst, .addwrap), - .sub => try self.airBinOp(inst, .sub), - .subwrap => try self.airBinOp(inst, .subwrap), - .mul => try self.airBinOp(inst, .mul), - .mulwrap => try self.airBinOp(inst, .mulwrap), - .shl => try self.airBinOp(inst, .shl), - .shl_exact => try self.airBinOp(inst, .shl_exact), - .bool_and => try self.airBinOp(inst, .bool_and), - .bool_or => try self.airBinOp(inst, .bool_or), - .bit_and => try self.airBinOp(inst, .bit_and), - .bit_or => try self.airBinOp(inst, .bit_or), - .xor => try self.airBinOp(inst, .xor), - .shr => try self.airBinOp(inst, .shr), - .shr_exact => try self.airBinOp(inst, .shr_exact), + .add => try self.airBinOp(inst, .add), + .addwrap => try self.airBinOp(inst, .addwrap), + .sub => try self.airBinOp(inst, .sub), + .subwrap => try self.airBinOp(inst, .subwrap), + .mul => try self.airBinOp(inst, .mul), + .mulwrap => try self.airBinOp(inst, .mulwrap), + .shl => try self.airBinOp(inst, .shl), + .shl_exact => try self.airBinOp(inst, .shl_exact), + .bool_and => try self.airBinOp(inst, .bool_and), + .bool_or => try self.airBinOp(inst, .bool_or), + .bit_and => try self.airBinOp(inst, .bit_and), + .bit_or => try self.airBinOp(inst, .bit_or), + .xor => try self.airBinOp(inst, .xor), + .shr => try self.airBinOp(inst, .shr), + .shr_exact => try self.airBinOp(inst, .shr_exact), + .div_float => try self.airBinOp(inst, .div_float), + .div_trunc => try self.airBinOp(inst, .div_trunc), + .div_floor => try self.airBinOp(inst, .div_floor), + .div_exact => try self.airBinOp(inst, .div_exact), + .rem => try self.airBinOp(inst, .rem), + .mod => try self.airBinOp(inst, .mod), - .ptr_add => try self.airPtrArithmetic(inst, .ptr_add), - .ptr_sub => try self.airPtrArithmetic(inst, .ptr_sub), + .ptr_add => try self.airPtrArithmetic(inst, .ptr_add), + .ptr_sub => try self.airPtrArithmetic(inst, .ptr_sub), + + .min => try self.airMin(inst), + .max => try self.airMax(inst), .add_sat => try self.airAddSat(inst), .sub_sat => try self.airSubSat(inst), .mul_sat => try self.airMulSat(inst), - .rem => try self.airRem(inst), - .mod => try self.airMod(inst), .shl_sat => try self.airShlSat(inst), - .min => try self.airMin(inst), - .max => try self.airMax(inst), .slice => try self.airSlice(inst), .sqrt, @@ -612,8 +617,6 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .mul_with_overflow => try self.airMulWithOverflow(inst), .shl_with_overflow => try self.airShlWithOverflow(inst), - .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst), - .cmp_lt => try self.airCmp(inst, .lt), .cmp_lte => try self.airCmp(inst, .lte), .cmp_eq => try self.airCmp(inst, .eq), @@ -1391,6 +1394,8 @@ fn binOpRegister( .lsl_register, .asr_register, .lsr_register, + .sdiv, + .udiv, => .{ .rrr = .{ .rd = dest_reg, .rn = lhs_reg, @@ -1629,6 +1634,67 @@ fn binOp( else => unreachable, } }, + .div_float => { + switch (lhs_ty.zigTypeTag()) { + .Float => return self.fail("TODO div_float", .{}), + .Vector => return self.fail("TODO div_float on vectors", .{}), + else => unreachable, + } + }, + .div_trunc, .div_floor, .div_exact => { + switch (lhs_ty.zigTypeTag()) { + .Float => return self.fail("TODO div on floats", .{}), + .Vector => return self.fail("TODO div on vectors", .{}), + .Int => { + assert(lhs_ty.eql(rhs_ty, mod)); + const int_info = lhs_ty.intInfo(self.target.*); + if (int_info.bits <= 64) { + switch (int_info.signedness) { + .signed => { + switch (tag) { + .div_trunc, .div_exact => { + // TODO optimize integer division by constants + return try self.binOpRegister(.sdiv, lhs, rhs, lhs_ty, rhs_ty, metadata); + }, + .div_floor => return self.fail("TODO div_floor on signed integers", .{}), + else => unreachable, + } + }, + .unsigned => { + // TODO optimize integer division by constants + return try self.binOpRegister(.udiv, lhs, rhs, lhs_ty, rhs_ty, metadata); + }, + } + } else { + return self.fail("TODO integer division for ints with bits > 64", .{}); + } + }, + else => unreachable, + } + }, + .rem, .mod => { + switch (lhs_ty.zigTypeTag()) { + .Float => return self.fail("TODO rem/mod on floats", .{}), + .Vector => return self.fail("TODO rem/mod on vectors", .{}), + .Int => { + assert(lhs_ty.eql(rhs_ty, mod)); + const int_info = lhs_ty.intInfo(self.target.*); + if (int_info.bits <= 32) { + switch (int_info.signedness) { + .signed => { + return self.fail("TODO rem/mod on signed integers", .{}); + }, + .unsigned => { + return self.fail("TODO rem/mod on unsigned integers", .{}); + }, + } + } else { + return self.fail("TODO rem/mod for integers with bits > 64", .{}); + } + }, + else => unreachable, + } + }, .addwrap, .subwrap, .mulwrap, @@ -2300,24 +2366,6 @@ fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none }); } -fn airDiv(self: *Self, inst: Air.Inst.Index) !void { - const bin_op = self.air.instructions.items(.data)[inst].bin_op; - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement div for {}", .{self.target.cpu.arch}); - return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); -} - -fn airRem(self: *Self, inst: Air.Inst.Index) !void { - const bin_op = self.air.instructions.items(.data)[inst].bin_op; - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement rem for {}", .{self.target.cpu.arch}); - return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); -} - -fn airMod(self: *Self, inst: Air.Inst.Index) !void { - const bin_op = self.air.instructions.items(.data)[inst].bin_op; - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement mod for {}", .{self.target.cpu.arch}); - return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); -} - fn airShlSat(self: *Self, inst: Air.Inst.Index) !void { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement shl_sat for {}", .{self.target.cpu.arch}); diff --git a/src/arch/aarch64/Emit.zig b/src/arch/aarch64/Emit.zig index 1ca198ccd8..ba85730276 100644 --- a/src/arch/aarch64/Emit.zig +++ b/src/arch/aarch64/Emit.zig @@ -91,9 +91,11 @@ pub fn emitMir( .sub_immediate => try emit.mirAddSubtractImmediate(inst), .subs_immediate => try emit.mirAddSubtractImmediate(inst), - .asr_register => try emit.mirShiftRegister(inst), - .lsl_register => try emit.mirShiftRegister(inst), - .lsr_register => try emit.mirShiftRegister(inst), + .asr_register => try emit.mirDataProcessing2Source(inst), + .lsl_register => try emit.mirDataProcessing2Source(inst), + .lsr_register => try emit.mirDataProcessing2Source(inst), + .sdiv => try emit.mirDataProcessing2Source(inst), + .udiv => try emit.mirDataProcessing2Source(inst), .asr_immediate => try emit.mirShiftImmediate(inst), .lsl_immediate => try emit.mirShiftImmediate(inst), @@ -520,7 +522,7 @@ fn mirAddSubtractImmediate(emit: *Emit, inst: Mir.Inst.Index) !void { } } -fn mirShiftRegister(emit: *Emit, inst: Mir.Inst.Index) !void { +fn mirDataProcessing2Source(emit: *Emit, inst: Mir.Inst.Index) !void { const tag = emit.mir.instructions.items(.tag)[inst]; const rrr = emit.mir.instructions.items(.data)[inst].rrr; const rd = rrr.rd; @@ -531,6 +533,8 @@ fn mirShiftRegister(emit: *Emit, inst: Mir.Inst.Index) !void { .asr_register => try emit.writeInstruction(Instruction.asrRegister(rd, rn, rm)), .lsl_register => try emit.writeInstruction(Instruction.lslRegister(rd, rn, rm)), .lsr_register => try emit.writeInstruction(Instruction.lsrRegister(rd, rn, rm)), + .sdiv => try emit.writeInstruction(Instruction.sdiv(rd, rn, rm)), + .udiv => try emit.writeInstruction(Instruction.udiv(rd, rn, rm)), else => unreachable, } } diff --git a/src/arch/aarch64/Mir.zig b/src/arch/aarch64/Mir.zig index c4d6af9db4..d1ba38a779 100644 --- a/src/arch/aarch64/Mir.zig +++ b/src/arch/aarch64/Mir.zig @@ -164,6 +164,8 @@ pub const Inst = struct { ret, /// Signed bitfield extract sbfx, + /// Signed divide + sdiv, /// Signed multiply high smulh, /// Signed multiply long @@ -212,6 +214,8 @@ pub const Inst = struct { tst_immediate, /// Unsigned bitfield extract ubfx, + /// Unsigned divide + udiv, /// Unsigned multiply high umulh, /// Unsigned multiply long diff --git a/src/arch/aarch64/bits.zig b/src/arch/aarch64/bits.zig index a3f5fbac51..ad45661b70 100644 --- a/src/arch/aarch64/bits.zig +++ b/src/arch/aarch64/bits.zig @@ -1698,6 +1698,14 @@ pub const Instruction = union(enum) { // Data processing (2 source) + pub fn udiv(rd: Register, rn: Register, rm: Register) Instruction { + return dataProcessing2Source(0b0, 0b000010, rd, rn, rm); + } + + pub fn sdiv(rd: Register, rn: Register, rm: Register) Instruction { + return dataProcessing2Source(0b0, 0b000011, rd, rn, rm); + } + pub fn lslv(rd: Register, rn: Register, rm: Register) Instruction { return dataProcessing2Source(0b0, 0b001000, rd, rn, rm); } From 508b90fcfa4749b50618f947e2c3573edcf29713 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Fri, 5 Aug 2022 15:22:53 +0200 Subject: [PATCH 014/290] stage2 AArch64: implement basic integer rem/mod --- src/arch/aarch64/CodeGen.zig | 85 ++++++++++++++++++++++++++++++------ src/arch/aarch64/Emit.zig | 30 ++++++++++--- src/arch/aarch64/Mir.zig | 11 +++++ 3 files changed, 107 insertions(+), 19 deletions(-) diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 37c3721709..4a08a91976 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -1038,6 +1038,7 @@ fn airIntCast(self: *Self, inst: Air.Inst.Index) !void { const operand_info = operand_ty.intInfo(self.target.*); const dest_ty = self.air.typeOfIndex(inst); + const dest_abi_size = dest_ty.abiSize(self.target.*); const dest_info = dest_ty.intInfo(self.target.*); const result: MCValue = result: { @@ -1047,16 +1048,21 @@ fn airIntCast(self: *Self, inst: Air.Inst.Index) !void { }; defer if (operand_lock) |lock| self.register_manager.unlockReg(lock); + const truncated: MCValue = switch (operand_mcv) { + .register => |r| MCValue{ .register = registerAlias(r, dest_abi_size) }, + else => operand_mcv, + }; + if (dest_info.bits > operand_info.bits) { const dest_mcv = try self.allocRegOrMem(inst, true); - try self.setRegOrMem(self.air.typeOfIndex(inst), dest_mcv, operand_mcv); + try self.setRegOrMem(self.air.typeOfIndex(inst), dest_mcv, truncated); break :result dest_mcv; } else { - if (self.reuseOperand(inst, operand, 0, operand_mcv)) { - break :result operand_mcv; + if (self.reuseOperand(inst, operand, 0, truncated)) { + break :result truncated; } else { const dest_mcv = try self.allocRegOrMem(inst, true); - try self.setRegOrMem(self.air.typeOfIndex(inst), dest_mcv, operand_mcv); + try self.setRegOrMem(self.air.typeOfIndex(inst), dest_mcv, truncated); break :result dest_mcv; } } @@ -1145,7 +1151,7 @@ fn trunc( return MCValue{ .register = dest_reg }; } else { - return self.fail("TODO: truncate to ints > 32 bits", .{}); + return self.fail("TODO: truncate to ints > 64 bits", .{}); } } @@ -1679,14 +1685,67 @@ fn binOp( .Int => { assert(lhs_ty.eql(rhs_ty, mod)); const int_info = lhs_ty.intInfo(self.target.*); - if (int_info.bits <= 32) { - switch (int_info.signedness) { - .signed => { - return self.fail("TODO rem/mod on signed integers", .{}); - }, - .unsigned => { - return self.fail("TODO rem/mod on unsigned integers", .{}); - }, + if (int_info.bits <= 64) { + if (int_info.signedness == .signed and tag == .mod) { + return self.fail("TODO mod on signed integers", .{}); + } else { + const lhs_is_register = lhs == .register; + const rhs_is_register = rhs == .register; + + const lhs_lock: ?RegisterLock = if (lhs_is_register) + self.register_manager.lockReg(lhs.register) + else + null; + defer if (lhs_lock) |reg| self.register_manager.unlockReg(reg); + + const lhs_reg = if (lhs_is_register) + lhs.register + else + try self.register_manager.allocReg(null, gp); + const new_lhs_lock = self.register_manager.lockReg(lhs_reg); + defer if (new_lhs_lock) |reg| self.register_manager.unlockReg(reg); + + const rhs_reg = if (rhs_is_register) + rhs.register + else + try self.register_manager.allocReg(null, gp); + const new_rhs_lock = self.register_manager.lockReg(rhs_reg); + defer if (new_rhs_lock) |reg| self.register_manager.unlockReg(reg); + + const dest_regs = try self.register_manager.allocRegs(2, .{ null, null }, gp); + const dest_regs_locks = self.register_manager.lockRegsAssumeUnused(2, dest_regs); + defer for (dest_regs_locks) |reg| { + self.register_manager.unlockReg(reg); + }; + const quotient_reg = dest_regs[0]; + const remainder_reg = dest_regs[1]; + + if (!lhs_is_register) try self.genSetReg(lhs_ty, lhs_reg, lhs); + if (!rhs_is_register) try self.genSetReg(rhs_ty, rhs_reg, rhs); + + _ = try self.addInst(.{ + .tag = switch (int_info.signedness) { + .signed => .sdiv, + .unsigned => .udiv, + }, + .data = .{ .rrr = .{ + .rd = quotient_reg, + .rn = lhs_reg, + .rm = rhs_reg, + } }, + }); + + _ = try self.addInst(.{ + .tag = .msub, + .data = .{ .rrrr = .{ + .rd = remainder_reg, + .rn = quotient_reg, + .rm = rhs_reg, + .ra = lhs_reg, + } }, + }); + + return MCValue{ .register = remainder_reg }; } } else { return self.fail("TODO rem/mod for integers with bits > 64", .{}); diff --git a/src/arch/aarch64/Emit.zig b/src/arch/aarch64/Emit.zig index ba85730276..8abc083a1e 100644 --- a/src/arch/aarch64/Emit.zig +++ b/src/arch/aarch64/Emit.zig @@ -190,6 +190,7 @@ pub fn emitMir( .movk => try emit.mirMoveWideImmediate(inst), .movz => try emit.mirMoveWideImmediate(inst), + .msub => try emit.mirDataProcessing3Source(inst), .mul => try emit.mirDataProcessing3Source(inst), .smulh => try emit.mirDataProcessing3Source(inst), .smull => try emit.mirDataProcessing3Source(inst), @@ -1140,14 +1141,31 @@ fn mirMoveWideImmediate(emit: *Emit, inst: Mir.Inst.Index) !void { fn mirDataProcessing3Source(emit: *Emit, inst: Mir.Inst.Index) !void { const tag = emit.mir.instructions.items(.tag)[inst]; - const rrr = emit.mir.instructions.items(.data)[inst].rrr; switch (tag) { - .mul => try emit.writeInstruction(Instruction.mul(rrr.rd, rrr.rn, rrr.rm)), - .smulh => try emit.writeInstruction(Instruction.smulh(rrr.rd, rrr.rn, rrr.rm)), - .smull => try emit.writeInstruction(Instruction.smull(rrr.rd, rrr.rn, rrr.rm)), - .umulh => try emit.writeInstruction(Instruction.umulh(rrr.rd, rrr.rn, rrr.rm)), - .umull => try emit.writeInstruction(Instruction.umull(rrr.rd, rrr.rn, rrr.rm)), + .mul, + .smulh, + .smull, + .umulh, + .umull, + => { + const rrr = emit.mir.instructions.items(.data)[inst].rrr; + switch (tag) { + .mul => try emit.writeInstruction(Instruction.mul(rrr.rd, rrr.rn, rrr.rm)), + .smulh => try emit.writeInstruction(Instruction.smulh(rrr.rd, rrr.rn, rrr.rm)), + .smull => try emit.writeInstruction(Instruction.smull(rrr.rd, rrr.rn, rrr.rm)), + .umulh => try emit.writeInstruction(Instruction.umulh(rrr.rd, rrr.rn, rrr.rm)), + .umull => try emit.writeInstruction(Instruction.umull(rrr.rd, rrr.rn, rrr.rm)), + else => unreachable, + } + }, + .msub => { + const rrrr = emit.mir.instructions.items(.data)[inst].rrrr; + switch (tag) { + .msub => try emit.writeInstruction(Instruction.msub(rrrr.rd, rrrr.rn, rrrr.rm, rrrr.ra)), + else => unreachable, + } + }, else => unreachable, } } diff --git a/src/arch/aarch64/Mir.zig b/src/arch/aarch64/Mir.zig index d1ba38a779..00537e0e38 100644 --- a/src/arch/aarch64/Mir.zig +++ b/src/arch/aarch64/Mir.zig @@ -148,6 +148,8 @@ pub const Inst = struct { movk, /// Move wide with zero movz, + /// Multiply-subtract + msub, /// Multiply mul, /// Bitwise NOT @@ -446,6 +448,15 @@ pub const Inst = struct { rn: Register, offset: bits.Instruction.LoadStorePairOffset, }, + /// Four registers + /// + /// Used by e.g. msub + rrrr: struct { + rd: Register, + rn: Register, + rm: Register, + ra: Register, + }, /// Debug info: line and column /// /// Used by e.g. dbg_line From f46c80b267396d02b5008bf8c426e0eb886a05d2 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Fri, 5 Aug 2022 19:59:26 +0200 Subject: [PATCH 015/290] stage2 AArch64: improve correctness of register aliases Also implements ptr_elem_ptr --- src/arch/aarch64/CodeGen.zig | 83 ++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 4a08a91976..ba53c2e757 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -1314,6 +1314,9 @@ fn binOpRegister( const lhs_is_register = lhs == .register; const rhs_is_register = rhs == .register; + if (lhs_is_register) assert(lhs.register == registerAlias(lhs.register, lhs_ty.abiSize(self.target.*))); + if (rhs_is_register) assert(rhs.register == registerAlias(rhs.register, rhs_ty.abiSize(self.target.*))); + const lhs_lock: ?RegisterLock = if (lhs_is_register) self.register_manager.lockReg(lhs.register) else @@ -1343,13 +1346,22 @@ fn binOpRegister( const new_lhs_lock = self.register_manager.lockReg(lhs_reg); defer if (new_lhs_lock) |reg| self.register_manager.unlockReg(reg); - const rhs_reg = if (rhs_is_register) rhs.register else blk: { + const rhs_reg = if (rhs_is_register) + // lhs is almost always equal to rhs, except in shifts. In + // order to guarantee that registers will have equal sizes, we + // use the register alias of rhs corresponding to the size of + // lhs. + registerAlias(rhs.register, lhs_ty.abiSize(self.target.*)) + else blk: { const track_inst: ?Air.Inst.Index = if (metadata) |md| inst: { break :inst Air.refToIndex(md.rhs).?; } else null; const raw_reg = try self.register_manager.allocReg(track_inst, gp); - const reg = registerAlias(raw_reg, rhs_ty.abiAlignment(self.target.*)); + + // Here, we deliberately use lhs as lhs and rhs may differ in + // the case of shifts. See comment above. + const reg = registerAlias(raw_reg, lhs_ty.abiSize(self.target.*)); if (track_inst) |inst| branch.inst_table.putAssumeCapacity(inst, .{ .register = reg }); @@ -1458,6 +1470,8 @@ fn binOpImmediate( ) !MCValue { const lhs_is_register = lhs == .register; + if (lhs_is_register) assert(lhs.register == registerAlias(lhs.register, lhs_ty.abiSize(self.target.*))); + const lhs_lock: ?RegisterLock = if (lhs_is_register) self.register_manager.lockReg(lhs.register) else @@ -1698,21 +1712,52 @@ fn binOp( null; defer if (lhs_lock) |reg| self.register_manager.unlockReg(reg); - const lhs_reg = if (lhs_is_register) - lhs.register + const rhs_lock: ?RegisterLock = if (rhs_is_register) + self.register_manager.lockReg(rhs.register) else - try self.register_manager.allocReg(null, gp); + null; + defer if (rhs_lock) |reg| self.register_manager.unlockReg(reg); + + const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; + + const lhs_reg = if (lhs_is_register) lhs.register else blk: { + const track_inst: ?Air.Inst.Index = if (metadata) |md| inst: { + break :inst Air.refToIndex(md.lhs).?; + } else null; + + const raw_reg = try self.register_manager.allocReg(track_inst, gp); + const reg = registerAlias(raw_reg, lhs_ty.abiSize(self.target.*)); + + if (track_inst) |inst| branch.inst_table.putAssumeCapacity(inst, .{ .register = reg }); + + break :blk reg; + }; const new_lhs_lock = self.register_manager.lockReg(lhs_reg); defer if (new_lhs_lock) |reg| self.register_manager.unlockReg(reg); - const rhs_reg = if (rhs_is_register) - rhs.register - else - try self.register_manager.allocReg(null, gp); + const rhs_reg = if (rhs_is_register) rhs.register else blk: { + const track_inst: ?Air.Inst.Index = if (metadata) |md| inst: { + break :inst Air.refToIndex(md.rhs).?; + } else null; + + const raw_reg = try self.register_manager.allocReg(track_inst, gp); + const reg = registerAlias(raw_reg, rhs_ty.abiAlignment(self.target.*)); + + if (track_inst) |inst| branch.inst_table.putAssumeCapacity(inst, .{ .register = reg }); + + break :blk reg; + }; const new_rhs_lock = self.register_manager.lockReg(rhs_reg); defer if (new_rhs_lock) |reg| self.register_manager.unlockReg(reg); - const dest_regs = try self.register_manager.allocRegs(2, .{ null, null }, gp); + const dest_regs: [2]Register = blk: { + const raw_regs = try self.register_manager.allocRegs(2, .{ null, null }, gp); + const abi_size = lhs_ty.abiSize(self.target.*); + break :blk .{ + registerAlias(raw_regs[0], abi_size), + registerAlias(raw_regs[1], abi_size), + }; + }; const dest_regs_locks = self.register_manager.lockRegsAssumeUnused(2, dest_regs); defer for (dest_regs_locks) |reg| { self.register_manager.unlockReg(reg); @@ -2037,7 +2082,7 @@ fn airOverflow(self: *Self, inst: Air.Inst.Index) !void { try self.truncRegister(dest_reg, truncated_reg, int_info.signedness, int_info.bits); // cmp dest, truncated - _ = try self.binOp(.cmp_eq, dest, .{ .register = truncated_reg }, Type.usize, Type.usize, null); + _ = try self.binOp(.cmp_eq, dest, .{ .register = truncated_reg }, lhs_ty, lhs_ty, null); try self.genSetStack(lhs_ty, stack_offset, .{ .register = truncated_reg }); try self.genSetStack(Type.initTag(.u1), stack_offset - overflow_bit_offset, .{ .condition_flags = .ne }); @@ -2753,7 +2798,15 @@ fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void { fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) !void { const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement ptr_elem_ptr for {}", .{self.target.cpu.arch}); + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { + const ptr_mcv = try self.resolveInst(extra.lhs); + const index_mcv = try self.resolveInst(extra.rhs); + + const ptr_ty = self.air.typeOf(extra.lhs); + + const addr = try self.binOp(.ptr_add, ptr_mcv, index_mcv, ptr_ty, Type.usize, null); + break :result addr; + }; return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none }); } @@ -3219,6 +3272,7 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const mcv = try self.resolveInst(operand); const struct_ty = self.air.typeOf(operand); + const struct_field_ty = struct_ty.structFieldType(index); const struct_field_offset = @intCast(u32, struct_ty.structFieldOffset(index, self.target.*)); switch (mcv) { @@ -3250,8 +3304,9 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { break :result field; } else { // Copy to new register - const dest_reg = try self.register_manager.allocReg(null, gp); - try self.genSetReg(struct_ty.structFieldType(index), dest_reg, field); + const raw_dest_reg = try self.register_manager.allocReg(null, gp); + const dest_reg = registerAlias(raw_dest_reg, struct_field_ty.abiSize(self.target.*)); + try self.genSetReg(struct_field_ty, dest_reg, field); break :result MCValue{ .register = dest_reg }; } From eec2978fac240f6852873bdd9a3ae03b77df6199 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 5 Aug 2022 16:49:06 +0300 Subject: [PATCH 016/290] Sema: better safety check on switch on corrupt value --- src/Sema.zig | 12 ++++++++---- test/behavior/switch.zig | 1 + test/cases/safety/switch on corrupted enum value.zig | 7 ++++--- .../cases/safety/switch on corrupted union value.zig | 7 ++++--- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index ad8879c599..d81d2fa726 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -9692,7 +9692,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } var final_else_body: []const Air.Inst.Index = &.{}; - if (special.body.len != 0 or !is_first) { + if (special.body.len != 0 or !is_first or case_block.wantSafety()) { var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, child_block.wip_capture_scope); defer wip_captures.deinit(); @@ -9715,9 +9715,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } else { // We still need a terminator in this block, but we have proven // that it is unreachable. - // TODO this should be a special safety panic other than unreachable, something - // like "panic: switch operand had corrupt value not allowed by the type" - try case_block.addUnreachable(src, true); + if (case_block.wantSafety()) { + _ = try sema.safetyPanic(&case_block, src, .corrupt_switch); + } else { + _ = try case_block.addNoOp(.unreach); + } } try wip_captures.finalize(); @@ -19970,6 +19972,7 @@ pub const PanicId = enum { /// TODO make this call `std.builtin.panicInactiveUnionField`. inactive_union_field, integer_part_out_of_bounds, + corrupt_switch, }; fn addSafetyCheck( @@ -20265,6 +20268,7 @@ fn safetyPanic( .exact_division_remainder => "exact division produced remainder", .inactive_union_field => "access of inactive union field", .integer_part_out_of_bounds => "integer part of floating point value out of bounds", + .corrupt_switch => "switch on corrupt value", }; const msg_inst = msg_inst: { diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig index 4e86bcadeb..d218fb6bc6 100644 --- a/test/behavior/switch.zig +++ b/test/behavior/switch.zig @@ -531,6 +531,7 @@ test "switch with null and T peer types and inferred result location type" { test "switch prongs with cases with identical payload types" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO const Union = union(enum) { A: usize, diff --git a/test/cases/safety/switch on corrupted enum value.zig b/test/cases/safety/switch on corrupted enum value.zig index dc7b9b3abf..fd94976763 100644 --- a/test/cases/safety/switch on corrupted enum value.zig +++ b/test/cases/safety/switch on corrupted enum value.zig @@ -2,7 +2,7 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { _ = stack_trace; - if (std.mem.eql(u8, message, "reached unreachable code")) { + if (std.mem.eql(u8, message, "switch on corrupt value")) { std.process.exit(0); } std.process.exit(1); @@ -10,17 +10,18 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noretur const E = enum(u32) { X = 1, + Y = 2, }; pub fn main() !void { var e: E = undefined; @memset(@ptrCast([*]u8, &e), 0x55, @sizeOf(E)); switch (e) { - .X => @breakpoint(), + .X, .Y => @breakpoint(), } return error.TestFailed; } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/switch on corrupted union value.zig b/test/cases/safety/switch on corrupted union value.zig index 0fadad3c7e..059f0dc042 100644 --- a/test/cases/safety/switch on corrupted union value.zig +++ b/test/cases/safety/switch on corrupted union value.zig @@ -2,7 +2,7 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { _ = stack_trace; - if (std.mem.eql(u8, message, "reached unreachable code")) { + if (std.mem.eql(u8, message, "switch on corrupt value")) { std.process.exit(0); } std.process.exit(1); @@ -10,17 +10,18 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noretur const U = union(enum(u32)) { X: u8, + Y: i8, }; pub fn main() !void { var u: U = undefined; @memset(@ptrCast([*]u8, &u), 0x55, @sizeOf(U)); switch (u) { - .X => @breakpoint(), + .X, .Y => @breakpoint(), } return error.TestFailed; } // run -// backend=stage1 +// backend=llvm // target=native From 6aa438f0654569c1713a5c0c49b0bdda20d87c0f Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 5 Aug 2022 16:50:27 +0300 Subject: [PATCH 017/290] Sema: add null pointer slice safety check when len is comptime known --- src/Sema.zig | 5 +++++ .../pointer slice sentinel mismatch.zig | 6 +++--- .../slice sentinel mismatch - floats.zig | 6 +++--- ... sentinel mismatch - optional pointers.zig | 6 +++--- .../safety/slice slice sentinel mismatch.zig | 6 +++--- .../slicing null C pointer runtime len.zig | 20 +++++++++++++++++++ test/cases/safety/slicing null C pointer.zig | 8 +++++--- 7 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 test/cases/safety/slicing null C pointer runtime len.zig diff --git a/src/Sema.zig b/src/Sema.zig index d81d2fa726..e964c8f76b 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -25569,6 +25569,11 @@ fn analyzeSlice( const new_ptr_val = opt_new_ptr_val orelse { const result = try block.addBitCast(return_ty, new_ptr); if (block.wantSafety()) { + // requirement: slicing C ptr is non-null + if (ptr_ptr_child_ty.isCPtr()) { + const is_non_null = try sema.analyzeIsNull(block, ptr_src, ptr, true); + try sema.addSafetyCheck(block, is_non_null, .unwrap_null); + } // requirement: result[new_len] == slice_sentinel try sema.panicSentinelMismatch(block, src, slice_sentinel, elem_ty, result, new_len); } diff --git a/test/cases/safety/pointer slice sentinel mismatch.zig b/test/cases/safety/pointer slice sentinel mismatch.zig index f79e2a860c..ec25ec2969 100644 --- a/test/cases/safety/pointer slice sentinel mismatch.zig +++ b/test/cases/safety/pointer slice sentinel mismatch.zig @@ -2,14 +2,14 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { _ = stack_trace; - if (std.mem.eql(u8, message, "sentinel mismatch")) { + if (std.mem.eql(u8, message, "sentinel mismatch: expected 0, found 4")) { std.process.exit(0); } std.process.exit(1); } pub fn main() !void { - var buf: [4]u8 = undefined; + var buf: [4]u8 = .{ 1, 2, 3, 4 }; const ptr: [*]u8 = &buf; const slice = ptr[0..3 :0]; _ = slice; @@ -17,5 +17,5 @@ pub fn main() !void { } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/slice sentinel mismatch - floats.zig b/test/cases/safety/slice sentinel mismatch - floats.zig index 3295c20db3..3d08872ca7 100644 --- a/test/cases/safety/slice sentinel mismatch - floats.zig +++ b/test/cases/safety/slice sentinel mismatch - floats.zig @@ -2,19 +2,19 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { _ = stack_trace; - if (std.mem.eql(u8, message, "sentinel mismatch")) { + if (std.mem.eql(u8, message, "sentinel mismatch: expected 1.20000004e+00, found 4.0e+00")) { std.process.exit(0); } std.process.exit(1); } pub fn main() !void { - var buf: [4]f32 = undefined; + var buf: [4]f32 = .{ 1, 2, 3, 4 }; const slice = buf[0..3 :1.2]; _ = slice; return error.TestFailed; } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/slice sentinel mismatch - optional pointers.zig b/test/cases/safety/slice sentinel mismatch - optional pointers.zig index ecb82c61d4..fbc6fcf428 100644 --- a/test/cases/safety/slice sentinel mismatch - optional pointers.zig +++ b/test/cases/safety/slice sentinel mismatch - optional pointers.zig @@ -2,19 +2,19 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { _ = stack_trace; - if (std.mem.eql(u8, message, "sentinel mismatch")) { + if (std.mem.eql(u8, message, "sentinel mismatch: expected null, found i32@10")) { std.process.exit(0); } std.process.exit(1); } pub fn main() !void { - var buf: [4]?*i32 = undefined; + var buf: [4]?*i32 = .{ @intToPtr(*i32, 4), @intToPtr(*i32, 8), @intToPtr(*i32, 12), @intToPtr(*i32, 16) }; const slice = buf[0..3 :null]; _ = slice; return error.TestFailed; } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/slice slice sentinel mismatch.zig b/test/cases/safety/slice slice sentinel mismatch.zig index 13b331a0f4..b1bca1a11f 100644 --- a/test/cases/safety/slice slice sentinel mismatch.zig +++ b/test/cases/safety/slice slice sentinel mismatch.zig @@ -2,18 +2,18 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { _ = stack_trace; - if (std.mem.eql(u8, message, "sentinel mismatch")) { + if (std.mem.eql(u8, message, "sentinel mismatch: expected 0, found 4")) { std.process.exit(0); } std.process.exit(1); } pub fn main() !void { - var buf: [4]u8 = undefined; + var buf: [4]u8 = .{ 1, 2, 3, 4 }; const slice = buf[0..]; const slice2 = slice[0..3 :0]; _ = slice2; return error.TestFailed; } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/slicing null C pointer runtime len.zig b/test/cases/safety/slicing null C pointer runtime len.zig new file mode 100644 index 0000000000..e45f9855a7 --- /dev/null +++ b/test/cases/safety/slicing null C pointer runtime len.zig @@ -0,0 +1,20 @@ +const std = @import("std"); + +pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { + _ = stack_trace; + if (std.mem.eql(u8, message, "attempt to use null value")) { + std.process.exit(0); + } + std.process.exit(1); +} + +pub fn main() !void { + var ptr: [*c]const u32 = null; + var len: usize = 3; + var slice = ptr[0..len]; + _ = slice; + return error.TestFailed; +} +// run +// backend=llvm +// target=native \ No newline at end of file diff --git a/test/cases/safety/slicing null C pointer.zig b/test/cases/safety/slicing null C pointer.zig index db8d235c45..1a77d85924 100644 --- a/test/cases/safety/slicing null C pointer.zig +++ b/test/cases/safety/slicing null C pointer.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "attempt to use null value")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -13,5 +15,5 @@ pub fn main() !void { return error.TestFailed; } // run -// backend=stage1 +// backend=llvm // target=native \ No newline at end of file From 5605f6e0e302cbf345a5229ea58aef6757fe139d Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 5 Aug 2022 17:08:31 +0300 Subject: [PATCH 018/290] Sema: account for sentinel in bounds check --- src/Sema.zig | 22 ++++++++++++++++++- ...ast []u8 to bigger slice of wrong size.zig | 10 +++++---- ...mpty slice with sentinel out of bounds.zig | 4 ++-- ...h sentinel out of bounds - runtime len.zig | 22 +++++++++++++++++++ .../slice with sentinel out of bounds.zig | 4 ++-- 5 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 test/cases/safety/slice with sentinel out of bounds - runtime len.zig diff --git a/src/Sema.zig b/src/Sema.zig index e964c8f76b..4bfd6ac1c4 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -25574,6 +25574,22 @@ fn analyzeSlice( const is_non_null = try sema.analyzeIsNull(block, ptr_src, ptr, true); try sema.addSafetyCheck(block, is_non_null, .unwrap_null); } + + if (slice_ty.isSlice()) { + const slice_len_inst = try block.addTyOp(.slice_len, Type.usize, ptr_or_slice); + const actual_len = if (slice_ty.sentinel() == null) + slice_len_inst + else + try sema.analyzeArithmetic(block, .add, slice_len_inst, .one, src, end_src, end_src); + + const actual_end = if (slice_sentinel != null) + try sema.analyzeArithmetic(block, .add, end, .one, src, end_src, end_src) + else + end; + + try sema.panicIndexOutOfBounds(block, src, actual_end, actual_len, .cmp_lte); + } + // requirement: result[new_len] == slice_sentinel try sema.panicSentinelMismatch(block, src, slice_sentinel, elem_ty, result, new_len); } @@ -25635,7 +25651,11 @@ fn analyzeSlice( break :blk try sema.analyzeArithmetic(block, .add, slice_len_inst, .one, src, end_src, end_src); } else null; if (opt_len_inst) |len_inst| { - try sema.panicIndexOutOfBounds(block, src, end, len_inst, .cmp_lte); + const actual_end = if (slice_sentinel != null) + try sema.analyzeArithmetic(block, .add, end, .one, src, end_src, end_src) + else + end; + try sema.panicIndexOutOfBounds(block, src, actual_end, len_inst, .cmp_lte); } // requirement: start <= end diff --git a/test/cases/safety/cast []u8 to bigger slice of wrong size.zig b/test/cases/safety/cast []u8 to bigger slice of wrong size.zig index 588801b27e..6fddb63bee 100644 --- a/test/cases/safety/cast []u8 to bigger slice of wrong size.zig +++ b/test/cases/safety/cast []u8 to bigger slice of wrong size.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "exact division produced remainder")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -15,5 +17,5 @@ fn widenSlice(slice: []align(1) const u8) []align(1) const i32 { return std.mem.bytesAsSlice(i32, slice); } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/empty slice with sentinel out of bounds.zig b/test/cases/safety/empty slice with sentinel out of bounds.zig index ad8010868a..835b084740 100644 --- a/test/cases/safety/empty slice with sentinel out of bounds.zig +++ b/test/cases/safety/empty slice with sentinel out of bounds.zig @@ -2,7 +2,7 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { _ = stack_trace; - if (std.mem.eql(u8, message, "index out of bounds")) { + if (std.mem.eql(u8, message, "attempt to index out of bound: index 1, len 0")) { std.process.exit(0); } std.process.exit(1); @@ -17,5 +17,5 @@ pub fn main() !void { } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/slice with sentinel out of bounds - runtime len.zig b/test/cases/safety/slice with sentinel out of bounds - runtime len.zig new file mode 100644 index 0000000000..fa2e127107 --- /dev/null +++ b/test/cases/safety/slice with sentinel out of bounds - runtime len.zig @@ -0,0 +1,22 @@ +const std = @import("std"); + +pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { + _ = stack_trace; + if (std.mem.eql(u8, message, "attempt to index out of bound: index 5, len 4")) { + std.process.exit(0); + } + std.process.exit(1); +} + +pub fn main() !void { + var buf = [4]u8{ 'a', 'b', 'c', 0 }; + const input: []u8 = &buf; + var len: usize = 4; + const slice = input[0..len :0]; + _ = slice; + return error.TestFailed; +} + +// run +// backend=llvm +// target=native diff --git a/test/cases/safety/slice with sentinel out of bounds.zig b/test/cases/safety/slice with sentinel out of bounds.zig index 1ca83ea481..3cc8bfb355 100644 --- a/test/cases/safety/slice with sentinel out of bounds.zig +++ b/test/cases/safety/slice with sentinel out of bounds.zig @@ -2,7 +2,7 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { _ = stack_trace; - if (std.mem.eql(u8, message, "index out of bounds")) { + if (std.mem.eql(u8, message, "attempt to index out of bound: index 5, len 4")) { std.process.exit(0); } std.process.exit(1); @@ -17,5 +17,5 @@ pub fn main() !void { } // run -// backend=stage1 +// backend=llvm // target=native From 9116e26c1ffb49cf68bebf3af2a019af08474d12 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 5 Aug 2022 17:36:45 +0300 Subject: [PATCH 019/290] Sema: add null check for implicit casts --- src/Sema.zig | 22 +++++++++++++++---- ...r casting null to non-optional pointer.zig | 10 ++++++--- ... slicing null C pointer - runtime len.zig} | 2 +- test/cases/safety/slicing null C pointer.zig | 2 +- 4 files changed, 27 insertions(+), 9 deletions(-) rename test/cases/safety/{slicing null C pointer runtime len.zig => slicing null C pointer - runtime len.zig} (96%) diff --git a/src/Sema.zig b/src/Sema.zig index 4bfd6ac1c4..e04190973b 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1577,8 +1577,7 @@ pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize) // st.index = 0; const index_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "index", src, true); - const zero = try sema.addConstant(Type.usize, Value.zero); - try sema.storePtr2(&err_trace_block, src, index_field_ptr, src, zero, src, .store); + try sema.storePtr2(&err_trace_block, src, index_field_ptr, src, .zero_usize, src, .store); // @errorReturnTrace() = &st; _ = try err_trace_block.addUnOp(.set_err_return_trace, st_ptr); @@ -17134,7 +17133,7 @@ fn zirAlignCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A const is_aligned = try block.addBinOp(.cmp_eq, remainder, .zero_usize); const ok = if (ptr_ty.isSlice()) ok: { const len = try sema.analyzeSliceLen(block, ptr_src, ptr); - const len_zero = try block.addBinOp(.cmp_eq, len, try sema.addConstant(Type.usize, Value.zero)); + const len_zero = try block.addBinOp(.cmp_eq, len, .zero_usize); break :ok try block.addBinOp(.bit_or, len_zero, is_aligned); } else is_aligned; try sema.addSafetyCheck(block, ok, .incorrect_alignment); @@ -21957,7 +21956,6 @@ fn coerceExtra( .ok => {}, else => break :src_c_ptr, } - // TODO add safety check for null pointer return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); } @@ -24430,6 +24428,22 @@ fn coerceCompatiblePtrs( return sema.addConstant(dest_ty, val); } try sema.requireRuntimeBlock(block, inst_src, null); + const inst_ty = sema.typeOf(inst); + const inst_allows_zero = (inst_ty.zigTypeTag() == .Pointer and inst_ty.ptrAllowsZero()) or true; + if (block.wantSafety() and inst_allows_zero and !dest_ty.ptrAllowsZero()) { + const actual_ptr = if (inst_ty.isSlice()) + try sema.analyzeSlicePtr(block, inst_src, inst, inst_ty) + else + inst; + const ptr_int = try block.addUnOp(.ptrtoint, actual_ptr); + const is_non_zero = try block.addBinOp(.cmp_neq, ptr_int, .zero_usize); + const ok = if (inst_ty.isSlice()) ok: { + const len = try sema.analyzeSliceLen(block, inst_src, inst); + const len_zero = try block.addBinOp(.cmp_eq, len, .zero_usize); + break :ok try block.addBinOp(.bit_or, len_zero, is_non_zero); + } else is_non_zero; + try sema.addSafetyCheck(block, ok, .cast_to_null); + } return sema.bitCast(block, dest_ty, inst, inst_src); } diff --git a/test/cases/safety/pointer casting null to non-optional pointer.zig b/test/cases/safety/pointer casting null to non-optional pointer.zig index 0254e002ad..e46b84f783 100644 --- a/test/cases/safety/pointer casting null to non-optional pointer.zig +++ b/test/cases/safety/pointer casting null to non-optional pointer.zig @@ -1,16 +1,20 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "cast causes pointer to be null")) { + std.process.exit(0); + } + std.process.exit(1); } + pub fn main() !void { var c_ptr: [*c]u8 = 0; var zig_ptr: *u8 = c_ptr; _ = zig_ptr; return error.TestFailed; } + // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/slicing null C pointer runtime len.zig b/test/cases/safety/slicing null C pointer - runtime len.zig similarity index 96% rename from test/cases/safety/slicing null C pointer runtime len.zig rename to test/cases/safety/slicing null C pointer - runtime len.zig index e45f9855a7..2767253612 100644 --- a/test/cases/safety/slicing null C pointer runtime len.zig +++ b/test/cases/safety/slicing null C pointer - runtime len.zig @@ -17,4 +17,4 @@ pub fn main() !void { } // run // backend=llvm -// target=native \ No newline at end of file +// target=native diff --git a/test/cases/safety/slicing null C pointer.zig b/test/cases/safety/slicing null C pointer.zig index 1a77d85924..f5041adae7 100644 --- a/test/cases/safety/slicing null C pointer.zig +++ b/test/cases/safety/slicing null C pointer.zig @@ -16,4 +16,4 @@ pub fn main() !void { } // run // backend=llvm -// target=native \ No newline at end of file +// target=native From 19d5ffc710faa23cd07a6dff23d4afc43c0c7f63 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 5 Aug 2022 17:47:01 +0300 Subject: [PATCH 020/290] Sema: add safety check for non-power-of-two shift amounts --- src/Sema.zig | 128 ++++++++++++------ .../safety/shift left by huge amount.zig | 2 +- .../safety/shift right by huge amount.zig | 2 +- ...ed integer division overflow - vectors.zig | 10 +- .../signed integer division overflow.zig | 10 +- 5 files changed, 102 insertions(+), 50 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index e04190973b..3b672ecd42 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -10227,34 +10227,57 @@ fn zirShl( } else rhs; try sema.requireRuntimeBlock(block, src, runtime_src); - if (block.wantSafety() and air_tag == .shl_exact) { - const op_ov_tuple_ty = try sema.overflowArithmeticTupleType(lhs_ty); - const op_ov = try block.addInst(.{ - .tag = .shl_with_overflow, - .data = .{ .ty_pl = .{ - .ty = try sema.addType(op_ov_tuple_ty), - .payload = try sema.addExtra(Air.Bin{ - .lhs = lhs, - .rhs = rhs, - }), - } }, - }); - const ov_bit = try sema.tupleFieldValByIndex(block, src, op_ov, 1, op_ov_tuple_ty); - const any_ov_bit = if (lhs_ty.zigTypeTag() == .Vector) - try block.addInst(.{ - .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, - .data = .{ .reduce = .{ - .operand = ov_bit, - .operation = .Or, - } }, - }) - else - ov_bit; - const zero_ov = try sema.addConstant(Type.@"u1", Value.zero); - const no_ov = try block.addBinOp(.cmp_eq, any_ov_bit, zero_ov); + if (block.wantSafety()) { + const bit_count = scalar_ty.intInfo(target).bits; + if (!std.math.isPowerOfTwo(bit_count)) { + const bit_count_val = try Value.Tag.int_u64.create(sema.arena, bit_count); - try sema.addSafetyCheck(block, no_ov, .shl_overflow); - return sema.tupleFieldValByIndex(block, src, op_ov, 0, op_ov_tuple_ty); + const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: { + const bit_count_inst = try sema.addConstant(rhs_ty, try Value.Tag.repeated.create(sema.arena, bit_count_val)); + const lt = try block.addCmpVector(rhs, bit_count_inst, .lt, try sema.addType(rhs_ty)); + break :ok try block.addInst(.{ + .tag = .reduce, + .data = .{ .reduce = .{ + .operand = lt, + .operation = .And, + } }, + }); + } else ok: { + const bit_count_inst = try sema.addConstant(rhs_ty, bit_count_val); + break :ok try block.addBinOp(.cmp_lt, rhs, bit_count_inst); + }; + try sema.addSafetyCheck(block, ok, .shift_rhs_too_big); + } + + if (air_tag == .shl_exact) { + const op_ov_tuple_ty = try sema.overflowArithmeticTupleType(lhs_ty); + const op_ov = try block.addInst(.{ + .tag = .shl_with_overflow, + .data = .{ .ty_pl = .{ + .ty = try sema.addType(op_ov_tuple_ty), + .payload = try sema.addExtra(Air.Bin{ + .lhs = lhs, + .rhs = rhs, + }), + } }, + }); + const ov_bit = try sema.tupleFieldValByIndex(block, src, op_ov, 1, op_ov_tuple_ty); + const any_ov_bit = if (lhs_ty.zigTypeTag() == .Vector) + try block.addInst(.{ + .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, + .data = .{ .reduce = .{ + .operand = ov_bit, + .operation = .Or, + } }, + }) + else + ov_bit; + const zero_ov = try sema.addConstant(Type.@"u1", Value.zero); + const no_ov = try block.addBinOp(.cmp_eq, any_ov_bit, zero_ov); + + try sema.addSafetyCheck(block, no_ov, .shl_overflow); + return sema.tupleFieldValByIndex(block, src, op_ov, 0, op_ov_tuple_ty); + } } return block.addBinOp(air_tag, lhs, new_rhs); } @@ -10333,20 +10356,43 @@ fn zirShr( try sema.requireRuntimeBlock(block, src, runtime_src); const result = try block.addBinOp(air_tag, lhs, rhs); - if (block.wantSafety() and air_tag == .shr_exact) { - const back = try block.addBinOp(.shl, result, rhs); + if (block.wantSafety()) { + const bit_count = scalar_ty.intInfo(target).bits; + if (!std.math.isPowerOfTwo(bit_count)) { + const bit_count_val = try Value.Tag.int_u64.create(sema.arena, bit_count); - const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: { - const eql = try block.addCmpVector(lhs, back, .eq, try sema.addType(rhs_ty)); - break :ok try block.addInst(.{ - .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, - .data = .{ .reduce = .{ - .operand = eql, - .operation = .And, - } }, - }); - } else try block.addBinOp(.cmp_eq, lhs, back); - try sema.addSafetyCheck(block, ok, .shr_overflow); + const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: { + const bit_count_inst = try sema.addConstant(rhs_ty, try Value.Tag.repeated.create(sema.arena, bit_count_val)); + const lt = try block.addCmpVector(rhs, bit_count_inst, .lt, try sema.addType(rhs_ty)); + break :ok try block.addInst(.{ + .tag = .reduce, + .data = .{ .reduce = .{ + .operand = lt, + .operation = .And, + } }, + }); + } else ok: { + const bit_count_inst = try sema.addConstant(rhs_ty, bit_count_val); + break :ok try block.addBinOp(.cmp_lt, rhs, bit_count_inst); + }; + try sema.addSafetyCheck(block, ok, .shift_rhs_too_big); + } + + if (air_tag == .shr_exact) { + const back = try block.addBinOp(.shl, result, rhs); + + const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: { + const eql = try block.addCmpVector(lhs, back, .eq, try sema.addType(rhs_ty)); + break :ok try block.addInst(.{ + .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, + .data = .{ .reduce = .{ + .operand = eql, + .operation = .And, + } }, + }); + } else try block.addBinOp(.cmp_eq, lhs, back); + try sema.addSafetyCheck(block, ok, .shr_overflow); + } } return result; } @@ -19972,6 +20018,7 @@ pub const PanicId = enum { inactive_union_field, integer_part_out_of_bounds, corrupt_switch, + shift_rhs_too_big, }; fn addSafetyCheck( @@ -20268,6 +20315,7 @@ fn safetyPanic( .inactive_union_field => "access of inactive union field", .integer_part_out_of_bounds => "integer part of floating point value out of bounds", .corrupt_switch => "switch on corrupt value", + .shift_rhs_too_big => "shift amount is greater than the type size", }; const msg_inst = msg_inst: { diff --git a/test/cases/safety/shift left by huge amount.zig b/test/cases/safety/shift left by huge amount.zig index b1159b7d75..e786a739d6 100644 --- a/test/cases/safety/shift left by huge amount.zig +++ b/test/cases/safety/shift left by huge amount.zig @@ -17,5 +17,5 @@ pub fn main() !void { } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/shift right by huge amount.zig b/test/cases/safety/shift right by huge amount.zig index 2c39011240..a45b8c24ce 100644 --- a/test/cases/safety/shift right by huge amount.zig +++ b/test/cases/safety/shift right by huge amount.zig @@ -17,5 +17,5 @@ pub fn main() !void { } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/signed integer division overflow - vectors.zig b/test/cases/safety/signed integer division overflow - vectors.zig index d59adeb698..8bc5be0d63 100644 --- a/test/cases/safety/signed integer division overflow - vectors.zig +++ b/test/cases/safety/signed integer division overflow - vectors.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "integer overflow")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -17,5 +19,5 @@ fn div(a: @Vector(4, i16), b: @Vector(4, i16)) @Vector(4, i16) { return @divTrunc(a, b); } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native diff --git a/test/cases/safety/signed integer division overflow.zig b/test/cases/safety/signed integer division overflow.zig index a46f175487..6d17c284df 100644 --- a/test/cases/safety/signed integer division overflow.zig +++ b/test/cases/safety/signed integer division overflow.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "integer overflow")) { + std.process.exit(0); + } + std.process.exit(1); } pub fn main() !void { @@ -15,5 +17,5 @@ fn div(a: i16, b: i16) i16 { return @divTrunc(a, b); } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native From f46d7304b176cdc77053225943a7d5030dd0d4ee Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 5 Aug 2022 18:15:31 +0300 Subject: [PATCH 021/290] stage2: add runtime safety for invalid enum values --- src/Air.zig | 5 ++ src/Liveness.zig | 2 + src/Sema.zig | 15 +++- src/arch/aarch64/CodeGen.zig | 2 + src/arch/arm/CodeGen.zig | 2 + src/arch/riscv64/CodeGen.zig | 2 + src/arch/sparc64/CodeGen.zig | 2 + src/arch/wasm/CodeGen.zig | 1 + src/arch/x86_64/CodeGen.zig | 2 + src/codegen/c.zig | 2 + src/codegen/llvm.zig | 87 +++++++++++++++++++ src/print_air.zig | 1 + .../@intToEnum - no matching tag value.zig | 9 +- .../@tagName on corrupted enum value.zig | 3 +- .../@tagName on corrupted union value.zig | 3 +- 15 files changed, 131 insertions(+), 7 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index 302822fc99..c5734278d3 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -660,6 +660,10 @@ pub const Inst = struct { /// Uses the `pl_op` field with payload `AtomicRmw`. Operand is `ptr`. atomic_rmw, + /// Returns true if enum tag value has a name. + /// Uses the `un_op` field. + is_named_enum_value, + /// Given an enum tag value, returns the tag name. The enum type may be non-exhaustive. /// Result type is always `[:0]const u8`. /// Uses the `un_op` field. @@ -1057,6 +1061,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .is_non_err, .is_err_ptr, .is_non_err_ptr, + .is_named_enum_value, => return Type.bool, .const_ty => return Type.type, diff --git a/src/Liveness.zig b/src/Liveness.zig index 435075a411..748016d584 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -291,6 +291,7 @@ pub fn categorizeOperand( .is_non_err_ptr, .ptrtoint, .bool_to_int, + .is_named_enum_value, .tag_name, .error_name, .sqrt, @@ -858,6 +859,7 @@ fn analyzeInst( .bool_to_int, .ret, .ret_load, + .is_named_enum_value, .tag_name, .error_name, .sqrt, diff --git a/src/Sema.zig b/src/Sema.zig index 3b672ecd42..56e08d081b 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6933,8 +6933,12 @@ fn zirIntToEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A } try sema.requireRuntimeBlock(block, src, operand_src); - // TODO insert safety check to make sure the value matches an enum value - return block.addTyOp(.intcast, dest_ty, operand); + const result = try block.addTyOp(.intcast, dest_ty, operand); + if (block.wantSafety() and !dest_ty.isNonexhaustiveEnum() and sema.mod.comp.bin_file.options.use_llvm) { + const ok = try block.addUnOp(.is_named_enum_value, result); + try sema.addSafetyCheck(block, ok, .invalid_enum_value); + } + return result; } /// Pointer in, pointer out. @@ -15887,6 +15891,11 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const field_name = enum_ty.enumFieldName(field_index); return sema.addStrLit(block, field_name); } + try sema.requireRuntimeBlock(block, src, operand_src); + if (block.wantSafety() and sema.mod.comp.bin_file.options.use_llvm) { + const ok = try block.addUnOp(.is_named_enum_value, casted_operand); + try sema.addSafetyCheck(block, ok, .invalid_enum_value); + } // In case the value is runtime-known, we have an AIR instruction for this instead // of trying to lower it in Sema because an optimization pass may result in the operand // being comptime-known, which would let us elide the `tag_name` AIR instruction. @@ -20019,6 +20028,7 @@ pub const PanicId = enum { integer_part_out_of_bounds, corrupt_switch, shift_rhs_too_big, + invalid_enum_value, }; fn addSafetyCheck( @@ -20316,6 +20326,7 @@ fn safetyPanic( .integer_part_out_of_bounds => "integer part of floating point value out of bounds", .corrupt_switch => "switch on corrupt value", .shift_rhs_too_big => "shift amount is greater than the type size", + .invalid_enum_value => "invalid enum value", }; const msg_inst = msg_inst: { diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index a8bafee4f8..369cabecea 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -753,6 +753,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .float_to_int_optimized, => return self.fail("TODO implement optimized float mode", .{}), + .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), + .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, // zig fmt: on diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index a603f72d53..be2891ef6f 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -768,6 +768,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .float_to_int_optimized, => return self.fail("TODO implement optimized float mode", .{}), + .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), + .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, // zig fmt: on diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index d09c6278b5..a5007928b6 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -693,6 +693,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .float_to_int_optimized, => return self.fail("TODO implement optimized float mode", .{}), + .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), + .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, // zig fmt: on diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index bc9fc115b2..bf834a36d9 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -705,6 +705,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .float_to_int_optimized, => @panic("TODO implement optimized float mode"), + .is_named_enum_value => @panic("TODO implement is_named_enum_value"), + .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, // zig fmt: on diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index dd9cfba548..ef92361de2 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1621,6 +1621,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .tag_name, .err_return_trace, .set_err_return_trace, + .is_named_enum_value, => |tag| return self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}), .add_optimized, diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index d59a114e51..7f7473bc66 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -775,6 +775,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .float_to_int_optimized, => return self.fail("TODO implement optimized float mode", .{}), + .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), + .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, // zig fmt: on diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 280b7604bf..b746818142 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1952,6 +1952,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .reduce_optimized, .float_to_int_optimized, => return f.fail("TODO implement optimized float mode", .{}), + + .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}), // zig fmt: on }; switch (result_value) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 5ea7ffad67..9c3efa18cd 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -201,6 +201,8 @@ pub const Object = struct { /// * it works for functions not all globals. /// Therefore, this table keeps track of the mapping. decl_map: std.AutoHashMapUnmanaged(Module.Decl.Index, *const llvm.Value), + /// Serves the same purpose as `decl_map` but only used for the `is_named_enum_value` instruction. + named_enum_map: std.AutoHashMapUnmanaged(Module.Decl.Index, *const llvm.Value), /// Maps Zig types to LLVM types. The table memory itself is backed by the GPA of /// the compiler, but the Type/Value memory here is backed by `type_map_arena`. /// TODO we need to remove entries from this map in response to incremental compilation @@ -377,6 +379,7 @@ pub const Object = struct { .target_data = target_data, .target = options.target, .decl_map = .{}, + .named_enum_map = .{}, .type_map = .{}, .type_map_arena = std.heap.ArenaAllocator.init(gpa), .di_type_map = .{}, @@ -396,6 +399,7 @@ pub const Object = struct { self.llvm_module.dispose(); self.context.dispose(); self.decl_map.deinit(gpa); + self.named_enum_map.deinit(gpa); self.type_map.deinit(gpa); self.type_map_arena.deinit(); self.extern_collisions.deinit(gpa); @@ -4180,6 +4184,8 @@ pub const FuncGen = struct { .union_init => try self.airUnionInit(inst), .prefetch => try self.airPrefetch(inst), + .is_named_enum_value => try self.airIsNamedEnumValue(inst), + .reduce => try self.airReduce(inst, false), .reduce_optimized => try self.airReduce(inst, true), @@ -7882,6 +7888,87 @@ pub const FuncGen = struct { } } + fn airIsNamedEnumValue(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + const enum_ty = self.air.typeOf(un_op); + + const llvm_fn = try self.getIsNamedEnumValueFunction(enum_ty); + const params = [_]*const llvm.Value{operand}; + return self.builder.buildCall(llvm_fn, ¶ms, params.len, .Fast, .Auto, ""); + } + + fn getIsNamedEnumValueFunction(self: *FuncGen, enum_ty: Type) !*const llvm.Value { + const enum_decl = enum_ty.getOwnerDecl(); + + // TODO: detect when the type changes and re-emit this function. + const gop = try self.dg.object.named_enum_map.getOrPut(self.dg.gpa, enum_decl); + if (gop.found_existing) return gop.value_ptr.*; + errdefer assert(self.dg.object.named_enum_map.remove(enum_decl)); + + var arena_allocator = std.heap.ArenaAllocator.init(self.gpa); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const mod = self.dg.module; + const llvm_fn_name = try std.fmt.allocPrintZ(arena, "__zig_is_named_enum_value_{s}", .{ + try mod.declPtr(enum_decl).getFullyQualifiedName(mod), + }); + + var int_tag_type_buffer: Type.Payload.Bits = undefined; + const int_tag_ty = enum_ty.intTagType(&int_tag_type_buffer); + const param_types = [_]*const llvm.Type{try self.dg.lowerType(int_tag_ty)}; + + const llvm_ret_ty = try self.dg.lowerType(Type.bool); + const fn_type = llvm.functionType(llvm_ret_ty, ¶m_types, param_types.len, .False); + const fn_val = self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type); + fn_val.setLinkage(.Internal); + fn_val.setFunctionCallConv(.Fast); + self.dg.addCommonFnAttributes(fn_val); + gop.value_ptr.* = fn_val; + + const prev_block = self.builder.getInsertBlock(); + const prev_debug_location = self.builder.getCurrentDebugLocation2(); + defer { + self.builder.positionBuilderAtEnd(prev_block); + if (self.di_scope != null) { + self.builder.setCurrentDebugLocation2(prev_debug_location); + } + } + + const entry_block = self.dg.context.appendBasicBlock(fn_val, "Entry"); + self.builder.positionBuilderAtEnd(entry_block); + self.builder.clearCurrentDebugLocation(); + + const fields = enum_ty.enumFields(); + const named_block = self.dg.context.appendBasicBlock(fn_val, "Named"); + const unnamed_block = self.dg.context.appendBasicBlock(fn_val, "Unnamed"); + const tag_int_value = fn_val.getParam(0); + const switch_instr = self.builder.buildSwitch(tag_int_value, unnamed_block, @intCast(c_uint, fields.count())); + + for (fields.keys()) |_, field_index| { + const this_tag_int_value = int: { + var tag_val_payload: Value.Payload.U32 = .{ + .base = .{ .tag = .enum_field_index }, + .data = @intCast(u32, field_index), + }; + break :int try self.dg.lowerValue(.{ + .ty = enum_ty, + .val = Value.initPayload(&tag_val_payload.base), + }); + }; + switch_instr.addCase(this_tag_int_value, named_block); + } + self.builder.positionBuilderAtEnd(named_block); + _ = self.builder.buildRet(self.dg.context.intType(1).constInt(1, .False)); + + self.builder.positionBuilderAtEnd(unnamed_block); + _ = self.builder.buildRet(self.dg.context.intType(1).constInt(0, .False)); + return fn_val; + } + fn airTagName(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; diff --git a/src/print_air.zig b/src/print_air.zig index ec4a94b420..23107946f6 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -170,6 +170,7 @@ const Writer = struct { .bool_to_int, .ret, .ret_load, + .is_named_enum_value, .tag_name, .error_name, .sqrt, diff --git a/test/cases/safety/@intToEnum - no matching tag value.zig b/test/cases/safety/@intToEnum - no matching tag value.zig index 79fcf33bc6..0e5f401f6d 100644 --- a/test/cases/safety/@intToEnum - no matching tag value.zig +++ b/test/cases/safety/@intToEnum - no matching tag value.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "invalid enum value")) { + std.process.exit(0); + } + std.process.exit(1); } const Foo = enum { A, @@ -18,6 +20,7 @@ fn bar(a: u2) Foo { return @intToEnum(Foo, a); } fn baz(_: Foo) void {} + // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/@tagName on corrupted enum value.zig b/test/cases/safety/@tagName on corrupted enum value.zig index 507157911e..4081d171c4 100644 --- a/test/cases/safety/@tagName on corrupted enum value.zig +++ b/test/cases/safety/@tagName on corrupted enum value.zig @@ -10,6 +10,7 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noretur const E = enum(u32) { X = 1, + Y = 2, }; pub fn main() !void { @@ -21,5 +22,5 @@ pub fn main() !void { } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/@tagName on corrupted union value.zig b/test/cases/safety/@tagName on corrupted union value.zig index 0c35b5ef3d..eb36fab262 100644 --- a/test/cases/safety/@tagName on corrupted union value.zig +++ b/test/cases/safety/@tagName on corrupted union value.zig @@ -10,6 +10,7 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noretur const U = union(enum(u32)) { X: u8, + Y: i8, }; pub fn main() !void { @@ -22,5 +23,5 @@ pub fn main() !void { } // run -// backend=stage1 +// backend=llvm // target=native From 0daa77bd63a3ef9666d5ccda40929403a4bb3305 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 5 Aug 2022 20:53:34 +0300 Subject: [PATCH 022/290] stage2 cbe: correct `airIsNull` ptr operand check --- src/codegen/c.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index b746818142..74e4404bce 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -3252,7 +3252,7 @@ fn airIsNull( const ty = f.air.typeOf(un_op); var opt_buf: Type.Payload.ElemType = undefined; - const payload_ty = if (ty.zigTypeTag() == .Pointer) + const payload_ty = if (deref_suffix[0] != 0) ty.childType().optionalChild(&opt_buf) else ty.optionalChild(&opt_buf); From e02f51762fc9d09caf37c78a0383164ff8f46b81 Mon Sep 17 00:00:00 2001 From: Austin Rude Date: Fri, 5 Aug 2022 16:01:57 -0600 Subject: [PATCH 023/290] autodoc: Run through prettier formatter with default settings --- lib/docs/main.js | 6363 +++++++++++++++++++++++----------------------- 1 file changed, 3246 insertions(+), 3117 deletions(-) diff --git a/lib/docs/main.js b/lib/docs/main.js index 064099e1ad..41a03322d9 100644 --- a/lib/docs/main.js +++ b/lib/docs/main.js @@ -1,3259 +1,3388 @@ -'use strict'; +"use strict"; var zigAnalysis; -(function() { - let domStatus = (document.getElementById("status")); - let domSectNav = (document.getElementById("sectNav")); - let domListNav = (document.getElementById("listNav")); - let domSectMainPkg = (document.getElementById("sectMainPkg")); - let domSectPkgs = (document.getElementById("sectPkgs")); - let domListPkgs = (document.getElementById("listPkgs")); - let domSectTypes = (document.getElementById("sectTypes")); - let domListTypes = (document.getElementById("listTypes")); - let domSectTests = (document.getElementById("sectTests")); - let domListTests = (document.getElementById("listTests")); - let domSectNamespaces = (document.getElementById("sectNamespaces")); - let domListNamespaces = (document.getElementById("listNamespaces")); - let domSectErrSets = (document.getElementById("sectErrSets")); - let domListErrSets = (document.getElementById("listErrSets")); - let domSectFns = (document.getElementById("sectFns")); - let domListFns = (document.getElementById("listFns")); - let domSectFields = (document.getElementById("sectFields")); - let domListFields = (document.getElementById("listFields")); - let domSectGlobalVars = (document.getElementById("sectGlobalVars")); - let domListGlobalVars = (document.getElementById("listGlobalVars")); - let domSectValues = (document.getElementById("sectValues")); - let domListValues = (document.getElementById("listValues")); - let domFnProto = (document.getElementById("fnProto")); - let domFnProtoCode = (document.getElementById("fnProtoCode")); - let domSectParams = (document.getElementById("sectParams")); - let domListParams = (document.getElementById("listParams")); - let domTldDocs = (document.getElementById("tldDocs")); - let domSectFnErrors = (document.getElementById("sectFnErrors")); - let domListFnErrors = (document.getElementById("listFnErrors")); - let domTableFnErrors =(document.getElementById("tableFnErrors")); - let domFnErrorsAnyError = (document.getElementById("fnErrorsAnyError")); - let domFnExamples = (document.getElementById("fnExamples")); - // let domListFnExamples = (document.getElementById("listFnExamples")); - let domFnNoExamples = (document.getElementById("fnNoExamples")); - let domDeclNoRef = (document.getElementById("declNoRef")); - let domSearch = (document.getElementById("search")); - let domSectSearchResults = (document.getElementById("sectSearchResults")); +(function () { + let domStatus = document.getElementById("status"); + let domSectNav = document.getElementById("sectNav"); + let domListNav = document.getElementById("listNav"); + let domSectMainPkg = document.getElementById("sectMainPkg"); + let domSectPkgs = document.getElementById("sectPkgs"); + let domListPkgs = document.getElementById("listPkgs"); + let domSectTypes = document.getElementById("sectTypes"); + let domListTypes = document.getElementById("listTypes"); + let domSectTests = document.getElementById("sectTests"); + let domListTests = document.getElementById("listTests"); + let domSectNamespaces = document.getElementById("sectNamespaces"); + let domListNamespaces = document.getElementById("listNamespaces"); + let domSectErrSets = document.getElementById("sectErrSets"); + let domListErrSets = document.getElementById("listErrSets"); + let domSectFns = document.getElementById("sectFns"); + let domListFns = document.getElementById("listFns"); + let domSectFields = document.getElementById("sectFields"); + let domListFields = document.getElementById("listFields"); + let domSectGlobalVars = document.getElementById("sectGlobalVars"); + let domListGlobalVars = document.getElementById("listGlobalVars"); + let domSectValues = document.getElementById("sectValues"); + let domListValues = document.getElementById("listValues"); + let domFnProto = document.getElementById("fnProto"); + let domFnProtoCode = document.getElementById("fnProtoCode"); + let domSectParams = document.getElementById("sectParams"); + let domListParams = document.getElementById("listParams"); + let domTldDocs = document.getElementById("tldDocs"); + let domSectFnErrors = document.getElementById("sectFnErrors"); + let domListFnErrors = document.getElementById("listFnErrors"); + let domTableFnErrors = document.getElementById("tableFnErrors"); + let domFnErrorsAnyError = document.getElementById("fnErrorsAnyError"); + let domFnExamples = document.getElementById("fnExamples"); + // let domListFnExamples = (document.getElementById("listFnExamples")); + let domFnNoExamples = document.getElementById("fnNoExamples"); + let domDeclNoRef = document.getElementById("declNoRef"); + let domSearch = document.getElementById("search"); + let domSectSearchResults = document.getElementById("sectSearchResults"); - let domListSearchResults = (document.getElementById("listSearchResults")); - let domSectSearchNoResults = (document.getElementById("sectSearchNoResults")); - let domSectInfo = (document.getElementById("sectInfo")); - // let domTdTarget = (document.getElementById("tdTarget")); - let domPrivDeclsBox = (document.getElementById("privDeclsBox")); - let domTdZigVer = (document.getElementById("tdZigVer")); - let domHdrName = (document.getElementById("hdrName")); - let domHelpModal = (document.getElementById("helpDialog")); + let domListSearchResults = document.getElementById("listSearchResults"); + let domSectSearchNoResults = document.getElementById("sectSearchNoResults"); + let domSectInfo = document.getElementById("sectInfo"); + // let domTdTarget = (document.getElementById("tdTarget")); + let domPrivDeclsBox = document.getElementById("privDeclsBox"); + let domTdZigVer = document.getElementById("tdZigVer"); + let domHdrName = document.getElementById("hdrName"); + let domHelpModal = document.getElementById("helpDialog"); - - let searchTimer = null; + let searchTimer = null; - - let escapeHtmlReplacements = { "&": "&", '"': """, "<": "<", ">": ">" }; + let escapeHtmlReplacements = { + "&": "&", + '"': """, + "<": "<", + ">": ">", + }; - let typeKinds = (indexTypeKinds()); - let typeTypeId = (findTypeTypeId()); - let pointerSizeEnum = { One: 0, Many: 1, Slice: 2, C: 3 }; + let typeKinds = indexTypeKinds(); + let typeTypeId = findTypeTypeId(); + let pointerSizeEnum = { One: 0, Many: 1, Slice: 2, C: 3 }; - // for each package, is an array with packages to get to this one - let canonPkgPaths = computeCanonicalPackagePaths(); + // for each package, is an array with packages to get to this one + let canonPkgPaths = computeCanonicalPackagePaths(); - + // for each decl, is an array with {declNames, pkgNames} to get to this one - // for each decl, is an array with {declNames, pkgNames} to get to this one - - let canonDeclPaths = null; // lazy; use getCanonDeclPath + let canonDeclPaths = null; // lazy; use getCanonDeclPath - // for each type, is an array with {declNames, pkgNames} to get to this one - - let canonTypeDecls = null; // lazy; use getCanonTypeDecl + // for each type, is an array with {declNames, pkgNames} to get to this one - + let canonTypeDecls = null; // lazy; use getCanonTypeDecl - - let curNav = { - showPrivDecls: false, - // each element is a package name, e.g. @import("a") then within there @import("b") - // starting implicitly from root package - pkgNames: [], - // same as above except actual packages, not names - pkgObjs: [], - // Each element is a decl name, `a.b.c`, a is 0, b is 1, c is 2, etc. - // empty array means refers to the package itself - declNames: [], - // these will be all types, except the last one may be a type or a decl - declObjs: [], + let curNav = { + showPrivDecls: false, + // each element is a package name, e.g. @import("a") then within there @import("b") + // starting implicitly from root package + pkgNames: [], + // same as above except actual packages, not names + pkgObjs: [], + // Each element is a decl name, `a.b.c`, a is 0, b is 1, c is 2, etc. + // empty array means refers to the package itself + declNames: [], + // these will be all types, except the last one may be a type or a decl + declObjs: [], - // (a, b, c, d) comptime call; result is the value the docs refer to - callName: null, - }; + // (a, b, c, d) comptime call; result is the value the docs refer to + callName: null, + }; - let curNavSearch = ""; - let curSearchIndex = -1; - let imFeelingLucky = false; + let curNavSearch = ""; + let curSearchIndex = -1; + let imFeelingLucky = false; - let rootIsStd = detectRootIsStd(); + let rootIsStd = detectRootIsStd(); - // map of decl index to list of non-generic fn indexes - // let nodesToFnsMap = indexNodesToFns(); - // map of decl index to list of comptime fn calls - // let nodesToCallsMap = indexNodesToCalls(); + // map of decl index to list of non-generic fn indexes + // let nodesToFnsMap = indexNodesToFns(); + // map of decl index to list of comptime fn calls + // let nodesToCallsMap = indexNodesToCalls(); - domSearch.addEventListener('keydown', onSearchKeyDown, false); - domPrivDeclsBox.addEventListener('change', function() { - if (this.checked != curNav.showPrivDecls) { - if (this.checked && location.hash.length > 1 && location.hash[1] != '*'){ - location.hash = "#*" + location.hash.substring(1); - return; - } - if (!this.checked && location.hash.length > 1 && location.hash[1] == '*') { - location.hash = "#" + location.hash.substring(2); - return; - } + domSearch.addEventListener("keydown", onSearchKeyDown, false); + domPrivDeclsBox.addEventListener( + "change", + function () { + if (this.checked != curNav.showPrivDecls) { + if ( + this.checked && + location.hash.length > 1 && + location.hash[1] != "*" + ) { + location.hash = "#*" + location.hash.substring(1); + return; } - }, false); - - if (location.hash == "") { - location.hash = "#root"; - } - - window.addEventListener('hashchange', onHashChange, false); - window.addEventListener('keydown', onWindowKeyDown, false); - onHashChange(); - - function renderTitle() { - let list = curNav.pkgNames.concat(curNav.declNames); - let suffix = " - Zig"; - if (list.length === 0) { - if (rootIsStd) { - document.title = "std" + suffix; - } else { - document.title = zigAnalysis.params.rootName + suffix; - } - } else { - document.title = list.join('.') + suffix; + if ( + !this.checked && + location.hash.length > 1 && + location.hash[1] == "*" + ) { + location.hash = "#" + location.hash.substring(2); + return; } - } - - - function isDecl(x) { - return "value" in x; - } - - - function isType(x) { - return "kind" in x && !("value" in x); - } - - - function isContainerType(x) { - return isType(x) && typeKindIsContainer((x).kind) ; - } - - - function typeShorthandName(expr) { - let resolvedExpr = resolveValue({expr: expr}); - if (!("type" in resolvedExpr)) { - return null; - } - let type = (zigAnalysis.types[resolvedExpr.type]); - - outer: for (let i = 0; i < 10000; i += 1) { - switch (type.kind) { - case typeKinds.Optional: - case typeKinds.Pointer: - let child = (type).child; - let resolvedChild = resolveValue(child); - if ("type" in resolvedChild) { - type = zigAnalysis.types[resolvedChild.type]; - continue; - } else { - return null; - } - default: - break outer; - } - - if (i == 9999) throw "Exhausted typeShorthandName quota"; - } - - - - let name = undefined; - if (type.kind === typeKinds.Struct) { - name = "struct"; - } else if (type.kind === typeKinds.Enum) { - name = "enum"; - } else if (type.kind === typeKinds.Union) { - name = "union"; - } else { - console.log("TODO: unhalndled case in typeShortName"); - return null; - } - - return escapeHtml(name); - } - - - function typeKindIsContainer(typeKind) { - return typeKind === typeKinds.Struct || - typeKind === typeKinds.Union || - typeKind === typeKinds.Enum; - } - - - function declCanRepresentTypeKind(typeKind) { - return typeKind === typeKinds.ErrorSet || typeKindIsContainer(typeKind); - } - - // - // function findCteInRefPath(path) { - // for (let i = path.length - 1; i >= 0; i -= 1) { - // const ref = path[i]; - // if ("string" in ref) continue; - // if ("comptimeExpr" in ref) return ref; - // if ("refPath" in ref) return findCteInRefPath(ref.refPath); - // return null; - // } - - // return null; - // } - - - function resolveValue(value) { - let i = 0; - while(i < 1000) { - i += 1; - - if ("refPath" in value.expr) { - value = {expr: value.expr.refPath[value.expr.refPath.length -1]}; - continue; - } - - if ("declRef" in value.expr) { - value = zigAnalysis.decls[value.expr.declRef].value; - continue; - } - - if ("as" in value.expr) { - value = { - typeRef: zigAnalysis.exprs[value.expr.as.typeRefArg], - expr: zigAnalysis.exprs[value.expr.as.exprArg], - }; - continue; - } - - return value; - - } - console.assert(false); - return ({}); - } - - -// function typeOfDecl(decl){ -// return decl.value.typeRef; -// -// let i = 0; -// while(i < 1000) { -// i += 1; -// console.assert(isDecl(decl)); -// if ("type" in decl.value) { -// return ({ type: typeTypeId }); -// } -// -//// if ("string" in decl.value) { -//// return ({ type: { -//// kind: typeKinds.Pointer, -//// size: pointerSizeEnum.One, -//// child: }); -//// } -// -// if ("refPath" in decl.value) { -// decl = ({ -// value: decl.value.refPath[decl.value.refPath.length -1] -// }); -// continue; -// } -// -// if ("declRef" in decl.value) { -// decl = zigAnalysis.decls[decl.value.declRef]; -// continue; -// } -// -// if ("int" in decl.value) { -// return decl.value.int.typeRef; -// } -// -// if ("float" in decl.value) { -// return decl.value.float.typeRef; -// } -// -// if ("array" in decl.value) { -// return decl.value.array.typeRef; -// } -// -// if ("struct" in decl.value) { -// return decl.value.struct.typeRef; -// } -// -// if ("comptimeExpr" in decl.value) { -// const cte = zigAnalysis.comptimeExprs[decl.value.comptimeExpr]; -// return cte.typeRef; -// } -// -// if ("call" in decl.value) { -// const fn_call = zigAnalysis.calls[decl.value.call]; -// let fn_decl = undefined; -// if ("declRef" in fn_call.func) { -// fn_decl = zigAnalysis.decls[fn_call.func.declRef]; -// } else if ("refPath" in fn_call.func) { -// console.assert("declRef" in fn_call.func.refPath[fn_call.func.refPath.length -1]); -// fn_decl = zigAnalysis.decls[fn_call.func.refPath[fn_call.func.refPath.length -1].declRef]; -// } else throw {}; -// -// const fn_decl_value = resolveValue(fn_decl.value); -// console.assert("type" in fn_decl_value); //TODO handle comptimeExpr -// const fn_type = (zigAnalysis.types[fn_decl_value.type]); -// console.assert(fn_type.kind === typeKinds.Fn); -// return fn_type.ret; -// } -// -// if ("void" in decl.value) { -// return ({ type: typeTypeId }); -// } -// -// if ("bool" in decl.value) { -// return ({ type: typeKinds.Bool }); -// } -// -// console.log("TODO: handle in `typeOfDecl` more cases: ", decl); -// console.assert(false); -// throw {}; -// } -// console.assert(false); -// return ({}); -// } - - function render() { - domStatus.classList.add("hidden"); - domFnProto.classList.add("hidden"); - domSectParams.classList.add("hidden"); - domTldDocs.classList.add("hidden"); - domSectMainPkg.classList.add("hidden"); - domSectPkgs.classList.add("hidden"); - domSectTypes.classList.add("hidden"); - domSectTests.classList.add("hidden"); - domSectNamespaces.classList.add("hidden"); - domSectErrSets.classList.add("hidden"); - domSectFns.classList.add("hidden"); - domSectFields.classList.add("hidden"); - domSectSearchResults.classList.add("hidden"); - domSectSearchNoResults.classList.add("hidden"); - domSectInfo.classList.add("hidden"); - domHdrName.classList.add("hidden"); - domSectNav.classList.add("hidden"); - domSectFnErrors.classList.add("hidden"); - domFnExamples.classList.add("hidden"); - domFnNoExamples.classList.add("hidden"); - domDeclNoRef.classList.add("hidden"); - domFnErrorsAnyError.classList.add("hidden"); - domTableFnErrors.classList.add("hidden"); - domSectGlobalVars.classList.add("hidden"); - domSectValues.classList.add("hidden"); - - renderTitle(); - renderInfo(); - renderPkgList(); - - domPrivDeclsBox.checked = curNav.showPrivDecls; - - if (curNavSearch !== "") { - return renderSearch(); - } - - let rootPkg = zigAnalysis.packages[zigAnalysis.rootPkg]; - let pkg = rootPkg; - curNav.pkgObjs = [pkg]; - for (let i = 0; i < curNav.pkgNames.length; i += 1) { - let childPkg = zigAnalysis.packages[pkg.table[curNav.pkgNames[i]]]; - if (childPkg == null) { - return render404(); - } - pkg = childPkg; - curNav.pkgObjs.push(pkg); - } - - - let currentType = zigAnalysis.types[pkg.main]; - curNav.declObjs = [currentType]; - for (let i = 0; i < curNav.declNames.length; i += 1) { - - - let childDecl = findSubDecl((currentType), curNav.declNames[i]); - if (childDecl == null) { - return render404(); - } - - let childDeclValue = resolveValue((childDecl).value).expr; - if ("type" in childDeclValue) { - - const t = zigAnalysis.types[childDeclValue.type]; - if (t.kind != typeKinds.Fn) { - childDecl = t; - } - } - - currentType = (childDecl); - curNav.declObjs.push(currentType); - } - - renderNav(); - - let last = curNav.declObjs[curNav.declObjs.length - 1]; - let lastIsDecl = isDecl(last); - let lastIsType = isType(last); - let lastIsContainerType = isContainerType(last); - - if (lastIsContainerType) { - return renderContainer((last)); - } - - if (!lastIsDecl && !lastIsType) { - return renderUnknownDecl((last)); - } - - if (lastIsType) { - return renderType((last)); - } - - if (lastIsDecl && last.kind === 'var') { - return renderVar((last)); - } - - if (lastIsDecl && last.kind === 'const') { - let typeObj = zigAnalysis.types[resolveValue((last).value).expr.type]; - if (typeObj && typeObj.kind === typeKinds.Fn) { - return renderFn((last)); - } - - return renderValue((last)); - } - } - - - function renderUnknownDecl(decl) { - domDeclNoRef.classList.remove("hidden"); - - let docs = zigAnalysis.astNodes[decl.src].docs; - if (docs != null) { - domTldDocs.innerHTML = markdown(docs); - } else { - domTldDocs.innerHTML = '

There are no doc comments for this declaration.

'; - } - domTldDocs.classList.remove("hidden"); - } - - - function typeIsErrSet(typeIndex) { - let typeObj = zigAnalysis.types[typeIndex]; - return typeObj.kind === typeKinds.ErrorSet; - } - - - function typeIsStructWithNoFields(typeIndex) { - let typeObj = zigAnalysis.types[typeIndex]; - if (typeObj.kind !== typeKinds.Struct) - return false; - return (typeObj).fields.length == 0; - } - - - function typeIsGenericFn(typeIndex) { - let typeObj = zigAnalysis.types[typeIndex]; - if (typeObj.kind !== typeKinds.Fn) { - return false; - } - return (typeObj).generic_ret != null; - } - - - function renderFn(fnDecl) { - if ("refPath" in fnDecl.value.expr) { - let last = fnDecl.value.expr.refPath.length - 1; - let lastExpr = fnDecl.value.expr.refPath[last]; - console.assert("declRef" in lastExpr); - fnDecl = zigAnalysis.decls[lastExpr.declRef]; - } - - let value = resolveValue(fnDecl.value); - console.assert("type" in value.expr); - let typeObj = (zigAnalysis.types[value.expr.type]); - - domFnProtoCode.innerHTML = exprName(value.expr, { - wantHtml: true, - wantLink: true, - fnDecl, - }); - - let docsSource = null; - let srcNode = zigAnalysis.astNodes[fnDecl.src]; - if (srcNode.docs != null) { - docsSource = srcNode.docs; - } - - renderFnParamDocs(fnDecl, typeObj); - - let retExpr = resolveValue({expr:typeObj.ret}).expr; - if ("type" in retExpr) { - let retIndex = retExpr.type; - let errSetTypeIndex = (null); - let retType = zigAnalysis.types[retIndex]; - if (retType.kind === typeKinds.ErrorSet) { - errSetTypeIndex = retIndex; - } else if (retType.kind === typeKinds.ErrorUnion) { - errSetTypeIndex = (retType).err.type; - } - if (errSetTypeIndex != null) { - let errSetType = (zigAnalysis.types[errSetTypeIndex]); - renderErrorSet(errSetType); - } - } - - let protoSrcIndex = fnDecl.src; - if (typeIsGenericFn(value.expr.type)) { - // does the generic_ret contain a container? - var resolvedGenericRet = resolveValue({expr: typeObj.generic_ret}); - - if ("call" in resolvedGenericRet.expr){ - let call = zigAnalysis.calls[resolvedGenericRet.expr.call]; - let resolvedFunc = resolveValue({expr: call.func}); - if (!("type" in resolvedFunc.expr)) return; - let callee = zigAnalysis.types[resolvedFunc.expr.type]; - if (!callee.generic_ret) return; - resolvedGenericRet = resolveValue({expr: callee.generic_ret}); - } - - // TODO: see if unwrapping the `as` here is a good idea or not. - if ("as" in resolvedGenericRet.expr) { - resolvedGenericRet = { - expr: zigAnalysis.exprs[resolvedGenericRet.expr.as.exprArg] - }; - } - - if (!("type" in resolvedGenericRet.expr)) return; - const genericType = zigAnalysis.types[resolvedGenericRet.expr.type]; - if (isContainerType(genericType)) { - renderContainer(genericType) - } - - - - - - // old code - // let instantiations = nodesToFnsMap[protoSrcIndex]; - // let calls = nodesToCallsMap[protoSrcIndex]; - // if (instantiations == null && calls == null) { - // domFnNoExamples.classList.remove("hidden"); - // } else if (calls != null) { - // // if (fnObj.combined === undefined) fnObj.combined = allCompTimeFnCallsResult(calls); - // if (fnObj.combined != null) renderContainer(fnObj.combined); - - // resizeDomList(domListFnExamples, calls.length, '
  • '); - - // for (let callI = 0; callI < calls.length; callI += 1) { - // let liDom = domListFnExamples.children[callI]; - // liDom.innerHTML = getCallHtml(fnDecl, calls[callI]); - // } - - // domFnExamples.classList.remove("hidden"); - // } else if (instantiations != null) { - // // TODO - // } - } else { - - domFnExamples.classList.add("hidden"); - domFnNoExamples.classList.add("hidden"); - } - - let protoSrcNode = zigAnalysis.astNodes[protoSrcIndex]; - if (docsSource == null && protoSrcNode != null && protoSrcNode.docs != null) { - docsSource = protoSrcNode.docs; - } - if (docsSource != null) { - domTldDocs.innerHTML = markdown(docsSource); - domTldDocs.classList.remove("hidden"); - } - domFnProto.classList.remove("hidden"); - } - - - function renderFnParamDocs(fnDecl, typeObj) { - let docCount = 0; - - let fnNode = zigAnalysis.astNodes[fnDecl.src]; - let fields = (fnNode.fields); - let isVarArgs = fnNode.varArgs; - - for (let i = 0; i < fields.length; i += 1) { - let field = fields[i]; - let fieldNode = zigAnalysis.astNodes[field]; - if (fieldNode.docs != null) { - docCount += 1; - } - } - if (docCount == 0) { - return; - } - - resizeDomList(domListParams, docCount, '
    '); - let domIndex = 0; - - for (let i = 0; i < fields.length; i += 1) { - let field = fields[i]; - let fieldNode = zigAnalysis.astNodes[field]; - let docs = fieldNode.docs; - if (fieldNode.docs == null) { - continue; - } - let docsNonEmpty = docs !== ""; - let divDom = domListParams.children[domIndex]; - domIndex += 1; - - - let value = typeObj.params[i]; - let preClass = docsNonEmpty ? ' class="fieldHasDocs"' : ""; - let html = '' + escapeHtml((fieldNode.name)) + ": "; - if (isVarArgs && i === typeObj.params.length - 1) { - html += '...'; - } else { - let name = exprName(value, {wantHtml: false, wantLink: false}); - html += '' + name + ''; - } - - html += ','; - - if (docsNonEmpty) { - html += '
    ' + markdown(docs) + '
    '; - } - divDom.innerHTML = html; - } - domSectParams.classList.remove("hidden"); - } - - function renderNav() { - let len = curNav.pkgNames.length + curNav.declNames.length; - resizeDomList(domListNav, len, '
  • '); - let list = []; - let hrefPkgNames = []; - let hrefDeclNames = ([]); - for (let i = 0; i < curNav.pkgNames.length; i += 1) { - hrefPkgNames.push(curNav.pkgNames[i]); - let name = curNav.pkgNames[i]; - if (name == "root") name = zigAnalysis.rootPkgName; - list.push({ - name: name, - link: navLink(hrefPkgNames, hrefDeclNames), - }); - } - for (let i = 0; i < curNav.declNames.length; i += 1) { - hrefDeclNames.push(curNav.declNames[i]); - list.push({ - name: curNav.declNames[i], - link: navLink(hrefPkgNames, hrefDeclNames), - }); - } - - for (let i = 0; i < list.length; i += 1) { - let liDom = domListNav.children[i]; - let aDom = liDom.children[0]; - aDom.textContent = list[i].name; - aDom.setAttribute('href', list[i].link); - if (i + 1 == list.length) { - aDom.classList.add("active"); - } else { - aDom.classList.remove("active"); - } - } - - domSectNav.classList.remove("hidden"); - } - - function renderInfo() { - domTdZigVer.textContent = zigAnalysis.params.zigVersion; - //domTdTarget.textContent = zigAnalysis.params.builds[0].target; - - domSectInfo.classList.remove("hidden"); - } - - function render404() { - domStatus.textContent = "404 Not Found"; - domStatus.classList.remove("hidden"); - } - - function renderPkgList() { - let rootPkg = zigAnalysis.packages[zigAnalysis.rootPkg]; - let list = []; - for (let key in rootPkg.table) { - let pkgIndex = rootPkg.table[key]; - if (zigAnalysis.packages[pkgIndex] == null) continue; - if (key == zigAnalysis.params.rootName) continue; - list.push({ - name: key, - pkg: pkgIndex, - }); - } - - { - let aDom = domSectMainPkg.children[1].children[0].children[0]; - aDom.textContent = zigAnalysis.rootPkgName; - aDom.setAttribute('href', navLinkPkg(zigAnalysis.rootPkg)); - if (zigAnalysis.params.rootName === curNav.pkgNames[0]) { - aDom.classList.add("active"); - } else { - aDom.classList.remove("active"); - } - domSectMainPkg.classList.remove("hidden"); - } - - list.sort(function(a, b) { - return operatorCompare(a.name.toLowerCase(), b.name.toLowerCase()); - }); - - if (list.length !== 0) { - resizeDomList(domListPkgs, list.length, '
  • '); - for (let i = 0; i < list.length; i += 1) { - let liDom = domListPkgs.children[i]; - let aDom = liDom.children[0]; - aDom.textContent = list[i].name; - aDom.setAttribute('href', navLinkPkg(list[i].pkg)); - if (list[i].name === curNav.pkgNames[0]) { - aDom.classList.add("active"); - } else { - aDom.classList.remove("active"); - } - } - - domSectPkgs.classList.remove("hidden"); - } - } - - - - function navLink(pkgNames, declNames, callName) { - let base = '#'; - if (curNav.showPrivDecls) { - base += "*"; - } - - if (pkgNames.length === 0 && declNames.length === 0) { - return base; - } else if (declNames.length === 0 && callName == null) { - return base + pkgNames.join('.'); - } else if (callName == null) { - return base + pkgNames.join('.') + ';' + declNames.join('.'); - } else { - return base + pkgNames.join('.') + ';' + declNames.join('.') + ';' + callName; - } - } - - - function navLinkPkg(pkgIndex) { - return navLink(canonPkgPaths[pkgIndex], []); - } - - - function navLinkDecl(childName) { - return navLink(curNav.pkgNames, curNav.declNames.concat([childName])); - } - - // - // function navLinkCall(callObj) { - // let declNamesCopy = curNav.declNames.concat([]); - // let callName = (declNamesCopy.pop()); - - // callName += '('; - // for (let arg_i = 0; arg_i < callObj.args.length; arg_i += 1) { - // if (arg_i !== 0) callName += ','; - // let argObj = callObj.args[arg_i]; - // callName += getValueText(argObj, argObj, false, false); - // } - // callName += ')'; - - // declNamesCopy.push(callName); - // return navLink(curNav.pkgNames, declNamesCopy); - // } - - - function resizeDomListDl(dlDom, desiredLen) { - // add the missing dom entries - for (let i = dlDom.childElementCount / 2; i < desiredLen; i += 1) { - dlDom.insertAdjacentHTML('beforeend', '
    '); - } - // remove extra dom entries - while (desiredLen < dlDom.childElementCount / 2) { - dlDom.removeChild(dlDom.lastChild); - dlDom.removeChild(dlDom.lastChild); - } - } - - - function resizeDomList(listDom, desiredLen, templateHtml) { - // add the missing dom entries - for (let i = listDom.childElementCount; i < desiredLen; i += 1) { - listDom.insertAdjacentHTML('beforeend', templateHtml); - } - // remove extra dom entries - while (desiredLen < listDom.childElementCount) { - listDom.removeChild(listDom.lastChild); - } - } - - function walkResultTypeRef(wr) { - if (wr.typeRef) return wr.typeRef; - let resolved = resolveValue(wr); - if (wr === resolved) { - return {type: 0}; } - return walkResultTypeRef(resolved); + }, + false + ); + + if (location.hash == "") { + location.hash = "#root"; + } + + window.addEventListener("hashchange", onHashChange, false); + window.addEventListener("keydown", onWindowKeyDown, false); + onHashChange(); + + function renderTitle() { + let list = curNav.pkgNames.concat(curNav.declNames); + let suffix = " - Zig"; + if (list.length === 0) { + if (rootIsStd) { + document.title = "std" + suffix; + } else { + document.title = zigAnalysis.params.rootName + suffix; + } + } else { + document.title = list.join(".") + suffix; } - - function exprName(expr, opts) { - switch (Object.keys(expr)[0]) { - default: throw "this expression is not implemented yet"; - case "bool": { - if (expr.bool) { - return "true"; - } - return "false"; - } - case "&": { - return "&" + exprName(zigAnalysis.exprs[expr["&"]]); - } - case "compileError": { - let compileError = expr.compileError; - return compileError; - } - case "enumLiteral": { - let literal = expr.enumLiteral; - return "." + literal; - } - case "void": { - return "void"; - } - case "slice":{ - let payloadHtml = ""; - const lhsExpr = zigAnalysis.exprs[expr.slice.lhs]; - const startExpr = zigAnalysis.exprs[expr.slice.start]; - let decl = exprName(lhsExpr); - let start = exprName(startExpr); - let end = ""; - let sentinel = ""; - if (expr.slice['end']) { - const endExpr = zigAnalysis.exprs[expr.slice.end]; - let end_ = exprName(endExpr); - end += end_; - } - if (expr.slice['sentinel']) { - const sentinelExpr = zigAnalysis.exprs[expr.slice.sentinel]; - let sentinel_ = exprName(sentinelExpr); - sentinel += " :" + sentinel_; - } - payloadHtml += decl + "["+ start + ".." + end + sentinel + "]"; - return payloadHtml; - } - case "sliceIndex": { - const sliceIndex = zigAnalysis.exprs[expr.sliceIndex]; - return exprName(sliceIndex, opts); - } - case "cmpxchg":{ - const typeIndex = zigAnalysis.exprs[expr.cmpxchg.type]; - const ptrIndex = zigAnalysis.exprs[expr.cmpxchg.ptr]; - const expectedValueIndex = zigAnalysis.exprs[expr.cmpxchg.expected_value]; - const newValueIndex = zigAnalysis.exprs[expr.cmpxchg.new_value]; - const successOrderIndex = zigAnalysis.exprs[expr.cmpxchg.success_order]; - const failureOrderIndex = zigAnalysis.exprs[expr.cmpxchg.failure_order]; + } - const type = exprName(typeIndex, opts); - const ptr = exprName(ptrIndex, opts); - const expectedValue = exprName(expectedValueIndex, opts); - const newValue = exprName(newValueIndex, opts); - const successOrder = exprName(successOrderIndex, opts); - const failureOrder = exprName(failureOrderIndex, opts); + function isDecl(x) { + return "value" in x; + } - let fnName = "@"; + function isType(x) { + return "kind" in x && !("value" in x); + } - switch (expr.cmpxchg.name) { - case "cmpxchg_strong": { - fnName += "cmpxchgStrong" - break; - } - case "cmpxchg_weak": { - fnName += "cmpxchgWeak" - break; - } - default: { - console.log("There's only cmpxchg_strong and cmpxchg_weak"); - } - } - - return fnName + "(" + type + ", " + ptr + ", " + expectedValue + ", "+ newValue + ", "+"." +successOrder + ", "+ "." +failureOrder + ")"; - } - case "cmpxchgIndex": { - const cmpxchgIndex = zigAnalysis.exprs[expr.cmpxchgIndex]; - return exprName(cmpxchgIndex, opts); - } - case "switchOp":{ - let condExpr = zigAnalysis.exprs[expr.switchOp.cond_index]; - let ast = zigAnalysis.astNodes[expr.switchOp.ast]; - let file_name = expr.switchOp.file_name; - let outer_decl_index = expr.switchOp.outer_decl; - let outer_decl = zigAnalysis.types[outer_decl_index]; - let line = 0; - // console.log(expr.switchOp) - // console.log(outer_decl) - while (outer_decl_index !== 0 && outer_decl.line_number > 0) { - line += outer_decl.line_number; - outer_decl_index = outer_decl.outer_decl; - outer_decl = zigAnalysis.types[outer_decl_index]; - // console.log(outer_decl) - } - line += ast.line + 1; - let payloadHtml = ""; - let cond = exprName(condExpr, opts); + function isContainerType(x) { + return isType(x) && typeKindIsContainer(x.kind); + } - payloadHtml += "
    " + "node_name: " + ast.name + "
    " + "file: " + file_name + "
    " + "line: " + line + "
    "; - payloadHtml += "switch(" + cond + ") {" + "" +"..." + "}"; - return payloadHtml; + function typeShorthandName(expr) { + let resolvedExpr = resolveValue({ expr: expr }); + if (!("type" in resolvedExpr)) { + return null; + } + let type = zigAnalysis.types[resolvedExpr.type]; + + outer: for (let i = 0; i < 10000; i += 1) { + switch (type.kind) { + case typeKinds.Optional: + case typeKinds.Pointer: + let child = type.child; + let resolvedChild = resolveValue(child); + if ("type" in resolvedChild) { + type = zigAnalysis.types[resolvedChild.type]; + continue; + } else { + return null; } - case "switchIndex": { - const switchIndex = zigAnalysis.exprs[expr.switchIndex]; - return exprName(switchIndex, opts); + default: + break outer; + } + + if (i == 9999) throw "Exhausted typeShorthandName quota"; + } + + let name = undefined; + if (type.kind === typeKinds.Struct) { + name = "struct"; + } else if (type.kind === typeKinds.Enum) { + name = "enum"; + } else if (type.kind === typeKinds.Union) { + name = "union"; + } else { + console.log("TODO: unhalndled case in typeShortName"); + return null; + } + + return escapeHtml(name); + } + + function typeKindIsContainer(typeKind) { + return ( + typeKind === typeKinds.Struct || + typeKind === typeKinds.Union || + typeKind === typeKinds.Enum + ); + } + + function declCanRepresentTypeKind(typeKind) { + return typeKind === typeKinds.ErrorSet || typeKindIsContainer(typeKind); + } + + // + // function findCteInRefPath(path) { + // for (let i = path.length - 1; i >= 0; i -= 1) { + // const ref = path[i]; + // if ("string" in ref) continue; + // if ("comptimeExpr" in ref) return ref; + // if ("refPath" in ref) return findCteInRefPath(ref.refPath); + // return null; + // } + + // return null; + // } + + function resolveValue(value) { + let i = 0; + while (i < 1000) { + i += 1; + + if ("refPath" in value.expr) { + value = { expr: value.expr.refPath[value.expr.refPath.length - 1] }; + continue; + } + + if ("declRef" in value.expr) { + value = zigAnalysis.decls[value.expr.declRef].value; + continue; + } + + if ("as" in value.expr) { + value = { + typeRef: zigAnalysis.exprs[value.expr.as.typeRefArg], + expr: zigAnalysis.exprs[value.expr.as.exprArg], + }; + continue; + } + + return value; + } + console.assert(false); + return {}; + } + + // function typeOfDecl(decl){ + // return decl.value.typeRef; + // + // let i = 0; + // while(i < 1000) { + // i += 1; + // console.assert(isDecl(decl)); + // if ("type" in decl.value) { + // return ({ type: typeTypeId }); + // } + // + //// if ("string" in decl.value) { + //// return ({ type: { + //// kind: typeKinds.Pointer, + //// size: pointerSizeEnum.One, + //// child: }); + //// } + // + // if ("refPath" in decl.value) { + // decl = ({ + // value: decl.value.refPath[decl.value.refPath.length -1] + // }); + // continue; + // } + // + // if ("declRef" in decl.value) { + // decl = zigAnalysis.decls[decl.value.declRef]; + // continue; + // } + // + // if ("int" in decl.value) { + // return decl.value.int.typeRef; + // } + // + // if ("float" in decl.value) { + // return decl.value.float.typeRef; + // } + // + // if ("array" in decl.value) { + // return decl.value.array.typeRef; + // } + // + // if ("struct" in decl.value) { + // return decl.value.struct.typeRef; + // } + // + // if ("comptimeExpr" in decl.value) { + // const cte = zigAnalysis.comptimeExprs[decl.value.comptimeExpr]; + // return cte.typeRef; + // } + // + // if ("call" in decl.value) { + // const fn_call = zigAnalysis.calls[decl.value.call]; + // let fn_decl = undefined; + // if ("declRef" in fn_call.func) { + // fn_decl = zigAnalysis.decls[fn_call.func.declRef]; + // } else if ("refPath" in fn_call.func) { + // console.assert("declRef" in fn_call.func.refPath[fn_call.func.refPath.length -1]); + // fn_decl = zigAnalysis.decls[fn_call.func.refPath[fn_call.func.refPath.length -1].declRef]; + // } else throw {}; + // + // const fn_decl_value = resolveValue(fn_decl.value); + // console.assert("type" in fn_decl_value); //TODO handle comptimeExpr + // const fn_type = (zigAnalysis.types[fn_decl_value.type]); + // console.assert(fn_type.kind === typeKinds.Fn); + // return fn_type.ret; + // } + // + // if ("void" in decl.value) { + // return ({ type: typeTypeId }); + // } + // + // if ("bool" in decl.value) { + // return ({ type: typeKinds.Bool }); + // } + // + // console.log("TODO: handle in `typeOfDecl` more cases: ", decl); + // console.assert(false); + // throw {}; + // } + // console.assert(false); + // return ({}); + // } + + function render() { + domStatus.classList.add("hidden"); + domFnProto.classList.add("hidden"); + domSectParams.classList.add("hidden"); + domTldDocs.classList.add("hidden"); + domSectMainPkg.classList.add("hidden"); + domSectPkgs.classList.add("hidden"); + domSectTypes.classList.add("hidden"); + domSectTests.classList.add("hidden"); + domSectNamespaces.classList.add("hidden"); + domSectErrSets.classList.add("hidden"); + domSectFns.classList.add("hidden"); + domSectFields.classList.add("hidden"); + domSectSearchResults.classList.add("hidden"); + domSectSearchNoResults.classList.add("hidden"); + domSectInfo.classList.add("hidden"); + domHdrName.classList.add("hidden"); + domSectNav.classList.add("hidden"); + domSectFnErrors.classList.add("hidden"); + domFnExamples.classList.add("hidden"); + domFnNoExamples.classList.add("hidden"); + domDeclNoRef.classList.add("hidden"); + domFnErrorsAnyError.classList.add("hidden"); + domTableFnErrors.classList.add("hidden"); + domSectGlobalVars.classList.add("hidden"); + domSectValues.classList.add("hidden"); + + renderTitle(); + renderInfo(); + renderPkgList(); + + domPrivDeclsBox.checked = curNav.showPrivDecls; + + if (curNavSearch !== "") { + return renderSearch(); + } + + let rootPkg = zigAnalysis.packages[zigAnalysis.rootPkg]; + let pkg = rootPkg; + curNav.pkgObjs = [pkg]; + for (let i = 0; i < curNav.pkgNames.length; i += 1) { + let childPkg = zigAnalysis.packages[pkg.table[curNav.pkgNames[i]]]; + if (childPkg == null) { + return render404(); + } + pkg = childPkg; + curNav.pkgObjs.push(pkg); + } + + let currentType = zigAnalysis.types[pkg.main]; + curNav.declObjs = [currentType]; + for (let i = 0; i < curNav.declNames.length; i += 1) { + let childDecl = findSubDecl(currentType, curNav.declNames[i]); + if (childDecl == null) { + return render404(); + } + + let childDeclValue = resolveValue(childDecl.value).expr; + if ("type" in childDeclValue) { + const t = zigAnalysis.types[childDeclValue.type]; + if (t.kind != typeKinds.Fn) { + childDecl = t; + } + } + + currentType = childDecl; + curNav.declObjs.push(currentType); + } + + renderNav(); + + let last = curNav.declObjs[curNav.declObjs.length - 1]; + let lastIsDecl = isDecl(last); + let lastIsType = isType(last); + let lastIsContainerType = isContainerType(last); + + if (lastIsContainerType) { + return renderContainer(last); + } + + if (!lastIsDecl && !lastIsType) { + return renderUnknownDecl(last); + } + + if (lastIsType) { + return renderType(last); + } + + if (lastIsDecl && last.kind === "var") { + return renderVar(last); + } + + if (lastIsDecl && last.kind === "const") { + let typeObj = zigAnalysis.types[resolveValue(last.value).expr.type]; + if (typeObj && typeObj.kind === typeKinds.Fn) { + return renderFn(last); + } + + return renderValue(last); + } + } + + function renderUnknownDecl(decl) { + domDeclNoRef.classList.remove("hidden"); + + let docs = zigAnalysis.astNodes[decl.src].docs; + if (docs != null) { + domTldDocs.innerHTML = markdown(docs); + } else { + domTldDocs.innerHTML = + "

    There are no doc comments for this declaration.

    "; + } + domTldDocs.classList.remove("hidden"); + } + + function typeIsErrSet(typeIndex) { + let typeObj = zigAnalysis.types[typeIndex]; + return typeObj.kind === typeKinds.ErrorSet; + } + + function typeIsStructWithNoFields(typeIndex) { + let typeObj = zigAnalysis.types[typeIndex]; + if (typeObj.kind !== typeKinds.Struct) return false; + return typeObj.fields.length == 0; + } + + function typeIsGenericFn(typeIndex) { + let typeObj = zigAnalysis.types[typeIndex]; + if (typeObj.kind !== typeKinds.Fn) { + return false; + } + return typeObj.generic_ret != null; + } + + function renderFn(fnDecl) { + if ("refPath" in fnDecl.value.expr) { + let last = fnDecl.value.expr.refPath.length - 1; + let lastExpr = fnDecl.value.expr.refPath[last]; + console.assert("declRef" in lastExpr); + fnDecl = zigAnalysis.decls[lastExpr.declRef]; + } + + let value = resolveValue(fnDecl.value); + console.assert("type" in value.expr); + let typeObj = zigAnalysis.types[value.expr.type]; + + domFnProtoCode.innerHTML = exprName(value.expr, { + wantHtml: true, + wantLink: true, + fnDecl, + }); + + let docsSource = null; + let srcNode = zigAnalysis.astNodes[fnDecl.src]; + if (srcNode.docs != null) { + docsSource = srcNode.docs; + } + + renderFnParamDocs(fnDecl, typeObj); + + let retExpr = resolveValue({ expr: typeObj.ret }).expr; + if ("type" in retExpr) { + let retIndex = retExpr.type; + let errSetTypeIndex = null; + let retType = zigAnalysis.types[retIndex]; + if (retType.kind === typeKinds.ErrorSet) { + errSetTypeIndex = retIndex; + } else if (retType.kind === typeKinds.ErrorUnion) { + errSetTypeIndex = retType.err.type; + } + if (errSetTypeIndex != null) { + let errSetType = zigAnalysis.types[errSetTypeIndex]; + renderErrorSet(errSetType); + } + } + + let protoSrcIndex = fnDecl.src; + if (typeIsGenericFn(value.expr.type)) { + // does the generic_ret contain a container? + var resolvedGenericRet = resolveValue({ expr: typeObj.generic_ret }); + + if ("call" in resolvedGenericRet.expr) { + let call = zigAnalysis.calls[resolvedGenericRet.expr.call]; + let resolvedFunc = resolveValue({ expr: call.func }); + if (!("type" in resolvedFunc.expr)) return; + let callee = zigAnalysis.types[resolvedFunc.expr.type]; + if (!callee.generic_ret) return; + resolvedGenericRet = resolveValue({ expr: callee.generic_ret }); + } + + // TODO: see if unwrapping the `as` here is a good idea or not. + if ("as" in resolvedGenericRet.expr) { + resolvedGenericRet = { + expr: zigAnalysis.exprs[resolvedGenericRet.expr.as.exprArg], + }; + } + + if (!("type" in resolvedGenericRet.expr)) return; + const genericType = zigAnalysis.types[resolvedGenericRet.expr.type]; + if (isContainerType(genericType)) { + renderContainer(genericType); + } + + // old code + // let instantiations = nodesToFnsMap[protoSrcIndex]; + // let calls = nodesToCallsMap[protoSrcIndex]; + // if (instantiations == null && calls == null) { + // domFnNoExamples.classList.remove("hidden"); + // } else if (calls != null) { + // // if (fnObj.combined === undefined) fnObj.combined = allCompTimeFnCallsResult(calls); + // if (fnObj.combined != null) renderContainer(fnObj.combined); + + // resizeDomList(domListFnExamples, calls.length, '
  • '); + + // for (let callI = 0; callI < calls.length; callI += 1) { + // let liDom = domListFnExamples.children[callI]; + // liDom.innerHTML = getCallHtml(fnDecl, calls[callI]); + // } + + // domFnExamples.classList.remove("hidden"); + // } else if (instantiations != null) { + // // TODO + // } + } else { + domFnExamples.classList.add("hidden"); + domFnNoExamples.classList.add("hidden"); + } + + let protoSrcNode = zigAnalysis.astNodes[protoSrcIndex]; + if ( + docsSource == null && + protoSrcNode != null && + protoSrcNode.docs != null + ) { + docsSource = protoSrcNode.docs; + } + if (docsSource != null) { + domTldDocs.innerHTML = markdown(docsSource); + domTldDocs.classList.remove("hidden"); + } + domFnProto.classList.remove("hidden"); + } + + function renderFnParamDocs(fnDecl, typeObj) { + let docCount = 0; + + let fnNode = zigAnalysis.astNodes[fnDecl.src]; + let fields = fnNode.fields; + let isVarArgs = fnNode.varArgs; + + for (let i = 0; i < fields.length; i += 1) { + let field = fields[i]; + let fieldNode = zigAnalysis.astNodes[field]; + if (fieldNode.docs != null) { + docCount += 1; + } + } + if (docCount == 0) { + return; + } + + resizeDomList(domListParams, docCount, "
    "); + let domIndex = 0; + + for (let i = 0; i < fields.length; i += 1) { + let field = fields[i]; + let fieldNode = zigAnalysis.astNodes[field]; + let docs = fieldNode.docs; + if (fieldNode.docs == null) { + continue; + } + let docsNonEmpty = docs !== ""; + let divDom = domListParams.children[domIndex]; + domIndex += 1; + + let value = typeObj.params[i]; + let preClass = docsNonEmpty ? ' class="fieldHasDocs"' : ""; + let html = "" + escapeHtml(fieldNode.name) + ": "; + if (isVarArgs && i === typeObj.params.length - 1) { + html += "..."; + } else { + let name = exprName(value, { wantHtml: false, wantLink: false }); + html += '' + name + ""; + } + + html += ","; + + if (docsNonEmpty) { + html += '
    ' + markdown(docs) + "
    "; + } + divDom.innerHTML = html; + } + domSectParams.classList.remove("hidden"); + } + + function renderNav() { + let len = curNav.pkgNames.length + curNav.declNames.length; + resizeDomList(domListNav, len, '
  • '); + let list = []; + let hrefPkgNames = []; + let hrefDeclNames = []; + for (let i = 0; i < curNav.pkgNames.length; i += 1) { + hrefPkgNames.push(curNav.pkgNames[i]); + let name = curNav.pkgNames[i]; + if (name == "root") name = zigAnalysis.rootPkgName; + list.push({ + name: name, + link: navLink(hrefPkgNames, hrefDeclNames), + }); + } + for (let i = 0; i < curNav.declNames.length; i += 1) { + hrefDeclNames.push(curNav.declNames[i]); + list.push({ + name: curNav.declNames[i], + link: navLink(hrefPkgNames, hrefDeclNames), + }); + } + + for (let i = 0; i < list.length; i += 1) { + let liDom = domListNav.children[i]; + let aDom = liDom.children[0]; + aDom.textContent = list[i].name; + aDom.setAttribute("href", list[i].link); + if (i + 1 == list.length) { + aDom.classList.add("active"); + } else { + aDom.classList.remove("active"); + } + } + + domSectNav.classList.remove("hidden"); + } + + function renderInfo() { + domTdZigVer.textContent = zigAnalysis.params.zigVersion; + //domTdTarget.textContent = zigAnalysis.params.builds[0].target; + + domSectInfo.classList.remove("hidden"); + } + + function render404() { + domStatus.textContent = "404 Not Found"; + domStatus.classList.remove("hidden"); + } + + function renderPkgList() { + let rootPkg = zigAnalysis.packages[zigAnalysis.rootPkg]; + let list = []; + for (let key in rootPkg.table) { + let pkgIndex = rootPkg.table[key]; + if (zigAnalysis.packages[pkgIndex] == null) continue; + if (key == zigAnalysis.params.rootName) continue; + list.push({ + name: key, + pkg: pkgIndex, + }); + } + + { + let aDom = domSectMainPkg.children[1].children[0].children[0]; + aDom.textContent = zigAnalysis.rootPkgName; + aDom.setAttribute("href", navLinkPkg(zigAnalysis.rootPkg)); + if (zigAnalysis.params.rootName === curNav.pkgNames[0]) { + aDom.classList.add("active"); + } else { + aDom.classList.remove("active"); + } + domSectMainPkg.classList.remove("hidden"); + } + + list.sort(function (a, b) { + return operatorCompare(a.name.toLowerCase(), b.name.toLowerCase()); + }); + + if (list.length !== 0) { + resizeDomList(domListPkgs, list.length, '
  • '); + for (let i = 0; i < list.length; i += 1) { + let liDom = domListPkgs.children[i]; + let aDom = liDom.children[0]; + aDom.textContent = list[i].name; + aDom.setAttribute("href", navLinkPkg(list[i].pkg)); + if (list[i].name === curNav.pkgNames[0]) { + aDom.classList.add("active"); + } else { + aDom.classList.remove("active"); + } + } + + domSectPkgs.classList.remove("hidden"); + } + } + + function navLink(pkgNames, declNames, callName) { + let base = "#"; + if (curNav.showPrivDecls) { + base += "*"; + } + + if (pkgNames.length === 0 && declNames.length === 0) { + return base; + } else if (declNames.length === 0 && callName == null) { + return base + pkgNames.join("."); + } else if (callName == null) { + return base + pkgNames.join(".") + ";" + declNames.join("."); + } else { + return ( + base + pkgNames.join(".") + ";" + declNames.join(".") + ";" + callName + ); + } + } + + function navLinkPkg(pkgIndex) { + return navLink(canonPkgPaths[pkgIndex], []); + } + + function navLinkDecl(childName) { + return navLink(curNav.pkgNames, curNav.declNames.concat([childName])); + } + + // + // function navLinkCall(callObj) { + // let declNamesCopy = curNav.declNames.concat([]); + // let callName = (declNamesCopy.pop()); + + // callName += '('; + // for (let arg_i = 0; arg_i < callObj.args.length; arg_i += 1) { + // if (arg_i !== 0) callName += ','; + // let argObj = callObj.args[arg_i]; + // callName += getValueText(argObj, argObj, false, false); + // } + // callName += ')'; + + // declNamesCopy.push(callName); + // return navLink(curNav.pkgNames, declNamesCopy); + // } + + function resizeDomListDl(dlDom, desiredLen) { + // add the missing dom entries + for (let i = dlDom.childElementCount / 2; i < desiredLen; i += 1) { + dlDom.insertAdjacentHTML("beforeend", "
    "); + } + // remove extra dom entries + while (desiredLen < dlDom.childElementCount / 2) { + dlDom.removeChild(dlDom.lastChild); + dlDom.removeChild(dlDom.lastChild); + } + } + + function resizeDomList(listDom, desiredLen, templateHtml) { + // add the missing dom entries + for (let i = listDom.childElementCount; i < desiredLen; i += 1) { + listDom.insertAdjacentHTML("beforeend", templateHtml); + } + // remove extra dom entries + while (desiredLen < listDom.childElementCount) { + listDom.removeChild(listDom.lastChild); + } + } + + function walkResultTypeRef(wr) { + if (wr.typeRef) return wr.typeRef; + let resolved = resolveValue(wr); + if (wr === resolved) { + return { type: 0 }; + } + return walkResultTypeRef(resolved); + } + + function exprName(expr, opts) { + switch (Object.keys(expr)[0]) { + default: + throw "this expression is not implemented yet"; + case "bool": { + if (expr.bool) { + return "true"; + } + return "false"; + } + case "&": { + return "&" + exprName(zigAnalysis.exprs[expr["&"]]); + } + case "compileError": { + let compileError = expr.compileError; + return compileError; + } + case "enumLiteral": { + let literal = expr.enumLiteral; + return "." + literal; + } + case "void": { + return "void"; + } + case "slice": { + let payloadHtml = ""; + const lhsExpr = zigAnalysis.exprs[expr.slice.lhs]; + const startExpr = zigAnalysis.exprs[expr.slice.start]; + let decl = exprName(lhsExpr); + let start = exprName(startExpr); + let end = ""; + let sentinel = ""; + if (expr.slice["end"]) { + const endExpr = zigAnalysis.exprs[expr.slice.end]; + let end_ = exprName(endExpr); + end += end_; + } + if (expr.slice["sentinel"]) { + const sentinelExpr = zigAnalysis.exprs[expr.slice.sentinel]; + let sentinel_ = exprName(sentinelExpr); + sentinel += " :" + sentinel_; + } + payloadHtml += decl + "[" + start + ".." + end + sentinel + "]"; + return payloadHtml; + } + case "sliceIndex": { + const sliceIndex = zigAnalysis.exprs[expr.sliceIndex]; + return exprName(sliceIndex, opts); + } + case "cmpxchg": { + const typeIndex = zigAnalysis.exprs[expr.cmpxchg.type]; + const ptrIndex = zigAnalysis.exprs[expr.cmpxchg.ptr]; + const expectedValueIndex = + zigAnalysis.exprs[expr.cmpxchg.expected_value]; + const newValueIndex = zigAnalysis.exprs[expr.cmpxchg.new_value]; + const successOrderIndex = zigAnalysis.exprs[expr.cmpxchg.success_order]; + const failureOrderIndex = zigAnalysis.exprs[expr.cmpxchg.failure_order]; + + const type = exprName(typeIndex, opts); + const ptr = exprName(ptrIndex, opts); + const expectedValue = exprName(expectedValueIndex, opts); + const newValue = exprName(newValueIndex, opts); + const successOrder = exprName(successOrderIndex, opts); + const failureOrder = exprName(failureOrderIndex, opts); + + let fnName = "@"; + + switch (expr.cmpxchg.name) { + case "cmpxchg_strong": { + fnName += "cmpxchgStrong"; + break; } - case "refPath" : { - let name = exprName(expr.refPath[0]); - for (let i = 1; i < expr.refPath.length; i++) { - let component = undefined; - if ("string" in expr.refPath[i]) { - component = expr.refPath[i].string; - } else { - component = exprName(expr.refPath[i]); - } - name += "." + component; + case "cmpxchg_weak": { + fnName += "cmpxchgWeak"; + break; + } + default: { + console.log("There's only cmpxchg_strong and cmpxchg_weak"); + } + } + + return ( + fnName + + "(" + + type + + ", " + + ptr + + ", " + + expectedValue + + ", " + + newValue + + ", " + + "." + + successOrder + + ", " + + "." + + failureOrder + + ")" + ); + } + case "cmpxchgIndex": { + const cmpxchgIndex = zigAnalysis.exprs[expr.cmpxchgIndex]; + return exprName(cmpxchgIndex, opts); + } + case "switchOp": { + let condExpr = zigAnalysis.exprs[expr.switchOp.cond_index]; + let ast = zigAnalysis.astNodes[expr.switchOp.ast]; + let file_name = expr.switchOp.file_name; + let outer_decl_index = expr.switchOp.outer_decl; + let outer_decl = zigAnalysis.types[outer_decl_index]; + let line = 0; + // console.log(expr.switchOp) + // console.log(outer_decl) + while (outer_decl_index !== 0 && outer_decl.line_number > 0) { + line += outer_decl.line_number; + outer_decl_index = outer_decl.outer_decl; + outer_decl = zigAnalysis.types[outer_decl_index]; + // console.log(outer_decl) + } + line += ast.line + 1; + let payloadHtml = ""; + let cond = exprName(condExpr, opts); + + payloadHtml += + "
    " + + "node_name: " + + ast.name + + "
    " + + "file: " + + file_name + + "
    " + + "line: " + + line + + "
    "; + payloadHtml += + "switch(" + + cond + + ") {" + + '' + + "..." + + "}"; + return payloadHtml; + } + case "switchIndex": { + const switchIndex = zigAnalysis.exprs[expr.switchIndex]; + return exprName(switchIndex, opts); + } + case "refPath": { + let name = exprName(expr.refPath[0]); + for (let i = 1; i < expr.refPath.length; i++) { + let component = undefined; + if ("string" in expr.refPath[i]) { + component = expr.refPath[i].string; + } else { + component = exprName(expr.refPath[i]); + } + name += "." + component; + } + return name; + } + case "fieldRef": { + const enumObj = exprName({ type: expr.fieldRef.type }, opts); + const field = + zigAnalysis.astNodes[enumObj.ast].fields[expr.fieldRef.index]; + const name = zigAnalysis.astNodes[field].name; + return name; + } + case "enumToInt": { + const enumToInt = zigAnalysis.exprs[expr.enumToInt]; + return "@enumToInt(" + exprName(enumToInt, opts) + ")"; + } + case "bitSizeOf": { + const bitSizeOf = zigAnalysis.exprs[expr.bitSizeOf]; + return "@bitSizeOf(" + exprName(bitSizeOf, opts) + ")"; + } + case "sizeOf": { + const sizeOf = zigAnalysis.exprs[expr.sizeOf]; + return "@sizeOf(" + exprName(sizeOf, opts) + ")"; + } + case "builtinIndex": { + const builtinIndex = zigAnalysis.exprs[expr.builtinIndex]; + return exprName(builtinIndex, opts); + } + case "builtin": { + const param_expr = zigAnalysis.exprs[expr.builtin.param]; + let param = exprName(param_expr, opts); + + let payloadHtml = "@"; + switch (expr.builtin.name) { + case "align_of": { + payloadHtml += "alignOf"; + break; + } + case "bool_to_int": { + payloadHtml += "boolToInt"; + break; + } + case "embed_file": { + payloadHtml += "embedFile"; + break; + } + case "error_name": { + payloadHtml += "errorName"; + break; + } + case "panic": { + payloadHtml += "panic"; + break; + } + case "set_cold": { + payloadHtml += "setCold"; + break; + } + case "set_runtime_safety": { + payloadHtml += "setRuntimeSafety"; + break; + } + case "sqrt": { + payloadHtml += "sqrt"; + break; + } + case "sin": { + payloadHtml += "sin"; + break; + } + case "cos": { + payloadHtml += "cos"; + break; + } + case "tan": { + payloadHtml += "tan"; + break; + } + case "exp": { + payloadHtml += "exp"; + break; + } + case "exp2": { + payloadHtml += "exp2"; + break; + } + case "log": { + payloadHtml += "log"; + break; + } + case "log2": { + payloadHtml += "log2"; + break; + } + case "log10": { + payloadHtml += "log10"; + break; + } + case "fabs": { + payloadHtml += "fabs"; + break; + } + case "floor": { + payloadHtml += "floor"; + break; + } + case "ceil": { + payloadHtml += "ceil"; + break; + } + case "trunc": { + payloadHtml += "trunc"; + break; + } + case "round": { + payloadHtml += "round"; + break; + } + case "tag_name": { + payloadHtml += "tagName"; + break; + } + case "reify": { + payloadHtml += "Type"; + break; + } + case "type_name": { + payloadHtml += "typeName"; + break; + } + case "frame_type": { + payloadHtml += "Frame"; + break; + } + case "frame_size": { + payloadHtml += "frameSize"; + break; + } + case "ptr_to_int": { + payloadHtml += "ptrToInt"; + break; + } + case "error_to_int": { + payloadHtml += "errorToInt"; + break; + } + case "int_to_error": { + payloadHtml += "intToError"; + break; + } + case "maximum": { + payloadHtml += "maximum"; + break; + } + case "minimum": { + payloadHtml += "minimum"; + break; + } + case "bit_not": { + return "~" + param; + } + case "clz": { + return "@clz(T" + ", " + param + ")"; + } + case "ctz": { + return "@ctz(T" + ", " + param + ")"; + } + case "pop_count": { + return "@popCount(T" + ", " + param + ")"; + } + case "byte_swap": { + return "@byteSwap(T" + ", " + param + ")"; + } + case "bit_reverse": { + return "@bitReverse(T" + ", " + param + ")"; + } + default: + console.log("builtin function not handled yet or doesn't exist!"); + } + return payloadHtml + "(" + param + ")"; + } + case "builtinBinIndex": { + const builtinBinIndex = zigAnalysis.exprs[expr.builtinBinIndex]; + return exprName(builtinBinIndex, opts); + } + case "builtinBin": { + const lhsOp = zigAnalysis.exprs[expr.builtinBin.lhs]; + const rhsOp = zigAnalysis.exprs[expr.builtinBin.rhs]; + let lhs = exprName(lhsOp, opts); + let rhs = exprName(rhsOp, opts); + + let payloadHtml = "@"; + switch (expr.builtinBin.name) { + case "float_to_int": { + payloadHtml += "floatToInt"; + break; + } + case "int_to_float": { + payloadHtml += "intToFloat"; + break; + } + case "int_to_ptr": { + payloadHtml += "intToPtr"; + break; + } + case "int_to_enum": { + payloadHtml += "intToEnum"; + break; + } + case "float_cast": { + payloadHtml += "floatCast"; + break; + } + case "int_cast": { + payloadHtml += "intCast"; + break; + } + case "ptr_cast": { + payloadHtml += "ptrCast"; + break; + } + case "truncate": { + payloadHtml += "truncate"; + break; + } + case "align_cast": { + payloadHtml += "alignCast"; + break; + } + case "has_decl": { + payloadHtml += "hasDecl"; + break; + } + case "has_field": { + payloadHtml += "hasField"; + break; + } + case "bit_reverse": { + payloadHtml += "bitReverse"; + break; + } + case "div_exact": { + payloadHtml += "divExact"; + break; + } + case "div_floor": { + payloadHtml += "divFloor"; + break; + } + case "div_trunc": { + payloadHtml += "divTrunc"; + break; + } + case "mod": { + payloadHtml += "mod"; + break; + } + case "rem": { + payloadHtml += "rem"; + break; + } + case "mod_rem": { + payloadHtml += "rem"; + break; + } + case "shl_exact": { + payloadHtml += "shlExact"; + break; + } + case "shr_exact": { + payloadHtml += "shrExact"; + break; + } + case "bitcast": { + payloadHtml += "bitCast"; + break; + } + case "align_cast": { + payloadHtml += "alignCast"; + break; + } + case "vector_type": { + payloadHtml += "Vector"; + break; + } + case "reduce": { + payloadHtml += "reduce"; + break; + } + case "splat": { + payloadHtml += "splat"; + break; + } + case "offset_of": { + payloadHtml += "offsetOf"; + break; + } + case "bit_offset_of": { + payloadHtml += "bitOffsetOf"; + break; + } + default: + console.log("builtin function not handled yet or doesn't exist!"); + } + return payloadHtml + "(" + lhs + ", " + rhs + ")"; + } + case "binOpIndex": { + const binOpIndex = zigAnalysis.exprs[expr.binOpIndex]; + return exprName(binOpIndex, opts); + } + case "binOp": { + const lhsOp = zigAnalysis.exprs[expr.binOp.lhs]; + const rhsOp = zigAnalysis.exprs[expr.binOp.rhs]; + let lhs = exprName(lhsOp, opts); + let rhs = exprName(rhsOp, opts); + + let print_lhs = ""; + let print_rhs = ""; + + if (lhsOp["binOpIndex"]) { + print_lhs = "(" + lhs + ")"; + } else { + print_lhs = lhs; + } + if (rhsOp["binOpIndex"]) { + print_rhs = "(" + rhs + ")"; + } else { + print_rhs = rhs; + } + + let operator = ""; + + switch (expr.binOp.name) { + case "add": { + operator += "+"; + break; + } + case "addwrap": { + operator += "+%"; + break; + } + case "add_sat": { + operator += "+|"; + break; + } + case "sub": { + operator += "-"; + break; + } + case "subwrap": { + operator += "-%"; + break; + } + case "sub_sat": { + operator += "-|"; + break; + } + case "mul": { + operator += "*"; + break; + } + case "mulwrap": { + operator += "*%"; + break; + } + case "mul_sat": { + operator += "*|"; + break; + } + case "div": { + operator += "/"; + break; + } + case "shl": { + operator += "<<"; + break; + } + case "shl_sat": { + operator += "<<|"; + break; + } + case "shr": { + operator += ">>"; + break; + } + case "bit_or": { + operator += "|"; + break; + } + case "bit_and": { + operator += "&"; + break; + } + case "array_cat": { + operator += "++"; + break; + } + case "array_mul": { + operator += "**"; + break; + } + default: + console.log("operator not handled yet or doesn't exist!"); + } + + return print_lhs + " " + operator + " " + print_rhs; + } + case "errorSets": { + const errUnionObj = zigAnalysis.types[expr.errorSets]; + let lhs = exprName(errUnionObj.lhs, opts); + let rhs = exprName(errUnionObj.rhs, opts); + return lhs + " || " + rhs; + } + case "errorUnion": { + const errUnionObj = zigAnalysis.types[expr.errorUnion]; + let lhs = exprName(errUnionObj.lhs, opts); + let rhs = exprName(errUnionObj.rhs, opts); + return lhs + "!" + rhs; + } + case "struct": { + const struct_name = + zigAnalysis.decls[expr.struct[0].val.typeRef.refPath[0].declRef].name; + let struct_body = ""; + struct_body += struct_name + "{ "; + for (let i = 0; i < expr.struct.length; i++) { + const val = expr.struct[i].name; + const exprArg = zigAnalysis.exprs[expr.struct[i].val.expr.as.exprArg]; + let value_field = exprArg[Object.keys(exprArg)[0]]; + if (value_field instanceof Object) { + value_field = + zigAnalysis.decls[value_field[0].val.typeRef.refPath[0].declRef] + .name; + } + struct_body += "." + val + " = " + value_field; + if (i !== expr.struct.length - 1) { + struct_body += ", "; + } else { + struct_body += " "; + } + } + struct_body += "}"; + return struct_body; + } + case "typeOf_peer": { + let payloadHtml = "@TypeOf("; + for (let i = 0; i < expr.typeOf_peer.length; i++) { + let elem = zigAnalysis.exprs[expr.typeOf_peer[i]]; + payloadHtml += exprName(elem, { wantHtml: true, wantLink: true }); + if (i !== expr.typeOf_peer.length - 1) { + payloadHtml += ", "; + } + } + payloadHtml += ")"; + return payloadHtml; + } + case "alignOf": { + const alignRefArg = zigAnalysis.exprs[expr.alignOf]; + let payloadHtml = + "@alignOf(" + + exprName(alignRefArg, { wantHtml: true, wantLink: true }) + + ")"; + return payloadHtml; + } + case "typeOf": { + const typeRefArg = zigAnalysis.exprs[expr.typeOf]; + let payloadHtml = + "@TypeOf(" + + exprName(typeRefArg, { wantHtml: true, wantLink: true }) + + ")"; + return payloadHtml; + } + case "typeInfo": { + const typeRefArg = zigAnalysis.exprs[expr.typeInfo]; + let payloadHtml = + "@typeInfo(" + + exprName(typeRefArg, { wantHtml: true, wantLink: true }) + + ")"; + return payloadHtml; + } + case "null": { + return "null"; + } + case "array": { + let payloadHtml = ".{"; + for (let i = 0; i < expr.array.length; i++) { + if (i != 0) payloadHtml += ", "; + let elem = zigAnalysis.exprs[expr.array[i]]; + payloadHtml += exprName(elem, opts); + } + return payloadHtml + "}"; + } + case "comptimeExpr": { + return zigAnalysis.comptimeExprs[expr.comptimeExpr].code; + } + case "call": { + let call = zigAnalysis.calls[expr.call]; + let payloadHtml = ""; + + switch (Object.keys(call.func)[0]) { + default: + throw "TODO"; + case "declRef": + case "refPath": { + payloadHtml += exprName(call.func, opts); + break; + } + } + payloadHtml += "("; + + for (let i = 0; i < call.args.length; i++) { + if (i != 0) payloadHtml += ", "; + payloadHtml += exprName(call.args[i], opts); + } + + payloadHtml += ")"; + return payloadHtml; + } + case "as": { + // @Check : this should be done in backend because there are legit @as() calls + // const typeRefArg = zigAnalysis.exprs[expr.as.typeRefArg]; + const exprArg = zigAnalysis.exprs[expr.as.exprArg]; + // return "@as(" + exprName(typeRefArg, opts) + + // ", " + exprName(exprArg, opts) + ")"; + return exprName(exprArg, opts); + } + case "declRef": { + return zigAnalysis.decls[expr.declRef].name; + } + case "refPath": { + return expr.refPath.map((x) => exprName(x, opts)).join("."); + } + case "int": { + return "" + expr.int; + } + case "float": { + return "" + expr.float.toFixed(2); + } + case "float128": { + return "" + expr.float128.toFixed(2); + } + case "undefined": { + return "undefined"; + } + case "string": { + return '"' + escapeHtml(expr.string) + '"'; + } + + case "anytype": { + return "anytype"; + } + + case "this": { + return "@This()"; + } + + case "type": { + let name = ""; + + let typeObj = expr.type; + if (typeof typeObj === "number") typeObj = zigAnalysis.types[typeObj]; + switch (typeObj.kind) { + default: + throw "TODO"; + case typeKinds.Struct: { + let structObj = typeObj; + return structObj; + } + case typeKinds.Enum: { + let enumObj = typeObj; + return enumObj; + } + case typeKinds.Opaque: { + let opaqueObj = typeObj; + + return opaqueObj.name; + } + case typeKinds.ComptimeExpr: { + return "anyopaque"; + } + case typeKinds.Array: { + let arrayObj = typeObj; + let name = "["; + let lenName = exprName(arrayObj.len, opts); + let sentinel = arrayObj.sentinel + ? ":" + exprName(arrayObj.sentinel, opts) + : ""; + // let is_mutable = arrayObj.is_multable ? "const " : ""; + + if (opts.wantHtml) { + name += + '' + lenName + sentinel + ""; + } else { + name += lenName + sentinel; } + name += "]"; + // name += is_mutable; + name += exprName(arrayObj.child, opts); return name; } - case "fieldRef" : { - const enumObj = exprName({"type":expr.fieldRef.type} ,opts); - const field = zigAnalysis.astNodes[enumObj.ast].fields[expr.fieldRef.index]; - const name = zigAnalysis.astNodes[field].name; - return name - } - case "enumToInt" : { - const enumToInt = zigAnalysis.exprs[expr.enumToInt]; - return "@enumToInt(" + exprName(enumToInt, opts) + ")"; - } - case "bitSizeOf" : { - const bitSizeOf = zigAnalysis.exprs[expr.bitSizeOf]; - return "@bitSizeOf(" + exprName(bitSizeOf, opts) + ")"; - } - case "sizeOf" : { - const sizeOf = zigAnalysis.exprs[expr.sizeOf]; - return "@sizeOf(" + exprName(sizeOf, opts) + ")"; - } - case "builtinIndex" : { - const builtinIndex = zigAnalysis.exprs[expr.builtinIndex]; - return exprName(builtinIndex, opts); - } - case "builtin": { - const param_expr = zigAnalysis.exprs[expr.builtin.param]; - let param = exprName(param_expr, opts); - - - let payloadHtml = "@"; - switch (expr.builtin.name) { - case "align_of": { - payloadHtml += "alignOf"; + case typeKinds.Optional: + return "?" + exprName(typeObj.child, opts); + case typeKinds.Pointer: { + let ptrObj = typeObj; + let sentinel = ptrObj.sentinel + ? ":" + exprName(ptrObj.sentinel, opts) + : ""; + let is_mutable = !ptrObj.is_mutable ? "const " : ""; + let name = ""; + switch (ptrObj.size) { + default: + console.log("TODO: implement unhandled pointer size case"); + case pointerSizeEnum.One: + name += "*"; + name += is_mutable; break; - } - case "bool_to_int": { - payloadHtml += "boolToInt"; + case pointerSizeEnum.Many: + name += "[*"; + name += sentinel; + name += "]"; + name += is_mutable; break; - } - case "embed_file": { - payloadHtml += "embedFile"; + case pointerSizeEnum.Slice: + if (ptrObj.is_ref) { + name += "*"; + } + name += "["; + name += sentinel; + name += "]"; + name += is_mutable; break; - } - case "error_name": { - payloadHtml += "errorName"; + case pointerSizeEnum.C: + name += "[*c"; + name += sentinel; + name += "]"; + name += is_mutable; break; - } - case "panic": { - payloadHtml += "panic"; - break; - } - case "set_cold": { - payloadHtml += "setCold"; - break; - } - case "set_runtime_safety": { - payloadHtml += "setRuntimeSafety"; - break; - } - case "sqrt": { - payloadHtml += "sqrt"; - break; - } - case "sin": { - payloadHtml += "sin"; - break; - } - case "cos": { - payloadHtml += "cos"; - break; - } - case "tan": { - payloadHtml += "tan"; - break; - } - case "exp": { - payloadHtml += "exp"; - break; - } - case "exp2": { - payloadHtml += "exp2"; - break; - } - case "log": { - payloadHtml += "log"; - break; - } - case "log2": { - payloadHtml += "log2"; - break; - } - case "log10": { - payloadHtml += "log10"; - break; - } - case "fabs": { - payloadHtml += "fabs"; - break; - } - case "floor": { - payloadHtml += "floor"; - break; - } - case "ceil": { - payloadHtml += "ceil"; - break; - } - case "trunc": { - payloadHtml += "trunc"; - break; - } - case "round": { - payloadHtml += "round"; - break; - } - case "tag_name": { - payloadHtml += "tagName"; - break; - } - case "reify": { - payloadHtml += "Type"; - break; - } - case "type_name": { - payloadHtml += "typeName"; - break; - } - case "frame_type": { - payloadHtml += "Frame"; - break; - } - case "frame_size": { - payloadHtml += "frameSize"; - break; - } - case "ptr_to_int": { - payloadHtml += "ptrToInt"; - break; - } - case "error_to_int": { - payloadHtml += "errorToInt"; - break; - } - case "int_to_error": { - payloadHtml += "intToError"; - break; - } - case "maximum": { - payloadHtml += "maximum"; - break; - } - case "minimum": { - payloadHtml += "minimum"; - break; - } - case "bit_not": { - return "~" + param; - } - case "clz": { - return "@clz(T" + ", " + param + ")"; - } - case "ctz": { - return "@ctz(T" + ", " + param + ")"; - } - case "pop_count": { - return "@popCount(T" + ", " + param + ")"; - } - case "byte_swap": { - return "@byteSwap(T" + ", " + param + ")"; - } - case "bit_reverse": { - return "@bitReverse(T" + ", " + param + ")"; - } - default: console.log("builtin function not handled yet or doesn't exist!"); - }; - return payloadHtml + "(" + param + ")"; - - } - case "builtinBinIndex" : { - const builtinBinIndex = zigAnalysis.exprs[expr.builtinBinIndex]; - return exprName(builtinBinIndex, opts); - } - case "builtinBin": { - const lhsOp = zigAnalysis.exprs[expr.builtinBin.lhs]; - const rhsOp = zigAnalysis.exprs[expr.builtinBin.rhs]; - let lhs = exprName(lhsOp, opts); - let rhs = exprName(rhsOp, opts); - - let payloadHtml = "@"; - switch (expr.builtinBin.name) { - case "float_to_int": { - payloadHtml += "floatToInt"; - break; - } - case "int_to_float": { - payloadHtml += "intToFloat"; - break; - } - case "int_to_ptr": { - payloadHtml += "intToPtr"; - break; - } - case "int_to_enum": { - payloadHtml += "intToEnum"; - break; - } - case "float_cast": { - payloadHtml += "floatCast"; - break; - } - case "int_cast": { - payloadHtml += "intCast"; - break; - } - case "ptr_cast": { - payloadHtml += "ptrCast"; - break; - } - case "truncate": { - payloadHtml += "truncate"; - break; - } - case "align_cast": { - payloadHtml += "alignCast"; - break; - } - case "has_decl": { - payloadHtml += "hasDecl"; - break; - } - case "has_field": { - payloadHtml += "hasField"; - break; - } - case "bit_reverse": { - payloadHtml += "bitReverse"; - break; - } - case "div_exact": { - payloadHtml += "divExact"; - break; - } - case "div_floor": { - payloadHtml += "divFloor"; - break; - } - case "div_trunc": { - payloadHtml += "divTrunc"; - break; - } - case "mod": { - payloadHtml += "mod"; - break; - } - case "rem": { - payloadHtml += "rem"; - break; - } - case "mod_rem": { - payloadHtml += "rem"; - break; - } - case "shl_exact": { - payloadHtml += "shlExact"; - break; - } - case "shr_exact": { - payloadHtml += "shrExact"; - break; - } - case "bitcast" : { - payloadHtml += "bitCast"; - break; - } - case "align_cast" : { - payloadHtml += "alignCast"; - break; - } - case "vector_type" : { - payloadHtml += "Vector"; - break; - } - case "reduce": { - payloadHtml += "reduce"; - break; - } - case "splat": { - payloadHtml += "splat"; - break; - } - case "offset_of": { - payloadHtml += "offsetOf"; - break; - } - case "bit_offset_of": { - payloadHtml += "bitOffsetOf"; - break; - } - default: console.log("builtin function not handled yet or doesn't exist!"); - }; - return payloadHtml + "(" + lhs + ", " + rhs + ")"; - - } - case "binOpIndex" : { - const binOpIndex = zigAnalysis.exprs[expr.binOpIndex]; - return exprName(binOpIndex, opts); - } - case "binOp": { - const lhsOp = zigAnalysis.exprs[expr.binOp.lhs]; - const rhsOp = zigAnalysis.exprs[expr.binOp.rhs]; - let lhs = exprName(lhsOp, opts); - let rhs = exprName(rhsOp, opts); - - let print_lhs = ""; - let print_rhs = ""; - - if (lhsOp['binOpIndex']) { - print_lhs = "(" + lhs + ")"; - } else { - print_lhs = lhs; } - if (rhsOp['binOpIndex']) { - print_rhs = "(" + rhs + ")"; - } else { - print_rhs = rhs; + // @check: after the major changes in arrays the consts are came from switch above + // if (!ptrObj.is_mutable) { + // if (opts.wantHtml) { + // name += 'const '; + // } else { + // name += "const "; + // } + // } + if (ptrObj.is_allowzero) { + name += "allowzero "; } - - let operator = ""; - - switch (expr.binOp.name) { - case "add": { - operator += "+"; - break; + if (ptrObj.is_volatile) { + name += "volatile "; + } + if (ptrObj.has_addrspace) { + name += "addrspace("; + name += "." + ""; + name += ") "; + } + if (ptrObj.has_align) { + let align = exprName(ptrObj.align, opts); + if (opts.wantHtml) { + name += 'align('; + } else { + name += "align("; } - case "addwrap": { - operator += "+%"; - break; + if (opts.wantHtml) { + name += '' + align + ""; + } else { + name += align; } - case "add_sat": { - operator += "+|"; - break; + if (ptrObj.hostIntBytes != null) { + name += ":"; + if (opts.wantHtml) { + name += + '' + + ptrObj.bitOffsetInHost + + ""; + } else { + name += ptrObj.bitOffsetInHost; + } + name += ":"; + if (opts.wantHtml) { + name += + '' + + ptrObj.hostIntBytes + + ""; + } else { + name += ptrObj.hostIntBytes; + } } - case "sub": { - operator += "-"; - break; - } - case "subwrap": { - operator += "-%"; - break; - } - case "sub_sat": { - operator += "-|"; - break; - } - case "mul": { - operator += "*"; - break; - } - case "mulwrap": { - operator += "*%"; - break; - } - case "mul_sat": { - operator += "*|"; - break; - } - case "div": { - operator += "/"; - break; - } - case "shl": { - operator += "<<"; - break; - } - case "shl_sat": { - operator += "<<|"; - break; - } - case "shr": { - operator += ">>"; - break; - } - case "bit_or" : { - operator += "|"; - break; - } - case "bit_and" : { - operator += "&"; - break; - } - case "array_cat" : { - operator += "++"; - break; - } - case "array_mul" : { - operator += "**"; - break; - } - default: console.log("operator not handled yet or doesn't exist!"); - }; - - return print_lhs + " " + operator + " " + print_rhs; - + name += ") "; + } + //name += typeValueName(ptrObj.child, wantHtml, wantSubLink, null); + name += exprName(ptrObj.child, opts); + return name; } - case "errorSets": { - const errUnionObj = zigAnalysis.types[expr.errorSets]; - let lhs = exprName(errUnionObj.lhs, opts); - let rhs = exprName(errUnionObj.rhs, opts); - return lhs + " || " + rhs; + case typeKinds.Float: { + let floatObj = typeObj; + if (opts.wantHtml) { + return '' + floatObj.name + ""; + } else { + return floatObj.name; + } } - case "errorUnion": { - const errUnionObj = zigAnalysis.types[expr.errorUnion]; + case typeKinds.Int: { + let intObj = typeObj; + let name = intObj.name; + if (opts.wantHtml) { + return '' + name + ""; + } else { + return name; + } + } + case typeKinds.ComptimeInt: + if (opts.wantHtml) { + return 'comptime_int'; + } else { + return "comptime_int"; + } + case typeKinds.ComptimeFloat: + if (opts.wantHtml) { + return 'comptime_float'; + } else { + return "comptime_float"; + } + case typeKinds.Type: + if (opts.wantHtml) { + return 'type'; + } else { + return "type"; + } + case typeKinds.Bool: + if (opts.wantHtml) { + return 'bool'; + } else { + return "bool"; + } + case typeKinds.Void: + if (opts.wantHtml) { + return 'void'; + } else { + return "void"; + } + case typeKinds.EnumLiteral: + if (opts.wantHtml) { + return '(enum literal)'; + } else { + return "(enum literal)"; + } + case typeKinds.NoReturn: + if (opts.wantHtml) { + return 'noreturn'; + } else { + return "noreturn"; + } + case typeKinds.ErrorSet: { + let errSetObj = typeObj; + if (errSetObj.fields == null) { + return 'anyerror'; + } else { + // throw "TODO"; + let html = "error{" + errSetObj.fields[0].name + "}"; + return html; + } + } + + case typeKinds.ErrorUnion: { + let errUnionObj = typeObj; let lhs = exprName(errUnionObj.lhs, opts); let rhs = exprName(errUnionObj.rhs, opts); return lhs + "!" + rhs; - } - case "struct": { - const struct_name = zigAnalysis.decls[expr.struct[0].val.typeRef.refPath[0].declRef].name; - let struct_body = ""; - struct_body += struct_name + "{ "; - for (let i = 0; i < expr.struct.length; i++) { - const val = expr.struct[i].name - const exprArg = zigAnalysis.exprs[expr.struct[i].val.expr.as.exprArg]; - let value_field = exprArg[Object.keys(exprArg)[0]]; - if (value_field instanceof Object) { - value_field = zigAnalysis.decls[value_field[0].val.typeRef.refPath[0].declRef].name; - }; - struct_body += "." + val + " = " + value_field; - if (i !== expr.struct.length - 1) { - struct_body += ", "; - } else { - struct_body += " "; + case typeKinds.InferredErrorUnion: { + let errUnionObj = typeObj; + let payload = exprName(errUnionObj.payload, opts); + return "!" + payload; + } + case typeKinds.Fn: { + let fnObj = typeObj; + let payloadHtml = ""; + if (opts.wantHtml) { + if (fnObj.is_extern) { + payloadHtml += "pub extern "; } - } - struct_body += "}"; - return struct_body; - } - case "typeOf_peer": { - let payloadHtml = "@TypeOf(" - for (let i = 0; i < expr.typeOf_peer.length; i++) { - let elem = zigAnalysis.exprs[expr.typeOf_peer[i]]; - payloadHtml += exprName(elem, {wantHtml: true, wantLink:true}); - if (i !== expr.typeOf_peer.length - 1) { - payloadHtml += ", "; + if (fnObj.has_lib_name) { + payloadHtml += '"' + fnObj.lib_name + '" '; } - } - payloadHtml += ")"; - return payloadHtml; - - } - case "alignOf": { - const alignRefArg = zigAnalysis.exprs[expr.alignOf]; - let payloadHtml = "@alignOf(" + exprName(alignRefArg, {wantHtml: true, wantLink:true}) + ")"; - return payloadHtml; - } - case "typeOf": { - const typeRefArg = zigAnalysis.exprs[expr.typeOf]; - let payloadHtml = "@TypeOf(" + exprName(typeRefArg, {wantHtml: true, wantLink:true}) + ")"; - return payloadHtml; - } - case "typeInfo": { - const typeRefArg = zigAnalysis.exprs[expr.typeInfo]; - let payloadHtml = "@typeInfo(" + exprName(typeRefArg, {wantHtml: true, wantLink:true}) + ")"; - return payloadHtml; - } - case "null": { - return "null"; - } - case "array": { - let payloadHtml = ".{"; - for (let i = 0; i < expr.array.length; i++) { - if (i != 0) payloadHtml += ", "; - let elem = zigAnalysis.exprs[expr.array[i]]; - payloadHtml += exprName(elem, opts); - } - return payloadHtml + "}"; - } - case "comptimeExpr": { - return zigAnalysis.comptimeExprs[expr.comptimeExpr].code; - } - case "call": { - let call = zigAnalysis.calls[expr.call]; - let payloadHtml = ""; - - - switch(Object.keys(call.func)[0]){ - default: throw "TODO"; - case "declRef": - case "refPath": { - payloadHtml += exprName(call.func, opts); - break; - } - } - payloadHtml += "("; - - for (let i = 0; i < call.args.length; i++) { - if (i != 0) payloadHtml += ", "; - payloadHtml += exprName(call.args[i], opts); - } - - payloadHtml += ")"; - return payloadHtml; - } - case "as": { - // @Check : this should be done in backend because there are legit @as() calls - // const typeRefArg = zigAnalysis.exprs[expr.as.typeRefArg]; - const exprArg = zigAnalysis.exprs[expr.as.exprArg]; - // return "@as(" + exprName(typeRefArg, opts) + - // ", " + exprName(exprArg, opts) + ")"; - return exprName(exprArg, opts); - } - case "declRef": { - return zigAnalysis.decls[expr.declRef].name; - } - case "refPath": { - return expr.refPath.map(x => exprName(x, opts)).join("."); - } - case "int": { - return "" + expr.int; - } - case "float": { - return "" + expr.float.toFixed(2); - } - case "float128": { - return "" + expr.float128.toFixed(2); - } - case "undefined": { - return "undefined"; - } - case "string": { - return "\"" + escapeHtml(expr.string) + "\""; - } - - case "anytype": { - return "anytype"; - } - - case "this":{ - return "@This()"; - } - - case "type": { - let name = ""; - - let typeObj = expr.type; - if (typeof typeObj === 'number') typeObj = zigAnalysis.types[typeObj]; - switch (typeObj.kind) { - default: throw "TODO"; - case typeKinds.Struct: - { - let structObj = (typeObj); - return structObj; - } - case typeKinds.Enum: - { - let enumObj = (typeObj); - return enumObj; - } - case typeKinds.Opaque: - { - let opaqueObj = (typeObj); - - return opaqueObj.name; - } - case typeKinds.ComptimeExpr: - { - return "anyopaque"; - } - case typeKinds.Array: - { - let arrayObj = typeObj; - let name = "["; - let lenName = exprName(arrayObj.len, opts); - let sentinel = arrayObj.sentinel ? ":"+exprName(arrayObj.sentinel, opts) : ""; - // let is_mutable = arrayObj.is_multable ? "const " : ""; - - if (opts.wantHtml) { - name += - '' + lenName + sentinel + ""; - } else { - name += lenName + sentinel; - } - name += "]"; - // name += is_mutable; - name += exprName(arrayObj.child, opts); - return name; - } - case typeKinds.Optional: - return "?" + exprName((typeObj).child, opts); - case typeKinds.Pointer: - { - let ptrObj = (typeObj); - let sentinel = ptrObj.sentinel ? ":"+exprName(ptrObj.sentinel, opts) : ""; - let is_mutable = !ptrObj.is_mutable ? "const " : ""; - let name = ""; - switch (ptrObj.size) { - default: - console.log("TODO: implement unhandled pointer size case"); - case pointerSizeEnum.One: - name += "*"; - name += is_mutable; - break; - case pointerSizeEnum.Many: - name += "[*"; - name += sentinel; - name += "]"; - name += is_mutable; - break; - case pointerSizeEnum.Slice: - if (ptrObj.is_ref) { - name += "*"; - } - name += "["; - name += sentinel; - name += "]"; - name += is_mutable; - break; - case pointerSizeEnum.C: - name += "[*c"; - name += sentinel; - name += "]"; - name += is_mutable; - break; - } - // @check: after the major changes in arrays the consts are came from switch above - // if (!ptrObj.is_mutable) { - // if (opts.wantHtml) { - // name += 'const '; - // } else { - // name += "const "; - // } - // } - if (ptrObj.is_allowzero) { - name += "allowzero "; - } - if (ptrObj.is_volatile) { - name += "volatile "; - } - if (ptrObj.has_addrspace) { - name += "addrspace("; - name += "." + ""; - name += ") "; - } - if (ptrObj.has_align) { - let align = exprName(ptrObj.align, opts); - if (opts.wantHtml) { - name += 'align('; - } else { - name += "align("; - } - if (opts.wantHtml) { - name += '' + align + ''; - } else { - name += align; - } - if (ptrObj.hostIntBytes != null) { - name += ":"; - if (opts.wantHtml) { - name += '' + ptrObj.bitOffsetInHost + ''; - } else { - name += ptrObj.bitOffsetInHost; - } - name += ":"; - if (opts.wantHtml) { - name += '' + ptrObj.hostIntBytes + ''; - } else { - name += ptrObj.hostIntBytes; - } - } - name += ") "; - } - //name += typeValueName(ptrObj.child, wantHtml, wantSubLink, null); - name += exprName(ptrObj.child, opts); - return name; - } - case typeKinds.Float: - { - let floatObj = (typeObj); - - if (opts.wantHtml) { - return '' + floatObj.name + ''; - } else { - return floatObj.name; - } - } - case typeKinds.Int: - { - let intObj = (typeObj); - let name = intObj.name; - if (opts.wantHtml) { - return '' + name + ''; - } else { - return name; - } - } - case typeKinds.ComptimeInt: - if (opts.wantHtml) { - return 'comptime_int'; - } else { - return "comptime_int"; - } - case typeKinds.ComptimeFloat: - if (opts.wantHtml) { - return 'comptime_float'; - } else { - return "comptime_float"; - } - case typeKinds.Type: - if (opts.wantHtml) { - return 'type'; - } else { - return "type"; - } - case typeKinds.Bool: - if (opts.wantHtml) { - return 'bool'; - } else { - return "bool"; - } - case typeKinds.Void: - if (opts.wantHtml) { - return 'void'; - } else { - return "void"; - } - case typeKinds.EnumLiteral: - if (opts.wantHtml) { - return '(enum literal)'; - } else { - return "(enum literal)"; - } - case typeKinds.NoReturn: - if (opts.wantHtml) { - return 'noreturn'; - } else { - return "noreturn"; - } - case typeKinds.ErrorSet: - { - let errSetObj = (typeObj); - if (errSetObj.fields == null) { - return 'anyerror'; - } else { - // throw "TODO"; - let html = "error{" + errSetObj.fields[0].name + "}"; - return html; - } - } - - case typeKinds.ErrorUnion: - { - let errUnionObj = (typeObj); - let lhs = exprName(errUnionObj.lhs, opts); - let rhs = exprName(errUnionObj.rhs, opts); - return lhs + "!" + rhs; - } - case typeKinds.InferredErrorUnion: - { - let errUnionObj = (typeObj); - let payload = exprName(errUnionObj.payload, opts); - return "!" + payload; - } - case typeKinds.Fn: - { - let fnObj = (typeObj); - let payloadHtml = ""; - if (opts.wantHtml) { - if (fnObj.is_extern) { - payloadHtml += "pub extern "; - } - if (fnObj.has_lib_name) { - payloadHtml += "\"" + fnObj.lib_name +"\" "; - } - payloadHtml += 'fn'; - if (opts.fnDecl) { - payloadHtml += ' '; - if (opts.linkFnNameDecl) { - payloadHtml += '' + - escapeHtml(opts.fnDecl.name) + ''; - } else { - payloadHtml += escapeHtml(opts.fnDecl.name); - } - payloadHtml += ''; - } - } else { - payloadHtml += 'fn '; - } - payloadHtml += '('; - if (fnObj.params) { - let fields = null; - let isVarArgs = false; - let fnNode = zigAnalysis.astNodes[fnObj.src]; - fields = fnNode.fields; - isVarArgs = fnNode.varArgs; - - for (let i = 0; i < fnObj.params.length; i += 1) { - if (i != 0) { - payloadHtml += ', '; - } - - payloadHtml += "
        
    " - let value = fnObj.params[i]; - let paramValue = resolveValue({expr: value}); - - if (fields != null) { - let paramNode = zigAnalysis.astNodes[fields[i]]; - - if (paramNode.varArgs) { - payloadHtml += '...'; - continue; - } - - if (paramNode.noalias) { - if (opts.wantHtml) { - payloadHtml += 'noalias '; - } else { - payloadHtml += 'noalias '; - } - } - - if (paramNode.comptime) { - if (opts.wantHtml) { - payloadHtml += 'comptime '; - } else { - payloadHtml += 'comptime '; - } - } - - let paramName = paramNode.name; - if (paramName != null) { - // skip if it matches the type name - if (!shouldSkipParamName(paramValue, paramName)) { - payloadHtml += paramName + ': '; - } - } - } - - if (isVarArgs && i === fnObj.params.length - 1) { - payloadHtml += '...'; - } - else if ("alignOf" in value) { - if (opts.wantHtml) { - payloadHtml += ''; - payloadHtml += - '' - + exprName(value, opts) + ''; - payloadHtml += ''; - } else { - payloadHtml += exprName(value, opts); - } - - } - else if ("typeOf" in value) { - if (opts.wantHtml) { - payloadHtml += ''; - payloadHtml += - '' - + exprName(value, opts) + ''; - payloadHtml += ''; - } else { - payloadHtml += exprName(value, opts); - } - - } - else if ("typeOf_peer" in value) { - if (opts.wantHtml) { - payloadHtml += ''; - payloadHtml += - '' - + exprName(value, opts) + ''; - payloadHtml += ''; - } else { - payloadHtml += exprName(value, opts); - } - - } - else if ("declRef" in value) { - if (opts.wantHtml) { - payloadHtml += ''; - payloadHtml += - '' - + exprName(value, opts) + ''; - payloadHtml += ''; - } else { - payloadHtml += exprName(value, opts); - } - - } - else if ("call" in value) { - if (opts.wantHtml) { - payloadHtml += ''; - payloadHtml += - '' - + exprName(value, opts) + ''; - payloadHtml += ''; - } else { - payloadHtml += exprName(value, opts); - } - } - else if ("refPath" in value) { - if (opts.wantHtml) { - payloadHtml += ''; - payloadHtml += - '' - + exprName(value, opts) + ''; - payloadHtml += ''; - } else { - payloadHtml += exprName(value, opts); - } - } else if ("type" in value) { - let name = exprName(value, { - wantHtml: false, - wantLink: false, - fnDecl: opts.fnDecl, - linkFnNameDecl: opts.linkFnNameDecl, - }); - payloadHtml += '' + name + ''; - } else if ("binOpIndex" in value) { - payloadHtml += exprName(value, opts); - }else if ("comptimeExpr" in value) { - let comptimeExpr = zigAnalysis.comptimeExprs[value.comptimeExpr].code; - if (opts.wantHtml) { - payloadHtml += '' + comptimeExpr + ''; - } else { - payloadHtml += comptimeExpr; - } - } else if (opts.wantHtml) { - payloadHtml += 'anytype'; - } else { - payloadHtml += 'anytype'; - } - } - } - - payloadHtml += ",
    " - payloadHtml += ') '; - - if (fnObj.has_align) { - let align = zigAnalysis.exprs[fnObj.align] - payloadHtml += "align(" + exprName(align, opts) + ") "; - } - if (fnObj.has_cc) { - let cc = zigAnalysis.exprs[fnObj.cc] - if (cc) { - payloadHtml += "callconv(." + cc.enumLiteral + ") "; - } - } - - if (fnObj.is_inferred_error) { - payloadHtml += "!"; - } - if (fnObj.ret != null) { - payloadHtml += exprName(fnObj.ret, opts); - } else if (opts.wantHtml) { - payloadHtml += 'anytype'; - } else { - payloadHtml += 'anytype'; - } - return payloadHtml; - } - // if (wantHtml) { - // return escapeHtml(typeObj.name); - // } else { - // return typeObj.name; - // } - } - } - - } - } - - - - function shouldSkipParamName(typeRef, paramName) { - let resolvedTypeRef = resolveValue({expr: typeRef}); - if ("type" in resolvedTypeRef) { - let typeObj = zigAnalysis.types[resolvedTypeRef.type]; - if (typeObj.kind === typeKinds.Pointer){ - let ptrObj = (typeObj); - if (getPtrSize(ptrObj) === pointerSizeEnum.One) { - const value = resolveValue(ptrObj.child); - return typeValueName(value, false, true).toLowerCase() === paramName; - } - } - } - return false; - } - - - function getPtrSize(typeObj) { - return (typeObj.size == null) ? pointerSizeEnum.One : typeObj.size; - } - - - function renderType(typeObj) { - let name; - if (rootIsStd && typeObj === zigAnalysis.types[zigAnalysis.packages[zigAnalysis.rootPkg].main]) { - name = "std"; - } else { - name = exprName({type:typeObj}, false, false); - } - if (name != null && name != "") { - domHdrName.innerText = name + " (" + zigAnalysis.typeKinds[typeObj.kind] + ")"; - domHdrName.classList.remove("hidden"); - } - if (typeObj.kind == typeKinds.ErrorSet) { - renderErrorSet((typeObj)); - } - } - - - function renderErrorSet(errSetType) { - if (errSetType.fields == null) { - domFnErrorsAnyError.classList.remove("hidden"); - } else { - let errorList = []; - for (let i = 0; i < errSetType.fields.length; i += 1) { - let errObj = errSetType.fields[i]; - //let srcObj = zigAnalysis.astNodes[errObj.src]; - errorList.push(errObj); - } - errorList.sort(function(a, b) { - return operatorCompare(a.name.toLowerCase(), b.name.toLowerCase()); - }); - - resizeDomListDl(domListFnErrors, errorList.length); - for (let i = 0; i < errorList.length; i += 1) { - let nameTdDom = domListFnErrors.children[i * 2 + 0]; - let descTdDom = domListFnErrors.children[i * 2 + 1]; - nameTdDom.textContent = errorList[i].name; - let docs = errorList[i].docs; - if (docs != null) { - descTdDom.innerHTML = markdown(docs); + payloadHtml += 'fn'; + if (opts.fnDecl) { + payloadHtml += ' '; + if (opts.linkFnNameDecl) { + payloadHtml += + '' + + escapeHtml(opts.fnDecl.name) + + ""; } else { - descTdDom.textContent = ""; + payloadHtml += escapeHtml(opts.fnDecl.name); } + payloadHtml += ""; + } + } else { + payloadHtml += "fn "; } - domTableFnErrors.classList.remove("hidden"); + payloadHtml += "("; + if (fnObj.params) { + let fields = null; + let isVarArgs = false; + let fnNode = zigAnalysis.astNodes[fnObj.src]; + fields = fnNode.fields; + isVarArgs = fnNode.varArgs; + + for (let i = 0; i < fnObj.params.length; i += 1) { + if (i != 0) { + payloadHtml += ", "; + } + + payloadHtml += + "
        
    "; + let value = fnObj.params[i]; + let paramValue = resolveValue({ expr: value }); + + if (fields != null) { + let paramNode = zigAnalysis.astNodes[fields[i]]; + + if (paramNode.varArgs) { + payloadHtml += "..."; + continue; + } + + if (paramNode.noalias) { + if (opts.wantHtml) { + payloadHtml += 'noalias '; + } else { + payloadHtml += "noalias "; + } + } + + if (paramNode.comptime) { + if (opts.wantHtml) { + payloadHtml += 'comptime '; + } else { + payloadHtml += "comptime "; + } + } + + let paramName = paramNode.name; + if (paramName != null) { + // skip if it matches the type name + if (!shouldSkipParamName(paramValue, paramName)) { + payloadHtml += paramName + ": "; + } + } + } + + if (isVarArgs && i === fnObj.params.length - 1) { + payloadHtml += "..."; + } else if ("alignOf" in value) { + if (opts.wantHtml) { + payloadHtml += ''; + payloadHtml += + '' + + exprName(value, opts) + + ""; + payloadHtml += ""; + } else { + payloadHtml += exprName(value, opts); + } + } else if ("typeOf" in value) { + if (opts.wantHtml) { + payloadHtml += ''; + payloadHtml += + '' + + exprName(value, opts) + + ""; + payloadHtml += ""; + } else { + payloadHtml += exprName(value, opts); + } + } else if ("typeOf_peer" in value) { + if (opts.wantHtml) { + payloadHtml += ''; + payloadHtml += + '' + + exprName(value, opts) + + ""; + payloadHtml += ""; + } else { + payloadHtml += exprName(value, opts); + } + } else if ("declRef" in value) { + if (opts.wantHtml) { + payloadHtml += ''; + payloadHtml += + '' + + exprName(value, opts) + + ""; + payloadHtml += ""; + } else { + payloadHtml += exprName(value, opts); + } + } else if ("call" in value) { + if (opts.wantHtml) { + payloadHtml += ''; + payloadHtml += + '' + + exprName(value, opts) + + ""; + payloadHtml += ""; + } else { + payloadHtml += exprName(value, opts); + } + } else if ("refPath" in value) { + if (opts.wantHtml) { + payloadHtml += ''; + payloadHtml += + '' + + exprName(value, opts) + + ""; + payloadHtml += ""; + } else { + payloadHtml += exprName(value, opts); + } + } else if ("type" in value) { + let name = exprName(value, { + wantHtml: false, + wantLink: false, + fnDecl: opts.fnDecl, + linkFnNameDecl: opts.linkFnNameDecl, + }); + payloadHtml += '' + name + ""; + } else if ("binOpIndex" in value) { + payloadHtml += exprName(value, opts); + } else if ("comptimeExpr" in value) { + let comptimeExpr = + zigAnalysis.comptimeExprs[value.comptimeExpr].code; + if (opts.wantHtml) { + payloadHtml += + '' + comptimeExpr + ""; + } else { + payloadHtml += comptimeExpr; + } + } else if (opts.wantHtml) { + payloadHtml += 'anytype'; + } else { + payloadHtml += "anytype"; + } + } + } + + payloadHtml += ",
    "; + payloadHtml += ") "; + + if (fnObj.has_align) { + let align = zigAnalysis.exprs[fnObj.align]; + payloadHtml += "align(" + exprName(align, opts) + ") "; + } + if (fnObj.has_cc) { + let cc = zigAnalysis.exprs[fnObj.cc]; + if (cc) { + payloadHtml += "callconv(." + cc.enumLiteral + ") "; + } + } + + if (fnObj.is_inferred_error) { + payloadHtml += "!"; + } + if (fnObj.ret != null) { + payloadHtml += exprName(fnObj.ret, opts); + } else if (opts.wantHtml) { + payloadHtml += 'anytype'; + } else { + payloadHtml += "anytype"; + } + return payloadHtml; + } + // if (wantHtml) { + // return escapeHtml(typeObj.name); + // } else { + // return typeObj.name; + // } } - domSectFnErrors.classList.remove("hidden"); + } + } + } + + function shouldSkipParamName(typeRef, paramName) { + let resolvedTypeRef = resolveValue({ expr: typeRef }); + if ("type" in resolvedTypeRef) { + let typeObj = zigAnalysis.types[resolvedTypeRef.type]; + if (typeObj.kind === typeKinds.Pointer) { + let ptrObj = typeObj; + if (getPtrSize(ptrObj) === pointerSizeEnum.One) { + const value = resolveValue(ptrObj.child); + return typeValueName(value, false, true).toLowerCase() === paramName; + } + } + } + return false; + } + + function getPtrSize(typeObj) { + return typeObj.size == null ? pointerSizeEnum.One : typeObj.size; + } + + function renderType(typeObj) { + let name; + if ( + rootIsStd && + typeObj === + zigAnalysis.types[zigAnalysis.packages[zigAnalysis.rootPkg].main] + ) { + name = "std"; + } else { + name = exprName({ type: typeObj }, false, false); + } + if (name != null && name != "") { + domHdrName.innerText = + name + " (" + zigAnalysis.typeKinds[typeObj.kind] + ")"; + domHdrName.classList.remove("hidden"); + } + if (typeObj.kind == typeKinds.ErrorSet) { + renderErrorSet(typeObj); + } + } + + function renderErrorSet(errSetType) { + if (errSetType.fields == null) { + domFnErrorsAnyError.classList.remove("hidden"); + } else { + let errorList = []; + for (let i = 0; i < errSetType.fields.length; i += 1) { + let errObj = errSetType.fields[i]; + //let srcObj = zigAnalysis.astNodes[errObj.src]; + errorList.push(errObj); + } + errorList.sort(function (a, b) { + return operatorCompare(a.name.toLowerCase(), b.name.toLowerCase()); + }); + + resizeDomListDl(domListFnErrors, errorList.length); + for (let i = 0; i < errorList.length; i += 1) { + let nameTdDom = domListFnErrors.children[i * 2 + 0]; + let descTdDom = domListFnErrors.children[i * 2 + 1]; + nameTdDom.textContent = errorList[i].name; + let docs = errorList[i].docs; + if (docs != null) { + descTdDom.innerHTML = markdown(docs); + } else { + descTdDom.textContent = ""; + } + } + domTableFnErrors.classList.remove("hidden"); + } + domSectFnErrors.classList.remove("hidden"); + } + + // function allCompTimeFnCallsHaveTypeResult(typeIndex, value) { + // let srcIndex = zigAnalysis.fns[value].src; + // let calls = nodesToCallsMap[srcIndex]; + // if (calls == null) return false; + // for (let i = 0; i < calls.length; i += 1) { + // let call = zigAnalysis.calls[calls[i]]; + // if (call.result.type !== typeTypeId) return false; + // } + // return true; + // } + // + // function allCompTimeFnCallsResult(calls) { + // let firstTypeObj = null; + // let containerObj = { + // privDecls: [], + // }; + // for (let callI = 0; callI < calls.length; callI += 1) { + // let call = zigAnalysis.calls[calls[callI]]; + // if (call.result.type !== typeTypeId) return null; + // let typeObj = zigAnalysis.types[call.result.value]; + // if (!typeKindIsContainer(typeObj.kind)) return null; + // if (firstTypeObj == null) { + // firstTypeObj = typeObj; + // containerObj.src = typeObj.src; + // } else if (firstTypeObj.src !== typeObj.src) { + // return null; + // } + // + // if (containerObj.fields == null) { + // containerObj.fields = (typeObj.fields || []).concat([]); + // } else for (let fieldI = 0; fieldI < typeObj.fields.length; fieldI += 1) { + // let prev = containerObj.fields[fieldI]; + // let next = typeObj.fields[fieldI]; + // if (prev === next) continue; + // if (typeof(prev) === 'object') { + // if (prev[next] == null) prev[next] = typeObj; + // } else { + // containerObj.fields[fieldI] = {}; + // containerObj.fields[fieldI][prev] = firstTypeObj; + // containerObj.fields[fieldI][next] = typeObj; + // } + // } + // + // if (containerObj.pubDecls == null) { + // containerObj.pubDecls = (typeObj.pubDecls || []).concat([]); + // } else for (let declI = 0; declI < typeObj.pubDecls.length; declI += 1) { + // let prev = containerObj.pubDecls[declI]; + // let next = typeObj.pubDecls[declI]; + // if (prev === next) continue; + // // TODO instead of showing "examples" as the public declarations, + // // do logic like this: + // //if (typeof(prev) !== 'object') { + // // let newDeclId = zigAnalysis.decls.length; + // // prev = clone(zigAnalysis.decls[prev]); + // // prev.id = newDeclId; + // // zigAnalysis.decls.push(prev); + // // containerObj.pubDecls[declI] = prev; + // //} + // //mergeDecls(prev, next, firstTypeObj, typeObj); + // } + // } + // for (let declI = 0; declI < containerObj.pubDecls.length; declI += 1) { + // let decl = containerObj.pubDecls[declI]; + // if (typeof(decl) === 'object') { + // containerObj.pubDecls[declI] = containerObj.pubDecls[declI].id; + // } + // } + // return containerObj; + // } + + function renderValue(decl) { + let resolvedValue = resolveValue(decl.value); + + if (resolvedValue.expr.fieldRef) { + const declRef = decl.value.expr.refPath[0].declRef; + const type = zigAnalysis.decls[declRef]; + domFnProtoCode.innerHTML = + 'const ' + + escapeHtml(decl.name) + + ": " + + type.name + + " = " + + exprName(decl.value.expr, { wantHtml: true, wantLink: true }) + + ";"; + } else if ( + resolvedValue.expr.string !== undefined || + resolvedValue.expr.call !== undefined || + resolvedValue.expr.comptimeExpr + ) { + domFnProtoCode.innerHTML = + 'const ' + + escapeHtml(decl.name) + + ": " + + exprName(resolvedValue.expr, { wantHtml: true, wantLink: true }) + + " = " + + exprName(decl.value.expr, { wantHtml: true, wantLink: true }) + + ";"; + } else if (resolvedValue.expr.compileError) { + domFnProtoCode.innerHTML = + 'const ' + + escapeHtml(decl.name) + + " = " + + exprName(decl.value.expr, { wantHtml: true, wantLink: true }) + + ";"; + } else { + domFnProtoCode.innerHTML = + 'const ' + + escapeHtml(decl.name) + + ": " + + exprName(resolvedValue.typeRef, { wantHtml: true, wantLink: true }) + + " = " + + exprName(decl.value.expr, { wantHtml: true, wantLink: true }) + + ";"; } -// function allCompTimeFnCallsHaveTypeResult(typeIndex, value) { -// let srcIndex = zigAnalysis.fns[value].src; -// let calls = nodesToCallsMap[srcIndex]; -// if (calls == null) return false; -// for (let i = 0; i < calls.length; i += 1) { -// let call = zigAnalysis.calls[calls[i]]; -// if (call.result.type !== typeTypeId) return false; -// } -// return true; -// } -// -// function allCompTimeFnCallsResult(calls) { -// let firstTypeObj = null; -// let containerObj = { -// privDecls: [], -// }; -// for (let callI = 0; callI < calls.length; callI += 1) { -// let call = zigAnalysis.calls[calls[callI]]; -// if (call.result.type !== typeTypeId) return null; -// let typeObj = zigAnalysis.types[call.result.value]; -// if (!typeKindIsContainer(typeObj.kind)) return null; -// if (firstTypeObj == null) { -// firstTypeObj = typeObj; -// containerObj.src = typeObj.src; -// } else if (firstTypeObj.src !== typeObj.src) { -// return null; -// } -// -// if (containerObj.fields == null) { -// containerObj.fields = (typeObj.fields || []).concat([]); -// } else for (let fieldI = 0; fieldI < typeObj.fields.length; fieldI += 1) { -// let prev = containerObj.fields[fieldI]; -// let next = typeObj.fields[fieldI]; -// if (prev === next) continue; -// if (typeof(prev) === 'object') { -// if (prev[next] == null) prev[next] = typeObj; -// } else { -// containerObj.fields[fieldI] = {}; -// containerObj.fields[fieldI][prev] = firstTypeObj; -// containerObj.fields[fieldI][next] = typeObj; -// } -// } -// -// if (containerObj.pubDecls == null) { -// containerObj.pubDecls = (typeObj.pubDecls || []).concat([]); -// } else for (let declI = 0; declI < typeObj.pubDecls.length; declI += 1) { -// let prev = containerObj.pubDecls[declI]; -// let next = typeObj.pubDecls[declI]; -// if (prev === next) continue; -// // TODO instead of showing "examples" as the public declarations, -// // do logic like this: -// //if (typeof(prev) !== 'object') { -// // let newDeclId = zigAnalysis.decls.length; -// // prev = clone(zigAnalysis.decls[prev]); -// // prev.id = newDeclId; -// // zigAnalysis.decls.push(prev); -// // containerObj.pubDecls[declI] = prev; -// //} -// //mergeDecls(prev, next, firstTypeObj, typeObj); -// } -// } -// for (let declI = 0; declI < containerObj.pubDecls.length; declI += 1) { -// let decl = containerObj.pubDecls[declI]; -// if (typeof(decl) === 'object') { -// containerObj.pubDecls[declI] = containerObj.pubDecls[declI].id; -// } -// } -// return containerObj; -// } + let docs = zigAnalysis.astNodes[decl.src].docs; + if (docs != null) { + domTldDocs.innerHTML = markdown(docs); + domTldDocs.classList.remove("hidden"); + } + domFnProto.classList.remove("hidden"); + } + function renderVar(decl) { + let declTypeRef = typeOfDecl(decl); + domFnProtoCode.innerHTML = + 'var ' + + escapeHtml(decl.name) + + ": " + + typeValueName(declTypeRef, true, true); - - function renderValue(decl) { - let resolvedValue = resolveValue(decl.value) + let docs = zigAnalysis.astNodes[decl.src].docs; + if (docs != null) { + domTldDocs.innerHTML = markdown(docs); + domTldDocs.classList.remove("hidden"); + } - if (resolvedValue.expr.fieldRef) { - const declRef = decl.value.expr.refPath[0].declRef; - const type = zigAnalysis.decls[declRef]; - domFnProtoCode.innerHTML = 'const ' + - escapeHtml(decl.name) + ': ' + type.name + - " = " + exprName(decl.value.expr, {wantHtml: true, wantLink:true}) + ";"; - } else if (resolvedValue.expr.string !== undefined || resolvedValue.expr.call !== undefined || resolvedValue.expr.comptimeExpr) { - domFnProtoCode.innerHTML = 'const ' + - escapeHtml(decl.name) + ': ' + exprName(resolvedValue.expr, {wantHtml: true, wantLink:true}) + - " = " + exprName(decl.value.expr, {wantHtml: true, wantLink:true}) + ";"; - } else if (resolvedValue.expr.compileError) { - domFnProtoCode.innerHTML = 'const ' + - escapeHtml(decl.name) + " = " + exprName(decl.value.expr, {wantHtml: true, wantLink:true}) + ";"; - } - else { - domFnProtoCode.innerHTML = 'const ' + - escapeHtml(decl.name) + ': ' + exprName(resolvedValue.typeRef, {wantHtml: true, wantLink:true}) + - " = " + exprName(decl.value.expr, {wantHtml: true, wantLink:true}) + ";"; + domFnProto.classList.remove("hidden"); + } + + function categorizeDecls( + decls, + typesList, + namespacesList, + errSetsList, + fnsList, + varsList, + valsList, + testsList + ) { + for (let i = 0; i < decls.length; i += 1) { + let decl = zigAnalysis.decls[decls[i]]; + let declValue = resolveValue(decl.value); + + if (decl.isTest) { + testsList.push(decl); + continue; } - let docs = zigAnalysis.astNodes[decl.src].docs; - if (docs != null) { - domTldDocs.innerHTML = markdown(docs); - domTldDocs.classList.remove("hidden"); - } + if (decl.kind === "var") { + varsList.push(decl); + continue; + } - domFnProto.classList.remove("hidden"); - } - - - function renderVar(decl) { - let declTypeRef = typeOfDecl(decl); - domFnProtoCode.innerHTML = 'var ' + - escapeHtml(decl.name) + ': ' + typeValueName(declTypeRef, true, true); - - let docs = zigAnalysis.astNodes[decl.src].docs; - if (docs != null) { - domTldDocs.innerHTML = markdown(docs); - domTldDocs.classList.remove("hidden"); - } - - domFnProto.classList.remove("hidden"); - } - - - - function categorizeDecls(decls, - typesList, namespacesList, errSetsList, - fnsList, varsList, valsList, testsList) { - - for (let i = 0; i < decls.length; i += 1) { - let decl = zigAnalysis.decls[decls[i]]; - let declValue = resolveValue(decl.value); - - if (decl.isTest) { - testsList.push(decl); - continue; - } - - if (decl.kind === 'var') { - varsList.push(decl); - continue; - } - - if (decl.kind === 'const') { - if ("type" in declValue.expr) { - // We have the actual type expression at hand. - const typeExpr = zigAnalysis.types[declValue.expr.type]; - if (typeExpr.kind == typeKinds.Fn) { - const funcRetExpr = resolveValue({ - expr: (typeExpr).ret - }); - if ("type" in funcRetExpr.expr && funcRetExpr.expr.type == typeTypeId) { - if (typeIsErrSet(declValue.expr.type)) { - errSetsList.push(decl); - } else if (typeIsStructWithNoFields(declValue.expr.type)) { - namespacesList.push(decl); - } else { - typesList.push(decl); - } - } else { - fnsList.push(decl); - } - } else { - if (typeIsErrSet(declValue.expr.type)) { - errSetsList.push(decl); - } else if (typeIsStructWithNoFields(declValue.expr.type)) { - namespacesList.push(decl); - } else { - typesList.push(decl); - } - } - } else if ("typeRef" in declValue) { - if ("type" in declValue.typeRef && declValue.typeRef == typeTypeId) { - // We don't know what the type expression is, but we know it's a type. - typesList.push(decl); - } else { - valsList.push(decl); - } - } else { - valsList.push(decl); - } - } - } - } - - - function renderContainer(container) { - - let typesList = []; - - let namespacesList = []; - - let errSetsList = []; - - let fnsList = []; - - let varsList = []; - - let valsList = []; - - let testsList = []; - - categorizeDecls(container.pubDecls, - typesList, namespacesList, errSetsList, - fnsList, varsList, valsList, testsList); - if (curNav.showPrivDecls) categorizeDecls(container.privDecls, - typesList, namespacesList, errSetsList, - fnsList, varsList, valsList, testsList); - - - typesList.sort(byNameProperty); - namespacesList.sort(byNameProperty); - errSetsList.sort(byNameProperty); - fnsList.sort(byNameProperty); - varsList.sort(byNameProperty); - valsList.sort(byNameProperty); - testsList.sort(byNameProperty); - - if (container.src != null) { - let docs = zigAnalysis.astNodes[container.src].docs; - if (docs != null) { - domTldDocs.innerHTML = markdown(docs); - domTldDocs.classList.remove("hidden"); - } - } - - if (typesList.length !== 0) { - window.x = typesList; - resizeDomList(domListTypes, typesList.length, '
  • '); - for (let i = 0; i < typesList.length; i += 1) { - let liDom = domListTypes.children[i]; - let aDom = liDom.children[0]; - let decl = typesList[i]; - aDom.textContent = decl.name; - aDom.setAttribute('href', navLinkDecl(decl.name)); - } - domSectTypes.classList.remove("hidden"); - } - if (namespacesList.length !== 0) { - resizeDomList(domListNamespaces, namespacesList.length, '
  • '); - for (let i = 0; i < namespacesList.length; i += 1) { - let liDom = domListNamespaces.children[i]; - let aDom = liDom.children[0]; - let decl = namespacesList[i]; - aDom.textContent = decl.name; - aDom.setAttribute('href', navLinkDecl(decl.name)); - } - domSectNamespaces.classList.remove("hidden"); - } - - if (errSetsList.length !== 0) { - resizeDomList(domListErrSets, errSetsList.length, '
  • '); - for (let i = 0; i < errSetsList.length; i += 1) { - let liDom = domListErrSets.children[i]; - let aDom = liDom.children[0]; - let decl = errSetsList[i]; - aDom.textContent = decl.name; - aDom.setAttribute('href', navLinkDecl(decl.name)); - } - domSectErrSets.classList.remove("hidden"); - } - - if (fnsList.length !== 0) { - resizeDomList(domListFns, fnsList.length, '
    '); - - for (let i = 0; i < fnsList.length; i += 1) { - let decl = fnsList[i]; - let trDom = domListFns.children[i]; - - let tdFnCode = trDom.children[0]; - let tdDesc = trDom.children[1]; - - let declType = resolveValue(decl.value); - console.assert("type" in declType.expr); - - tdFnCode.innerHTML = exprName(declType.expr,{ - wantHtml: true, - wantLink: true, - fnDecl: decl, - linkFnNameDecl: navLinkDecl(decl.name), - }); - - let docs = zigAnalysis.astNodes[decl.src].docs; - if (docs != null) { - tdDesc.innerHTML = shortDescMarkdown(docs); - } else { - tdDesc.textContent = ""; - } - } - domSectFns.classList.remove("hidden"); - } - - let containerNode = zigAnalysis.astNodes[container.src]; - if (containerNode.fields && containerNode.fields.length > 0) { - resizeDomList(domListFields, containerNode.fields.length, '
    '); - - for (let i = 0; i < containerNode.fields.length; i += 1) { - let fieldNode = zigAnalysis.astNodes[containerNode.fields[i]]; - let divDom = domListFields.children[i]; - let fieldName = (fieldNode.name); - let docs = fieldNode.docs; - let docsNonEmpty = docs != null && docs !== ""; - let extraPreClass = docsNonEmpty ? " fieldHasDocs" : ""; - - let html = '
    ' + escapeHtml(fieldName);
    -
    -                if (container.kind === typeKinds.Enum) {
    -                    html += ' = ' + fieldName + '';
    -                } else {
    -                    let fieldTypeExpr = container.fields[i];
    -                    html += ": ";
    -                    let name = exprName(fieldTypeExpr, false, false);
    -                    html += ''+ name +'';
    -                    let tsn = typeShorthandName(fieldTypeExpr);
    -                    if (tsn) {
    -                        html += ' ('+ tsn +')';
    -
    -                    }
    -                }
    -
    -                html += ',
    '; - - if (docsNonEmpty) { - html += '
    ' + markdown(docs) + '
    '; - } - divDom.innerHTML = html; - } - domSectFields.classList.remove("hidden"); - } - - if (varsList.length !== 0) { - resizeDomList(domListGlobalVars, varsList.length, - ''); - for (let i = 0; i < varsList.length; i += 1) { - let decl = varsList[i]; - let trDom = domListGlobalVars.children[i]; - - let tdName = trDom.children[0]; - let tdNameA = tdName.children[0]; - let tdType = trDom.children[1]; - let tdDesc = trDom.children[2]; - - tdNameA.setAttribute('href', navLinkDecl(decl.name)); - tdNameA.textContent = decl.name; - - tdType.innerHTML = typeValueName(typeOfDecl(decl), true, true); - - let docs = zigAnalysis.astNodes[decl.src].docs; - if (docs != null) { - tdDesc.innerHTML = shortDescMarkdown(docs); - } else { - tdDesc.textContent = ""; - } - } - domSectGlobalVars.classList.remove("hidden"); - } - - if (valsList.length !== 0) { - resizeDomList(domListValues, valsList.length, - ''); - for (let i = 0; i < valsList.length; i += 1) { - let decl = valsList[i]; - let trDom = domListValues.children[i]; - - let tdName = trDom.children[0]; - let tdNameA = tdName.children[0]; - let tdType = trDom.children[1]; - let tdDesc = trDom.children[2]; - - tdNameA.setAttribute('href', navLinkDecl(decl.name)); - tdNameA.textContent = decl.name; - - tdType.innerHTML = exprName(walkResultTypeRef(decl.value), - {wantHtml:true, wantLink:true}); - - let docs = zigAnalysis.astNodes[decl.src].docs; - if (docs != null) { - tdDesc.innerHTML = shortDescMarkdown(docs); - } else { - tdDesc.textContent = ""; - } - } - domSectValues.classList.remove("hidden"); - } - - if (testsList.length !== 0) { - resizeDomList(domListTests, testsList.length, - ''); - for (let i = 0; i < testsList.length; i += 1) { - let decl = testsList[i]; - let trDom = domListTests.children[i]; - - let tdName = trDom.children[0]; - let tdNameA = tdName.children[0]; - let tdType = trDom.children[1]; - let tdDesc = trDom.children[2]; - - tdNameA.setAttribute('href', navLinkDecl(decl.name)); - tdNameA.textContent = decl.name; - - tdType.innerHTML = exprName(walkResultTypeRef(decl.value), - {wantHtml:true, wantLink:true}); - - let docs = zigAnalysis.astNodes[decl.src].docs; - if (docs != null) { - tdDesc.innerHTML = shortDescMarkdown(docs); - } else { - tdDesc.textContent = ""; - } - } - domSectTests.classList.remove("hidden"); - } - } - - - - function operatorCompare(a, b) { - if (a === b) { - return 0; - } else if (a < b) { - return -1; - } else { - return 1; - } - } - - function detectRootIsStd() { - let rootPkg = zigAnalysis.packages[zigAnalysis.rootPkg]; - if (rootPkg.table["std"] == null) { - // no std mapped into the root package - return false; - } - let stdPkg = zigAnalysis.packages[rootPkg.table["std"]]; - if (stdPkg == null) return false; - return rootPkg.file === stdPkg.file; - } - - function indexTypeKinds() { - let map = ({}); - for (let i = 0; i < zigAnalysis.typeKinds.length; i += 1) { - map[zigAnalysis.typeKinds[i]] = i; - } - // This is just for debugging purposes, not needed to function - let assertList = ["Type","Void","Bool","NoReturn","Int","Float","Pointer","Array","Struct", - "ComptimeFloat","ComptimeInt","Undefined","Null","Optional","ErrorUnion","ErrorSet","Enum", - "Union","Fn","BoundFn","Opaque","Frame","AnyFrame","Vector","EnumLiteral"]; - for (let i = 0; i < assertList.length; i += 1) { - if (map[assertList[i]] == null) throw new Error("No type kind '" + assertList[i] + "' found"); - } - return map; - } - - function findTypeTypeId() { - for (let i = 0; i < zigAnalysis.types.length; i += 1) { - if (zigAnalysis.types[i].kind == typeKinds.Type) { - return i; - } - } - throw new Error("No type 'type' found"); - } - - function updateCurNav() { - - curNav = { - showPrivDecls: false, - pkgNames: [], - pkgObjs: [], - declNames: [], - declObjs: [], - callName: null, - }; - curNavSearch = ""; - - if (location.hash[0] === '#' && location.hash.length > 1) { - let query = location.hash.substring(1); - if (query[0] === '*') { - curNav.showPrivDecls = true; - query = query.substring(1); - } - - let qpos = query.indexOf("?"); - let nonSearchPart; - if (qpos === -1) { - nonSearchPart = query; + if (decl.kind === "const") { + if ("type" in declValue.expr) { + // We have the actual type expression at hand. + const typeExpr = zigAnalysis.types[declValue.expr.type]; + if (typeExpr.kind == typeKinds.Fn) { + const funcRetExpr = resolveValue({ + expr: typeExpr.ret, + }); + if ( + "type" in funcRetExpr.expr && + funcRetExpr.expr.type == typeTypeId + ) { + if (typeIsErrSet(declValue.expr.type)) { + errSetsList.push(decl); + } else if (typeIsStructWithNoFields(declValue.expr.type)) { + namespacesList.push(decl); + } else { + typesList.push(decl); + } } else { - nonSearchPart = query.substring(0, qpos); - curNavSearch = decodeURIComponent(query.substring(qpos + 1)); + fnsList.push(decl); } - - let parts = nonSearchPart.split(";"); - curNav.pkgNames = decodeURIComponent(parts[0]).split("."); - if (parts[1] != null) { - curNav.declNames = decodeURIComponent(parts[1]).split("."); - } - } - } - - function onHashChange() { - updateCurNav(); - if (domSearch.value !== curNavSearch) { - domSearch.value = curNavSearch; - } - render(); - if (imFeelingLucky) { - imFeelingLucky = false; - activateSelectedResult(); - } - } - - - function findSubDecl(parentType, childName) { - { - // Generic functions - if ("value" in parentType) { - const rv = resolveValue(parentType.value); - if ("type" in rv.expr) { - const t = zigAnalysis.types[rv.expr.type]; - if (t.kind == typeKinds.Fn && t.generic_ret != null) { - const rgr = resolveValue({expr: t.generic_ret}); - if ("type" in rgr.expr) { - parentType = zigAnalysis.types[rgr.expr.type]; - } - } - } - } - } - - - if (!parentType.pubDecls) return null; - for (let i = 0; i < parentType.pubDecls.length; i += 1) { - let declIndex = parentType.pubDecls[i]; - let childDecl = zigAnalysis.decls[declIndex]; - if (childDecl.name === childName) { - return childDecl; - } - } - if (!parentType.privDecls) return null; - for (let i = 0; i < parentType.privDecls.length; i += 1) { - let declIndex = parentType.privDecls[i]; - let childDecl = zigAnalysis.decls[declIndex]; - if (childDecl.name === childName) { - return childDecl; - } - } - return null; - } - - - - - function computeCanonicalPackagePaths() { - let list = new Array(zigAnalysis.packages.length); - // Now we try to find all the packages from root. - let rootPkg = zigAnalysis.packages[zigAnalysis.rootPkg]; - // Breadth-first to keep the path shortest possible. - let stack = [{ - path: ([]), - pkg: rootPkg, - }]; - while (stack.length !== 0) { - let item = (stack.shift()); - for (let key in item.pkg.table) { - let childPkgIndex = item.pkg.table[key]; - if (list[childPkgIndex] != null) continue; - let childPkg = zigAnalysis.packages[childPkgIndex]; - if (childPkg == null) continue; - - let newPath = item.path.concat([key]) - list[childPkgIndex] = newPath; - stack.push({ - path: newPath, - pkg: childPkg, - }); - } - } - return list; - } - - - - function computeCanonDeclPaths() { - let list = new Array(zigAnalysis.decls.length); - canonTypeDecls = new Array(zigAnalysis.types.length); - - for (let pkgI = 0; pkgI < zigAnalysis.packages.length; pkgI += 1) { - if (pkgI === zigAnalysis.rootPkg && rootIsStd) continue; - let pkg = zigAnalysis.packages[pkgI]; - let pkgNames = canonPkgPaths[pkgI]; - if (pkgNames === undefined) continue; - - let stack = [{ - declNames: ([]), - type: zigAnalysis.types[pkg.main], - }]; - while (stack.length !== 0) { - let item = (stack.shift()); - - if (isContainerType(item.type)) { - let t = (item.type); - - let len = t.pubDecls ? t.pubDecls.length : 0; - for (let declI = 0; declI < len; declI += 1) { - let mainDeclIndex = t.pubDecls[declI]; - if (list[mainDeclIndex] != null) continue; - - let decl = zigAnalysis.decls[mainDeclIndex]; - let declVal = resolveValue(decl.value); - let declNames = item.declNames.concat([decl.name]); - list[mainDeclIndex] = { - pkgNames: pkgNames, - declNames: declNames, - }; - if ("type" in declVal.expr) { - let value = zigAnalysis.types[declVal.expr.type]; - if (declCanRepresentTypeKind(value.kind)) - { - canonTypeDecls[declVal.type] = mainDeclIndex; - } - - if (isContainerType(value)) { - stack.push({ - declNames: declNames, - type:value, - }); - } - - - // Generic fun/ction - if (value.kind == typeKinds.Fn && value.generic_ret != null) { - let resolvedVal = resolveValue({ expr: value.generic_ret}); - if ("type" in resolvedVal.expr) { - let generic_type = zigAnalysis.types[resolvedVal.expr.type]; - if (isContainerType(generic_type)){ - stack.push({ - declNames: declNames, - type: generic_type, - }); - } - } - } - } - } - } - } - } - return list; - } - - - function getCanonDeclPath(index) { - if (canonDeclPaths == null) { - canonDeclPaths = computeCanonDeclPaths(); - } - //let cd = (canonDeclPaths); - return canonDeclPaths[index]; - } - - - function getCanonTypeDecl(index) { - getCanonDeclPath(0); - //let ct = (canonTypeDecls); - return canonTypeDecls[index]; - } - - - function escapeHtml(text) { - return text.replace(/[&"<>]/g, function (m) { - return escapeHtmlReplacements[m]; - }); - } - - - function shortDescMarkdown(docs) { - const trimmed_docs = docs.trim(); - let index = trimmed_docs.indexOf('.'); - if (index < 0) { - index = trimmed_docs.indexOf('\n'); - if (index < 0) { - index = trimmed_docs.length; + } else { + if (typeIsErrSet(declValue.expr.type)) { + errSetsList.push(decl); + } else if (typeIsStructWithNoFields(declValue.expr.type)) { + namespacesList.push(decl); + } else { + typesList.push(decl); } + } + } else if ("typeRef" in declValue) { + if ("type" in declValue.typeRef && declValue.typeRef == typeTypeId) { + // We don't know what the type expression is, but we know it's a type. + typesList.push(decl); + } else { + valsList.push(decl); + } } else { - index += 1; // include the period + valsList.push(decl); } - const slice = trimmed_docs.slice(0, index); - return markdown(slice); + } + } + } + + function renderContainer(container) { + let typesList = []; + + let namespacesList = []; + + let errSetsList = []; + + let fnsList = []; + + let varsList = []; + + let valsList = []; + + let testsList = []; + + categorizeDecls( + container.pubDecls, + typesList, + namespacesList, + errSetsList, + fnsList, + varsList, + valsList, + testsList + ); + if (curNav.showPrivDecls) + categorizeDecls( + container.privDecls, + typesList, + namespacesList, + errSetsList, + fnsList, + varsList, + valsList, + testsList + ); + + typesList.sort(byNameProperty); + namespacesList.sort(byNameProperty); + errSetsList.sort(byNameProperty); + fnsList.sort(byNameProperty); + varsList.sort(byNameProperty); + valsList.sort(byNameProperty); + testsList.sort(byNameProperty); + + if (container.src != null) { + let docs = zigAnalysis.astNodes[container.src].docs; + if (docs != null) { + domTldDocs.innerHTML = markdown(docs); + domTldDocs.classList.remove("hidden"); + } } + if (typesList.length !== 0) { + window.x = typesList; + resizeDomList( + domListTypes, + typesList.length, + '
  • ' + ); + for (let i = 0; i < typesList.length; i += 1) { + let liDom = domListTypes.children[i]; + let aDom = liDom.children[0]; + let decl = typesList[i]; + aDom.textContent = decl.name; + aDom.setAttribute("href", navLinkDecl(decl.name)); + } + domSectTypes.classList.remove("hidden"); + } + if (namespacesList.length !== 0) { + resizeDomList( + domListNamespaces, + namespacesList.length, + '
  • ' + ); + for (let i = 0; i < namespacesList.length; i += 1) { + let liDom = domListNamespaces.children[i]; + let aDom = liDom.children[0]; + let decl = namespacesList[i]; + aDom.textContent = decl.name; + aDom.setAttribute("href", navLinkDecl(decl.name)); + } + domSectNamespaces.classList.remove("hidden"); + } - function markdown(input) { - const raw_lines = input.split('\n'); // zig allows no '\r', so we don't need to split on CR - - const lines = []; + if (errSetsList.length !== 0) { + resizeDomList( + domListErrSets, + errSetsList.length, + '
  • ' + ); + for (let i = 0; i < errSetsList.length; i += 1) { + let liDom = domListErrSets.children[i]; + let aDom = liDom.children[0]; + let decl = errSetsList[i]; + aDom.textContent = decl.name; + aDom.setAttribute("href", navLinkDecl(decl.name)); + } + domSectErrSets.classList.remove("hidden"); + } - // PHASE 1: - // Dissect lines and determine the type for each line. - // Also computes indentation level and removes unnecessary whitespace + if (fnsList.length !== 0) { + resizeDomList( + domListFns, + fnsList.length, + "
    " + ); - let is_reading_code = false; - let code_indent = 0; - for (let line_no = 0; line_no < raw_lines.length; line_no++) { - const raw_line = raw_lines[line_no]; + for (let i = 0; i < fnsList.length; i += 1) { + let decl = fnsList[i]; + let trDom = domListFns.children[i]; - const line = { - indent: 0, - raw_text: raw_line, - text: raw_line.trim(), - type: "p", // p, h1 … h6, code, ul, ol, blockquote, skip, empty - ordered_number: -1, // NOTE: hack to make the type checker happy - }; + let tdFnCode = trDom.children[0]; + let tdDesc = trDom.children[1]; - if (!is_reading_code) { - while ((line.indent < line.raw_text.length) && line.raw_text[line.indent] == ' ') { - line.indent += 1; - } + let declType = resolveValue(decl.value); + console.assert("type" in declType.expr); - if (line.text.startsWith("######")) { - line.type = "h6"; - line.text = line.text.substr(6); - } - else if (line.text.startsWith("#####")) { - line.type = "h5"; - line.text = line.text.substr(5); - } - else if (line.text.startsWith("####")) { - line.type = "h4"; - line.text = line.text.substr(4); - } - else if (line.text.startsWith("###")) { - line.type = "h3"; - line.text = line.text.substr(3); - } - else if (line.text.startsWith("##")) { - line.type = "h2"; - line.text = line.text.substr(2); - } - else if (line.text.startsWith("#")) { - line.type = "h1"; - line.text = line.text.substr(1); - } - else if (line.text.startsWith("-")) { - line.type = "ul"; - line.text = line.text.substr(1); - } - else if (line.text.match(/^\d+\..*$/)) { // if line starts with {number}{dot} - const match = (line.text.match(/(\d+)\./)); - line.type = "ul"; - line.text = line.text.substr(match[0].length); - line.ordered_number = Number(match[1].length); - } - else if (line.text == "```") { - line.type = "skip"; - is_reading_code = true; - code_indent = line.indent; - } - else if (line.text == "") { - line.type = "empty"; - } - } - else { - if (line.text == "```") { - is_reading_code = false; - line.type = "skip"; - } else { - line.type = "code"; - line.text = line.raw_text.substr(code_indent); // remove the indent of the ``` from all the code block - } - } + tdFnCode.innerHTML = exprName(declType.expr, { + wantHtml: true, + wantLink: true, + fnDecl: decl, + linkFnNameDecl: navLinkDecl(decl.name), + }); - if (line.type != "skip") { - lines.push(line); - } + let docs = zigAnalysis.astNodes[decl.src].docs; + if (docs != null) { + tdDesc.innerHTML = shortDescMarkdown(docs); + } else { + tdDesc.textContent = ""; + } + } + domSectFns.classList.remove("hidden"); + } + + let containerNode = zigAnalysis.astNodes[container.src]; + if (containerNode.fields && containerNode.fields.length > 0) { + resizeDomList(domListFields, containerNode.fields.length, "
    "); + + for (let i = 0; i < containerNode.fields.length; i += 1) { + let fieldNode = zigAnalysis.astNodes[containerNode.fields[i]]; + let divDom = domListFields.children[i]; + let fieldName = fieldNode.name; + let docs = fieldNode.docs; + let docsNonEmpty = docs != null && docs !== ""; + let extraPreClass = docsNonEmpty ? " fieldHasDocs" : ""; + + let html = + '
    ' +
    +          escapeHtml(fieldName);
    +
    +        if (container.kind === typeKinds.Enum) {
    +          html += ' = ' + fieldName + "";
    +        } else {
    +          let fieldTypeExpr = container.fields[i];
    +          html += ": ";
    +          let name = exprName(fieldTypeExpr, false, false);
    +          html += '' + name + "";
    +          let tsn = typeShorthandName(fieldTypeExpr);
    +          if (tsn) {
    +            html += " (" + tsn + ")";
    +          }
             }
     
    -        // PHASE 2:
    -        // Render HTML from markdown lines.
    -            // Look at each line and emit fitting HTML code
    +        html += ",
    "; - - function markdownInlines(innerText) { + if (docsNonEmpty) { + html += '
    ' + markdown(docs) + "
    "; + } + divDom.innerHTML = html; + } + domSectFields.classList.remove("hidden"); + } - // inline types: - // **{INLINE}** : - // __{INLINE}__ : - // ~~{INLINE}~~ : - // *{INLINE}* : - // _{INLINE}_ : - // `{TEXT}` : - // [{INLINE}]({URL}) : - // ![{TEXT}]({URL}) : - // [[std;format.fmt]] : (inner link) + if (varsList.length !== 0) { + resizeDomList( + domListGlobalVars, + varsList.length, + '' + ); + for (let i = 0; i < varsList.length; i += 1) { + let decl = varsList[i]; + let trDom = domListGlobalVars.children[i]; - - - const formats = [ - { - marker: "**", - tag: "strong", - }, - { - marker: "~~", - tag: "s", - }, - { - marker: "__", - tag: "u", - }, - { - marker: "*", - tag: "em", - } - ]; + let tdName = trDom.children[0]; + let tdNameA = tdName.children[0]; + let tdType = trDom.children[1]; + let tdDesc = trDom.children[2]; - - const stack = []; + tdNameA.setAttribute("href", navLinkDecl(decl.name)); + tdNameA.textContent = decl.name; - let innerHTML = ""; - let currentRun = ""; + tdType.innerHTML = typeValueName(typeOfDecl(decl), true, true); - function flushRun() { - if (currentRun != "") { - innerHTML += escapeHtml(currentRun); - } - currentRun = ""; + let docs = zigAnalysis.astNodes[decl.src].docs; + if (docs != null) { + tdDesc.innerHTML = shortDescMarkdown(docs); + } else { + tdDesc.textContent = ""; + } + } + domSectGlobalVars.classList.remove("hidden"); + } + + if (valsList.length !== 0) { + resizeDomList( + domListValues, + valsList.length, + '' + ); + for (let i = 0; i < valsList.length; i += 1) { + let decl = valsList[i]; + let trDom = domListValues.children[i]; + + let tdName = trDom.children[0]; + let tdNameA = tdName.children[0]; + let tdType = trDom.children[1]; + let tdDesc = trDom.children[2]; + + tdNameA.setAttribute("href", navLinkDecl(decl.name)); + tdNameA.textContent = decl.name; + + tdType.innerHTML = exprName(walkResultTypeRef(decl.value), { + wantHtml: true, + wantLink: true, + }); + + let docs = zigAnalysis.astNodes[decl.src].docs; + if (docs != null) { + tdDesc.innerHTML = shortDescMarkdown(docs); + } else { + tdDesc.textContent = ""; + } + } + domSectValues.classList.remove("hidden"); + } + + if (testsList.length !== 0) { + resizeDomList( + domListTests, + testsList.length, + '' + ); + for (let i = 0; i < testsList.length; i += 1) { + let decl = testsList[i]; + let trDom = domListTests.children[i]; + + let tdName = trDom.children[0]; + let tdNameA = tdName.children[0]; + let tdType = trDom.children[1]; + let tdDesc = trDom.children[2]; + + tdNameA.setAttribute("href", navLinkDecl(decl.name)); + tdNameA.textContent = decl.name; + + tdType.innerHTML = exprName(walkResultTypeRef(decl.value), { + wantHtml: true, + wantLink: true, + }); + + let docs = zigAnalysis.astNodes[decl.src].docs; + if (docs != null) { + tdDesc.innerHTML = shortDescMarkdown(docs); + } else { + tdDesc.textContent = ""; + } + } + domSectTests.classList.remove("hidden"); + } + } + + function operatorCompare(a, b) { + if (a === b) { + return 0; + } else if (a < b) { + return -1; + } else { + return 1; + } + } + + function detectRootIsStd() { + let rootPkg = zigAnalysis.packages[zigAnalysis.rootPkg]; + if (rootPkg.table["std"] == null) { + // no std mapped into the root package + return false; + } + let stdPkg = zigAnalysis.packages[rootPkg.table["std"]]; + if (stdPkg == null) return false; + return rootPkg.file === stdPkg.file; + } + + function indexTypeKinds() { + let map = {}; + for (let i = 0; i < zigAnalysis.typeKinds.length; i += 1) { + map[zigAnalysis.typeKinds[i]] = i; + } + // This is just for debugging purposes, not needed to function + let assertList = [ + "Type", + "Void", + "Bool", + "NoReturn", + "Int", + "Float", + "Pointer", + "Array", + "Struct", + "ComptimeFloat", + "ComptimeInt", + "Undefined", + "Null", + "Optional", + "ErrorUnion", + "ErrorSet", + "Enum", + "Union", + "Fn", + "BoundFn", + "Opaque", + "Frame", + "AnyFrame", + "Vector", + "EnumLiteral", + ]; + for (let i = 0; i < assertList.length; i += 1) { + if (map[assertList[i]] == null) + throw new Error("No type kind '" + assertList[i] + "' found"); + } + return map; + } + + function findTypeTypeId() { + for (let i = 0; i < zigAnalysis.types.length; i += 1) { + if (zigAnalysis.types[i].kind == typeKinds.Type) { + return i; + } + } + throw new Error("No type 'type' found"); + } + + function updateCurNav() { + curNav = { + showPrivDecls: false, + pkgNames: [], + pkgObjs: [], + declNames: [], + declObjs: [], + callName: null, + }; + curNavSearch = ""; + + if (location.hash[0] === "#" && location.hash.length > 1) { + let query = location.hash.substring(1); + if (query[0] === "*") { + curNav.showPrivDecls = true; + query = query.substring(1); + } + + let qpos = query.indexOf("?"); + let nonSearchPart; + if (qpos === -1) { + nonSearchPart = query; + } else { + nonSearchPart = query.substring(0, qpos); + curNavSearch = decodeURIComponent(query.substring(qpos + 1)); + } + + let parts = nonSearchPart.split(";"); + curNav.pkgNames = decodeURIComponent(parts[0]).split("."); + if (parts[1] != null) { + curNav.declNames = decodeURIComponent(parts[1]).split("."); + } + } + } + + function onHashChange() { + updateCurNav(); + if (domSearch.value !== curNavSearch) { + domSearch.value = curNavSearch; + } + render(); + if (imFeelingLucky) { + imFeelingLucky = false; + activateSelectedResult(); + } + } + + function findSubDecl(parentType, childName) { + { + // Generic functions + if ("value" in parentType) { + const rv = resolveValue(parentType.value); + if ("type" in rv.expr) { + const t = zigAnalysis.types[rv.expr.type]; + if (t.kind == typeKinds.Fn && t.generic_ret != null) { + const rgr = resolveValue({ expr: t.generic_ret }); + if ("type" in rgr.expr) { + parentType = zigAnalysis.types[rgr.expr.type]; } + } + } + } + } - let parsing_code = false; - let codetag = ""; - let in_code = false; + if (!parentType.pubDecls) return null; + for (let i = 0; i < parentType.pubDecls.length; i += 1) { + let declIndex = parentType.pubDecls[i]; + let childDecl = zigAnalysis.decls[declIndex]; + if (childDecl.name === childName) { + return childDecl; + } + } + if (!parentType.privDecls) return null; + for (let i = 0; i < parentType.privDecls.length; i += 1) { + let declIndex = parentType.privDecls[i]; + let childDecl = zigAnalysis.decls[declIndex]; + if (childDecl.name === childName) { + return childDecl; + } + } + return null; + } - for (let i = 0; i < innerText.length; i++) { + function computeCanonicalPackagePaths() { + let list = new Array(zigAnalysis.packages.length); + // Now we try to find all the packages from root. + let rootPkg = zigAnalysis.packages[zigAnalysis.rootPkg]; + // Breadth-first to keep the path shortest possible. + let stack = [ + { + path: [], + pkg: rootPkg, + }, + ]; + while (stack.length !== 0) { + let item = stack.shift(); + for (let key in item.pkg.table) { + let childPkgIndex = item.pkg.table[key]; + if (list[childPkgIndex] != null) continue; + let childPkg = zigAnalysis.packages[childPkgIndex]; + if (childPkg == null) continue; - if (parsing_code && in_code) { - if (innerText.substr(i, codetag.length) == codetag) { - // remove leading and trailing whitespace if string both starts and ends with one. - if (currentRun[0] == " " && currentRun[currentRun.length - 1] == " ") { - currentRun = currentRun.substr(1, currentRun.length - 2); - } - flushRun(); - i += codetag.length - 1; - in_code = false; - parsing_code = false; - innerHTML += ""; - codetag = ""; - } else { - currentRun += innerText[i]; - } - continue; + let newPath = item.path.concat([key]); + list[childPkgIndex] = newPath; + stack.push({ + path: newPath, + pkg: childPkg, + }); + } + } + return list; + } + + function computeCanonDeclPaths() { + let list = new Array(zigAnalysis.decls.length); + canonTypeDecls = new Array(zigAnalysis.types.length); + + for (let pkgI = 0; pkgI < zigAnalysis.packages.length; pkgI += 1) { + if (pkgI === zigAnalysis.rootPkg && rootIsStd) continue; + let pkg = zigAnalysis.packages[pkgI]; + let pkgNames = canonPkgPaths[pkgI]; + if (pkgNames === undefined) continue; + + let stack = [ + { + declNames: [], + type: zigAnalysis.types[pkg.main], + }, + ]; + while (stack.length !== 0) { + let item = stack.shift(); + + if (isContainerType(item.type)) { + let t = item.type; + + let len = t.pubDecls ? t.pubDecls.length : 0; + for (let declI = 0; declI < len; declI += 1) { + let mainDeclIndex = t.pubDecls[declI]; + if (list[mainDeclIndex] != null) continue; + + let decl = zigAnalysis.decls[mainDeclIndex]; + let declVal = resolveValue(decl.value); + let declNames = item.declNames.concat([decl.name]); + list[mainDeclIndex] = { + pkgNames: pkgNames, + declNames: declNames, + }; + if ("type" in declVal.expr) { + let value = zigAnalysis.types[declVal.expr.type]; + if (declCanRepresentTypeKind(value.kind)) { + canonTypeDecls[declVal.type] = mainDeclIndex; + } + + if (isContainerType(value)) { + stack.push({ + declNames: declNames, + type: value, + }); + } + + // Generic fun/ction + if (value.kind == typeKinds.Fn && value.generic_ret != null) { + let resolvedVal = resolveValue({ expr: value.generic_ret }); + if ("type" in resolvedVal.expr) { + let generic_type = zigAnalysis.types[resolvedVal.expr.type]; + if (isContainerType(generic_type)) { + stack.push({ + declNames: declNames, + type: generic_type, + }); + } } + } + } + } + } + } + } + return list; + } - if (innerText[i] == "`") { - flushRun(); - if (!parsing_code) { - innerHTML += ""; - } - parsing_code = true; - codetag += "`"; - continue; - } + function getCanonDeclPath(index) { + if (canonDeclPaths == null) { + canonDeclPaths = computeCanonDeclPaths(); + } + //let cd = (canonDeclPaths); + return canonDeclPaths[index]; + } - if (parsing_code) { - currentRun += innerText[i]; - in_code = true; - } else { - let any = false; - for (let idx = (stack.length > 0 ? -1 : 0); idx < formats.length; idx++) { - const fmt = idx >= 0 ? formats[idx] : stack[stack.length - 1]; - if (innerText.substr(i, fmt.marker.length) == fmt.marker) { - flushRun(); - if (stack[stack.length - 1] == fmt) { - stack.pop(); - innerHTML += ""; - } else { - stack.push(fmt); - innerHTML += "<" + fmt.tag + ">"; - } - i += fmt.marker.length - 1; - any = true; - break; - } - } - if (!any) { - currentRun += innerText[i]; - } - } + function getCanonTypeDecl(index) { + getCanonDeclPath(0); + //let ct = (canonTypeDecls); + return canonTypeDecls[index]; + } + + function escapeHtml(text) { + return text.replace(/[&"<>]/g, function (m) { + return escapeHtmlReplacements[m]; + }); + } + + function shortDescMarkdown(docs) { + const trimmed_docs = docs.trim(); + let index = trimmed_docs.indexOf("."); + if (index < 0) { + index = trimmed_docs.indexOf("\n"); + if (index < 0) { + index = trimmed_docs.length; + } + } else { + index += 1; // include the period + } + const slice = trimmed_docs.slice(0, index); + return markdown(slice); + } + + function markdown(input) { + const raw_lines = input.split("\n"); // zig allows no '\r', so we don't need to split on CR + + const lines = []; + + // PHASE 1: + // Dissect lines and determine the type for each line. + // Also computes indentation level and removes unnecessary whitespace + + let is_reading_code = false; + let code_indent = 0; + for (let line_no = 0; line_no < raw_lines.length; line_no++) { + const raw_line = raw_lines[line_no]; + + const line = { + indent: 0, + raw_text: raw_line, + text: raw_line.trim(), + type: "p", // p, h1 … h6, code, ul, ol, blockquote, skip, empty + ordered_number: -1, // NOTE: hack to make the type checker happy + }; + + if (!is_reading_code) { + while ( + line.indent < line.raw_text.length && + line.raw_text[line.indent] == " " + ) { + line.indent += 1; + } + + if (line.text.startsWith("######")) { + line.type = "h6"; + line.text = line.text.substr(6); + } else if (line.text.startsWith("#####")) { + line.type = "h5"; + line.text = line.text.substr(5); + } else if (line.text.startsWith("####")) { + line.type = "h4"; + line.text = line.text.substr(4); + } else if (line.text.startsWith("###")) { + line.type = "h3"; + line.text = line.text.substr(3); + } else if (line.text.startsWith("##")) { + line.type = "h2"; + line.text = line.text.substr(2); + } else if (line.text.startsWith("#")) { + line.type = "h1"; + line.text = line.text.substr(1); + } else if (line.text.startsWith("-")) { + line.type = "ul"; + line.text = line.text.substr(1); + } else if (line.text.match(/^\d+\..*$/)) { + // if line starts with {number}{dot} + const match = line.text.match(/(\d+)\./); + line.type = "ul"; + line.text = line.text.substr(match[0].length); + line.ordered_number = Number(match[1].length); + } else if (line.text == "```") { + line.type = "skip"; + is_reading_code = true; + code_indent = line.indent; + } else if (line.text == "") { + line.type = "empty"; + } + } else { + if (line.text == "```") { + is_reading_code = false; + line.type = "skip"; + } else { + line.type = "code"; + line.text = line.raw_text.substr(code_indent); // remove the indent of the ``` from all the code block + } + } + + if (line.type != "skip") { + lines.push(line); + } + } + + // PHASE 2: + // Render HTML from markdown lines. + // Look at each line and emit fitting HTML code + + function markdownInlines(innerText) { + // inline types: + // **{INLINE}** : + // __{INLINE}__ : + // ~~{INLINE}~~ : + // *{INLINE}* : + // _{INLINE}_ : + // `{TEXT}` : + // [{INLINE}]({URL}) : + // ![{TEXT}]({URL}) : + // [[std;format.fmt]] : (inner link) + + const formats = [ + { + marker: "**", + tag: "strong", + }, + { + marker: "~~", + tag: "s", + }, + { + marker: "__", + tag: "u", + }, + { + marker: "*", + tag: "em", + }, + ]; + + const stack = []; + + let innerHTML = ""; + let currentRun = ""; + + function flushRun() { + if (currentRun != "") { + innerHTML += escapeHtml(currentRun); + } + currentRun = ""; + } + + let parsing_code = false; + let codetag = ""; + let in_code = false; + + for (let i = 0; i < innerText.length; i++) { + if (parsing_code && in_code) { + if (innerText.substr(i, codetag.length) == codetag) { + // remove leading and trailing whitespace if string both starts and ends with one. + if ( + currentRun[0] == " " && + currentRun[currentRun.length - 1] == " " + ) { + currentRun = currentRun.substr(1, currentRun.length - 2); } flushRun(); - - while (stack.length > 0) { - const fmt = (stack.pop()); - innerHTML += ""; - } - - return innerHTML; + i += codetag.length - 1; + in_code = false; + parsing_code = false; + innerHTML += ""; + codetag = ""; + } else { + currentRun += innerText[i]; + } + continue; } - - function previousLineIs(type, line_no) { - if (line_no > 0) { - return (lines[line_no - 1].type == type); - } else { - return false; - } + if (innerText[i] == "`") { + flushRun(); + if (!parsing_code) { + innerHTML += ""; + } + parsing_code = true; + codetag += "`"; + continue; } - - function nextLineIs(type, line_no) { - if (line_no < (lines.length - 1)) { - return (lines[line_no + 1].type == type); - } else { - return false; - } - } - - - function getPreviousLineIndent(line_no) { - if (line_no > 0) { - return lines[line_no - 1].indent; - } else { - return 0; - } - } - - - function getNextLineIndent(line_no) { - if (line_no < (lines.length - 1)) { - return lines[line_no + 1].indent; - } else { - return 0; - } - } - - let html = ""; - for (let line_no = 0; line_no < lines.length; line_no++) { - const line = lines[line_no]; - - - - switch (line.type) { - case "h1": - case "h2": - case "h3": - case "h4": - case "h5": - case "h6": - html += "<" + line.type + ">" + markdownInlines(line.text) + "\n"; - break; - - case "ul": - case "ol": - if (!previousLineIs("ul", line_no) || getPreviousLineIndent(line_no) < line.indent) { - html += "<" + line.type + ">\n"; - } - - html += "
  • " + markdownInlines(line.text) + "
  • \n"; - - if (!nextLineIs("ul", line_no) || getNextLineIndent(line_no) < line.indent) { - html += "\n"; - } - break; - - case "p": - if (!previousLineIs("p", line_no)) { - html += "

    \n"; - } - html += markdownInlines(line.text) + "\n"; - if (!nextLineIs("p", line_no)) { - html += "

    \n"; - } - break; - - case "code": - if (!previousLineIs("code", line_no)) { - html += "
    ";
    -                    }
    -                    html += escapeHtml(line.text) + "\n";
    -                    if (!nextLineIs("code", line_no)) {
    -                        html += "
    \n"; - } - break; - } - } - - return html; - } - - function activateSelectedResult() { - if (domSectSearchResults.classList.contains("hidden")) { - return; - } - - let liDom = domListSearchResults.children[curSearchIndex]; - if (liDom == null && domListSearchResults.children.length !== 0) { - liDom = domListSearchResults.children[0]; - } - if (liDom != null) { - let aDom = liDom.children[0]; - location.href = (aDom.getAttribute("href")); - curSearchIndex = -1; - } - domSearch.blur(); - } - - - function onSearchKeyDown(ev) { - switch (getKeyString(ev)) { - case "Enter": - // detect if this search changes anything - let terms1 = getSearchTerms(); - startSearch(); - updateCurNav(); - let terms2 = getSearchTerms(); - // we might have to wait for onHashChange to trigger - imFeelingLucky = (terms1.join(' ') !== terms2.join(' ')); - if (!imFeelingLucky) activateSelectedResult(); - - ev.preventDefault(); - ev.stopPropagation(); - return; - case "Esc": - domSearch.value = ""; - domSearch.blur(); - curSearchIndex = -1; - ev.preventDefault(); - ev.stopPropagation(); - startSearch(); - return; - case "Up": - moveSearchCursor(-1); - ev.preventDefault(); - ev.stopPropagation(); - return; - case "Down": - moveSearchCursor(1); - ev.preventDefault(); - ev.stopPropagation(); - return; - default: - if (ev.shiftKey || ev.ctrlKey || ev.altKey) return; - - curSearchIndex = -1; - ev.stopPropagation(); - startAsyncSearch(); - return; - } - } - - - - function moveSearchCursor(dir) { - if (curSearchIndex < 0 || curSearchIndex >= domListSearchResults.children.length) { - if (dir > 0) { - curSearchIndex = -1 + dir; - } else if (dir < 0) { - curSearchIndex = domListSearchResults.children.length + dir; - } + if (parsing_code) { + currentRun += innerText[i]; + in_code = true; } else { - curSearchIndex += dir; + let any = false; + for ( + let idx = stack.length > 0 ? -1 : 0; + idx < formats.length; + idx++ + ) { + const fmt = idx >= 0 ? formats[idx] : stack[stack.length - 1]; + if (innerText.substr(i, fmt.marker.length) == fmt.marker) { + flushRun(); + if (stack[stack.length - 1] == fmt) { + stack.pop(); + innerHTML += ""; + } else { + stack.push(fmt); + innerHTML += "<" + fmt.tag + ">"; + } + i += fmt.marker.length - 1; + any = true; + break; + } + } + if (!any) { + currentRun += innerText[i]; + } } - if (curSearchIndex < 0) { - curSearchIndex = 0; - } - if (curSearchIndex >= domListSearchResults.children.length) { - curSearchIndex = domListSearchResults.children.length - 1; - } - renderSearchCursor(); + } + flushRun(); + + while (stack.length > 0) { + const fmt = stack.pop(); + innerHTML += ""; + } + + return innerHTML; } - - function getKeyString(ev) { - let name; - let ignoreShift = false; - switch (ev.which) { - case 13: - name = "Enter"; - break; - case 27: - name = "Esc"; - break; - case 38: - name = "Up"; - break; - case 40: - name = "Down"; - break; - default: - ignoreShift = true; - name = (ev.key != null) ? ev.key : String.fromCharCode(ev.charCode || ev.keyCode); - } - if (!ignoreShift && ev.shiftKey) name = "Shift+" + name; - if (ev.altKey) name = "Alt+" + name; - if (ev.ctrlKey) name = "Ctrl+" + name; - return name; + function previousLineIs(type, line_no) { + if (line_no > 0) { + return lines[line_no - 1].type == type; + } else { + return false; + } } - - function onWindowKeyDown(ev) { - switch (getKeyString(ev)) { - case "Esc": - if (!domHelpModal.classList.contains("hidden")) { - domHelpModal.classList.add("hidden"); - ev.preventDefault(); - ev.stopPropagation(); - } - break; - case "s": - domSearch.focus(); - domSearch.select(); - ev.preventDefault(); - ev.stopPropagation(); - startAsyncSearch(); - break; - case "?": - ev.preventDefault(); - ev.stopPropagation(); - showHelpModal(); - break; - } + function nextLineIs(type, line_no) { + if (line_no < lines.length - 1) { + return lines[line_no + 1].type == type; + } else { + return false; + } } -function showHelpModal() { + function getPreviousLineIndent(line_no) { + if (line_no > 0) { + return lines[line_no - 1].indent; + } else { + return 0; + } + } + + function getNextLineIndent(line_no) { + if (line_no < lines.length - 1) { + return lines[line_no + 1].indent; + } else { + return 0; + } + } + + let html = ""; + for (let line_no = 0; line_no < lines.length; line_no++) { + const line = lines[line_no]; + + switch (line.type) { + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + html += + "<" + + line.type + + ">" + + markdownInlines(line.text) + + "\n"; + break; + + case "ul": + case "ol": + if ( + !previousLineIs("ul", line_no) || + getPreviousLineIndent(line_no) < line.indent + ) { + html += "<" + line.type + ">\n"; + } + + html += "
  • " + markdownInlines(line.text) + "
  • \n"; + + if ( + !nextLineIs("ul", line_no) || + getNextLineIndent(line_no) < line.indent + ) { + html += "\n"; + } + break; + + case "p": + if (!previousLineIs("p", line_no)) { + html += "

    \n"; + } + html += markdownInlines(line.text) + "\n"; + if (!nextLineIs("p", line_no)) { + html += "

    \n"; + } + break; + + case "code": + if (!previousLineIs("code", line_no)) { + html += "
    ";
    +          }
    +          html += escapeHtml(line.text) + "\n";
    +          if (!nextLineIs("code", line_no)) {
    +            html += "
    \n"; + } + break; + } + } + + return html; + } + + function activateSelectedResult() { + if (domSectSearchResults.classList.contains("hidden")) { + return; + } + + let liDom = domListSearchResults.children[curSearchIndex]; + if (liDom == null && domListSearchResults.children.length !== 0) { + liDom = domListSearchResults.children[0]; + } + if (liDom != null) { + let aDom = liDom.children[0]; + location.href = aDom.getAttribute("href"); + curSearchIndex = -1; + } + domSearch.blur(); + } + + function onSearchKeyDown(ev) { + switch (getKeyString(ev)) { + case "Enter": + // detect if this search changes anything + let terms1 = getSearchTerms(); + startSearch(); + updateCurNav(); + let terms2 = getSearchTerms(); + // we might have to wait for onHashChange to trigger + imFeelingLucky = terms1.join(" ") !== terms2.join(" "); + if (!imFeelingLucky) activateSelectedResult(); + + ev.preventDefault(); + ev.stopPropagation(); + return; + case "Esc": + domSearch.value = ""; + domSearch.blur(); + curSearchIndex = -1; + ev.preventDefault(); + ev.stopPropagation(); + startSearch(); + return; + case "Up": + moveSearchCursor(-1); + ev.preventDefault(); + ev.stopPropagation(); + return; + case "Down": + moveSearchCursor(1); + ev.preventDefault(); + ev.stopPropagation(); + return; + default: + if (ev.shiftKey || ev.ctrlKey || ev.altKey) return; + + curSearchIndex = -1; + ev.stopPropagation(); + startAsyncSearch(); + return; + } + } + + function moveSearchCursor(dir) { + if ( + curSearchIndex < 0 || + curSearchIndex >= domListSearchResults.children.length + ) { + if (dir > 0) { + curSearchIndex = -1 + dir; + } else if (dir < 0) { + curSearchIndex = domListSearchResults.children.length + dir; + } + } else { + curSearchIndex += dir; + } + if (curSearchIndex < 0) { + curSearchIndex = 0; + } + if (curSearchIndex >= domListSearchResults.children.length) { + curSearchIndex = domListSearchResults.children.length - 1; + } + renderSearchCursor(); + } + + function getKeyString(ev) { + let name; + let ignoreShift = false; + switch (ev.which) { + case 13: + name = "Enter"; + break; + case 27: + name = "Esc"; + break; + case 38: + name = "Up"; + break; + case 40: + name = "Down"; + break; + default: + ignoreShift = true; + name = + ev.key != null + ? ev.key + : String.fromCharCode(ev.charCode || ev.keyCode); + } + if (!ignoreShift && ev.shiftKey) name = "Shift+" + name; + if (ev.altKey) name = "Alt+" + name; + if (ev.ctrlKey) name = "Ctrl+" + name; + return name; + } + + function onWindowKeyDown(ev) { + switch (getKeyString(ev)) { + case "Esc": + if (!domHelpModal.classList.contains("hidden")) { + domHelpModal.classList.add("hidden"); + ev.preventDefault(); + ev.stopPropagation(); + } + break; + case "s": + domSearch.focus(); + domSearch.select(); + ev.preventDefault(); + ev.stopPropagation(); + startAsyncSearch(); + break; + case "?": + ev.preventDefault(); + ev.stopPropagation(); + showHelpModal(); + break; + } + } + + function showHelpModal() { domHelpModal.classList.remove("hidden"); - domHelpModal.style.left = (window.innerWidth / 2 - domHelpModal.clientWidth / 2) + "px"; - domHelpModal.style.top = (window.innerHeight / 2 - domHelpModal.clientHeight / 2) + "px"; + domHelpModal.style.left = + window.innerWidth / 2 - domHelpModal.clientWidth / 2 + "px"; + domHelpModal.style.top = + window.innerHeight / 2 - domHelpModal.clientHeight / 2 + "px"; domHelpModal.focus(); -} + } -function clearAsyncSearch() { + function clearAsyncSearch() { if (searchTimer != null) { - clearTimeout(searchTimer); - searchTimer = null; + clearTimeout(searchTimer); + searchTimer = null; } -} + } -function startAsyncSearch() { + function startAsyncSearch() { clearAsyncSearch(); searchTimer = setTimeout(startSearch, 100); -} -function startSearch() { + } + function startSearch() { clearAsyncSearch(); let oldHash = location.hash; let parts = oldHash.split("?"); - let newPart2 = (domSearch.value === "") ? "" : ("?" + domSearch.value); - location.hash = (parts.length === 1) ? (oldHash + newPart2) : (parts[0] + newPart2); -} -function getSearchTerms() { + let newPart2 = domSearch.value === "" ? "" : "?" + domSearch.value; + location.hash = + parts.length === 1 ? oldHash + newPart2 : parts[0] + newPart2; + } + function getSearchTerms() { let list = curNavSearch.trim().split(/[ \r\n\t]+/); list.sort(); return list; -} -function renderSearch() { + } + function renderSearch() { let matchedItems = []; - let ignoreCase = (curNavSearch.toLowerCase() === curNavSearch); + let ignoreCase = curNavSearch.toLowerCase() === curNavSearch; let terms = getSearchTerms(); - decl_loop: for (let declIndex = 0; declIndex < zigAnalysis.decls.length; declIndex += 1) { - let canonPath = getCanonDeclPath(declIndex); - if (canonPath == null) continue; + decl_loop: for ( + let declIndex = 0; + declIndex < zigAnalysis.decls.length; + declIndex += 1 + ) { + let canonPath = getCanonDeclPath(declIndex); + if (canonPath == null) continue; - let decl = zigAnalysis.decls[declIndex]; - let lastPkgName = canonPath.pkgNames[canonPath.pkgNames.length - 1]; - let fullPathSearchText = lastPkgName + "." + canonPath.declNames.join('.'); - let astNode = zigAnalysis.astNodes[decl.src]; - let fileAndDocs = "" //zigAnalysis.files[astNode.file]; - // TODO: understand what this piece of code is trying to achieve - // also right now `files` are expressed as a hashmap. - if (astNode.docs != null) { - fileAndDocs += "\n" + astNode.docs; + let decl = zigAnalysis.decls[declIndex]; + let lastPkgName = canonPath.pkgNames[canonPath.pkgNames.length - 1]; + let fullPathSearchText = + lastPkgName + "." + canonPath.declNames.join("."); + let astNode = zigAnalysis.astNodes[decl.src]; + let fileAndDocs = ""; //zigAnalysis.files[astNode.file]; + // TODO: understand what this piece of code is trying to achieve + // also right now `files` are expressed as a hashmap. + if (astNode.docs != null) { + fileAndDocs += "\n" + astNode.docs; + } + let fullPathSearchTextLower = fullPathSearchText; + if (ignoreCase) { + fullPathSearchTextLower = fullPathSearchTextLower.toLowerCase(); + fileAndDocs = fileAndDocs.toLowerCase(); + } + + let points = 0; + for (let termIndex = 0; termIndex < terms.length; termIndex += 1) { + let term = terms[termIndex]; + + // exact, case sensitive match of full decl path + if (fullPathSearchText === term) { + points += 4; + continue; } - let fullPathSearchTextLower = fullPathSearchText; - if (ignoreCase) { - fullPathSearchTextLower = fullPathSearchTextLower.toLowerCase(); - fileAndDocs = fileAndDocs.toLowerCase(); + // exact, case sensitive match of just decl name + if (decl.name == term) { + points += 3; + continue; + } + // substring, case insensitive match of full decl path + if (fullPathSearchTextLower.indexOf(term) >= 0) { + points += 2; + continue; + } + if (fileAndDocs.indexOf(term) >= 0) { + points += 1; + continue; } - let points = 0; - for (let termIndex = 0; termIndex < terms.length; termIndex += 1) { - let term = terms[termIndex]; + continue decl_loop; + } - // exact, case sensitive match of full decl path - if (fullPathSearchText === term) { - points += 4; - continue; - } - // exact, case sensitive match of just decl name - if (decl.name == term) { - points += 3; - continue; - } - // substring, case insensitive match of full decl path - if (fullPathSearchTextLower.indexOf(term) >= 0) { - points += 2; - continue; - } - if (fileAndDocs.indexOf(term) >= 0) { - points += 1; - continue; - } - - continue decl_loop; - } - - matchedItems.push({ - decl: decl, - path: canonPath, - points: points, - }); + matchedItems.push({ + decl: decl, + path: canonPath, + points: points, + }); } if (matchedItems.length !== 0) { - resizeDomList(domListSearchResults, matchedItems.length, '
  • '); + resizeDomList( + domListSearchResults, + matchedItems.length, + '
  • ' + ); - matchedItems.sort(function(a, b) { - let cmp = operatorCompare(b.points, a.points); - if (cmp != 0) return cmp; - return operatorCompare(a.decl.name, b.decl.name); - }); + matchedItems.sort(function (a, b) { + let cmp = operatorCompare(b.points, a.points); + if (cmp != 0) return cmp; + return operatorCompare(a.decl.name, b.decl.name); + }); - for (let i = 0; i < matchedItems.length; i += 1) { - let liDom = domListSearchResults.children[i]; - let aDom = liDom.children[0]; - let match = matchedItems[i]; - let lastPkgName = match.path.pkgNames[match.path.pkgNames.length - 1]; - aDom.textContent = lastPkgName + "." + match.path.declNames.join('.'); - aDom.setAttribute('href', navLink(match.path.pkgNames, match.path.declNames)); - } - renderSearchCursor(); + for (let i = 0; i < matchedItems.length; i += 1) { + let liDom = domListSearchResults.children[i]; + let aDom = liDom.children[0]; + let match = matchedItems[i]; + let lastPkgName = match.path.pkgNames[match.path.pkgNames.length - 1]; + aDom.textContent = lastPkgName + "." + match.path.declNames.join("."); + aDom.setAttribute( + "href", + navLink(match.path.pkgNames, match.path.declNames) + ); + } + renderSearchCursor(); - domSectSearchResults.classList.remove("hidden"); + domSectSearchResults.classList.remove("hidden"); } else { - domSectSearchNoResults.classList.remove("hidden"); + domSectSearchNoResults.classList.remove("hidden"); } -} + } -function renderSearchCursor() { + function renderSearchCursor() { for (let i = 0; i < domListSearchResults.children.length; i += 1) { - let liDom = (domListSearchResults.children[i]); - if (curSearchIndex === i) { - liDom.classList.add("selected"); - } else { - liDom.classList.remove("selected"); - } + let liDom = domListSearchResults.children[i]; + if (curSearchIndex === i) { + liDom.classList.add("selected"); + } else { + liDom.classList.remove("selected"); + } } -} + } + // function indexNodesToCalls() { + // let map = {}; + // for (let i = 0; i < zigAnalysis.calls.length; i += 1) { + // let call = zigAnalysis.calls[i]; + // let fn = zigAnalysis.fns[call.fn]; + // if (map[fn.src] == null) { + // map[fn.src] = [i]; + // } else { + // map[fn.src].push(i); + // } + // } + // return map; + // } - -// function indexNodesToCalls() { -// let map = {}; -// for (let i = 0; i < zigAnalysis.calls.length; i += 1) { -// let call = zigAnalysis.calls[i]; -// let fn = zigAnalysis.fns[call.fn]; -// if (map[fn.src] == null) { -// map[fn.src] = [i]; -// } else { -// map[fn.src].push(i); -// } -// } -// return map; -// } - - - -function byNameProperty(a, b) { + function byNameProperty(a, b) { return operatorCompare(a.name, b.name); -} - - - + } })(); From 7afe7de4f0949a8af0d89ccd4b26808a9c07cc5c Mon Sep 17 00:00:00 2001 From: Austin Rude Date: Fri, 5 Aug 2022 21:45:46 -0600 Subject: [PATCH 024/290] autodoc: only modify the DOM once to display the search results --- lib/docs/main.js | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/docs/main.js b/lib/docs/main.js index 41a03322d9..2b6417c015 100644 --- a/lib/docs/main.js +++ b/lib/docs/main.js @@ -3326,29 +3326,28 @@ var zigAnalysis; } if (matchedItems.length !== 0) { - resizeDomList( - domListSearchResults, - matchedItems.length, - '
  • ' - ); - matchedItems.sort(function (a, b) { let cmp = operatorCompare(b.points, a.points); if (cmp != 0) return cmp; return operatorCompare(a.decl.name, b.decl.name); }); + // Build up the list of search results + let matchedItemsHTML = ""; + for (let i = 0; i < matchedItems.length; i += 1) { - let liDom = domListSearchResults.children[i]; - let aDom = liDom.children[0]; - let match = matchedItems[i]; - let lastPkgName = match.path.pkgNames[match.path.pkgNames.length - 1]; - aDom.textContent = lastPkgName + "." + match.path.declNames.join("."); - aDom.setAttribute( - "href", - navLink(match.path.pkgNames, match.path.declNames) - ); + const match = matchedItems[i]; + const lastPkgName = match.path.pkgNames[match.path.pkgNames.length - 1]; + + const text = lastPkgName + "." + match.path.declNames.join("."); + const href = navLink(match.path.pkgNames, match.path.declNames); + + matchedItemsHTML += `
  • ${text}
  • `; } + + // Replace the search results using our newly constructed HTML string + domListSearchResults.innerHTML = matchedItemsHTML; + renderSearchCursor(); domSectSearchResults.classList.remove("hidden"); From 5c9826630dfb8a2f59663a569bb8e452359c9524 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 5 Aug 2022 17:10:01 -0700 Subject: [PATCH 025/290] Sema: elide safety of modulus and remainder division sometimes Piggybacking on 40f8f0134f5da9baaefd0fdab529d5585fa46199, remainder division, modulus, and `%` syntax no longer emit safety checks for a comptime-known denominator. --- src/Sema.zig | 573 +++++++++++------- test/cases/safety/modrem by zero.zig | 20 + test/cases/safety/modulus by zero.zig | 20 + ...ber.zig => remainder division by zero.zig} | 6 +- 4 files changed, 382 insertions(+), 237 deletions(-) create mode 100644 test/cases/safety/modrem by zero.zig create mode 100644 test/cases/safety/modulus by zero.zig rename test/cases/safety/{remainder division by negative number.zig => remainder division by zero.zig} (69%) diff --git a/src/Sema.zig b/src/Sema.zig index ad8879c599..76c077c103 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -876,9 +876,6 @@ fn analyzeBodyInner( .add => try sema.zirArithmetic(block, inst, .add), .addwrap => try sema.zirArithmetic(block, inst, .addwrap), .add_sat => try sema.zirArithmetic(block, inst, .add_sat), - .mod_rem => try sema.zirArithmetic(block, inst, .mod_rem), - .mod => try sema.zirArithmetic(block, inst, .mod), - .rem => try sema.zirArithmetic(block, inst, .rem), .mul => try sema.zirArithmetic(block, inst, .mul), .mulwrap => try sema.zirArithmetic(block, inst, .mulwrap), .mul_sat => try sema.zirArithmetic(block, inst, .mul_sat), @@ -891,6 +888,10 @@ fn analyzeBodyInner( .div_floor => try sema.zirDivFloor(block, inst), .div_trunc => try sema.zirDivTrunc(block, inst), + .mod_rem => try sema.zirModRem(block, inst), + .mod => try sema.zirMod(block, inst), + .rem => try sema.zirRem(block, inst), + .maximum => try sema.zirMinMax(block, inst, .max), .minimum => try sema.zirMinMax(block, inst, .min), @@ -11621,6 +11622,341 @@ fn airTag(block: *Block, is_int: bool, normal: Air.Inst.Tag, optimized: Air.Inst }; } +fn zirModRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node }; + const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const lhs = try sema.resolveInst(extra.lhs); + const rhs = try sema.resolveInst(extra.rhs); + const lhs_ty = sema.typeOf(lhs); + const rhs_ty = sema.typeOf(rhs); + const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(); + const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(); + try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); + try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .mod_rem); + + const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; + const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ + .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, + }); + + const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); + const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); + + const lhs_scalar_ty = lhs_ty.scalarType(); + const rhs_scalar_ty = rhs_ty.scalarType(); + const scalar_tag = resolved_type.scalarType().zigTypeTag(); + + const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; + + try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .mod_rem); + + const mod = sema.mod; + const target = mod.getTarget(); + const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs); + const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs); + + const runtime_src = rs: { + // For integers: + // Either operand being undef is a compile error because there exists + // a possible value (TODO what is it?) that would invoke illegal behavior. + // TODO: can lhs undef be handled better? + // + // For floats: + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined, result is undefined. + // + // For either one: if the result would be different between @mod and @rem, + // then emit a compile error saying you have to pick one. + if (is_int) { + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, lhs_src); + } + if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.addConstant(resolved_type, Value.zero); + } + } else if (lhs_scalar_ty.isSignedInt()) { + return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.failWithDivideByZero(block, rhs_src); + } + if (maybe_lhs_val) |lhs_val| { + const rem_result = try lhs_val.intRem(rhs_val, resolved_type, sema.arena, target); + // If this answer could possibly be different by doing `intMod`, + // we must emit a compile error. Otherwise, it's OK. + if ((try rhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) != (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) and + !(try rem_result.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) + { + const bad_src = if (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) + lhs_src + else + rhs_src; + return sema.failWithModRemNegative(block, bad_src, lhs_ty, rhs_ty); + } + if (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) { + // Negative + return sema.addConstant(resolved_type, Value.zero); + } + return sema.addConstant(resolved_type, rem_result); + } + break :rs lhs_src; + } else if (rhs_scalar_ty.isSignedInt()) { + return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); + } else { + break :rs rhs_src; + } + } + // float operands + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.failWithDivideByZero(block, rhs_src); + } + if (try rhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) { + return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef() or (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src)))) { + return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); + } + return sema.addConstant( + resolved_type, + try lhs_val.floatRem(rhs_val, resolved_type, sema.arena, target), + ); + } else { + return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); + } + } else { + return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); + } + }; + + try sema.requireRuntimeBlock(block, src, runtime_src); + + if (block.wantSafety()) { + try sema.addDivByZeroSafety(block, resolved_type, maybe_rhs_val, casted_rhs, is_int); + } + + const air_tag = airTag(block, is_int, .rem, .rem_optimized); + return block.addBinOp(air_tag, casted_lhs, casted_rhs); +} + +fn zirMod(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node }; + const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const lhs = try sema.resolveInst(extra.lhs); + const rhs = try sema.resolveInst(extra.rhs); + const lhs_ty = sema.typeOf(lhs); + const rhs_ty = sema.typeOf(rhs); + const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(); + const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(); + try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); + try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .mod); + + const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; + const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ + .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, + }); + + const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); + const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); + + const scalar_tag = resolved_type.scalarType().zigTypeTag(); + + const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; + + try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .mod); + + const mod = sema.mod; + const target = mod.getTarget(); + const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs); + const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs); + + const runtime_src = rs: { + // For integers: + // Either operand being undef is a compile error because there exists + // a possible value (TODO what is it?) that would invoke illegal behavior. + // TODO: can lhs zero be handled better? + // TODO: can lhs undef be handled better? + // + // For floats: + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined, result is undefined. + if (is_int) { + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, lhs_src); + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.failWithDivideByZero(block, rhs_src); + } + if (maybe_lhs_val) |lhs_val| { + return sema.addConstant( + resolved_type, + try lhs_val.intMod(rhs_val, resolved_type, sema.arena, target), + ); + } + break :rs lhs_src; + } else { + break :rs rhs_src; + } + } + // float operands + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.failWithDivideByZero(block, rhs_src); + } + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.addConstUndef(resolved_type); + } + if (maybe_rhs_val) |rhs_val| { + return sema.addConstant( + resolved_type, + try lhs_val.floatMod(rhs_val, resolved_type, sema.arena, target), + ); + } else break :rs rhs_src; + } else break :rs lhs_src; + }; + + try sema.requireRuntimeBlock(block, src, runtime_src); + + if (block.wantSafety()) { + try sema.addDivByZeroSafety(block, resolved_type, maybe_rhs_val, casted_rhs, is_int); + } + + const air_tag = airTag(block, is_int, .mod, .mod_optimized); + return block.addBinOp(air_tag, casted_lhs, casted_rhs); +} + +fn zirRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node }; + const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const lhs = try sema.resolveInst(extra.lhs); + const rhs = try sema.resolveInst(extra.rhs); + const lhs_ty = sema.typeOf(lhs); + const rhs_ty = sema.typeOf(rhs); + const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(); + const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(); + try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); + try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .rem); + + const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; + const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ + .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, + }); + + const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); + const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); + + const scalar_tag = resolved_type.scalarType().zigTypeTag(); + + const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; + + try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .rem); + + const mod = sema.mod; + const target = mod.getTarget(); + const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs); + const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs); + + const runtime_src = rs: { + // For integers: + // Either operand being undef is a compile error because there exists + // a possible value (TODO what is it?) that would invoke illegal behavior. + // TODO: can lhs zero be handled better? + // TODO: can lhs undef be handled better? + // + // For floats: + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined, result is undefined. + if (is_int) { + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, lhs_src); + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.failWithDivideByZero(block, rhs_src); + } + if (maybe_lhs_val) |lhs_val| { + return sema.addConstant( + resolved_type, + try lhs_val.intRem(rhs_val, resolved_type, sema.arena, target), + ); + } + break :rs lhs_src; + } else { + break :rs rhs_src; + } + } + // float operands + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.failWithDivideByZero(block, rhs_src); + } + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.addConstUndef(resolved_type); + } + if (maybe_rhs_val) |rhs_val| { + return sema.addConstant( + resolved_type, + try lhs_val.floatRem(rhs_val, resolved_type, sema.arena, target), + ); + } else break :rs rhs_src; + } else break :rs lhs_src; + }; + + try sema.requireRuntimeBlock(block, src, runtime_src); + + if (block.wantSafety()) { + try sema.addDivByZeroSafety(block, resolved_type, maybe_rhs_val, casted_rhs, is_int); + } + + const air_tag = airTag(block, is_int, .rem, .rem_optimized); + return block.addBinOp(air_tag, casted_lhs, casted_rhs); +} + fn zirOverflowArithmetic( sema: *Sema, block: *Block, @@ -11882,8 +12218,6 @@ fn analyzeArithmetic( const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); - const lhs_scalar_ty = lhs_ty.scalarType(); - const rhs_scalar_ty = rhs_ty.scalarType(); const scalar_tag = resolved_type.scalarType().zigTypeTag(); const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; @@ -12229,206 +12563,6 @@ fn analyzeArithmetic( } else break :rs .{ .src = lhs_src, .air_tag = .mul_sat }; } else break :rs .{ .src = rhs_src, .air_tag = .mul_sat }; }, - .mod_rem => { - // For integers: - // Either operand being undef is a compile error because there exists - // a possible value (TODO what is it?) that would invoke illegal behavior. - // TODO: can lhs undef be handled better? - // - // For floats: - // If the rhs is zero, compile error for division by zero. - // If the rhs is undefined, compile error because there is a possible - // value (zero) for which the division would be illegal behavior. - // If the lhs is undefined, result is undefined. - // - // For either one: if the result would be different between @mod and @rem, - // then emit a compile error saying you have to pick one. - if (is_int) { - if (maybe_lhs_val) |lhs_val| { - if (lhs_val.isUndef()) { - return sema.failWithUseOfUndef(block, lhs_src); - } - if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.addConstant(resolved_type, Value.zero); - } - } else if (lhs_scalar_ty.isSignedInt()) { - return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); - } - if (maybe_rhs_val) |rhs_val| { - if (rhs_val.isUndef()) { - return sema.failWithUseOfUndef(block, rhs_src); - } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.failWithDivideByZero(block, rhs_src); - } - if (maybe_lhs_val) |lhs_val| { - const rem_result = try lhs_val.intRem(rhs_val, resolved_type, sema.arena, target); - // If this answer could possibly be different by doing `intMod`, - // we must emit a compile error. Otherwise, it's OK. - if ((try rhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) != (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) and - !(try rem_result.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) - { - const bad_src = if (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) - lhs_src - else - rhs_src; - return sema.failWithModRemNegative(block, bad_src, lhs_ty, rhs_ty); - } - if (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) { - // Negative - return sema.addConstant(resolved_type, Value.zero); - } - return sema.addConstant(resolved_type, rem_result); - } - break :rs .{ .src = lhs_src, .air_tag = .rem }; - } else if (rhs_scalar_ty.isSignedInt()) { - return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); - } else { - break :rs .{ .src = rhs_src, .air_tag = .rem }; - } - } - // float operands - if (maybe_rhs_val) |rhs_val| { - if (rhs_val.isUndef()) { - return sema.failWithUseOfUndef(block, rhs_src); - } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.failWithDivideByZero(block, rhs_src); - } - if (try rhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) { - return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); - } - if (maybe_lhs_val) |lhs_val| { - if (lhs_val.isUndef() or (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src)))) { - return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); - } - return sema.addConstant( - resolved_type, - try lhs_val.floatRem(rhs_val, resolved_type, sema.arena, target), - ); - } else { - return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); - } - } else { - return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); - } - }, - .rem => { - // For integers: - // Either operand being undef is a compile error because there exists - // a possible value (TODO what is it?) that would invoke illegal behavior. - // TODO: can lhs zero be handled better? - // TODO: can lhs undef be handled better? - // - // For floats: - // If the rhs is zero, compile error for division by zero. - // If the rhs is undefined, compile error because there is a possible - // value (zero) for which the division would be illegal behavior. - // If the lhs is undefined, result is undefined. - if (is_int) { - if (maybe_lhs_val) |lhs_val| { - if (lhs_val.isUndef()) { - return sema.failWithUseOfUndef(block, lhs_src); - } - } - if (maybe_rhs_val) |rhs_val| { - if (rhs_val.isUndef()) { - return sema.failWithUseOfUndef(block, rhs_src); - } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.failWithDivideByZero(block, rhs_src); - } - if (maybe_lhs_val) |lhs_val| { - return sema.addConstant( - resolved_type, - try lhs_val.intRem(rhs_val, resolved_type, sema.arena, target), - ); - } - break :rs .{ .src = lhs_src, .air_tag = .rem }; - } else { - break :rs .{ .src = rhs_src, .air_tag = .rem }; - } - } - // float operands - if (maybe_rhs_val) |rhs_val| { - if (rhs_val.isUndef()) { - return sema.failWithUseOfUndef(block, rhs_src); - } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.failWithDivideByZero(block, rhs_src); - } - } - const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .rem_optimized else .rem; - if (maybe_lhs_val) |lhs_val| { - if (lhs_val.isUndef()) { - return sema.addConstUndef(resolved_type); - } - if (maybe_rhs_val) |rhs_val| { - return sema.addConstant( - resolved_type, - try lhs_val.floatRem(rhs_val, resolved_type, sema.arena, target), - ); - } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; - } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; - }, - .mod => { - // For integers: - // Either operand being undef is a compile error because there exists - // a possible value (TODO what is it?) that would invoke illegal behavior. - // TODO: can lhs zero be handled better? - // TODO: can lhs undef be handled better? - // - // For floats: - // If the rhs is zero, compile error for division by zero. - // If the rhs is undefined, compile error because there is a possible - // value (zero) for which the division would be illegal behavior. - // If the lhs is undefined, result is undefined. - if (is_int) { - if (maybe_lhs_val) |lhs_val| { - if (lhs_val.isUndef()) { - return sema.failWithUseOfUndef(block, lhs_src); - } - } - if (maybe_rhs_val) |rhs_val| { - if (rhs_val.isUndef()) { - return sema.failWithUseOfUndef(block, rhs_src); - } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.failWithDivideByZero(block, rhs_src); - } - if (maybe_lhs_val) |lhs_val| { - return sema.addConstant( - resolved_type, - try lhs_val.intMod(rhs_val, resolved_type, sema.arena, target), - ); - } - break :rs .{ .src = lhs_src, .air_tag = .mod }; - } else { - break :rs .{ .src = rhs_src, .air_tag = .mod }; - } - } - // float operands - if (maybe_rhs_val) |rhs_val| { - if (rhs_val.isUndef()) { - return sema.failWithUseOfUndef(block, rhs_src); - } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.failWithDivideByZero(block, rhs_src); - } - } - const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .mod_optimized else .mod; - if (maybe_lhs_val) |lhs_val| { - if (lhs_val.isUndef()) { - return sema.addConstUndef(resolved_type); - } - if (maybe_rhs_val) |rhs_val| { - return sema.addConstant( - resolved_type, - try lhs_val.floatMod(rhs_val, resolved_type, sema.arena, target), - ); - } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; - } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; - }, else => unreachable, } }; @@ -12472,33 +12606,6 @@ fn analyzeArithmetic( return sema.tupleFieldValByIndex(block, src, op_ov, 0, op_ov_tuple_ty); } } - switch (rs.air_tag) { - .rem, .mod, .rem_optimized, .mod_optimized => { - const ok = if (resolved_type.zigTypeTag() == .Vector) ok: { - const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); - const zero = try sema.addConstant(sema.typeOf(casted_rhs), zero_val); - const ok = try block.addCmpVector(casted_rhs, zero, if (scalar_tag == .Int) .gt else .neq, try sema.addType(resolved_type)); - break :ok try block.addInst(.{ - .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, - .data = .{ .reduce = .{ - .operand = ok, - .operation = .And, - } }, - }); - } else ok: { - const zero = try sema.addConstant(sema.typeOf(casted_rhs), Value.zero); - const air_tag = if (scalar_tag == .Int) - Air.Inst.Tag.cmp_gt - else if (block.float_mode == .Optimized) - Air.Inst.Tag.cmp_neq_optimized - else - Air.Inst.Tag.cmp_neq; - break :ok try block.addBinOp(air_tag, casted_rhs, zero); - }; - try sema.addSafetyCheck(block, ok, .remainder_division_zero_negative); - }, - else => {}, - } } return block.addBinOp(rs.air_tag, casted_lhs, casted_rhs); } @@ -19965,7 +20072,6 @@ pub const PanicId = enum { shl_overflow, shr_overflow, divide_by_zero, - remainder_division_zero_negative, exact_division_remainder, /// TODO make this call `std.builtin.panicInactiveUnionField`. inactive_union_field, @@ -20261,7 +20367,6 @@ fn safetyPanic( .shl_overflow => "left shift overflowed bits", .shr_overflow => "right shift overflowed bits", .divide_by_zero => "division by zero", - .remainder_division_zero_negative => "remainder division by zero or negative value", .exact_division_remainder => "exact division produced remainder", .inactive_union_field => "access of inactive union field", .integer_part_out_of_bounds => "integer part of floating point value out of bounds", diff --git a/test/cases/safety/modrem by zero.zig b/test/cases/safety/modrem by zero.zig new file mode 100644 index 0000000000..435570f2fb --- /dev/null +++ b/test/cases/safety/modrem by zero.zig @@ -0,0 +1,20 @@ +const std = @import("std"); + +pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { + _ = stack_trace; + if (std.mem.eql(u8, message, "division by zero")) { + std.process.exit(0); + } + std.process.exit(1); +} +pub fn main() !void { + const x = div0(999, 0); + _ = x; + return error.TestFailed; +} +fn div0(a: u32, b: u32) u32 { + return a / b; +} +// run +// backend=llvm +// target=native diff --git a/test/cases/safety/modulus by zero.zig b/test/cases/safety/modulus by zero.zig new file mode 100644 index 0000000000..9d57865a87 --- /dev/null +++ b/test/cases/safety/modulus by zero.zig @@ -0,0 +1,20 @@ +const std = @import("std"); + +pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { + _ = stack_trace; + if (std.mem.eql(u8, message, "division by zero")) { + std.process.exit(0); + } + std.process.exit(1); +} +pub fn main() !void { + const x = mod0(999, 0); + _ = x; + return error.TestFailed; +} +fn mod0(a: i32, b: i32) i32 { + return @mod(a, b); +} +// run +// backend=llvm +// target=native diff --git a/test/cases/safety/remainder division by negative number.zig b/test/cases/safety/remainder division by zero.zig similarity index 69% rename from test/cases/safety/remainder division by negative number.zig rename to test/cases/safety/remainder division by zero.zig index 2edbf4509c..71e295c4dd 100644 --- a/test/cases/safety/remainder division by negative number.zig +++ b/test/cases/safety/remainder division by zero.zig @@ -2,17 +2,17 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { _ = stack_trace; - if (std.mem.eql(u8, message, "remainder division by zero or negative value")) { + if (std.mem.eql(u8, message, "division by zero")) { std.process.exit(0); } std.process.exit(1); } pub fn main() !void { - const x = div0(999, -1); + const x = rem0(999, 0); _ = x; return error.TestFailed; } -fn div0(a: i32, b: i32) i32 { +fn rem0(a: i32, b: i32) i32 { return @rem(a, b); } // run From 86d9c3de2b6b43329c16678f1bd7eaddd3d218d7 Mon Sep 17 00:00:00 2001 From: Anton Lilja <12533691+antlilja@users.noreply.github.com> Date: Sat, 6 Aug 2022 13:17:09 +0200 Subject: [PATCH 026/290] Sema: fix infinite recursion in `explainWhyTypeIsComptime` Co-authored-by: Veikka Tuominen --- src/Sema.zig | 36 +++++++++++++++---- ..._known_struct_is_resolved_before_error.zig | 19 ++++++++++ ...f_referential_struct_requires_comptime.zig | 18 ++++++++++ ...lf_referential_union_requires_comptime.zig | 17 +++++++++ 4 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 test/cases/compile_errors/AstGen_comptime_known_struct_is_resolved_before_error.zig create mode 100644 test/cases/compile_errors/self_referential_struct_requires_comptime.zig create mode 100644 test/cases/compile_errors/self_referential_union_requires_comptime.zig diff --git a/src/Sema.zig b/src/Sema.zig index 76c077c103..8c2125026d 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -19764,6 +19764,8 @@ fn validateRunTimeType( }; } +const TypeSet = std.HashMapUnmanaged(Type, void, Type.HashContext64, std.hash_map.default_max_load_percentage); + fn explainWhyTypeIsComptime( sema: *Sema, block: *Block, @@ -19771,6 +19773,22 @@ fn explainWhyTypeIsComptime( msg: *Module.ErrorMsg, src_loc: Module.SrcLoc, ty: Type, +) CompileError!void { + var type_set = TypeSet{}; + defer type_set.deinit(sema.gpa); + + try sema.resolveTypeFully(block, src, ty); + return sema.explainWhyTypeIsComptimeInner(block, src, msg, src_loc, ty, &type_set); +} + +fn explainWhyTypeIsComptimeInner( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + msg: *Module.ErrorMsg, + src_loc: Module.SrcLoc, + ty: Type, + type_set: *TypeSet, ) CompileError!void { const mod = sema.mod; switch (ty.zigTypeTag()) { @@ -19808,7 +19826,7 @@ fn explainWhyTypeIsComptime( }, .Array, .Vector => { - try sema.explainWhyTypeIsComptime(block, src, msg, src_loc, ty.elemType()); + try sema.explainWhyTypeIsComptimeInner(block, src, msg, src_loc, ty.elemType(), type_set); }, .Pointer => { const elem_ty = ty.elemType2(); @@ -19826,18 +19844,20 @@ fn explainWhyTypeIsComptime( } return; } - try sema.explainWhyTypeIsComptime(block, src, msg, src_loc, ty.elemType()); + try sema.explainWhyTypeIsComptimeInner(block, src, msg, src_loc, ty.elemType(), type_set); }, .Optional => { var buf: Type.Payload.ElemType = undefined; - try sema.explainWhyTypeIsComptime(block, src, msg, src_loc, ty.optionalChild(&buf)); + try sema.explainWhyTypeIsComptimeInner(block, src, msg, src_loc, ty.optionalChild(&buf), type_set); }, .ErrorUnion => { - try sema.explainWhyTypeIsComptime(block, src, msg, src_loc, ty.errorUnionPayload()); + try sema.explainWhyTypeIsComptimeInner(block, src, msg, src_loc, ty.errorUnionPayload(), type_set); }, .Struct => { + if ((try type_set.getOrPutContext(sema.gpa, ty, .{ .mod = mod })).found_existing) return; + if (ty.castTag(.@"struct")) |payload| { const struct_obj = payload.data; for (struct_obj.fields.values()) |field, i| { @@ -19845,9 +19865,10 @@ fn explainWhyTypeIsComptime( .index = i, .range = .type, }); + if (try sema.typeRequiresComptime(block, src, field.ty)) { try mod.errNoteNonLazy(field_src_loc, msg, "struct requires comptime because of this field", .{}); - try sema.explainWhyTypeIsComptime(block, src, msg, field_src_loc, field.ty); + try sema.explainWhyTypeIsComptimeInner(block, src, msg, field_src_loc, field.ty, type_set); } } } @@ -19855,6 +19876,8 @@ fn explainWhyTypeIsComptime( }, .Union => { + if ((try type_set.getOrPutContext(sema.gpa, ty, .{ .mod = mod })).found_existing) return; + if (ty.cast(Type.Payload.Union)) |payload| { const union_obj = payload.data; for (union_obj.fields.values()) |field, i| { @@ -19862,9 +19885,10 @@ fn explainWhyTypeIsComptime( .index = i, .range = .type, }); + if (try sema.typeRequiresComptime(block, src, field.ty)) { try mod.errNoteNonLazy(field_src_loc, msg, "union requires comptime because of this field", .{}); - try sema.explainWhyTypeIsComptime(block, src, msg, field_src_loc, field.ty); + try sema.explainWhyTypeIsComptimeInner(block, src, msg, field_src_loc, field.ty, type_set); } } } diff --git a/test/cases/compile_errors/AstGen_comptime_known_struct_is_resolved_before_error.zig b/test/cases/compile_errors/AstGen_comptime_known_struct_is_resolved_before_error.zig new file mode 100644 index 0000000000..8e9358c6f4 --- /dev/null +++ b/test/cases/compile_errors/AstGen_comptime_known_struct_is_resolved_before_error.zig @@ -0,0 +1,19 @@ +const S1 = struct { + a: S2, +}; +const S2 = struct { + b: fn () void, +}; +pub export fn entry() void { + var s: S1 = undefined; + _ = s; +} + +// error +// backend=stage2 +// target=native +// +// :8:12: error: variable of type 'tmp.S1' must be const or comptime +// :2:8: note: struct requires comptime because of this field +// :5:8: note: struct requires comptime because of this field +// :5:8: note: use '*const fn() void' for a function pointer type diff --git a/test/cases/compile_errors/self_referential_struct_requires_comptime.zig b/test/cases/compile_errors/self_referential_struct_requires_comptime.zig new file mode 100644 index 0000000000..3ce7571026 --- /dev/null +++ b/test/cases/compile_errors/self_referential_struct_requires_comptime.zig @@ -0,0 +1,18 @@ +const S = struct { + a: fn () void, + b: *S, +}; +pub export fn entry() void { + var s: S = undefined; + _ = s; +} + + +// error +// backend=stage2 +// target=native +// +// :6:12: error: variable of type 'tmp.S' must be const or comptime +// :2:8: note: struct requires comptime because of this field +// :2:8: note: use '*const fn() void' for a function pointer type +// :3:8: note: struct requires comptime because of this field diff --git a/test/cases/compile_errors/self_referential_union_requires_comptime.zig b/test/cases/compile_errors/self_referential_union_requires_comptime.zig new file mode 100644 index 0000000000..a2433adde9 --- /dev/null +++ b/test/cases/compile_errors/self_referential_union_requires_comptime.zig @@ -0,0 +1,17 @@ +const U = union { + a: fn () void, + b: *U, +}; +pub export fn entry() void { + var u: U = undefined; + _ = u; +} + +// error +// backend=stage2 +// target=native +// +// :6:12: error: variable of type 'tmp.U' must be const or comptime +// :2:8: note: union requires comptime because of this field +// :2:8: note: use '*const fn() void' for a function pointer type +// :3:8: note: union requires comptime because of this field From 75275a1514b6954bed09c4c14a325e883a129c7b Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 6 Aug 2022 15:22:14 +0300 Subject: [PATCH 027/290] Sema: do not emit pointer safety checks for pointers to zero-bit types --- src/Sema.zig | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 56e08d081b..4c11894c24 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -16877,7 +16877,7 @@ fn zirIntToPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai } try sema.requireRuntimeBlock(block, src, operand_src); - if (block.wantSafety()) { + if (block.wantSafety() and try sema.typeHasRuntimeBits(block, sema.src, type_res.elemType2())) { if (!type_res.isAllowzeroPtr()) { const is_non_zero = try block.addBinOp(.cmp_neq, operand_coerced, .zero_usize); try sema.addSafetyCheck(block, is_non_zero, .cast_to_null); @@ -17169,7 +17169,9 @@ fn zirAlignCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A } try sema.requireRuntimeBlock(block, inst_data.src(), ptr_src); - if (block.wantSafety() and dest_align > 1) { + if (block.wantSafety() and dest_align > 1 and + try sema.typeHasRuntimeBits(block, sema.src, dest_ty.elemType2())) + { const val_payload = try sema.arena.create(Value.Payload.U64); val_payload.* = .{ .base = .{ .tag = .int_u64 }, @@ -24489,7 +24491,9 @@ fn coerceCompatiblePtrs( try sema.requireRuntimeBlock(block, inst_src, null); const inst_ty = sema.typeOf(inst); const inst_allows_zero = (inst_ty.zigTypeTag() == .Pointer and inst_ty.ptrAllowsZero()) or true; - if (block.wantSafety() and inst_allows_zero and !dest_ty.ptrAllowsZero()) { + if (block.wantSafety() and inst_allows_zero and !dest_ty.ptrAllowsZero() and + try sema.typeHasRuntimeBits(block, sema.src, dest_ty.elemType2())) + { const actual_ptr = if (inst_ty.isSlice()) try sema.analyzeSlicePtr(block, inst_src, inst, inst_ty) else From 4ef567ba41b7b9f83eb26d52128125b6837beb2f Mon Sep 17 00:00:00 2001 From: r00ster91 Date: Sat, 6 Aug 2022 15:01:55 +0200 Subject: [PATCH 028/290] feat: make help modal disappear if you click outside it --- lib/docs/main.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/docs/main.js b/lib/docs/main.js index 41a03322d9..2bb796f49b 100644 --- a/lib/docs/main.js +++ b/lib/docs/main.js @@ -132,6 +132,12 @@ var zigAnalysis; location.hash = "#root"; } + // make the modal disappear if you click outside it + domHelpModal.addEventListener("click", ev => { + if (ev.target.className == "help-modal") + domHelpModal.classList.add("hidden"); + }) + window.addEventListener("hashchange", onHashChange, false); window.addEventListener("keydown", onWindowKeyDown, false); onHashChange(); From 6354851909749d9741d520ead2d1f9e517df3548 Mon Sep 17 00:00:00 2001 From: r00ster91 Date: Sat, 6 Aug 2022 15:05:58 +0200 Subject: [PATCH 029/290] fix: "dialog" -> "modal" "Dialog" is the incorrect term here because a dialog is a separate window that still lets you use the app but a modal is a window where you can't continue using the app until you close it. --- lib/docs/index.html | 11 +++++------ lib/docs/main.js | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/docs/index.html b/lib/docs/index.html index dbcb54288a..5d6f381863 100644 --- a/lib/docs/index.html +++ b/lib/docs/index.html @@ -294,7 +294,6 @@ padding: 1px 1em; } - /* help dialog */ .help-modal { display: flex; width: 100%; @@ -308,7 +307,7 @@ backdrop-filter: blur(0.3em); } - .help-modal > .dialog { + .help-modal > .modal { max-width: 97vw; max-height: 97vh; overflow: auto; @@ -707,12 +706,12 @@
    - -
    +
    diff --git a/lib/docs/main.js b/lib/docs/main.js index 680fc1d962..e2b5162beb 100644 --- a/lib/docs/main.js +++ b/lib/docs/main.js @@ -41,7 +41,7 @@ var zigAnalysis; const domSearch = document.getElementById("search"); const domSectSearchResults = document.getElementById("sectSearchResults"); const domSectSearchAllResultsLink = document.getElementById("sectSearchAllResultsLink"); - + const domDocs = document.getElementById("docs"); const domListSearchResults = document.getElementById("listSearchResults"); const domSectSearchNoResults = document.getElementById("sectSearchNoResults"); const domSectInfo = document.getElementById("sectInfo"); @@ -3262,9 +3262,9 @@ var zigAnalysis; break; case "s": if (domHelpModal.classList.contains("hidden")) { - // TODO: scroll the page to the very top domSearch.focus(); domSearch.select(); + domDocs.scrollTo(0, 0); ev.preventDefault(); ev.stopPropagation(); startAsyncSearch(); From 2279f27e0f97e66aedc87ec5c85f05b185a3631d Mon Sep 17 00:00:00 2001 From: Yujiri Date: Fri, 12 Aug 2022 11:56:00 +0000 Subject: [PATCH 116/290] Fix #12423: auto_hash not hashing arrays of slices uniquely --- lib/std/hash/auto_hash.zig | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/lib/std/hash/auto_hash.zig b/lib/std/hash/auto_hash.zig index 951c0148d9..32f4f4378f 100644 --- a/lib/std/hash/auto_hash.zig +++ b/lib/std/hash/auto_hash.zig @@ -30,13 +30,15 @@ pub fn hashPointer(hasher: anytype, key: anytype, comptime strat: HashStrategy) .DeepRecursive => hash(hasher, key.*, .DeepRecursive), }, - .Slice => switch (strat) { - .Shallow => { - hashPointer(hasher, key.ptr, .Shallow); - hash(hasher, key.len, .Shallow); - }, - .Deep => hashArray(hasher, key, .Shallow), - .DeepRecursive => hashArray(hasher, key, .DeepRecursive), + .Slice => { + switch (strat) { + .Shallow => { + hashPointer(hasher, key.ptr, .Shallow); + }, + .Deep => hashArray(hasher, key, .Shallow), + .DeepRecursive => hashArray(hasher, key, .DeepRecursive), + } + hash(hasher, key.len, .Shallow); }, .Many, @@ -53,17 +55,8 @@ pub fn hashPointer(hasher: anytype, key: anytype, comptime strat: HashStrategy) /// Helper function to hash a set of contiguous objects, from an array or slice. pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { - switch (strat) { - .Shallow => { - for (key) |element| { - hash(hasher, element, .Shallow); - } - }, - else => { - for (key) |element| { - hash(hasher, element, strat); - } - }, + for (key) |element| { + hash(hasher, element, strat); } } @@ -359,6 +352,12 @@ test "testHash array" { try testing.expectEqual(h, hasher.final()); } +test "testHash multi-dimensional array" { + const a = [_][]const u32{ &.{ 1, 2, 3 }, &.{ 4, 5 } }; + const b = [_][]const u32{ &.{ 1, 2 }, &.{ 3, 4, 5 } }; + try testing.expect(testHash(a) != testHash(b)); +} + test "testHash struct" { const Foo = struct { a: u32 = 1, From 764cf4e53ffc29bbd39c636020019ae419135d82 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sat, 13 Aug 2022 22:08:40 -0700 Subject: [PATCH 117/290] std.fs: Fix `WalkerEntry.dir` not always being the containing dir Before this commit, the modified test would fail with `FileNotFound` because the `entry.dir` would be for the entry itself rather than the containing dir of the entry. That is, if you were walking a tree of `a/b`, then (previously) the entry for `b` would incorrectly have an `entry.dir` for `b` rather than `a`. --- lib/std/fs.zig | 6 ++++-- lib/std/fs/test.zig | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 0968e16812..b1e88d2e01 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -877,8 +877,9 @@ pub const IterableDir = struct { /// a reference to the path. pub fn next(self: *Walker) !?WalkerEntry { while (self.stack.items.len != 0) { - // `top` becomes invalid after appending to `self.stack` + // `top` and `containing` become invalid after appending to `self.stack` var top = &self.stack.items[self.stack.items.len - 1]; + var containing = top; var dirname_len = top.dirname_len; if (try top.iter.next()) |base| { self.name_buffer.shrinkRetainingCapacity(dirname_len); @@ -899,10 +900,11 @@ pub const IterableDir = struct { .dirname_len = self.name_buffer.items.len, }); top = &self.stack.items[self.stack.items.len - 1]; + containing = &self.stack.items[self.stack.items.len - 2]; } } return WalkerEntry{ - .dir = top.iter.dir, + .dir = containing.iter.dir, .basename = self.name_buffer.items[dirname_len..], .path = self.name_buffer.items, .kind = base.kind, diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 538ce1bf5e..a7686080c1 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1058,6 +1058,9 @@ test "walker" { std.debug.print("found unexpected path: {s}\n", .{std.fmt.fmtSliceEscapeLower(entry.path)}); return err; }; + // make sure that the entry.dir is the containing dir + var entry_dir = try entry.dir.openDir(entry.basename, .{}); + defer entry_dir.close(); num_walked += 1; } try testing.expectEqual(expected_paths.kvs.len, num_walked); From cb901e578cbc321003d5e4327d51d349d91f6dd6 Mon Sep 17 00:00:00 2001 From: LeRoyce Pearson Date: Mon, 15 Aug 2022 02:28:42 -0600 Subject: [PATCH 118/290] stage2: add compile errors for comptime `@shrExact` and `@divExact` failures --- src/Sema.zig | 12 +++++++++--- test/cases/compile_errors/exact division failure.zig | 10 ++++++++++ .../compile_errors/float exact division failure.zig | 10 ++++++++++ .../{stage1/obj => }/shrExact_shifts_out_1_bits.zig | 4 ++-- 4 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 test/cases/compile_errors/exact division failure.zig create mode 100644 test/cases/compile_errors/float exact division failure.zig rename test/cases/compile_errors/{stage1/obj => }/shrExact_shifts_out_1_bits.zig (58%) diff --git a/src/Sema.zig b/src/Sema.zig index d697ab0a99..12851cc9ad 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -10441,7 +10441,7 @@ fn zirShr( // Detect if any ones would be shifted out. const truncated = try lhs_val.intTruncBitsAsValue(lhs_ty, sema.arena, .unsigned, rhs_val, target); if (!(try truncated.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { - return sema.addConstUndef(lhs_ty); + return sema.fail(block, src, "exact shift shifted out 1 bits", .{}); } } const val = try lhs_val.shr(rhs_val, lhs_ty, sema.arena, target); @@ -11346,13 +11346,19 @@ fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai if (maybe_lhs_val) |lhs_val| { if (maybe_rhs_val) |rhs_val| { if (is_int) { - // TODO: emit compile error if there is a remainder + const modulus_val = try lhs_val.intMod(rhs_val, resolved_type, sema.arena, target); + if (modulus_val.compareWithZero(.neq)) { + return sema.fail(block, src, "exact division produced remainder", .{}); + } return sema.addConstant( resolved_type, try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target), ); } else { - // TODO: emit compile error if there is a remainder + const modulus_val = try lhs_val.floatMod(rhs_val, resolved_type, sema.arena, target); + if (modulus_val.compareWithZero(.neq)) { + return sema.fail(block, src, "exact division produced remainder", .{}); + } return sema.addConstant( resolved_type, try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target), diff --git a/test/cases/compile_errors/exact division failure.zig b/test/cases/compile_errors/exact division failure.zig new file mode 100644 index 0000000000..d134e0e2fb --- /dev/null +++ b/test/cases/compile_errors/exact division failure.zig @@ -0,0 +1,10 @@ +comptime { + const x = @divExact(10, 3); + _ = x; +} + +// error +// backend=llvm +// target=native +// +// :2:15: error: exact division produced remainder diff --git a/test/cases/compile_errors/float exact division failure.zig b/test/cases/compile_errors/float exact division failure.zig new file mode 100644 index 0000000000..c09defc56e --- /dev/null +++ b/test/cases/compile_errors/float exact division failure.zig @@ -0,0 +1,10 @@ +comptime { + const x = @divExact(10.0, 3.0); + _ = x; +} + +// error +// backend=llvm +// target=native +// +// :2:15: error: exact division produced remainder diff --git a/test/cases/compile_errors/stage1/obj/shrExact_shifts_out_1_bits.zig b/test/cases/compile_errors/shrExact_shifts_out_1_bits.zig similarity index 58% rename from test/cases/compile_errors/stage1/obj/shrExact_shifts_out_1_bits.zig rename to test/cases/compile_errors/shrExact_shifts_out_1_bits.zig index 223db76630..dd23c4bcb3 100644 --- a/test/cases/compile_errors/stage1/obj/shrExact_shifts_out_1_bits.zig +++ b/test/cases/compile_errors/shrExact_shifts_out_1_bits.zig @@ -4,7 +4,7 @@ comptime { } // error -// backend=stage1 +// backend=llvm // target=native // -// tmp.zig:2:15: error: exact shift shifted out 1 bits +// :2:15: error: exact shift shifted out 1 bits From a9c4dc84f487c4764c5787439ea87962f698d511 Mon Sep 17 00:00:00 2001 From: sin-ack Date: Fri, 12 Aug 2022 21:51:07 +0000 Subject: [PATCH 119/290] Sema: Revert sema.err to null if the Decl already has an error Previously we would assign the error message to Sema and then never clear it even when destroying the error message, which caused memory corruption. Closes #12437 --- src/Sema.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index ed8204b346..879ecb4e2f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1906,8 +1906,6 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError { } const mod = sema.mod; - sema.err = err_msg; - { errdefer err_msg.destroy(mod.gpa); if (err_msg.src_loc.lazy == .unneeded) { @@ -1925,8 +1923,10 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError { const gop = mod.failed_decls.getOrPutAssumeCapacity(sema.owner_decl_index); if (gop.found_existing) { // If there are multiple errors for the same Decl, prefer the first one added. + sema.err = null; err_msg.destroy(mod.gpa); } else { + sema.err = err_msg; gop.value_ptr.* = err_msg; } return error.AnalysisFail; From 95573dbeebf7a9d3617c1944ab0a74691b7e9d51 Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Mon, 15 Aug 2022 20:44:30 +0200 Subject: [PATCH 120/290] ci: add gzip compression to stdlib docs & langref --- ci/srht/update_download_page | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/ci/srht/update_download_page b/ci/srht/update_download_page index 5c007b6986..5277da7d2b 100755 --- a/ci/srht/update_download_page +++ b/ci/srht/update_download_page @@ -103,11 +103,19 @@ cd "$HOME" # Upload new stdlib autodocs mkdir -p docs_to_upload/documentation/master/std/ -cp "$ZIGDIR/docs/std/index.html" docs_to_upload/documentation/master/std/index.html -cp "$ZIGDIR/docs/std/data.js" docs_to_upload/documentation/master/std/data.js -cp "$ZIGDIR/docs/std/main.js" docs_to_upload/documentation/master/std/main.js -cp "$LANGREF" docs_to_upload/documentation/master/index.html -$S3CMD put -P --no-mime-magic --recursive --add-header="Cache-Control: max-age=0, must-revalidate" "docs_to_upload/" s3://ziglang.org/ + +gzip -c -9 "$ZIGDIR/docs/std/index.html" > docs_to_upload/documentation/master/std/index.html +gzip -c -9 "$ZIGDIR/docs/std/data.js" > docs_to_upload/documentation/master/std/data.js +gzip -c -9 "$ZIGDIR/docs/std/main.js" > docs_to_upload/documentation/master/std/main.js +gzip -c -9 "$LANGREF" > docs_to_upload/documentation/master/index.html +$S3CMD put -P --no-mime-magic --recursive --add-header="Content-Encoding:gzip" --add-header="Cache-Control: max-age=0, must-revalidate" "docs_to_upload/" s3://ziglang.org/ + +## Copy without compression: +# cp "$ZIGDIR/docs/std/index.html" docs_to_upload/documentation/master/std/index.html +# cp "$ZIGDIR/docs/std/data.js" docs_to_upload/documentation/master/std/data.js +# cp "$ZIGDIR/docs/std/main.js" docs_to_upload/documentation/master/std/main.js +# cp "$LANGREF" docs_to_upload/documentation/master/index.html +# $S3CMD put -P --no-mime-magic --recursive --add-header="Cache-Control: max-age=0, must-revalidate" "docs_to_upload/" s3://ziglang.org/ git clone --depth 1 git@github.com:ziglang/www.ziglang.org.git cd www.ziglang.org From b3922289be1ffaf194b55face332892280981356 Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Mon, 15 Aug 2022 21:56:59 +0200 Subject: [PATCH 121/290] Zir: add missing support for packed ints in declIterator --- src/Zir.zig | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Zir.zig b/src/Zir.zig index 538ef6aaf8..7a1db54ea2 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -3608,6 +3608,16 @@ pub fn declIterator(zir: Zir, decl_inst: u32) DeclIterator { break :decls_len decls_len; } else 0; + if (small.has_backing_int) { + const backing_int_body_len = zir.extra[extra_index]; + extra_index += 1; // backing_int_body_len + if (backing_int_body_len == 0) { + extra_index += 1; // backing_int_ref + } else { + extra_index += backing_int_body_len; // backing_int_body_inst + } + } + return declIteratorInner(zir, extra_index, decls_len); }, .enum_decl => { From f07cba10a3b8bfe5fa75da9f67284c9b2869b261 Mon Sep 17 00:00:00 2001 From: r00ster91 Date: Fri, 12 Aug 2022 14:23:57 +0200 Subject: [PATCH 122/290] test(names): remove unnecessary "tokenizer - " prefix --- lib/std/zig/tokenizer.zig | 60 +++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index 5c6d732419..178249091e 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -1485,7 +1485,7 @@ test "line comment followed by top-level comptime" { }); } -test "tokenizer - unknown length pointer and then c pointer" { +test "unknown length pointer and then c pointer" { try testTokenize( \\[*]u8 \\[*c]u8 @@ -1502,7 +1502,7 @@ test "tokenizer - unknown length pointer and then c pointer" { }); } -test "tokenizer - code point literal with hex escape" { +test "code point literal with hex escape" { try testTokenize( \\'\x1b' , &.{.char_literal}); @@ -1511,21 +1511,21 @@ test "tokenizer - code point literal with hex escape" { , &.{ .invalid, .invalid }); } -test "tokenizer - newline in char literal" { +test "newline in char literal" { try testTokenize( \\' \\' , &.{ .invalid, .invalid }); } -test "tokenizer - newline in string literal" { +test "newline in string literal" { try testTokenize( \\" \\" , &.{ .invalid, .string_literal }); } -test "tokenizer - code point literal with unicode escapes" { +test "code point literal with unicode escapes" { // Valid unicode escapes try testTokenize( \\'\u{3}' @@ -1575,13 +1575,13 @@ test "tokenizer - code point literal with unicode escapes" { , &.{ .invalid, .integer_literal, .invalid }); } -test "tokenizer - code point literal with unicode code point" { +test "code point literal with unicode code point" { try testTokenize( \\'💩' , &.{.char_literal}); } -test "tokenizer - float literal e exponent" { +test "float literal e exponent" { try testTokenize("a = 4.94065645841246544177e-324;\n", &.{ .identifier, .equal, @@ -1590,7 +1590,7 @@ test "tokenizer - float literal e exponent" { }); } -test "tokenizer - float literal p exponent" { +test "float literal p exponent" { try testTokenize("a = 0x1.a827999fcef32p+1022;\n", &.{ .identifier, .equal, @@ -1599,11 +1599,11 @@ test "tokenizer - float literal p exponent" { }); } -test "tokenizer - chars" { +test "chars" { try testTokenize("'c'", &.{.char_literal}); } -test "tokenizer - invalid token characters" { +test "invalid token characters" { try testTokenize("#", &.{.invalid}); try testTokenize("`", &.{.invalid}); try testTokenize("'c", &.{.invalid}); @@ -1611,7 +1611,7 @@ test "tokenizer - invalid token characters" { try testTokenize("''", &.{ .invalid, .invalid }); } -test "tokenizer - invalid literal/comment characters" { +test "invalid literal/comment characters" { try testTokenize("\"\x00\"", &.{ .string_literal, .invalid, @@ -1627,12 +1627,12 @@ test "tokenizer - invalid literal/comment characters" { }); } -test "tokenizer - utf8" { +test "utf8" { try testTokenize("//\xc2\x80", &.{}); try testTokenize("//\xf4\x8f\xbf\xbf", &.{}); } -test "tokenizer - invalid utf8" { +test "invalid utf8" { try testTokenize("//\x80", &.{ .invalid, }); @@ -1659,7 +1659,7 @@ test "tokenizer - invalid utf8" { }); } -test "tokenizer - illegal unicode codepoints" { +test "illegal unicode codepoints" { // unicode newline characters.U+0085, U+2028, U+2029 try testTokenize("//\xc2\x84", &.{}); try testTokenize("//\xc2\x85", &.{ @@ -1676,7 +1676,7 @@ test "tokenizer - illegal unicode codepoints" { try testTokenize("//\xe2\x80\xaa", &.{}); } -test "tokenizer - string identifier and builtin fns" { +test "string identifier and builtin fns" { try testTokenize( \\const @"if" = @import("std"); , &.{ @@ -1691,7 +1691,7 @@ test "tokenizer - string identifier and builtin fns" { }); } -test "tokenizer - multiline string literal with literal tab" { +test "multiline string literal with literal tab" { try testTokenize( \\\\foo bar , &.{ @@ -1699,7 +1699,7 @@ test "tokenizer - multiline string literal with literal tab" { }); } -test "tokenizer - comments with literal tab" { +test "comments with literal tab" { try testTokenize( \\//foo bar \\//!foo bar @@ -1715,14 +1715,14 @@ test "tokenizer - comments with literal tab" { }); } -test "tokenizer - pipe and then invalid" { +test "pipe and then invalid" { try testTokenize("||=", &.{ .pipe_pipe, .equal, }); } -test "tokenizer - line comment and doc comment" { +test "line comment and doc comment" { try testTokenize("//", &.{}); try testTokenize("// a / b", &.{}); try testTokenize("// /", &.{}); @@ -1733,7 +1733,7 @@ test "tokenizer - line comment and doc comment" { try testTokenize("//!!", &.{.container_doc_comment}); } -test "tokenizer - line comment followed by identifier" { +test "line comment followed by identifier" { try testTokenize( \\ Unexpected, \\ // another @@ -1746,7 +1746,7 @@ test "tokenizer - line comment followed by identifier" { }); } -test "tokenizer - UTF-8 BOM is recognized and skipped" { +test "UTF-8 BOM is recognized and skipped" { try testTokenize("\xEF\xBB\xBFa;\n", &.{ .identifier, .semicolon, @@ -1788,7 +1788,7 @@ test "correctly parse pointer dereference followed by asterisk" { }); } -test "tokenizer - range literals" { +test "range literals" { try testTokenize("0...9", &.{ .integer_literal, .ellipsis3, .integer_literal }); try testTokenize("'0'...'9'", &.{ .char_literal, .ellipsis3, .char_literal }); try testTokenize("0x00...0x09", &.{ .integer_literal, .ellipsis3, .integer_literal }); @@ -1796,7 +1796,7 @@ test "tokenizer - range literals" { try testTokenize("0o00...0o11", &.{ .integer_literal, .ellipsis3, .integer_literal }); } -test "tokenizer - number literals decimal" { +test "number literals decimal" { try testTokenize("0", &.{.integer_literal}); try testTokenize("1", &.{.integer_literal}); try testTokenize("2", &.{.integer_literal}); @@ -1863,7 +1863,7 @@ test "tokenizer - number literals decimal" { try testTokenize("1.0e0_+", &.{ .invalid, .plus }); } -test "tokenizer - number literals binary" { +test "number literals binary" { try testTokenize("0b0", &.{.integer_literal}); try testTokenize("0b1", &.{.integer_literal}); try testTokenize("0b2", &.{ .invalid, .integer_literal }); @@ -1902,7 +1902,7 @@ test "tokenizer - number literals binary" { try testTokenize("0b1_,", &.{ .invalid, .comma }); } -test "tokenizer - number literals octal" { +test "number literals octal" { try testTokenize("0o0", &.{.integer_literal}); try testTokenize("0o1", &.{.integer_literal}); try testTokenize("0o2", &.{.integer_literal}); @@ -1941,7 +1941,7 @@ test "tokenizer - number literals octal" { try testTokenize("0o_,", &.{ .invalid, .identifier, .comma }); } -test "tokenizer - number literals hexadecimal" { +test "number literals hexadecimal" { try testTokenize("0x0", &.{.integer_literal}); try testTokenize("0x1", &.{.integer_literal}); try testTokenize("0x2", &.{.integer_literal}); @@ -2029,22 +2029,22 @@ test "tokenizer - number literals hexadecimal" { try testTokenize("0x0.0p0_", &.{ .invalid, .eof }); } -test "tokenizer - multi line string literal with only 1 backslash" { +test "multi line string literal with only 1 backslash" { try testTokenize("x \\\n;", &.{ .identifier, .invalid, .semicolon }); } -test "tokenizer - invalid builtin identifiers" { +test "invalid builtin identifiers" { try testTokenize("@()", &.{ .invalid, .l_paren, .r_paren }); try testTokenize("@0()", &.{ .invalid, .integer_literal, .l_paren, .r_paren }); } -test "tokenizer - invalid token with unfinished escape right before eof" { +test "invalid token with unfinished escape right before eof" { try testTokenize("\"\\", &.{.invalid}); try testTokenize("'\\", &.{.invalid}); try testTokenize("'\\u", &.{.invalid}); } -test "tokenizer - saturating" { +test "saturating" { try testTokenize("<<", &.{.angle_bracket_angle_bracket_left}); try testTokenize("<<|", &.{.angle_bracket_angle_bracket_left_pipe}); try testTokenize("<<|=", &.{.angle_bracket_angle_bracket_left_pipe_equal}); From e3b3eab840d6fc055cdf12cde6d9077cb59b4022 Mon Sep 17 00:00:00 2001 From: r00ster91 Date: Fri, 12 Aug 2022 14:25:47 +0200 Subject: [PATCH 123/290] test(names): some renamings --- lib/std/zig/tokenizer.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index 178249091e..8cc221a8dc 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -1469,8 +1469,8 @@ pub const Tokenizer = struct { } }; -test "tokenizer" { - try testTokenize("test", &.{.keyword_test}); +test "keywords" { + try testTokenize("test const else", &.{ .keyword_test, .keyword_const, .keyword_else }); } test "line comment followed by top-level comptime" { @@ -2044,7 +2044,7 @@ test "invalid token with unfinished escape right before eof" { try testTokenize("'\\u", &.{.invalid}); } -test "saturating" { +test "saturating operators" { try testTokenize("<<", &.{.angle_bracket_angle_bracket_left}); try testTokenize("<<|", &.{.angle_bracket_angle_bracket_left_pipe}); try testTokenize("<<|=", &.{.angle_bracket_angle_bracket_left_pipe_equal}); From 5490688d658973982d111ad5ad52bc497ef15d84 Mon Sep 17 00:00:00 2001 From: r00ster91 Date: Fri, 12 Aug 2022 14:28:22 +0200 Subject: [PATCH 124/290] refactor: use std.ascii functions --- lib/std/zig/tokenizer.zig | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index 8cc221a8dc..89d4ee59d9 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -1,5 +1,4 @@ const std = @import("../std.zig"); -const mem = std.mem; pub const Token = struct { tag: Tag, @@ -350,7 +349,7 @@ pub const Tokenizer = struct { pub fn init(buffer: [:0]const u8) Tokenizer { // Skip the UTF-8 BOM if present - const src_start = if (mem.startsWith(u8, buffer, "\xEF\xBB\xBF")) 3 else @as(usize, 0); + const src_start: usize = if (std.mem.startsWith(u8, buffer, "\xEF\xBB\xBF")) 3 else 0; return Tokenizer{ .buffer = buffer, .index = src_start, @@ -1433,8 +1432,8 @@ pub const Tokenizer = struct { fn getInvalidCharacterLength(self: *Tokenizer) u3 { const c0 = self.buffer[self.index]; - if (c0 < 0x80) { - if (c0 < 0x20 or c0 == 0x7f) { + if (std.ascii.isASCII(c0)) { + if (std.ascii.isCntrl(c0)) { // ascii control codes are never allowed // (note that \n was checked before we got here) return 1; From 83909651ea99eb45c67ead40b5fcb5773d1998d5 Mon Sep 17 00:00:00 2001 From: r00ster91 Date: Sat, 13 Aug 2022 11:44:19 +0200 Subject: [PATCH 125/290] test: simplify testTokenize What this does is already done by `expectEqual`. Now the trace seems to be shorter and more concise so the errors should be easier to read now. --- lib/std/zig/tokenizer.zig | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index 89d4ee59d9..eaa0ddd716 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -2061,17 +2061,14 @@ test "saturating operators" { try testTokenize("-|=", &.{.minus_pipe_equal}); } -fn testTokenize(source: [:0]const u8, expected_tokens: []const Token.Tag) !void { +fn testTokenize(source: [:0]const u8, expected_token_tags: []const Token.Tag) !void { var tokenizer = Tokenizer.init(source); - for (expected_tokens) |expected_token_id| { + for (expected_token_tags) |expected_token_tag| { const token = tokenizer.next(); - if (token.tag != expected_token_id) { - std.debug.panic("expected {s}, found {s}\n", .{ - @tagName(expected_token_id), @tagName(token.tag), - }); - } + try std.testing.expectEqual(expected_token_tag, token.tag); } const last_token = tokenizer.next(); try std.testing.expectEqual(Token.Tag.eof, last_token.tag); try std.testing.expectEqual(source.len, last_token.loc.start); + try std.testing.expectEqual(source.len, last_token.loc.end); } From c17793b4875cc9e1ccb605431142ffecb0b6f3f2 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 16 Aug 2022 16:37:27 +0300 Subject: [PATCH 126/290] Sema: ignore current declaration in ambiguous reference error Closes #12429 --- src/Sema.zig | 11 +++++++++++ test/behavior/basic.zig | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/Sema.zig b/src/Sema.zig index 879ecb4e2f..d7d6994bcd 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5384,6 +5384,17 @@ fn lookupInNamespace( } } + { + var i: usize = 0; + while (i < candidates.items.len) { + if (candidates.items[i] == sema.owner_decl_index) { + _ = candidates.orderedRemove(i); + } else { + i += 1; + } + } + } + switch (candidates.items.len) { 0 => {}, 1 => { diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index 1a1412420a..4d8b176fbf 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -1104,3 +1104,24 @@ test "namespace lookup ignores decl causing the lookup" { }; _ = S.foo(); } + +test "ambiguous reference error ignores current declaration" { + const S = struct { + const foo = 666; + + const a = @This(); + const b = struct { + const foo = a.foo; + const bar = struct { + bar: u32 = b.foo, + }; + + comptime { + _ = b.foo; + } + }; + + usingnamespace b; + }; + try expect(S.b.foo == 666); +} From 0a0b3dda03fc26dd2b3fdef1c66401cc182f5409 Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Tue, 16 Aug 2022 16:19:54 +0200 Subject: [PATCH 127/290] autodoc: remove reference to github, replace with placeholder link --- lib/docs/main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/docs/main.js b/lib/docs/main.js index 717aeb4d7f..c08378f5f3 100644 --- a/lib/docs/main.js +++ b/lib/docs/main.js @@ -51,7 +51,7 @@ var zigAnalysis; const domHdrName = document.getElementById("hdrName"); const domHelpModal = document.getElementById("helpModal"); const domSearchPlaceholder = document.getElementById("searchPlaceholder"); - const sourceFileUrlTemplate = "https://github.com/ziglang/zig/blob/master/lib/std/{{file}}#L{{line}}" + const sourceFileUrlTemplate = "/src-viewer/{{file}}#L{{line}}" let searchTimer = null; let searchTrimResults = true; @@ -974,7 +974,7 @@ var zigAnalysis; "switch(" + cond + ") {" + - ' Date: Tue, 16 Aug 2022 16:51:32 +0200 Subject: [PATCH 128/290] autodoc: absolute line numbers in decl [src] links --- lib/docs/main.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/docs/main.js b/lib/docs/main.js index c08378f5f3..a5ddb3b6fb 100644 --- a/lib/docs/main.js +++ b/lib/docs/main.js @@ -53,6 +53,7 @@ var zigAnalysis; const domSearchPlaceholder = document.getElementById("searchPlaceholder"); const sourceFileUrlTemplate = "/src-viewer/{{file}}#L{{line}}" + let lineCounter = 1; let searchTimer = null; let searchTrimResults = true; @@ -404,6 +405,8 @@ var zigAnalysis; if (curNavSearch !== "") { return renderSearch(); } + + lineCounter = 1; let rootPkg = zigAnalysis.packages[zigAnalysis.rootPkg]; let pkg = rootPkg; @@ -434,6 +437,10 @@ var zigAnalysis; } currentType = childDecl; + if ("src" in currentType) { + const ast_node = zigAnalysis.astNodes[currentType.src]; + lineCounter += ast_node.line; + } curNav.declObjs.push(currentType); } @@ -2264,7 +2271,9 @@ var zigAnalysis; function renderSourceFileLink(decl) { let srcNode = zigAnalysis.astNodes[decl.src]; - return "[src]"; + return "[src]"; } function renderContainer(container) { From 9d4561ef0082728bbba085e8a4689ccc93a37b27 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 16 Aug 2022 16:55:49 +0300 Subject: [PATCH 129/290] AstGen: detect declarations shadowing locals Closes #9355 --- lib/std/bounded_array.zig | 14 +++---- lib/std/debug.zig | 4 +- src/AstGen.zig | 40 +++++++++++++++++++ .../compile_errors/decl_shadows_local.zig | 22 ++++++++++ 4 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 test/cases/compile_errors/decl_shadows_local.zig diff --git a/lib/std/bounded_array.zig b/lib/std/bounded_array.zig index 8134beb6e3..3d74e5e47f 100644 --- a/lib/std/bounded_array.zig +++ b/lib/std/bounded_array.zig @@ -15,16 +15,16 @@ const testing = std.testing; /// var slice = a.slice(); // a slice of the 64-byte array /// var a_clone = a; // creates a copy - the structure doesn't use any internal pointers /// ``` -pub fn BoundedArray(comptime T: type, comptime capacity: usize) type { +pub fn BoundedArray(comptime T: type, comptime buffer_capacity: usize) type { return struct { const Self = @This(); - buffer: [capacity]T = undefined, + buffer: [buffer_capacity]T = undefined, len: usize = 0, /// Set the actual length of the slice. /// Returns error.Overflow if it exceeds the length of the backing array. pub fn init(len: usize) error{Overflow}!Self { - if (len > capacity) return error.Overflow; + if (len > buffer_capacity) return error.Overflow; return Self{ .len = len }; } @@ -41,7 +41,7 @@ pub fn BoundedArray(comptime T: type, comptime capacity: usize) type { /// Adjust the slice's length to `len`. /// Does not initialize added items if any. pub fn resize(self: *Self, len: usize) error{Overflow}!void { - if (len > capacity) return error.Overflow; + if (len > buffer_capacity) return error.Overflow; self.len = len; } @@ -69,7 +69,7 @@ pub fn BoundedArray(comptime T: type, comptime capacity: usize) type { /// Check that the slice can hold at least `additional_count` items. pub fn ensureUnusedCapacity(self: Self, additional_count: usize) error{Overflow}!void { - if (self.len + additional_count > capacity) { + if (self.len + additional_count > buffer_capacity) { return error.Overflow; } } @@ -83,7 +83,7 @@ pub fn BoundedArray(comptime T: type, comptime capacity: usize) type { /// Increase length by 1, returning pointer to the new item. /// Asserts that there is space for the new item. pub fn addOneAssumeCapacity(self: *Self) *T { - assert(self.len < capacity); + assert(self.len < buffer_capacity); self.len += 1; return &self.slice()[self.len - 1]; } @@ -236,7 +236,7 @@ pub fn BoundedArray(comptime T: type, comptime capacity: usize) type { pub fn appendNTimesAssumeCapacity(self: *Self, value: T, n: usize) void { const old_len = self.len; self.len += n; - assert(self.len <= capacity); + assert(self.len <= buffer_capacity); mem.set(T, self.slice()[old_len..self.len], value); } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 0738b5af0b..33b8a98d7f 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1947,7 +1947,7 @@ noinline fn showMyTrace() usize { /// For more advanced usage, see `ConfigurableTrace`. pub const Trace = ConfigurableTrace(2, 4, builtin.mode == .Debug); -pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize, comptime enabled: bool) type { +pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize, comptime is_enabled: bool) type { return struct { addrs: [actual_size][stack_frame_count]usize = undefined, notes: [actual_size][]const u8 = undefined, @@ -1956,7 +1956,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize const actual_size = if (enabled) size else 0; const Index = if (enabled) usize else u0; - pub const enabled = enabled; + pub const enabled = is_enabled; pub const add = if (enabled) addNoInline else addNoOp; diff --git a/src/AstGen.zig b/src/AstGen.zig index 1151ed60da..2fcd8cd994 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -11751,6 +11751,46 @@ fn scanDecls(astgen: *AstGen, namespace: *Scope.Namespace, members: []const Ast. error.OutOfMemory => return error.OutOfMemory, } } + + // const index_name = try astgen.identAsString(index_token); + var s = namespace.parent; + while (true) switch (s.tag) { + .local_val => { + const local_val = s.cast(Scope.LocalVal).?; + if (local_val.name == name_str_index) { + return astgen.failTokNotes(name_token, "redeclaration of {s} '{s}'", .{ + @tagName(local_val.id_cat), token_bytes, + }, &[_]u32{ + try astgen.errNoteTok( + local_val.token_src, + "previous declaration here", + .{}, + ), + }); + } + s = local_val.parent; + }, + .local_ptr => { + const local_ptr = s.cast(Scope.LocalPtr).?; + if (local_ptr.name == name_str_index) { + return astgen.failTokNotes(name_token, "redeclaration of {s} '{s}'", .{ + @tagName(local_ptr.id_cat), token_bytes, + }, &[_]u32{ + try astgen.errNoteTok( + local_ptr.token_src, + "previous declaration here", + .{}, + ), + }); + } + s = local_ptr.parent; + }, + .namespace => s = s.cast(Scope.Namespace).?.parent, + .gen_zir => s = s.cast(GenZir).?.parent, + .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent, + .defer_gen => s = s.cast(Scope.DeferGen).?.parent, + .top => break, + }; gop.value_ptr.* = member_node; } return decl_count; diff --git a/test/cases/compile_errors/decl_shadows_local.zig b/test/cases/compile_errors/decl_shadows_local.zig new file mode 100644 index 0000000000..cb48cafa45 --- /dev/null +++ b/test/cases/compile_errors/decl_shadows_local.zig @@ -0,0 +1,22 @@ +fn foo(a: usize) void { + struct { + const a = 1; + }; +} +fn bar(a: usize) void { + struct { + const b = struct { + const a = 1; + }; + }; + _ = a; +} + +// error +// backend=stage2 +// target=native +// +// :3:15: error: redeclaration of function parameter 'a' +// :1:8: note: previous declaration here +// :9:19: error: redeclaration of function parameter 'a' +// :6:8: note: previous declaration here From d84282174c449325b4bc43fed4d65289445399d4 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 17 Aug 2022 09:00:23 +0200 Subject: [PATCH 130/290] link-test: move tls test to macho/tls This test was always really testing correct behavior of our in-house MachO linker to begin with. --- test/link.zig | 13 ++++--------- test/link/{ => macho}/tls/a.c | 0 test/link/{ => macho}/tls/build.zig | 1 + test/link/{ => macho}/tls/main.zig | 0 4 files changed, 5 insertions(+), 9 deletions(-) rename test/link/{ => macho}/tls/a.c (100%) rename test/link/{ => macho}/tls/build.zig (88%) rename test/link/{ => macho}/tls/main.zig (100%) diff --git a/test/link.zig b/test/link.zig index 01edd07261..a8a39a7018 100644 --- a/test/link.zig +++ b/test/link.zig @@ -3,11 +3,6 @@ const builtin = @import("builtin"); const tests = @import("tests.zig"); pub fn addCases(cases: *tests.StandaloneContext) void { - if (builtin.os.tag == .windows) { - // https://github.com/ziglang/zig/issues/12421 - return; - } - cases.addBuildFile("test/link/bss/build.zig", .{ .build_modes = false, // we only guarantee zerofill for undefined in Debug }); @@ -28,10 +23,6 @@ pub fn addCases(cases: *tests.StandaloneContext) void { .build_modes = true, }); - cases.addBuildFile("test/link/tls/build.zig", .{ - .build_modes = true, - }); - cases.addBuildFile("test/link/wasm/type/build.zig", .{ .build_modes = true, .requires_stage2 = true, @@ -115,4 +106,8 @@ pub fn addCases(cases: *tests.StandaloneContext) void { .build_modes = true, .requires_macos_sdk = true, }); + + cases.addBuildFile("test/link/macho/tls/build.zig", .{ + .build_modes = true, + }); } diff --git a/test/link/tls/a.c b/test/link/macho/tls/a.c similarity index 100% rename from test/link/tls/a.c rename to test/link/macho/tls/a.c diff --git a/test/link/tls/build.zig b/test/link/macho/tls/build.zig similarity index 88% rename from test/link/tls/build.zig rename to test/link/macho/tls/build.zig index ebf15ca439..7bf3ea1c9e 100644 --- a/test/link/tls/build.zig +++ b/test/link/macho/tls/build.zig @@ -2,6 +2,7 @@ const Builder = @import("std").build.Builder; pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const lib = b.addSharedLibrary("a", null, b.version(1, 0, 0)); lib.setBuildMode(mode); diff --git a/test/link/tls/main.zig b/test/link/macho/tls/main.zig similarity index 100% rename from test/link/tls/main.zig rename to test/link/macho/tls/main.zig From db0f372da8b25f4a911cd8e0b7f8e5cdfc64f940 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 17 Aug 2022 13:10:58 +0300 Subject: [PATCH 131/290] Sema: make optional noreturn behave correctly --- src/Sema.zig | 15 ++++++-- test/behavior/optional.zig | 36 +++++++++++++++++++ .../invalid_optional_payload_type.zig | 13 +++++++ 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 test/cases/compile_errors/invalid_optional_payload_type.zig diff --git a/src/Sema.zig b/src/Sema.zig index d7d6994bcd..ceeb8af23c 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6684,8 +6684,13 @@ fn zirOptionalType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const src = inst_data.src(); - const child_type = try sema.resolveType(block, src, inst_data.operand); + const operand_src: LazySrcLoc = .{ .node_offset_un_op = inst_data.src_node }; + const child_type = try sema.resolveType(block, operand_src, inst_data.operand); + if (child_type.zigTypeTag() == .Opaque) { + return sema.fail(block, operand_src, "opaque type '{}' cannot be optional", .{child_type.fmt(sema.mod)}); + } else if (child_type.zigTypeTag() == .Null) { + return sema.fail(block, operand_src, "type '{}' cannot be optional", .{child_type.fmt(sema.mod)}); + } const opt_type = try Type.optional(sema.arena, child_type); return sema.addType(opt_type); @@ -25714,6 +25719,12 @@ fn analyzeIsNull( return Air.Inst.Ref.bool_false; } } + + const operand_ty = sema.typeOf(operand); + var buf: Type.Payload.ElemType = undefined; + if (operand_ty.zigTypeTag() == .Optional and operand_ty.optionalChild(&buf).zigTypeTag() == .NoReturn) { + return Air.Inst.Ref.bool_true; + } try sema.requireRuntimeBlock(block, src, null); const air_tag: Air.Inst.Tag = if (invert_logic) .is_non_null else .is_null; return block.addUnOp(air_tag, operand); diff --git a/test/behavior/optional.zig b/test/behavior/optional.zig index 4e5eb5061c..c13a3b7e4f 100644 --- a/test/behavior/optional.zig +++ b/test/behavior/optional.zig @@ -369,3 +369,39 @@ test "optional pointer to zero bit error union payload" { some.foo(); } else |_| {} } + +const NoReturn = struct { + var a: u32 = undefined; + fn someData() bool { + a -= 1; + return a == 0; + } + fn loop() ?noreturn { + while (true) { + if (someData()) return null; + } + } + fn testOrelse() u32 { + loop() orelse return 123; + @compileError("bad"); + } +}; + +test "optional of noreturn used with if" { + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + + NoReturn.a = 64; + if (NoReturn.loop()) |_| { + @compileError("bad"); + } else { + try expect(true); + } +} + +test "optional of noreturn used with orelse" { + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + + NoReturn.a = 64; + const val = NoReturn.testOrelse(); + try expect(val == 123); +} diff --git a/test/cases/compile_errors/invalid_optional_payload_type.zig b/test/cases/compile_errors/invalid_optional_payload_type.zig new file mode 100644 index 0000000000..0058cd5e36 --- /dev/null +++ b/test/cases/compile_errors/invalid_optional_payload_type.zig @@ -0,0 +1,13 @@ +comptime { + _ = ?anyopaque; +} +comptime { + _ = ?@TypeOf(null); +} + +// error +// backend=stage2 +// target=native +// +// :2:10: error: opaque type 'anyopaque' cannot be optional +// :5:10: error: type '@TypeOf(null)' cannot be optional From b0a55e1b3be3a274546f9c18016e9609d546bdb0 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 17 Aug 2022 13:21:07 +0300 Subject: [PATCH 132/290] Sema: make noreturn error union behave correctly --- src/Sema.zig | 14 +++++ test/behavior/error.zig | 62 ++++++++++++++++++- .../invalid_error_union_payload_type.zig | 13 ++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 test/cases/compile_errors/invalid_error_union_payload_type.zig diff --git a/src/Sema.zig b/src/Sema.zig index ceeb8af23c..772abf5a84 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6789,6 +6789,15 @@ fn zirErrorUnionType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr error_set.fmt(sema.mod), }); } + if (payload.zigTypeTag() == .Opaque) { + return sema.fail(block, rhs_src, "error union with payload of opaque type '{}' not allowed", .{ + payload.fmt(sema.mod), + }); + } else if (payload.zigTypeTag() == .ErrorSet) { + return sema.fail(block, rhs_src, "error union with payload of error set type '{}' not allowed", .{ + payload.fmt(sema.mod), + }); + } const err_union_ty = try Type.errorUnion(sema.arena, error_set, payload, sema.mod); return sema.addType(err_union_ty); } @@ -25763,6 +25772,11 @@ fn analyzeIsNonErrComptimeOnly( if (ot == .ErrorSet) return Air.Inst.Ref.bool_false; assert(ot == .ErrorUnion); + const payload_ty = operand_ty.errorUnionPayload(); + if (payload_ty.zigTypeTag() == .NoReturn) { + return Air.Inst.Ref.bool_false; + } + if (Air.refToIndex(operand)) |operand_inst| { switch (sema.air_instructions.items(.tag)[operand_inst]) { .wrap_errunion_payload => return Air.Inst.Ref.bool_true, diff --git a/test/behavior/error.zig b/test/behavior/error.zig index 306dad5d9e..84b18a2738 100644 --- a/test/behavior/error.zig +++ b/test/behavior/error.zig @@ -725,7 +725,7 @@ test "simple else prong allowed even when all errors handled" { try expect(value == 255); } -test { +test "pointer to error union payload" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO @@ -736,3 +736,63 @@ test { const payload_ptr = &(err_union catch unreachable); try expect(payload_ptr.* == 15); } + +const NoReturn = struct { + var a: u32 = undefined; + fn someData() bool { + a -= 1; + return a == 0; + } + fn loop() !noreturn { + while (true) { + if (someData()) + return error.GenericFailure; + } + } + fn testTry() anyerror { + try loop(); + } + fn testCatch() anyerror { + loop() catch return error.OtherFailure; + @compileError("bad"); + } +}; + +test "error union of noreturn used with if" { + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + + NoReturn.a = 64; + if (NoReturn.loop()) { + @compileError("bad"); + } else |err| { + try expect(err == error.GenericFailure); + } +} + +test "error union of noreturn used with try" { + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + + NoReturn.a = 64; + const err = NoReturn.testTry(); + try expect(err == error.GenericFailure); +} + +test "error union of noreturn used with catch" { + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + + NoReturn.a = 64; + const err = NoReturn.testCatch(); + try expect(err == error.OtherFailure); +} diff --git a/test/cases/compile_errors/invalid_error_union_payload_type.zig b/test/cases/compile_errors/invalid_error_union_payload_type.zig new file mode 100644 index 0000000000..f8646d9450 --- /dev/null +++ b/test/cases/compile_errors/invalid_error_union_payload_type.zig @@ -0,0 +1,13 @@ +comptime { + _ = anyerror!anyopaque; +} +comptime { + _ = anyerror!anyerror; +} + +// error +// backend=stage2 +// target=native +// +// :2:18: error: error union with payload of opaque type 'anyopaque' not allowed +// :5:18: error: error union with payload of error set type 'anyerror' not allowed From c3d5428cba4d02afbe1ec34bdc7bcb68f56d1b45 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 17 Aug 2022 15:07:20 +0300 Subject: [PATCH 133/290] Sema: properly handle noreturn fields in unions --- src/Sema.zig | 202 +++++++++++++++--- test/behavior/union.zig | 45 ++++ .../compile_errors/noreturn_struct_field.zig | 12 ++ ...ast_to_union_which_has_non-void_fields.zig | 2 - .../union_noreturn_field_initialized.zig | 43 ++++ 5 files changed, 270 insertions(+), 34 deletions(-) create mode 100644 test/cases/compile_errors/noreturn_struct_field.zig create mode 100644 test/cases/compile_errors/union_noreturn_field_initialized.zig diff --git a/src/Sema.zig b/src/Sema.zig index 772abf5a84..1aebe8e98e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3772,6 +3772,7 @@ fn validateStructInit( } var root_msg: ?*Module.ErrorMsg = null; + errdefer if (root_msg) |msg| msg.destroy(sema.gpa); const struct_ptr = try sema.resolveInst(struct_ptr_zir_ref); if ((is_comptime or block.is_comptime) and @@ -3947,6 +3948,7 @@ fn validateStructInit( } if (root_msg) |msg| { + root_msg = null; if (struct_ty.castTag(.@"struct")) |struct_obj| { const fqn = try struct_obj.data.getFullyQualifiedName(sema.mod); defer gpa.free(fqn); @@ -4005,6 +4007,8 @@ fn zirValidateArrayInit( if (instrs.len != array_len and array_ty.isTuple()) { const struct_obj = array_ty.castTag(.tuple).?.data; var root_msg: ?*Module.ErrorMsg = null; + errdefer if (root_msg) |msg| msg.destroy(sema.gpa); + for (struct_obj.values) |default_val, i| { if (i < instrs.len) continue; @@ -4019,6 +4023,7 @@ fn zirValidateArrayInit( } if (root_msg) |msg| { + root_msg = null; return sema.failWithOwnedErrorMsg(msg); } } @@ -8964,12 +8969,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError }, }; - const union_originally = blk: { + const maybe_union_ty = blk: { const zir_data = sema.code.instructions.items(.data); const cond_index = Zir.refToIndex(extra.data.operand).?; const raw_operand = sema.resolveInst(zir_data[cond_index].un_node.operand) catch unreachable; - break :blk sema.typeOf(raw_operand).zigTypeTag() == .Union; + break :blk sema.typeOf(raw_operand); }; + const union_originally = maybe_union_ty.zigTypeTag() == .Union; + var seen_union_fields: []?Module.SwitchProngSrc = &.{}; + defer gpa.free(seen_union_fields); const operand_ty = sema.typeOf(operand); @@ -9004,7 +9012,8 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError .Union => unreachable, // handled in zirSwitchCond .Enum => { var seen_fields = try gpa.alloc(?Module.SwitchProngSrc, operand_ty.enumFieldCount()); - defer gpa.free(seen_fields); + defer if (!union_originally) gpa.free(seen_fields); + if (union_originally) seen_union_fields = seen_fields; mem.set(?Module.SwitchProngSrc, seen_fields, null); // This is used for non-exhaustive enum values that do not correspond to any tags. @@ -9637,18 +9646,28 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const item = try sema.resolveInst(item_ref); // `item` is already guaranteed to be constant known. - _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { - error.ComptimeBreak => { - const zir_datas = sema.code.instructions.items(.data); - const break_data = zir_datas[sema.comptime_break_inst].@"break"; - try sema.addRuntimeBreak(&case_block, .{ - .block_inst = break_data.block_inst, - .operand = break_data.operand, - .inst = sema.comptime_break_inst, - }); - }, - else => |e| return e, - }; + const analyze_body = if (union_originally) blk: { + const item_val = sema.resolveConstValue(block, .unneeded, item, undefined) catch unreachable; + const field_ty = maybe_union_ty.unionFieldType(item_val, sema.mod); + break :blk field_ty.zigTypeTag() != .NoReturn; + } else true; + + if (analyze_body) { + _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { + error.ComptimeBreak => { + const zir_datas = sema.code.instructions.items(.data); + const break_data = zir_datas[sema.comptime_break_inst].@"break"; + try sema.addRuntimeBreak(&case_block, .{ + .block_inst = break_data.block_inst, + .operand = break_data.operand, + .inst = sema.comptime_break_inst, + }); + }, + else => |e| return e, + }; + } else { + _ = try case_block.addNoOp(.unreach); + } try wip_captures.finalize(); @@ -9689,20 +9708,34 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (ranges_len == 0) { cases_len += 1; + const analyze_body = if (union_originally) + for (items) |item_ref| { + const item = try sema.resolveInst(item_ref); + const item_val = sema.resolveConstValue(block, .unneeded, item, undefined) catch unreachable; + const field_ty = maybe_union_ty.unionFieldType(item_val, sema.mod); + if (field_ty.zigTypeTag() != .NoReturn) break true; + } else false + else + true; + const body = sema.code.extra[extra_index..][0..body_len]; extra_index += body_len; - _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { - error.ComptimeBreak => { - const zir_datas = sema.code.instructions.items(.data); - const break_data = zir_datas[sema.comptime_break_inst].@"break"; - try sema.addRuntimeBreak(&case_block, .{ - .block_inst = break_data.block_inst, - .operand = break_data.operand, - .inst = sema.comptime_break_inst, - }); - }, - else => |e| return e, - }; + if (analyze_body) { + _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { + error.ComptimeBreak => { + const zir_datas = sema.code.instructions.items(.data); + const break_data = zir_datas[sema.comptime_break_inst].@"break"; + try sema.addRuntimeBreak(&case_block, .{ + .block_inst = break_data.block_inst, + .operand = break_data.operand, + .inst = sema.comptime_break_inst, + }); + }, + else => |e| return e, + }; + } else { + _ = try case_block.addNoOp(.unreach); + } try cases_extra.ensureUnusedCapacity(gpa, 2 + items.len + case_block.instructions.items.len); @@ -9824,7 +9857,17 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = wip_captures.scope; - if (special.body.len != 0) { + const analyze_body = if (union_originally) + for (seen_union_fields) |seen_field, index| { + if (seen_field != null) continue; + const union_obj = maybe_union_ty.cast(Type.Payload.Union).?.data; + const field_ty = union_obj.fields.values()[index].ty; + if (field_ty.zigTypeTag() != .NoReturn) break true; + } else false + else + true; + + if (special.body.len != 0 and analyze_body) { _ = sema.analyzeBodyInner(&case_block, special.body) catch |err| switch (err) { error.ComptimeBreak => { const zir_datas = sema.code.instructions.items(.data); @@ -13225,6 +13268,14 @@ fn analyzeCmpUnionTag( const coerced_tag = try sema.coerce(block, union_tag_ty, tag, tag_src); const coerced_union = try sema.coerce(block, union_tag_ty, un, un_src); + if (try sema.resolveMaybeUndefVal(block, tag_src, coerced_tag)) |enum_val| { + if (enum_val.isUndef()) return sema.addConstUndef(Type.bool); + const field_ty = union_ty.unionFieldType(enum_val, sema.mod); + if (field_ty.zigTypeTag() == .NoReturn) { + return Air.Inst.Ref.bool_false; + } + } + return sema.cmpSelf(block, src, coerced_union, coerced_tag, op, un_src, tag_src); } @@ -15579,6 +15630,8 @@ fn finishStructInit( const gpa = sema.gpa; var root_msg: ?*Module.ErrorMsg = null; + errdefer if (root_msg) |msg| msg.destroy(sema.gpa); + if (struct_ty.isAnonStruct()) { const struct_obj = struct_ty.castTag(.anon_struct).?.data; for (struct_obj.values) |default_val, i| { @@ -15634,6 +15687,7 @@ fn finishStructInit( } if (root_msg) |msg| { + root_msg = null; if (struct_ty.castTag(.@"struct")) |struct_obj| { const fqn = try struct_obj.data.getFullyQualifiedName(sema.mod); defer gpa.free(fqn); @@ -21682,6 +21736,18 @@ fn unionFieldPtr( .@"addrspace" = union_ptr_ty.ptrAddressSpace(), }); + if (initializing and field.ty.zigTypeTag() == .NoReturn) { + const msg = msg: { + const msg = try sema.errMsg(block, src, "cannot initialize 'noreturn' field of union", .{}); + errdefer msg.destroy(sema.gpa); + + try sema.addFieldErrNote(block, union_ty, field_index, msg, "field '{s}' declared here", .{field_name}); + try sema.addDeclaredHereNote(msg, union_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); + } + if (try sema.resolveDefinedValue(block, src, union_ptr)) |union_ptr_val| ct: { switch (union_obj.layout) { .Auto => if (!initializing) { @@ -21734,6 +21800,10 @@ fn unionFieldPtr( const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag); try sema.addSafetyCheck(block, ok, .inactive_union_field); } + if (field.ty.zigTypeTag() == .NoReturn) { + _ = try block.addNoOp(.unreach); + return Air.Inst.Ref.unreachable_value; + } return block.addStructFieldPtr(union_ptr, field_index, ptr_field_ty); } @@ -21802,6 +21872,10 @@ fn unionFieldVal( const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag); try sema.addSafetyCheck(block, ok, .inactive_union_field); } + if (field.ty.zigTypeTag() == .NoReturn) { + _ = try block.addNoOp(.unreach); + return Air.Inst.Ref.unreachable_value; + } return block.addStructFieldVal(union_byval, field_index, field.ty); } @@ -25002,6 +25076,18 @@ fn coerceEnumToUnion( }; const field = union_obj.fields.values()[field_index]; const field_ty = try sema.resolveTypeFields(block, inst_src, field.ty); + if (field_ty.zigTypeTag() == .NoReturn) { + const msg = msg: { + const msg = try sema.errMsg(block, inst_src, "cannot initialize 'noreturn' field of union", .{}); + errdefer msg.destroy(sema.gpa); + + const field_name = union_obj.fields.keys()[field_index]; + try sema.addFieldErrNote(block, union_ty, field_index, msg, "field '{s}' declared here", .{field_name}); + try sema.addDeclaredHereNote(msg, union_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); + } const opv = (try sema.typeHasOnePossibleValue(block, inst_src, field_ty)) orelse { const msg = msg: { const field_name = union_obj.fields.keys()[field_index]; @@ -25037,13 +25123,37 @@ fn coerceEnumToUnion( return sema.failWithOwnedErrorMsg(msg); } + const union_obj = union_ty.cast(Type.Payload.Union).?.data; + { + var msg: ?*Module.ErrorMsg = null; + errdefer if (msg) |some| some.destroy(sema.gpa); + + for (union_obj.fields.values()) |field, i| { + if (field.ty.zigTypeTag() == .NoReturn) { + const err_msg = msg orelse try sema.errMsg( + block, + inst_src, + "runtime coercion from enum '{}' to union '{}' which has a 'noreturn' field", + .{ tag_ty.fmt(sema.mod), union_ty.fmt(sema.mod) }, + ); + msg = err_msg; + + try sema.addFieldErrNote(block, union_ty, i, err_msg, "'noreturn' field here", .{}); + } + } + if (msg) |some| { + msg = null; + try sema.addDeclaredHereNote(some, union_ty); + return sema.failWithOwnedErrorMsg(some); + } + } + // If the union has all fields 0 bits, the union value is just the enum value. if (union_ty.unionHasAllZeroBitFieldTypes()) { return block.addBitCast(union_ty, enum_tag); } const msg = msg: { - const union_obj = union_ty.cast(Type.Payload.Union).?.data; const msg = try sema.errMsg( block, inst_src, @@ -25054,11 +25164,11 @@ fn coerceEnumToUnion( var it = union_obj.fields.iterator(); var field_index: usize = 0; - while (it.next()) |field| { + while (it.next()) |field| : (field_index += 1) { const field_name = field.key_ptr.*; const field_ty = field.value_ptr.ty; + if (!field_ty.hasRuntimeBits()) continue; try sema.addFieldErrNote(block, union_ty, field_index, msg, "field '{s}' has type '{}'", .{ field_name, field_ty.fmt(sema.mod) }); - field_index += 1; } try sema.addDeclaredHereNote(msg, union_ty); break :msg msg; @@ -25361,6 +25471,7 @@ fn coerceTupleToStruct( // Populate default field values and report errors for missing fields. var root_msg: ?*Module.ErrorMsg = null; + errdefer if (root_msg) |msg| msg.destroy(sema.gpa); for (field_refs) |*field_ref, i| { if (field_ref.* != .none) continue; @@ -25386,6 +25497,7 @@ fn coerceTupleToStruct( } if (root_msg) |msg| { + root_msg = null; try sema.addDeclaredHereNote(msg, struct_ty); return sema.failWithOwnedErrorMsg(msg); } @@ -25455,6 +25567,7 @@ fn coerceTupleToTuple( // Populate default field values and report errors for missing fields. var root_msg: ?*Module.ErrorMsg = null; + errdefer if (root_msg) |msg| msg.destroy(sema.gpa); for (field_refs) |*field_ref, i| { if (field_ref.* != .none) continue; @@ -25490,6 +25603,7 @@ fn coerceTupleToTuple( } if (root_msg) |msg| { + root_msg = null; try sema.addDeclaredHereNote(msg, tuple_ty); return sema.failWithOwnedErrorMsg(msg); } @@ -27914,6 +28028,18 @@ fn semaStructFields(mod: *Module, struct_obj: *Module.Struct) CompileError!void }; return sema.failWithOwnedErrorMsg(msg); } + if (field_ty.zigTypeTag() == .NoReturn) { + const msg = msg: { + const tree = try sema.getAstTree(&block_scope); + const field_src = enumFieldSrcLoc(decl, tree.*, 0, i); + const msg = try sema.errMsg(&block_scope, field_src, "struct fields cannot be 'noreturn'", .{}); + errdefer msg.destroy(sema.gpa); + + try sema.addDeclaredHereNote(msg, field_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); + } if (struct_obj.layout == .Extern and !sema.validateExternType(field.ty, .other)) { const msg = msg: { const tree = try sema.getAstTree(&block_scope); @@ -28725,6 +28851,16 @@ fn enumFieldSrcLoc( .container_decl_arg_trailing, => tree.containerDeclArg(enum_node), + .tagged_union, + .tagged_union_trailing, + => tree.taggedUnion(enum_node), + .tagged_union_two, + .tagged_union_two_trailing, + => tree.taggedUnionTwo(&buffer, enum_node), + .tagged_union_enum_tag, + .tagged_union_enum_tag_trailing, + => tree.taggedUnionEnumTag(enum_node), + // Container was constructed with `@Type`. else => return LazySrcLoc.nodeOffset(0), }; @@ -29375,7 +29511,9 @@ fn unionFieldAlignment( src: LazySrcLoc, field: Module.Union.Field, ) !u32 { - if (field.abi_align == 0) { + if (field.ty.zigTypeTag() == .NoReturn) { + return @as(u32, 0); + } else if (field.abi_align == 0) { return sema.typeAbiAlignment(block, src, field.ty); } else { return field.abi_align; diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 2f6fa78f0c..efca75af30 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -1256,3 +1256,48 @@ test "return an extern union from C calling convention" { }); try expect(u.d == 4.0); } + +test "noreturn field in union" { + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + + const U = union(enum) { + a: u32, + b: noreturn, + c: noreturn, + }; + var a = U{ .a = 1 }; + var count: u32 = 0; + if (a == .b) @compileError("bad"); + switch (a) { + .a => count += 1, + .b => |val| { + _ = val; + @compileError("bad"); + }, + .c => @compileError("bad"), + } + switch (a) { + .a => count += 1, + .b, .c => @compileError("bad"), + } + switch (a) { + .a, .b, .c => { + count += 1; + try expect(a == .a); + }, + } + switch (a) { + .a => count += 1, + else => @compileError("bad"), + } + switch (a) { + else => { + count += 1; + try expect(a == .a); + }, + } + try expect(count == 5); +} diff --git a/test/cases/compile_errors/noreturn_struct_field.zig b/test/cases/compile_errors/noreturn_struct_field.zig new file mode 100644 index 0000000000..90b243c31d --- /dev/null +++ b/test/cases/compile_errors/noreturn_struct_field.zig @@ -0,0 +1,12 @@ +const S = struct { + s: noreturn, +}; +comptime { + _ = @typeInfo(S); +} + +// error +// backend=stage2 +// target=native +// +// :2:5: error: struct fields cannot be 'noreturn' diff --git a/test/cases/compile_errors/runtime_cast_to_union_which_has_non-void_fields.zig b/test/cases/compile_errors/runtime_cast_to_union_which_has_non-void_fields.zig index c312d6db40..0142f422f4 100644 --- a/test/cases/compile_errors/runtime_cast_to_union_which_has_non-void_fields.zig +++ b/test/cases/compile_errors/runtime_cast_to_union_which_has_non-void_fields.zig @@ -18,6 +18,4 @@ fn foo(l: Letter) void { // // :11:20: error: runtime coercion from enum 'tmp.Letter' to union 'tmp.Value' which has non-void fields // :3:5: note: field 'A' has type 'i32' -// :4:5: note: field 'B' has type 'void' -// :5:5: note: field 'C' has type 'void' // :2:15: note: union declared here diff --git a/test/cases/compile_errors/union_noreturn_field_initialized.zig b/test/cases/compile_errors/union_noreturn_field_initialized.zig new file mode 100644 index 0000000000..66304d6a74 --- /dev/null +++ b/test/cases/compile_errors/union_noreturn_field_initialized.zig @@ -0,0 +1,43 @@ +pub export fn entry1() void { + const U = union(enum) { + a: u32, + b: noreturn, + fn foo(_: @This()) void {} + fn bar() noreturn { + unreachable; + } + }; + + var a = U{ .b = undefined }; + _ = a; +} +pub export fn entry2() void { + const U = union(enum) { + a: noreturn, + }; + var u: U = undefined; + u = .a; +} +pub export fn entry3() void { + const U = union(enum) { + a: noreturn, + b: void, + }; + var e = @typeInfo(U).Union.tag_type.?.a; + var u: U = undefined; + u = e; +} + +// error +// backend=stage2 +// target=native +// +// :11:21: error: cannot initialize 'noreturn' field of union +// :4:9: note: field 'b' declared here +// :2:15: note: union declared here +// :19:10: error: cannot initialize 'noreturn' field of union +// :16:9: note: field 'a' declared here +// :15:15: note: union declared here +// :28:9: error: runtime coercion from enum '@typeInfo(tmp.entry3.U).Union.tag_type.?' to union 'tmp.entry3.U' which has a 'noreturn' field +// :23:9: note: 'noreturn' field here +// :22:15: note: union declared here From c764640e92c9e4d32b89650ac774bebf1498be92 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 17 Aug 2022 12:55:08 -0700 Subject: [PATCH 134/290] Sema: fix generics with struct literal coerced to tagged union The `Value.eql` function has to test for value equality *as-if* the lhs value parameter is coerced into the type of the rhs. For tagged unions, there was a problematic case when the lhs was an anonymous struct, because in such case the value is empty_struct_value and the type contains all the value information. But the only type available in the function was the rhs type. So the fix involved making `Value.eqlAdvanced` also accept the lhs type, and then enhancing the logic to handle the case of the `.anon_struct` tag. closes #12418 Tests run locally: * test-behavior * test-cases --- src/Sema.zig | 56 +++++++++++++++++---------- src/value.zig | 77 ++++++++++++++++++++++++++------------ test/behavior/generics.zig | 19 ++++++++++ 3 files changed, 110 insertions(+), 42 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index d7d6994bcd..f1d140520c 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1495,7 +1495,8 @@ pub fn resolveInst(sema: *Sema, zir_ref: Zir.Inst.Ref) !Air.Inst.Ref { // Finally, the last section of indexes refers to the map of ZIR=>AIR. const inst = sema.inst_map.get(@intCast(u32, i)).?; - if (sema.typeOf(inst).tag() == .generic_poison) return error.GenericPoison; + const ty = sema.typeOf(inst); + if (ty.tag() == .generic_poison) return error.GenericPoison; return inst; } @@ -5570,11 +5571,15 @@ const GenericCallAdapter = struct { generic_fn: *Module.Fn, precomputed_hash: u64, func_ty_info: Type.Payload.Function.Data, - /// Unlike comptime_args, the Type here is not always present. - /// .generic_poison is used to communicate non-anytype parameters. - comptime_tvs: []const TypedValue, + args: []const Arg, module: *Module, + const Arg = struct { + ty: Type, + val: Value, + is_anytype: bool, + }; + pub fn eql(ctx: @This(), adapted_key: void, other_key: *Module.Fn) bool { _ = adapted_key; // The generic function Decl is guaranteed to be the first dependency @@ -5585,10 +5590,10 @@ const GenericCallAdapter = struct { const other_comptime_args = other_key.comptime_args.?; for (other_comptime_args[0..ctx.func_ty_info.param_types.len]) |other_arg, i| { - const this_arg = ctx.comptime_tvs[i]; + const this_arg = ctx.args[i]; const this_is_comptime = this_arg.val.tag() != .generic_poison; const other_is_comptime = other_arg.val.tag() != .generic_poison; - const this_is_anytype = this_arg.ty.tag() != .generic_poison; + const this_is_anytype = this_arg.is_anytype; const other_is_anytype = other_key.isAnytypeParam(ctx.module, @intCast(u32, i)); if (other_is_anytype != this_is_anytype) return false; @@ -5607,7 +5612,17 @@ const GenericCallAdapter = struct { } } else if (this_is_comptime) { // Both are comptime parameters but not anytype parameters. - if (!this_arg.val.eql(other_arg.val, other_arg.ty, ctx.module)) { + // We assert no error is possible here because any lazy values must be resolved + // before inserting into the generic function hash map. + const is_eql = Value.eqlAdvanced( + this_arg.val, + this_arg.ty, + other_arg.val, + other_arg.ty, + ctx.module, + null, + ) catch unreachable; + if (!is_eql) { return false; } } @@ -6258,8 +6273,7 @@ fn instantiateGenericCall( var hasher = std.hash.Wyhash.init(0); std.hash.autoHash(&hasher, @ptrToInt(module_fn)); - const comptime_tvs = try sema.arena.alloc(TypedValue, func_ty_info.param_types.len); - + const generic_args = try sema.arena.alloc(GenericCallAdapter.Arg, func_ty_info.param_types.len); { var i: usize = 0; for (fn_info.param_body) |inst| { @@ -6283,8 +6297,9 @@ fn instantiateGenericCall( else => continue, } + const arg_ty = sema.typeOf(uncasted_args[i]); + if (is_comptime) { - const arg_ty = sema.typeOf(uncasted_args[i]); const arg_val = sema.analyzeGenericCallArgVal(block, .unneeded, uncasted_args[i]) catch |err| switch (err) { error.NeededSourceLocation => { const decl = sema.mod.declPtr(block.src_decl); @@ -6297,27 +6312,30 @@ fn instantiateGenericCall( arg_val.hash(arg_ty, &hasher, mod); if (is_anytype) { arg_ty.hashWithHasher(&hasher, mod); - comptime_tvs[i] = .{ + generic_args[i] = .{ .ty = arg_ty, .val = arg_val, + .is_anytype = true, }; } else { - comptime_tvs[i] = .{ - .ty = Type.initTag(.generic_poison), + generic_args[i] = .{ + .ty = arg_ty, .val = arg_val, + .is_anytype = false, }; } } else if (is_anytype) { - const arg_ty = sema.typeOf(uncasted_args[i]); arg_ty.hashWithHasher(&hasher, mod); - comptime_tvs[i] = .{ + generic_args[i] = .{ .ty = arg_ty, .val = Value.initTag(.generic_poison), + .is_anytype = true, }; } else { - comptime_tvs[i] = .{ - .ty = Type.initTag(.generic_poison), + generic_args[i] = .{ + .ty = arg_ty, .val = Value.initTag(.generic_poison), + .is_anytype = false, }; } @@ -6331,7 +6349,7 @@ fn instantiateGenericCall( .generic_fn = module_fn, .precomputed_hash = precomputed_hash, .func_ty_info = func_ty_info, - .comptime_tvs = comptime_tvs, + .args = generic_args, .module = mod, }; const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter); @@ -30124,7 +30142,7 @@ fn valuesEqual( rhs: Value, ty: Type, ) CompileError!bool { - return Value.eqlAdvanced(lhs, rhs, ty, sema.mod, sema.kit(block, src)); + return Value.eqlAdvanced(lhs, ty, rhs, ty, sema.mod, sema.kit(block, src)); } /// Asserts the values are comparable vectors of type `ty`. diff --git a/src/value.zig b/src/value.zig index 677a459afe..9909cab5ce 100644 --- a/src/value.zig +++ b/src/value.zig @@ -2004,6 +2004,10 @@ pub const Value = extern union { return (try orderAgainstZeroAdvanced(lhs, sema_kit)).compare(op); } + pub fn eql(a: Value, b: Value, ty: Type, mod: *Module) bool { + return eqlAdvanced(a, ty, b, ty, mod, null) catch unreachable; + } + /// This function is used by hash maps and so treats floating-point NaNs as equal /// to each other, and not equal to other floating-point values. /// Similarly, it treats `undef` as a distinct value from all other values. @@ -2012,13 +2016,10 @@ pub const Value = extern union { /// for `a`. This function must act *as if* `a` has been coerced to `ty`. This complication /// is required in order to make generic function instantiation efficient - specifically /// the insertion into the monomorphized function table. - pub fn eql(a: Value, b: Value, ty: Type, mod: *Module) bool { - return eqlAdvanced(a, b, ty, mod, null) catch unreachable; - } - /// If `null` is provided for `sema_kit` then it is guaranteed no error will be returned. pub fn eqlAdvanced( a: Value, + a_ty: Type, b: Value, ty: Type, mod: *Module, @@ -2044,33 +2045,34 @@ pub const Value = extern union { const a_payload = a.castTag(.opt_payload).?.data; const b_payload = b.castTag(.opt_payload).?.data; var buffer: Type.Payload.ElemType = undefined; - return eqlAdvanced(a_payload, b_payload, ty.optionalChild(&buffer), mod, sema_kit); + const payload_ty = ty.optionalChild(&buffer); + return eqlAdvanced(a_payload, payload_ty, b_payload, payload_ty, mod, sema_kit); }, .slice => { const a_payload = a.castTag(.slice).?.data; const b_payload = b.castTag(.slice).?.data; - if (!(try eqlAdvanced(a_payload.len, b_payload.len, Type.usize, mod, sema_kit))) { + if (!(try eqlAdvanced(a_payload.len, Type.usize, b_payload.len, Type.usize, mod, sema_kit))) { return false; } var ptr_buf: Type.SlicePtrFieldTypeBuffer = undefined; const ptr_ty = ty.slicePtrFieldType(&ptr_buf); - return eqlAdvanced(a_payload.ptr, b_payload.ptr, ptr_ty, mod, sema_kit); + return eqlAdvanced(a_payload.ptr, ptr_ty, b_payload.ptr, ptr_ty, mod, sema_kit); }, .elem_ptr => { const a_payload = a.castTag(.elem_ptr).?.data; const b_payload = b.castTag(.elem_ptr).?.data; if (a_payload.index != b_payload.index) return false; - return eqlAdvanced(a_payload.array_ptr, b_payload.array_ptr, ty, mod, sema_kit); + return eqlAdvanced(a_payload.array_ptr, ty, b_payload.array_ptr, ty, mod, sema_kit); }, .field_ptr => { const a_payload = a.castTag(.field_ptr).?.data; const b_payload = b.castTag(.field_ptr).?.data; if (a_payload.field_index != b_payload.field_index) return false; - return eqlAdvanced(a_payload.container_ptr, b_payload.container_ptr, ty, mod, sema_kit); + return eqlAdvanced(a_payload.container_ptr, ty, b_payload.container_ptr, ty, mod, sema_kit); }, .@"error" => { const a_name = a.castTag(.@"error").?.data.name; @@ -2080,7 +2082,8 @@ pub const Value = extern union { .eu_payload => { const a_payload = a.castTag(.eu_payload).?.data; const b_payload = b.castTag(.eu_payload).?.data; - return eqlAdvanced(a_payload, b_payload, ty.errorUnionPayload(), mod, sema_kit); + const payload_ty = ty.errorUnionPayload(); + return eqlAdvanced(a_payload, payload_ty, b_payload, payload_ty, mod, sema_kit); }, .eu_payload_ptr => @panic("TODO: Implement more pointer eql cases"), .opt_payload_ptr => @panic("TODO: Implement more pointer eql cases"), @@ -2098,7 +2101,7 @@ pub const Value = extern union { const types = ty.tupleFields().types; assert(types.len == a_field_vals.len); for (types) |field_ty, i| { - if (!(try eqlAdvanced(a_field_vals[i], b_field_vals[i], field_ty, mod, sema_kit))) { + if (!(try eqlAdvanced(a_field_vals[i], field_ty, b_field_vals[i], field_ty, mod, sema_kit))) { return false; } } @@ -2109,7 +2112,7 @@ pub const Value = extern union { const fields = ty.structFields().values(); assert(fields.len == a_field_vals.len); for (fields) |field, i| { - if (!(try eqlAdvanced(a_field_vals[i], b_field_vals[i], field.ty, mod, sema_kit))) { + if (!(try eqlAdvanced(a_field_vals[i], field.ty, b_field_vals[i], field.ty, mod, sema_kit))) { return false; } } @@ -2120,7 +2123,7 @@ pub const Value = extern union { for (a_field_vals) |a_elem, i| { const b_elem = b_field_vals[i]; - if (!(try eqlAdvanced(a_elem, b_elem, elem_ty, mod, sema_kit))) { + if (!(try eqlAdvanced(a_elem, elem_ty, b_elem, elem_ty, mod, sema_kit))) { return false; } } @@ -2132,7 +2135,7 @@ pub const Value = extern union { switch (ty.containerLayout()) { .Packed, .Extern => { const tag_ty = ty.unionTagTypeHypothetical(); - if (!(try a_union.tag.eqlAdvanced(b_union.tag, tag_ty, mod, sema_kit))) { + if (!(try eqlAdvanced(a_union.tag, tag_ty, b_union.tag, tag_ty, mod, sema_kit))) { // In this case, we must disregard mismatching tags and compare // based on the in-memory bytes of the payloads. @panic("TODO comptime comparison of extern union values with mismatching tags"); @@ -2140,13 +2143,13 @@ pub const Value = extern union { }, .Auto => { const tag_ty = ty.unionTagTypeHypothetical(); - if (!(try a_union.tag.eqlAdvanced(b_union.tag, tag_ty, mod, sema_kit))) { + if (!(try eqlAdvanced(a_union.tag, tag_ty, b_union.tag, tag_ty, mod, sema_kit))) { return false; } }, } const active_field_ty = ty.unionFieldType(a_union.tag, mod); - return a_union.val.eqlAdvanced(b_union.val, active_field_ty, mod, sema_kit); + return eqlAdvanced(a_union.val, active_field_ty, b_union.val, active_field_ty, mod, sema_kit); }, else => {}, } else if (a_tag == .null_value or b_tag == .null_value) { @@ -2180,7 +2183,7 @@ pub const Value = extern union { const b_val = b.enumToInt(ty, &buf_b); var buf_ty: Type.Payload.Bits = undefined; const int_ty = ty.intTagType(&buf_ty); - return eqlAdvanced(a_val, b_val, int_ty, mod, sema_kit); + return eqlAdvanced(a_val, int_ty, b_val, int_ty, mod, sema_kit); }, .Array, .Vector => { const len = ty.arrayLen(); @@ -2191,17 +2194,44 @@ pub const Value = extern union { while (i < len) : (i += 1) { const a_elem = elemValueBuffer(a, mod, i, &a_buf); const b_elem = elemValueBuffer(b, mod, i, &b_buf); - if (!(try eqlAdvanced(a_elem, b_elem, elem_ty, mod, sema_kit))) { + if (!(try eqlAdvanced(a_elem, elem_ty, b_elem, elem_ty, mod, sema_kit))) { return false; } } return true; }, .Struct => { - // A tuple can be represented with .empty_struct_value, - // the_one_possible_value, .aggregate in which case we could - // end up here and the values are equal if the type has zero fields. - return ty.isTupleOrAnonStruct() and ty.structFieldCount() != 0; + // A struct can be represented with one of: + // .empty_struct_value, + // .the_one_possible_value, + // .aggregate, + // Note that we already checked above for matching tags, e.g. both .aggregate. + return ty.onePossibleValue() != null; + }, + .Union => { + // Here we have to check for value equality, as-if `a` has been coerced to `ty`. + if (ty.onePossibleValue() != null) { + return true; + } + if (a_ty.castTag(.anon_struct)) |payload| { + const tuple = payload.data; + if (tuple.values.len != 1) { + return false; + } + const field_name = tuple.names[0]; + const union_obj = ty.cast(Type.Payload.Union).?.data; + const field_index = union_obj.fields.getIndex(field_name) orelse return false; + const tag_and_val = b.castTag(.@"union").?.data; + var field_tag_buf: Value.Payload.U32 = .{ + .base = .{ .tag = .enum_field_index }, + .data = @intCast(u32, field_index), + }; + const field_tag = Value.initPayload(&field_tag_buf.base); + const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty, mod); + if (!tag_matches) return false; + return eqlAdvanced(tag_and_val.val, union_obj.tag_ty, tuple.values[0], tuple.types[0], mod, sema_kit); + } + return false; }, .Float => { switch (ty.floatBits(target)) { @@ -2230,7 +2260,8 @@ pub const Value = extern union { .base = .{ .tag = .opt_payload }, .data = a, }; - return eqlAdvanced(Value.initPayload(&buffer.base), b, ty, mod, sema_kit); + const opt_val = Value.initPayload(&buffer.base); + return eqlAdvanced(opt_val, ty, b, ty, mod, sema_kit); } }, else => {}, diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig index cd4e018be3..d930fb7d27 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -323,3 +323,22 @@ test "generic function instantiation non-duplicates" { S.copy(u8, &buffer, "hello"); S.copy(u8, &buffer, "hello2"); } + +test "generic instantiation of tagged union with only one field" { + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + const S = struct { + const U = union(enum) { + s: []const u8, + }; + + fn foo(comptime u: U) usize { + return u.s.len; + } + }; + + try expect(S.foo(.{ .s = "a" }) == 1); + try expect(S.foo(.{ .s = "ab" }) == 2); +} From 233049503a41ac4c6895f7126cda63349606b8f8 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 17 Aug 2022 16:10:46 +0300 Subject: [PATCH 135/290] Sema: allow empty enums and unions --- src/AstGen.zig | 9 - src/Sema.zig | 174 +++++++++--------- src/print_zir.zig | 15 +- src/type.zig | 5 +- test/behavior.zig | 1 + test/behavior/empty_union.zig | 54 ++++++ .../compile_errors/enum_with_0_fields.zig | 7 - ...e_for_exhaustive_enum_with_zero_fields.zig | 18 -- .../reify_type_for_union_with_zero_fields.zig | 17 -- .../union_fields_with_value_assignments.zig | 7 - .../compile_errors/union_with_0_fields.zig | 7 - ...types_in_function_call_raises_an_error.zig | 11 -- test/stage2/cbe.zig | 9 - 13 files changed, 156 insertions(+), 178 deletions(-) create mode 100644 test/behavior/empty_union.zig delete mode 100644 test/cases/compile_errors/enum_with_0_fields.zig delete mode 100644 test/cases/compile_errors/reify_type_for_exhaustive_enum_with_zero_fields.zig delete mode 100644 test/cases/compile_errors/reify_type_for_union_with_zero_fields.zig delete mode 100644 test/cases/compile_errors/union_fields_with_value_assignments.zig delete mode 100644 test/cases/compile_errors/union_with_0_fields.zig delete mode 100644 test/cases/compile_errors/using_invalid_types_in_function_call_raises_an_error.zig diff --git a/src/AstGen.zig b/src/AstGen.zig index 2fcd8cd994..ee1dbeffa4 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -4557,9 +4557,6 @@ fn unionDeclInner( wip_members.appendToField(@enumToInt(tag_value)); } } - if (field_count == 0) { - return astgen.failNode(node, "union declarations must have at least one tag", .{}); - } if (!block_scope.isEmpty()) { _ = try block_scope.addBreak(.break_inline, decl_inst, .void_value); @@ -4715,12 +4712,6 @@ fn containerDecl( .nonexhaustive_node = nonexhaustive_node, }; }; - if (counts.total_fields == 0 and counts.nonexhaustive_node == 0) { - // One can construct an enum with no tags, and it functions the same as `noreturn`. But - // this is only useful for generic code; when explicitly using `enum {}` syntax, there - // must be at least one tag. - try astgen.appendErrorNode(node, "enum declarations must have at least one tag", .{}); - } if (counts.nonexhaustive_node != 0 and container_decl.ast.arg == 0) { try astgen.appendErrorNodeNotes( node, diff --git a/src/Sema.zig b/src/Sema.zig index 1aebe8e98e..1a0ded4a00 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8979,6 +8979,8 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError var seen_union_fields: []?Module.SwitchProngSrc = &.{}; defer gpa.free(seen_union_fields); + var empty_enum = false; + const operand_ty = sema.typeOf(operand); var else_error_ty: ?Type = null; @@ -9012,6 +9014,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError .Union => unreachable, // handled in zirSwitchCond .Enum => { var seen_fields = try gpa.alloc(?Module.SwitchProngSrc, operand_ty.enumFieldCount()); + empty_enum = seen_fields.len == 0 and !operand_ty.isNonexhaustiveEnum(); defer if (!union_originally) gpa.free(seen_fields); if (union_originally) seen_union_fields = seen_fields; mem.set(?Module.SwitchProngSrc, seen_fields, null); @@ -9607,6 +9610,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } if (scalar_cases_len + multi_cases_len == 0) { + if (empty_enum) { + return Air.Inst.Ref.void_value; + } if (special_prong == .none) { return sema.fail(block, src, "switch must handle all possibilities", .{}); } @@ -16690,43 +16696,39 @@ fn zirReify(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, in // Fields const fields_len = try sema.usizeCast(block, src, fields_val.sliceLen(mod)); - if (fields_len > 0) { - try enum_obj.fields.ensureTotalCapacity(new_decl_arena_allocator, fields_len); - try enum_obj.values.ensureTotalCapacityContext(new_decl_arena_allocator, fields_len, .{ + try enum_obj.fields.ensureTotalCapacity(new_decl_arena_allocator, fields_len); + try enum_obj.values.ensureTotalCapacityContext(new_decl_arena_allocator, fields_len, .{ + .ty = enum_obj.tag_ty, + .mod = mod, + }); + + var i: usize = 0; + while (i < fields_len) : (i += 1) { + const elem_val = try fields_val.elemValue(sema.mod, sema.arena, i); + const field_struct_val = elem_val.castTag(.aggregate).?.data; + // TODO use reflection instead of magic numbers here + // name: []const u8 + const name_val = field_struct_val[0]; + // value: comptime_int + const value_val = field_struct_val[1]; + + const field_name = try name_val.toAllocatedBytes( + Type.initTag(.const_slice_u8), + new_decl_arena_allocator, + sema.mod, + ); + + const gop = enum_obj.fields.getOrPutAssumeCapacity(field_name); + if (gop.found_existing) { + // TODO: better source location + return sema.fail(block, src, "duplicate enum tag {s}", .{field_name}); + } + + const copied_tag_val = try value_val.copy(new_decl_arena_allocator); + enum_obj.values.putAssumeCapacityNoClobberContext(copied_tag_val, {}, .{ .ty = enum_obj.tag_ty, .mod = mod, }); - - var i: usize = 0; - while (i < fields_len) : (i += 1) { - const elem_val = try fields_val.elemValue(sema.mod, sema.arena, i); - const field_struct_val = elem_val.castTag(.aggregate).?.data; - // TODO use reflection instead of magic numbers here - // name: []const u8 - const name_val = field_struct_val[0]; - // value: comptime_int - const value_val = field_struct_val[1]; - - const field_name = try name_val.toAllocatedBytes( - Type.initTag(.const_slice_u8), - new_decl_arena_allocator, - sema.mod, - ); - - const gop = enum_obj.fields.getOrPutAssumeCapacity(field_name); - if (gop.found_existing) { - // TODO: better source location - return sema.fail(block, src, "duplicate enum tag {s}", .{field_name}); - } - - const copied_tag_val = try value_val.copy(new_decl_arena_allocator); - enum_obj.values.putAssumeCapacityNoClobberContext(copied_tag_val, {}, .{ - .ty = enum_obj.tag_ty, - .mod = mod, - }); - } - } else { - return sema.fail(block, src, "enums must have at least one field", .{}); } try new_decl.finalizeNewArena(&new_decl_arena); @@ -16851,58 +16853,54 @@ fn zirReify(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, in } // Fields - if (fields_len > 0) { - try union_obj.fields.ensureTotalCapacity(new_decl_arena_allocator, fields_len); + try union_obj.fields.ensureTotalCapacity(new_decl_arena_allocator, fields_len); - var i: usize = 0; - while (i < fields_len) : (i += 1) { - const elem_val = try fields_val.elemValue(sema.mod, sema.arena, i); - const field_struct_val = elem_val.castTag(.aggregate).?.data; - // TODO use reflection instead of magic numbers here - // name: []const u8 - const name_val = field_struct_val[0]; - // field_type: type, - const field_type_val = field_struct_val[1]; - // alignment: comptime_int, - const alignment_val = field_struct_val[2]; + var i: usize = 0; + while (i < fields_len) : (i += 1) { + const elem_val = try fields_val.elemValue(sema.mod, sema.arena, i); + const field_struct_val = elem_val.castTag(.aggregate).?.data; + // TODO use reflection instead of magic numbers here + // name: []const u8 + const name_val = field_struct_val[0]; + // field_type: type, + const field_type_val = field_struct_val[1]; + // alignment: comptime_int, + const alignment_val = field_struct_val[2]; - const field_name = try name_val.toAllocatedBytes( - Type.initTag(.const_slice_u8), - new_decl_arena_allocator, - sema.mod, - ); + const field_name = try name_val.toAllocatedBytes( + Type.initTag(.const_slice_u8), + new_decl_arena_allocator, + sema.mod, + ); - if (enum_field_names) |set| { - set.putAssumeCapacity(field_name, {}); - } - - if (tag_ty_field_names) |*names| { - const enum_has_field = names.orderedRemove(field_name); - if (!enum_has_field) { - const msg = msg: { - const msg = try sema.errMsg(block, src, "no field named '{s}' in enum '{}'", .{ field_name, union_obj.tag_ty.fmt(sema.mod) }); - errdefer msg.destroy(sema.gpa); - try sema.addDeclaredHereNote(msg, union_obj.tag_ty); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(msg); - } - } - - const gop = union_obj.fields.getOrPutAssumeCapacity(field_name); - if (gop.found_existing) { - // TODO: better source location - return sema.fail(block, src, "duplicate union field {s}", .{field_name}); - } - - var buffer: Value.ToTypeBuffer = undefined; - gop.value_ptr.* = .{ - .ty = try field_type_val.toType(&buffer).copy(new_decl_arena_allocator), - .abi_align = @intCast(u32, alignment_val.toUnsignedInt(target)), - }; + if (enum_field_names) |set| { + set.putAssumeCapacity(field_name, {}); } - } else { - return sema.fail(block, src, "unions must have at least one field", .{}); + + if (tag_ty_field_names) |*names| { + const enum_has_field = names.orderedRemove(field_name); + if (!enum_has_field) { + const msg = msg: { + const msg = try sema.errMsg(block, src, "no field named '{s}' in enum '{}'", .{ field_name, union_obj.tag_ty.fmt(sema.mod) }); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, union_obj.tag_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); + } + } + + const gop = union_obj.fields.getOrPutAssumeCapacity(field_name); + if (gop.found_existing) { + // TODO: better source location + return sema.fail(block, src, "duplicate union field {s}", .{field_name}); + } + + var buffer: Value.ToTypeBuffer = undefined; + gop.value_ptr.* = .{ + .ty = try field_type_val.toType(&buffer).copy(new_decl_arena_allocator), + .abi_align = @intCast(u32, alignment_val.toUnsignedInt(target)), + }; } if (tag_ty_field_names) |names| { @@ -28146,10 +28144,6 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void { extra_index = decls_it.extra_index; const body = zir.extra[extra_index..][0..body_len]; - if (fields_len == 0) { - assert(body.len == 0); - return; - } extra_index += body.len; const decl = mod.declPtr(decl_index); @@ -28237,6 +28231,10 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void { enum_field_names = &union_obj.tag_ty.castTag(.enum_simple).?.data.fields; } + if (fields_len == 0) { + return; + } + const bits_per_field = 4; const fields_per_u32 = 32 / bits_per_field; const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable; @@ -28772,7 +28770,9 @@ pub fn typeHasOnePossibleValue( const union_obj = resolved_ty.cast(Type.Payload.Union).?.data; const tag_val = (try sema.typeHasOnePossibleValue(block, src, union_obj.tag_ty)) orelse return null; - const only_field = union_obj.fields.values()[0]; + const fields = union_obj.fields.values(); + if (fields.len == 0) return Value.initTag(.empty_struct_value); + const only_field = fields[0]; if (only_field.ty.eql(resolved_ty, sema.mod)) { const msg = try Module.ErrorMsg.create( sema.gpa, diff --git a/src/print_zir.zig b/src/print_zir.zig index 4bc96c4259..579a7970b7 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -1443,7 +1443,7 @@ const Writer = struct { try self.writeFlag(stream, "autoenum, ", small.auto_enum_tag); if (decls_len == 0) { - try stream.writeAll("{}, "); + try stream.writeAll("{}"); } else { const prev_parent_decl_node = self.parent_decl_node; if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off); @@ -1454,16 +1454,21 @@ const Writer = struct { extra_index = try self.writeDecls(stream, decls_len, extra_index); self.indent -= 2; try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}, "); + try stream.writeAll("}"); } - assert(fields_len != 0); - if (tag_type_ref != .none) { - try self.writeInstRef(stream, tag_type_ref); try stream.writeAll(", "); + try self.writeInstRef(stream, tag_type_ref); } + if (fields_len == 0) { + try stream.writeAll("})"); + try self.writeSrcNode(stream, src_node); + return; + } + try stream.writeAll(", "); + const body = self.code.extra[extra_index..][0..body_len]; extra_index += body.len; diff --git a/src/type.zig b/src/type.zig index 582ea230ef..55847b8add 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2488,7 +2488,7 @@ pub const Type = extern union { }, .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; - if (try union_obj.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) { + if (union_obj.fields.count() > 0 and try union_obj.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) { return true; } if (sema_kit) |sk| { @@ -3113,6 +3113,9 @@ pub const Type = extern union { .sema_kit => unreachable, // handled above .lazy => |arena| return AbiAlignmentAdvanced{ .val = try Value.Tag.lazy_align.create(arena, ty) }, }; + if (union_obj.fields.count() == 0) { + return AbiAlignmentAdvanced{ .scalar = @boolToInt(union_obj.layout == .Extern) }; + } var max_align: u32 = 0; if (have_tag) max_align = union_obj.tag_ty.abiAlignment(target); diff --git a/test/behavior.zig b/test/behavior.zig index 40f8ca0fb3..ba8379cd72 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -167,6 +167,7 @@ test { if (builtin.zig_backend != .stage1) { _ = @import("behavior/decltest.zig"); _ = @import("behavior/packed_struct_explicit_backing_int.zig"); + _ = @import("behavior/empty_union.zig"); } if (builtin.os.tag != .wasi) { diff --git a/test/behavior/empty_union.zig b/test/behavior/empty_union.zig new file mode 100644 index 0000000000..051e464b72 --- /dev/null +++ b/test/behavior/empty_union.zig @@ -0,0 +1,54 @@ +const builtin = @import("builtin"); +const std = @import("std"); +const expect = std.testing.expect; + +test "switch on empty enum" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + + const E = enum {}; + var e: E = undefined; + switch (e) {} +} + +test "switch on empty enum with a specified tag type" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + + const E = enum(u8) {}; + var e: E = undefined; + switch (e) {} +} + +test "switch on empty auto numbered tagged union" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + + const U = union(enum(u8)) {}; + var u: U = undefined; + switch (u) {} +} + +test "switch on empty tagged union" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + + const E = enum {}; + const U = union(E) {}; + var u: U = undefined; + switch (u) {} +} + +test "empty union" { + const U = union {}; + try expect(@sizeOf(U) == 0); + try expect(@alignOf(U) == 0); +} + +test "empty extern union" { + const U = extern union {}; + try expect(@sizeOf(U) == 0); + try expect(@alignOf(U) == 1); +} diff --git a/test/cases/compile_errors/enum_with_0_fields.zig b/test/cases/compile_errors/enum_with_0_fields.zig deleted file mode 100644 index f34065b69d..0000000000 --- a/test/cases/compile_errors/enum_with_0_fields.zig +++ /dev/null @@ -1,7 +0,0 @@ -const Foo = enum {}; - -// error -// backend=stage2 -// target=native -// -// :1:13: error: enum declarations must have at least one tag diff --git a/test/cases/compile_errors/reify_type_for_exhaustive_enum_with_zero_fields.zig b/test/cases/compile_errors/reify_type_for_exhaustive_enum_with_zero_fields.zig deleted file mode 100644 index 44876e938a..0000000000 --- a/test/cases/compile_errors/reify_type_for_exhaustive_enum_with_zero_fields.zig +++ /dev/null @@ -1,18 +0,0 @@ -const Tag = @Type(.{ - .Enum = .{ - .layout = .Auto, - .tag_type = u1, - .fields = &.{}, - .decls = &.{}, - .is_exhaustive = true, - }, -}); -export fn entry() void { - _ = @intToEnum(Tag, 0); -} - -// error -// backend=stage2 -// target=native -// -// :1:13: error: enums must have at least one field diff --git a/test/cases/compile_errors/reify_type_for_union_with_zero_fields.zig b/test/cases/compile_errors/reify_type_for_union_with_zero_fields.zig deleted file mode 100644 index 0b4f395c81..0000000000 --- a/test/cases/compile_errors/reify_type_for_union_with_zero_fields.zig +++ /dev/null @@ -1,17 +0,0 @@ -const Untagged = @Type(.{ - .Union = .{ - .layout = .Auto, - .tag_type = null, - .fields = &.{}, - .decls = &.{}, - }, -}); -export fn entry() void { - _ = Untagged{}; -} - -// error -// backend=stage2 -// target=native -// -// :1:18: error: unions must have at least one field diff --git a/test/cases/compile_errors/union_fields_with_value_assignments.zig b/test/cases/compile_errors/union_fields_with_value_assignments.zig deleted file mode 100644 index 2121568dd2..0000000000 --- a/test/cases/compile_errors/union_fields_with_value_assignments.zig +++ /dev/null @@ -1,7 +0,0 @@ -const Foo = union {}; - -// error -// backend=stage2 -// target=native -// -// :1:13: error: union declarations must have at least one tag diff --git a/test/cases/compile_errors/union_with_0_fields.zig b/test/cases/compile_errors/union_with_0_fields.zig deleted file mode 100644 index 2121568dd2..0000000000 --- a/test/cases/compile_errors/union_with_0_fields.zig +++ /dev/null @@ -1,7 +0,0 @@ -const Foo = union {}; - -// error -// backend=stage2 -// target=native -// -// :1:13: error: union declarations must have at least one tag diff --git a/test/cases/compile_errors/using_invalid_types_in_function_call_raises_an_error.zig b/test/cases/compile_errors/using_invalid_types_in_function_call_raises_an_error.zig deleted file mode 100644 index ee6d1b8b7c..0000000000 --- a/test/cases/compile_errors/using_invalid_types_in_function_call_raises_an_error.zig +++ /dev/null @@ -1,11 +0,0 @@ -const MenuEffect = enum {}; -fn func(effect: MenuEffect) void { _ = effect; } -export fn entry() void { - func(MenuEffect.ThisDoesNotExist); -} - -// error -// backend=stage2 -// target=native -// -// :1:20: error: enum declarations must have at least one tag diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 644cba74c1..321de1e3f6 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -704,15 +704,6 @@ pub fn addCases(ctx: *TestContext) !void { ":5:9: error: '_' is used to mark an enum as non-exhaustive and cannot be assigned a value", }); - case.addError( - \\const E1 = enum {}; - \\export fn foo() void { - \\ _ = E1.a; - \\} - , &.{ - ":1:12: error: enum declarations must have at least one tag", - }); - case.addError( \\const E1 = enum { a, b, _ }; \\export fn foo() void { From 070282a96ec23fb41041843d5753608ec5090f8b Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 17 Aug 2022 16:58:16 +0200 Subject: [PATCH 136/290] libstd: fix off-by-one error in def of ProcSym in pdb Make sure `ProcSym` includes a single element byte-array which delimits the start of the symbol's name as part of its definition. This makes the code more elegant in that accessing the name is equivalent to taking the address of this one element array. --- lib/std/pdb.zig | 9 ++++++--- test/stack_traces.zig | 7 +------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/std/pdb.zig b/lib/std/pdb.zig index a44296c920..00ce2cc5ba 100644 --- a/lib/std/pdb.zig +++ b/lib/std/pdb.zig @@ -310,6 +310,10 @@ pub const SymbolKind = enum(u16) { pub const TypeIndex = u32; +// TODO According to this header: +// https://github.com/microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L3722 +// we should define RecordPrefix as part of the ProcSym structure. +// This might be important when we start generating PDB in self-hosted with our own PE linker. pub const ProcSym = extern struct { Parent: u32, End: u32, @@ -321,8 +325,7 @@ pub const ProcSym = extern struct { CodeOffset: u32, Segment: u16, Flags: ProcSymFlags, - // following is a null terminated string - // Name: [*]u8, + Name: [1]u8, // null-terminated }; pub const ProcSymFlags = packed struct { @@ -693,7 +696,7 @@ pub const Pdb = struct { .S_LPROC32, .S_GPROC32 => { const proc_sym = @ptrCast(*align(1) ProcSym, &module.symbols[symbol_i + @sizeOf(RecordPrefix)]); if (address >= proc_sym.CodeOffset and address < proc_sym.CodeOffset + proc_sym.CodeSize) { - return mem.sliceTo(@ptrCast([*:0]u8, proc_sym) + @sizeOf(ProcSym), 0); + return mem.sliceTo(@ptrCast([*:0]u8, &proc_sym.Name[0]), 0); } }, else => {}, diff --git a/test/stack_traces.zig b/test/stack_traces.zig index e514e0fd88..06534a2f7e 100644 --- a/test/stack_traces.zig +++ b/test/stack_traces.zig @@ -3,11 +3,6 @@ const os = std.os; const tests = @import("tests.zig"); pub fn addCases(cases: *tests.StackTracesContext) void { - if (@import("builtin").os.tag == .windows) { - // https://github.com/ziglang/zig/issues/12422 - return; - } - cases.addCase(.{ .name = "return", .source = @@ -178,7 +173,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void { cases.addCase(.{ .exclude_os = .{ .openbsd, // integer overflow - .windows, + .windows, // TODO intermittent failures }, .name = "dumpCurrentStackTrace", .source = From 07f64a2e13ab80acffba7f7bdd5d7c58df7893c0 Mon Sep 17 00:00:00 2001 From: Martin Hafskjold Thoresen Date: Wed, 10 Aug 2022 23:32:02 +0200 Subject: [PATCH 137/290] Sema: error on ambiguous coercion of comptime float and ints The following, from the documentation as of the time of writing, illustrates the problem: ```zig // Compile time coercion of float to int test "implicit cast to comptime_int" { var f: f32 = 54.0 / 5; _ = f; } ``` It is not clear how to unify the types of 54.0 and 5 to perform the division. We can either - cast 54.0 to comptime_int resulting in @as(comptime_int, 10), which is casted to @as(f32, 10), or - cast 5 to comptime_float resulting in @as(comptime_float, 10.8), which is casted to @as(f32, 10.8) Since the two resulting values are different, a compiler error is appropriate. If we know that casting to either type will result in the same value we don't need to error. For instance, 10.0 / 2 is okay, as is 10 / 2.0. Fixes: #12364 --- src/Sema.zig | 16 ++++++++++++++++ test/behavior/floatop.zig | 8 ++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index f1d140520c..e5700a6fe4 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -11202,6 +11202,22 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs); const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs); + if ((lhs_ty.tag() == .comptime_float and rhs_ty.tag() == .comptime_int) or + (lhs_ty.tag() == .comptime_int and rhs_ty.tag() == .comptime_float)) + { + // If it makes a difference whether we coerce to ints or floats before doing the division, error. + // If lhs % rhs is 0, it doesn't matter. + var lhs_val = maybe_lhs_val orelse unreachable; + var rhs_val = maybe_rhs_val orelse unreachable; + var rem = lhs_val.floatRem(rhs_val, resolved_type, sema.arena, target) catch unreachable; + var float_rem = rem.toFloat(f32); + if (float_rem != 0.0) { + return sema.fail(block, src, "ambiguous coercion of division operands: '{s}' and '{s}': division has non-zero reminder: {d}", .{ + @tagName(lhs_ty.tag()), @tagName(rhs_ty.tag()), float_rem, + }); + } + } + // TODO: emit compile error when .div is used on integers and there would be an // ambiguous result between div_floor and div_trunc. diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig index c057f7a842..a5eb25d4f5 100644 --- a/test/behavior/floatop.zig +++ b/test/behavior/floatop.zig @@ -194,8 +194,8 @@ fn testSin() !void { const eps = epsForType(ty); try expect(@sin(@as(ty, 0)) == 0); try expect(math.approxEqAbs(ty, @sin(@as(ty, std.math.pi)), 0, eps)); - try expect(math.approxEqAbs(ty, @sin(@as(ty, std.math.pi / 2)), 1, eps)); - try expect(math.approxEqAbs(ty, @sin(@as(ty, std.math.pi / 4)), 0.7071067811865475, eps)); + try expect(math.approxEqAbs(ty, @sin(@as(ty, std.math.pi / 2.0)), 1, eps)); + try expect(math.approxEqAbs(ty, @sin(@as(ty, std.math.pi / 4.0)), 0.7071067811865475, eps)); } { @@ -228,8 +228,8 @@ fn testCos() !void { const eps = epsForType(ty); try expect(@cos(@as(ty, 0)) == 1); try expect(math.approxEqAbs(ty, @cos(@as(ty, std.math.pi)), -1, eps)); - try expect(math.approxEqAbs(ty, @cos(@as(ty, std.math.pi / 2)), 0, eps)); - try expect(math.approxEqAbs(ty, @cos(@as(ty, std.math.pi / 4)), 0.7071067811865475, eps)); + try expect(math.approxEqAbs(ty, @cos(@as(ty, std.math.pi / 2.0)), 0, eps)); + try expect(math.approxEqAbs(ty, @cos(@as(ty, std.math.pi / 4.0)), 0.7071067811865475, eps)); } { From 59b6483d63ca68f63c0b3758e80da918629ada2c Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 16 Aug 2022 16:17:56 +0300 Subject: [PATCH 138/290] add test --- src/Sema.zig | 17 +++++++------ ...mbiguous_coercion_of_division_operands.zig | 24 +++++++++++++++++++ 2 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 test/cases/compile_errors/ambiguous_coercion_of_division_operands.zig diff --git a/src/Sema.zig b/src/Sema.zig index e5700a6fe4..29a53ebb2a 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -11202,18 +11202,17 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs); const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs); - if ((lhs_ty.tag() == .comptime_float and rhs_ty.tag() == .comptime_int) or - (lhs_ty.tag() == .comptime_int and rhs_ty.tag() == .comptime_float)) + if ((lhs_ty.zigTypeTag() == .ComptimeFloat and rhs_ty.zigTypeTag() == .ComptimeInt) or + (lhs_ty.zigTypeTag() == .ComptimeInt and rhs_ty.zigTypeTag() == .ComptimeFloat)) { // If it makes a difference whether we coerce to ints or floats before doing the division, error. // If lhs % rhs is 0, it doesn't matter. - var lhs_val = maybe_lhs_val orelse unreachable; - var rhs_val = maybe_rhs_val orelse unreachable; - var rem = lhs_val.floatRem(rhs_val, resolved_type, sema.arena, target) catch unreachable; - var float_rem = rem.toFloat(f32); - if (float_rem != 0.0) { - return sema.fail(block, src, "ambiguous coercion of division operands: '{s}' and '{s}': division has non-zero reminder: {d}", .{ - @tagName(lhs_ty.tag()), @tagName(rhs_ty.tag()), float_rem, + const lhs_val = maybe_lhs_val orelse unreachable; + const rhs_val = maybe_rhs_val orelse unreachable; + const rem = lhs_val.floatRem(rhs_val, resolved_type, sema.arena, target) catch unreachable; + if (rem.compareWithZero(.neq)) { + return sema.fail(block, src, "ambiguous coercion of division operands '{s}' and '{s}'; division has non-zero reminder '{}'", .{ + @tagName(lhs_ty.tag()), @tagName(rhs_ty.tag()), rem.fmtValue(resolved_type, sema.mod), }); } } diff --git a/test/cases/compile_errors/ambiguous_coercion_of_division_operands.zig b/test/cases/compile_errors/ambiguous_coercion_of_division_operands.zig new file mode 100644 index 0000000000..d8fa4e5726 --- /dev/null +++ b/test/cases/compile_errors/ambiguous_coercion_of_division_operands.zig @@ -0,0 +1,24 @@ +export fn entry1() void { + var f: f32 = 54.0 / 5; + _ = f; +} +export fn entry2() void { + var f: f32 = 54 / 5.0; + _ = f; +} +export fn entry3() void { + var f: f32 = 55.0 / 5; + _ = f; +} +export fn entry4() void { + var f: f32 = 55 / 5.0; + _ = f; +} + + +// error +// backend=stage2 +// target=native +// +// :2:23: error: ambiguous coercion of division operands 'comptime_float' and 'comptime_int'; division has non-zero reminder '4' +// :6:21: error: ambiguous coercion of division operands 'comptime_int' and 'comptime_float'; division has non-zero reminder '4' From 2cccd144914d8715894458f317fa4c3c572cdcad Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 17 Aug 2022 14:06:47 -0700 Subject: [PATCH 139/290] fix typo in compile error message --- src/Sema.zig | 2 +- .../ambiguous_coercion_of_division_operands.zig | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 29a53ebb2a..5fd1c1ce4c 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -11211,7 +11211,7 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins const rhs_val = maybe_rhs_val orelse unreachable; const rem = lhs_val.floatRem(rhs_val, resolved_type, sema.arena, target) catch unreachable; if (rem.compareWithZero(.neq)) { - return sema.fail(block, src, "ambiguous coercion of division operands '{s}' and '{s}'; division has non-zero reminder '{}'", .{ + return sema.fail(block, src, "ambiguous coercion of division operands '{s}' and '{s}'; non-zero remainder '{}'", .{ @tagName(lhs_ty.tag()), @tagName(rhs_ty.tag()), rem.fmtValue(resolved_type, sema.mod), }); } diff --git a/test/cases/compile_errors/ambiguous_coercion_of_division_operands.zig b/test/cases/compile_errors/ambiguous_coercion_of_division_operands.zig index d8fa4e5726..f3e51a1bed 100644 --- a/test/cases/compile_errors/ambiguous_coercion_of_division_operands.zig +++ b/test/cases/compile_errors/ambiguous_coercion_of_division_operands.zig @@ -15,10 +15,9 @@ export fn entry4() void { _ = f; } - // error // backend=stage2 // target=native // -// :2:23: error: ambiguous coercion of division operands 'comptime_float' and 'comptime_int'; division has non-zero reminder '4' -// :6:21: error: ambiguous coercion of division operands 'comptime_int' and 'comptime_float'; division has non-zero reminder '4' +// :2:23: error: ambiguous coercion of division operands 'comptime_float' and 'comptime_int'; non-zero remainder '4' +// :6:21: error: ambiguous coercion of division operands 'comptime_int' and 'comptime_float'; non-zero remainder '4' From 9b595dd55f1ae336e2fb4e2a64334efe137dc19a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 17 Aug 2022 10:46:42 +0200 Subject: [PATCH 140/290] link-test: refactor and reorg Move common tests by target file format (Wasm, MachO) into helper functions in `link.zig`, and sort alphabetically within for easier tracking versus file organization on disk. --- test/link.zig | 97 ++++++++++++----------- test/link/macho/dead_strip/build.zig | 8 +- test/link/macho/pagezero/build.zig | 5 +- test/link/macho/search_strategy/build.zig | 8 +- test/link/macho/stack_size/build.zig | 3 +- test/link/macho/tls/build.zig | 5 +- 6 files changed, 69 insertions(+), 57 deletions(-) diff --git a/test/link.zig b/test/link.zig index a8a39a7018..88b370f343 100644 --- a/test/link.zig +++ b/test/link.zig @@ -23,7 +23,12 @@ pub fn addCases(cases: *tests.StandaloneContext) void { .build_modes = true, }); - cases.addBuildFile("test/link/wasm/type/build.zig", .{ + addWasmCases(cases); + addMachOCases(cases); +} + +fn addWasmCases(cases: *tests.StandaloneContext) void { + cases.addBuildFile("test/link/wasm/bss/build.zig", .{ .build_modes = true, .requires_stage2 = true, }); @@ -38,23 +43,13 @@ pub fn addCases(cases: *tests.StandaloneContext) void { .requires_stage2 = true, }); - cases.addBuildFile("test/link/wasm/bss/build.zig", .{ + cases.addBuildFile("test/link/wasm/type/build.zig", .{ .build_modes = true, .requires_stage2 = true, }); +} - cases.addBuildFile("test/link/macho/entry/build.zig", .{ - .build_modes = true, - }); - - cases.addBuildFile("test/link/macho/pagezero/build.zig", .{ - .build_modes = false, - }); - - cases.addBuildFile("test/link/macho/dylib/build.zig", .{ - .build_modes = true, - }); - +fn addMachOCases(cases: *tests.StandaloneContext) void { cases.addBuildFile("test/link/macho/dead_strip/build.zig", .{ .build_modes = false, }); @@ -64,41 +59,11 @@ pub fn addCases(cases: *tests.StandaloneContext) void { .requires_macos_sdk = true, }); - cases.addBuildFile("test/link/macho/needed_library/build.zig", .{ + cases.addBuildFile("test/link/macho/dylib/build.zig", .{ .build_modes = true, }); - cases.addBuildFile("test/link/macho/weak_library/build.zig", .{ - .build_modes = true, - }); - - cases.addBuildFile("test/link/macho/needed_framework/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - }); - - cases.addBuildFile("test/link/macho/weak_framework/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - }); - - // Try to build and run an Objective-C executable. - cases.addBuildFile("test/link/macho/objc/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - }); - - // Try to build and run an Objective-C++ executable. - cases.addBuildFile("test/link/macho/objcpp/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - }); - - cases.addBuildFile("test/link/macho/stack_size/build.zig", .{ - .build_modes = true, - }); - - cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{ + cases.addBuildFile("test/link/macho/entry/build.zig", .{ .build_modes = true, }); @@ -107,7 +72,47 @@ pub fn addCases(cases: *tests.StandaloneContext) void { .requires_macos_sdk = true, }); + cases.addBuildFile("test/link/macho/needed_framework/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); + + cases.addBuildFile("test/link/macho/needed_library/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/macho/objc/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); + + cases.addBuildFile("test/link/macho/objcpp/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); + + cases.addBuildFile("test/link/macho/pagezero/build.zig", .{ + .build_modes = false, + }); + + cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/macho/stack_size/build.zig", .{ + .build_modes = true, + }); + cases.addBuildFile("test/link/macho/tls/build.zig", .{ .build_modes = true, }); + + cases.addBuildFile("test/link/macho/weak_library/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/macho/weak_framework/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); } diff --git a/test/link/macho/dead_strip/build.zig b/test/link/macho/dead_strip/build.zig index dea225dd6f..25759f5619 100644 --- a/test/link/macho/dead_strip/build.zig +++ b/test/link/macho/dead_strip/build.zig @@ -4,13 +4,14 @@ const LibExeObjectStep = std.build.LibExeObjStep; pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const test_step = b.step("test", "Test the program"); test_step.dependOn(b.getInstallStep()); { // Without -dead_strip, we expect `iAmUnused` symbol present - const exe = createScenario(b, mode); + const exe = createScenario(b, mode, target); const check = exe.checkObject(.macho); check.checkInSymtab(); @@ -23,7 +24,7 @@ pub fn build(b: *Builder) void { { // With -dead_strip, no `iAmUnused` symbol should be present - const exe = createScenario(b, mode); + const exe = createScenario(b, mode, target); exe.link_gc_sections = true; const check = exe.checkObject(.macho); @@ -36,10 +37,11 @@ pub fn build(b: *Builder) void { } } -fn createScenario(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep { +fn createScenario(b: *Builder, mode: std.builtin.Mode, target: std.zig.CrossTarget) *LibExeObjectStep { const exe = b.addExecutable("test", null); exe.addCSourceFile("main.c", &[0][]const u8{}); exe.setBuildMode(mode); + exe.setTarget(target); exe.linkLibC(); return exe; } diff --git a/test/link/macho/pagezero/build.zig b/test/link/macho/pagezero/build.zig index 9dbc0e6473..5a7044d960 100644 --- a/test/link/macho/pagezero/build.zig +++ b/test/link/macho/pagezero/build.zig @@ -3,13 +3,14 @@ const Builder = std.build.Builder; pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const test_step = b.step("test", "Test"); test_step.dependOn(b.getInstallStep()); { const exe = b.addExecutable("pagezero", null); - exe.setTarget(.{ .os_tag = .macos }); + exe.setTarget(target); exe.setBuildMode(mode); exe.addCSourceFile("main.c", &.{}); exe.linkLibC(); @@ -29,7 +30,7 @@ pub fn build(b: *Builder) void { { const exe = b.addExecutable("no_pagezero", null); - exe.setTarget(.{ .os_tag = .macos }); + exe.setTarget(target); exe.setBuildMode(mode); exe.addCSourceFile("main.c", &.{}); exe.linkLibC(); diff --git a/test/link/macho/search_strategy/build.zig b/test/link/macho/search_strategy/build.zig index 39a82bc6a7..e556b5bb23 100644 --- a/test/link/macho/search_strategy/build.zig +++ b/test/link/macho/search_strategy/build.zig @@ -1,17 +1,17 @@ const std = @import("std"); const Builder = std.build.Builder; const LibExeObjectStep = std.build.LibExeObjStep; -const target: std.zig.CrossTarget = .{ .os_tag = .macos }; pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const test_step = b.step("test", "Test"); test_step.dependOn(b.getInstallStep()); { // -search_dylibs_first - const exe = createScenario(b, mode); + const exe = createScenario(b, mode, target); exe.search_strategy = .dylibs_first; const check = exe.checkObject(.macho); @@ -26,7 +26,7 @@ pub fn build(b: *Builder) void { { // -search_paths_first - const exe = createScenario(b, mode); + const exe = createScenario(b, mode, target); exe.search_strategy = .paths_first; const run = std.build.EmulatableRunStep.create(b, "run", exe); @@ -36,7 +36,7 @@ pub fn build(b: *Builder) void { } } -fn createScenario(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep { +fn createScenario(b: *Builder, mode: std.builtin.Mode, target: std.zig.CrossTarget) *LibExeObjectStep { const static = b.addStaticLibrary("a", null); static.setTarget(target); static.setBuildMode(mode); diff --git a/test/link/macho/stack_size/build.zig b/test/link/macho/stack_size/build.zig index 3abf48df7a..91c44baf52 100644 --- a/test/link/macho/stack_size/build.zig +++ b/test/link/macho/stack_size/build.zig @@ -3,12 +3,13 @@ const Builder = std.build.Builder; pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const test_step = b.step("test", "Test"); test_step.dependOn(b.getInstallStep()); const exe = b.addExecutable("main", null); - exe.setTarget(.{ .os_tag = .macos }); + exe.setTarget(target); exe.setBuildMode(mode); exe.addCSourceFile("main.c", &.{}); exe.linkLibC(); diff --git a/test/link/macho/tls/build.zig b/test/link/macho/tls/build.zig index 7bf3ea1c9e..031a05cedf 100644 --- a/test/link/macho/tls/build.zig +++ b/test/link/macho/tls/build.zig @@ -1,4 +1,5 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); +const Builder = std.build.Builder; pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); @@ -6,11 +7,13 @@ pub fn build(b: *Builder) void { const lib = b.addSharedLibrary("a", null, b.version(1, 0, 0)); lib.setBuildMode(mode); + lib.setTarget(target); lib.addCSourceFile("a.c", &.{}); lib.linkLibC(); const test_exe = b.addTest("main.zig"); test_exe.setBuildMode(mode); + test_exe.setTarget(target); test_exe.linkLibrary(lib); test_exe.linkLibC(); From 515ee5b2fa0161e3df4cfe47c71847f8b3476bf2 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 17 Aug 2022 13:30:23 +0200 Subject: [PATCH 141/290] libstd: do not follow symlinks in renameatW This correctly mimicks POSIX behavior. --- lib/std/os.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/os.zig b/lib/std/os.zig index 984758565c..c757561b07 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2651,6 +2651,7 @@ pub fn renameatW( .creation = windows.FILE_OPEN, .io_mode = .blocking, .filter = .any, // This function is supposed to rename both files and directories. + .follow_symlinks = false, }) catch |err| switch (err) { error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. else => |e| return e, From b6ce0cce69e4bf37509f99040abda4ca92bec6cb Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 17 Aug 2022 14:53:20 +0200 Subject: [PATCH 142/290] windows-ci: pass -Domit-stage2 to test-toolchain --- ci/azure/pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/azure/pipelines.yml b/ci/azure/pipelines.yml index 5ff0a5d61b..5d208b31a2 100644 --- a/ci/azure/pipelines.yml +++ b/ci/azure/pipelines.yml @@ -103,7 +103,7 @@ jobs: #& "$ZIGINSTALLDIR\bin\zig.exe" test "..\test\behavior.zig" -fno-stage1 -fLLVM -I "..\test" 2>&1 #CheckLastExitCode - & "$ZIGINSTALLDIR\bin\zig.exe" build test-toolchain -Dskip-non-native -Dskip-stage2-tests 2>&1 + & "$ZIGINSTALLDIR\bin\zig.exe" build test-toolchain -Dskip-non-native -Dskip-stage2-tests -Domit-stage2 2>&1 CheckLastExitCode & "$ZIGINSTALLDIR\bin\zig.exe" build test-std -Dskip-non-native 2>&1 CheckLastExitCode From 79757f233d9bfc646caa13d20243266a19bbdf91 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Wed, 17 Aug 2022 21:28:11 +0200 Subject: [PATCH 143/290] fix memory leak in NativePaths.zig --- lib/std/zig/system/NativePaths.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/std/zig/system/NativePaths.zig b/lib/std/zig/system/NativePaths.zig index e9e7460314..5f52b04ce4 100644 --- a/lib/std/zig/system/NativePaths.zig +++ b/lib/std/zig/system/NativePaths.zig @@ -109,6 +109,8 @@ pub fn detect(allocator: Allocator, native_info: NativeTargetInfo) !NativePaths if (native_target.os.tag != .windows) { const triple = try native_target.linuxTriple(allocator); + defer allocator.free(triple); + const qual = native_target.cpu.arch.ptrBitWidth(); // TODO: $ ld --verbose | grep SEARCH_DIR From 656b9429d05c4aad2d514e6d4df15326d903e3f4 Mon Sep 17 00:00:00 2001 From: Der Teufel Date: Thu, 18 Aug 2022 14:03:42 +0200 Subject: [PATCH 144/290] autodoc: An attempt at generating HTML files from all imported source files. Files generated from the standard library could be considered for placing with main.js and index.html in lib/docs. Paths should reflect packages in the future. --- lib/docs/main.js | 2 +- src/Autodoc.zig | 42 +++++ src/Docgen.zig | 424 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 467 insertions(+), 1 deletion(-) create mode 100644 src/Docgen.zig diff --git a/lib/docs/main.js b/lib/docs/main.js index 0a99432c67..a0b8001a9e 100644 --- a/lib/docs/main.js +++ b/lib/docs/main.js @@ -51,7 +51,7 @@ var zigAnalysis; const domHdrName = document.getElementById("hdrName"); const domHelpModal = document.getElementById("helpModal"); const domSearchPlaceholder = document.getElementById("searchPlaceholder"); - const sourceFileUrlTemplate = "/src-viewer/{{file}}#L{{line}}" + const sourceFileUrlTemplate = "src-viewer/{{file}}#L{{line}}" const domLangRefLink = document.getElementById("langRefLink"); let lineCounter = 1; diff --git a/src/Autodoc.zig b/src/Autodoc.zig index d90ebc3de8..2364a40f9f 100644 --- a/src/Autodoc.zig +++ b/src/Autodoc.zig @@ -9,6 +9,7 @@ const Package = @import("Package.zig"); const Zir = @import("Zir.zig"); const Ref = Zir.Inst.Ref; const log = std.log.scoped(.autodoc); +const Docgen = @import("Docgen.zig"); module: *Module, doc_location: Compilation.EmitLoc, @@ -266,6 +267,27 @@ pub fn generateZirData(self: *Autodoc) !void { try buffer.flush(); } + output_dir.makeDir("src-viewer") catch |e| switch (e) { + error.PathAlreadyExists => {}, + else => |err| return err, + }; + const html_dir = try output_dir.openDir("src-viewer", .{}); + + var files_iterator = self.files.iterator(); + + while (files_iterator.next()) |entry| { + const new_html_path = entry.key_ptr.*.sub_file_path; + + const html_file = try createFromPath(html_dir, new_html_path); + defer html_file.close(); + var buffer = std.io.bufferedWriter(html_file.writer()); + + const out = buffer.writer(); + + try Docgen.genHtml(self.module.gpa, entry.key_ptr.*, out); + try buffer.flush(); + } + // copy main.js, index.html var docs_dir = try self.module.comp.zig_lib_directory.handle.openDir("docs", .{}); defer docs_dir.close(); @@ -273,6 +295,26 @@ pub fn generateZirData(self: *Autodoc) !void { try docs_dir.copyFile("index.html", output_dir, "index.html", .{}); } +fn createFromPath(base_dir: std.fs.Dir, path: []const u8) !std.fs.File { + var path_tokens = std.mem.tokenize(u8, path, std.fs.path.sep_str); + var dir = base_dir; + while (path_tokens.next()) |toc| { + if (path_tokens.peek() != null) { + dir.makeDir(toc) catch |e| switch (e) { + error.PathAlreadyExists => {}, + else => |err| return err, + }; + dir = try dir.openDir(toc, .{}); + } else { + return dir.createFile(toc, .{}) catch |e| switch (e) { + error.PathAlreadyExists => try dir.openFile(toc, .{}), + else => |e| return e, + }; + } + } + return error.EmptyPath; +} + /// Represents a chain of scopes, used to resolve decl references to the /// corresponding entry in `self.decls`. const Scope = struct { diff --git a/src/Docgen.zig b/src/Docgen.zig new file mode 100644 index 0000000000..294ff359f0 --- /dev/null +++ b/src/Docgen.zig @@ -0,0 +1,424 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const io = std.io; +const fs = std.fs; +const process = std.process; +const ChildProcess = std.ChildProcess; +const Progress = std.Progress; +const print = std.debug.print; +const mem = std.mem; +const testing = std.testing; +const Allocator = std.mem.Allocator; +const Module = @import("Module.zig"); + +pub fn genHtml( + allocator: Allocator, + src: *Module.File, + out: anytype, +) !void { + try out.writeAll( + \\ + \\ + \\ + \\ + \\ + ); + try out.print(" {s} - source view\n", .{src.sub_file_path}); + try out.writeAll(\\ + \\ + \\ + \\ + \\ + ); + + const source = try src.getSource(allocator); + try tokenizeAndPrintRaw(allocator, out, source.bytes); + try out.writeAll(\\ + \\ + ); +} + +const start_line = ""; +const end_line = "\n"; + + + var line_counter: usize = 1; + +pub fn tokenizeAndPrintRaw( + allocator: Allocator, + out: anytype, + raw_src: [:0]const u8, +) !void { + const src = try allocator.dupeZ(u8, raw_src); + defer allocator.free(src); + + line_counter = 1; + + try out.print("
    " ++ start_line, .{line_counter});
    +    var tokenizer = std.zig.Tokenizer.init(src);
    +    var index: usize = 0;
    +    var next_tok_is_fn = false;
    +    while (true) {
    +        const prev_tok_was_fn = next_tok_is_fn;
    +        next_tok_is_fn = false;
    +
    +        const token = tokenizer.next();
    +        if (mem.indexOf(u8, src[index..token.loc.start], "//")) |comment_start_off| {
    +            // render one comment
    +            const comment_start = index + comment_start_off;
    +            const comment_end_off = mem.indexOf(u8, src[comment_start..token.loc.start], "\n");
    +            const comment_end = if (comment_end_off) |o| comment_start + o else token.loc.start;
    +
    +            try writeEscapedLines(out, src[index..comment_start]);
    +            try out.writeAll("");
    +            try writeEscaped(out, src[comment_start..comment_end]);
    +            try out.writeAll("\n");
    +            index = comment_end;
    +            tokenizer.index = index;
    +            continue;
    +        }
    +
    +        try writeEscapedLines(out, src[index..token.loc.start]);
    +        switch (token.tag) {
    +            .eof => break,
    +
    +            .keyword_addrspace,
    +            .keyword_align,
    +            .keyword_and,
    +            .keyword_asm,
    +            .keyword_async,
    +            .keyword_await,
    +            .keyword_break,
    +            .keyword_catch,
    +            .keyword_comptime,
    +            .keyword_const,
    +            .keyword_continue,
    +            .keyword_defer,
    +            .keyword_else,
    +            .keyword_enum,
    +            .keyword_errdefer,
    +            .keyword_error,
    +            .keyword_export,
    +            .keyword_extern,
    +            .keyword_for,
    +            .keyword_if,
    +            .keyword_inline,
    +            .keyword_noalias,
    +            .keyword_noinline,
    +            .keyword_nosuspend,
    +            .keyword_opaque,
    +            .keyword_or,
    +            .keyword_orelse,
    +            .keyword_packed,
    +            .keyword_anyframe,
    +            .keyword_pub,
    +            .keyword_resume,
    +            .keyword_return,
    +            .keyword_linksection,
    +            .keyword_callconv,
    +            .keyword_struct,
    +            .keyword_suspend,
    +            .keyword_switch,
    +            .keyword_test,
    +            .keyword_threadlocal,
    +            .keyword_try,
    +            .keyword_union,
    +            .keyword_unreachable,
    +            .keyword_usingnamespace,
    +            .keyword_var,
    +            .keyword_volatile,
    +            .keyword_allowzero,
    +            .keyword_while,
    +            .keyword_anytype,
    +            => {
    +                try out.writeAll("");
    +                try writeEscaped(out, src[token.loc.start..token.loc.end]);
    +                try out.writeAll("");
    +            },
    +
    +            .keyword_fn => {
    +                try out.writeAll("");
    +                try writeEscaped(out, src[token.loc.start..token.loc.end]);
    +                try out.writeAll("");
    +                next_tok_is_fn = true;
    +            },
    +
    +            .string_literal,
    +            .char_literal,
    +            => {
    +                try out.writeAll("");
    +                try writeEscaped(out, src[token.loc.start..token.loc.end]);
    +                try out.writeAll("");
    +            },
    +
    +            .multiline_string_literal_line => {
    +                if (src[token.loc.end - 1] == '\n') {
    +                    try out.writeAll("");
    +                    try writeEscaped(out, src[token.loc.start .. token.loc.end - 1]);
    +                    line_counter += 1;
    +                    try out.print("" ++ end_line ++ "\n" ++ start_line, .{line_counter});
    +                } else {
    +                    try out.writeAll("");
    +                    try writeEscaped(out, src[token.loc.start..token.loc.end]);
    +                    try out.writeAll("");
    +                }
    +            },
    +
    +            .builtin => {
    +                try out.writeAll("");
    +                try writeEscaped(out, src[token.loc.start..token.loc.end]);
    +                try out.writeAll("");
    +            },
    +
    +            .doc_comment,
    +            .container_doc_comment,
    +            => {
    +                try out.writeAll("");
    +                try writeEscaped(out, src[token.loc.start..token.loc.end]);
    +                try out.writeAll("");
    +            },
    +
    +            .identifier => {
    +                const tok_bytes = src[token.loc.start..token.loc.end];
    +                if (mem.eql(u8, tok_bytes, "undefined") or
    +                    mem.eql(u8, tok_bytes, "null") or
    +                    mem.eql(u8, tok_bytes, "true") or
    +                    mem.eql(u8, tok_bytes, "false"))
    +                {
    +                    try out.writeAll("");
    +                    try writeEscaped(out, tok_bytes);
    +                    try out.writeAll("");
    +                } else if (prev_tok_was_fn) {
    +                    try out.writeAll("");
    +                    try writeEscaped(out, tok_bytes);
    +                    try out.writeAll("");
    +                } else {
    +                    const is_int = blk: {
    +                        if (src[token.loc.start] != 'i' and src[token.loc.start] != 'u')
    +                            break :blk false;
    +                        var i = token.loc.start + 1;
    +                        if (i == token.loc.end)
    +                            break :blk false;
    +                        while (i != token.loc.end) : (i += 1) {
    +                            if (src[i] < '0' or src[i] > '9')
    +                                break :blk false;
    +                        }
    +                        break :blk true;
    +                    };
    +                    if (is_int or isType(tok_bytes)) {
    +                        try out.writeAll("");
    +                        try writeEscaped(out, tok_bytes);
    +                        try out.writeAll("");
    +                    } else {
    +                        try writeEscaped(out, tok_bytes);
    +                    }
    +                }
    +            },
    +
    +            .integer_literal,
    +            .float_literal,
    +            => {
    +                try out.writeAll("");
    +                try writeEscaped(out, src[token.loc.start..token.loc.end]);
    +                try out.writeAll("");
    +            },
    +
    +            .bang,
    +            .pipe,
    +            .pipe_pipe,
    +            .pipe_equal,
    +            .equal,
    +            .equal_equal,
    +            .equal_angle_bracket_right,
    +            .bang_equal,
    +            .l_paren,
    +            .r_paren,
    +            .semicolon,
    +            .percent,
    +            .percent_equal,
    +            .l_brace,
    +            .r_brace,
    +            .l_bracket,
    +            .r_bracket,
    +            .period,
    +            .period_asterisk,
    +            .ellipsis2,
    +            .ellipsis3,
    +            .caret,
    +            .caret_equal,
    +            .plus,
    +            .plus_plus,
    +            .plus_equal,
    +            .plus_percent,
    +            .plus_percent_equal,
    +            .plus_pipe,
    +            .plus_pipe_equal,
    +            .minus,
    +            .minus_equal,
    +            .minus_percent,
    +            .minus_percent_equal,
    +            .minus_pipe,
    +            .minus_pipe_equal,
    +            .asterisk,
    +            .asterisk_equal,
    +            .asterisk_asterisk,
    +            .asterisk_percent,
    +            .asterisk_percent_equal,
    +            .asterisk_pipe,
    +            .asterisk_pipe_equal,
    +            .arrow,
    +            .colon,
    +            .slash,
    +            .slash_equal,
    +            .comma,
    +            .ampersand,
    +            .ampersand_equal,
    +            .question_mark,
    +            .angle_bracket_left,
    +            .angle_bracket_left_equal,
    +            .angle_bracket_angle_bracket_left,
    +            .angle_bracket_angle_bracket_left_equal,
    +            .angle_bracket_angle_bracket_left_pipe,
    +            .angle_bracket_angle_bracket_left_pipe_equal,
    +            .angle_bracket_right,
    +            .angle_bracket_right_equal,
    +            .angle_bracket_angle_bracket_right,
    +            .angle_bracket_angle_bracket_right_equal,
    +            .tilde,
    +            => try writeEscaped(out, src[token.loc.start..token.loc.end]),
    +
    +            .invalid, .invalid_periodasterisks => return error.ParseError,
    +        }
    +        index = token.loc.end;
    +    }
    +    try out.writeAll(end_line ++ "
    "); +} + +fn writeEscapedLines(out: anytype, text: []const u8) !void { + for (text) |char| { + if (char == '\n') { + try out.writeAll(end_line); + line_counter += 1; + try out.print(start_line, .{line_counter}); + } else { + try writeEscaped(out, &[_]u8{char}); + } + } +} + +fn writeEscaped(out: anytype, input: []const u8) !void { + for (input) |c| { + try switch (c) { + '&' => out.writeAll("&"), + '<' => out.writeAll("<"), + '>' => out.writeAll(">"), + '"' => out.writeAll("""), + else => out.writeByte(c), + }; + } +} + +const builtin_types = [_][]const u8{ + "f16", "f32", "f64", "f128", "c_longdouble", "c_short", + "c_ushort", "c_int", "c_uint", "c_long", "c_ulong", "c_longlong", + "c_ulonglong", "c_char", "anyopaque", "void", "bool", "isize", + "usize", "noreturn", "type", "anyerror", "comptime_int", "comptime_float", +}; + +fn isType(name: []const u8) bool { + for (builtin_types) |t| { + if (mem.eql(u8, t, name)) + return true; + } + return false; +} From 63c25cc1cc4aef1ae5c7425496d99b30db2f44d7 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Tue, 16 Aug 2022 20:41:48 +0200 Subject: [PATCH 145/290] wasm: fix callInstrinsic return value Rather than storing it in a local and returning that, we now keep this on the stack as all internal functions expect it to be on the stack already and therefore were generating extra `local.set` instructions. --- src/arch/wasm/CodeGen.zig | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 8eb3e2175b..95a0a8e4aa 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -4106,7 +4106,6 @@ fn fpext(self: *Self, operand: WValue, given: Type, wanted: Type) InnerError!WVa return f32_result; } if (wanted_bits == 64) { - try self.emitWValue(f32_result); try self.addTag(.f64_promote_f32); return WValue{ .stack = {} }; } @@ -4628,7 +4627,6 @@ fn airMulAdd(self: *Self, inst: Air.Inst.Index) InnerError!WValue { Type.f32, &.{ rhs_ext, lhs_ext, addend_ext }, ); - defer result.free(self); return try (try self.fptrunc(result, Type.f32, ty)).toLocal(self, ty); } @@ -5353,6 +5351,7 @@ fn airShlSat(self: *Self, inst: Air.Inst.Index) InnerError!WValue { /// This function call assumes the C-ABI. /// Asserts arguments are not stack values when the return value is /// passed as the first parameter. +/// May leave the return value on the stack. fn callIntrinsic( self: *Self, name: []const u8, @@ -5398,8 +5397,6 @@ fn callIntrinsic( } else if (want_sret_param) { return sret; } else { - const result_local = try self.allocLocal(return_type); - try self.addLabel(.local_set, result_local.local); - return result_local; + return WValue{ .stack = {} }; } } From 4f2143beccb822f42c77d1d6c5ad31388123803e Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Tue, 16 Aug 2022 20:50:50 +0200 Subject: [PATCH 146/290] link/Wasm: improve symbol resolution This adds additional checks during symbol resolution: - Ensures function signatures match when a symbol will be replaced. - Ensures global types match when the symbol is being replaced. - When both symbols are undefined, ensures they have a matching module name. Those changes ensure the result will pass the validator when the runtime compiles the Wasm module. Additionally, this also slightly changes the behavior when both the existing symbol and new symbol are both defined. Rather than always resulting in a collision, it only results in a collision when both are also weak. Else, the non-weak symbol will be picked. --- src/link/Wasm.zig | 100 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 4 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 30e2192268..1bdd9426a5 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -463,8 +463,6 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void { continue; } - // TODO: Store undefined symbols so we can verify at the end if they've all been found - // if not, emit an error (unless --allow-undefined is enabled). const maybe_existing = try self.globals.getOrPut(self.base.allocator, sym_name_index); if (!maybe_existing.found_existing) { maybe_existing.value_ptr.* = location; @@ -483,8 +481,15 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void { break :blk self.objects.items[file].name; } else self.name; - if (!existing_sym.isUndefined()) { - if (!symbol.isUndefined()) { + if (!existing_sym.isUndefined()) outer: { + if (!symbol.isUndefined()) inner: { + if (symbol.isWeak()) { + break :inner; // ignore the new symbol (discard it) + } + if (existing_sym.isWeak()) { + break :outer; // existing is weak, while new one isn't. Replace it. + } + // both are defined and weak, we have a symbol collision. log.err("symbol '{s}' defined multiple times", .{sym_name}); log.err(" first definition in '{s}'", .{existing_file_path}); log.err(" next definition in '{s}'", .{object.name}); @@ -502,6 +507,53 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void { return error.SymbolMismatchingType; } + if (existing_sym.isUndefined() and symbol.isUndefined()) { + const existing_name = if (existing_loc.file) |file_index| blk: { + const obj = self.objects.items[file_index]; + const name_index = obj.findImport(symbol.tag.externalType(), existing_sym.index).module_name; + break :blk obj.string_table.get(name_index); + } else blk: { + const name_index = self.imports.get(existing_loc).?.module_name; + break :blk self.string_table.get(name_index); + }; + + const module_index = object.findImport(symbol.tag.externalType(), symbol.index).module_name; + const module_name = object.string_table.get(module_index); + if (!mem.eql(u8, existing_name, module_name)) { + log.err("symbol '{s}' module name mismatch. Expected '{s}', but found '{s}'", .{ + sym_name, + existing_name, + module_name, + }); + log.err(" first definition in '{s}'", .{existing_file_path}); + log.err(" next definition in '{s}'", .{object.name}); + return error.ModuleNameMismatch; + } + } + + if (existing_sym.tag == .global) { + const existing_ty = self.getGlobalType(existing_loc); + const new_ty = self.getGlobalType(location); + if (existing_ty.mutable != new_ty.mutable or existing_ty.valtype != new_ty.valtype) { + log.err("symbol '{s}' mismatching global types", .{sym_name}); + log.err(" first definition in '{s}'", .{existing_file_path}); + log.err(" next definition in '{s}'", .{object.name}); + return error.GlobalTypeMismatch; + } + } + + if (existing_sym.tag == .function) { + const existing_ty = self.getFunctionSignature(existing_loc); + const new_ty = self.getFunctionSignature(location); + if (!existing_ty.eql(new_ty)) { + log.err("symbol '{s}' mismatching function signatures.", .{sym_name}); + log.err(" expected signature {}, but found signature {}", .{ existing_ty, new_ty }); + log.err(" first definition in '{s}'", .{existing_file_path}); + log.err(" next definition in '{s}'", .{object.name}); + return error.FunctionSignatureMismatch; + } + } + // when both symbols are weak, we skip overwriting if (existing_sym.isWeak() and symbol.isWeak()) { try self.discarded.put(self.base.allocator, location, existing_loc); @@ -797,6 +849,46 @@ fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, code: []const u8) !void { try self.resolved_symbols.put(self.base.allocator, atom.symbolLoc(), {}); } +/// From a given symbol location, returns its `wasm.GlobalType`. +/// Asserts the Symbol represents a global. +fn getGlobalType(self: *const Wasm, loc: SymbolLoc) wasm.GlobalType { + const symbol = loc.getSymbol(self); + assert(symbol.tag == .global); + const is_undefined = symbol.isUndefined(); + if (loc.file) |file_index| { + const obj: Object = self.objects.items[file_index]; + if (is_undefined) { + return obj.findImport(.global, symbol.index).kind.global; + } + return obj.globals[symbol.index].global_type; + } + if (is_undefined) { + return self.imports.get(loc).?.kind.global; + } + return self.wasm_globals.items[symbol.index].global_type; +} + +/// From a given symbol location, returns its `wasm.Type`. +/// Asserts the Symbol represents a function. +fn getFunctionSignature(self: *const Wasm, loc: SymbolLoc) wasm.Type { + const symbol = loc.getSymbol(self); + assert(symbol.tag == .function); + const is_undefined = symbol.isUndefined(); + if (loc.file) |file_index| { + const obj: Object = self.objects.items[file_index]; + if (is_undefined) { + const ty_index = obj.findImport(.function, symbol.index).kind.function; + return obj.func_types[ty_index]; + } + return obj.func_types[obj.functions[symbol.index].type_index]; + } + if (is_undefined) { + const ty_index = self.imports.get(loc).?.kind.function; + return self.func_types.items[ty_index]; + } + return self.func_types.items[self.functions.get(.{ .file = loc.file, .index = loc.index }).?.type_index]; +} + /// Lowers a constant typed value to a local symbol and atom. /// Returns the symbol index of the local /// The given `decl` is the parent decl whom owns the constant. From f0bfdf9766716572d114b4bddefc6d26bb0ec298 Mon Sep 17 00:00:00 2001 From: Der Teufel Date: Thu, 18 Aug 2022 15:31:56 +0200 Subject: [PATCH 147/290] Fixed stage1 compilation error --- src/Autodoc.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Autodoc.zig b/src/Autodoc.zig index 2364a40f9f..de8a832029 100644 --- a/src/Autodoc.zig +++ b/src/Autodoc.zig @@ -308,7 +308,7 @@ fn createFromPath(base_dir: std.fs.Dir, path: []const u8) !std.fs.File { } else { return dir.createFile(toc, .{}) catch |e| switch (e) { error.PathAlreadyExists => try dir.openFile(toc, .{}), - else => |e| return e, + else => |err| return err, }; } } From 51a4861a081eea5d9bfd590c63f4a4506305f941 Mon Sep 17 00:00:00 2001 From: Der Teufel Date: Thu, 18 Aug 2022 15:44:42 +0200 Subject: [PATCH 148/290] Fixed Docgen.zig formatting --- src/Docgen.zig | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Docgen.zig b/src/Docgen.zig index 294ff359f0..1edb551ed9 100644 --- a/src/Docgen.zig +++ b/src/Docgen.zig @@ -24,7 +24,8 @@ pub fn genHtml( \\ ); try out.print(" {s} - source view\n", .{src.sub_file_path}); - try out.writeAll(\\ + try out.writeAll( + \\ \\