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 | 45 ++++---------- 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, 370 insertions(+), 110 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index dc2cd1a9c3a24fc6af03ecfd8e80e572320c085a..5c8873a1997ab2249cb82ea76c0b02edcc3063c2 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 45b074a8483ac31c0cbe6d99ffa4f1d9249966dd..b9ac0b5c435d5c19f3d3c7dc447b2606baa57fca 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, + const field_type = try gz.addPlNode(.field_type_ref, params[1], Zir.Inst.FieldTypeRef{ + .container_type = union_type, + .field_name = field_name, + }); + 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, }); - // TODO check if we need to do the elision like below in asRlPtr - return expr(parent_gz, scope, .{ .ptr = union_init_ptr }, expr_node); + return rvalue(gz, rl, result, node); } fn asRlPtr( diff --git a/src/Liveness.zig b/src/Liveness.zig index f4ee20c49ad80e0d2004c72b29d5d87678c6b36e..1fd6cd91ee73a0f13d017861c888ab8305cf20d4 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 91386405be0564b3fe4e3761ea177b76fd1e5618..048895e5bf1f138b1a8faf37535c31a8c33f40a8 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 cd4c526822c02d3556e36c6fa5a13dc33d378e66..dc793100285ee4a2218c2be0e8a9bb6d9a7dd47c 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 86fe44773e0d18c117c145dc466560b949a742e7..b70b41e5060487fed0cc791f000fced9f9b33464 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 0a3070e881e62b65fa57c17150bac550b7d0dabc..c9413975e30d5bf37abed4f75007e6f075168b74 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 8f1d3e8be44ed702806d46c8090b71d241792952..f4e97fa8b1bce59c7d0b683d24c85e22a705962f 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 33c5b8635136458de7cf0cd3725d1477c4110b82..c654ea625d4a1bb07bbf320835f6f6090e13403c 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 c99b44e61253eebc88b802e1b9327969055889a9..e2e6b00e84ab0c633dc0b6e67c3d210da9594658 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 b30a38fc40c2dd3b5c707dc3dabd9b8f8f53ec29..47d1e1f4d3fec025b42d80d017a036c312dfa0cc 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 2a0751a202947e68ff66ba691d44d09cc559ac9e..f09acaa47c008a577911f1ffb445f1c9dacbd6c6 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 bc5f5ca7c7919371ad6d17ec7651bee754d91d4b..c0642a59ded7208ce3583956c56e7e00f46fe836 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 06ec464f914d75755035811862bea1b77948074b..4609c4d1488ed42fd31270b4d3c1e6be8d5cd5ee 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 9ef67e3213f0ef428c5bd1ddf7e8dac3dce6b499..f5e6f22e45907619d9a8075b78148ef1bfbace22 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 be288ee7404106c8b56902207e7f475e1fe9328e..91bd783ce28e5903646fae33af1b6e2a55a0e5d2 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 { -- 2.54.0