| author | |
| committer | |
| log | 9ff926bb1e47ebad0f13835355841ef4654fbe60 |
| tree | 976c2e9cdac3f0944a68ac6343f144700f1e299a |
| parent | f3607c75cc072df8ad52d3ae8ff6bcfb2dfeeb8f |
AstGen doesn't emit a `ref` ZIR inst if it encounters a dereference `.*`
with a reference result location (`ref`/`ref_coerced_ty`/`ref_const`),
since the operand of the dereference expression already is a reference.
This caused some problems as Zig allows `.*` on both single-item *and* C
pointers, but most logic in Sema assumes that everything with a reference
rl is always a single-item pointer. This largely didn't cause any issues
since C pointers can do everything single-item pointers can, but it caused
Sema to mistake `&p.*[0]` with `@TypeOf(p) == [*c][n]T` for an index into
a many-item pointer `[*][n]T` instead of a single-item pointer `*[n]T`.
To avoid such problems in the future, a pointer obtained from `&p.*` is
now always turned into a proper single-item pointer. This is achieved by
introducing two new ZIR instructions, `deref` and `ref_deref`:
For `ptr.*`:
```
%1 = validate_deref(%ptr)
%2 = load(%ptr)
```
vvv
```
%1 = deref(%ptr)
```
For `&ptr.*`:
```
%1 = validate_deref(%ptr)
// use %ptr directly
```
vvv
```
%1 = ref_deref(%ptr)
// use %1
```
This makes `validate_deref` superfluous, so it's been removed.
Also fixes `node_offset_deref_ptr` source location to actually point to
the pointer being dereferenced and uses it to provide better source locs
for `deref` and `ref_deref`.9 files changed, 136 insertions(+), 47 deletions(-)
lib/std/zig/AstGen.zig+4-4| ... | ... | @@ -927,15 +927,14 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE |
| 927 | 927 | |
| 928 | 928 | .deref => { |
| 929 | 929 | const lhs = try expr(gz, scope, .{ .rl = .none }, tree.nodeData(node).node); |
| 930 | _ = try gz.addUnNode(.validate_deref, lhs, node); | |
| 931 | 930 | switch (ri.rl) { |
| 932 | 931 | .ref, |
| 933 | 932 | .ref_coerced_ty, |
| 934 | 933 | .ref_const, |
| 935 | => return lhs, | |
| 934 | => return gz.addUnNode(.ref_deref, lhs, node), | |
| 936 | 935 | |
| 937 | 936 | else => { |
| 938 | const result = try gz.addUnNode(.load, lhs, node); | |
| 937 | const result = try gz.addUnNode(.deref, lhs, node); | |
| 939 | 938 | return rvalue(gz, ri, result, node); |
| 940 | 939 | }, |
| 941 | 940 | } |
| ... | ... | @@ -2759,6 +2758,8 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As |
| 2759 | 2758 | .mulwrap, |
| 2760 | 2759 | .mul_sat, |
| 2761 | 2760 | .ref, |
| 2761 | .deref, | |
| 2762 | .ref_deref, | |
| 2762 | 2763 | .shl, |
| 2763 | 2764 | .shl_sat, |
| 2764 | 2765 | .shr, |
| ... | ... | @@ -2932,7 +2933,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As |
| 2932 | 2933 | .memcpy, |
| 2933 | 2934 | .memset, |
| 2934 | 2935 | .memmove, |
| 2935 | .validate_deref, | |
| 2936 | 2936 | .validate_destructure, |
| 2937 | 2937 | .save_err_ret_index, |
| 2938 | 2938 | .restore_err_ret_index_unconditional, |
lib/std/zig/Zir.zig+21-11| ... | ... | @@ -567,12 +567,21 @@ pub const Inst = struct { |
| 567 | 567 | /// Uses the `pl_node` field with payload `Bin`. |
| 568 | 568 | merge_error_sets, |
| 569 | 569 | /// Turns an R-Value into a const L-Value. In other words, it takes a value, |
| 570 | /// stores it in a memory location, and returns a const pointer to it. If the value | |
| 571 | /// is `comptime`, the memory location is global static constant data. Otherwise, | |
| 572 | /// the memory location is in the stack frame, local to the scope containing the | |
| 573 | /// instruction. | |
| 570 | /// stores it in a memory location, and returns a const single-item pointer to it. | |
| 571 | /// If the value is `comptime`, the memory location is global static constant data. | |
| 572 | /// Otherwise, the memory location is in the stack frame, local to the scope | |
| 573 | /// containing the instruction. | |
| 574 | 574 | /// Uses the `un_tok` union field. |
| 575 | 575 | ref, |
| 576 | /// Implements the dereference operand (`.*`). Checks that operand is a pointer | |
| 577 | /// that supports being directly dereferenced. | |
| 578 | /// Uses the `un_node` union field. | |
| 579 | deref, | |
| 580 | /// Emitted when a dereference (`.*`) with a reference result location occurs. | |
| 581 | /// Checks that operand is a pointer that supports being directly dereferenced | |
| 582 | /// and returns a single-item pointer to the dereferenced memory location. | |
| 583 | /// Uses the `un_node` union field. | |
| 584 | ref_deref, | |
| 576 | 585 | /// Sends control flow back to the function's callee. |
| 577 | 586 | /// Includes an operand as the return value. |
| 578 | 587 | /// Includes an AST node source location. |
| ... | ... | @@ -717,9 +726,6 @@ pub const Inst = struct { |
| 717 | 726 | /// - `if (eu) |payload| {...} else |err| {...}`, AST node is the `if`. |
| 718 | 727 | /// Uses the `pl_node` union field. Payload is `SwitchBlock`. |
| 719 | 728 | switch_block_err_union, |
| 720 | /// Check that operand type supports the dereference operand (.*). | |
| 721 | /// Uses the `un_node` field. | |
| 722 | validate_deref, | |
| 723 | 729 | /// Check that the operand's type is an array or tuple with the given number of elements. |
| 724 | 730 | /// Uses the `pl_node` field. Payload is `ValidateDestructure`. |
| 725 | 731 | validate_destructure, |
| ... | ... | @@ -1170,6 +1176,8 @@ pub const Inst = struct { |
| 1170 | 1176 | .mulwrap, |
| 1171 | 1177 | .mul_sat, |
| 1172 | 1178 | .ref, |
| 1179 | .deref, | |
| 1180 | .ref_deref, | |
| 1173 | 1181 | .shl, |
| 1174 | 1182 | .shl_sat, |
| 1175 | 1183 | .shr, |
| ... | ... | @@ -1213,7 +1221,6 @@ pub const Inst = struct { |
| 1213 | 1221 | .switch_block, |
| 1214 | 1222 | .switch_block_ref, |
| 1215 | 1223 | .switch_block_err_union, |
| 1216 | .validate_deref, | |
| 1217 | 1224 | .validate_destructure, |
| 1218 | 1225 | .union_init, |
| 1219 | 1226 | .field_type_ref, |
| ... | ... | @@ -1352,7 +1359,6 @@ pub const Inst = struct { |
| 1352 | 1359 | .atomic_store, |
| 1353 | 1360 | .store_node, |
| 1354 | 1361 | .store_to_inferred_ptr, |
| 1355 | .validate_deref, | |
| 1356 | 1362 | .validate_destructure, |
| 1357 | 1363 | .@"export", |
| 1358 | 1364 | .set_runtime_safety, |
| ... | ... | @@ -1456,6 +1462,8 @@ pub const Inst = struct { |
| 1456 | 1462 | .mulwrap, |
| 1457 | 1463 | .mul_sat, |
| 1458 | 1464 | .ref, |
| 1465 | .deref, | |
| 1466 | .ref_deref, | |
| 1459 | 1467 | .shl, |
| 1460 | 1468 | .shl_sat, |
| 1461 | 1469 | .shr, |
| ... | ... | @@ -1708,6 +1716,8 @@ pub const Inst = struct { |
| 1708 | 1716 | .merge_error_sets = .pl_node, |
| 1709 | 1717 | .mod_rem = .pl_node, |
| 1710 | 1718 | .ref = .un_tok, |
| 1719 | .deref = .un_node, | |
| 1720 | .ref_deref = .un_node, | |
| 1711 | 1721 | .ret_node = .un_node, |
| 1712 | 1722 | .ret_load = .un_node, |
| 1713 | 1723 | .ret_implicit = .un_tok, |
| ... | ... | @@ -1744,7 +1754,6 @@ pub const Inst = struct { |
| 1744 | 1754 | .switch_block = .pl_node, |
| 1745 | 1755 | .switch_block_ref = .pl_node, |
| 1746 | 1756 | .switch_block_err_union = .pl_node, |
| 1747 | .validate_deref = .un_node, | |
| 1748 | 1757 | .validate_destructure = .pl_node, |
| 1749 | 1758 | .field_type_ref = .pl_node, |
| 1750 | 1759 | .union_init = .pl_node, |
| ... | ... | @@ -4186,6 +4195,8 @@ fn findTrackableInner( |
| 4186 | 4195 | .for_len, |
| 4187 | 4196 | .merge_error_sets, |
| 4188 | 4197 | .ref, |
| 4198 | .deref, | |
| 4199 | .ref_deref, | |
| 4189 | 4200 | .ret_node, |
| 4190 | 4201 | .ret_load, |
| 4191 | 4202 | .ret_implicit, |
| ... | ... | @@ -4219,7 +4230,6 @@ fn findTrackableInner( |
| 4219 | 4230 | .enum_literal, |
| 4220 | 4231 | .decl_literal, |
| 4221 | 4232 | .decl_literal_no_coerce, |
| 4222 | .validate_deref, | |
| 4223 | 4233 | .validate_destructure, |
| 4224 | 4234 | .field_type_ref, |
| 4225 | 4235 | .opt_eu_base_ptr_init, |
src/Sema.zig+65-29| ... | ... | @@ -1241,6 +1241,8 @@ fn analyzeBodyInner( |
| 1241 | 1241 | .optional_type => try sema.zirOptionalType(block, inst), |
| 1242 | 1242 | .ptr_type => try sema.zirPtrType(block, inst), |
| 1243 | 1243 | .ref => try sema.zirRef(block, inst), |
| 1244 | .deref => try sema.zirDeref(block, inst), | |
| 1245 | .ref_deref => try sema.zirRefDeref(block, inst), | |
| 1244 | 1246 | .shr => try sema.zirShr(block, inst, .shr), |
| 1245 | 1247 | .shr_exact => try sema.zirShr(block, inst, .shr_exact), |
| 1246 | 1248 | .slice_end => try sema.zirSliceEnd(block, inst), |
| ... | ... | @@ -1566,11 +1568,6 @@ fn analyzeBodyInner( |
| 1566 | 1568 | i += 1; |
| 1567 | 1569 | continue; |
| 1568 | 1570 | }, |
| 1569 | .validate_deref => { | |
| 1570 | try sema.zirValidateDeref(block, inst); | |
| 1571 | i += 1; | |
| 1572 | continue; | |
| 1573 | }, | |
| 1574 | 1571 | .validate_destructure => { |
| 1575 | 1572 | try sema.zirValidateDestructure(block, inst); |
| 1576 | 1573 | i += 1; |
| ... | ... | @@ -3082,6 +3079,69 @@ fn zirRef(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins |
| 3082 | 3079 | return sema.analyzeRef(block, block.tokenOffset(inst_data.src_tok), operand, .none); |
| 3083 | 3080 | } |
| 3084 | 3081 | |
| 3082 | fn zirDeref(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { | |
| 3083 | const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; | |
| 3084 | const src = block.nodeOffset(inst_data.src_node); | |
| 3085 | const ptr_src = block.src(.{ .node_offset_deref_ptr = inst_data.src_node }); | |
| 3086 | const operand = sema.resolveInst(inst_data.operand); | |
| 3087 | ||
| 3088 | try sema.validateDeref(block, src, operand, sema.typeOf(operand)); | |
| 3089 | ||
| 3090 | return sema.analyzeLoad(block, src, operand, ptr_src); | |
| 3091 | } | |
| 3092 | ||
| 3093 | fn zirRefDeref(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { | |
| 3094 | const pt = sema.pt; | |
| 3095 | const zcu = pt.zcu; | |
| 3096 | const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; | |
| 3097 | const src = block.nodeOffset(inst_data.src_node); | |
| 3098 | const ptr_src = block.src(.{ .node_offset_deref_ptr = inst_data.src_node }); | |
| 3099 | const operand = sema.resolveInst(inst_data.operand); | |
| 3100 | const operand_ty = sema.typeOf(operand); | |
| 3101 | ||
| 3102 | try sema.validateDeref(block, src, operand, operand_ty); | |
| 3103 | ||
| 3104 | const ptr_info = operand_ty.ptrInfo(zcu); | |
| 3105 | return switch (ptr_info.flags.size) { | |
| 3106 | .many, .slice => unreachable, // cannot be dereferenced | |
| 3107 | .c => ptr: { | |
| 3108 | var single_ptr_flags = ptr_info.flags; | |
| 3109 | single_ptr_flags.size = .one; | |
| 3110 | single_ptr_flags.is_allowzero = false; | |
| 3111 | const single_ptr_ty = try pt.ptrType(.{ | |
| 3112 | .child = ptr_info.child, | |
| 3113 | .flags = single_ptr_flags, | |
| 3114 | }); | |
| 3115 | break :ptr try sema.coerceCompatiblePtrs(block, single_ptr_ty, operand, ptr_src); | |
| 3116 | }, | |
| 3117 | .one => operand, | |
| 3118 | }; | |
| 3119 | } | |
| 3120 | ||
| 3121 | fn validateDeref( | |
| 3122 | sema: *Sema, | |
| 3123 | block: *Block, | |
| 3124 | src: LazySrcLoc, | |
| 3125 | ref: Air.Inst.Ref, | |
| 3126 | ty: Type, | |
| 3127 | ) CompileError!void { | |
| 3128 | const pt = sema.pt; | |
| 3129 | const zcu = pt.zcu; | |
| 3130 | if (ty.zigTypeTag(zcu) != .pointer) { | |
| 3131 | return sema.fail(block, src, "cannot dereference non-pointer type '{f}'", .{ty.fmt(pt)}); | |
| 3132 | } else switch (ty.ptrSize(zcu)) { | |
| 3133 | .one, .c => {}, | |
| 3134 | .many => return sema.fail(block, src, "index syntax required for unknown-length pointer type '{f}'", .{ty.fmt(pt)}), | |
| 3135 | .slice => return sema.fail(block, src, "index syntax required for slice type '{f}'", .{ty.fmt(pt)}), | |
| 3136 | } | |
| 3137 | if (sema.resolveValue(ref)) |val| { | |
| 3138 | // Error for deref of undef pointer, unless the pointee is OPV in which case it's legal. | |
| 3139 | if (val.isUndef(zcu) and ty.childType(zcu).classify(zcu) != .one_possible_value) { | |
| 3140 | return sema.fail(block, src, "cannot dereference undefined value", .{}); | |
| 3141 | } | |
| 3142 | } | |
| 3143 | } | |
| 3144 | ||
| 3085 | 3145 | fn zirEnsureResultUsed(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { |
| 3086 | 3146 | const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; |
| 3087 | 3147 | const operand = sema.resolveInst(inst_data.operand); |
| ... | ... | @@ -4606,30 +4666,6 @@ fn zirValidatePtrArrayInit( |
| 4606 | 4666 | } |
| 4607 | 4667 | } |
| 4608 | 4668 | |
| 4609 | fn zirValidateDeref(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { | |
| 4610 | const pt = sema.pt; | |
| 4611 | const zcu = pt.zcu; | |
| 4612 | const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; | |
| 4613 | const src = block.nodeOffset(inst_data.src_node); | |
| 4614 | const operand = sema.resolveInst(inst_data.operand); | |
| 4615 | const operand_ty = sema.typeOf(operand); | |
| 4616 | ||
| 4617 | if (operand_ty.zigTypeTag(zcu) != .pointer) { | |
| 4618 | return sema.fail(block, src, "cannot dereference non-pointer type '{f}'", .{operand_ty.fmt(pt)}); | |
| 4619 | } else switch (operand_ty.ptrSize(zcu)) { | |
| 4620 | .one, .c => {}, | |
| 4621 | .many => return sema.fail(block, src, "index syntax required for unknown-length pointer type '{f}'", .{operand_ty.fmt(pt)}), | |
| 4622 | .slice => return sema.fail(block, src, "index syntax required for slice type '{f}'", .{operand_ty.fmt(pt)}), | |
| 4623 | } | |
| 4624 | ||
| 4625 | if (sema.resolveValue(operand)) |val| { | |
| 4626 | // Error for deref of undef pointer, unless the pointee is OPV in which case it's legal. | |
| 4627 | if (val.isUndef(zcu) and operand_ty.childType(zcu).classify(zcu) != .one_possible_value) { | |
| 4628 | return sema.fail(block, src, "cannot dereference undefined value", .{}); | |
| 4629 | } | |
| 4630 | } | |
| 4631 | } | |
| 4632 | ||
| 4633 | 4669 | fn zirValidateDestructure(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { |
| 4634 | 4670 | const pt = sema.pt; |
| 4635 | 4671 | const zcu = pt.zcu; |
src/Zcu.zig+1-1| ... | ... | @@ -1531,7 +1531,7 @@ pub const SrcLoc = struct { |
| 1531 | 1531 | .node_offset_deref_ptr => |node_off| { |
| 1532 | 1532 | const tree = try src_loc.file_scope.getTree(zcu); |
| 1533 | 1533 | const node = node_off.toAbsolute(src_loc.base_node); |
| 1534 | return tree.nodeToSpan(node); | |
| 1534 | return tree.nodeToSpan(tree.nodeData(node).node); | |
| 1535 | 1535 | }, |
| 1536 | 1536 | .node_offset_asm_source => |node_off| { |
| 1537 | 1537 | const tree = try src_loc.file_scope.getTree(zcu); |
src/print_zir.zig+2-1| ... | ... | @@ -204,6 +204,8 @@ const Writer = struct { |
| 204 | 204 | .ensure_result_used, |
| 205 | 205 | .ensure_result_non_error, |
| 206 | 206 | .ensure_err_union_payload_void, |
| 207 | .deref, | |
| 208 | .ref_deref, | |
| 207 | 209 | .ret_node, |
| 208 | 210 | .ret_load, |
| 209 | 211 | .resolve_inferred_alloc, |
| ... | ... | @@ -260,7 +262,6 @@ const Writer = struct { |
| 260 | 262 | .bit_reverse, |
| 261 | 263 | .@"resume", |
| 262 | 264 | .make_ptr_const, |
| 263 | .validate_deref, | |
| 264 | 265 | .validate_const, |
| 265 | 266 | .check_comptime_control_flow, |
| 266 | 267 | .opt_eu_base_ptr_init, |
test/behavior/array.zig+25| ... | ... | @@ -1136,3 +1136,28 @@ test "resist alias of explicit copy of array passed as arg" { |
| 1136 | 1136 | |
| 1137 | 1137 | try expect(buf_b[0] == 1234); |
| 1138 | 1138 | } |
| 1139 | ||
| 1140 | test "access element through reference" { | |
| 1141 | const S = struct { | |
| 1142 | fn doTheTest(x: u8) !void { | |
| 1143 | { | |
| 1144 | var val: [1]u8 = .{x}; | |
| 1145 | const single_ptr: *[1]u8 = &val; | |
| 1146 | try expect(single_ptr.*[0] == x); | |
| 1147 | const elem_ptr = &single_ptr.*[0]; | |
| 1148 | comptime assert(@TypeOf(elem_ptr) == *u8); | |
| 1149 | try expect(elem_ptr.* == x); | |
| 1150 | } | |
| 1151 | { | |
| 1152 | var val: [1]u8 = .{x}; | |
| 1153 | const c_ptr: [*c][1]u8 = &val; | |
| 1154 | try expect(c_ptr.*[0] == x); | |
| 1155 | const elem_ptr = &c_ptr.*[0]; | |
| 1156 | comptime assert(@TypeOf(elem_ptr) == *u8); | |
| 1157 | try expect(elem_ptr.* == x); | |
| 1158 | } | |
| 1159 | } | |
| 1160 | }; | |
| 1161 | try comptime S.doTheTest(123); | |
| 1162 | try S.doTheTest(123); | |
| 1163 | } |
test/behavior/pointers.zig+1-1| ... | ... | @@ -793,6 +793,6 @@ test "comptime C pointer to optional pointer" { |
| 793 | 793 | const opt: ?*u8 = @ptrFromInt(0x1000); |
| 794 | 794 | const outer_ptr: [*c]const ?*u8 = &opt; |
| 795 | 795 | const inner_ptr = &outer_ptr.*.?; |
| 796 | comptime assert(@TypeOf(inner_ptr) == [*c]const *u8); | |
| 796 | comptime assert(@TypeOf(inner_ptr) == *const *u8); | |
| 797 | 797 | comptime assert(@intFromPtr(inner_ptr.*) == 0x1000); |
| 798 | 798 | } |
test/cases/compile_errors/deref_ptr_to_comptime_only_type.zig created+9| ... | ... | @@ -0,0 +1,9 @@ |
| 1 | export fn entry(ptr: *anyopaque) void { | |
| 2 | const ct_only: *type = @ptrCast(ptr); | |
| 3 | _ = ct_only.*; | |
| 4 | } | |
| 5 | ||
| 6 | // error | |
| 7 | // | |
| 8 | // :3:16: error: cannot load comptime-only type 'type' | |
| 9 | // :3:9: note: pointer of type '*type' is runtime-known |
test/cases/compile_errors/ref_deref_of_null_c_ptr.zig created+8| ... | ... | @@ -0,0 +1,8 @@ |
| 1 | export fn entry() void { | |
| 2 | const ptr: [*c]u8 = null; | |
| 3 | _ = &ptr.*; | |
| 4 | } | |
| 5 | ||
| 6 | // error | |
| 7 | // | |
| 8 | // :3:10: error: null pointer casted to type '*u8' |