From 89e8bb409a6b3dd47f9d1e1ab6e2abc5fb0ef746 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 8 Nov 2022 16:48:12 +0200 Subject: [PATCH 01/13] AstGen: use `condbr_inline` if force_comptime is set The `finishThenElseBlock` would correctly use `break_inline` which would cause Sema to use `addRuntimeBreak` instead of doing the branch at comptime. --- src/AstGen.zig | 12 ++++++---- ...comptime_only_scope_uses_condbr_inline.zig | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 test/cases/compile_errors/branch_in_comptime_only_scope_uses_condbr_inline.zig diff --git a/src/AstGen.zig b/src/AstGen.zig index 5006730cad6683946c4220a9b3b2f5673a3cab1a..d876e2497703b123c107ca97218220ebf2ed3de9 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -5294,9 +5294,11 @@ fn orelseCatchExpr( // up for this fact by calling rvalue on the else branch. const operand = try reachableExpr(&block_scope, &block_scope.base, operand_ri, lhs, rhs); const cond = try block_scope.addUnNode(cond_op, operand, node); - const condbr = try block_scope.addCondBr(.condbr, node); + const condbr_tag: Zir.Inst.Tag = if (parent_gz.force_comptime) .condbr_inline else .condbr; + const condbr = try block_scope.addCondBr(condbr_tag, node); - const block = try parent_gz.makeBlockInst(.block, node); + const block_tag: Zir.Inst.Tag = if (parent_gz.force_comptime) .block_inline else .block; + const block = try parent_gz.makeBlockInst(block_tag, node); try block_scope.setBlockBody(block); // block_scope unstacked now, can add new instructions to parent_gz try parent_gz.instructions.append(astgen.gpa, block); @@ -5608,9 +5610,11 @@ fn ifExpr( } }; - const condbr = try block_scope.addCondBr(.condbr, node); + const condbr_tag: Zir.Inst.Tag = if (parent_gz.force_comptime) .condbr_inline else .condbr; + const condbr = try block_scope.addCondBr(condbr_tag, node); - const block = try parent_gz.makeBlockInst(.block, node); + const block_tag: Zir.Inst.Tag = if (parent_gz.force_comptime) .block_inline else .block; + const block = try parent_gz.makeBlockInst(block_tag, node); try block_scope.setBlockBody(block); // block_scope unstacked now, can add new instructions to parent_gz try parent_gz.instructions.append(astgen.gpa, block); diff --git a/test/cases/compile_errors/branch_in_comptime_only_scope_uses_condbr_inline.zig b/test/cases/compile_errors/branch_in_comptime_only_scope_uses_condbr_inline.zig new file mode 100644 index 0000000000000000000000000000000000000000..92e9620c9989d195a564087e96a5277856a843d4 --- /dev/null +++ b/test/cases/compile_errors/branch_in_comptime_only_scope_uses_condbr_inline.zig @@ -0,0 +1,22 @@ +pub export fn entry1() void { + var x: u32 = 3; + _ = @shuffle(u32, [_]u32{0}, @splat(1, @as(u32, 0)), [_]i8{ + if (x > 1) 1 else -1, + }); +} + +pub export fn entry2() void { + var y: ?i8 = -1; + _ = @shuffle(u32, [_]u32{0}, @splat(1, @as(u32, 0)), [_]i8{ + y orelse 1, + }); +} + +// error +// backend=stage2 +// target=native +// +// :4:15: error: unable to resolve comptime value +// :4:15: note: condition in comptime branch must be comptime-known +// :11:11: error: unable to resolve comptime value +// :11:11: note: condition in comptime branch must be comptime-known -- 2.54.0 From cacfb0cfe4217a19872d62e248389bc522b36bf8 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 8 Nov 2022 16:50:23 +0200 Subject: [PATCH 02/13] llvm: fix leaks of fully qualified names --- src/codegen/llvm.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index acbf5b337f413ff5419d9882f0a567cf01bde53a..b10402cc09a66b5b86142eb391f9e384fdaa670b 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -8671,9 +8671,9 @@ pub const FuncGen = struct { 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), - }); + const fqn = try mod.declPtr(enum_decl).getFullyQualifiedName(mod); + defer self.gpa.free(fqn); + const llvm_fn_name = try std.fmt.allocPrintZ(arena, "__zig_is_named_enum_value_{s}", .{fqn}); var int_tag_type_buffer: Type.Payload.Bits = undefined; const int_tag_ty = enum_ty.intTagType(&int_tag_type_buffer); @@ -8752,9 +8752,9 @@ pub const FuncGen = struct { const arena = arena_allocator.allocator(); const mod = self.dg.module; - const llvm_fn_name = try std.fmt.allocPrintZ(arena, "__zig_tag_name_{s}", .{ - try mod.declPtr(enum_decl).getFullyQualifiedName(mod), - }); + const fqn = try mod.declPtr(enum_decl).getFullyQualifiedName(mod); + defer self.gpa.free(fqn); + const llvm_fn_name = try std.fmt.allocPrintZ(arena, "__zig_tag_name_{s}", .{fqn}); const slice_ty = Type.initTag(.const_slice_u8_sentinel_0); const llvm_ret_ty = try self.dg.lowerType(slice_ty); -- 2.54.0 From c4465556fdf14d87f718a7aced5210ec457c1f5a Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 8 Nov 2022 19:03:47 +0200 Subject: [PATCH 03/13] Type: check return_type for generic poison before comparing Closes #13423 --- src/type.zig | 4 +++- test/behavior/generics.zig | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/type.zig b/src/type.zig index f24c89ef6fec201c5f62ddb4393b3af59e2eca26..cd3943a2866505dfb62f613e67c642da24b275c7 100644 --- a/src/type.zig +++ b/src/type.zig @@ -640,7 +640,9 @@ pub const Type = extern union { const a_info = a.fnInfo(); const b_info = b.fnInfo(); - if (!eql(a_info.return_type, b_info.return_type, mod)) + if (a_info.return_type.tag() != .generic_poison and + b_info.return_type.tag() != .generic_poison and + !eql(a_info.return_type, b_info.return_type, mod)) return false; if (a_info.is_var_args != b_info.is_var_args) diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig index 02070cb933481e6c689c515354fe951545323d88..0d704d96804a089d0c0008c3eba6d446e33f507b 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -405,3 +405,15 @@ test "null sentinel pointer passed as generic argument" { }; try S.doTheTest((@intToPtr([*:null]const [*c]const u8, 8))); } + +test "generic function passed as comptime argument" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + + const S = struct { + fn doMath(comptime f: fn (type, i32, i32) error{Overflow}!i32, a: i32, b: i32) !void { + const result = try f(i32, a, b); + try expect(result == 11); + } + }; + try S.doMath(std.math.add, 5, 6); +} -- 2.54.0 From d2cc55109a64aaf004384b942e08e95829b9341f Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 8 Nov 2022 19:31:09 +0200 Subject: [PATCH 04/13] llvm: correct calculation of index of zero-bit field If the field comes before any non-zero-bit field then the index of the next field should be returned. Closes #13363 --- src/codegen/llvm.zig | 4 ++-- test/behavior/struct.zig | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index b10402cc09a66b5b86142eb391f9e384fdaa670b..2daad0193695821bf9b3cbac88a26d85b022b55c 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -10216,7 +10216,7 @@ fn llvmFieldIndex( llvm_field_index += 1; } - if (field_index == i) { + if (field_index <= i) { ptr_pl_buf.* = .{ .data = .{ .pointee_type = field_ty, @@ -10249,7 +10249,7 @@ fn llvmFieldIndex( llvm_field_index += 1; } - if (field_index == i) { + if (field_index <= i) { ptr_pl_buf.* = .{ .data = .{ .pointee_type = field.ty, diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index a6cfd0f987b943113cc49b6f571341eb22a81bde..b598dca0262691ffb9e385228a4eac0817eaaa83 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -1398,3 +1398,23 @@ test "under-aligned struct field" { const result = std.mem.readIntNative(u64, array[4..12]); try expect(result == 1234); } + +test "address of zero-bit field is equal to address of only field" { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + 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_c) return error.SkipZigTest; // TODO + + { + const A = struct { b: void = {}, u: u8 }; + var a = A{ .u = 0 }; + const a_ptr = @fieldParentPtr(A, "b", &a.b); + try std.testing.expectEqual(&a, a_ptr); + } + { + const A = struct { u: u8, b: void = {} }; + var a = A{ .u = 0 }; + const a_ptr = @fieldParentPtr(A, "b", &a.b); + try std.testing.expectEqual(&a, a_ptr); + } +} -- 2.54.0 From 0a188190b3b2b42906d0cc38f101b010bc07b414 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 8 Nov 2022 19:48:21 +0200 Subject: [PATCH 05/13] AstGen: make pointless discard error more strict The error should only happen as a result of `_ = ` not for an operand of a break expression that is discarded. Closes #13212 --- src/AstGen.zig | 17 ++++++++++++----- test/cases/compile_errors/pointless discard.zig | 8 ++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index d876e2497703b123c107ca97218220ebf2ed3de9..32830e3f39cb3da33b8f5eb6d5c0f60ff8053fd8 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -339,6 +339,8 @@ pub const ResultInfo = struct { fn_arg, /// The expression is the right-hand side of an initializer for a `const` variable const_init, + /// The expression is the right-hand side of an assignment expression. + assignment, /// No specific operator in particular. none, }; @@ -3216,7 +3218,7 @@ fn assign(gz: *GenZir, scope: *Scope, infix_node: Ast.Node.Index) InnerError!voi // This intentionally does not support `@"_"` syntax. const ident_name = tree.tokenSlice(main_tokens[lhs]); if (mem.eql(u8, ident_name, "_")) { - _ = try expr(gz, scope, .{ .rl = .discard }, rhs); + _ = try expr(gz, scope, .{ .rl = .discard, .ctx = .assignment }, rhs); return; } } @@ -7088,7 +7090,7 @@ fn localVarRef( if (local_val.name == name_str_index) { // Locals cannot shadow anything, so we do not need to look for ambiguous // references in this case. - if (ri.rl == .discard) { + if (ri.rl == .discard and ri.ctx == .assignment) { local_val.discarded = ident_token; } else { local_val.used = ident_token; @@ -7111,7 +7113,7 @@ fn localVarRef( .local_ptr => { const local_ptr = s.cast(Scope.LocalPtr).?; if (local_ptr.name == name_str_index) { - if (ri.rl == .discard) { + if (ri.rl == .discard and ri.ctx == .assignment) { local_ptr.discarded = ident_token; } else { local_ptr.used = ident_token; @@ -10672,14 +10674,19 @@ const GenZir = struct { gz.break_result_info = parent_ri; }, - .discard, .none, .ref => { + .none, .ref => { gz.rl_ty_inst = .none; gz.break_result_info = parent_ri; }, + .discard => { + gz.rl_ty_inst = .none; + gz.break_result_info = .{ .rl = .discard }; + }, + .ptr => |ptr_res| { gz.rl_ty_inst = .none; - gz.break_result_info = .{ .rl = .{ .ptr = .{ .inst = ptr_res.inst } } }; + gz.break_result_info = .{ .rl = .{ .ptr = .{ .inst = ptr_res.inst } }, .ctx = parent_ri.ctx }; }, .inferred_ptr => |ptr| { diff --git a/test/cases/compile_errors/pointless discard.zig b/test/cases/compile_errors/pointless discard.zig index 048bf8ac8dd917d3a3d22c91b9197d22f33c279b..14400a442380d63b2b98065018cee75af2a9dbd2 100644 --- a/test/cases/compile_errors/pointless discard.zig +++ b/test/cases/compile_errors/pointless discard.zig @@ -3,6 +3,14 @@ export fn foo() void { x += 1; _ = x; } +export fn bar() void { + var b: u32 = 1; + _ = blk: { + const a = 1; + b = a; + break :blk a; + }; +} // error // backend=stage2 -- 2.54.0 From 25c850642190bf9939790b872a3657410ec4d19f Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 9 Nov 2022 16:49:24 +0200 Subject: [PATCH 06/13] AstGen: emit dbg_stmt before (nearly) all operations that have a safety check All implicit casts can also potentially lead to a panic being emitted but adding a dbg_stmt before every instruction is not feasible. This adds 24k new instructions to the ZIR for Sema.zig increasing its size from 3.8MiB to 4.0MiB. Closes #13488 --- src/AstGen.zig | 165 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 137 insertions(+), 28 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 32830e3f39cb3da33b8f5eb6d5c0f60ff8053fd8..ef6bf2e7da079bf397be99dabab3ff9d78c7c2f3 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -828,7 +828,13 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE .slice_open => { const lhs = try expr(gz, scope, .{ .rl = .ref }, node_datas[node].lhs); + + maybeAdvanceSourceCursorToMainToken(gz, node); + const line = gz.astgen.source_line - gz.decl_line; + const column = gz.astgen.source_column; + const start = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, node_datas[node].rhs); + try emitDbgStmt(gz, line, column); const result = try gz.addPlNode(.slice_start, node, Zir.Inst.SliceStart{ .lhs = lhs, .start = start, @@ -837,9 +843,15 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE }, .slice => { const lhs = try expr(gz, scope, .{ .rl = .ref }, node_datas[node].lhs); + + maybeAdvanceSourceCursorToMainToken(gz, node); + const line = gz.astgen.source_line - gz.decl_line; + const column = gz.astgen.source_column; + const extra = tree.extraData(node_datas[node].rhs, Ast.Node.Slice); const start = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, extra.start); const end = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, extra.end); + try emitDbgStmt(gz, line, column); const result = try gz.addPlNode(.slice_end, node, Zir.Inst.SliceEnd{ .lhs = lhs, .start = start, @@ -849,10 +861,16 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE }, .slice_sentinel => { const lhs = try expr(gz, scope, .{ .rl = .ref }, node_datas[node].lhs); + + maybeAdvanceSourceCursorToMainToken(gz, node); + const line = gz.astgen.source_line - gz.decl_line; + const column = gz.astgen.source_column; + const extra = tree.extraData(node_datas[node].rhs, Ast.Node.SliceSentinel); const start = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, extra.start); const end = if (extra.end != 0) try expr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, extra.end) else .none; const sentinel = try expr(gz, scope, .{ .rl = .none }, extra.sentinel); + try emitDbgStmt(gz, line, column); const result = try gz.addPlNode(.slice_sentinel, node, Zir.Inst.SliceSentinel{ .lhs = lhs, .start = start, @@ -883,16 +901,26 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE return rvalue(gz, ri, result, node); }, .unwrap_optional => switch (ri.rl) { - .ref => return gz.addUnNode( - .optional_payload_safe_ptr, - try expr(gz, scope, .{ .rl = .ref }, node_datas[node].lhs), - node, - ), - else => return rvalue(gz, ri, try gz.addUnNode( - .optional_payload_safe, - try expr(gz, scope, .{ .rl = .none }, node_datas[node].lhs), - node, - ), node), + .ref => { + const lhs = try expr(gz, scope, .{ .rl = .ref }, node_datas[node].lhs); + + maybeAdvanceSourceCursorToMainToken(gz, node); + const line = gz.astgen.source_line - gz.decl_line; + const column = gz.astgen.source_column; + try emitDbgStmt(gz, line, column); + + return gz.addUnNode(.optional_payload_safe_ptr, lhs, node); + }, + else => { + const lhs = try expr(gz, scope, .{ .rl = .none }, node_datas[node].lhs); + + maybeAdvanceSourceCursorToMainToken(gz, node); + const line = gz.astgen.source_line - gz.decl_line; + const column = gz.astgen.source_column; + try emitDbgStmt(gz, line, column); + + return rvalue(gz, ri, try gz.addUnNode(.optional_payload_safe, lhs, node), node); + }, }, .block_two, .block_two_semicolon => { const statements = [2]Ast.Node.Index{ node_datas[node].lhs, node_datas[node].rhs }; @@ -3241,10 +3269,27 @@ fn assignOp( const node_datas = tree.nodes.items(.data); const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs); + + var line: u32 = undefined; + var column: u32 = undefined; + switch (op_inst_tag) { + .add, .sub, .mul, .div, .mod_rem => { + maybeAdvanceSourceCursorToMainToken(gz, infix_node); + line = gz.astgen.source_line - gz.decl_line; + column = gz.astgen.source_column; + }, + else => {}, + } const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node); const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node); const rhs = try expr(gz, scope, .{ .rl = .{ .coerced_ty = lhs_type } }, node_datas[infix_node].rhs); + switch (op_inst_tag) { + .add, .sub, .mul, .div, .mod_rem => { + try emitDbgStmt(gz, line, column); + }, + else => {}, + } const result = try gz.addPlNode(op_inst_tag, infix_node, Zir.Inst.Bin{ .lhs = lhs, .rhs = rhs, @@ -5475,9 +5520,15 @@ fn addFieldAccess( const dot_token = main_tokens[node]; const field_ident = dot_token + 1; const str_index = try astgen.identAsString(field_ident); + const lhs = try expr(gz, scope, lhs_ri, object_node); + + maybeAdvanceSourceCursorToMainToken(gz, node); + const line = gz.astgen.source_line - gz.decl_line; + const column = gz.astgen.source_column; + try emitDbgStmt(gz, line, column); return gz.addPlNode(tag, node, Zir.Inst.Field{ - .lhs = try expr(gz, scope, lhs_ri, object_node), + .lhs = lhs, .field_name_start = str_index, }); } @@ -5488,18 +5539,33 @@ fn arrayAccess( ri: ResultInfo, node: Ast.Node.Index, ) InnerError!Zir.Inst.Ref { - const astgen = gz.astgen; - const tree = astgen.tree; + const tree = gz.astgen.tree; const node_datas = tree.nodes.items(.data); switch (ri.rl) { - .ref => return gz.addPlNode(.elem_ptr_node, node, Zir.Inst.Bin{ - .lhs = try expr(gz, scope, .{ .rl = .ref }, node_datas[node].lhs), - .rhs = try expr(gz, scope, .{ .rl = .{ .ty = .usize_type } }, node_datas[node].rhs), - }), - else => return rvalue(gz, ri, try gz.addPlNode(.elem_val_node, node, Zir.Inst.Bin{ - .lhs = try expr(gz, scope, .{ .rl = .none }, node_datas[node].lhs), - .rhs = try expr(gz, scope, .{ .rl = .{ .ty = .usize_type } }, node_datas[node].rhs), - }), node), + .ref => { + const lhs = try expr(gz, scope, .{ .rl = .ref }, node_datas[node].lhs); + + maybeAdvanceSourceCursorToMainToken(gz, node); + const line = gz.astgen.source_line - gz.decl_line; + const column = gz.astgen.source_column; + + const rhs = try expr(gz, scope, .{ .rl = .{ .ty = .usize_type } }, node_datas[node].rhs); + try emitDbgStmt(gz, line, column); + + return gz.addPlNode(.elem_ptr_node, node, Zir.Inst.Bin{ .lhs = lhs, .rhs = rhs }); + }, + else => { + const lhs = try expr(gz, scope, .{ .rl = .none }, node_datas[node].lhs); + + maybeAdvanceSourceCursorToMainToken(gz, node); + const line = gz.astgen.source_line - gz.decl_line; + const column = gz.astgen.source_column; + + const rhs = try expr(gz, scope, .{ .rl = .{ .ty = .usize_type } }, node_datas[node].rhs); + try emitDbgStmt(gz, line, column); + + return rvalue(gz, ri, try gz.addPlNode(.elem_val_node, node, Zir.Inst.Bin{ .lhs = lhs, .rhs = rhs }), node); + }, } } @@ -5514,10 +5580,26 @@ fn simpleBinOp( const tree = astgen.tree; const node_datas = tree.nodes.items(.data); - const result = try gz.addPlNode(op_inst_tag, node, Zir.Inst.Bin{ - .lhs = try reachableExpr(gz, scope, .{ .rl = .none }, node_datas[node].lhs, node), - .rhs = try reachableExpr(gz, scope, .{ .rl = .none }, node_datas[node].rhs, node), - }); + const lhs = try reachableExpr(gz, scope, .{ .rl = .none }, node_datas[node].lhs, node); + var line: u32 = undefined; + var column: u32 = undefined; + switch (op_inst_tag) { + .add, .sub, .mul, .div, .mod_rem => { + maybeAdvanceSourceCursorToMainToken(gz, node); + line = gz.astgen.source_line - gz.decl_line; + column = gz.astgen.source_column; + }, + else => {}, + } + const rhs = try reachableExpr(gz, scope, .{ .rl = .none }, node_datas[node].rhs, node); + + switch (op_inst_tag) { + .add, .sub, .mul, .div, .mod_rem => { + try emitDbgStmt(gz, line, column); + }, + else => {}, + } + const result = try gz.addPlNode(op_inst_tag, node, Zir.Inst.Bin{ .lhs = lhs, .rhs = rhs }); return rvalue(gz, ri, result, node); } @@ -7858,9 +7940,7 @@ fn builtinCall( }, .src => { - const token_starts = tree.tokens.items(.start); - const node_start = token_starts[tree.firstToken(node)]; - astgen.advanceSourceCursor(node_start); + maybeAdvanceSourceCursorToMainToken(gz, node); const result = try gz.addExtendedPayload(.builtin_src, Zir.Inst.Src{ .node = gz.nodeIndexToRelative(node), .line = astgen.source_line, @@ -7975,6 +8055,8 @@ fn builtinCall( return rvalue(gz, ri, result, node); }, .err_set_cast => { + try emitDbgNode(gz, node); + const result = try gz.addExtendedPayload(.err_set_cast, Zir.Inst.BinNode{ .lhs = try typeExpr(gz, scope, params[0]), .rhs = try expr(gz, scope, .{ .rl = .none }, params[1]), @@ -8280,6 +8362,8 @@ fn typeCast( rhs_node: Ast.Node.Index, tag: Zir.Inst.Tag, ) InnerError!Zir.Inst.Ref { + try emitDbgNode(gz, node); + const result = try gz.addPlNode(tag, node, Zir.Inst.Bin{ .lhs = try typeExpr(gz, scope, lhs_node), .rhs = try expr(gz, scope, .{ .rl = .none }, rhs_node), @@ -8309,6 +8393,10 @@ fn simpleUnOp( operand_node: Ast.Node.Index, tag: Zir.Inst.Tag, ) InnerError!Zir.Inst.Ref { + switch (tag) { + .tag_name, .error_name, .ptr_to_int => try emitDbgNode(gz, node), + else => {}, + } const operand = try expr(gz, scope, operand_ri, operand_node); const result = try gz.addUnNode(tag, operand, node); return rvalue(gz, ri, result, node); @@ -8381,6 +8469,8 @@ fn divBuiltin( rhs_node: Ast.Node.Index, tag: Zir.Inst.Tag, ) InnerError!Zir.Inst.Ref { + try emitDbgNode(gz, node); + const result = try gz.addPlNode(tag, node, Zir.Inst.Bin{ .lhs = try expr(gz, scope, .{ .rl = .none }, lhs_node), .rhs = try expr(gz, scope, .{ .rl = .none }, rhs_node), @@ -8434,8 +8524,15 @@ fn shiftOp( tag: Zir.Inst.Tag, ) InnerError!Zir.Inst.Ref { const lhs = try expr(gz, scope, .{ .rl = .none }, lhs_node); + + maybeAdvanceSourceCursorToMainToken(gz, node); + const line = gz.astgen.source_line - gz.decl_line; + const column = gz.astgen.source_column; + const log2_int_type = try gz.addUnNode(.typeof_log2_int_type, lhs, lhs_node); const rhs = try expr(gz, scope, .{ .rl = .{ .ty = log2_int_type }, .ctx = .shift_op }, rhs_node); + + try emitDbgStmt(gz, line, column); const result = try gz.addPlNode(tag, node, Zir.Inst.Bin{ .lhs = lhs, .rhs = rhs, @@ -12065,6 +12162,18 @@ fn detectLocalShadowing( }; } +/// Advances the source cursor to the main token of `node` if not in comptime scope. +/// Usually paired with `emitDbgStmt`. +fn maybeAdvanceSourceCursorToMainToken(gz: *GenZir, node: Ast.Node.Index) void { + if (gz.force_comptime) return; + + const tree = gz.astgen.tree; + const token_starts = tree.tokens.items(.start); + const main_tokens = tree.nodes.items(.main_token); + const node_start = token_starts[main_tokens[node]]; + gz.astgen.advanceSourceCursor(node_start); +} + /// Advances the source cursor to the beginning of `node`. fn advanceSourceCursorToNode(astgen: *AstGen, node: Ast.Node.Index) void { const tree = astgen.tree; -- 2.54.0 From 9b832e7f530833de93857444a86e34c8d99e4755 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 9 Nov 2022 18:01:34 +0200 Subject: [PATCH 07/13] Sema: make check for namespace lookup of private declarations more strict Previously sema only checked that the private declaration was in the same file as the lookup but now it also checks that the namespace where the decl was included from was also in the same file. Closes #13077 --- src/Sema.zig | 8 ++++---- test/behavior/usingnamespace.zig | 4 ++++ test/behavior/usingnamespace/file_0.zig | 1 + test/behavior/usingnamespace/file_1.zig | 9 +++++++++ test/behavior/usingnamespace/imports.zig | 5 +++++ 5 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 test/behavior/usingnamespace/file_0.zig create mode 100644 test/behavior/usingnamespace/file_1.zig create mode 100644 test/behavior/usingnamespace/imports.zig diff --git a/src/Sema.zig b/src/Sema.zig index 0ca92fb6498a06589833f2752d8cbece5497edcf..b93a892dcce43e0b87954bda67d8ee40940c926f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5658,14 +5658,14 @@ fn lookupInNamespace( const src_file = block.namespace.file_scope; const gpa = sema.gpa; - var checked_namespaces: std.AutoArrayHashMapUnmanaged(*Namespace, void) = .{}; + var checked_namespaces: std.AutoArrayHashMapUnmanaged(*Namespace, bool) = .{}; defer checked_namespaces.deinit(gpa); // Keep track of name conflicts for error notes. var candidates: std.ArrayListUnmanaged(Decl.Index) = .{}; defer candidates.deinit(gpa); - try checked_namespaces.put(gpa, namespace, {}); + try checked_namespaces.put(gpa, namespace, namespace.file_scope == src_file); var check_i: usize = 0; while (check_i < checked_namespaces.count()) : (check_i += 1) { @@ -5674,7 +5674,7 @@ fn lookupInNamespace( // Skip decls which are not marked pub, which are in a different // file than the `a.b`/`@hasDecl` syntax. const decl = mod.declPtr(decl_index); - if (decl.is_pub or src_file == decl.getFileScope()) { + if (decl.is_pub or (src_file == decl.getFileScope() and checked_namespaces.values()[check_i])) { try candidates.append(gpa, decl_index); } } @@ -5693,7 +5693,7 @@ fn lookupInNamespace( try sema.ensureDeclAnalyzed(sub_usingnamespace_decl_index); const ns_ty = sub_usingnamespace_decl.val.castTag(.ty).?.data; const sub_ns = ns_ty.getNamespace().?; - try checked_namespaces.put(gpa, sub_ns, {}); + try checked_namespaces.put(gpa, sub_ns, src_file == sub_usingnamespace_decl.getFileScope()); } } diff --git a/test/behavior/usingnamespace.zig b/test/behavior/usingnamespace.zig index 83f720ff85697ae550adc8c66f920a2e9a42604c..97ab86b1729c8caa9a1a2997bf6f4470e4d61c09 100644 --- a/test/behavior/usingnamespace.zig +++ b/test/behavior/usingnamespace.zig @@ -75,3 +75,7 @@ test { const a = AA.b(42); try expect(a.x == AA.c().expected); } + +comptime { + _ = @import("usingnamespace/file_1.zig"); +} diff --git a/test/behavior/usingnamespace/file_0.zig b/test/behavior/usingnamespace/file_0.zig new file mode 100644 index 0000000000000000000000000000000000000000..584f583c56d1d0039e3a5819cc48b388b3dc8c69 --- /dev/null +++ b/test/behavior/usingnamespace/file_0.zig @@ -0,0 +1 @@ +pub const A = 123; diff --git a/test/behavior/usingnamespace/file_1.zig b/test/behavior/usingnamespace/file_1.zig new file mode 100644 index 0000000000000000000000000000000000000000..971fb8958d59dbf733ae3abd56c15cd9fb15b8bf --- /dev/null +++ b/test/behavior/usingnamespace/file_1.zig @@ -0,0 +1,9 @@ +const std = @import("std"); +const expect = std.testing.expect; +const imports = @import("imports.zig"); + +const A = 456; + +test { + try expect(imports.A == 123); +} diff --git a/test/behavior/usingnamespace/imports.zig b/test/behavior/usingnamespace/imports.zig new file mode 100644 index 0000000000000000000000000000000000000000..bbbc7dd8ca87f25abcbef88a4463deeddf70ec52 --- /dev/null +++ b/test/behavior/usingnamespace/imports.zig @@ -0,0 +1,5 @@ +const file_0 = @import("file_0.zig"); +const file_1 = @import("file_1.zig"); + +pub usingnamespace file_0; +pub usingnamespace file_1; -- 2.54.0 From 40a2dfc12a611082ba6810c566a6a46acdb864fc Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 9 Nov 2022 18:32:15 +0200 Subject: [PATCH 08/13] Sema: coerce array operands to shuffle Closes #13494 --- src/Sema.zig | 4 ++-- test/behavior/vector.zig | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index b93a892dcce43e0b87954bda67d8ee40940c926f..51b1592c267098cda6d26a0b441c327819fe5586 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -20179,8 +20179,8 @@ fn analyzeShuffle( .elem_type = elem_ty, }); - if (maybe_a_len == null) a = try sema.addConstUndef(a_ty); - if (maybe_b_len == null) b = try sema.addConstUndef(b_ty); + if (maybe_a_len == null) a = try sema.addConstUndef(a_ty) else a = try sema.coerce(block, a_ty, a, a_src); + if (maybe_b_len == null) b = try sema.addConstUndef(b_ty) else b = try sema.coerce(block, b_ty, b, b_src); const operand_info = [2]std.meta.Tuple(&.{ u64, LazySrcLoc, Type }){ .{ a_len, a_src, a_ty }, diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index 1f4faae636ce304d0c549d377353348325137440..36a51d8275a1c93c3918731b6ba2674b2b6b4ff9 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -3,6 +3,7 @@ const builtin = @import("builtin"); const mem = std.mem; const math = std.math; const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; test "implicit cast vector to array - bool" { if (builtin.zig_backend == .stage1) { @@ -1231,3 +1232,17 @@ test "modRem with zero divisor" { _ = zeros[0]; } } + +test "array operands to shuffle are coerced to vectors" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + + const mask = [5]i32{ -1, 0, 1, 2, 3 }; + + var a = [5]u32{ 3, 5, 7, 9, 0 }; + var b = @shuffle(u32, a, @splat(5, @as(u24, 0)), mask); + try expectEqual([_]u32{ 0, 3, 5, 7, 9 }, b); +} -- 2.54.0 From 52b8efc726097df70dca7287b0a8e16b0a8a642d Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 9 Nov 2022 20:09:06 +0200 Subject: [PATCH 09/13] Sema: check for error unwrap in `condbr_inline` The part of `condbr` that is supposed to be the same as `condbr_inline` already does this. --- src/Sema.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Sema.zig b/src/Sema.zig index 51b1592c267098cda6d26a0b441c327819fe5586..c393dab59856184a930830d39fa45772aaed9dae 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1491,6 +1491,8 @@ fn analyzeBodyInner( return err; }; const inline_body = if (cond.val.toBool()) then_body else else_body; + + try sema.maybeErrorUnwrapCondbr(block, inline_body, extra.data.condition, cond_src); const old_runtime_index = block.runtime_index; defer block.runtime_index = old_runtime_index; const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse -- 2.54.0 From e01ec96288bd32c7ec3bba01ee200cc115cdfb1d Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 9 Nov 2022 23:06:26 +0200 Subject: [PATCH 10/13] Autodoc: not all `block_inline`s contain a `break_inline` --- src/AstGen.zig | 4 +++- src/Autodoc.zig | 15 ++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index ef6bf2e7da079bf397be99dabab3ff9d78c7c2f3..5113ddfb8f28da2d66577dc424c2f2e4dd1b3c8d 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -7940,7 +7940,9 @@ fn builtinCall( }, .src => { - maybeAdvanceSourceCursorToMainToken(gz, node); + const token_starts = tree.tokens.items(.start); + const node_start = token_starts[tree.firstToken(node)]; + astgen.advanceSourceCursor(node_start); const result = try gz.addExtendedPayload(.builtin_src, Zir.Inst.Src{ .node = gz.nodeIndexToRelative(node), .line = astgen.source_line, diff --git a/src/Autodoc.zig b/src/Autodoc.zig index 04b13c3026d64f5c7f7385e1a9e2a87944013766..4d13416e1f420fd2e37643aa8ea4ce330e1c580a 100644 --- a/src/Autodoc.zig +++ b/src/Autodoc.zig @@ -2129,7 +2129,15 @@ fn walkInstruction( file, parent_scope, parent_src, - getBlockInlineBreak(file.zir, inst_index), + getBlockInlineBreak(file.zir, inst_index) orelse { + const res = DocData.WalkResult{ .expr = .{ + .comptimeExpr = self.comptime_exprs.items.len, + } }; + try self.comptime_exprs.append(self.arena, .{ + .code = "if (...) { ... }", + }); + return res; + }, need_type, ); }, @@ -3155,7 +3163,7 @@ fn walkDecls( 2 => { // decl test const decl_being_tested = scope.resolveDeclName(doc_comment_index); - const func_index = getBlockInlineBreak(file.zir, value_index); + const func_index = getBlockInlineBreak(file.zir, value_index).?; const pl_node = data[Zir.refToIndex(func_index).?].pl_node; const fn_src = try self.srcLocInfo(file, pl_node.src_node, decl_src); @@ -4301,12 +4309,13 @@ fn walkRef( } } -fn getBlockInlineBreak(zir: Zir, inst_index: usize) Zir.Inst.Ref { +fn getBlockInlineBreak(zir: Zir, inst_index: usize) ?Zir.Inst.Ref { const tags = zir.instructions.items(.tag); const data = zir.instructions.items(.data); const pl_node = data[inst_index].pl_node; const extra = zir.extraData(Zir.Inst.Block, pl_node.payload_index); const break_index = zir.extra[extra.end..][extra.data.body_len - 1]; + if (tags[break_index] == .condbr_inline) return null; std.debug.assert(tags[break_index] == .break_inline); return data[break_index].@"break".operand; } -- 2.54.0 From d42f4abb9dc906ef20b622656c7672cb7df02096 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 11 Nov 2022 17:56:37 +0200 Subject: [PATCH 11/13] llvm: correctly lower references to generic functions Closes #13522 --- src/codegen/llvm.zig | 7 +++++-- test/behavior/pointers.zig | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 2daad0193695821bf9b3cbac88a26d85b022b55c..506c34af1d2e9860c3e2683f7207ce263075709c 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -3198,7 +3198,8 @@ pub const DeclGen = struct { /// There are other similar cases handled here as well. fn lowerPtrElemTy(dg: *DeclGen, elem_ty: Type) Allocator.Error!*llvm.Type { const lower_elem_ty = switch (elem_ty.zigTypeTag()) { - .Opaque, .Fn => true, + .Opaque => true, + .Fn => !elem_ty.fnInfo().is_generic, .Array => elem_ty.childType().hasRuntimeBitsIgnoreComptime(), else => elem_ty.hasRuntimeBitsIgnoreComptime(), }; @@ -4145,7 +4146,9 @@ pub const DeclGen = struct { } const is_fn_body = decl.ty.zigTypeTag() == .Fn; - if (!is_fn_body and !decl.ty.hasRuntimeBits()) { + if ((!is_fn_body and !decl.ty.hasRuntimeBits()) or + (is_fn_body and decl.ty.fnInfo().is_generic)) + { return self.lowerPtrToVoid(tv.ty); } diff --git a/test/behavior/pointers.zig b/test/behavior/pointers.zig index 8ee7b5142ae946af3c5bcf224ae799d9a7cbfafe..43e62fdc9360b31a2acddccae4b283a309f47b7d 100644 --- a/test/behavior/pointers.zig +++ b/test/behavior/pointers.zig @@ -489,3 +489,20 @@ test "ptrCast comptime known slice to C pointer" { var p = @ptrCast([*c]const u8, s); try std.testing.expectEqualStrings(s, std.mem.sliceTo(p, 0)); } + +test "ptrToInt on a generic function" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64 and builtin.os.tag != .linux) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64 and builtin.os.tag != .linux) return error.SkipZigTest; // TODO + + const S = struct { + fn generic(i: anytype) @TypeOf(i) { + return i; + } + fn doTheTest(a: anytype) !void { + try expect(@ptrToInt(a) != 0); + } + }; + try S.doTheTest(&S.generic); +} -- 2.54.0 From a760ce598c7656f7582d8305582e374af68254d9 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 11 Nov 2022 18:54:41 +0200 Subject: [PATCH 12/13] Sema: ensure that `!is_comptime and !is_typeof` implies `sema.func != null` Closes #13481 --- src/Sema.zig | 7 +------ test/behavior/eval.zig | 8 ++++++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index c393dab59856184a930830d39fa45772aaed9dae..f40f36e511ffd3c9ad415664002966894bfaa83f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6333,6 +6333,7 @@ fn analyzeCall( .instructions = .{}, .label = null, .inlining = &inlining, + .is_typeof = block.is_typeof, .is_comptime = is_comptime_call, .comptime_reason = comptime_reason, .error_return_trace_index = block.error_return_trace_index, @@ -16532,9 +16533,6 @@ fn zirSaveErrRetIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE // This is only relevant at runtime. if (block.is_comptime or block.is_typeof) return; - // This is only relevant within functions. - if (sema.func == null) return; - const save_index = inst_data.operand == .none or b: { const operand = try sema.resolveInst(inst_data.operand); const operand_ty = sema.typeOf(operand); @@ -27505,9 +27503,6 @@ fn analyzeLoad( if (try sema.pointerDeref(block, src, ptr_val, ptr_ty)) |elem_val| { return sema.addConstant(elem_ty, elem_val); } - if (block.is_typeof) { - return sema.addConstUndef(elem_ty); - } } return block.addTyOp(.load, elem_ty, ptr); diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig index 8a669b28f36dd0f3249961ecc8127d157b7ed0ea..cd0891990cf235bd5089249bf613db85247f68ab 100644 --- a/test/behavior/eval.zig +++ b/test/behavior/eval.zig @@ -1499,3 +1499,11 @@ test "non-optional and optional array elements concatenated" { var index: usize = 0; try expect(array[index].? == 'A'); } + +test "inline call in @TypeOf inherits is_inline property" { + const S = struct { + inline fn doNothing() void {} + const T = @TypeOf(doNothing()); + }; + try expect(S.T == void); +} -- 2.54.0 From 87cf2783ebdf96e3dfa1c24a53dba301591d5f07 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 12 Nov 2022 13:11:54 +0200 Subject: [PATCH 13/13] llvm: check that tuple fields have runtime bits Just checking that they aren't comptime isn't enough for `@Type` constructed tuples. Closes #13531 --- src/codegen/llvm.zig | 8 ++++---- src/type.zig | 2 +- test/behavior/tuple.zig | 10 ++++++++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 506c34af1d2e9860c3e2683f7207ce263075709c..abe4256a30c1cdc9a51f927059411875e6e31c5b 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1966,7 +1966,7 @@ pub const Object = struct { for (tuple.types) |field_ty, i| { const field_val = tuple.values[i]; - if (field_val.tag() != .unreachable_value) continue; + if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue; const field_size = field_ty.abiSize(target); const field_align = field_ty.abiAlignment(target); @@ -2901,7 +2901,7 @@ pub const DeclGen = struct { for (tuple.types) |field_ty, i| { const field_val = tuple.values[i]; - if (field_val.tag() != .unreachable_value) continue; + if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue; const field_align = field_ty.abiAlignment(target); big_align = @max(big_align, field_align); @@ -10207,7 +10207,7 @@ fn llvmFieldIndex( const tuple = ty.tupleFields(); var llvm_field_index: c_uint = 0; for (tuple.types) |field_ty, i| { - if (tuple.values[i].tag() != .unreachable_value) continue; + if (tuple.values[i].tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue; const field_align = field_ty.abiAlignment(target); big_align = @max(big_align, field_align); @@ -10771,7 +10771,7 @@ fn isByRef(ty: Type) bool { const tuple = ty.tupleFields(); var count: usize = 0; for (tuple.values) |field_val, i| { - if (field_val.tag() != .unreachable_value) continue; + if (field_val.tag() != .unreachable_value or !tuple.types[i].hasRuntimeBits()) continue; count += 1; if (count > max_fields_byval) return true; diff --git a/src/type.zig b/src/type.zig index cd3943a2866505dfb62f613e67c642da24b275c7..d80c63f3ce234e163ed68d5b621c81cb77fb97c8 100644 --- a/src/type.zig +++ b/src/type.zig @@ -5759,7 +5759,7 @@ pub const Type = extern union { for (tuple.types) |field_ty, i| { const field_val = tuple.values[i]; - if (field_val.tag() != .unreachable_value) { + if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) { // comptime field if (i == index) return offset; continue; diff --git a/test/behavior/tuple.zig b/test/behavior/tuple.zig index f9a1603a5f445a8aaccb3f5800bc2438531d89e4..a1725875544f617fb29f32386f8ad39e13a7aa1f 100644 --- a/test/behavior/tuple.zig +++ b/test/behavior/tuple.zig @@ -323,3 +323,13 @@ test "zero sized struct in tuple handled correctly" { var s: State = undefined; try expect(s.do() == 0); } + +test "tuple type with void field and a runtime field" { + 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 T = std.meta.Tuple(&[_]type{ usize, void }); + var t: T = .{ 5, {} }; + try expect(t[0] == 5); +} -- 2.54.0