Skip to content

Commit

Permalink
compiler: preserve result type information through address-of operator
Browse files Browse the repository at this point in the history
This commit introduces the new `ref_coerced_ty` result type into AstGen.
This represents a expression which we want to treat as an lvalue, and
the pointer will be coerced to a given type.

This change gives known result types to many expressions, in particular
struct and array initializations. This allows certain casts to work
which previously required explicitly specifying types via `@as`. It also
eliminates our dependence on anonymous struct types for expressions of
the form `&.{ ... }` - this paves the way for ziglang#16865, and also results
in less Sema magic happening for such initializations, also leading to
potentially better runtime code.

As part of these changes, this commit also implements ziglang#17194 by
disallowing RLS on explicitly-typed struct and array initializations.
Apologies for linking these changes - it seemed rather pointless to try
and separate them, since they both make big changes to struct and array
initializations in AstGen. The rationale for this change can be found in
the proposal - in essence, performing RLS whilst maintaining the
semantics of the intermediary type is a very difficult problem to solve.

This allowed the problematic `coerce_result_ptr` ZIR instruction to be
completely eliminated, which in turn also simplified the logic for
inferred allocations in Sema - thanks to this, we break even on line
count!

In doing this, the ZIR instructions surrounding these initializations
have been restructured - some have been added and removed, and others
renamed for clarity (and their semantics changed slightly). In order to
optimize ZIR tag count, the `struct_init_anon_ref` and
`array_init_anon_ref` instructions have been removed in favour of using
`ref` on a standard anonymous value initialization, since these
instructions are now virtually never used.

Resolves: ziglang#16512
Resolves: ziglang#17194
  • Loading branch information
mlugg committed Sep 20, 2023
1 parent e702f25 commit dab90b3
Show file tree
Hide file tree
Showing 9 changed files with 972 additions and 951 deletions.
622 changes: 317 additions & 305 deletions src/AstGen.zig

Large diffs are not rendered by default.

37 changes: 3 additions & 34 deletions src/Autodoc.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2378,34 +2378,6 @@ fn walkInstruction(
.expr = .{ .@"&" = expr_index },
};
},
.array_init_anon_ref => {
const pl_node = data[inst_index].pl_node;
const extra = file.zir.extraData(Zir.Inst.MultiOp, pl_node.payload_index);
const operands = file.zir.refSlice(extra.end, extra.data.operands_len);
const array_data = try self.arena.alloc(usize, operands.len);

for (operands, 0..) |op, idx| {
const wr = try self.walkRef(
file,
parent_scope,
parent_src,
op,
false,
call_ctx,
);
const expr_index = self.exprs.items.len;
try self.exprs.append(self.arena, wr.expr);
array_data[idx] = expr_index;
}

const expr_index = self.exprs.items.len;
try self.exprs.append(self.arena, .{ .array = array_data });

return DocData.WalkResult{
.typeRef = null,
.expr = .{ .@"&" = expr_index },
};
},
.float => {
const float = data[inst_index].float;
return DocData.WalkResult{
Expand Down Expand Up @@ -2696,9 +2668,7 @@ fn walkInstruction(
.expr = .{ .declRef = decl_status },
};
},
.field_val, .field_ptr, .field_type => {
// TODO: field type uses Zir.Inst.FieldType, it just happens to have the
// same layout as Zir.Inst.Field :^)
.field_val, .field_ptr => {
const pl_node = data[inst_index].pl_node;
const extra = file.zir.extraData(Zir.Inst.Field, pl_node.payload_index);

Expand All @@ -2717,8 +2687,7 @@ fn walkInstruction(
};

if (tags[lhs] != .field_val and
tags[lhs] != .field_ptr and
tags[lhs] != .field_type) break :blk lhs_extra.data.lhs;
tags[lhs] != .field_ptr) break :blk lhs_extra.data.lhs;

lhs_extra = file.zir.extraData(
Zir.Inst.Field,
Expand Down Expand Up @@ -2857,7 +2826,7 @@ fn walkInstruction(

const field_name = blk: {
const field_inst_index = init_extra.data.field_type;
if (tags[field_inst_index] != .field_type) unreachable;
if (tags[field_inst_index] != .struct_init_field_type) unreachable;
const field_pl_node = data[field_inst_index].pl_node;
const field_extra = file.zir.extraData(
Zir.Inst.FieldType,
Expand Down
779 changes: 339 additions & 440 deletions src/Sema.zig

Large diffs are not rendered by default.

313 changes: 170 additions & 143 deletions src/Zir.zig

Large diffs are not rendered by default.

79 changes: 50 additions & 29 deletions src/print_zir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,6 @@ const Writer = struct {
.store_to_inferred_ptr,
=> try self.writeBin(stream, inst),

.elem_type_index => try self.writeElemTypeIndex(stream, inst),

.alloc,
.alloc_mut,
.alloc_comptime_mut,
Expand Down Expand Up @@ -184,7 +182,6 @@ const Writer = struct {
.is_non_err_ptr,
.ret_is_non_err,
.typeof,
.struct_init_empty,
.type_info,
.size_of,
.bit_size_of,
Expand Down Expand Up @@ -224,13 +221,10 @@ const Writer = struct {
.bit_reverse,
.@"resume",
.@"await",
.array_base_ptr,
.field_base_ptr,
.validate_struct_init_ty,
.make_ptr_const,
.validate_deref,
.check_comptime_control_flow,
.opt_eu_base_ty,
.opt_eu_base_ptr_init,
=> try self.writeUnNode(stream, inst),

.ref,
Expand All @@ -243,7 +237,6 @@ const Writer = struct {
=> try self.writeBoolBr(stream, inst),

.validate_destructure => try self.writeValidateDestructure(stream, inst),
.validate_array_init_ty => try self.writeValidateArrayInitTy(stream, inst),
.array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst),
.ptr_type => try self.writePtrType(stream, inst),
.int => try self.writeInt(stream, inst),
Expand All @@ -259,12 +252,6 @@ const Writer = struct {
.@"break",
.break_inline,
=> try self.writeBreak(stream, inst),
.array_init,
.array_init_ref,
=> try self.writeArrayInit(stream, inst),
.array_init_anon,
.array_init_anon_ref,
=> try self.writeArrayInitAnon(stream, inst),

.slice_start => try self.writeSliceStart(stream, inst),
.slice_end => try self.writeSliceEnd(stream, inst),
Expand All @@ -273,10 +260,44 @@ const Writer = struct {

.union_init => try self.writeUnionInit(stream, inst),

// Struct inits

.struct_init_empty,
.struct_init_empty_result,
.struct_init_empty_ref_result,
=> try self.writeUnNode(stream, inst),

.struct_init_anon => try self.writeStructInitAnon(stream, inst),

.struct_init,
.struct_init_ref,
=> try self.writeStructInit(stream, inst),

.validate_struct_init_ty,
.validate_struct_init_result_ty,
=> try self.writeUnNode(stream, inst),

.validate_ptr_struct_init => try self.writeBlock(stream, inst),
.struct_init_field_type => try self.writeStructInitFieldType(stream, inst),
.struct_init_field_ptr => try self.writePlNodeField(stream, inst),

// Array inits

.array_init_anon => try self.writeArrayInitAnon(stream, inst),

.array_init,
.array_init_ref,
=> try self.writeArrayInit(stream, inst),

.validate_array_init_ty,
.validate_array_init_result_ty,
=> try self.writeValidateArrayInitTy(stream, inst),

.validate_array_init_ref_ty => try self.writeValidateArrayInitRefTy(stream, inst),
.validate_ptr_array_init => try self.writeBlock(stream, inst),
.array_init_elem_type => try self.writeArrayInitElemType(stream, inst),
.array_init_elem_ptr => try self.writeArrayInitElemPtr(stream, inst),

.atomic_load => try self.writeAtomicLoad(stream, inst),
.atomic_store => try self.writeAtomicStore(stream, inst),
.atomic_rmw => try self.writeAtomicRmw(stream, inst),
Expand All @@ -285,11 +306,6 @@ const Writer = struct {
.field_parent_ptr => try self.writeFieldParentPtr(stream, inst),
.builtin_call => try self.writeBuiltinCall(stream, inst),

.struct_init_anon,
.struct_init_anon_ref,
=> try self.writeStructInitAnon(stream, inst),

.field_type => try self.writeFieldType(stream, inst),
.field_type_ref => try self.writeFieldTypeRef(stream, inst),

.add,
Expand Down Expand Up @@ -352,16 +368,14 @@ const Writer = struct {
.elem_val_node,
.elem_ptr,
.elem_val,
.coerce_result_ptr,
.array_type,
.coerce_ptr_elem_ty,
=> try self.writePlNodeBin(stream, inst),

.for_len => try self.writePlNodeMultiOp(stream, inst),

.elem_val_imm => try self.writeElemValImm(stream, inst),

.elem_ptr_imm => try self.writeElemPtrImm(stream, inst),

.@"export" => try self.writePlNodeExport(stream, inst),
.export_value => try self.writePlNodeExportValue(stream, inst),

Expand All @@ -373,8 +387,6 @@ const Writer = struct {
.block_inline,
.suspend_block,
.loop,
.validate_struct_init,
.validate_array_init,
.c_import,
.typeof_builtin,
=> try self.writeBlock(stream, inst),
Expand All @@ -395,9 +407,8 @@ const Writer = struct {
.switch_block_ref,
=> try self.writeSwitchBlock(stream, inst),

.field_ptr,
.field_ptr_init,
.field_val,
.field_ptr,
=> try self.writePlNodeField(stream, inst),

.field_ptr_named,
Expand Down Expand Up @@ -560,7 +571,7 @@ const Writer = struct {
try stream.writeByte(')');
}

fn writeElemTypeIndex(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
fn writeArrayInitElemType(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].bin;
try self.writeInstRef(stream, inst_data.lhs);
try stream.print(", {d})", .{@intFromEnum(inst_data.rhs)});
Expand Down Expand Up @@ -915,7 +926,7 @@ const Writer = struct {
try stream.print(", {d})", .{inst_data.idx});
}

fn writeElemPtrImm(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
fn writeArrayInitElemPtr(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Zir.Inst.ElemPtrImm, inst_data.payload_index).data;

Expand Down Expand Up @@ -947,6 +958,16 @@ const Writer = struct {
try self.writeSrc(stream, inst_data.src());
}

fn writeValidateArrayInitRefTy(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Zir.Inst.ArrayInitRefTy, inst_data.payload_index).data;

try self.writeInstRef(stream, extra.ptr_ty);
try stream.writeAll(", ");
try stream.print(", {}) ", .{extra.elem_count});
try self.writeSrc(stream, inst_data.src());
}

fn writeStructInit(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Zir.Inst.StructInit, inst_data.payload_index);
Expand Down Expand Up @@ -1077,7 +1098,7 @@ const Writer = struct {
try self.writeSrc(stream, inst_data.src());
}

fn writeFieldType(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
fn writeStructInitFieldType(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Zir.Inst.FieldType, inst_data.payload_index).data;
try self.writeInstRef(stream, extra.container_type);
Expand Down
34 changes: 34 additions & 0 deletions test/behavior/array.zig
Original file line number Diff line number Diff line change
Expand Up @@ -799,3 +799,37 @@ test "runtime side-effects in comptime-known array init" {
try expectEqual([4]u4{ 1, 2, 4, 8 }, init);
try expectEqual(@as(u4, std.math.maxInt(u4)), side_effects);
}

test "slice initialized through reference to anonymous array init provides result types" {
var my_u32: u32 = 123;
var my_u64: u64 = 456;
const foo: []const u16 = &.{
@intCast(my_u32),
@intCast(my_u64),
@truncate(my_u32),
@truncate(my_u64),
};
try std.testing.expectEqualSlices(u16, &.{ 123, 456, 123, 456 }, foo);
}

test "pointer to array initialized through reference to anonymous array init provides result types" {
var my_u32: u32 = 123;
var my_u64: u64 = 456;
const foo: *const [4]u16 = &.{
@intCast(my_u32),
@intCast(my_u64),
@truncate(my_u32),
@truncate(my_u64),
};
try std.testing.expectEqualSlices(u16, &.{ 123, 456, 123, 456 }, foo);
}

test "tuple initialized through reference to anonymous array init provides result types" {
const Tuple = struct { u64, *const u32 };
const foo: *const Tuple = &.{
@intCast(12345),
@ptrFromInt(0x1000),
};
try expect(foo[0] == 12345);
try expect(@intFromPtr(foo[1]) == 0x1000);
}
26 changes: 26 additions & 0 deletions test/behavior/cast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2526,3 +2526,29 @@ test "@as does not corrupt values with incompatible representations" {
});
try std.testing.expectApproxEqAbs(@as(f32, 1.23), x, 0.001);
}

test "result information is preserved through many nested structures" {
if (builtin.zig_backend == .stage2_wasm) 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
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;

const S = struct {
fn doTheTest() !void {
const E = error{Foo};
const T = *const ?E!struct { x: ?*const E!?u8 };

var val: T = &.{ .x = &@truncate(0x1234) };

const struct_val = val.*.? catch unreachable;
const int_val = (struct_val.x.?.* catch unreachable).?;

try expect(int_val == 0x34);
}
};

try S.doTheTest();
try comptime S.doTheTest();
}
18 changes: 18 additions & 0 deletions test/behavior/pointers.zig
Original file line number Diff line number Diff line change
Expand Up @@ -557,3 +557,21 @@ test "pointer to array has explicit alignment" {
const casted = S.func(&bases);
try expect(casted[0].a == 2);
}

test "result type preserved through multiple references" {
const S = struct { x: u32 };
var my_u64: u64 = 12345;
const foo: *const *const *const S = &&&.{
.x = @intCast(my_u64),
};
try expect(foo.*.*.*.x == 12345);
}

test "result type found through optional pointer" {
const ptr1: ?*const u32 = &@intCast(123);
const ptr2: ?[]const u8 = &.{ @intCast(123), @truncate(0xABCD) };
try expect(ptr1.?.* == 123);
try expect(ptr2.?.len == 2);
try expect(ptr2.?[0] == 123);
try expect(ptr2.?[1] == 0xCD);
}
15 changes: 15 additions & 0 deletions test/behavior/struct.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1763,3 +1763,18 @@ test "runtime side-effects in comptime-known struct init" {
try expectEqual(S{ .a = 1, .b = 2, .c = 4, .d = 8 }, init);
try expectEqual(@as(u4, std.math.maxInt(u4)), side_effects);
}

test "pointer to struct initialized through reference to anonymous initializer provides result types" {
const S = struct { a: u8, b: u16, c: *const anyopaque };
var my_u16: u16 = 0xABCD;
const s: *const S = &.{
// intentionally out of order
.c = @ptrCast("hello"),
.b = my_u16,
.a = @truncate(my_u16),
};
try expect(s.a == 0xCD);
try expect(s.b == 0xABCD);
const str: *const [5]u8 = @ptrCast(s.c);
try std.testing.expectEqualSlices(u8, "hello", str);
}

0 comments on commit dab90b3

Please sign in to comment.