authorgravatar for mason@gamesbymason.comMason Remaley <mason@gamesbymason.com> 2026-10-20 15:31:05-07:00
committergravatar for mason@gamesbymason.comMason Remaley <mason@gamesbymason.com> 2026-10-21 02:17:59-07:00
loge572063f92ddd497c5bb6a067358bdbae1c16aab
tree25315d5cdeb5f0ced3b49007de2f9eae91bdcceb
parentc8bb44618a9c3c7bfff4a9f672a451fdeda1bb02

Allow splatting to scalar types

This makes writing code that is generic over scalar and vector types easier. In particular, this helps when multiplying a generic argument by a constant, or passing a generic arugment alongside a scalar value to a function or builtin such as `@mulAdd`. This also has the side benefit of making the implicit splat for some binary operations as suggested by @SpexGuy in #35376 simpler to implement for inplace arithmetic operations. part of #35376

5 files changed, 36 insertions(+), 18 deletions(-)

doc/langref.html.in+1-1
...@@ -5582,7 +5582,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val...@@ -5582,7 +5582,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
5582 <p>5582 <p>
5583 Produces an array or vector where each element is the value5583 Produces an array or vector where each element is the value
5584 {#syntax#}element{#endsyntax#}. The return type, including the number of5584 {#syntax#}element{#endsyntax#}. The return type, including the number of
5585 elements, is inferred.5585 elements, is inferred. If the result type is a scalar, `element` is returned.
5586 </p>5586 </p>
5587 {#code|test_splat_builtin.zig#}5587 {#code|test_splat_builtin.zig#}
5588 {#see_also|Vectors|Arrays|@shuffle#}5588 {#see_also|Vectors|Arrays|@shuffle#}
doc/langref/test_splat_builtin.zig+7
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1const std = @import("std");1const std = @import("std");
2const expectEqualSlices = std.testing.expectEqualSlices;2const expectEqualSlices = std.testing.expectEqualSlices;
3const expectEqual = std.testing.expectEqual;
34
4test "vector @splat" {5test "vector @splat" {
5 const scalar: u32 = 5;6 const scalar: u32 = 5;
...@@ -13,4 +14,10 @@ test "array @splat" {...@@ -13,4 +14,10 @@ test "array @splat" {
13 try expectEqualSlices(u32, &[_]u32{ 5, 5, 5, 5 }, &@as([4]u32, result));14 try expectEqualSlices(u32, &[_]u32{ 5, 5, 5, 5 }, &@as([4]u32, result));
14}15}
1516
17test "scalar @splat" {
18 const scalar: u32 = 5;
19 const result: u32 = @splat(scalar);
20 try expectEqual(5, result);
21}
22
16// test23// test
src/Sema.zig+9-10
...@@ -7515,13 +7515,12 @@ fn zirSplatOpResultType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compil...@@ -7515,13 +7515,12 @@ fn zirSplatOpResultType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compil
7515 const un_node = sema.code.instructions.items(.data)[@backingInt(inst)].un_node;7515 const un_node = sema.code.instructions.items(.data)[@backingInt(inst)].un_node;
75167516
7517 const raw_ty = try sema.resolveTypeOrPoison(block, LazySrcLoc.unneeded, un_node.operand) orelse return .generic_poison_type;7517 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)) {7520 switch (base_ty.zigTypeTag(zcu)) {
7521 .array, .vector => {},7521 .array, .vector => return Air.internedToRef(base_ty.childType(zcu).toIntern()),
7522 else => return sema.fail(block, block.nodeOffset(un_node.src_node), "expected array or vector type, found '{f}'", .{vec_ty.fmt(zcu)}),7522 else => return Air.internedToRef(base_ty.toIntern()),
7523 }7523 }
7524 return Air.internedToRef(vec_ty.childType(zcu).toIntern());
7525}7524}
75267525
7527fn zirVectorType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {7526fn 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...@@ -23314,17 +23313,17 @@ fn zirSplat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
23314 const inst_data = sema.code.instructions.items(.data)[@backingInt(inst)].pl_node;23313 const inst_data = sema.code.instructions.items(.data)[@backingInt(inst)].pl_node;
23315 const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;23314 const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
23316 const src = block.nodeOffset(inst_data.src_node);23315 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);
23318 const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@splat");23317 const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@splat");
23318 const operand = sema.resolveInst(extra.rhs);
2331923319
23320 switch (dest_ty.zigTypeTag(zcu)) {23320 switch (dest_ty.zigTypeTag(zcu)) {
23321 .array, .vector => {},23321 .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),
23323 }23323 }
2332423324
23325 const operand = sema.resolveInst(extra.rhs);
23326 const scalar_ty = dest_ty.childType(zcu);23325 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
23329 const len = try sema.usizeCast(block, src, dest_ty.arrayLen(zcu));23328 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...@@ -23348,7 +23347,7 @@ fn zirSplat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
23348 return .fromValue(try pt.aggregateSplatValue(dest_ty, scalar_val));23347 return .fromValue(try pt.aggregateSplatValue(dest_ty, scalar_val));
23349 }23348 }
2335023349
23351 try sema.requireRuntimeBlock(block, src, scalar_src);23350 try sema.requireRuntimeBlock(block, src, operand_src);
2335223351
23353 switch (dest_ty.zigTypeTag(zcu)) {23352 switch (dest_ty.zigTypeTag(zcu)) {
23354 .vector, .array => return block.addTyOp(.splat, dest_ty, scalar),23353 .vector, .array => return block.addTyOp(.splat, dest_ty, scalar),
test/behavior/splat.zig+19
...@@ -160,3 +160,22 @@ test "vector @splat" {...@@ -160,3 +160,22 @@ test "vector @splat" {
160 try S.doTheTest();160 try S.doTheTest();
161 try comptime S.doTheTest();161 try comptime S.doTheTest();
162}162}
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/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'