From e86ff712a666ab5be54fa763cc12a5f245718117 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 20 Jan 2022 00:33:51 -0700 Subject: stage2: implement tuples * AIR instruction vector_init gains the ability to init arrays and tuples in addition to vectors. This will probably also gain the ability to initialize structs and be renamed to `aggregate_init`. * AstGen prefers to use an `anon_array_init` ZIR instruction for local variables when the init expr is an array literal and there is no type. --- src/Air.zig | 4 +- src/AstGen.zig | 32 ++++--- src/Liveness.zig | 2 +- src/Sema.zig | 239 +++++++++++++++++++++++++++++++++++++++------------ src/codegen/llvm.zig | 178 +++++++++++++++++++++++++++++++++++--- src/print_air.zig | 2 +- src/print_zir.zig | 3 +- src/type.zig | 183 ++++++++++++++++++++++++++++++++++++--- 8 files changed, 548 insertions(+), 95 deletions(-) (limited to 'src') diff --git a/src/Air.zig b/src/Air.zig index 0f9542af1b..bf7ce886fc 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -510,9 +510,11 @@ pub const Inst = struct { /// Uses the `un_op` field. error_name, - /// Constructs a vector value out of runtime-known elements. + /// Constructs a vector, tuple, or array value out of runtime-known elements. + /// Some of the elements may be comptime-known. /// Uses the `ty_pl` field, payload is index of an array of elements, each of which /// is a `Ref`. Length of the array is given by the vector type. + /// TODO rename this to `array_init` and make it support array values too. vector_init, /// Communicates an intent to load memory. diff --git a/src/AstGen.zig b/src/AstGen.zig index d546eb7bef..750c7f53a9 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2581,9 +2581,12 @@ fn varDecl( // Depending on the type of AST the initialization expression is, we may need an lvalue // or an rvalue as a result location. If it is an rvalue, we can use the instruction as // the variable, no memory location needed. - if (align_inst == .none and !nodeMayNeedMemoryLocation(tree, var_decl.ast.init_node)) { - const result_loc: ResultLoc = if (var_decl.ast.type_node != 0) .{ - .ty = try typeExpr(gz, scope, var_decl.ast.type_node), + const type_node = var_decl.ast.type_node; + if (align_inst == .none and + !nodeMayNeedMemoryLocation(tree, var_decl.ast.init_node, type_node != 0)) + { + const result_loc: ResultLoc = if (type_node != 0) .{ + .ty = try typeExpr(gz, scope, type_node), } else .none; const init_inst = try reachableExpr(gz, scope, result_loc, var_decl.ast.init_node, node); @@ -6008,7 +6011,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref return Zir.Inst.Ref.unreachable_value; } - const rl: ResultLoc = if (nodeMayNeedMemoryLocation(tree, operand_node)) .{ + const rl: ResultLoc = if (nodeMayNeedMemoryLocation(tree, operand_node, true)) .{ .ptr = try gz.addNodeExtended(.ret_ptr, node), } else .{ .ty = try gz.addNodeExtended(.ret_type, node), @@ -7725,7 +7728,7 @@ const primitives = std.ComptimeStringMap(Zir.Inst.Ref, .{ .{ "void", .void_type }, }); -fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool { +fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index, have_res_ty: bool) bool { const node_tags = tree.nodes.items(.tag); const node_datas = tree.nodes.items(.data); const main_tokens = tree.nodes.items(.main_token); @@ -7875,24 +7878,27 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool .@"orelse", => node = node_datas[node].rhs, - // True because these are exactly the expressions we need memory locations for. + // Array and struct init exprs write to result locs, but anon literals do not. .array_init_one, .array_init_one_comma, + .struct_init_one, + .struct_init_one_comma, + .array_init, + .array_init_comma, + .struct_init, + .struct_init_comma, + => return have_res_ty or node_datas[node].lhs != 0, + + // Anon literals do not need result location. .array_init_dot_two, .array_init_dot_two_comma, .array_init_dot, .array_init_dot_comma, - .array_init, - .array_init_comma, - .struct_init_one, - .struct_init_one_comma, .struct_init_dot_two, .struct_init_dot_two_comma, .struct_init_dot, .struct_init_dot_comma, - .struct_init, - .struct_init_comma, - => return true, + => return have_res_ty, // True because depending on comptime conditions, sub-expressions // may be the kind that need memory locations. diff --git a/src/Liveness.zig b/src/Liveness.zig index 1bdbab6788..1bc3159964 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -373,7 +373,7 @@ fn analyzeInst( .vector_init => { const ty_pl = inst_datas[inst].ty_pl; const vector_ty = a.air.getRefType(ty_pl.ty); - const len = vector_ty.vectorLen(); + const len = vector_ty.arrayLen(); const elements = @bitCast([]const Air.Inst.Ref, a.air.extra[ty_pl.payload..][0..len]); if (elements.len <= bpi - 1) { diff --git a/src/Sema.zig b/src/Sema.zig index aca844218e..08d8df3236 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2628,6 +2628,7 @@ fn validateUnionInit( // Otherwise, the bitcast should be preserved and a store instruction should be // emitted to store the constant union value through the bitcast. }, + .alloc => {}, else => |t| { if (std.debug.runtime_safety) { std.debug.panic("unexpected AIR tag for union pointer: {s}", .{@tagName(t)}); @@ -10694,12 +10695,77 @@ fn zirArrayInit( } } -fn zirArrayInitAnon(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is_ref: bool) CompileError!Air.Inst.Ref { +fn zirArrayInitAnon( + sema: *Sema, + block: *Block, + inst: Zir.Inst.Index, + is_ref: bool, +) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); + const extra = sema.code.extraData(Zir.Inst.MultiOp, inst_data.payload_index); + const operands = sema.code.refSlice(extra.end, extra.data.operands_len); + + const types = try sema.arena.alloc(Type, operands.len); + const values = try sema.arena.alloc(Value, operands.len); + + const opt_runtime_src = rs: { + var runtime_src: ?LazySrcLoc = null; + for (operands) |operand, i| { + const elem = sema.resolveInst(operand); + types[i] = sema.typeOf(elem); + const operand_src = src; // TODO better source location + if (try sema.resolveMaybeUndefVal(block, operand_src, elem)) |val| { + values[i] = val; + } else { + values[i] = Value.initTag(.unreachable_value); + runtime_src = operand_src; + } + } + break :rs runtime_src; + }; - _ = is_ref; - return sema.fail(block, src, "TODO: Sema.zirArrayInitAnon", .{}); + const tuple_ty = try Type.Tag.tuple.create(sema.arena, .{ + .types = types, + .values = values, + }); + + const runtime_src = opt_runtime_src orelse { + const tuple_val = try Value.Tag.@"struct".create(sema.arena, values); + if (!is_ref) return sema.addConstant(tuple_ty, tuple_val); + + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); + const decl = try anon_decl.finish( + try tuple_ty.copy(anon_decl.arena()), + try tuple_val.copy(anon_decl.arena()), + ); + return sema.analyzeDeclRef(decl); + }; + + if (is_ref) { + const alloc = try block.addTy(.alloc, tuple_ty); + for (operands) |operand, i_usize| { + const i = @intCast(u32, i_usize); + const field_ptr_ty = try Type.ptr(sema.arena, .{ + .mutable = true, + .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + .pointee_type = types[i], + }); + const field_ptr = try block.addStructFieldPtr(alloc, i, field_ptr_ty); + _ = try block.addBinOp(.store, field_ptr, sema.resolveInst(operand)); + } + + return alloc; + } + + const element_refs = try sema.arena.alloc(Air.Inst.Ref, operands.len); + for (operands) |operand, i| { + element_refs[i] = sema.resolveInst(operand); + } + + try sema.requireRuntimeBlock(block, runtime_src); + return block.addVectorInit(tuple_ty, element_refs); } fn zirFieldTypeRef(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -13540,10 +13606,50 @@ fn elemVal( // TODO: If the index is a vector, the result should be a vector. return elemValArray(sema, block, array, elem_index, array_src, elem_index_src); }, + .Struct => { + // Tuple field access. + const index_val = try sema.resolveConstValue(block, elem_index_src, elem_index); + const index = @intCast(u32, index_val.toUnsignedInt()); + return tupleField(sema, block, array, index, array_src, elem_index_src); + }, else => unreachable, } } +fn tupleField( + sema: *Sema, + block: *Block, + tuple: Air.Inst.Ref, + field_index: u32, + tuple_src: LazySrcLoc, + field_index_src: LazySrcLoc, +) CompileError!Air.Inst.Ref { + const tuple_ty = sema.typeOf(tuple); + const tuple_info = tuple_ty.castTag(.tuple).?.data; + + if (field_index > tuple_info.types.len) { + return sema.fail(block, field_index_src, "index {d} outside tuple of length {d}", .{ + field_index, tuple_info.types.len, + }); + } + + const field_ty = tuple_info.types[field_index]; + const field_val = tuple_info.values[field_index]; + + if (field_val.tag() != .unreachable_value) { + return sema.addConstant(field_ty, field_val); // comptime field + } + + if (try sema.resolveMaybeUndefVal(block, tuple_src, tuple)) |tuple_val| { + if (tuple_val.isUndef()) return sema.addConstUndef(field_ty); + const field_values = tuple_val.castTag(.@"struct").?.data; + return sema.addConstant(field_ty, field_values[field_index]); + } + + try sema.requireRuntimeBlock(block, tuple_src); + return block.addStructFieldVal(tuple, field_index, field_ty); +} + fn elemValArray( sema: *Sema, block: *Block, @@ -13901,17 +14007,19 @@ fn coerce( else => {}, }, .Array => switch (inst_ty.zigTypeTag()) { - .Vector => return sema.coerceVectorInMemory(block, dest_ty, dest_ty_src, inst, inst_src), + .Vector => return sema.coerceArrayLike(block, dest_ty, dest_ty_src, inst, inst_src), .Struct => { if (inst == .empty_struct) { return arrayInitEmpty(sema, dest_ty); } + if (inst_ty.tag() == .tuple) { + return sema.coerceTupleToArray(block, dest_ty, dest_ty_src, inst, inst_src); + } }, else => {}, }, .Vector => switch (inst_ty.zigTypeTag()) { - .Array => return sema.coerceVectorInMemory(block, dest_ty, dest_ty_src, inst, inst_src), - .Vector => return sema.coerceVectors(block, dest_ty, dest_ty_src, inst, inst_src), + .Array, .Vector => return sema.coerceArrayLike(block, dest_ty, dest_ty_src, inst, inst_src), else => {}, }, .Struct => { @@ -14847,10 +14955,8 @@ fn coerceEnumToUnion( return sema.failWithOwnedErrorMsg(msg); } -/// Coerces vectors/arrays which have the same in-memory layout. This can be used for -/// both coercing from and to vectors. -/// TODO (affects the lang spec) delete this in favor of always using `coerceVectors`. -fn coerceVectorInMemory( +/// If the lengths match, coerces element-wise. +fn coerceArrayLike( sema: *Sema, block: *Block, dest_ty: Type, @@ -14860,7 +14966,7 @@ fn coerceVectorInMemory( ) !Air.Inst.Ref { const inst_ty = sema.typeOf(inst); const inst_len = inst_ty.arrayLen(); - const dest_len = dest_ty.arrayLen(); + const dest_len = try sema.usizeCast(block, dest_ty_src, dest_ty.arrayLen()); if (dest_len != inst_len) { const msg = msg: { @@ -14879,22 +14985,50 @@ fn coerceVectorInMemory( const dest_elem_ty = dest_ty.childType(); const inst_elem_ty = inst_ty.childType(); const in_memory_result = try sema.coerceInMemoryAllowed(block, dest_elem_ty, inst_elem_ty, false, target, dest_ty_src, inst_src); - if (in_memory_result != .ok) { - // TODO recursive error notes for coerceInMemoryAllowed failure - return sema.fail(block, inst_src, "expected {}, found {}", .{ dest_ty, inst_ty }); + if (in_memory_result == .ok) { + if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |inst_val| { + // These types share the same comptime value representation. + return sema.addConstant(dest_ty, inst_val); + } + try sema.requireRuntimeBlock(block, inst_src); + return block.addBitCast(dest_ty, inst); } - if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |inst_val| { - // These types share the same comptime value representation. - return sema.addConstant(dest_ty, inst_val); + const element_vals = try sema.arena.alloc(Value, dest_len); + const element_refs = try sema.arena.alloc(Air.Inst.Ref, dest_len); + var runtime_src: ?LazySrcLoc = null; + + for (element_vals) |*elem, i| { + const index_ref = try sema.addConstant( + Type.usize, + try Value.Tag.int_u64.create(sema.arena, i), + ); + const elem_src = inst_src; // TODO better source location + const elem_ref = try elemValArray(sema, block, inst, index_ref, inst_src, elem_src); + const coerced = try sema.coerce(block, dest_elem_ty, elem_ref, elem_src); + element_refs[i] = coerced; + if (runtime_src == null) { + if (try sema.resolveMaybeUndefVal(block, elem_src, coerced)) |elem_val| { + elem.* = elem_val; + } else { + runtime_src = elem_src; + } + } } - try sema.requireRuntimeBlock(block, inst_src); - return block.addBitCast(dest_ty, inst); + if (runtime_src) |rs| { + try sema.requireRuntimeBlock(block, rs); + return block.addVectorInit(dest_ty, element_refs); + } + + return sema.addConstant( + dest_ty, + try Value.Tag.array.create(sema.arena, element_vals), + ); } /// If the lengths match, coerces element-wise. -fn coerceVectors( +fn coerceTupleToArray( sema: *Sema, block: *Block, dest_ty: Type, @@ -14904,7 +15038,7 @@ fn coerceVectors( ) !Air.Inst.Ref { const inst_ty = sema.typeOf(inst); const inst_len = inst_ty.arrayLen(); - const dest_len = try sema.usizeCast(block, dest_ty_src, dest_ty.arrayLen()); + const dest_len = dest_ty.arrayLen(); if (dest_len != inst_len) { const msg = msg: { @@ -14919,30 +15053,15 @@ fn coerceVectors( return sema.failWithOwnedErrorMsg(msg); } - const target = sema.mod.getTarget(); - const dest_elem_ty = dest_ty.childType(); - const inst_elem_ty = inst_ty.childType(); - const in_memory_result = try sema.coerceInMemoryAllowed(block, dest_elem_ty, inst_elem_ty, false, target, dest_ty_src, inst_src); - if (in_memory_result == .ok) { - if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |inst_val| { - // These types share the same comptime value representation. - return sema.addConstant(dest_ty, inst_val); - } - try sema.requireRuntimeBlock(block, inst_src); - return block.addBitCast(dest_ty, inst); - } - const element_vals = try sema.arena.alloc(Value, dest_len); const element_refs = try sema.arena.alloc(Air.Inst.Ref, dest_len); - var runtime_src: ?LazySrcLoc = null; + const dest_elem_ty = dest_ty.childType(); - for (element_vals) |*elem, i| { - const index_ref = try sema.addConstant( - Type.usize, - try Value.Tag.int_u64.create(sema.arena, i), - ); + var runtime_src: ?LazySrcLoc = null; + for (element_vals) |*elem, i_usize| { + const i = @intCast(u32, i_usize); const elem_src = inst_src; // TODO better source location - const elem_ref = try elemValArray(sema, block, inst, index_ref, inst_src, elem_src); + const elem_ref = try tupleField(sema, block, inst, i, inst_src, elem_src); const coerced = try sema.coerce(block, dest_elem_ty, elem_ref, elem_src); element_refs[i] = coerced; if (runtime_src == null) { @@ -15833,19 +15952,22 @@ fn resolveStructLayout( ty: Type, ) CompileError!void { const resolved_ty = try sema.resolveTypeFields(block, src, ty); - const struct_obj = resolved_ty.castTag(.@"struct").?.data; - switch (struct_obj.status) { - .none, .have_field_types => {}, - .field_types_wip, .layout_wip => { - return sema.fail(block, src, "struct {} depends on itself", .{ty}); - }, - .have_layout, .fully_resolved_wip, .fully_resolved => return, - } - struct_obj.status = .layout_wip; - for (struct_obj.fields.values()) |field| { - try sema.resolveTypeLayout(block, src, field.ty); + if (resolved_ty.castTag(.@"struct")) |payload| { + const struct_obj = payload.data; + switch (struct_obj.status) { + .none, .have_field_types => {}, + .field_types_wip, .layout_wip => { + return sema.fail(block, src, "struct {} depends on itself", .{ty}); + }, + .have_layout, .fully_resolved_wip, .fully_resolved => return, + } + struct_obj.status = .layout_wip; + for (struct_obj.fields.values()) |field| { + try sema.resolveTypeLayout(block, src, field.ty); + } + struct_obj.status = .have_layout; } - struct_obj.status = .have_layout; + // otherwise it's a tuple; no need to resolve anything } fn resolveUnionLayout( @@ -16642,6 +16764,17 @@ pub fn typeHasOnePossibleValue( } return Value.initTag(.empty_struct_value); }, + + .tuple => { + const tuple = ty.castTag(.tuple).?.data; + for (tuple.values) |val| { + if (val.tag() == .unreachable_value) { + return null; // non-comptime field + } + } + return Value.initTag(.empty_struct_value); + }, + .enum_numbered => { const resolved_ty = try sema.resolveTypeFields(block, src, ty); const enum_obj = resolved_ty.castTag(.enum_numbered).?.data; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 7a496af7b1..00733dd34b 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -916,6 +916,31 @@ pub const DeclGen = struct { // reference, we need to copy it here. gop.key_ptr.* = try t.copy(dg.object.type_map_arena.allocator()); + if (t.castTag(.tuple)) |tuple| { + const llvm_struct_ty = dg.context.structCreateNamed(""); + gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls + + const types = tuple.data.types; + const values = tuple.data.values; + var llvm_field_types = try std.ArrayListUnmanaged(*const llvm.Type).initCapacity(gpa, types.len); + defer llvm_field_types.deinit(gpa); + + for (types) |field_ty, i| { + const field_val = values[i]; + if (field_val.tag() != .unreachable_value) continue; + + llvm_field_types.appendAssumeCapacity(try dg.llvmType(field_ty)); + } + + llvm_struct_ty.structSetBody( + llvm_field_types.items.ptr, + @intCast(c_uint, llvm_field_types.items.len), + .False, + ); + + return llvm_struct_ty; + } + const struct_obj = t.castTag(.@"struct").?.data; const name = try struct_obj.getFullyQualifiedName(gpa); @@ -2687,10 +2712,23 @@ pub const FuncGen = struct { if (!field_ty.hasCodeGenBits()) { return null; } + const target = self.dg.module.getTarget(); - assert(isByRef(struct_ty)); + if (!isByRef(struct_ty)) { + assert(!isByRef(field_ty)); + switch (struct_ty.zigTypeTag()) { + .Struct => { + var ptr_ty_buf: Type.Payload.Pointer = undefined; + const llvm_field_index = llvmFieldIndex(struct_ty, field_index, target, &ptr_ty_buf).?; + return self.builder.buildExtractValue(struct_llvm_val, llvm_field_index, ""); + }, + .Union => { + return self.todo("airStructFieldVal byval union", .{}); + }, + else => unreachable, + } + } - const target = self.dg.module.getTarget(); switch (struct_ty.zigTypeTag()) { .Struct => { var ptr_ty_buf: Type.Payload.Pointer = undefined; @@ -4370,19 +4408,85 @@ pub const FuncGen = struct { if (self.liveness.isUnused(inst)) return null; const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; - const vector_ty = self.air.typeOfIndex(inst); - const len = vector_ty.arrayLen(); + const result_ty = self.air.typeOfIndex(inst); + const len = @intCast(usize, result_ty.arrayLen()); const elements = @bitCast([]const Air.Inst.Ref, self.air.extra[ty_pl.payload..][0..len]); - const llvm_vector_ty = try self.dg.llvmType(vector_ty); - const llvm_u32 = self.context.intType(32); + const llvm_result_ty = try self.dg.llvmType(result_ty); - var vector = llvm_vector_ty.getUndef(); - for (elements) |elem, i| { - const index_u32 = llvm_u32.constInt(i, .False); - const llvm_elem = try self.resolveInst(elem); - vector = self.builder.buildInsertElement(vector, llvm_elem, index_u32, ""); + switch (result_ty.zigTypeTag()) { + .Vector => { + const llvm_u32 = self.context.intType(32); + + var vector = llvm_result_ty.getUndef(); + for (elements) |elem, i| { + const index_u32 = llvm_u32.constInt(i, .False); + const llvm_elem = try self.resolveInst(elem); + vector = self.builder.buildInsertElement(vector, llvm_elem, index_u32, ""); + } + return vector; + }, + .Struct => { + const tuple = result_ty.castTag(.tuple).?.data; + + if (isByRef(result_ty)) { + const llvm_u32 = self.context.intType(32); + const alloca_inst = self.buildAlloca(llvm_result_ty); + const target = self.dg.module.getTarget(); + alloca_inst.setAlignment(result_ty.abiAlignment(target)); + + var indices: [2]*const llvm.Value = .{ llvm_u32.constNull(), undefined }; + var llvm_i: u32 = 0; + + for (elements) |elem, i| { + if (tuple.values[i].tag() != .unreachable_value) continue; + const field_ty = tuple.types[i]; + const llvm_elem = try self.resolveInst(elem); + indices[1] = llvm_u32.constInt(llvm_i, .False); + llvm_i += 1; + const field_ptr = self.builder.buildInBoundsGEP(alloca_inst, &indices, indices.len, ""); + const store_inst = self.builder.buildStore(llvm_elem, field_ptr); + store_inst.setAlignment(field_ty.abiAlignment(target)); + } + + return alloca_inst; + } else { + var result = llvm_result_ty.getUndef(); + var llvm_i: u32 = 0; + for (elements) |elem, i| { + if (tuple.values[i].tag() != .unreachable_value) continue; + + const llvm_elem = try self.resolveInst(elem); + result = self.builder.buildInsertValue(result, llvm_elem, llvm_i, ""); + llvm_i += 1; + } + return result; + } + }, + .Array => { + assert(isByRef(result_ty)); + + const llvm_usize = try self.dg.llvmType(Type.usize); + const target = self.dg.module.getTarget(); + const alloca_inst = self.buildAlloca(llvm_result_ty); + alloca_inst.setAlignment(result_ty.abiAlignment(target)); + + const elem_ty = result_ty.childType(); + + for (elements) |elem, i| { + const indices: [2]*const llvm.Value = .{ + llvm_usize.constNull(), + llvm_usize.constInt(@intCast(c_uint, i), .False), + }; + const elem_ptr = self.builder.buildInBoundsGEP(alloca_inst, &indices, indices.len, ""); + const llvm_elem = try self.resolveInst(elem); + const store_inst = self.builder.buildStore(llvm_elem, elem_ptr); + store_inst.setAlignment(elem_ty.abiAlignment(target)); + } + + return alloca_inst; + }, + else => unreachable, } - return vector; } fn airPrefetch(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { @@ -4956,6 +5060,29 @@ fn llvmFieldIndex( target: std.Target, ptr_pl_buf: *Type.Payload.Pointer, ) ?c_uint { + if (ty.castTag(.tuple)) |payload| { + const values = payload.data.values; + var llvm_field_index: c_uint = 0; + for (values) |val, i| { + if (val.tag() != .unreachable_value) { + continue; + } + if (field_index > i) { + llvm_field_index += 1; + continue; + } + const field_ty = payload.data.types[i]; + ptr_pl_buf.* = .{ + .data = .{ + .pointee_type = field_ty, + .@"align" = field_ty.abiAlignment(target), + .@"addrspace" = .generic, + }, + }; + return llvm_field_index; + } + return null; + } const struct_obj = ty.castTag(.@"struct").?.data; if (struct_obj.layout != .Packed) { var llvm_field_index: c_uint = 0; @@ -4976,7 +5103,7 @@ fn llvmFieldIndex( }; return llvm_field_index; } else { - // We did not find an llvm field that corrispons to this zig field. + // We did not find an llvm field that corresponds to this zig field. return null; } } @@ -5072,6 +5199,10 @@ fn firstParamSRet(fn_info: Type.Payload.Function.Data, target: std.Target) bool } fn isByRef(ty: Type) bool { + // For tuples (and TODO structs), if there are more than this many non-void + // fields, then we make it byref, otherwise byval. + const max_fields_byval = 2; + switch (ty.zigTypeTag()) { .Type, .ComptimeInt, @@ -5096,7 +5227,26 @@ fn isByRef(ty: Type) bool { .AnyFrame, => return false, - .Array, .Struct, .Frame => return ty.hasCodeGenBits(), + .Array, .Frame => return ty.hasCodeGenBits(), + .Struct => { + if (!ty.hasCodeGenBits()) return false; + if (ty.castTag(.tuple)) |tuple| { + var count: usize = 0; + for (tuple.data.values) |field_val, i| { + if (field_val.tag() != .unreachable_value) continue; + count += 1; + if (count > max_fields_byval) { + return true; + } + const field_ty = tuple.data.types[i]; + if (isByRef(field_ty)) { + return true; + } + } + return false; + } + return true; + }, .Union => return ty.hasCodeGenBits(), .ErrorUnion => return isByRef(ty.errorUnionPayload()), .Optional => { diff --git a/src/print_air.zig b/src/print_air.zig index 4e0b7cd4fd..9a116f6d8e 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -296,7 +296,7 @@ const Writer = struct { fn writeVectorInit(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; const vector_ty = w.air.getRefType(ty_pl.ty); - const len = vector_ty.vectorLen(); + const len = vector_ty.arrayLen(); const elements = @bitCast([]const Air.Inst.Ref, w.air.extra[ty_pl.payload..][0..len]); try s.print("{}, [", .{vector_ty}); diff --git a/src/print_zir.zig b/src/print_zir.zig index 6cbef5d446..6f580abab5 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -1963,7 +1963,8 @@ const Writer = struct { if (i != 0) try stream.writeAll(", "); try self.writeInstRef(stream, arg); } - try stream.writeAll("})"); + try stream.writeAll("}) "); + try self.writeSrc(stream, inst_data.src()); } fn writeUnreachable(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { diff --git a/src/type.zig b/src/type.zig index f62c94c469..f7d8795162 100644 --- a/src/type.zig +++ b/src/type.zig @@ -128,6 +128,7 @@ pub const Type = extern union { .prefetch_options, .export_options, .extern_options, + .tuple, => return .Struct, .enum_full, @@ -604,6 +605,24 @@ pub const Type = extern union { return a_payload.data == b_payload.data; } } + if (a.castTag(.tuple)) |a_payload| { + if (b.castTag(.tuple)) |b_payload| { + if (a_payload.data.types.len != b_payload.data.types.len) return false; + + for (a_payload.data.types) |a_ty, i| { + const b_ty = b_payload.data.types[i]; + if (!eql(a_ty, b_ty)) return false; + } + + for (a_payload.data.values) |a_val, i| { + const ty = a_payload.data.types[i]; + const b_val = b_payload.data.values[i]; + if (!Value.eql(a_val, b_val, ty)) return false; + } + + return true; + } + } return a.tag() == b.tag(); }, .Enum => { @@ -891,6 +910,21 @@ pub const Type = extern union { .elem_type = try payload.elem_type.copy(allocator), }); }, + .tuple => { + const payload = self.castTag(.tuple).?.data; + const types = try allocator.alloc(Type, payload.types.len); + const values = try allocator.alloc(Value, payload.values.len); + for (payload.types) |ty, i| { + types[i] = try ty.copy(allocator); + } + for (payload.values) |val, i| { + values[i] = try val.copy(allocator); + } + return Tag.tuple.create(allocator, .{ + .types = types, + .values = values, + }); + }, .function => { const payload = self.castTag(.function).?.data; const param_types = try allocator.alloc(Type, payload.param_types.len); @@ -1119,6 +1153,24 @@ pub const Type = extern union { ty = payload.elem_type; continue; }, + .tuple => { + const tuple = ty.castTag(.tuple).?.data; + try writer.writeAll("tuple{"); + for (tuple.types) |field_ty, i| { + if (i != 0) try writer.writeAll(", "); + const val = tuple.values[i]; + if (val.tag() != .unreachable_value) { + try writer.writeAll("comptime "); + } + try field_ty.format("", .{}, writer); + if (val.tag() != .unreachable_value) { + try writer.writeAll(" = "); + try val.format("", .{}, writer); + } + } + try writer.writeAll("}"); + return; + }, .single_const_pointer => { const pointee_type = ty.castTag(.single_const_pointer).?.data; try writer.writeAll("*const "); @@ -1480,15 +1532,40 @@ pub const Type = extern union { return requiresComptime(optionalChild(ty, &buf)); }, - .error_union, - .anyframe_T, - .@"struct", - .@"union", - .union_tagged, - .enum_numbered, - .enum_full, - .enum_nonexhaustive, - => false, // TODO some of these should be `true` depending on their child types + .tuple => { + const tuple = ty.castTag(.tuple).?.data; + for (tuple.types) |field_ty| { + if (requiresComptime(field_ty)) { + return true; + } + } + return false; + }, + + .@"struct" => { + const struct_obj = ty.castTag(.@"struct").?.data; + for (struct_obj.fields.values()) |field| { + if (requiresComptime(field.ty)) { + return true; + } + } + return false; + }, + + .@"union", .union_tagged => { + const union_obj = ty.cast(Payload.Union).?.data; + for (union_obj.fields.values()) |field| { + if (requiresComptime(field.ty)) { + return true; + } + } + return false; + }, + + .error_union => return requiresComptime(errorUnionPayload(ty)), + .anyframe_T => return ty.castTag(.anyframe_T).?.data.requiresComptime(), + .enum_numbered => return ty.castTag(.enum_numbered).?.data.tag_ty.requiresComptime(), + .enum_full, .enum_nonexhaustive => return ty.cast(Payload.EnumFull).?.data.tag_ty.requiresComptime(), }; } @@ -1697,6 +1774,16 @@ pub const Type = extern union { return payload.error_set.hasCodeGenBits() or payload.payload.hasCodeGenBits(); }, + .tuple => { + const tuple = self.castTag(.tuple).?.data; + for (tuple.types) |ty, i| { + const val = tuple.values[i]; + if (val.tag() != .unreachable_value) continue; // comptime field + if (ty.hasCodeGenBits()) return true; + } + return false; + }, + .void, .type, .comptime_int, @@ -1968,6 +2055,21 @@ pub const Type = extern union { } return big_align; }, + + .tuple => { + const tuple = self.castTag(.tuple).?.data; + var big_align: u32 = 0; + for (tuple.types) |field_ty, i| { + const val = tuple.values[i]; + if (val.tag() != .unreachable_value) continue; // comptime field + if (!field_ty.hasCodeGenBits()) continue; + + const field_align = field_ty.abiAlignment(target); + big_align = @maximum(big_align, field_align); + } + return big_align; + }, + .enum_full, .enum_nonexhaustive, .enum_simple, .enum_numbered => { var buffer: Payload.Bits = undefined; const int_tag_ty = self.intTagType(&buffer); @@ -2037,13 +2139,14 @@ pub const Type = extern union { .void, => 0, - .@"struct" => { + .@"struct", .tuple => { const field_count = self.structFieldCount(); if (field_count == 0) { return 0; } return self.structFieldOffset(field_count, target); }, + .enum_simple, .enum_full, .enum_nonexhaustive, .enum_numbered => { var buffer: Payload.Bits = undefined; const int_tag_ty = self.intTagType(&buffer); @@ -2231,6 +2334,11 @@ pub const Type = extern union { } return total; }, + + .tuple => { + @panic("TODO bitSize tuples"); + }, + .enum_simple, .enum_full, .enum_nonexhaustive, .enum_numbered => { var buffer: Payload.Bits = undefined; const int_tag_ty = ty.intTagType(&buffer); @@ -2926,6 +3034,7 @@ pub const Type = extern union { pub fn containerLayout(ty: Type) std.builtin.TypeInfo.ContainerLayout { return switch (ty.tag()) { + .tuple => .Auto, .@"struct" => ty.castTag(.@"struct").?.data.layout, .@"union" => ty.castTag(.@"union").?.data.layout, .union_tagged => ty.castTag(.union_tagged).?.data.layout, @@ -2998,6 +3107,7 @@ pub const Type = extern union { .array_sentinel => ty.castTag(.array_sentinel).?.data.len, .array_u8 => ty.castTag(.array_u8).?.data, .array_u8_sentinel_0 => ty.castTag(.array_u8_sentinel_0).?.data, + .tuple => ty.castTag(.tuple).?.data.types.len, else => unreachable, }; @@ -3010,6 +3120,7 @@ pub const Type = extern union { pub fn vectorLen(ty: Type) u32 { return switch (ty.tag()) { .vector => @intCast(u32, ty.castTag(.vector).?.data.len), + .tuple => @intCast(u32, ty.castTag(.tuple).?.data.types.len), else => unreachable, }; } @@ -3463,6 +3574,17 @@ pub const Type = extern union { } return Value.initTag(.empty_struct_value); }, + + .tuple => { + const tuple = ty.castTag(.tuple).?.data; + for (tuple.values) |val| { + if (val.tag() == .unreachable_value) { + return null; // non-comptime field + } + } + return Value.initTag(.empty_struct_value); + }, + .enum_numbered => { const enum_numbered = ty.castTag(.enum_numbered).?.data; if (enum_numbered.fields.count() == 1) { @@ -3539,7 +3661,8 @@ pub const Type = extern union { .Slice, .Many, .C => true, .One => ty.elemType().zigTypeTag() == .Array, }, - else => false, // TODO tuples are indexable + .Struct => ty.tag() == .tuple, + else => false, }; } @@ -3766,6 +3889,7 @@ pub const Type = extern union { return struct_obj.fields.count(); }, .empty_struct => return 0, + .tuple => return ty.castTag(.tuple).?.data.types.len, else => unreachable, } } @@ -3781,6 +3905,7 @@ pub const Type = extern union { const union_obj = ty.cast(Payload.Union).?.data; return union_obj.fields.values()[index].ty; }, + .tuple => return ty.castTag(.tuple).?.data.types[index], else => unreachable, } } @@ -3933,6 +4058,31 @@ pub const Type = extern union { it.offset = std.mem.alignForwardGeneric(u64, it.offset, it.big_align); return it.offset; }, + + .tuple => { + const tuple = ty.castTag(.tuple).?.data; + + var offset: u64 = 0; + var big_align: u32 = 0; + + for (tuple.types) |field_ty, i| { + const field_val = tuple.values[i]; + if (field_val.tag() != .unreachable_value) { + // comptime field + if (i == index) return offset; + continue; + } + + const field_align = field_ty.abiAlignment(target); + big_align = @maximum(big_align, field_align); + offset = std.mem.alignForwardGeneric(u64, offset, field_align); + if (i == index) return offset; + offset += field_ty.abiSize(target); + } + offset = std.mem.alignForwardGeneric(u64, offset, big_align); + return offset; + }, + .@"union" => return 0, .union_tagged => { const union_obj = ty.castTag(.union_tagged).?.data; @@ -4182,6 +4332,8 @@ pub const Type = extern union { array, array_sentinel, vector, + /// Possible Value tags for this: @"struct" + tuple, pointer, single_const_pointer, single_mut_pointer, @@ -4326,6 +4478,7 @@ pub const Type = extern union { .enum_simple => Payload.EnumSimple, .enum_numbered => Payload.EnumNumbered, .empty_struct => Payload.ContainerScope, + .tuple => Payload.Tuple, }; } @@ -4490,6 +4643,14 @@ pub const Type = extern union { data: *Module.Struct, }; + pub const Tuple = struct { + base: Payload = .{ .tag = .tuple }, + data: struct { + types: []Type, + values: []Value, + }, + }; + pub const Union = struct { base: Payload, data: *Module.Union, -- cgit v1.2.3 From 4fccc95b0152aefeb40912768ec045b57a2fdc2b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 20 Jan 2022 15:36:40 -0700 Subject: Sema: fix requiresComptime infinite recursion When asking a struct or union whether the type requires comptime, it may need to ask itself recursively, for example because of a field which is a pointer to itself. This commit adds a field to each to keep track of when computing the "requires comptime" value and returns `false` if the check is already ongoing. --- src/Module.zig | 4 ++++ src/type.zig | 44 +++++++++++++++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/Module.zig b/src/Module.zig index c66509f33a..1cb890b886 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -821,6 +821,8 @@ pub const ErrorSet = struct { } }; +pub const RequiresComptime = enum { no, yes, unknown, wip }; + /// Represents the data that a struct declaration provides. pub const Struct = struct { /// The Decl that corresponds to the struct itself. @@ -849,6 +851,7 @@ pub const Struct = struct { /// If true, definitely nonzero size at runtime. If false, resolving the fields /// is necessary to determine whether it has bits at runtime. known_has_bits: bool, + requires_comptime: RequiresComptime = .unknown, pub const Fields = std.StringArrayHashMapUnmanaged(Field); @@ -1038,6 +1041,7 @@ pub const Union = struct { // which `have_layout` does not ensure. fully_resolved, }, + requires_comptime: RequiresComptime = .unknown, pub const Field = struct { /// undefined until `status` is `have_field_types` or `have_layout`. diff --git a/src/type.zig b/src/type.zig index f7d8795162..0801d7e01f 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1544,22 +1544,40 @@ pub const Type = extern union { .@"struct" => { const struct_obj = ty.castTag(.@"struct").?.data; - for (struct_obj.fields.values()) |field| { - if (requiresComptime(field.ty)) { - return true; - } + switch (struct_obj.requires_comptime) { + .no, .wip => return false, + .yes => return true, + .unknown => { + struct_obj.requires_comptime = .wip; + for (struct_obj.fields.values()) |field| { + if (requiresComptime(field.ty)) { + struct_obj.requires_comptime = .yes; + return true; + } + } + struct_obj.requires_comptime = .no; + return false; + }, } - return false; }, .@"union", .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; - for (union_obj.fields.values()) |field| { - if (requiresComptime(field.ty)) { - return true; - } + switch (union_obj.requires_comptime) { + .no, .wip => return false, + .yes => return true, + .unknown => { + union_obj.requires_comptime = .wip; + for (union_obj.fields.values()) |field| { + if (requiresComptime(field.ty)) { + union_obj.requires_comptime = .yes; + return true; + } + } + union_obj.requires_comptime = .no; + return false; + }, } - return false; }, .error_union => return requiresComptime(errorUnionPayload(ty)), @@ -3661,7 +3679,7 @@ pub const Type = extern union { .Slice, .Many, .C => true, .One => ty.elemType().zigTypeTag() == .Array, }, - .Struct => ty.tag() == .tuple, + .Struct => ty.isTuple(), else => false, }; } @@ -4501,6 +4519,10 @@ pub const Type = extern union { } }; + pub fn isTuple(ty: Type) bool { + return ty.tag() == .tuple; + } + /// The sub-types are named after what fields they contain. pub const Payload = struct { tag: Tag, -- cgit v1.2.3 From 1417698c111e7f75d1c6959c7af8cd1a4d5231f6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 20 Jan 2022 15:40:48 -0700 Subject: Sema: storePtr optimization for tuples To generate better code for tuples, we detect a tuple operand in storePtr, and analyze field loads and stores directly. This avoids an extra allocation + memcpy which would occur if we used `coerce`. --- src/Sema.zig | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/Sema.zig b/src/Sema.zig index 08d8df3236..8093e04a34 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -14012,7 +14012,7 @@ fn coerce( if (inst == .empty_struct) { return arrayInitEmpty(sema, dest_ty); } - if (inst_ty.tag() == .tuple) { + if (inst_ty.isTuple()) { return sema.coerceTupleToArray(block, dest_ty, dest_ty_src, inst, inst_src); } }, @@ -14384,12 +14384,30 @@ fn storePtr2( uncasted_operand: Air.Inst.Ref, operand_src: LazySrcLoc, air_tag: Air.Inst.Tag, -) !void { +) CompileError!void { const ptr_ty = sema.typeOf(ptr); if (ptr_ty.isConstPtr()) - return sema.fail(block, src, "cannot assign to constant", .{}); + return sema.fail(block, ptr_src, "cannot assign to constant", .{}); const elem_ty = ptr_ty.childType(); + + // To generate better code for tuples, we detect a tuple operand here, and + // analyze field loads and stores directly. This avoids an extra allocation + memcpy + // which would occur if we used `coerce`. + const operand_ty = sema.typeOf(uncasted_operand); + if (operand_ty.castTag(.tuple)) |payload| { + const tuple_fields_len = payload.data.types.len; + var i: u32 = 0; + while (i < tuple_fields_len) : (i += 1) { + const elem_src = operand_src; // TODO better source location + const elem = try tupleField(sema, block, uncasted_operand, i, operand_src, elem_src); + const elem_index = try sema.addIntUnsigned(Type.usize, i); + const elem_ptr = try sema.elemPtr(block, ptr_src, ptr, elem_index, elem_src); + try sema.storePtr2(block, src, elem_ptr, elem_src, elem, elem_src, .store); + } + return; + } + const operand = try sema.coerce(block, elem_ty, uncasted_operand, operand_src); if ((try sema.typeHasOnePossibleValue(block, src, elem_ty)) != null) return; -- cgit v1.2.3 From 1f823eecdd071f619c761a743119f1a2a89af1bf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 20 Jan 2022 16:21:49 -0700 Subject: stage2: fix compilation on 32 bit targets --- src/Liveness.zig | 2 +- src/Sema.zig | 2 +- src/print_air.zig | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/Liveness.zig b/src/Liveness.zig index 1bc3159964..c7d0e481c5 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -373,7 +373,7 @@ fn analyzeInst( .vector_init => { const ty_pl = inst_datas[inst].ty_pl; const vector_ty = a.air.getRefType(ty_pl.ty); - const len = vector_ty.arrayLen(); + const len = @intCast(usize, vector_ty.arrayLen()); const elements = @bitCast([]const Air.Inst.Ref, a.air.extra[ty_pl.payload..][0..len]); if (elements.len <= bpi - 1) { diff --git a/src/Sema.zig b/src/Sema.zig index 8093e04a34..d3802a1784 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -15056,7 +15056,7 @@ fn coerceTupleToArray( ) !Air.Inst.Ref { const inst_ty = sema.typeOf(inst); const inst_len = inst_ty.arrayLen(); - const dest_len = dest_ty.arrayLen(); + const dest_len = try sema.usizeCast(block, dest_ty_src, dest_ty.arrayLen()); if (dest_len != inst_len) { const msg = msg: { diff --git a/src/print_air.zig b/src/print_air.zig index 9a116f6d8e..d9bb14ca7b 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -296,7 +296,7 @@ const Writer = struct { fn writeVectorInit(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; const vector_ty = w.air.getRefType(ty_pl.ty); - const len = vector_ty.arrayLen(); + const len = @intCast(usize, vector_ty.arrayLen()); const elements = @bitCast([]const Air.Inst.Ref, w.air.extra[ty_pl.payload..][0..len]); try s.print("{}, [", .{vector_ty}); -- cgit v1.2.3