authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-21 18:56:30+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-21 18:56:30+02:00
logd1f80fe1063ab9ef28d527e27d2f19b23ea06bc3
tree2a4f010ae1533a2f451a8f72895057bbf2cd5ef7
parent755d09660d8886f77b089bb97157649d2fe65cf9
parentbd2db84eede87bf2336a624ff21e798e8b631921

Merge pull request 'Sema: allow splatting to scalar types' (#37349) from masonremaley/generic-splat into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/37349 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

8 files changed, 58 insertions(+), 48 deletions(-)

doc/langref.html.in+1-1
......@@ -5591,7 +5591,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
55915591 <p>
55925592 Produces an array or vector where each element is the value
55935593 {#syntax#}element{#endsyntax#}. The return type, including the number of
5594 elements, is inferred.
5594 elements, is inferred. If the result type is a scalar, `element` is returned.
55955595 </p>
55965596 {#code|test_splat_builtin.zig#}
55975597 {#see_also|Vectors|Arrays|@shuffle#}
doc/langref/test_splat_builtin.zig+7
......@@ -1,5 +1,6 @@
11const std = @import("std");
22const expectEqualSlices = std.testing.expectEqualSlices;
3const expectEqual = std.testing.expectEqual;
34
45test "vector @splat" {
56 const scalar: u32 = 5;
......@@ -13,4 +14,10 @@ test "array @splat" {
1314 try expectEqualSlices(u32, &[_]u32{ 5, 5, 5, 5 }, &@as([4]u32, result));
1415}
1516
17test "scalar @splat" {
18 const scalar: u32 = 5;
19 const result: u32 = @splat(scalar);
20 try expectEqual(5, result);
21}
22
1623// test
src/Sema.zig+9-10
......@@ -7515,13 +7515,12 @@ fn zirSplatOpResultType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compil
75157515 const un_node = sema.code.instructions.items(.data)[@backingInt(inst)].un_node;
75167516
75177517 const raw_ty = try sema.resolveTypeOrPoison(block, LazySrcLoc.unneeded, un_node.operand) orelse return .generic_poison_type;
7518 const vec_ty = raw_ty.optEuBaseType(zcu);
7518 const base_ty = raw_ty.optEuBaseType(zcu);
75197519
7520 switch (vec_ty.zigTypeTag(zcu)) {
7521 .array, .vector => {},
7522 else => return sema.fail(block, block.nodeOffset(un_node.src_node), "expected array or vector type, found '{f}'", .{vec_ty.fmt(zcu)}),
7520 switch (base_ty.zigTypeTag(zcu)) {
7521 .array, .vector => return Air.internedToRef(base_ty.childType(zcu).toIntern()),
7522 else => return Air.internedToRef(base_ty.toIntern()),
75237523 }
7524 return Air.internedToRef(vec_ty.childType(zcu).toIntern());
75257524}
75267525
75277526fn zirVectorType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
......@@ -23314,17 +23313,17 @@ fn zirSplat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
2331423313 const inst_data = sema.code.instructions.items(.data)[@backingInt(inst)].pl_node;
2331523314 const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
2331623315 const src = block.nodeOffset(inst_data.src_node);
23317 const scalar_src = block.builtinCallArgSrc(inst_data.src_node, 0);
23316 const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
2331823317 const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@splat");
23318 const operand = sema.resolveInst(extra.rhs);
2331923319
2332023320 switch (dest_ty.zigTypeTag(zcu)) {
2332123321 .array, .vector => {},
23322 else => return sema.fail(block, src, "expected array or vector type, found '{f}'", .{dest_ty.fmt(zcu)}),
23322 else => return sema.coerce(block, dest_ty, operand, operand_src),
2332323323 }
2332423324
23325 const operand = sema.resolveInst(extra.rhs);
2332623325 const scalar_ty = dest_ty.childType(zcu);
23327 const scalar = try sema.coerce(block, scalar_ty, operand, scalar_src);
23326 const scalar = try sema.coerce(block, scalar_ty, operand, operand_src);
2332823327
2332923328 const len = try sema.usizeCast(block, src, dest_ty.arrayLen(zcu));
2333023329
......@@ -23348,7 +23347,7 @@ fn zirSplat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
2334823347 return .fromValue(try pt.aggregateSplatValue(dest_ty, scalar_val));
2334923348 }
2335023349
23351 try sema.requireRuntimeBlock(block, src, scalar_src);
23350 try sema.requireRuntimeBlock(block, src, operand_src);
2335223351
2335323352 switch (dest_ty.zigTypeTag(zcu)) {
2335423353 .vector, .array => return block.addTyOp(.splat, dest_ty, scalar),
test/behavior/splat.zig+19
......@@ -160,3 +160,22 @@ test "vector @splat" {
160160 try S.doTheTest();
161161 try comptime S.doTheTest();
162162}
163
164test "scalar @splat" {
165 const S = struct {
166 fn testForTypes(R: type, result: R, Op: type, op: Op) !void {
167 try expect(@as(R, result) == @as(Op, @splat(op)));
168 }
169 fn doTheTest() !void {
170 try testForTypes(comptime_float, 3.1415, comptime_float, 3.1415);
171 try testForTypes(f32, 3.1415, comptime_float, 3.1415);
172 try testForTypes(f32, 3.1415, f32, 3.1415);
173 try testForTypes(u8, 31, u8, 31);
174 try testForTypes(u8, 31, u16, 31);
175 try testForTypes(i16, 31, i16, 31);
176 try testForTypes(i16, 31, i8, 31);
177 }
178 };
179 try S.doTheTest();
180 try comptime S.doTheTest();
181}
test/behavior/x86_64/binary.zig+9-10
......@@ -16,7 +16,6 @@ const nan = math.nan;
1616const Scalar = math.Scalar;
1717const select = math.select;
1818const sign = math.sign;
19const splat = math.splat;
2019const Sse = math.Sse;
2120const tmin = math.tmin;
2221
......@@ -5250,7 +5249,7 @@ test divTruncUnoptimized {
52505249
52515250inline fn divTruncOptimized(comptime Type: type, lhs: Type, rhs: Type) Type {
52525251 @setFloatMode(.optimized);
5253 return @divTrunc(lhs, select(@abs(rhs) > splat(Type, 0.0), rhs, splat(Type, 1.0)));
5252 return @divTrunc(lhs, select(@abs(rhs) > @as(Type, @splat(0.0)), rhs, @as(Type, @splat(1.0))));
52545253}
52555254test divTruncOptimized {
52565255 const test_div_trunc_optimized = binary(divTruncOptimized, .{ .compare = .approx_int });
......@@ -5271,7 +5270,7 @@ test divFloorUnoptimized {
52715270
52725271inline fn divFloorOptimized(comptime Type: type, lhs: Type, rhs: Type) Type {
52735272 @setFloatMode(.optimized);
5274 return @divFloor(lhs, select(@abs(rhs) > splat(Type, 0.0), rhs, splat(Type, 1.0)));
5273 return @divFloor(lhs, select(@abs(rhs) > @as(Type, @splat(0.0)), rhs, @as(Type, @splat(1.0))));
52755274}
52765275test divFloorOptimized {
52775276 const test_div_floor_optimized = binary(divFloorOptimized, .{ .compare = .approx_int });
......@@ -5292,7 +5291,7 @@ test divCeilUnoptimized {
52925291
52935292inline fn divCeilOptimized(comptime Type: type, lhs: Type, rhs: Type) Type {
52945293 @setFloatMode(.optimized);
5295 return @divCeil(lhs, select(@abs(rhs) > splat(Type, 0.0), rhs, splat(Type, 1.0)));
5294 return @divCeil(lhs, select(@abs(rhs) > @as(Type, @splat(0.0)), rhs, @as(Type, @splat(1.0))));
52965295}
52975296test divCeilOptimized {
52985297 const test_div_ceil_optimized = binary(divCeilOptimized, .{ .compare = .approx_int });
......@@ -5393,7 +5392,7 @@ test mulWithOverflow {
53935392inline fn shlWithOverflow(comptime Type: type, lhs: Type, rhs: Type) struct { Type, ChangeScalar(Type, u1) } {
53945393 const bit_cast_rhs: AsSignedness(Type, .unsigned) = @bitCast(rhs);
53955394 const truncate_rhs: Log2Int(Type) = @truncate(bit_cast_rhs);
5396 return @shlWithOverflow(lhs, if (comptime cast(Log2Int(Scalar(Type)), @bitSizeOf(Scalar(Type)))) |bits| truncate_rhs % splat(Log2Int(Type), bits) else truncate_rhs);
5395 return @shlWithOverflow(lhs, if (comptime cast(Log2Int(Scalar(Type)), @bitSizeOf(Scalar(Type)))) |bits| truncate_rhs % @as(Log2Int(Type), @splat(bits)) else truncate_rhs);
53975396}
53985397test shlWithOverflow {
53995398 const test_shl_with_overflow = binary(shlWithOverflow, .{});
......@@ -5492,7 +5491,7 @@ test bitOr {
54925491inline fn shr(comptime Type: type, lhs: Type, rhs: Type) Type {
54935492 const bit_cast_rhs: AsSignedness(Type, .unsigned) = @bitCast(rhs);
54945493 const truncate_rhs: Log2Int(Type) = @truncate(bit_cast_rhs);
5495 return lhs >> if (comptime cast(Log2Int(Scalar(Type)), @bitSizeOf(Scalar(Type)))) |bits| truncate_rhs % splat(Log2Int(Type), bits) else truncate_rhs;
5494 return lhs >> if (comptime cast(Log2Int(Scalar(Type)), @bitSizeOf(Scalar(Type)))) |bits| truncate_rhs % @as(Log2Int(Type), @splat(bits)) else truncate_rhs;
54965495}
54975496test shr {
54985497 const test_shr = binary(shr, .{});
......@@ -5503,7 +5502,7 @@ test shr {
55035502inline fn shrExact(comptime Type: type, lhs: Type, rhs: Type) Type {
55045503 const bit_cast_rhs: AsSignedness(Type, .unsigned) = @bitCast(rhs);
55055504 const truncate_rhs: Log2Int(Type) = @truncate(bit_cast_rhs);
5506 const final_rhs = if (comptime cast(Log2Int(Scalar(Type)), @bitSizeOf(Scalar(Type)))) |bits| truncate_rhs % splat(Log2Int(Type), bits) else truncate_rhs;
5505 const final_rhs = if (comptime cast(Log2Int(Scalar(Type)), @bitSizeOf(Scalar(Type)))) |bits| truncate_rhs % @as(Log2Int(Type), @splat(bits)) else truncate_rhs;
55075506 return @shrExact(lhs >> final_rhs << final_rhs, final_rhs);
55085507}
55095508test shrExact {
......@@ -5515,7 +5514,7 @@ test shrExact {
55155514inline fn shl(comptime Type: type, lhs: Type, rhs: Type) Type {
55165515 const bit_cast_rhs: AsSignedness(Type, .unsigned) = @bitCast(rhs);
55175516 const truncate_rhs: Log2Int(Type) = @truncate(bit_cast_rhs);
5518 return lhs << if (comptime cast(Log2Int(Scalar(Type)), @bitSizeOf(Scalar(Type)))) |bits| truncate_rhs % splat(Log2Int(Type), bits) else truncate_rhs;
5517 return lhs << if (comptime cast(Log2Int(Scalar(Type)), @bitSizeOf(Scalar(Type)))) |bits| truncate_rhs % @as(Log2Int(Type), @splat(bits)) else truncate_rhs;
55195518}
55205519test shl {
55215520 const test_shl = binary(shl, .{});
......@@ -5527,7 +5526,7 @@ inline fn shlExactUnsafe(comptime Type: type, lhs: Type, rhs: Type) Type {
55275526 @setRuntimeSafety(false);
55285527 const bit_cast_rhs: AsSignedness(Type, .unsigned) = @bitCast(rhs);
55295528 const truncate_rhs: Log2Int(Type) = @truncate(bit_cast_rhs);
5530 const final_rhs = if (comptime cast(Log2Int(Scalar(Type)), @bitSizeOf(Scalar(Type)))) |bits| truncate_rhs % splat(Log2Int(Type), bits) else truncate_rhs;
5529 const final_rhs = if (comptime cast(Log2Int(Scalar(Type)), @bitSizeOf(Scalar(Type)))) |bits| truncate_rhs % @as(Log2Int(Type), @splat(bits)) else truncate_rhs;
55315530 return @shlExact(lhs << final_rhs >> final_rhs, final_rhs);
55325531}
55335532test shlExactUnsafe {
......@@ -5540,7 +5539,7 @@ inline fn shlSaturate(comptime Type: type, lhs: Type, rhs: Type) Type {
55405539 // workaround https://github.com/ziglang/zig/issues/23034
55415540 if (@inComptime()) {
55425541 // workaround https://github.com/ziglang/zig/issues/23139
5543 return lhs <<| @min(@abs(rhs), splat(ChangeScalar(Type, u64), imax(u64)));
5542 return lhs <<| @min(@abs(rhs), @as(ChangeScalar(Type, u64), @splat(imax(u64))));
55445543 }
55455544 return lhs <<| @abs(rhs);
55465545}
test/behavior/x86_64/cast.zig+7-8
......@@ -12,7 +12,6 @@ const math = @import("math.zig");
1212const nan = math.nan;
1313const next = math.next;
1414const Scalar = math.Scalar;
15const splat = math.splat;
1615const Sse = math.Sse;
1716const tmin = math.tmin;
1817
......@@ -14508,18 +14507,18 @@ inline fn intCast(comptime Result: type, comptime Type: type, rhs: Type, comptim
1450814507 rhs & imax(Type) >> bits,
1450914508 .vector => rhs | @select(
1451014509 Scalar(Type),
14511 ct_rhs < splat(Type, 0),
14512 splat(Type, imin(Scalar(Type)) >> bits),
14513 splat(Type, 0),
14510 ct_rhs < @as(Type, @splat(0)),
14511 @as(Type, @splat(imin(Scalar(Type)) >> bits)),
14512 @as(Type, @splat(0)),
1451414513 ) & ~@select(
1451514514 Scalar(Type),
14516 ct_rhs >= splat(Type, 0),
14517 splat(Type, imin(Scalar(Type)) >> bits),
14518 splat(Type, 0),
14515 ct_rhs >= @as(Type, @splat(0)),
14516 @as(Type, @splat(imin(Scalar(Type)) >> bits)),
14517 @as(Type, @splat(0)),
1451914518 ),
1452014519 else => comptime unreachable,
1452114520 },
14522 .mask => |bits| if (bits == rhs_info.bits) rhs else rhs & splat(Type, (1 << bits) - 1),
14521 .mask => |bits| if (bits == rhs_info.bits) rhs else rhs & @as(Type, @splat((1 << bits) - 1)),
1452314522 });
1452414523}
1452514524test intCast {
test/behavior/x86_64/math.zig+6-12
......@@ -72,19 +72,13 @@ pub fn Log2Int(comptime Type: type) type {
7272pub fn Log2IntCeil(comptime Type: type) type {
7373 return ChangeScalar(Type, math.Log2IntCeil(Scalar(Type)));
7474}
75pub fn splat(comptime Type: type, scalar: Scalar(Type)) Type {
76 return switch (@typeInfo(Type)) {
77 else => scalar,
78 .vector => @splat(scalar),
79 };
80}
8175pub fn sign(rhs: anytype) ChangeScalar(@TypeOf(rhs), bool) {
8276 const Int = ChangeScalar(@TypeOf(rhs), switch (@typeInfo(Scalar(@TypeOf(rhs)))) {
8377 .int, .comptime_int => Scalar(@TypeOf(rhs)),
8478 .float => |float| @Int(.signed, float.bits),
8579 else => @compileError(@typeName(@TypeOf(rhs))),
8680 });
87 return @as(Int, @bitCast(rhs)) < splat(Int, 0);
81 return @as(Int, @bitCast(rhs)) < @as(Int, @splat(0));
8882}
8983pub fn select(cond: anytype, lhs: anytype, rhs: @TypeOf(lhs)) @TypeOf(lhs) {
9084 return switch (@typeInfo(@TypeOf(cond))) {
......@@ -117,19 +111,19 @@ pub noinline fn checkExpected(expected: anytype, actual: @TypeOf(expected), comp
117111 .approx_or_overflow => @exp2(@log2(epsilon) * 0.4),
118112 };
119113 const approx_unequal = @abs(expected - actual) > @max(
120 @abs(expected) * splat(Expected, tolerance),
121 splat(Expected, switch (compare) {
114 @abs(expected) * @as(Expected, @splat(tolerance)),
115 @as(Expected, @splat(switch (compare) {
122116 .strict, .relaxed => comptime unreachable,
123117 .approx, .approx_or_overflow => tolerance,
124118 .approx_int => 1,
125 }),
119 })),
126120 );
127121 break :unexpected switch (compare) {
128122 .strict, .relaxed => comptime unreachable,
129123 .approx, .approx_int => approx_unequal,
130124 .approx_or_overflow => approx_unequal &
131 (((@abs(expected) != splat(Expected, inf(Expected))) &
132 (@abs(actual) != splat(Expected, inf(Expected)))) |
125 (((@abs(expected) != @as(Expected, @splat(inf(Expected)))) &
126 (@abs(actual) != @as(Expected, @splat(inf(Expected))))) |
133127 (sign(expected) != sign(actual))),
134128 };
135129 },
test/cases/compile_errors/splat_bad_result_type.zig deleted-7
......@@ -1,7 +0,0 @@
1export fn f() void {
2 _ = @as(u32, @splat(5));
3}
4
5// error
6//
7// :2:18: error: expected array or vector type, found 'u32'