| author | |
| committer | |
| log | 069a009fbc6e34cc5a14cea037a2129cf4642434 |
| tree | 581889431796bef24de22e1c12ce8c6a6fa472b1 |
| parent | 3c46da14db296beec186c3df4fe3669f6c99b715 |
Closes: #3652112 files changed, 131 insertions(+), 42 deletions(-)
src/Air.zig+2-2| ... | ... | @@ -780,8 +780,8 @@ pub const Inst = struct { |
| 780 | 780 | reduce, |
| 781 | 781 | /// Same as `reduce` with optimized float mode. |
| 782 | 782 | reduce_optimized, |
| 783 | /// Given an integer, bool, float, or pointer operand, return a vector with all elements | |
| 784 | /// equal to the scalar value. | |
| 783 | /// Given an operand, return a vector or array with all elements equal to the operand. | |
| 784 | /// For a sentinel-terminated array, the sentinel is derived from the result type. | |
| 785 | 785 | /// Uses the `ty_op` field. |
| 786 | 786 | splat, |
| 787 | 787 | /// Constructs a vector by selecting elements from a single vector based on a mask. Each |
src/Air/Legalize.zig+22-8| ... | ... | @@ -200,6 +200,8 @@ pub const Feature = enum { |
| 200 | 200 | expand_packed_agg_field_val, |
| 201 | 201 | /// Replace `aggregate_init` of a packed struct with a sequence of `shl_exact`, `bit_cast`, `int_cast`, and `bit_or`. |
| 202 | 202 | expand_packed_aggregate_init, |
| 203 | /// Replace `splat` of an array with an `aggregate_init`. | |
| 204 | expand_array_splat, | |
| 203 | 205 | /// Replace `array_to_vector` with an `array_elem_val` per element followed by an `aggregate_init`. |
| 204 | 206 | expand_array_to_vector, |
| 205 | 207 | |
| ... | ... | @@ -891,15 +893,27 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 891 | 893 | .soft_float => unreachable, // the operand is not a scalar |
| 892 | 894 | } |
| 893 | 895 | }, |
| 894 | .splat => if (l.features.has(.splat_one_elem_to_bit_cast)) { | |
| 896 | .splat => { | |
| 895 | 897 | const ty_op = l.air_instructions.items(.data)[@backingInt(inst)].ty_op; |
| 896 | switch (ty_op.ty.vectorLen(zcu)) { | |
| 897 | 0 => unreachable, | |
| 898 | 1 => continue :inst l.replaceInst(inst, .bit_cast, .{ .ty_op = .{ | |
| 899 | .ty = ty_op.ty, | |
| 900 | .operand = ty_op.operand, | |
| 901 | } }), | |
| 902 | else => {}, | |
| 898 | switch (ty_op.ty.zigTypeTag(zcu)) { | |
| 899 | .vector => switch (ty_op.ty.vectorLen(zcu)) { | |
| 900 | 0 => unreachable, | |
| 901 | 1 => continue :inst l.replaceInst(inst, .bit_cast, .{ .ty_op = .{ | |
| 902 | .ty = ty_op.ty, | |
| 903 | .operand = ty_op.operand, | |
| 904 | } }), | |
| 905 | else => {}, | |
| 906 | }, | |
| 907 | .array => if (l.features.has(.expand_array_splat)) { | |
| 908 | const len: usize = @intCast(ty_op.ty.arrayLen(zcu)); | |
| 909 | const elems_start: u32 = @intCast(l.air_extra.items.len); | |
| 910 | try l.air_extra.appendNTimes(l.pt.zcu.gpa, @backingInt(ty_op.operand), len); | |
| 911 | continue :inst l.replaceInst(inst, .aggregate_init, .{ .ty_pl = .{ | |
| 912 | .ty = ty_op.ty, | |
| 913 | .payload = elems_start, | |
| 914 | } }); | |
| 915 | }, | |
| 916 | else => unreachable, | |
| 903 | 917 | } |
| 904 | 918 | }, |
| 905 | 919 | .shuffle_one => { |
src/Sema.zig+1-7| ... | ... | @@ -23271,13 +23271,7 @@ fn zirSplat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I |
| 23271 | 23271 | try sema.requireRuntimeBlock(block, src, scalar_src); |
| 23272 | 23272 | |
| 23273 | 23273 | switch (dest_ty.zigTypeTag(zcu)) { |
| 23274 | .array => { | |
| 23275 | const elems = try sema.arena.alloc(Air.Inst.Ref, len + @intFromBool(maybe_sentinel != null)); | |
| 23276 | @memset(elems[0..len], scalar); | |
| 23277 | if (maybe_sentinel) |s| elems[len] = Air.internedToRef(s.toIntern()); | |
| 23278 | return block.addAggregateInit(dest_ty, elems); | |
| 23279 | }, | |
| 23280 | .vector => return block.addTyOp(.splat, dest_ty, scalar), | |
| 23274 | .vector, .array => return block.addTyOp(.splat, dest_ty, scalar), | |
| 23281 | 23275 | else => unreachable, |
| 23282 | 23276 | } |
| 23283 | 23277 | } |
src/codegen/aarch64.zig+1| ... | ... | @@ -8,6 +8,7 @@ pub const Select = @import("aarch64/Select.zig"); |
| 8 | 8 | pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { |
| 9 | 9 | return comptime &.initMany(&.{ |
| 10 | 10 | .expand_bit_cast_safe, |
| 11 | .expand_array_splat, | |
| 11 | 12 | .expand_array_to_vector, |
| 12 | 13 | }); |
| 13 | 14 | } |
src/codegen/c.zig+1| ... | ... | @@ -39,6 +39,7 @@ pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features { |
| 39 | 39 | .expand_packed_store = true, |
| 40 | 40 | .expand_packed_agg_field_val = true, |
| 41 | 41 | .expand_packed_aggregate_init = true, |
| 42 | .expand_array_splat = true, | |
| 42 | 43 | .expand_array_to_vector = true, |
| 43 | 44 | |
| 44 | 45 | .scalarize_bit_cast_array = true, |
src/codegen/llvm/FuncGen.zig+77-25| ... | ... | @@ -5590,8 +5590,36 @@ fn airMemset(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error |
| 5590 | 5590 | .slice => null, |
| 5591 | 5591 | .many, .c => unreachable, |
| 5592 | 5592 | }); |
| 5593 | const len_bytes = try self.sliceOrArrayLenInBytes(dest_slice, ptr_ty); | |
| 5593 | 5594 | |
| 5594 | if (allow_byte_memset) if (bin_op.rhs.toInterned()) |elem_ip_index| { | |
| 5595 | try self.lowerMemset( | |
| 5596 | dest_ptr, | |
| 5597 | dest_ptr_align, | |
| 5598 | bin_op.rhs, | |
| 5599 | elem_ty, | |
| 5600 | len_bytes, | |
| 5601 | access_kind, | |
| 5602 | safety, | |
| 5603 | allow_byte_memset, | |
| 5604 | ); | |
| 5605 | return .none; | |
| 5606 | } | |
| 5607 | ||
| 5608 | fn lowerMemset( | |
| 5609 | self: *FuncGen, | |
| 5610 | dest_ptr: Builder.Value, | |
| 5611 | dest_ptr_align: InternPool.Alignment, | |
| 5612 | elem_ref: Air.Inst.Ref, | |
| 5613 | elem_ty: Type, | |
| 5614 | len_bytes: Builder.Value, | |
| 5615 | access_kind: Builder.MemoryAccessKind, | |
| 5616 | safety: bool, | |
| 5617 | allow_byte_memset: bool, | |
| 5618 | ) Allocator.Error!void { | |
| 5619 | const o = self.object; | |
| 5620 | const zcu = o.zcu; | |
| 5621 | ||
| 5622 | if (allow_byte_memset) if (elem_ref.toInterned()) |elem_ip_index| { | |
| 5595 | 5623 | const elem_val: Value = .fromInterned(elem_ip_index); |
| 5596 | 5624 | if (elem_val.isUndef(zcu)) { |
| 5597 | 5625 | // Even if safety is disabled, we still emit a memset to undefined since it conveys |
| ... | ... | @@ -5601,20 +5629,19 @@ fn airMemset(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error |
| 5601 | 5629 | try o.builder.intValue(.i8, 0xaa) |
| 5602 | 5630 | else |
| 5603 | 5631 | try o.builder.undefValue(.i8); |
| 5604 | const len = try self.sliceOrArrayLenInBytes(dest_slice, ptr_ty); | |
| 5605 | 5632 | _ = try self.wip.callMemSet( |
| 5606 | 5633 | dest_ptr, |
| 5607 | 5634 | dest_ptr_align.toLlvm(), |
| 5608 | 5635 | fill_byte, |
| 5609 | len, | |
| 5636 | len_bytes, | |
| 5610 | 5637 | access_kind, |
| 5611 | 5638 | self.disable_intrinsics, |
| 5612 | 5639 | ); |
| 5613 | 5640 | const owner_mod = self.ownerModule(); |
| 5614 | 5641 | if (safety and owner_mod.valgrind) { |
| 5615 | try self.valgrindMarkUndef(dest_ptr, len); | |
| 5642 | try self.valgrindMarkUndef(dest_ptr, len_bytes); | |
| 5616 | 5643 | } |
| 5617 | return .none; | |
| 5644 | return; | |
| 5618 | 5645 | } |
| 5619 | 5646 | |
| 5620 | 5647 | // Test if the element value is compile-time known to be a |
| ... | ... | @@ -5623,20 +5650,19 @@ fn airMemset(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error |
| 5623 | 5650 | // intrinsic can be used. |
| 5624 | 5651 | if (try elem_val.hasRepeatedByteRepr(zcu)) |byte_val| { |
| 5625 | 5652 | const fill_byte = try o.builder.intValue(.i8, byte_val); |
| 5626 | const len = try self.sliceOrArrayLenInBytes(dest_slice, ptr_ty); | |
| 5627 | 5653 | _ = try self.wip.callMemSet( |
| 5628 | 5654 | dest_ptr, |
| 5629 | 5655 | dest_ptr_align.toLlvm(), |
| 5630 | 5656 | fill_byte, |
| 5631 | len, | |
| 5657 | len_bytes, | |
| 5632 | 5658 | access_kind, |
| 5633 | 5659 | self.disable_intrinsics, |
| 5634 | 5660 | ); |
| 5635 | return .none; | |
| 5661 | return; | |
| 5636 | 5662 | } |
| 5637 | 5663 | }; |
| 5638 | 5664 | |
| 5639 | const value = try self.resolveInst(bin_op.rhs); | |
| 5665 | const value = try self.resolveInst(elem_ref); | |
| 5640 | 5666 | const elem_abi_size = elem_ty.abiSize(zcu); |
| 5641 | 5667 | |
| 5642 | 5668 | intrinsic: { |
| ... | ... | @@ -5660,16 +5686,15 @@ fn airMemset(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error |
| 5660 | 5686 | break :intrinsic; |
| 5661 | 5687 | }; |
| 5662 | 5688 | // Great, we can use the intrinsic! |
| 5663 | const len = try self.sliceOrArrayLenInBytes(dest_slice, ptr_ty); | |
| 5664 | 5689 | _ = try self.wip.callMemSet( |
| 5665 | 5690 | dest_ptr, |
| 5666 | 5691 | dest_ptr_align.toLlvm(), |
| 5667 | 5692 | fill_byte, |
| 5668 | len, | |
| 5693 | len_bytes, | |
| 5669 | 5694 | access_kind, |
| 5670 | 5695 | self.disable_intrinsics, |
| 5671 | 5696 | ); |
| 5672 | return .none; | |
| 5697 | return; | |
| 5673 | 5698 | } |
| 5674 | 5699 | |
| 5675 | 5700 | // non-byte-sized element. lower with a loop. something like this: |
| ... | ... | @@ -5693,15 +5718,7 @@ fn airMemset(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error |
| 5693 | 5718 | const body_block = try self.wip.block(1, "InlineMemsetBody"); |
| 5694 | 5719 | const end_block = try self.wip.block(1, "InlineMemsetEnd"); |
| 5695 | 5720 | |
| 5696 | const end_ptr = switch (ptr_ty.ptrSize(zcu)) { | |
| 5697 | .slice => try self.ptraddScaled( | |
| 5698 | dest_ptr, | |
| 5699 | try self.wip.extractValue(dest_slice, &.{1}, ""), | |
| 5700 | elem_abi_size, | |
| 5701 | ), | |
| 5702 | .one => try self.ptraddConst(dest_ptr, ptr_ty.childType(zcu).abiSize(zcu)), | |
| 5703 | .many, .c => unreachable, | |
| 5704 | }; | |
| 5721 | const end_ptr = try self.ptraddScaled(dest_ptr, len_bytes, 1); | |
| 5705 | 5722 | _ = try self.wip.br(loop_block); |
| 5706 | 5723 | |
| 5707 | 5724 | self.wip.cursor = .{ .block = loop_block }; |
| ... | ... | @@ -5718,7 +5735,7 @@ fn airMemset(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error |
| 5718 | 5735 | |
| 5719 | 5736 | self.wip.cursor = .{ .block = end_block }; |
| 5720 | 5737 | it_ptr.finish(&.{ next_ptr, dest_ptr }, &.{ body_block, entry_block }, &self.wip); |
| 5721 | return .none; | |
| 5738 | return; | |
| 5722 | 5739 | } |
| 5723 | 5740 | |
| 5724 | 5741 | fn airMemcpy(self: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value { |
| ... | ... | @@ -5980,10 +5997,45 @@ fn airErrorName(self: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Va |
| 5980 | 5997 | } |
| 5981 | 5998 | |
| 5982 | 5999 | fn airSplat(self: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value { |
| 6000 | const o = self.object; | |
| 6001 | const zcu = o.zcu; | |
| 5983 | 6002 | const ty_op = self.air.instructions.items(.data)[@backingInt(inst)].ty_op; |
| 5984 | const scalar = try self.resolveInst(ty_op.operand); | |
| 5985 | const vector_ty = self.typeOfIndex(inst); | |
| 5986 | return self.wip.splatVector(try self.object.lowerType(vector_ty, .as_value), scalar, ""); | |
| 6003 | const result_ty = self.typeOfIndex(inst); | |
| 6004 | switch (result_ty.zigTypeTag(zcu)) { | |
| 6005 | .vector => { | |
| 6006 | const scalar = try self.resolveInst(ty_op.operand); | |
| 6007 | return self.wip.splatVector(try o.lowerType(result_ty, .as_value), scalar, ""); | |
| 6008 | }, | |
| 6009 | .array => { | |
| 6010 | assert(isByRef(result_ty, zcu)); | |
| 6011 | ||
| 6012 | const result_ptr = try self.buildZigAlloca(result_ty, .none); | |
| 6013 | const array_info = result_ty.arrayInfo(zcu); | |
| 6014 | const elem_size = array_info.elem_type.abiSize(zcu); | |
| 6015 | const len_bytes = array_info.len * elem_size; | |
| 6016 | const len_bytes_llvm = try o.builder.intValue(try o.lowerType(.usize, .as_value), len_bytes); | |
| 6017 | ||
| 6018 | try self.lowerMemset( | |
| 6019 | result_ptr, | |
| 6020 | result_ty.abiAlignment(zcu), | |
| 6021 | ty_op.operand, | |
| 6022 | array_info.elem_type, | |
| 6023 | len_bytes_llvm, | |
| 6024 | .normal, | |
| 6025 | false, | |
| 6026 | !self.needMemsetWorkaround(len_bytes), | |
| 6027 | ); | |
| 6028 | ||
| 6029 | if (array_info.sentinel) |sent_val| { | |
| 6030 | const sent_ptr = try self.ptraddConst(result_ptr, len_bytes); | |
| 6031 | const sent_elem = try self.resolveValue(sent_val); | |
| 6032 | try self.store(sent_ptr, .none, sent_elem.toValue(), array_info.elem_type, .normal); | |
| 6033 | } | |
| 6034 | ||
| 6035 | return result_ptr; | |
| 6036 | }, | |
| 6037 | else => unreachable, | |
| 6038 | } | |
| 5987 | 6039 | } |
| 5988 | 6040 | |
| 5989 | 6041 | fn airSelect(self: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value { |
src/codegen/riscv64/CodeGen.zig+1| ... | ... | @@ -59,6 +59,7 @@ pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { |
| 59 | 59 | .expand_sub_safe, |
| 60 | 60 | .expand_mul_safe, |
| 61 | 61 | |
| 62 | .expand_array_splat, | |
| 62 | 63 | .expand_array_to_vector, |
| 63 | 64 | }); |
| 64 | 65 | } |
src/codegen/sparc64/CodeGen.zig+1| ... | ... | @@ -42,6 +42,7 @@ const InnerError = codegen.Error || error{OutOfRegisters}; |
| 42 | 42 | |
| 43 | 43 | pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features { |
| 44 | 44 | return comptime &.initMany(&.{ |
| 45 | .expand_array_splat, | |
| 45 | 46 | .expand_array_to_vector, |
| 46 | 47 | }); |
| 47 | 48 | } |
src/codegen/spirv/CodeGen.zig+1| ... | ... | @@ -129,6 +129,7 @@ pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { |
| 129 | 129 | .expand_sub_safe, |
| 130 | 130 | .expand_mul_safe, |
| 131 | 131 | |
| 132 | .expand_array_splat, | |
| 132 | 133 | .expand_array_to_vector, |
| 133 | 134 | }); |
| 134 | 135 | } |
src/codegen/wasm/CodeGen.zig+1| ... | ... | @@ -38,6 +38,7 @@ pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { |
| 38 | 38 | .expand_packed_store, |
| 39 | 39 | .expand_packed_agg_field_val, |
| 40 | 40 | .expand_packed_aggregate_init, |
| 41 | .expand_array_splat, | |
| 41 | 42 | .expand_array_to_vector, |
| 42 | 43 | |
| 43 | 44 | .scalarize_add, |
src/codegen/x86_64/CodeGen.zig+1| ... | ... | @@ -78,6 +78,7 @@ pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { |
| 78 | 78 | .expand_packed_store, |
| 79 | 79 | .expand_packed_agg_field_val, |
| 80 | 80 | .expand_packed_aggregate_init, |
| 81 | .expand_array_splat, | |
| 81 | 82 | .expand_array_to_vector, |
| 82 | 83 | }); |
| 83 | 84 | } |
test/behavior/memset.zig+22| ... | ... | @@ -30,6 +30,28 @@ fn testMemsetArray() !void { |
| 30 | 30 | } |
| 31 | 31 | } |
| 32 | 32 | |
| 33 | test "@memset preserves array sentinel" { | |
| 34 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 35 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; | |
| 36 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 37 | ||
| 38 | try testMemsetArraySentinel(); | |
| 39 | try comptime testMemsetArraySentinel(); | |
| 40 | } | |
| 41 | ||
| 42 | fn testMemsetArraySentinel() !void { | |
| 43 | var value: u32 = 42; | |
| 44 | _ = &value; | |
| 45 | var array: [3:0]u32 = .{ 1, 2, 3 }; | |
| 46 | ||
| 47 | @memset(&array, value); | |
| 48 | ||
| 49 | try expect(array[0] == value); | |
| 50 | try expect(array[1] == value); | |
| 51 | try expect(array[2] == value); | |
| 52 | try expect(array[3] == 0); | |
| 53 | } | |
| 54 | ||
| 33 | 55 | test "@memset on slices" { |
| 34 | 56 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; |
| 35 | 57 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; |