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/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; }, 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/Sema.zig b/src/Sema.zig index e3e8982fac..12da483e24 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)); } @@ -1968,44 +1986,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( @@ -2304,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; @@ -2366,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, @@ -5119,16 +5156,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 { @@ -8822,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; @@ -9031,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 => { @@ -9331,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) { @@ -10688,12 +10719,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 +10745,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 +10868,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 +10904,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 +10933,74 @@ 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 }, - ); + .Pointer => if (inner_ty.isSlice()) { + 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")) { + 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()), + )); } - }, - .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 => {}, + 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")) { + 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()), + )); } - }, - .Many, .C => {}, + 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, + 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 @@ -10980,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| { @@ -11004,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(); @@ -11030,14 +11062,34 @@ 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}), } }, - .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}); + return sema.fail(block, src, "type '{}' does not support field access (fieldPtr, {}.{s})", .{ object_ty, object_ptr_ty, field_name }); } fn fieldCallBind( @@ -11209,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, .{ @@ -11246,8 +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 = struct_obj.fields.getIndex(field_name) orelse - return sema.failWithBadFieldAccess(block, struct_obj, field_name_src, field_name); + 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| { @@ -11258,16 +11311,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( @@ -11326,8 +11370,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]; @@ -11340,7 +11385,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( @@ -13141,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 => {}, } } @@ -13693,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, @@ -13790,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]; } @@ -13803,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]; } @@ -13815,12 +13863,19 @@ 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.hasCodeGenBits()) { + return Value.zero; } else { return null; } }, - .enum_nonexhaustive => ty = ty.castTag(.enum_nonexhaustive).?.data.tag_ty, .@"union" => { return null; // TODO }, @@ -13836,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; } @@ -13844,14 +13899,16 @@ fn typeHasOnePossibleValue( .vector, .array, .array_u8 => { if (ty.arrayLen() == 0) return Value.initTag(.empty_array); - ty = ty.elemType(); - continue; + 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/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..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) { @@ -1709,6 +1713,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 +2099,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); @@ -2223,17 +2240,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, ""); } } 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; 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, diff --git a/src/type.zig b/src/type.zig index 2bf268bc5f..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, }; @@ -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) { @@ -3107,13 +3115,19 @@ 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.hasCodeGenBits()) { + return Value.zero; } else { return null; } }, - .enum_nonexhaustive => ty = ty.castTag(.enum_nonexhaustive).?.data.tag_ty, - .enum_numbered => ty = ty.castTag(.enum_numbered).?.data.tag_ty, .@"union" => { return null; // TODO }, @@ -3129,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; } @@ -3137,8 +3151,9 @@ pub const Type = extern union { .vector, .array, .array_u8 => { if (ty.arrayLen() == 0) return Value.initTag(.empty_array); - ty = ty.elemType(); - continue; + if (ty.elemType().onePossibleValue() != null) + return Value.initTag(.the_only_possible_value); + return null; }, .inferred_alloc_const => unreachable, @@ -3179,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) { @@ -4013,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 { 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); },