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 5006730cad..d876e24977 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 0000000000..92e9620c99 --- /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 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 acbf5b337f..b10402cc09 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); 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 f24c89ef6f..cd3943a286 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 02070cb933..0d704d9680 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); +} 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 b10402cc09..2daad01936 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 a6cfd0f987..b598dca026 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); + } +} 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 d876e24977..32830e3f39 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 048bf8ac8d..14400a4423 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 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 32830e3f39..ef6bf2e7da 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; 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 0ca92fb649..b93a892dcc 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 83f720ff85..97ab86b172 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 0000000000..584f583c56 --- /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 0000000000..971fb8958d --- /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 0000000000..bbbc7dd8ca --- /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; 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 b93a892dcc..51b1592c26 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 1f4faae636..36a51d8275 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); +} 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 51b1592c26..c393dab598 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 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 ef6bf2e7da..5113ddfb8f 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 04b13c3026..4d13416e1f 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; } 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 2daad01936..506c34af1d 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 8ee7b5142a..43e62fdc93 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); +} 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 c393dab598..f40f36e511 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 8a669b28f3..cd0891990c 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); +} 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 506c34af1d..abe4256a30 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 cd3943a286..d80c63f3ce 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 f9a1603a5f..a172587554 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); +}