From 2687b8f7f4825c9018af6998e1de140e6185f9cd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 26 Feb 2022 20:59:36 -0700 Subject: [PATCH] stage2: implement `@unionInit` The ZIR instruction `union_init_ptr` is renamed to `union_init`. I made it always use by-value semantics for now, not taking the time to invest in result location semantics, in case we decide to change the rules for unions. This way is much simpler. There is a new AIR instruction: union_init. This is for a comptime known tag, runtime-known field value. vector_init is renamed to aggregate_init, which solves a TODO comment. --- src/Air.zig | 18 ++++-- src/AstGen.zig | 47 ++++----------- src/Liveness.zig | 12 ++-- src/Module.zig | 9 +++ src/Sema.zig | 113 ++++++++++++++++++++++++++--------- src/Zir.zig | 15 +++-- src/arch/aarch64/CodeGen.zig | 14 ++++- src/arch/arm/CodeGen.zig | 15 ++++- src/arch/riscv64/CodeGen.zig | 15 ++++- src/arch/wasm/CodeGen.zig | 12 +++- src/arch/x86_64/CodeGen.zig | 17 +++++- src/codegen/c.zig | 22 ++++++- src/codegen/llvm.zig | 108 ++++++++++++++++++++++++++++++++- src/print_air.zig | 13 +++- src/print_zir.zig | 10 ++-- test/behavior/union.zig | 42 ++++++++++++- 16 files changed, 371 insertions(+), 111 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index dc2cd1a9c3..5c8873a199 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -561,13 +561,15 @@ pub const Inst = struct { /// Uses the `un_op` field. error_name, - /// Constructs a vector, tuple, or array value out of runtime-known elements. + /// Constructs a vector, tuple, struct, 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 `aggregate_init` and make it support array values and - /// struct values too. - vector_init, + aggregate_init, + + /// Constructs a union from a field index and a runtime-known init value. + /// Uses the `ty_pl` field with payload `UnionInit`. + union_init, /// Communicates an intent to load memory. /// Result is always unused. @@ -768,6 +770,11 @@ pub const AtomicRmw = struct { } }; +pub const UnionInit = struct { + field_index: u32, + init: Inst.Ref, +}; + pub fn getMainBody(air: Air) []const Air.Inst.Index { const body_index = air.extra[@enumToInt(ExtraIndex.main_block)]; const extra = air.extraData(Block, body_index); @@ -864,7 +871,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .cmpxchg_weak, .cmpxchg_strong, .slice, - .vector_init, + .aggregate_init, + .union_init, .field_parent_ptr, => return air.getRefType(datas[inst].ty_pl.ty), diff --git a/src/AstGen.zig b/src/AstGen.zig index 45b074a848..b9ac0b5c43 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2052,8 +2052,8 @@ fn unusedResultDeferExpr(gz: *GenZir, defer_scope: *Scope.Defer, expr_scope: *Sc _ = try unusedResultExpr(gz, expr_scope, expr_node); } -/// Returns AST source node of the thing that is noreturn if the statement is definitely `noreturn`. -/// Otherwise returns 0. +/// Returns AST source node of the thing that is noreturn if the statement is +/// definitely `noreturn`. Otherwise returns 0. fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) InnerError!Ast.Node.Index { try emitDbgNode(gz, statement); // We need to emit an error if the result is not `noreturn` or `void`, but @@ -2201,7 +2201,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .array_init_anon, .array_init_ref, .array_init_anon_ref, - .union_init_ptr, + .union_init, .field_type, .field_type_ref, .error_set_decl, @@ -6802,40 +6802,17 @@ fn unionInit( ) InnerError!Zir.Inst.Ref { 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, .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, - }); - const result = try expr(gz, scope, .{ .ty = union_type }, params[2]); - return rvalue(gz, rl, result, node); - }, - .ptr => |result_ptr| { - return unionInitRlPtr(gz, scope, node, result_ptr, params[2], union_type, field_name); - }, - .block_ptr => |block_scope| { - return unionInitRlPtr(gz, scope, node, block_scope.rl_ptr, params[2], union_type, field_name); - }, - } -} - -fn unionInitRlPtr( - parent_gz: *GenZir, - scope: *Scope, - node: Ast.Node.Index, - result_ptr: Zir.Inst.Ref, - expr_node: Ast.Node.Index, - union_type: Zir.Inst.Ref, - field_name: Zir.Inst.Ref, -) InnerError!Zir.Inst.Ref { - const union_init_ptr = try parent_gz.addPlNode(.union_init_ptr, node, Zir.Inst.UnionInitPtr{ - .result_ptr = result_ptr, - .union_type = union_type, + const field_type = try gz.addPlNode(.field_type_ref, params[1], Zir.Inst.FieldTypeRef{ + .container_type = union_type, .field_name = field_name, }); - // TODO check if we need to do the elision like below in asRlPtr - return expr(parent_gz, scope, .{ .ptr = union_init_ptr }, expr_node); + const init = try reachableExpr(gz, scope, .{ .ty = field_type }, params[2], node); + const result = try gz.addPlNode(.union_init, node, Zir.Inst.UnionInit{ + .union_type = union_type, + .init = init, + .field_name = field_name, + }); + return rvalue(gz, rl, result, node); } fn asRlPtr( diff --git a/src/Liveness.zig b/src/Liveness.zig index f4ee20c49a..1fd6cd91ee 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -25,7 +25,7 @@ tomb_bits: []usize, /// array. The meaning of the data depends on the AIR tag. /// * `cond_br` - points to a `CondBr` in `extra` at this index. /// * `switch_br` - points to a `SwitchBr` in `extra` at this index. -/// * `asm`, `call`, `vector_init` - the value is a set of bits which are the extra tomb +/// * `asm`, `call`, `aggregate_init` - the value is a set of bits which are the extra tomb /// bits of operands. /// The main tomb bits are still used and the extra ones are starting with the lsb of the /// value here. @@ -420,10 +420,10 @@ fn analyzeInst( } return extra_tombs.finish(); }, - .vector_init => { + .aggregate_init => { const ty_pl = inst_datas[inst].ty_pl; - const vector_ty = a.air.getRefType(ty_pl.ty); - const len = @intCast(usize, vector_ty.arrayLen()); + const aggregate_ty = a.air.getRefType(ty_pl.ty); + const len = @intCast(usize, aggregate_ty.arrayLen()); const elements = @bitCast([]const Air.Inst.Ref, a.air.extra[ty_pl.payload..][0..len]); if (elements.len <= bpi - 1) { @@ -442,6 +442,10 @@ fn analyzeInst( } return extra_tombs.finish(); }, + .union_init => { + const extra = a.air.extraData(Air.UnionInit, inst_datas[inst].ty_pl.payload).data; + return trackOperands(a, new_set, inst, main_tomb, .{ extra.init, .none, .none }); + }, .struct_field_ptr, .struct_field_val => { const extra = a.air.extraData(Air.StructField, inst_datas[inst].ty_pl.payload).data; return trackOperands(a, new_set, inst, main_tomb, .{ extra.struct_operand, .none, .none }); diff --git a/src/Module.zig b/src/Module.zig index 91386405be..048895e5bf 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1123,6 +1123,15 @@ pub const Union = struct { /// undefined until `status` is `have_field_types` or `have_layout`. ty: Type, abi_align: Value, + + /// Returns the field alignment, assuming the union is not packed. + pub fn normalAlignment(field: Field, target: Target) u32 { + if (field.abi_align.tag() == .abi_align_default) { + return field.ty.abiAlignment(target); + } else { + return @intCast(u32, field.abi_align.toUnsignedInt()); + } + } }; pub const Fields = std.StringArrayHashMapUnmanaged(Field); diff --git a/src/Sema.zig b/src/Sema.zig index cd4c526822..dc79310028 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -204,7 +204,7 @@ pub const Block = struct { return block.namespace.file_scope; } - pub fn addTy( + fn addTy( block: *Block, tag: Air.Inst.Tag, ty: Type, @@ -215,7 +215,7 @@ pub const Block = struct { }); } - pub fn addTyOp( + fn addTyOp( block: *Block, tag: Air.Inst.Tag, ty: Type, @@ -230,7 +230,7 @@ pub const Block = struct { }); } - pub fn addBitCast(block: *Block, ty: Type, operand: Air.Inst.Ref) Allocator.Error!Air.Inst.Ref { + fn addBitCast(block: *Block, ty: Type, operand: Air.Inst.Ref) Allocator.Error!Air.Inst.Ref { return block.addInst(.{ .tag = .bitcast, .data = .{ .ty_op = .{ @@ -240,14 +240,14 @@ pub const Block = struct { }); } - pub fn addNoOp(block: *Block, tag: Air.Inst.Tag) error{OutOfMemory}!Air.Inst.Ref { + fn addNoOp(block: *Block, tag: Air.Inst.Tag) error{OutOfMemory}!Air.Inst.Ref { return block.addInst(.{ .tag = tag, .data = .{ .no_op = {} }, }); } - pub fn addUnOp( + fn addUnOp( block: *Block, tag: Air.Inst.Tag, operand: Air.Inst.Ref, @@ -258,7 +258,7 @@ pub const Block = struct { }); } - pub fn addBr( + fn addBr( block: *Block, target_block: Air.Inst.Index, operand: Air.Inst.Ref, @@ -328,7 +328,7 @@ pub const Block = struct { }); } - pub fn addStructFieldVal( + fn addStructFieldVal( block: *Block, struct_val: Air.Inst.Ref, field_index: u32, @@ -346,7 +346,7 @@ pub const Block = struct { }); } - pub fn addSliceElemPtr( + fn addSliceElemPtr( block: *Block, slice: Air.Inst.Ref, elem_index: Air.Inst.Ref, @@ -364,7 +364,7 @@ pub const Block = struct { }); } - pub fn addPtrElemPtr( + fn addPtrElemPtr( block: *Block, array_ptr: Air.Inst.Ref, elem_index: Air.Inst.Ref, @@ -374,7 +374,7 @@ pub const Block = struct { return block.addPtrElemPtrTypeRef(array_ptr, elem_index, ty_ref); } - pub fn addPtrElemPtrTypeRef( + fn addPtrElemPtrTypeRef( block: *Block, array_ptr: Air.Inst.Ref, elem_index: Air.Inst.Ref, @@ -392,7 +392,7 @@ pub const Block = struct { }); } - pub fn addVectorInit( + fn addAggregateInit( block: *Block, vector_ty: Type, elements: []const Air.Inst.Ref, @@ -404,7 +404,7 @@ pub const Block = struct { sema.appendRefsAssumeCapacity(elements); return block.addInst(.{ - .tag = .vector_init, + .tag = .aggregate_init, .data = .{ .ty_pl = .{ .ty = ty_ref, .payload = extra_index, @@ -412,6 +412,24 @@ pub const Block = struct { }); } + fn addUnionInit( + block: *Block, + union_ty: Type, + field_index: u32, + init: Air.Inst.Ref, + ) !Air.Inst.Ref { + return block.addInst(.{ + .tag = .union_init, + .data = .{ .ty_pl = .{ + .ty = try block.sema.addType(union_ty), + .payload = try block.sema.addExtra(Air.UnionInit{ + .field_index = field_index, + .init = init, + }), + } }, + }); + } + pub fn addInst(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref { return Air.indexToRef(try block.addInstAsIndex(inst)); } @@ -697,7 +715,7 @@ fn analyzeBodyInner( .array_init_ref => try sema.zirArrayInit(block, inst, true), .array_init_anon => try sema.zirArrayInitAnon(block, inst, false), .array_init_anon_ref => try sema.zirArrayInitAnon(block, inst, true), - .union_init_ptr => try sema.zirUnionInitPtr(block, inst), + .union_init => try sema.zirUnionInit(block, inst), .field_type => try sema.zirFieldType(block, inst), .field_type_ref => try sema.zirFieldTypeRef(block, inst), .ptr_to_int => try sema.zirPtrToInt(block, inst), @@ -11273,10 +11291,31 @@ fn arrayInitEmpty(sema: *Sema, obj_ty: Type) CompileError!Air.Inst.Ref { } } -fn zirUnionInitPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirUnionInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirUnionInitPtr", .{}); + const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const field_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const init_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.UnionInit, inst_data.payload_index).data; + const union_ty = try sema.resolveType(block, ty_src, extra.union_type); + const field_name = try sema.resolveConstString(block, field_src, extra.field_name); + const init = sema.resolveInst(extra.init); + const union_obj = union_ty.cast(Type.Payload.Union).?.data; + const field_index_usize = union_obj.fields.getIndex(field_name) orelse + return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name); + const field_index = @intCast(u32, field_index_usize); + + if (try sema.resolveMaybeUndefVal(block, init_src, init)) |init_val| { + const tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + return sema.addConstant( + union_ty, + try Value.Tag.@"union".create(sema.arena, .{ .tag = tag_val, .val = init_val }), + ); + } + + try sema.requireRuntimeBlock(block, init_src); + try sema.resolveTypeLayout(block, ty_src, union_ty); + return block.addUnionInit(union_ty, field_index, init); } fn zirStructInit( @@ -11447,7 +11486,7 @@ fn finishStructInit( } try sema.requireRuntimeBlock(block, src); - return block.addVectorInit(struct_ty, field_inits); + return block.addAggregateInit(struct_ty, field_inits); } fn zirStructInitAnon(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is_ref: bool) CompileError!Air.Inst.Ref { @@ -11527,7 +11566,7 @@ fn zirArrayInit( return alloc; } - return block.addVectorInit(array_ty, resolved_args); + return block.addAggregateInit(array_ty, resolved_args); } fn zirArrayInitAnon( @@ -11593,7 +11632,7 @@ fn zirArrayInitAnon( element_refs[i] = sema.resolveInst(operand); } - return block.addVectorInit(tuple_ty, element_refs); + return block.addAggregateInit(tuple_ty, element_refs); } fn addConstantMaybeRef( @@ -11617,31 +11656,47 @@ fn addConstantMaybeRef( fn zirFieldTypeRef(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirFieldTypeRef", .{}); + const extra = sema.code.extraData(Zir.Inst.FieldTypeRef, inst_data.payload_index).data; + const ty_src = inst_data.src(); + const field_src = inst_data.src(); + const aggregate_ty = try sema.resolveType(block, ty_src, extra.container_type); + const field_name = try sema.resolveConstString(block, field_src, extra.field_name); + return sema.fieldType(block, aggregate_ty, field_name, field_src, ty_src); } fn zirFieldType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.FieldType, inst_data.payload_index).data; - const src = inst_data.src(); + const ty_src = inst_data.src(); + const field_src = inst_data.src(); + const aggregate_ty = try sema.resolveType(block, ty_src, extra.container_type); const field_name = sema.code.nullTerminatedString(extra.name_start); - const unresolved_ty = try sema.resolveType(block, src, extra.container_type); - const resolved_ty = try sema.resolveTypeFields(block, src, unresolved_ty); + return sema.fieldType(block, aggregate_ty, field_name, field_src, ty_src); +} + +fn fieldType( + sema: *Sema, + block: *Block, + aggregate_ty: Type, + field_name: []const u8, + field_src: LazySrcLoc, + ty_src: LazySrcLoc, +) CompileError!Air.Inst.Ref { + const resolved_ty = try sema.resolveTypeFields(block, ty_src, aggregate_ty); switch (resolved_ty.zigTypeTag()) { .Struct => { const struct_obj = resolved_ty.castTag(.@"struct").?.data; const field = struct_obj.fields.get(field_name) orelse - return sema.failWithBadStructFieldAccess(block, struct_obj, src, field_name); + return sema.failWithBadStructFieldAccess(block, struct_obj, field_src, field_name); return sema.addType(field.ty); }, .Union => { const union_obj = resolved_ty.cast(Type.Payload.Union).?.data; const field = union_obj.fields.get(field_name) orelse - return sema.failWithBadUnionFieldAccess(block, union_obj, src, field_name); + return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name); return sema.addType(field.ty); }, - else => return sema.fail(block, src, "expected struct or union; found '{}'", .{ + else => return sema.fail(block, ty_src, "expected struct or union; found '{}'", .{ resolved_ty, }), } @@ -16396,7 +16451,7 @@ fn coerceArrayLike( if (runtime_src) |rs| { try sema.requireRuntimeBlock(block, rs); - return block.addVectorInit(dest_ty, element_refs); + return block.addAggregateInit(dest_ty, element_refs); } return sema.addConstant( @@ -16453,7 +16508,7 @@ fn coerceTupleToArray( if (runtime_src) |rs| { try sema.requireRuntimeBlock(block, rs); - return block.addVectorInit(dest_ty, element_refs); + return block.addAggregateInit(dest_ty, element_refs); } return sema.addConstant( diff --git a/src/Zir.zig b/src/Zir.zig index 86fe44773e..b70b41e506 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -721,10 +721,9 @@ pub const Inst = struct { /// Anonymous array initialization syntax, make the result a pointer. /// Uses the `pl_node` field. Payload is `MultiOp`. array_init_anon_ref, - /// Given a pointer to a union and a comptime known field name, activates that field - /// and returns a pointer to it. - /// Uses the `pl_node` field. Payload is `UnionInitPtr`. - union_init_ptr, + /// Implements the `@unionInit` builtin. + /// Uses the `pl_node` field. Payload is `UnionInit`. + union_init, /// Implements the `@typeInfo` builtin. Uses `un_node`. type_info, /// Implements the `@sizeOf` builtin. Uses `un_node`. @@ -1125,7 +1124,7 @@ pub const Inst = struct { .array_init_anon, .array_init_ref, .array_init_anon_ref, - .union_init_ptr, + .union_init, .field_type, .field_type_ref, .int_to_enum, @@ -1383,7 +1382,7 @@ pub const Inst = struct { .array_init_anon = .pl_node, .array_init_ref = .pl_node, .array_init_anon_ref = .pl_node, - .union_init_ptr = .pl_node, + .union_init = .pl_node, .type_info = .un_node, .size_of = .un_node, .bit_size_of = .un_node, @@ -2896,10 +2895,10 @@ pub const Inst = struct { ordering: Ref, }; - pub const UnionInitPtr = struct { - result_ptr: Ref, + pub const UnionInit = struct { union_type: Ref, field_name: Ref, + init: Ref, }; pub const AtomicStore = struct { diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 0a3070e881..c9413975e3 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -625,7 +625,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .tag_name => try self.airTagName(inst), .error_name => try self.airErrorName(inst), .splat => try self.airSplat(inst), - .vector_init => try self.airVectorInit(inst), + .aggregate_init => try self.airAggregateInit(inst), + .union_init => try self.airUnionInit(inst), .prefetch => try self.airPrefetch(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), @@ -3472,14 +3473,14 @@ fn airSplat(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void { +fn airAggregateInit(self: *Self, inst: Air.Inst.Index) !void { const vector_ty = self.air.typeOfIndex(inst); const len = vector_ty.vectorLen(); const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const elements = @bitCast([]const Air.Inst.Ref, self.air.extra[ty_pl.payload..][0..len]); const result: MCValue = res: { if (self.liveness.isUnused(inst)) break :res MCValue.dead; - return self.fail("TODO implement airVectorInit for {}", .{self.target.cpu.arch}); + return self.fail("TODO implement airAggregateInit for {}", .{self.target.cpu.arch}); }; if (elements.len <= Liveness.bpi - 1) { @@ -3494,6 +3495,13 @@ fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void { return bt.finishAir(result); } +fn airUnionInit(self: *Self, inst: Air.Inst.Index) !void { + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.UnionInit, ty_pl.payload).data; + _ = extra; + return self.fail("TODO implement airUnionInit for aarch64", .{}); +} + fn airPrefetch(self: *Self, inst: Air.Inst.Index) !void { const prefetch = self.air.instructions.items(.data)[inst].prefetch; return self.finishAir(inst, MCValue.dead, .{ prefetch.ptr, .none, .none }); diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 8f1d3e8be4..f4e97fa8b1 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -609,7 +609,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .tag_name => try self.airTagName(inst), .error_name => try self.airErrorName(inst), .splat => try self.airSplat(inst), - .vector_init => try self.airVectorInit(inst), + .aggregate_init => try self.airAggregateInit(inst), + .union_init => try self.airUnionInit(inst), .prefetch => try self.airPrefetch(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), @@ -3937,14 +3938,14 @@ fn airSplat(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void { +fn airAggregateInit(self: *Self, inst: Air.Inst.Index) !void { const vector_ty = self.air.typeOfIndex(inst); const len = vector_ty.vectorLen(); const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const elements = @bitCast([]const Air.Inst.Ref, self.air.extra[ty_pl.payload..][0..len]); const result: MCValue = res: { if (self.liveness.isUnused(inst)) break :res MCValue.dead; - return self.fail("TODO implement airVectorInit for arm", .{}); + return self.fail("TODO implement airAggregateInit for arm", .{}); }; if (elements.len <= Liveness.bpi - 1) { @@ -3959,6 +3960,14 @@ fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void { return bt.finishAir(result); } +fn airUnionInit(self: *Self, inst: Air.Inst.Index) !void { + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.UnionInit, ty_pl.payload).data; + _ = extra; + + return self.fail("TODO implement airUnionInit for arm", .{}); +} + fn airPrefetch(self: *Self, inst: Air.Inst.Index) !void { const prefetch = self.air.instructions.items(.data)[inst].prefetch; return self.finishAir(inst, MCValue.dead, .{ prefetch.ptr, .none, .none }); diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 33c5b86351..c654ea625d 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -596,7 +596,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .tag_name => try self.airTagName(inst), .error_name => try self.airErrorName(inst), .splat => try self.airSplat(inst), - .vector_init => try self.airVectorInit(inst), + .aggregate_init => try self.airAggregateInit(inst), + .union_init => try self.airUnionInit(inst), .prefetch => try self.airPrefetch(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), @@ -2157,14 +2158,14 @@ fn airSplat(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void { +fn airAggregateInit(self: *Self, inst: Air.Inst.Index) !void { const vector_ty = self.air.typeOfIndex(inst); const len = vector_ty.vectorLen(); const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const elements = @bitCast([]const Air.Inst.Ref, self.air.extra[ty_pl.payload..][0..len]); const result: MCValue = res: { if (self.liveness.isUnused(inst)) break :res MCValue.dead; - return self.fail("TODO implement airVectorInit for riscv64", .{}); + return self.fail("TODO implement airAggregateInit for riscv64", .{}); }; if (elements.len <= Liveness.bpi - 1) { @@ -2179,6 +2180,14 @@ fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void { return bt.finishAir(result); } +fn airUnionInit(self: *Self, inst: Air.Inst.Index) !void { + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.UnionInit, ty_pl.payload).data; + _ = extra; + return self.fail("TODO implement airUnionInit for riscv64", .{}); + // return self.finishAir(inst, result, .{ extra.ptr, extra.expected_value, extra.new_value }); +} + fn airPrefetch(self: *Self, inst: Air.Inst.Index) !void { const prefetch = self.air.instructions.items(.data)[inst].prefetch; return self.finishAir(inst, MCValue.dead, .{ prefetch.ptr, .none, .none }); diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index c99b44e612..e2e6b00e84 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1642,7 +1642,8 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .ret_ptr => self.airRetPtr(inst), .ret_load => self.airRetLoad(inst), .splat => self.airSplat(inst), - .vector_init => self.airVectorInit(inst), + .aggregate_init => self.airAggregateInit(inst), + .union_init => self.airUnionInit(inst), .prefetch => self.airPrefetch(inst), .slice => self.airSlice(inst), @@ -3350,7 +3351,7 @@ fn airSplat(self: *Self, inst: Air.Inst.Index) InnerError!WValue { return self.fail("TODO: Implement wasm airSplat", .{}); } -fn airVectorInit(self: *Self, inst: Air.Inst.Index) InnerError!WValue { +fn airAggregateInit(self: *Self, inst: Air.Inst.Index) InnerError!WValue { if (self.liveness.isUnused(inst)) return WValue{ .none = {} }; const vector_ty = self.air.typeOfIndex(inst); @@ -3359,7 +3360,7 @@ fn airVectorInit(self: *Self, inst: Air.Inst.Index) InnerError!WValue { const elements = @bitCast([]const Air.Inst.Ref, self.air.extra[ty_pl.payload..][0..len]); switch (vector_ty.zigTypeTag()) { - .Vector => return self.fail("TODO: Wasm backend: implement airVectorInit for vectors", .{}), + .Vector => return self.fail("TODO: Wasm backend: implement airAggregateInit for vectors", .{}), .Array => { const result = try self.allocStack(vector_ty); const elem_ty = vector_ty.childType(); @@ -3413,6 +3414,11 @@ fn airVectorInit(self: *Self, inst: Air.Inst.Index) InnerError!WValue { } } +fn airUnionInit(self: *Self, inst: Air.Inst.Index) InnerError!WValue { + if (self.liveness.isUnused(inst)) return WValue{ .none = {} }; + return self.fail("TODO: Wasm backend: implement airUnionInit", .{}); +} + fn airPrefetch(self: *Self, inst: Air.Inst.Index) InnerError!WValue { const prefetch = self.air.instructions.items(.data)[inst].prefetch; _ = prefetch; diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index b30a38fc40..47d1e1f4d3 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -708,7 +708,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .tag_name => try self.airTagName(inst), .error_name => try self.airErrorName(inst), .splat => try self.airSplat(inst), - .vector_init => try self.airVectorInit(inst), + .aggregate_init => try self.airAggregateInit(inst), + .union_init => try self.airUnionInit(inst), .prefetch => try self.airPrefetch(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), @@ -5232,14 +5233,14 @@ fn airSplat(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void { +fn airAggregateInit(self: *Self, inst: Air.Inst.Index) !void { const vector_ty = self.air.typeOfIndex(inst); const len = vector_ty.vectorLen(); const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const elements = @bitCast([]const Air.Inst.Ref, self.air.extra[ty_pl.payload..][0..len]); const result: MCValue = res: { if (self.liveness.isUnused(inst)) break :res MCValue.dead; - return self.fail("TODO implement airVectorInit for x86_64", .{}); + return self.fail("TODO implement airAggregateInit for x86_64", .{}); }; if (elements.len <= Liveness.bpi - 1) { @@ -5254,6 +5255,16 @@ fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void { return bt.finishAir(result); } +fn airUnionInit(self: *Self, inst: Air.Inst.Index) !void { + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.UnionInit, ty_pl.payload).data; + const result: MCValue = res: { + if (self.liveness.isUnused(inst)) break :res MCValue.dead; + return self.fail("TODO implement airAggregateInit for x86_64", .{}); + }; + return self.finishAir(inst, result, .{ extra.init, .none, .none }); +} + fn airPrefetch(self: *Self, inst: Air.Inst.Index) !void { const prefetch = self.air.instructions.items(.data)[inst].prefetch; return self.finishAir(inst, MCValue.dead, .{ prefetch.ptr, .none, .none }); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 2a0751a202..f09acaa47c 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1713,7 +1713,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .tag_name => try airTagName(f, inst), .error_name => try airErrorName(f, inst), .splat => try airSplat(f, inst), - .vector_init => try airVectorInit(f, inst), + .aggregate_init => try airAggregateInit(f, inst), + .union_init => try airUnionInit(f, inst), .prefetch => try airPrefetch(f, inst), .int_to_float, @@ -3526,7 +3527,7 @@ fn airSplat(f: *Function, inst: Air.Inst.Index) !CValue { return f.fail("TODO: C backend: implement airSplat", .{}); } -fn airVectorInit(f: *Function, inst: Air.Inst.Index) !CValue { +fn airAggregateInit(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); @@ -3541,7 +3542,22 @@ fn airVectorInit(f: *Function, inst: Air.Inst.Index) !CValue { _ = elements; _ = local; - return f.fail("TODO: C backend: implement airVectorInit", .{}); + return f.fail("TODO: C backend: implement airAggregateInit", .{}); +} + +fn airUnionInit(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; + + const inst_ty = f.air.typeOfIndex(inst); + const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; + + const writer = f.object.writer(); + const local = try f.allocLocal(inst_ty, .Const); + try writer.writeAll(" = "); + + _ = local; + _ = ty_pl; + return f.fail("TODO: C backend: implement airUnionInit", .{}); } fn airPrefetch(f: *Function, inst: Air.Inst.Index) !CValue { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index bc5f5ca7c7..c0642a59de 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -2175,7 +2175,8 @@ pub const FuncGen = struct { .tag_name => try self.airTagName(inst), .error_name => try self.airErrorName(inst), .splat => try self.airSplat(inst), - .vector_init => try self.airVectorInit(inst), + .aggregate_init => try self.airAggregateInit(inst), + .union_init => try self.airUnionInit(inst), .prefetch => try self.airPrefetch(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), @@ -4608,7 +4609,7 @@ pub const FuncGen = struct { return self.builder.buildShuffleVector(op_vector, undef_vector, mask_llvm_ty.constNull(), ""); } - fn airVectorInit(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airAggregateInit(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; @@ -4693,6 +4694,109 @@ pub const FuncGen = struct { } } + fn airUnionInit(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.UnionInit, ty_pl.payload).data; + const union_ty = self.air.typeOfIndex(inst); + const union_llvm_ty = try self.dg.llvmType(union_ty); + const target = self.dg.module.getTarget(); + const layout = union_ty.unionGetLayout(target); + if (layout.payload_size == 0) { + if (layout.tag_size == 0) { + return null; + } + assert(!isByRef(union_ty)); + return union_llvm_ty.constInt(extra.field_index, .False); + } + assert(isByRef(union_ty)); + // The llvm type of the alloca will the the named LLVM union type, which will not + // necessarily match the format that we need, depending on which tag is active. We + // must construct the correct unnamed struct type here and bitcast, in order to + // then set the fields appropriately. + const result_ptr = self.buildAlloca(union_llvm_ty); + const llvm_payload = try self.resolveInst(extra.init); + const union_obj = union_ty.cast(Type.Payload.Union).?.data; + assert(union_obj.haveFieldTypes()); + const field = union_obj.fields.values()[extra.field_index]; + const field_llvm_ty = try self.dg.llvmType(field.ty); + const tag_llvm_ty = try self.dg.llvmType(union_obj.tag_ty); + const field_size = field.ty.abiSize(target); + const field_align = field.normalAlignment(target); + + const llvm_union_ty = t: { + const payload = p: { + if (!field.ty.hasRuntimeBits()) { + const padding_len = @intCast(c_uint, layout.payload_size); + break :p self.context.intType(8).arrayType(padding_len); + } + if (field_size == layout.payload_size) { + break :p field_llvm_ty; + } + const padding_len = @intCast(c_uint, layout.payload_size - field_size); + const fields: [2]*const llvm.Type = .{ + field_llvm_ty, self.context.intType(8).arrayType(padding_len), + }; + break :p self.context.structType(&fields, fields.len, .False); + }; + if (layout.tag_size == 0) { + const fields: [1]*const llvm.Type = .{payload}; + break :t self.context.structType(&fields, fields.len, .False); + } + var fields: [2]*const llvm.Type = undefined; + if (layout.tag_align >= layout.payload_align) { + fields = .{ tag_llvm_ty, payload }; + } else { + fields = .{ payload, tag_llvm_ty }; + } + break :t self.context.structType(&fields, fields.len, .False); + }; + + const casted_ptr = self.builder.buildBitCast(result_ptr, llvm_union_ty.pointerType(0), ""); + + // Now we follow the layout as expressed above with GEP instructions to set the + // tag and the payload. + const index_type = self.context.intType(32); + + if (layout.tag_size == 0) { + const indices: [3]*const llvm.Value = .{ + index_type.constNull(), + index_type.constNull(), + index_type.constNull(), + }; + const len: c_uint = if (field_size == layout.payload_size) 2 else 3; + const field_ptr = self.builder.buildInBoundsGEP(casted_ptr, &indices, len, ""); + const store_inst = self.builder.buildStore(llvm_payload, field_ptr); + store_inst.setAlignment(field_align); + return result_ptr; + } + + { + const indices: [3]*const llvm.Value = .{ + index_type.constNull(), + index_type.constInt(@boolToInt(layout.tag_align >= layout.payload_align), .False), + index_type.constNull(), + }; + const len: c_uint = if (field_size == layout.payload_size) 2 else 3; + const field_ptr = self.builder.buildInBoundsGEP(casted_ptr, &indices, len, ""); + const store_inst = self.builder.buildStore(llvm_payload, field_ptr); + store_inst.setAlignment(field_align); + } + { + const indices: [2]*const llvm.Value = .{ + index_type.constNull(), + index_type.constInt(@boolToInt(layout.tag_align < layout.payload_align), .False), + }; + const field_ptr = self.builder.buildInBoundsGEP(casted_ptr, &indices, indices.len, ""); + const llvm_tag = union_llvm_ty.constInt(extra.field_index, .False); + const store_inst = self.builder.buildStore(llvm_tag, field_ptr); + store_inst.setAlignment(union_obj.tag_ty.abiAlignment(target)); + } + + return result_ptr; + } + fn airPrefetch(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { const prefetch = self.air.instructions.items(.data)[inst].prefetch; diff --git a/src/print_air.zig b/src/print_air.zig index 06ec464f91..4609c4d148 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -232,7 +232,8 @@ const Writer = struct { .assembly => try w.writeAssembly(s, inst), .dbg_stmt => try w.writeDbgStmt(s, inst), .call => try w.writeCall(s, inst), - .vector_init => try w.writeVectorInit(s, inst), + .aggregate_init => try w.writeAggregateInit(s, inst), + .union_init => try w.writeUnionInit(s, inst), .br => try w.writeBr(s, inst), .cond_br => try w.writeCondBr(s, inst), .switch_br => try w.writeSwitchBr(s, inst), @@ -301,7 +302,7 @@ const Writer = struct { try s.writeAll("}"); } - fn writeVectorInit(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { + fn writeAggregateInit(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 = @intCast(usize, vector_ty.arrayLen()); @@ -315,6 +316,14 @@ const Writer = struct { try s.writeAll("]"); } + fn writeUnionInit(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { + const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; + const extra = w.air.extraData(Air.UnionInit, ty_pl.payload).data; + + try s.print("{d}, ", .{extra.field_index}); + try w.writeOperand(s, inst, 0, extra.init); + } + fn writeStructField(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; const extra = w.air.extraData(Air.StructField, ty_pl.payload).data; diff --git a/src/print_zir.zig b/src/print_zir.zig index 9ef67e3213..f5e6f22e45 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -273,7 +273,7 @@ const Writer = struct { .slice_end => try self.writeSliceEnd(stream, inst), .slice_sentinel => try self.writeSliceSentinel(stream, inst), - .union_init_ptr => try self.writeUnionInitPtr(stream, inst), + .union_init => try self.writeUnionInit(stream, inst), .struct_init, .struct_init_ref, @@ -692,14 +692,14 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } - fn writeUnionInitPtr(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + fn writeUnionInit(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Zir.Inst.UnionInitPtr, inst_data.payload_index).data; - try self.writeInstRef(stream, extra.result_ptr); - try stream.writeAll(", "); + const extra = self.code.extraData(Zir.Inst.UnionInit, inst_data.payload_index).data; try self.writeInstRef(stream, extra.union_type); try stream.writeAll(", "); try self.writeInstRef(stream, extra.field_name); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.init); try stream.writeAll(") "); try self.writeSrc(stream, inst_data.src()); } diff --git a/test/behavior/union.zig b/test/behavior/union.zig index be288ee740..91bd783ce2 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -785,8 +785,40 @@ test "return union init with void payload" { comptime try S.entry(); } +test "@unionInit stored to a const" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + + const S = struct { + const U = union(enum) { + boolean: bool, + byte: u8, + }; + fn doTheTest() !void { + { + var t = true; + const u = @unionInit(U, "boolean", t); + try expect(u.boolean); + } + { + var byte: u8 = 69; + const u = @unionInit(U, "byte", byte); + try expect(u.byte == 69); + } + } + }; + + comptime try S.doTheTest(); + try S.doTheTest(); +} + test "@unionInit can modify a union type" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO const UnionInitEnum = union(enum) { Boolean: bool, @@ -807,7 +839,9 @@ test "@unionInit can modify a union type" { } test "@unionInit can modify a pointer value" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO const UnionInitEnum = union(enum) { Boolean: bool, @@ -825,7 +859,9 @@ test "@unionInit can modify a pointer value" { } test "union no tag with struct member" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO const Struct = struct {}; const Union = union {