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 4ac2775515901b7a3977d9c17ea25ff3012ffab8..b80e00ad595ffe9153e2ab5b329d0805fef7ea6a 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 a0829d6eb7eae7ef784521c1eb98a2ec503e0be5..07e81dc5beecf3d9309d85abc7aca26cd98ee068 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 ccd677df0b19a0619b10ebbec67bd799b3103cc7..5af3c0038d54e4c8bda2f7ba7f8589c31aa45bc1 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 93d98c41d357dce533442884c7d85b898503a885..a603f72d53385a2b9a6d7f379889a8cc1215e8ae 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 220fb18699e7aac56b114b20b1ce805cc986124d..d09c6278b5eac0a655ce17fa9357768b520411aa 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 2c6a322fca946d35b7509e4705ea55aa3d7d377d..bc9fc115b2461bfa72630a0b141f11870ad22297 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 91072d0b4c8b4aac4a45fe2368b52da8cdb5a90b..dd9cfba5480b03780a695383d7b24acfce531aeb 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 09721c661f094597e4950476e3807e4efbc2b242..d59a114e510cf2632f96c672143b72f9f2495fa2 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 664edb0304c878dd9420838ba1d100d1ce2e50cc..5ea7ffad67a8b28f0d4002572743085d3fb29071 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), -- 2.54.0 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 b80e00ad595ffe9153e2ab5b329d0805fef7ea6a..625af7ca07f3be5b552bb3605f850dddd006d0d5 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 07e81dc5beecf3d9309d85abc7aca26cd98ee068..1629b7711a5115b7381f0ac6695b9651f36a7f3b 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, -- 2.54.0 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 fb0993afe59f38f9d0bb7caaa1ebc21a46ee9faa..6e8cee7b84db143a7eec8bd7f67ce205d4727c65 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, -- 2.54.0 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 97e47d84f372fb598a49ef0b68ae66195117e56f..fa23b861e380a9df9f4be4c02ba79c9eba754791 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 5a640c5b4bb00a639c4b4112b4a3d60b2ad8552b..540c560839bc9043407674086b5051d6e5d813f5 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; \\ } \\} -- 2.54.0 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 c6717ad1c0d57b8b6a91c1689a3f2d1298736fcb..d5006bf81fae523f4f7cbaaca70887d5613eefa9 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)) -- 2.54.0 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 31bc367e64375508e86c3275502197972791d57f..08781767de4e27a9baa917d078854f1cc0993148 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); -- 2.54.0 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 5decb88ff3de88f3011e53e8fba4585d82b37369..aef0e0eb190128a3e36143edcd6705a2ed7badd4 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. -- 2.54.0 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 a8bafee4f8a632d5e89b8f6b0cadcbf136789af5..b68ae283b57ce616cd97a53be101453703b1fb4d 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}), -- 2.54.0 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 b68ae283b57ce616cd97a53be101453703b1fb4d..63be9a2220729ac62f8400020a144740f9c1758a 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 47a0c0889306d3176183212fcc901e0020f7c9fa..9320138f65cc93f70bc2201e4f78e08580caa355 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 2fef069f7acdd269a9e2464508fec9a6f520bfe1..6242026b66c11ff19cf0740a0ec8e5a96766ad05 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) -- 2.54.0 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 63be9a2220729ac62f8400020a144740f9c1758a..f3a6ad84edcb325445f025733567c39c6c703441 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 9320138f65cc93f70bc2201e4f78e08580caa355..1ca198ccd8b11ef322912d50d0f4cf694e2c12fc 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 6242026b66c11ff19cf0740a0ec8e5a96766ad05..c4d6af9db40711d5898ff238a6c52832d71b0e05 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 -- 2.54.0 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 f3a6ad84edcb325445f025733567c39c6c703441..c5b71657aa3d44325cb3df5e41ac029044f36125 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 }; } } -- 2.54.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 c5b71657aa3d44325cb3df5e41ac029044f36125..c30b8d97b3a0bffbbc62654c457b41362e197da4 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; -- 2.54.0 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 c30b8d97b3a0bffbbc62654c457b41362e197da4..37c37217098cc7715b315830568e66fb2ab1541a 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 1ca198ccd8b11ef322912d50d0f4cf694e2c12fc..ba857302769f3cbb95b675f4310c6c9c4035008d 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 c4d6af9db40711d5898ff238a6c52832d71b0e05..d1ba38a7792518c47c1998e5007bf6bc44a4adb3 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 a3f5fbac510d499913df33c8799ea3019114652d..ad45661b70d0bcbbc872c774d042ebe413ec2099 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); } -- 2.54.0 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 37c37217098cc7715b315830568e66fb2ab1541a..4a08a91976b38f30234905d0d0404209006eb8fa 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 ba857302769f3cbb95b675f4310c6c9c4035008d..8abc083a1eecf1f334243d4c104a3eedfb494e1a 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 d1ba38a7792518c47c1998e5007bf6bc44a4adb3..00537e0e38ed97864df1b61511bc1b38819f4397 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 -- 2.54.0 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 4a08a91976b38f30234905d0d0404209006eb8fa..ba53c2e757b8f0d85a4d8a1d3535e464b75fd77c 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 }; } -- 2.54.0 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 ad8879c59918e6d0f52970ce2f7dbb07e1bf0022..d81d2fa72685adc59a389853aa378eb2ce324c79 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 4e86bcadeb65cf9b5892fb456536100a8c9a15eb..d218fb6bc61f9ebffb3766b3cbd37d5e5cfdab1d 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 dc7b9b3abf69f0388bea61b744fe3b0fbf7faa1f..fd94976763c9a61f5bd8126ddbbaf7b42b8cffc6 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 0fadad3c7e98bdfb63d99ea1f94b381451a8b92e..059f0dc0427fc39b0e3faf560961f504f2096a24 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 -- 2.54.0 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 d81d2fa72685adc59a389853aa378eb2ce324c79..e964c8f76bbfde6c90f87919dfaa835d6cc0d93b 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 f79e2a860c0628a850bac647ca3aa4edfd4f8ae0..ec25ec2969e0cf41d719115ac31d99fd26a706a2 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 3295c20db3606a0c296bb3d71070fa26fab3e999..3d08872ca747a50d12db306e754782a03b66a5df 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 ecb82c61d48bc6a5be63161b486550f3b3a8914f..fbc6fcf42802af988c01ba73d93a4f3fcfccbf4c 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 13b331a0f4a99960dcaa7f6539eb7c37faa73279..b1bca1a11f0db83d0f5af731075a4a0b35bf2be8 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 0000000000000000000000000000000000000000..e45f9855a7f8893455ecafd14841b3ea1e4b767b --- /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 db8d235c45c8bb7251d27632a707d7779db0e68f..1a77d859241060633b20f10c43c623b107fdf7e3 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 -- 2.54.0 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 e964c8f76bbfde6c90f87919dfaa835d6cc0d93b..4bfd6ac1c4a1720cdd8ee76e97ef6cc0084e02be 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 588801b27e3e3913223883439a9050ea56ee9400..6fddb63bee0c83f37dd4b002399ad39367938c50 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 ad8010868a42ed600bb151e1d395b498906da4e5..835b084740c5ed359f778e10a21389ae417ff552 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 0000000000000000000000000000000000000000..fa2e127107c8bd1af1b0fbd0b259461a4e81e366 --- /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 1ca83ea481592fd0b2f4a963db2c1edfedb1516a..3cc8bfb355a459477c1bf7b8e6172bc342376776 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 -- 2.54.0 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 4bfd6ac1c4a1720cdd8ee76e97ef6cc0084e02be..e04190973b4eb0b8f5d4fa2ef73af1aed0bb55bd 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 0254e002ad0816183cb4489eef743641ab864904..e46b84f7838cc6eed4394d23b0a01daa9890d4af 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 e45f9855a7f8893455ecafd14841b3ea1e4b767b..276725361235ea4dbc1223fa63d362471e4e9324 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 1a77d859241060633b20f10c43c623b107fdf7e3..f5041adae76faef19ac0b6e73a7da1c63a98b12c 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 -- 2.54.0 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 | 126 ++++++++++++------ .../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, 101 insertions(+), 49 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index e04190973b4eb0b8f5d4fa2ef73af1aed0bb55bd..3b672ecd42c7925a9ac698ef97c76b3b36d679b4 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, + 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 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, + }), } }, - }) - 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); + }); + 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); + 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 b1159b7d750fca63b71e3263ef54390a12a9e339..e786a739d6078920123a205aa2aa76ba873ef2f7 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 2c39011240ac98fe1434999a6b93da22c53146f5..a45b8c24cebfb0ecf181615728a352a8ebf10379 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 d59adeb69856fd11a991b0342dac2263c2f3e648..8bc5be0d630ba0bec1b392ca678405fadd8004b4 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 a46f175487835780d77b64e9bfdbd76c82f0fa04..6d17c284dfc7233e8f5332aeae7205c83fe66385 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 -- 2.54.0 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 302822fc9917339cbf65d56832c23eaf756a792a..c5734278d338458e549749bccf5462b301d28e97 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 435075a411773350737f0a7e2ebfe4fad5fe2ebd..748016d5845d689cd69d0d43a39ee05991c08327 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 3b672ecd42c7925a9ac698ef97c76b3b36d679b4..56e08d081bddbceeeb1d25365933decfca5bac19 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 a8bafee4f8a632d5e89b8f6b0cadcbf136789af5..369cabeceab1ab4fbe93bbc9cc9a04385f1b4863 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 a603f72d53385a2b9a6d7f379889a8cc1215e8ae..be2891ef6fc0116e15af4099387ebd2d1eafddb6 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 d09c6278b5eac0a655ce17fa9357768b520411aa..a5007928b639e9a785cafc922b6a4d1819ab215e 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 bc9fc115b2461bfa72630a0b141f11870ad22297..bf834a36d97a2db27df9e7b993800630a5f034e6 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 dd9cfba5480b03780a695383d7b24acfce531aeb..ef92361de2c03e2844524904525a1b6a729c9161 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 d59a114e510cf2632f96c672143b72f9f2495fa2..7f7473bc666484c8b6e3f7fce6995a17df75a1ee 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 280b7604bfd25e9516b36b7edb0f238e8781e2e5..b746818142a42468e19bebb7763987586fb93892 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 5ea7ffad67a8b28f0d4002572743085d3fb29071..9c3efa18cd9f4ab556922af6f5c7fddb57681205 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 ec4a94b420636f287c9f2092a132cb343fcdfc91..23107946f6fee77767631ced2b2032988b1a77ec 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 79fcf33bc687ac336aa6556843434d2f13b4748e..0e5f401f6da18c2ce92f9837697d6e0bb1c8f8f1 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 507157911ef5a3a9b7a05cd174b294a93ea88c9a..4081d171c48e523c374666ec034a3090e2e10a60 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 0c35b5ef3d964763c591af20b88e28b092c9ff48..eb36fab2620c88f6de98f814a186df81290aa7dd 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 -- 2.54.0 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 b746818142a42468e19bebb7763987586fb93892..74e4404bce2bc3fd86ab1daef83f8dc9ff33885a 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); -- 2.54.0 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 | 6235 +++++++++++++++++++++++----------------------- 1 file changed, 3182 insertions(+), 3053 deletions(-) diff --git a/lib/docs/main.js b/lib/docs/main.js index 064099e1ad23c5675758f98fdd50a387dd997ff0..41a03322d9afb9b9fdfb38c2cdcb3794f0865614 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")); - - 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 escapeHtmlReplacements = { "&": "&", '"': """, "<": "<", ">": ">" }; - - 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 decl, is an array with {declNames, pkgNames} to get to this one - - 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 - - - - - 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, - }; - - let curNavSearch = ""; - let curSearchIndex = -1; - let imFeelingLucky = false; - - 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(); - - 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; - } - } - }, 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; +(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 searchTimer = null; + + let escapeHtmlReplacements = { + "&": "&", + '"': """, + "<": "<", + ">": ">", + }; + + 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 decl, is an array with {declNames, pkgNames} to get to this one + + 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 + + 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, + }; + + let curNavSearch = ""; + let curSearchIndex = -1; + let imFeelingLucky = false; + + 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(); + + 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; + } + } + }, + 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 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); + } + + 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 "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 { - document.title = zigAnalysis.params.rootName + suffix; + name += lenName + sentinel; } - } else { - document.title = list.join('.') + suffix; - } - } - - - 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; + 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; } - - 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]); + // @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 "; // } - - // 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; + 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("; } - case "cmpxchg_weak": { - fnName += "cmpxchgWeak" - break; + if (opts.wantHtml) { + name += '' + align + ""; + } else { + name += align; } - 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; + if (ptrObj.hostIntBytes != null) { + name += ":"; + if (opts.wantHtml) { + name += + '' + + ptrObj.bitOffsetInHost + + ""; + } else { + name += ptrObj.bitOffsetInHost; + } + name += ":"; + if (opts.wantHtml) { + name += + '' + + ptrObj.hostIntBytes + + ""; } else { - component = exprName(expr.refPath[i]); + name += ptrObj.hostIntBytes; } - name += "." + component; + } + name += ") "; } + //name += typeValueName(ptrObj.child, wantHtml, wantSubLink, null); + name += exprName(ptrObj.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"; - 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 typeKinds.Float: { + let floatObj = typeObj; + if (opts.wantHtml) { + return '' + floatObj.name + ""; + } else { + return floatObj.name; + } } - case "binOpIndex" : { - const binOpIndex = zigAnalysis.exprs[expr.binOpIndex]; - return exprName(binOpIndex, opts); + case typeKinds.Int: { + let intObj = typeObj; + let name = intObj.name; + if (opts.wantHtml) { + return '' + name + ""; + } else { + return name; + } } - 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 + ")"; + case typeKinds.ComptimeInt: + if (opts.wantHtml) { + return 'comptime_int'; } else { - print_lhs = lhs; + return "comptime_int"; } - if (rhsOp['binOpIndex']) { - print_rhs = "(" + rhs + ")"; + case typeKinds.ComptimeFloat: + if (opts.wantHtml) { + return 'comptime_float'; } else { - print_rhs = rhs; + 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; } - - 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]; + 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 += " "; - } - } - struct_body += "}"; - return struct_body; + case typeKinds.InferredErrorUnion: { + let errUnionObj = typeObj; + let payload = exprName(errUnionObj.payload, opts); + return "!" + payload; } - 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 += ", "; + case typeKinds.Fn: { + let fnObj = typeObj; + let payloadHtml = ""; + if (opts.wantHtml) { + if (fnObj.is_extern) { + payloadHtml += "pub extern "; } - } - 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; - } + if (fnObj.has_lib_name) { + payloadHtml += '"' + fnObj.lib_name + '" '; } - payloadHtml += "("; - - for (let i = 0; i < call.args.length; i++) { - if (i != 0) payloadHtml += ", "; - payloadHtml += exprName(call.args[i], opts); + 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 += ")"; - 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()"; - } + payloadHtml += + "
        
    "; + let value = fnObj.params[i]; + let paramValue = resolveValue({ expr: value }); - case "type": { - let name = ""; + if (fields != null) { + let paramNode = zigAnalysis.astNodes[fields[i]]; - 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; + if (paramNode.varArgs) { + payloadHtml += "..."; + continue; } - case typeKinds.Opaque: - { - let opaqueObj = (typeObj); - return opaqueObj.name; - } - case typeKinds.ComptimeExpr: - { - return "anyopaque"; + if (paramNode.noalias) { + if (opts.wantHtml) { + payloadHtml += 'noalias '; + } else { + payloadHtml += "noalias "; + } } - 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 (paramNode.comptime) { if (opts.wantHtml) { - name += - '' + lenName + sentinel + ""; + payloadHtml += 'comptime '; } else { - name += lenName + sentinel; + payloadHtml += "comptime "; } - 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; + + let paramName = paramNode.name; + if (paramName != null) { + // skip if it matches the type name + if (!shouldSkipParamName(paramValue, paramName)) { + payloadHtml += paramName + ": "; + } } - case typeKinds.Float: - { - let floatObj = (typeObj); + } - if (opts.wantHtml) { - return '' + floatObj.name + ''; - } else { - return floatObj.name; - } + 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); } - case typeKinds.Int: - { - let intObj = (typeObj); - let name = intObj.name; - if (opts.wantHtml) { - return '' + name + ''; - } else { - return name; - } + } else if ("typeOf" in value) { + if (opts.wantHtml) { + payloadHtml += ''; + payloadHtml += + '' + + exprName(value, opts) + + ""; + payloadHtml += ""; + } else { + payloadHtml += exprName(value, opts); } - 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; - } + } else if ("typeOf_peer" in value) { + if (opts.wantHtml) { + payloadHtml += ''; + payloadHtml += + '' + + exprName(value, opts) + + ""; + payloadHtml += ""; + } else { + payloadHtml += exprName(value, opts); } - - case typeKinds.ErrorUnion: - { - let errUnionObj = (typeObj); - let lhs = exprName(errUnionObj.lhs, opts); - let rhs = exprName(errUnionObj.rhs, opts); - return lhs + "!" + rhs; + } else if ("declRef" in value) { + if (opts.wantHtml) { + payloadHtml += ''; + payloadHtml += + '' + + exprName(value, opts) + + ""; + payloadHtml += ""; + } else { + payloadHtml += exprName(value, opts); } - case typeKinds.InferredErrorUnion: - { - let errUnionObj = (typeObj); - let payload = exprName(errUnionObj.payload, opts); - return "!" + payload; + } else if ("call" in value) { + if (opts.wantHtml) { + payloadHtml += ''; + payloadHtml += + '' + + exprName(value, opts) + + ""; + payloadHtml += ""; + } else { + payloadHtml += exprName(value, opts); } - 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; + } 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; } - // if (wantHtml) { - // return escapeHtml(typeObj.name); - // } else { - // return typeObj.name; - // } + } 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 + ") "; + } + } - - 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; - } + 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; + // } } - return false; + } } - - - function getPtrSize(typeObj) { - return (typeObj.size == null) ? pointerSizeEnum.One : typeObj.size; + } + + 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; + } + } } - - - function renderType(typeObj) { - let name; - if (rootIsStd && typeObj === zigAnalysis.types[zigAnalysis.packages[zigAnalysis.rootPkg].main]) { - name = "std"; + 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 { - 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)); + 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 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()); + 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); + + 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, }); - - 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 = ""; - } + 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); } - domTableFnErrors.classList.remove("hidden"); + } + } 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); } - 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 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"); } - - 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); - - let docs = zigAnalysis.astNodes[decl.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"); + } - domFnProto.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, + "
    " + ); - - function categorizeDecls(decls, - typesList, namespacesList, errSetsList, - fnsList, varsList, valsList, testsList) { + for (let i = 0; i < fnsList.length; i += 1) { + let decl = fnsList[i]; + let trDom = domListFns.children[i]; - for (let i = 0; i < decls.length; i += 1) { - let decl = zigAnalysis.decls[decls[i]]; - let declValue = resolveValue(decl.value); + let tdFnCode = trDom.children[0]; + let tdDesc = trDom.children[1]; - if (decl.isTest) { - testsList.push(decl); - continue; - } + let declType = resolveValue(decl.value); + console.assert("type" in declType.expr); - if (decl.kind === 'var') { - varsList.push(decl); - continue; - } + tdFnCode.innerHTML = exprName(declType.expr, { + wantHtml: true, + wantLink: true, + fnDecl: decl, + linkFnNameDecl: navLinkDecl(decl.name), + }); - 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); - } - } + let docs = zigAnalysis.astNodes[decl.src].docs; + if (docs != null) { + tdDesc.innerHTML = shortDescMarkdown(docs); + } else { + tdDesc.textContent = ""; } + } + domSectFns.classList.remove("hidden"); } - - 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"); - } + 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 + ")";
    +          }
             }
     
    -        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"); - } + html += ",
    "; - 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 (docsNonEmpty) { + html += '
    ' + markdown(docs) + "
    "; } + divDom.innerHTML = html; + } + domSectFields.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); + 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]; - tdFnCode.innerHTML = exprName(declType.expr,{ - wantHtml: true, - wantLink: true, - fnDecl: decl, - linkFnNameDecl: navLinkDecl(decl.name), - }); + let tdName = trDom.children[0]; + let tdNameA = tdName.children[0]; + let tdType = trDom.children[1]; + let tdDesc = trDom.children[2]; - let docs = zigAnalysis.astNodes[decl.src].docs; - if (docs != null) { - tdDesc.innerHTML = shortDescMarkdown(docs); - } else { - tdDesc.textContent = ""; - } - } - domSectFns.classList.remove("hidden"); - } + tdNameA.setAttribute("href", navLinkDecl(decl.name)); + tdNameA.textContent = decl.name; - 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"); - } + tdType.innerHTML = typeValueName(typeOfDecl(decl), true, true); - 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"); + 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 (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, + }); - 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"); + 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, + }); - - function operatorCompare(a, b) { - if (a === b) { - return 0; - } else if (a < b) { - return -1; + let docs = zigAnalysis.astNodes[decl.src].docs; + if (docs != null) { + tdDesc.innerHTML = shortDescMarkdown(docs); } else { - return 1; + tdDesc.textContent = ""; } + } + domSectTests.classList.remove("hidden"); } - - 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 operatorCompare(a, b) { + if (a === b) { + return 0; + } else if (a < b) { + return -1; + } else { + return 1; } + } - 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 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 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); + } - 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 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("."); + } + } + } - 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]; } + } } + } } - function onHashChange() { - updateCurNav(); - if (domSearch.value !== curNavSearch) { - domSearch.value = curNavSearch; - } - render(); - if (imFeelingLucky) { - imFeelingLucky = false; - activateSelectedResult(); - } + 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; - - function findSubDecl(parentType, childName) { + let stack = [ { - // 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]; - } - } - } - } - } + declNames: [], + type: zigAnalysis.types[pkg.main], + }, + ]; + while (stack.length !== 0) { + let item = stack.shift(); + if (isContainerType(item.type)) { + let t = item.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; - } + 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; + } - - - 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; + if (isContainerType(value)) { stack.push({ - path: newPath, - pkg: childPkg, + declNames: declNames, + type: value, }); - } - } - 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, - }); - } - } - } - } - } + } + + // 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; + } } + return list; + } - - function getCanonDeclPath(index) { - if (canonDeclPaths == null) { - canonDeclPaths = computeCanonDeclPaths(); - } - //let cd = (canonDeclPaths); - return canonDeclPaths[index]; + 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 getCanonTypeDecl(index) { + getCanonDeclPath(0); + //let ct = (canonTypeDecls); + return canonTypeDecls[index]; + } + + function escapeHtml(text) { + return text.replace(/[&"<>]/g, function (m) { + return escapeHtmlReplacements[m]; + }); + } - - 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 - 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; - } + 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 { - index += 1; // include the period + line.type = "code"; + line.text = line.raw_text.substr(code_indent); // remove the indent of the ``` from all the code block } - const slice = trimmed_docs.slice(0, index); - return markdown(slice); + } + + if (line.type != "skip") { + lines.push(line); + } } + // PHASE 2: + // Render HTML from markdown lines. + // Look at each line and emit fitting HTML code - function markdown(input) { - const raw_lines = input.split('\n'); // zig allows no '\r', so we don't need to split on CR - - const lines = []; + function markdownInlines(innerText) { + // inline types: + // **{INLINE}** : + // __{INLINE}__ : + // ~~{INLINE}~~ : + // *{INLINE}* : + // _{INLINE}_ : + // `{TEXT}` : + // [{INLINE}]({URL}) : + // ![{TEXT}]({URL}) : + // [[std;format.fmt]] : (inner link) - // PHASE 1: - // Dissect lines and determine the type for each line. - // Also computes indentation level and removes unnecessary whitespace + const formats = [ + { + marker: "**", + tag: "strong", + }, + { + marker: "~~", + tag: "s", + }, + { + marker: "__", + tag: "u", + }, + { + marker: "*", + tag: "em", + }, + ]; - 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 stack = []; - 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 innerHTML = ""; + let currentRun = ""; - 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); - } + function flushRun() { + if (currentRun != "") { + innerHTML += escapeHtml(currentRun); } - - // 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(); - i += codetag.length - 1; - in_code = false; - parsing_code = false; - innerHTML += ""; - codetag = ""; - } else { - currentRun += innerText[i]; - } - continue; - } - - if (innerText[i] == "`") { - flushRun(); - if (!parsing_code) { - innerHTML += ""; - } - parsing_code = true; - codetag += "`"; - continue; - } - - 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]; - } - } + 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(); + i += codetag.length - 1; + in_code = false; + parsing_code = false; + innerHTML += ""; + codetag = ""; + } else { + currentRun += innerText[i]; + } + continue; + } - while (stack.length > 0) { - const fmt = (stack.pop()); + if (innerText[i] == "`") { + flushRun(); + if (!parsing_code) { + innerHTML += ""; + } + parsing_code = true; + codetag += "`"; + continue; + } + + 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; } - - return innerHTML; + } + if (!any) { + currentRun += innerText[i]; + } } + } + flushRun(); - - function previousLineIs(type, line_no) { - if (line_no > 0) { - return (lines[line_no - 1].type == type); - } else { - return false; - } - } + while (stack.length > 0) { + const fmt = stack.pop(); + innerHTML += ""; + } - - function nextLineIs(type, line_no) { - if (line_no < (lines.length - 1)) { - return (lines[line_no + 1].type == type); - } else { - return false; - } - } + return innerHTML; + } - - function getPreviousLineIndent(line_no) { - if (line_no > 0) { - return lines[line_no - 1].indent; - } else { - return 0; - } - } + function previousLineIs(type, line_no) { + if (line_no > 0) { + return lines[line_no - 1].type == type; + } else { + return false; + } + } - - function getNextLineIndent(line_no) { - if (line_no < (lines.length - 1)) { - return lines[line_no + 1].indent; - } else { - return 0; - } - } + function nextLineIs(type, line_no) { + if (line_no < lines.length - 1) { + return lines[line_no + 1].type == type; + } else { + return false; + } + } - 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; - } - } + function getPreviousLineIndent(line_no) { + if (line_no > 0) { + return lines[line_no - 1].indent; + } else { + return 0; + } + } - return html; + function getNextLineIndent(line_no) { + if (line_no < lines.length - 1) { + return lines[line_no + 1].indent; + } else { + return 0; + } } - function activateSelectedResult() { - if (domSectSearchResults.classList.contains("hidden")) { - return; - } + 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; + } + } - 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; - } + 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(); - } - - - 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.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; - } + 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 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 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 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() { + 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 fullPathSearchTextLower = fullPathSearchText; - if (ignoreCase) { - fullPathSearchTextLower = fullPathSearchTextLower.toLowerCase(); - fileAndDocs = fileAndDocs.toLowerCase(); - } + 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]; + 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; - } - // 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; + // 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); -} - - - + } })(); -- 2.54.0 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 41a03322d9afb9b9fdfb38c2cdcb3794f0865614..2b6417c0152bb0f0a3b7844edd670fae162bb8ff 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"); -- 2.54.0 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 ad8879c59918e6d0f52970ce2f7dbb07e1bf0022..76c077c1033a11c73db5466523a1ca5f64e61b57 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 0000000000000000000000000000000000000000..435570f2fbf09ec21c7ec83b174b644c074b34f1 --- /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 0000000000000000000000000000000000000000..9d57865a87f55207cfbf7c6bf5e5338f1a26ed28 --- /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 2edbf4509cae33d41445625cd28173cf698a9b11..71e295c4dd6dfd5059c81c85e5851d4753aa68fb 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 -- 2.54.0 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 76c077c1033a11c73db5466523a1ca5f64e61b57..8c2125026dd442591b091015612a50e006c1d28c 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 0000000000000000000000000000000000000000..8e9358c6f44ea0bd64fb94512dcd0b8cc797ba12 --- /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 0000000000000000000000000000000000000000..3ce75710267c485a69bab33e3d132d2b26b1a836 --- /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 0000000000000000000000000000000000000000..a2433adde9786a283b5deada7f3493d41fb1f688 --- /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 -- 2.54.0 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 56e08d081bddbceeeb1d25365933decfca5bac19..4c11894c247724620225b3c7365939498d195133 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 -- 2.54.0 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 41a03322d9afb9b9fdfb38c2cdcb3794f0865614..2bb796f49b4b89e8f916e8a269096563e27cbd12 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(); -- 2.54.0 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 dbcb54288afdb472eae9738755d52724a9fb93d8..5d6f3818639a15c7f56e671e669b712d7d524d2f 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 680fc1d962afbbb89a1db7f267a6fd6a11bcde8e..e2b5162beb326b7148d610750994ab118a3e5f5a 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(); -- 2.54.0 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 951c0148d9d03f1f3a1835de2583ec9c5555488b..32f4f4378f68b590b5b277721a546feeff0b51a3 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, -- 2.54.0 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 0968e168124f0784b4084373e973a006451e886b..b1e88d2e0109c96dc98fcda9213f88c1ee50ab6b 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 538ce1bf5ed18f3df908cb1bad4a3d839c20c0ac..a7686080c1500c09bfe6ce6d148d481860c25f68 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); -- 2.54.0 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 d697ab0a9958fea7647be7e20d9e1385ce063be8..12851cc9add831e94fc3fea67618a266c251bac6 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 0000000000000000000000000000000000000000..d134e0e2fbabd3fe38886a1e97c5a8835903fd1e --- /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 0000000000000000000000000000000000000000..c09defc56ebf72c04ed24248fcc45a5143534087 --- /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 223db7663042f98a32ff416c5e513d806e6e155a..dd23c4bcb3f68258fc52fe6ad53924900cfe1a1e 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 -- 2.54.0 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 ed8204b3467c0003cb6a33e4b7097a8196fe5bad..879ecb4e2f4161f838f78fe18bf68e45af0fee20 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; -- 2.54.0 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 5c007b6986d4d94c8cfb5e892d5e7de233ef3fd3..5277da7d2bb361c2fa3dd3406940ffd6d078d291 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 -- 2.54.0 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 538ef6aaf8c35b12a3e4586e47103283d2b29cd6..7a1db54ea28c0c7b4e8416699cd2ff33d7173a2b 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 => { -- 2.54.0 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 5c6d7324193148a39fa6e8e62f0a7c11c89f1680..178249091e72861b91c802823e4c228bd25a297b 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}); -- 2.54.0 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 178249091e72861b91c802823e4c228bd25a297b..8cc221a8dca47db076bdec5d8ecad6aeefabcd69 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}); -- 2.54.0 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 8cc221a8dca47db076bdec5d8ecad6aeefabcd69..89d4ee59d944b725556bc2c23e4f47b154559d0d 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; -- 2.54.0 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 89d4ee59d944b725556bc2c23e4f47b154559d0d..eaa0ddd716da7a4ed5a0e1eca4954f01c47bb616 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); } -- 2.54.0 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 879ecb4e2f4161f838f78fe18bf68e45af0fee20..d7d6994bcdef41c391f3bc2f10366490ab8b202a 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 1a1412420a84f90abf2ac55a698620cb54d8b57e..4d8b176fbfd85543812892ef21acc4c7217daa27 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); +} -- 2.54.0 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 717aeb4d7fe1ffe9424f3b4df355ee93567b72a7..c08378f5f39dac54c0746c71a57426911d18e510 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 c08378f5f39dac54c0746c71a57426911d18e510..a5ddb3b6fb0641a9d18c4fca96e6e13c7cf8c62e 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) { -- 2.54.0 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 8134beb6e32c9011f052e901f71a36346f80a9f6..3d74e5e47fe589ec39b0495a2a0dc2f9e9a11d7c 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 0738b5af0b63754bd20bc1efe653fa66cd49beb3..33b8a98d7fbc715694490241305d3853b6732dbc 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 1151ed60daafecd81da92c24d26ab016a164bf7a..2fcd8cd9947e332a66c3beb68131b6cabf098afd 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 0000000000000000000000000000000000000000..cb48cafa4574e28f0c6f84ff4c3fa44cc49ec409 --- /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 -- 2.54.0 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 01edd07261d05ba943e449528ba410625951e68e..a8a39a7018c3912d624b349988cdcf34dcb56b5d 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 ebf15ca4399a349e29c8c9c5722b70dc25774cc6..7bf3ea1c9e14b41a626eb5d9f192353bede2ee16 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 -- 2.54.0 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 d7d6994bcdef41c391f3bc2f10366490ab8b202a..ceeb8af23c72712a734142ffefec4448fb5365c2 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 4e5eb5061cd74f98eed9d39aae9fec50c8927783..c13a3b7e4fd2c82de872d373bf10d9fbb7c7e78a 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 0000000000000000000000000000000000000000..0058cd5e3663c3d55aa46d950ab03084b85bbf6e --- /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 -- 2.54.0 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 ceeb8af23c72712a734142ffefec4448fb5365c2..772abf5a84d6e58f4808c7af5041b07fecb2d09c 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 306dad5d9edc7e6f5a579fc35d860f2d6a9d4ec5..84b18a27383ebd053b33614245a0031b003c20a7 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 0000000000000000000000000000000000000000..f8646d9450c663f808eb2d4275f66180d50c24b8 --- /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 -- 2.54.0 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 772abf5a84d6e58f4808c7af5041b07fecb2d09c..1aebe8e98eb0bdfa1c2e9e6a58774a75ae62435a 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 2f6fa78f0ce0db757880696819024e70d46794e4..efca75af30ed3b9d4251f13b5c69327be5383999 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 0000000000000000000000000000000000000000..90b243c31df4aa517fb5be0a6b126351617bb054 --- /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 c312d6db40680475b44655eab9bf62091f547de4..0142f422f475acc15df4048e77e714dbe230dd46 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 0000000000000000000000000000000000000000..66304d6a74ef2e5786bce749ce3e36b3caa738fd --- /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 -- 2.54.0 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 d7d6994bcdef41c391f3bc2f10366490ab8b202a..f1d140520cc1588676f11f4079b05c62003ed1ea 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 677a459afe77a4af576060241d89dae2069a4e55..9909cab5ce535dcbcf73078e3a5fbb2932e2d9a5 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 cd4e018be3a4751698ba7be10f0ab0ff12dffab8..d930fb7d27d1369544d166f22948705d19c2da64 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); +} -- 2.54.0 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 | 168 +++++++++--------- 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, 153 insertions(+), 175 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 2fcd8cd9947e332a66c3beb68131b6cabf098afd..ee1dbeffa44d48eb44fc0ce761b663c22646d12d 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 1aebe8e98eb0bdfa1c2e9e6a58774a75ae62435a..1a0ded4a0015c40d33b639f0f2899d43466b3c62 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); - } - } + if (enum_field_names) |set| { + set.putAssumeCapacity(field_name, {}); + } - 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}); + 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); } + } - 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)), - }; + 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}); } - } else { - return sema.fail(block, src, "unions must have at least one field", .{}); + + 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 4bc96c42591cf6e01c66bde67c3305e21b76535d..579a7970b7de5a0a716f6941204f6bce66d6d609 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 582ea230ef99fb64f2cd040ada393840bdda58a9..55847b8adda6a68efbda6ce9f020ecdcd6679ef7 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 40f8ca0fb3800fafe4d21156fc8c1c3c470c01c7..ba8379cd723b85ae2af0a51580ec5893d2d35db0 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 0000000000000000000000000000000000000000..051e464b72a1f1b09135108b718680b374b5d621 --- /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 f34065b69d6b6426c3b5312c4517d58b730444a0..0000000000000000000000000000000000000000 --- 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 44876e938af0eff4aef0836ba910bb5d3082a3c3..0000000000000000000000000000000000000000 --- 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 0b4f395c8182c9977643adb2f7d3761e72ad39bf..0000000000000000000000000000000000000000 --- 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 2121568dd20f610e6159106c029745c4946bb5e5..0000000000000000000000000000000000000000 --- 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 2121568dd20f610e6159106c029745c4946bb5e5..0000000000000000000000000000000000000000 --- 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 ee6d1b8b7c90762621d0c8c082116543f8e60332..0000000000000000000000000000000000000000 --- 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 644cba74c1303b58c95546ed1ea5f29f733bdd9f..321de1e3f67a83867c2608ef0193843b691b8113 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 { -- 2.54.0 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 a44296c9209e81fe2d91159609cf37000846460f..00ce2cc5baab19b3704d4713bfa9d8d1793bb13b 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 e514e0fd881a419893cb9dacf56928c891fa171f..06534a2f7e45c489a0ca08a85396cfe00395865f 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 = -- 2.54.0 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 f1d140520cc1588676f11f4079b05c62003ed1ea..e5700a6fe4c20b55e4506150acc466e6fb83eb95 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 c057f7a8426bd1049b34a05e2b0a52b614ef79cd..a5eb25d4f5b96bd201208d60fcc1b2ff5d642284 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)); } { -- 2.54.0 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 e5700a6fe4c20b55e4506150acc466e6fb83eb95..29a53ebb2a7283206b659f6e8700cece0f6d9850 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 0000000000000000000000000000000000000000..d8fa4e5726aa60311b0bf248f90201930cc947e7 --- /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' -- 2.54.0 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 29a53ebb2a7283206b659f6e8700cece0f6d9850..5fd1c1ce4cbb6c5b04a8a9305ea9b5ad7437625f 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 d8fa4e5726aa60311b0bf248f90201930cc947e7..f3e51a1bed2459b067e42f966e0b6ba4b4afe328 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' -- 2.54.0 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 | 53 +++++++++++++---------- 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, 47 insertions(+), 35 deletions(-) diff --git a/test/link.zig b/test/link.zig index a8a39a7018c3912d624b349988cdcf34dcb56b5d..88b370f343503207a93dde2e0ce0576134bd4d0e 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,50 +59,60 @@ 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", .{ + cases.addBuildFile("test/link/macho/entry/build.zig", .{ .build_modes = true, }); + cases.addBuildFile("test/link/macho/headerpad/build.zig", .{ + .build_modes = true, + .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/weak_framework/build.zig", .{ + cases.addBuildFile("test/link/macho/needed_library/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/pagezero/build.zig", .{ + .build_modes = false, }); cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{ .build_modes = true, }); - cases.addBuildFile("test/link/macho/headerpad/build.zig", .{ + cases.addBuildFile("test/link/macho/stack_size/build.zig", .{ .build_modes = true, - .requires_macos_sdk = 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 dea225dd6fd72715d7364210a31cfcd03e12504c..25759f56192a874d08166af689e0f8b9af22d3c7 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 9dbc0e64733fc272a1be4103406936da301bc4de..5a7044d960c29becc56acaf16e44e6110dfa8f75 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 39a82bc6a74d7742598e8577adf860698d9b715a..e556b5bb23538c22c5be38252bf42248a3897f33 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 3abf48df7af1863b3f0498a8be2659c6d42ca012..91c44baf5212de27b1934971602df431653df730 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 7bf3ea1c9e14b41a626eb5d9f192353bede2ee16..031a05cedf9907407592cce3c2e9b1e7ad8b536e 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(); -- 2.54.0 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 984758565cf9475e954ff41c292028ca45e09323..c757561b0717b53251238980ba40c5d28c0a68cb 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, -- 2.54.0 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 5ff0a5d61b1506c1605ebe347988d430c5c52c8c..5d208b31a2f732703c288644faf12f25bb89d0e9 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 -- 2.54.0 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 e9e7460314654d3974c9d804034e9e951516b062..5f52b04ce4237e0ec43827ad8a1fe9e7ccfad5fd 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 -- 2.54.0 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 0a99432c67cb75bb0521d27f4d8e7fde4ed82ac2..a0b8001a9ea859048230a585488f294f9105cae8 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 d90ebc3de875034edb2027327df8ebdfdabe4571..2364a40f9f20b90c9448c393d3b7d8d584e9deb6 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 0000000000000000000000000000000000000000..294ff359f0360a4b66acc74169cb38cb03484cc1 --- /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; +} -- 2.54.0 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 8eb3e2175b9a292f54bdc9cee85d8d70d5133c3c..95a0a8e4aa83b0adf377de3922392158782ed8d6 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 = {} }; } } -- 2.54.0 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 30e219226895d522eae60431a93982d293d45d89..1bdd9426a58b464b17d5d7bb598b094462c8a598 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. -- 2.54.0 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 2364a40f9f20b90c9448c393d3b7d8d584e9deb6..de8a83202983417ab3c5b2fa705efdb7f329a9db 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, }; } } -- 2.54.0 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 294ff359f0360a4b66acc74169cb38cb03484cc1..1edb551ed9756c6ebc3066aa7fdf5435c4e42eb8 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( + \\ \\