From e5d6fe18b90cf2602e7e7712b4dd807b4e243356 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 17 Oct 2021 20:11:22 +0200 Subject: [PATCH 01/11] stage2: restructure Sema.fieldVal and sema.fieldPtr Dereferencing single pointers is now handled outside of the main switch, which allows deduplication of some cases. This also implements the relevant operations for pointers to types and pointers to slices. --- src/Sema.zig | 278 +++++++++++++++++++++++++-------------------------- 1 file changed, 137 insertions(+), 141 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index e3e8982fac..a07c5761ff 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5119,16 +5119,10 @@ fn zirFieldVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node }; - const lhs_src: LazySrcLoc = src; // TODO const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data; const field_name = sema.code.nullTerminatedString(extra.field_name_start); const object = sema.resolveInst(extra.lhs); - if (sema.typeOf(object).isSinglePointer()) { - const result_ptr = try sema.fieldPtr(block, src, object, field_name, field_name_src); - return sema.analyzeLoad(block, src, result_ptr, lhs_src); - } else { - return sema.fieldVal(block, src, object, field_name, field_name_src); - } + return sema.fieldVal(block, src, object, field_name, field_name_src); } fn zirFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -10688,12 +10682,22 @@ fn fieldVal( const object_src = src; // TODO better source location const object_ty = sema.typeOf(object); - switch (object_ty.zigTypeTag()) { + // Zig allows dereferencing a single pointer during field lookup. Note that + // we don't actually need to generate the dereference some field lookups, like the + // length of arrays and other comptime operations. + const is_pointer_to = object_ty.isSinglePointer(); + + const inner_ty = if (is_pointer_to) + object_ty.childType() + else + object_ty; + + switch (inner_ty.zigTypeTag()) { .Array => { if (mem.eql(u8, field_name, "len")) { return sema.addConstant( Type.initTag(.comptime_int), - try Value.Tag.int_u64.create(arena, object_ty.arrayLen()), + try Value.Tag.int_u64.create(arena, inner_ty.arrayLen()), ); } else { return sema.fail( @@ -10704,75 +10708,60 @@ fn fieldVal( ); } }, - .Pointer => switch (object_ty.ptrSize()) { - .Slice => { - if (mem.eql(u8, field_name, "ptr")) { - const buf = try arena.create(Type.SlicePtrFieldTypeBuffer); - const result_ty = object_ty.slicePtrFieldType(buf); - if (try sema.resolveMaybeUndefVal(block, object_src, object)) |val| { - if (val.isUndef()) return sema.addConstUndef(result_ty); - return sema.addConstant(result_ty, val.slicePtr()); - } - try sema.requireRuntimeBlock(block, src); - return block.addTyOp(.slice_ptr, result_ty, object); - } else if (mem.eql(u8, field_name, "len")) { - const result_ty = Type.usize; - if (try sema.resolveMaybeUndefVal(block, object_src, object)) |val| { - if (val.isUndef()) return sema.addConstUndef(result_ty); - return sema.addConstant( - result_ty, - try Value.Tag.int_u64.create(arena, val.sliceLen()), - ); - } - try sema.requireRuntimeBlock(block, src); - return block.addTyOp(.slice_len, result_ty, object); - } else { - return sema.fail( - block, - field_name_src, - "no member named '{s}' in '{}'", - .{ field_name, object_ty }, + .Pointer => if (inner_ty.isSlice()) { + if (mem.eql(u8, field_name, "ptr")) { + const slice = if (is_pointer_to) + try sema.analyzeLoad(block, src, object, object_src) + else + object; + + const buf = try arena.create(Type.SlicePtrFieldTypeBuffer); + const result_ty = inner_ty.slicePtrFieldType(buf); + + if (try sema.resolveMaybeUndefVal(block, object_src, slice)) |val| { + if (val.isUndef()) return sema.addConstUndef(result_ty); + return sema.addConstant(result_ty, val.slicePtr()); + } + try sema.requireRuntimeBlock(block, src); + return block.addTyOp(.slice_ptr, result_ty, slice); + } else if (mem.eql(u8, field_name, "len")) { + const slice = if (is_pointer_to) + try sema.analyzeLoad(block, src, object, object_src) + else + object; + + const result_ty = Type.usize; + + if (try sema.resolveMaybeUndefVal(block, object_src, slice)) |val| { + if (val.isUndef()) return sema.addConstUndef(result_ty); + return sema.addConstant( + result_ty, + try Value.Tag.int_u64.create(arena, val.sliceLen()), ); } - }, - .One => { - const ptr_child = object_ty.elemType(); - switch (ptr_child.zigTypeTag()) { - .Array => { - if (mem.eql(u8, field_name, "len")) { - return sema.addConstant( - Type.initTag(.comptime_int), - try Value.Tag.int_u64.create(arena, ptr_child.arrayLen()), - ); - } else { - return sema.fail( - block, - field_name_src, - "no member named '{s}' in '{}'", - .{ field_name, object_ty }, - ); - } - }, - .Struct => { - const struct_ptr_deref = try sema.analyzeLoad(block, src, object, object_src); - return sema.unionFieldVal(block, src, struct_ptr_deref, field_name, field_name_src, ptr_child); - }, - .Union => { - const union_ptr_deref = try sema.analyzeLoad(block, src, object, object_src); - return sema.unionFieldVal(block, src, union_ptr_deref, field_name, field_name_src, ptr_child); - }, - else => {}, - } - }, - .Many, .C => {}, + try sema.requireRuntimeBlock(block, src); + return block.addTyOp(.slice_len, result_ty, slice); + } else { + return sema.fail( + block, + field_name_src, + "no member named '{s}' in '{}'", + .{ field_name, object_ty }, + ); + } }, .Type => { - const val = (try sema.resolveDefinedValue(block, object_src, object)).?; + const dereffed_type = if (is_pointer_to) + try sema.analyzeLoad(block, src, object, object_src) + else + object; + + const val = (try sema.resolveDefinedValue(block, object_src, dereffed_type)).?; var to_type_buffer: Value.ToTypeBuffer = undefined; const child_type = val.toType(&to_type_buffer); + switch (child_type.zigTypeTag()) { .ErrorSet => { - // TODO resolve inferred error sets const name: []const u8 = if (child_type.castTag(.error_set)) |payload| blk: { const error_set = payload.data; // TODO this is O(N). I'm putting off solving this until we solve inferred @@ -10842,8 +10831,20 @@ fn fieldVal( else => return sema.fail(block, src, "type '{}' has no members", .{child_type}), } }, - .Struct => return sema.structFieldVal(block, src, object, field_name, field_name_src, object_ty), - .Union => return sema.unionFieldVal(block, src, object, field_name, field_name_src, object_ty), + .Struct => if (is_pointer_to) { + // Avoid loading the entire struct by fetching a pointer and loading that + const field_ptr = try sema.structFieldPtr(block, src, object, field_name, field_name_src, inner_ty); + return sema.analyzeLoad(block, src, field_ptr, object_src); + } else { + return sema.structFieldVal(block, src, object, field_name, field_name_src, inner_ty); + }, + .Union => if (is_pointer_to) { + // Avoid loading the entire union by fetching a pointer and loading that + const field_ptr = try sema.unionFieldPtr(block, src, object, field_name, field_name_src, inner_ty); + return sema.analyzeLoad(block, src, field_ptr, object_src); + } else { + return sema.unionFieldVal(block, src, object, field_name, field_name_src, inner_ty); + }, else => {}, } return sema.fail(block, src, "type '{}' does not support field access", .{object_ty}); @@ -10866,14 +10867,25 @@ fn fieldPtr( .Pointer => object_ptr_ty.elemType(), else => return sema.fail(block, object_ptr_src, "expected pointer, found '{}'", .{object_ptr_ty}), }; - switch (object_ty.zigTypeTag()) { + + // Zig allows dereferencing a single pointer during field lookup. Note that + // we don't actually need to generate the dereference some field lookups, like the + // length of arrays and other comptime operations. + const is_pointer_to = object_ty.isSinglePointer(); + + const inner_ty = if (is_pointer_to) + object_ty.childType() + else + object_ty; + + switch (inner_ty.zigTypeTag()) { .Array => { if (mem.eql(u8, field_name, "len")) { var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); return sema.analyzeDeclRef(try anon_decl.finish( Type.initTag(.comptime_int), - try Value.Tag.int_u64.create(anon_decl.arena(), object_ty.arrayLen()), + try Value.Tag.int_u64.create(anon_decl.arena(), inner_ty.arrayLen()), )); } else { return sema.fail( @@ -10884,77 +10896,49 @@ fn fieldPtr( ); } }, - .Pointer => switch (object_ty.ptrSize()) { - .Slice => { - // Here for the ptr and len fields what we need to do is the situation - // when a temporary has its address taken, e.g. `&a[c..d].len`. - // This value may be known at compile-time or runtime. In the former - // case, it should create an anonymous Decl and return a decl_ref to it. - // In the latter case, it should add an `alloc` instruction, store - // the runtime value to it, and then return the `alloc`. - // In both cases the pointer should be const. - if (mem.eql(u8, field_name, "ptr")) { - return sema.fail( - block, - field_name_src, - "TODO: implement reference to 'ptr' field of slice '{}'", - .{object_ty}, - ); - } else if (mem.eql(u8, field_name, "len")) { - return sema.fail( - block, - field_name_src, - "TODO: implement reference to 'len' field of slice '{}'", - .{object_ty}, - ); - } else { - return sema.fail( - block, - field_name_src, - "no member named '{s}' in '{}'", - .{ field_name, object_ty }, - ); - } - }, - .One => { - const ptr_child = object_ty.elemType(); - switch (ptr_child.zigTypeTag()) { - .Array => { - if (mem.eql(u8, field_name, "len")) { - var anon_decl = try block.startAnonDecl(); - defer anon_decl.deinit(); - return sema.analyzeDeclRef(try anon_decl.finish( - Type.initTag(.comptime_int), - try Value.Tag.int_u64.create(anon_decl.arena(), ptr_child.arrayLen()), - )); - } else { - return sema.fail( - block, - field_name_src, - "no member named '{s}' in '{}'", - .{ field_name, object_ty }, - ); - } - }, - .Struct => { - const struct_ptr_deref = try sema.analyzeLoad(block, src, object_ptr, object_ptr_src); - return sema.structFieldPtr(block, src, struct_ptr_deref, field_name, field_name_src, ptr_child); - }, - .Union => { - const union_ptr_deref = try sema.analyzeLoad(block, src, object_ptr, object_ptr_src); - return sema.unionFieldPtr(block, src, union_ptr_deref, field_name, field_name_src, ptr_child); - }, - else => {}, - } - }, - .Many, .C => {}, + .Pointer => if (inner_ty.isSlice()) { + // Here for the ptr and len fields what we need to do is the situation + // when a temporary has its address taken, e.g. `&a[c..d].len`. + // This value may be known at compile-time or runtime. In the former + // case, it should create an anonymous Decl and return a decl_ref to it. + // In the latter case, it should add an `alloc` instruction, store + // the runtime value to it, and then return the `alloc`. + // In both cases the pointer should be const. + if (mem.eql(u8, field_name, "ptr")) { + return sema.fail( + block, + field_name_src, + "TODO: implement reference to 'ptr' field of slice '{}'", + .{inner_ty}, + ); + } else if (mem.eql(u8, field_name, "len")) { + return sema.fail( + block, + field_name_src, + "TODO: implement reference to 'len' field of slice '{}'", + .{inner_ty}, + ); + } else { + return sema.fail( + block, + field_name_src, + "no member named '{s}' in '{}'", + .{ field_name, object_ty }, + ); + } }, .Type => { _ = try sema.resolveConstValue(block, object_ptr_src, object_ptr); const result = try sema.analyzeLoad(block, src, object_ptr, object_ptr_src); - const val = (sema.resolveDefinedValue(block, src, result) catch unreachable).?; + const inner = if (is_pointer_to) + try sema.analyzeLoad(block, src, result, object_ptr_src) + else + result; + + const val = (sema.resolveDefinedValue(block, src, inner) catch unreachable).?; var to_type_buffer: Value.ToTypeBuffer = undefined; const child_type = val.toType(&to_type_buffer); + switch (child_type.zigTypeTag()) { .ErrorSet => { // TODO resolve inferred error sets @@ -11033,8 +11017,20 @@ fn fieldPtr( else => return sema.fail(block, src, "type '{}' has no members", .{child_type}), } }, - .Struct => return sema.structFieldPtr(block, src, object_ptr, field_name, field_name_src, object_ty), - .Union => return sema.unionFieldPtr(block, src, object_ptr, field_name, field_name_src, object_ty), + .Struct => { + const inner_ptr = if (is_pointer_to) + try sema.analyzeLoad(block, src, object_ptr, object_ptr_src) + else + object_ptr; + return sema.structFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty); + }, + .Union => { + const inner_ptr = if (is_pointer_to) + try sema.analyzeLoad(block, src, object_ptr, object_ptr_src) + else + object_ptr; + return sema.unionFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty); + }, else => {}, } return sema.fail(block, src, "type '{}' does not support field access", .{object_ty}); From 05c5c99a95c894d7e3241e1729e09aa7eacb6035 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 18 Oct 2021 01:00:07 +0200 Subject: [PATCH 02/11] stage2: air ptr_slice_len_ptr and ptr_slice_ptr_ptr --- src/Air.zig | 8 ++++++++ src/Liveness.zig | 2 ++ src/arch/aarch64/CodeGen.zig | 15 +++++++++++++++ src/codegen.zig | 19 +++++++++++++++++++ src/codegen/c.zig | 18 ++++++++++++++++++ src/codegen/llvm.zig | 13 +++++++++++++ src/print_air.zig | 2 ++ 7 files changed, 77 insertions(+) diff --git a/src/Air.zig b/src/Air.zig index 86e16487bb..f3e0865ba8 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -367,6 +367,12 @@ pub const Inst = struct { /// Given a slice value, return the pointer. /// Uses the `ty_op` field. slice_ptr, + /// Given a pointer to a slice, return a pointer to the length of the slice. + /// Uses the `ty_op` field. + ptr_slice_len_ptr, + /// Given a pointer to a slice, return a pointer to the pointer of the slice. + /// Uses the `ty_op` field. + ptr_slice_ptr_ptr, /// Given an array value and element index, return the element value at that index. /// Result type is the element type of the array operand. /// Uses the `bin_op` field. @@ -707,6 +713,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .wrap_errunion_payload, .wrap_errunion_err, .slice_ptr, + .ptr_slice_len_ptr, + .ptr_slice_ptr_ptr, .struct_field_ptr_index_0, .struct_field_ptr_index_1, .struct_field_ptr_index_2, diff --git a/src/Liveness.zig b/src/Liveness.zig index 6f7c938f4c..c56bcc8c09 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -300,6 +300,8 @@ fn analyzeInst( .wrap_errunion_err, .slice_ptr, .slice_len, + .ptr_slice_len_ptr, + .ptr_slice_ptr_ptr, .struct_field_ptr_index_0, .struct_field_ptr_index_1, .struct_field_ptr_index_2, diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 73ada3a9ca..34d9090224 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -494,6 +494,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .slice_ptr => try self.airSlicePtr(inst), .slice_len => try self.airSliceLen(inst), + .ptr_slice_len_ptr => try self.airPtrSliceLenPtr(inst), + .ptr_slice_ptr_ptr => try self.airPtrSlicePtrPtr(inst), + .array_elem_val => try self.airArrayElemVal(inst), .slice_elem_val => try self.airSliceElemVal(inst), .ptr_slice_elem_val => try self.airPtrSliceElemVal(inst), @@ -1057,6 +1060,18 @@ fn airSliceLen(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } +fn airPtrSliceLenPtr(self: *Self, inst: Air.Inst.Index) !void { + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement ptr_slice_len_ptr for {}", .{self.target.cpu.arch}); + return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); +} + +fn airPtrSlicePtrPtr(self: *Self, inst: Air.Inst.Index) !void { + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement ptr_slice_ptr_ptr for {}", .{self.target.cpu.arch}); + return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); +} + fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void { const is_volatile = false; // TODO const bin_op = self.air.instructions.items(.data)[inst].bin_op; diff --git a/src/codegen.zig b/src/codegen.zig index 0371f32a8a..bedbdf44f1 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -842,6 +842,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .slice_ptr => try self.airSlicePtr(inst), .slice_len => try self.airSliceLen(inst), + .ptr_slice_len_ptr => try self.airPtrSliceLenPtr(inst), + .ptr_slice_ptr_ptr => try self.airPtrSlicePtrPtr(inst), + .array_elem_val => try self.airArrayElemVal(inst), .slice_elem_val => try self.airSliceElemVal(inst), .ptr_slice_elem_val => try self.airPtrSliceElemVal(inst), @@ -1498,6 +1501,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } + fn airPtrSliceLenPtr(self: *Self, inst: Air.Inst.Index) !void { + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement ptr_slice_len_ptr for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); + } + + fn airPtrSlicePtrPtr(self: *Self, inst: Air.Inst.Index) !void { + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement ptr_slice_ptr_ptr for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); + } + fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void { const is_volatile = false; // TODO const bin_op = self.air.instructions.items(.data)[inst].bin_op; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index ad98dc87c1..1bb337743d 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1075,6 +1075,9 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .slice_ptr => try airSliceField(f, inst, ".ptr;\n"), .slice_len => try airSliceField(f, inst, ".len;\n"), + .ptr_slice_len_ptr => try airPtrSliceFieldPtr(f, inst, ".len;\n"), + .ptr_slice_ptr_ptr => try airPtrSliceFieldPtr(f, inst, ".ptr;\n"), + .ptr_elem_val => try airPtrElemVal(f, inst, "["), .ptr_ptr_elem_val => try airPtrElemVal(f, inst, "[0]["), .ptr_elem_ptr => try airPtrElemPtr(f, inst), @@ -1114,6 +1117,21 @@ fn airSliceField(f: *Function, inst: Air.Inst.Index, suffix: []const u8) !CValue return local; } +fn airPtrSliceFieldPtr(f: *Function, inst: Air.Inst.Index, suffix: []const u8) !CValue { + if (f.liveness.isUnused(inst)) + return CValue.none; + + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const operand = try f.resolveInst(ty_op.operand); + const writer = f.object.writer(); + + _ = writer; + _ = operand; + _ = suffix; + + return f.fail("TODO: C backend: airPtrSliceFieldPtr", .{}); +} + fn airPtrElemVal(f: *Function, inst: Air.Inst.Index, prefix: []const u8) !CValue { const is_volatile = false; // TODO if (!is_volatile and f.liveness.isUnused(inst)) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 1a8a70c39b..8ec5075f84 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1709,6 +1709,10 @@ pub const FuncGen = struct { .assembly => try self.airAssembly(inst), .slice_ptr => try self.airSliceField(inst, 0), .slice_len => try self.airSliceField(inst, 1), + + .ptr_slice_ptr_ptr => try self.airPtrSliceFieldPtr(inst, 0), + .ptr_slice_len_ptr => try self.airPtrSliceFieldPtr(inst, 1), + .array_to_slice => try self.airArrayToSlice(inst), .float_to_int => try self.airFloatToInt(inst), .int_to_float => try self.airIntToFloat(inst), @@ -2091,6 +2095,15 @@ pub const FuncGen = struct { return self.builder.buildExtractValue(operand, index, ""); } + fn airPtrSliceFieldPtr(self: *FuncGen, inst: Air.Inst.Index, index: c_uint) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const slice_ptr = try self.resolveInst(ty_op.operand); + + return self.builder.buildStructGEP(slice_ptr, index, ""); + } + fn airSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const slice_ty = self.air.typeOf(bin_op.lhs); diff --git a/src/print_air.zig b/src/print_air.zig index 861483abac..48014d02b0 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -183,6 +183,8 @@ const Writer = struct { .wrap_errunion_err, .slice_ptr, .slice_len, + .ptr_slice_len_ptr, + .ptr_slice_ptr_ptr, .struct_field_ptr_index_0, .struct_field_ptr_index_1, .struct_field_ptr_index_2, From c507e0b76396a200392e16b92292bfc442c6421f Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 18 Oct 2021 15:09:13 +0200 Subject: [PATCH 03/11] stage2: Sema.fieldPtr for slice ptr and len --- src/Sema.zig | 63 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index a07c5761ff..7b4eafe1d4 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -10897,27 +10897,52 @@ fn fieldPtr( } }, .Pointer => if (inner_ty.isSlice()) { - // Here for the ptr and len fields what we need to do is the situation - // when a temporary has its address taken, e.g. `&a[c..d].len`. - // This value may be known at compile-time or runtime. In the former - // case, it should create an anonymous Decl and return a decl_ref to it. - // In the latter case, it should add an `alloc` instruction, store - // the runtime value to it, and then return the `alloc`. - // In both cases the pointer should be const. + const inner_ptr = if (is_pointer_to) + try sema.analyzeLoad(block, src, object_ptr, object_ptr_src) + else + object_ptr; + if (mem.eql(u8, field_name, "ptr")) { - return sema.fail( - block, - field_name_src, - "TODO: implement reference to 'ptr' field of slice '{}'", - .{inner_ty}, - ); + const buf = try sema.arena.create(Type.SlicePtrFieldTypeBuffer); + const slice_ptr_ty = inner_ty.slicePtrFieldType(buf); + + if (try sema.resolveDefinedValue(block, object_ptr_src, inner_ptr)) |val| { + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); + + return sema.analyzeDeclRef(try anon_decl.finish( + try slice_ptr_ty.copy(anon_decl.arena()), + try val.slicePtr().copy(anon_decl.arena()), + )); + } + try sema.requireRuntimeBlock(block, src); + + const result_ty = try Type.ptr(sema.arena, .{ + .pointee_type = slice_ptr_ty, + .mutable = object_ptr_ty.ptrIsMutable(), + .@"addrspace" = object_ptr_ty.ptrAddressSpace(), + }); + + return block.addTyOp(.ptr_slice_ptr_ptr, result_ty, inner_ptr); } else if (mem.eql(u8, field_name, "len")) { - return sema.fail( - block, - field_name_src, - "TODO: implement reference to 'len' field of slice '{}'", - .{inner_ty}, - ); + if (try sema.resolveDefinedValue(block, object_ptr_src, inner_ptr)) |val| { + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); + + return sema.analyzeDeclRef(try anon_decl.finish( + Type.usize, + try Value.Tag.int_u64.create(anon_decl.arena(), val.sliceLen()), + )); + } + try sema.requireRuntimeBlock(block, src); + + const result_ty = try Type.ptr(sema.arena, .{ + .pointee_type = Type.usize, + .mutable = object_ptr_ty.ptrIsMutable(), + .@"addrspace" = object_ptr_ty.ptrAddressSpace(), + }); + + return block.addTyOp(.ptr_slice_len_ptr, result_ty, inner_ptr); } else { return sema.fail( block, From bfedf40c9267367d6ffdad54050dcbd451136b58 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 18 Oct 2021 18:18:33 +0200 Subject: [PATCH 04/11] stage2: zirIndexablePtrLen for non-pointer types --- src/Sema.zig | 68 ++++++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 7b4eafe1d4..7a5ac1baa1 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1968,44 +1968,38 @@ fn zirIndexablePtrLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - const array = sema.resolveInst(inst_data.operand); - const array_ty = sema.typeOf(array); + const object = sema.resolveInst(inst_data.operand); + const object_ty = sema.typeOf(object); - if (array_ty.isSlice()) { - return sema.analyzeSliceLen(block, src, array); + const is_pointer_to = object_ty.isSinglePointer(); + + const array_ty = if (is_pointer_to) + object_ty.childType() + else + object_ty; + + if (!array_ty.isIndexable()) { + const msg = msg: { + const msg = try sema.errMsg( + block, + src, + "type '{}' does not support indexing", + .{array_ty}, + ); + errdefer msg.destroy(sema.gpa); + try sema.errNote( + block, + src, + msg, + "for loop operand must be an array, slice, tuple, or vector", + .{}, + ); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); } - if (array_ty.isSinglePointer()) { - const elem_ty = array_ty.elemType(); - if (elem_ty.isSlice()) { - const slice_inst = try sema.analyzeLoad(block, src, array, src); - return sema.analyzeSliceLen(block, src, slice_inst); - } - if (!elem_ty.isIndexable()) { - const msg = msg: { - const msg = try sema.errMsg( - block, - src, - "type '{}' does not support indexing", - .{elem_ty}, - ); - errdefer msg.destroy(sema.gpa); - try sema.errNote( - block, - src, - msg, - "for loop operand must be an array, slice, tuple, or vector", - .{}, - ); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(msg); - } - const result_ptr = try sema.fieldPtr(block, src, array, "len", src); - return sema.analyzeLoad(block, src, result_ptr, src); - } - - return sema.fail(block, src, "TODO implement Sema.zirIndexablePtrLen", .{}); + return sema.fieldVal(block, src, object, "len", src); } fn zirAllocExtended( @@ -10847,7 +10841,7 @@ fn fieldVal( }, else => {}, } - return sema.fail(block, src, "type '{}' does not support field access", .{object_ty}); + return sema.fail(block, src, "type '{}' does not support field access (fieldVal, {}.{s})", .{object_ty, object_ty, field_name}); } fn fieldPtr( @@ -11058,7 +11052,7 @@ fn fieldPtr( }, else => {}, } - return sema.fail(block, src, "type '{}' does not support field access", .{object_ty}); + return sema.fail(block, src, "type '{}' does not support field access (fieldPtr, {}.{s})", .{object_ty, object_ptr_ty, field_name}); } fn fieldCallBind( From b65582e834de34f2351fa04a47f20e7f9c16a47c Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Tue, 19 Oct 2021 15:08:28 +0200 Subject: [PATCH 05/11] stage2: remove AstGen none_or_ref The remaining uses of this result location were causing a bunch of errors problems where the pointers returned from rvalue and lvalue expressions would be confused, allowing for extra pointers on rvalue expressions. For example: ```zig const X = struct {a: i32}; var x: X = .{.a = 1}; var ptr = &x; _ = x.a; ``` In the last line, the lookup of x with result location .none_or_ref would return a double pointer (**X). This would be dereferenced one, after which a relative pointer to `a` would be fetched and derefenced to get the final result. However, this also allows us to manually construct a double pointer, and fetch the field of the inner type of that: ```zig _ = &(&(x)).a; ``` This problem also manifests itself with element access. There are two obvious ways to fix the problem, both of which include replacing the usage of .none_or_ref for field- and element accesses with something which deterministically produce either a pointer or value: either result location .ref or .none. In the former case, this would be paired with .elem_ptr, and in the latter case with .elem_val. Note that the stage 1 compiler does not have this problem, because there is no equivalent of .elem_val and .field_val. In this way it is equivalent to using the result location .ref for field- and element accesses. In this case i have used .none, as this matches language behaviour more closely. --- src/AstGen.zig | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 6445f6ed11..83d82f4083 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -193,9 +193,6 @@ pub const ResultLoc = union(enum) { /// The expression must generate a pointer rather than a value. For example, the left hand side /// of an assignment uses this kind of result location. ref, - /// The callee will accept a ref, but it is not necessary, and the `ResultLoc` - /// may be treated as `none` instead. - none_or_ref, /// The expression will be coerced into this type, but it will be evaluated as an rvalue. ty: Zir.Inst.Ref, /// Same as `ty` but it is guaranteed that Sema will additionally perform the coercion, @@ -231,7 +228,7 @@ pub const ResultLoc = union(enum) { fn strategy(rl: ResultLoc, block_scope: *GenZir) Strategy { switch (rl) { // In this branch there will not be any store_to_block_ptr instructions. - .discard, .none, .none_or_ref, .ty, .coerced_ty, .ref => return .{ + .discard, .none, .ty, .coerced_ty, .ref => return .{ .tag = .break_operand, .elide_store_to_block_ptr_instructions = false, }, @@ -727,7 +724,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr .start = start, }); switch (rl) { - .ref, .none_or_ref => return result, + .ref => return result, else => { const dereffed = try gz.addUnNode(.load, result, node); return rvalue(gz, rl, dereffed, node); @@ -745,7 +742,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr .end = end, }); switch (rl) { - .ref, .none_or_ref => return result, + .ref => return result, else => { const dereffed = try gz.addUnNode(.load, result, node); return rvalue(gz, rl, dereffed, node); @@ -765,7 +762,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr .sentinel = sentinel, }); switch (rl) { - .ref, .none_or_ref => return result, + .ref => return result, else => { const dereffed = try gz.addUnNode(.load, result, node); return rvalue(gz, rl, dereffed, node); @@ -776,7 +773,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr .deref => { const lhs = try expr(gz, scope, .none, node_datas[node].lhs); switch (rl) { - .ref, .none_or_ref => return lhs, + .ref => return lhs, else => { const result = try gz.addUnNode(.load, lhs, node); return rvalue(gz, rl, result, node); @@ -1273,7 +1270,7 @@ fn arrayInitExpr( return arrayInitExprRlNone(gz, scope, node, array_init.ast.elements, .array_init_anon_ref); } }, - .none, .none_or_ref => { + .none => { if (types.array != .none) { return arrayInitExprRlTy(gz, scope, node, array_init.ast.elements, types.elem, .array_init); } else { @@ -1475,7 +1472,7 @@ fn structInitExpr( return structInitExprRlNone(gz, scope, node, struct_init, .struct_init_anon_ref); } }, - .none, .none_or_ref => { + .none => { if (struct_init.ast.type_expr != 0) { const ty_inst = try typeExpr(gz, scope, struct_init.ast.type_expr); return structInitExprRlTy(gz, scope, node, struct_init, ty_inst, .struct_init); @@ -5133,7 +5130,7 @@ fn fieldAccess( if (rl == .ref) { return addFieldAccess(.field_ptr, gz, scope, .ref, node); } else { - const access = try addFieldAccess(.field_val, gz, scope, .none_or_ref, node); + const access = try addFieldAccess(.field_val, gz, scope, .none, node); return rvalue(gz, rl, access, node); } } @@ -5178,7 +5175,7 @@ fn arrayAccess( ), else => return rvalue(gz, rl, try gz.addBin( .elem_val, - try expr(gz, scope, .none_or_ref, node_datas[node].lhs), + try expr(gz, scope, .none, node_datas[node].lhs), try expr(gz, scope, .{ .ty = .usize_type }, node_datas[node].rhs), ), node), } @@ -6664,7 +6661,7 @@ fn identifier( ); switch (rl) { - .ref, .none_or_ref => return ptr_inst, + .ref => return ptr_inst, else => { const loaded = try gz.addUnNode(.load, ptr_inst, ident); return rvalue(gz, rl, loaded, ident); @@ -6700,7 +6697,7 @@ fn identifier( // Decl references happen by name rather than ZIR index so that when unrelated // decls are modified, ZIR code containing references to them can be unmodified. switch (rl) { - .ref, .none_or_ref => return gz.addStrTok(.decl_ref, name_str_index, ident_token), + .ref => return gz.addStrTok(.decl_ref, name_str_index, ident_token), else => { const result = try gz.addStrTok(.decl_val, name_str_index, ident_token); return rvalue(gz, rl, result, ident); @@ -7105,7 +7102,7 @@ fn as( ) InnerError!Zir.Inst.Ref { const dest_type = try typeExpr(gz, scope, lhs); switch (rl) { - .none, .none_or_ref, .discard, .ref, .ty, .coerced_ty => { + .none, .discard, .ref, .ty, .coerced_ty => { const result = try reachableExpr(gz, scope, .{ .ty = dest_type }, rhs, node); return rvalue(gz, rl, result, node); }, @@ -7128,7 +7125,7 @@ fn unionInit( const union_type = try typeExpr(gz, scope, params[0]); const field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]); switch (rl) { - .none, .none_or_ref, .discard, .ref, .ty, .coerced_ty, .inferred_ptr => { + .none, .discard, .ref, .ty, .coerced_ty, .inferred_ptr => { _ = try gz.addPlNode(.field_type_ref, params[1], Zir.Inst.FieldTypeRef{ .container_type = union_type, .field_name = field_name, @@ -7192,7 +7189,7 @@ fn bitCast( const astgen = gz.astgen; const dest_type = try typeExpr(gz, scope, lhs); switch (rl) { - .none, .none_or_ref, .discard, .ty, .coerced_ty => { + .none, .discard, .ty, .coerced_ty => { const operand = try expr(gz, scope, .none, rhs); const result = try gz.addPlNode(.bitcast, node, Zir.Inst.Bin{ .lhs = dest_type, @@ -8799,7 +8796,7 @@ fn rvalue( ) InnerError!Zir.Inst.Ref { if (gz.endsWithNoReturn()) return result; switch (rl) { - .none, .none_or_ref, .coerced_ty => return result, + .none, .coerced_ty => return result, .discard => { // Emit a compile error for discarding error values. _ = try gz.addUnNode(.ensure_result_non_error, result, src_node); @@ -9561,9 +9558,7 @@ const GenZir = struct { gz.rl_ty_inst = ty_inst; gz.break_result_loc = parent_rl; }, - .none_or_ref => { - gz.break_result_loc = .ref; - }, + .discard, .none, .ptr, .ref => { gz.break_result_loc = parent_rl; }, From 7b97f6792fdc4f2774f109ec016ad19bf341e768 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Tue, 19 Oct 2021 23:19:56 +0200 Subject: [PATCH 06/11] stage2: add Value.the_only_possible_value --- src/value.zig | 111 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 37 deletions(-) diff --git a/src/value.zig b/src/value.zig index 1ae5351f54..e59cc662a7 100644 --- a/src/value.zig +++ b/src/value.zig @@ -86,6 +86,8 @@ pub const Value = extern union { one, void_value, unreachable_value, + /// The only possible value for a particular type, which is stored externally. + the_only_possible_value, null_value, bool_true, bool_false, @@ -226,6 +228,7 @@ pub const Value = extern union { .one, .void_value, .unreachable_value, + .the_only_possible_value, .empty_struct_value, .empty_array, .null_value, @@ -415,6 +418,7 @@ pub const Value = extern union { .one, .void_value, .unreachable_value, + .the_only_possible_value, .empty_array, .null_value, .bool_true, @@ -664,6 +668,7 @@ pub const Value = extern union { .one => return out_stream.writeAll("1"), .void_value => return out_stream.writeAll("{}"), .unreachable_value => return out_stream.writeAll("unreachable"), + .the_only_possible_value => return out_stream.writeAll("(the only possible value)"), .bool_true => return out_stream.writeAll("true"), .bool_false => return out_stream.writeAll("false"), .ty => return val.castTag(.ty).?.data.format("", options, out_stream), @@ -755,6 +760,7 @@ pub const Value = extern union { const decl_val = try decl.value(); return decl_val.toAllocatedBytes(decl.ty, allocator); }, + .the_only_possible_value => return &[_]u8{}, else => unreachable, } } @@ -847,53 +853,63 @@ pub const Value = extern union { // TODO should `@intToEnum` do this `@intCast` for you? return @intToEnum(E, @intCast(@typeInfo(E).Enum.tag_type, field_index)); }, + .the_only_possible_value => { + const fields = std.meta.fields(E); + assert(fields.len == 1); + return @intToEnum(E, fields[0].value); + }, else => unreachable, } } pub fn enumToInt(val: Value, ty: Type, buffer: *Payload.U64) Value { - if (val.castTag(.enum_field_index)) |enum_field_payload| { - const field_index = enum_field_payload.data; - switch (ty.tag()) { - .enum_full, .enum_nonexhaustive => { - const enum_full = ty.cast(Type.Payload.EnumFull).?.data; - if (enum_full.values.count() != 0) { - return enum_full.values.keys()[field_index]; - } else { - // Field index and integer values are the same. - buffer.* = .{ - .base = .{ .tag = .int_u64 }, - .data = field_index, - }; - return Value.initPayload(&buffer.base); - } - }, - .enum_numbered => { - const enum_obj = ty.castTag(.enum_numbered).?.data; - if (enum_obj.values.count() != 0) { - return enum_obj.values.keys()[field_index]; - } else { - // Field index and integer values are the same. - buffer.* = .{ - .base = .{ .tag = .int_u64 }, - .data = field_index, - }; - return Value.initPayload(&buffer.base); - } - }, - .enum_simple => { + const field_index = switch (val.tag()) { + .enum_field_index => val.castTag(.enum_field_index).?.data, + .the_only_possible_value => blk: { + assert(ty.enumFieldCount() == 1); + break :blk 0; + }, + // Assume it is already an integer and return it directly. + else => return val, + }; + + switch (ty.tag()) { + .enum_full, .enum_nonexhaustive => { + const enum_full = ty.cast(Type.Payload.EnumFull).?.data; + if (enum_full.values.count() != 0) { + return enum_full.values.keys()[field_index]; + } else { // Field index and integer values are the same. buffer.* = .{ .base = .{ .tag = .int_u64 }, .data = field_index, }; return Value.initPayload(&buffer.base); - }, - else => unreachable, - } + } + }, + .enum_numbered => { + const enum_obj = ty.castTag(.enum_numbered).?.data; + if (enum_obj.values.count() != 0) { + return enum_obj.values.keys()[field_index]; + } else { + // Field index and integer values are the same. + buffer.* = .{ + .base = .{ .tag = .int_u64 }, + .data = field_index, + }; + return Value.initPayload(&buffer.base); + } + }, + .enum_simple => { + // Field index and integer values are the same. + buffer.* = .{ + .base = .{ .tag = .int_u64 }, + .data = field_index, + }; + return Value.initPayload(&buffer.base); + }, + else => unreachable, } - // Assume it is already an integer and return it directly. - return val; } /// Asserts the value is an integer. @@ -901,6 +917,7 @@ pub const Value = extern union { switch (self.tag()) { .zero, .bool_false, + .the_only_possible_value, // i0, u0 => return BigIntMutable.init(&space.limbs, 0).toConst(), .one, @@ -922,6 +939,7 @@ pub const Value = extern union { switch (self.tag()) { .zero, .bool_false, + .the_only_possible_value, // i0, u0 => return 0, .one, @@ -943,6 +961,7 @@ pub const Value = extern union { switch (self.tag()) { .zero, .bool_false, + .the_only_possible_value, // i0, u0 => return 0, .one, @@ -1124,6 +1143,11 @@ pub const Value = extern union { @panic("TODO implement int_big_negative Value clz"); }, + .the_only_possible_value => { + assert(ty_bits == 0); + return ty_bits; + }, + else => unreachable, } } @@ -1134,6 +1158,7 @@ pub const Value = extern union { switch (self.tag()) { .zero, .bool_false, + .the_only_possible_value, => return 0, .one, @@ -1213,6 +1238,11 @@ pub const Value = extern union { else => unreachable, }, + .the_only_possible_value => { + assert(ty.intInfo(target).bits == 0); + return true; + }, + else => unreachable, } } @@ -1251,7 +1281,7 @@ pub const Value = extern union { /// Asserts the value is numeric pub fn isZero(self: Value) bool { return switch (self.tag()) { - .zero => true, + .zero, .the_only_possible_value => true, .one => false, .int_u64 => self.castTag(.int_u64).?.data == 0, @@ -1272,6 +1302,7 @@ pub const Value = extern union { return switch (lhs.tag()) { .zero, .bool_false, + .the_only_possible_value, => .eq, .one, @@ -1354,7 +1385,7 @@ pub const Value = extern union { assert(b_tag != .undef); if (a_tag == b_tag) { switch (a_tag) { - .void_value, .null_value => return true, + .void_value, .null_value, .the_only_possible_value => return true, .enum_literal => { const a_name = a.castTag(.enum_literal).?.data; const b_name = b.castTag(.enum_literal).?.data; @@ -1706,6 +1737,9 @@ pub const Value = extern union { .decl_ref => return val.castTag(.decl_ref).?.data.val.elemValueAdvanced(index, arena, buffer), .decl_ref_mut => return val.castTag(.decl_ref_mut).?.data.decl.val.elemValueAdvanced(index, arena, buffer), + // The child type of arrays which have only one possible value need to have only one possible value itself. + .the_only_possible_value => return val, + else => unreachable, } } @@ -1722,6 +1756,8 @@ pub const Value = extern union { // TODO assert the tag is correct return payload.val; }, + // Structs which have only one possible value need to consist of members which have only one possible value. + .the_only_possible_value => return val, else => unreachable, } @@ -1820,6 +1856,7 @@ pub const Value = extern union { pub fn intToFloat(val: Value, allocator: *Allocator, dest_ty: Type, target: Target) !Value { switch (val.tag()) { .undef, .zero, .one => return val, + .the_only_possible_value => return Value.initTag(.zero), // for i0, u0 .int_u64 => { return intToFloatInner(val.castTag(.int_u64).?.data, allocator, dest_ty, target); }, From d6f048c4565ee7b3d86c070c07dd555376a52ad2 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Wed, 20 Oct 2021 00:30:20 +0200 Subject: [PATCH 07/11] stage2: make (typeHas)OnePossibleValue return the right value --- src/Sema.zig | 13 ++++++++++--- src/type.zig | 22 ++++++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 7a5ac1baa1..d30b46ffba 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -13835,7 +13835,14 @@ fn typeHasOnePossibleValue( return null; } }, - .enum_nonexhaustive => ty = ty.castTag(.enum_nonexhaustive).?.data.tag_ty, + .enum_nonexhaustive => { + const tag_ty = ty.castTag(.enum_nonexhaustive).?.data.tag_ty; + if (tag_ty.cast(Type.Payload.Bits).?.data == 0) { + return Value.initTag(.zero); + } else { + return null; + } + }, .@"union" => { return null; // TODO }, @@ -13859,8 +13866,8 @@ fn typeHasOnePossibleValue( .vector, .array, .array_u8 => { if (ty.arrayLen() == 0) return Value.initTag(.empty_array); - ty = ty.elemType(); - continue; + _ = (try sema.typeHasOnePossibleValue(block, src, ty.elemType())) orelse return null; + return Value.initTag(.the_only_possible_value); }, .inferred_alloc_const => unreachable, diff --git a/src/type.zig b/src/type.zig index 2bf268bc5f..459a9b387f 100644 --- a/src/type.zig +++ b/src/type.zig @@ -3096,6 +3096,14 @@ pub const Type = extern union { } return Value.initTag(.empty_struct_value); }, + .enum_numbered => { + const enum_numbered = ty.castTag(.enum_numbered).?.data; + if (enum_numbered.fields.count() == 1) { + return enum_numbered.values.keys()[0]; + } else { + return null; + } + }, .enum_full => { const enum_full = ty.castTag(.enum_full).?.data; if (enum_full.fields.count() == 1) { @@ -3112,8 +3120,14 @@ pub const Type = extern union { return null; } }, - .enum_nonexhaustive => ty = ty.castTag(.enum_nonexhaustive).?.data.tag_ty, - .enum_numbered => ty = ty.castTag(.enum_numbered).?.data.tag_ty, + .enum_nonexhaustive => { + const tag_ty = ty.castTag(.enum_nonexhaustive).?.data.tag_ty; + if (tag_ty.cast(Type.Payload.Bits).?.data == 0) { + return Value.initTag(.zero); + } else { + return null; + } + }, .@"union" => { return null; // TODO }, @@ -3137,8 +3151,8 @@ pub const Type = extern union { .vector, .array, .array_u8 => { if (ty.arrayLen() == 0) return Value.initTag(.empty_array); - ty = ty.elemType(); - continue; + _ = ty.elemType().onePossibleValue() orelse return null; + return Value.initTag(.the_only_possible_value); }, .inferred_alloc_const => unreachable, From 6329f4e47a78dc75b234e6d89c82fb3cff058fe1 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Wed, 20 Oct 2021 02:23:09 +0200 Subject: [PATCH 08/11] stage2: union field value --- src/Sema.zig | 40 ++++++++++++++++++++++++++-------------- src/codegen/llvm.zig | 37 +++++++++++++++++++++++++++---------- 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index d30b46ffba..c08cc09c68 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -300,7 +300,7 @@ pub const Block = struct { .ty = ty, .payload = try block.sema.addExtra(Air.StructField{ .struct_operand = struct_ptr, - .field_index = @intCast(u32, field_index), + .field_index = field_index, }), } }, }); @@ -315,6 +315,24 @@ pub const Block = struct { }); } + pub fn addStructFieldVal( + block: *Block, + struct_val: Air.Inst.Ref, + field_index: u32, + field_ty: Type, + ) !Air.Inst.Ref { + return block.addInst(.{ + .tag = .struct_field_val, + .data = .{ .ty_pl = .{ + .ty = try block.sema.addType(field_ty), + .payload = try block.sema.addExtra(Air.StructField{ + .struct_operand = struct_val, + .field_index = field_index, + }), + } }, + }); + } + pub fn addInst(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref { return Air.indexToRef(try block.addInstAsIndex(inst)); } @@ -11261,8 +11279,10 @@ fn structFieldVal( const struct_ty = try sema.resolveTypeFields(block, src, unresolved_struct_ty); const struct_obj = struct_ty.castTag(.@"struct").?.data; - const field_index = struct_obj.fields.getIndex(field_name) orelse + const field_index_big = struct_obj.fields.getIndex(field_name) orelse return sema.failWithBadFieldAccess(block, struct_obj, field_name_src, field_name); + const field_index = @intCast(u32, field_index_big); + const field = struct_obj.fields.values()[field_index]; if (try sema.resolveMaybeUndefVal(block, src, struct_byval)) |struct_val| { @@ -11273,16 +11293,7 @@ fn structFieldVal( } try sema.requireRuntimeBlock(block, src); - return block.addInst(.{ - .tag = .struct_field_val, - .data = .{ .ty_pl = .{ - .ty = try sema.addType(field.ty), - .payload = try sema.addExtra(Air.StructField{ - .struct_operand = struct_byval, - .field_index = @intCast(u32, field_index), - }), - } }, - }); + return block.addStructFieldVal(struct_byval, field_index, field.ty); } fn unionFieldPtr( @@ -11341,8 +11352,9 @@ fn unionFieldVal( const union_ty = try sema.resolveTypeFields(block, src, unresolved_union_ty); const union_obj = union_ty.cast(Type.Payload.Union).?.data; - const field_index = union_obj.fields.getIndex(field_name) orelse + const field_index_big = union_obj.fields.getIndex(field_name) orelse return sema.failWithBadUnionFieldAccess(block, union_obj, field_name_src, field_name); + const field_index = @intCast(u32, field_index_big); const field = union_obj.fields.values()[field_index]; @@ -11355,7 +11367,7 @@ fn unionFieldVal( } try sema.requireRuntimeBlock(block, src); - return sema.fail(block, src, "TODO implement runtime union field access", .{}); + return block.addStructFieldVal(union_byval, field_index, field.ty); } fn elemPtr( diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 8ec5075f84..361bedd577 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -2236,17 +2236,34 @@ pub const FuncGen = struct { const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data; const struct_ty = self.air.typeOf(struct_field.struct_operand); const struct_llvm_val = try self.resolveInst(struct_field.struct_operand); - const field_index = llvmFieldIndex(struct_ty, struct_field.field_index); - if (isByRef(struct_ty)) { - const field_ptr = self.builder.buildStructGEP(struct_llvm_val, field_index, ""); - const field_ty = struct_ty.structFieldType(struct_field.field_index); - if (isByRef(field_ty)) { - return field_ptr; - } else { - return self.builder.buildLoad(field_ptr, ""); - } + const field_index = struct_field.field_index; + const field_ty = struct_ty.structFieldType(field_index); + if (!field_ty.hasCodeGenBits()) { + return null; + } + + assert(isByRef(struct_ty)); + + const field_ptr = switch (struct_ty.zigTypeTag()) { + .Struct => blk: { + const llvm_field_index = llvmFieldIndex(struct_ty, field_index); + break :blk self.builder.buildStructGEP(struct_llvm_val, llvm_field_index, ""); + }, + .Union => blk: { + const llvm_field_ty = try self.dg.llvmType(field_ty); + const target = self.dg.module.getTarget(); + const layout = struct_ty.unionGetLayout(target); + const payload_index = @boolToInt(layout.tag_align >= layout.payload_align); + const union_field_ptr = self.builder.buildStructGEP(struct_llvm_val, payload_index, ""); + break :blk self.builder.buildBitCast(union_field_ptr, llvm_field_ty.pointerType(0), ""); + }, + else => unreachable, + }; + + if (isByRef(field_ty)) { + return field_ptr; } else { - return self.builder.buildExtractValue(struct_llvm_val, field_index, ""); + return self.builder.buildLoad(field_ptr, ""); } } From e4c437f23baecf9d170f3f058cde78f34dedd5d0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 18 Oct 2021 17:16:43 -0700 Subject: [PATCH 09/11] stage2: implement union member access as enum tag --- src/Sema.zig | 100 ++++++++++++++++++++++++++----------------- src/codegen/llvm.zig | 4 ++ 2 files changed, 65 insertions(+), 39 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index c08cc09c68..aa00ad3fb6 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2316,7 +2316,7 @@ fn validateStructInit( const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data; const field_name = sema.code.nullTerminatedString(field_ptr_extra.field_name_start); const field_index = struct_obj.fields.getIndex(field_name) orelse - return sema.failWithBadFieldAccess(block, struct_obj, field_src, field_name); + return sema.failWithBadStructFieldAccess(block, struct_obj, field_src, field_name); if (found_fields[field_index] != 0) { const other_field_ptr = found_fields[field_index]; const other_field_ptr_data = sema.code.instructions.items(.data)[other_field_ptr].pl_node; @@ -2378,7 +2378,32 @@ fn zirValidateArrayInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compil } } -fn failWithBadFieldAccess( +fn failWithBadMemberAccess( + sema: *Sema, + block: *Block, + agg_ty: Type, + field_src: LazySrcLoc, + field_name: []const u8, +) CompileError { + const kw_name = switch (agg_ty.zigTypeTag()) { + .Union => "union", + .Struct => "struct", + .Opaque => "opaque", + .Enum => "enum", + else => unreachable, + }; + const msg = msg: { + const msg = try sema.errMsg(block, field_src, "{s} '{}' has no member named '{s}'", .{ + kw_name, agg_ty, field_name, + }); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, agg_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); +} + +fn failWithBadStructFieldAccess( sema: *Sema, block: *Block, struct_obj: *Module.Struct, @@ -8828,7 +8853,7 @@ fn zirStructInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is_ref: bool) const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data; const field_name = sema.code.nullTerminatedString(field_type_extra.name_start); const field_index = struct_obj.fields.getIndex(field_name) orelse - return sema.failWithBadFieldAccess(block, struct_obj, field_src, field_name); + return sema.failWithBadStructFieldAccess(block, struct_obj, field_src, field_name); if (found_fields[field_index] != 0) { const other_field_type = found_fields[field_index]; const other_field_type_data = zir_datas[other_field_type].pl_node; @@ -9037,7 +9062,7 @@ fn zirFieldType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A .Struct => { const struct_obj = resolved_ty.castTag(.@"struct").?.data; const field = struct_obj.fields.get(field_name) orelse - return sema.failWithBadFieldAccess(block, struct_obj, src, field_name); + return sema.failWithBadStructFieldAccess(block, struct_obj, src, field_name); return sema.addType(field.ty); }, .Union => { @@ -10859,7 +10884,7 @@ fn fieldVal( }, else => {}, } - return sema.fail(block, src, "type '{}' does not support field access (fieldVal, {}.{s})", .{object_ty, object_ty, field_name}); + return sema.fail(block, src, "type '{}' does not support field access (fieldVal, {}.{s})", .{ object_ty, object_ty, field_name }); } fn fieldPtr( @@ -11001,22 +11026,24 @@ fn fieldPtr( try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }), )); }, - .Struct, .Opaque, .Union => { + .Union => { if (child_type.getNamespace()) |namespace| { if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| { return inst; } } - // TODO add note: declared here - const kw_name = switch (child_type.zigTypeTag()) { - .Struct => "struct", - .Opaque => "opaque", - .Union => "union", - else => unreachable, - }; - return sema.fail(block, src, "{s} '{}' has no member named '{s}'", .{ - kw_name, child_type, field_name, - }); + if (child_type.unionTagType()) |enum_ty| { + if (enum_ty.enumFieldIndex(field_name)) |field_index| { + const field_index_u32 = @intCast(u32, field_index); + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); + return sema.analyzeDeclRef(try anon_decl.finish( + try enum_ty.copy(anon_decl.arena()), + try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32), + )); + } + } + return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name); }, .Enum => { if (child_type.getNamespace()) |namespace| { @@ -11025,23 +11052,7 @@ fn fieldPtr( } } const field_index = child_type.enumFieldIndex(field_name) orelse { - const msg = msg: { - const msg = try sema.errMsg( - block, - src, - "enum '{}' has no member named '{s}'", - .{ child_type, field_name }, - ); - errdefer msg.destroy(sema.gpa); - try sema.mod.errNoteNonLazy( - child_type.declSrcLoc(), - msg, - "enum declared here", - .{}, - ); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name); }; const field_index_u32 = @intCast(u32, field_index); var anon_decl = try block.startAnonDecl(); @@ -11051,6 +11062,14 @@ fn fieldPtr( try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32), )); }, + .Struct, .Opaque => { + if (child_type.getNamespace()) |namespace| { + if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| { + return inst; + } + } + return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name); + }, else => return sema.fail(block, src, "type '{}' has no members", .{child_type}), } }, @@ -11070,7 +11089,7 @@ fn fieldPtr( }, else => {}, } - return sema.fail(block, src, "type '{}' does not support field access (fieldPtr, {}.{s})", .{object_ty, object_ptr_ty, field_name}); + return sema.fail(block, src, "type '{}' does not support field access (fieldPtr, {}.{s})", .{ object_ty, object_ptr_ty, field_name }); } fn fieldCallBind( @@ -11242,7 +11261,7 @@ fn structFieldPtr( const struct_obj = struct_ty.castTag(.@"struct").?.data; const field_index_big = struct_obj.fields.getIndex(field_name) orelse - return sema.failWithBadFieldAccess(block, struct_obj, field_name_src, field_name); + return sema.failWithBadStructFieldAccess(block, struct_obj, field_name_src, field_name); const field_index = @intCast(u32, field_index_big); const field = struct_obj.fields.values()[field_index]; const ptr_field_ty = try Type.ptr(arena, .{ @@ -11279,10 +11298,9 @@ fn structFieldVal( const struct_ty = try sema.resolveTypeFields(block, src, unresolved_struct_ty); const struct_obj = struct_ty.castTag(.@"struct").?.data; - const field_index_big = struct_obj.fields.getIndex(field_name) orelse - return sema.failWithBadFieldAccess(block, struct_obj, field_name_src, field_name); - const field_index = @intCast(u32, field_index_big); - + const field_index_usize = struct_obj.fields.getIndex(field_name) orelse + return sema.failWithBadStructFieldAccess(block, struct_obj, field_name_src, field_name); + const field_index = @intCast(u32, field_index_usize); const field = struct_obj.fields.values()[field_index]; if (try sema.resolveMaybeUndefVal(block, src, struct_byval)) |struct_val| { @@ -13168,6 +13186,10 @@ pub fn resolveTypeLayout( } union_obj.status = .have_layout; }, + .Array => { + const elem_ty = ty.childType(); + return sema.resolveTypeLayout(block, src, elem_ty); + }, else => {}, } } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 361bedd577..bcffc1f0a2 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1261,6 +1261,10 @@ pub const DeclGen = struct { } const field_ty = tv.ty.unionFieldType(tag_and_val.tag); const payload = p: { + if (!field_ty.hasCodeGenBits()) { + const padding_len = @intCast(c_uint, layout.payload_size); + break :p self.context.intType(8).arrayType(padding_len).getUndef(); + } const field = try genTypedValue(self, .{ .ty = field_ty, .val = tag_and_val.val }); const field_size = field_ty.abiSize(target); if (field_size == layout.payload_size) { From 2192d404d58a3e95d8fbb26e7fd73a95756172a6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 19 Oct 2021 19:20:31 -0700 Subject: [PATCH 10/11] stage2: wasm: implement struct_field_val --- src/codegen/wasm.zig | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 6902553257..f0d9e43439 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -866,6 +866,7 @@ pub const Context = struct { .struct_field_ptr_index_1 => self.airStructFieldPtrIndex(inst, 1), .struct_field_ptr_index_2 => self.airStructFieldPtrIndex(inst, 2), .struct_field_ptr_index_3 => self.airStructFieldPtrIndex(inst, 3), + .struct_field_val => self.airStructFieldVal(inst), .switch_br => self.airSwitchBr(inst), .unreach => self.airUnreachable(inst), .wrap_optional => self.airWrapOptional(inst), @@ -1456,6 +1457,15 @@ pub const Context = struct { return WValue{ .local = struct_ptr.multi_value.index + index }; } + fn airStructFieldVal(self: *Context, inst: Air.Inst.Index) InnerError!WValue { + if (self.liveness.isUnused(inst)) return WValue.none; + + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.StructField, ty_pl.payload).data; + const struct_multivalue = self.resolveInst(extra.struct_operand).multi_value; + return WValue{ .local = struct_multivalue.index + extra.field_index }; + } + fn airSwitchBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue { // result type is always 'noreturn' const blocktype = wasm.block_empty; From c1508c98f479497c2b2586ea944be9f3ddae28fc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 19 Oct 2021 19:38:43 -0700 Subject: [PATCH 11/11] stage2 minor cleanups --- src/Sema.zig | 29 +++++++++++++++-------------- src/type.zig | 19 ++++++++++--------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index aa00ad3fb6..12da483e24 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -9362,7 +9362,7 @@ fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const dest_info = dest_ty.intInfo(target); if (src_info.bits == 0 or dest_info.bits == 0) { - return sema.addConstant(dest_ty, Value.initTag(.zero)); + return sema.addConstant(dest_ty, Value.zero); } if (!src_is_comptime_int) { @@ -10884,7 +10884,7 @@ fn fieldVal( }, else => {}, } - return sema.fail(block, src, "type '{}' does not support field access (fieldVal, {}.{s})", .{ object_ty, object_ty, field_name }); + return sema.fail(block, src, "type '{}' does not support field access", .{object_ty}); } fn fieldPtr( @@ -13742,10 +13742,9 @@ fn typeHasOnePossibleValue( sema: *Sema, block: *Block, src: LazySrcLoc, - starting_type: Type, + ty: Type, ) CompileError!?Value { - var ty = starting_type; - while (true) switch (ty.tag()) { + switch (ty.tag()) { .f16, .f32, .f64, @@ -13839,7 +13838,7 @@ fn typeHasOnePossibleValue( const enum_obj = resolved_ty.castTag(.enum_numbered).?.data; if (enum_obj.fields.count() == 1) { if (enum_obj.values.count() == 0) { - return Value.initTag(.zero); // auto-numbered + return Value.zero; // auto-numbered } else { return enum_obj.values.keys()[0]; } @@ -13852,7 +13851,7 @@ fn typeHasOnePossibleValue( const enum_obj = resolved_ty.castTag(.enum_full).?.data; if (enum_obj.fields.count() == 1) { if (enum_obj.values.count() == 0) { - return Value.initTag(.zero); // auto-numbered + return Value.zero; // auto-numbered } else { return enum_obj.values.keys()[0]; } @@ -13864,15 +13863,15 @@ fn typeHasOnePossibleValue( const resolved_ty = try sema.resolveTypeFields(block, src, ty); const enum_simple = resolved_ty.castTag(.enum_simple).?.data; if (enum_simple.fields.count() == 1) { - return Value.initTag(.zero); + return Value.zero; } else { return null; } }, .enum_nonexhaustive => { const tag_ty = ty.castTag(.enum_nonexhaustive).?.data.tag_ty; - if (tag_ty.cast(Type.Payload.Bits).?.data == 0) { - return Value.initTag(.zero); + if (!tag_ty.hasCodeGenBits()) { + return Value.zero; } else { return null; } @@ -13892,7 +13891,7 @@ fn typeHasOnePossibleValue( .int_unsigned, .int_signed => { if (ty.cast(Type.Payload.Bits).?.data == 0) { - return Value.initTag(.zero); + return Value.zero; } else { return null; } @@ -13900,14 +13899,16 @@ fn typeHasOnePossibleValue( .vector, .array, .array_u8 => { if (ty.arrayLen() == 0) return Value.initTag(.empty_array); - _ = (try sema.typeHasOnePossibleValue(block, src, ty.elemType())) orelse return null; - return Value.initTag(.the_only_possible_value); + if ((try sema.typeHasOnePossibleValue(block, src, ty.elemType())) != null) { + return Value.initTag(.the_only_possible_value); + } + return null; }, .inferred_alloc_const => unreachable, .inferred_alloc_mut => unreachable, .generic_poison => return error.GenericPoison, - }; + } } fn getAstTree(sema: *Sema, block: *Block) CompileError!*const std.zig.Ast { diff --git a/src/type.zig b/src/type.zig index 459a9b387f..259a6b8c70 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2676,7 +2676,7 @@ pub const Type = extern union { .pointer => return self.castTag(.pointer).?.data.sentinel, .array_sentinel => return self.castTag(.array_sentinel).?.data.sentinel, - .array_u8_sentinel_0 => return Value.initTag(.zero), + .array_u8_sentinel_0 => return Value.zero, else => unreachable, }; @@ -3115,15 +3115,15 @@ pub const Type = extern union { .enum_simple => { const enum_simple = ty.castTag(.enum_simple).?.data; if (enum_simple.fields.count() == 1) { - return Value.initTag(.zero); + return Value.zero; } else { return null; } }, .enum_nonexhaustive => { const tag_ty = ty.castTag(.enum_nonexhaustive).?.data.tag_ty; - if (tag_ty.cast(Type.Payload.Bits).?.data == 0) { - return Value.initTag(.zero); + if (!tag_ty.hasCodeGenBits()) { + return Value.zero; } else { return null; } @@ -3143,7 +3143,7 @@ pub const Type = extern union { .int_unsigned, .int_signed => { if (ty.cast(Payload.Bits).?.data == 0) { - return Value.initTag(.zero); + return Value.zero; } else { return null; } @@ -3151,8 +3151,9 @@ pub const Type = extern union { .vector, .array, .array_u8 => { if (ty.arrayLen() == 0) return Value.initTag(.empty_array); - _ = ty.elemType().onePossibleValue() orelse return null; - return Value.initTag(.the_only_possible_value); + if (ty.elemType().onePossibleValue() != null) + return Value.initTag(.the_only_possible_value); + return null; }, .inferred_alloc_const => unreachable, @@ -3193,7 +3194,7 @@ pub const Type = extern union { const info = self.intInfo(target); if (info.signedness == .unsigned) { - return Value.initTag(.zero); + return Value.zero; } if (info.bits <= 6) { @@ -4027,7 +4028,7 @@ pub const Type = extern union { ) Allocator.Error!Type { if (elem_type.eql(Type.u8)) { if (sent) |some| { - if (some.eql(Value.initTag(.zero), elem_type)) { + if (some.eql(Value.zero, elem_type)) { return Tag.array_u8_sentinel_0.create(arena, len); } } else {