Skip to content

Commit 7384097

Browse files
committed
sema: The builtin function @max/@min support incompatible arbitrary integer types
In normal arithmatic operations on two or more integer operands, all intergers must be compatible with each other. But in the function @max/@min, this constraint need not be satisfied. For example, the expression `@min(@as(i32, -30), @as(u32, 42))` is a legal expression.
1 parent 5ad91a6 commit 7384097

File tree

2 files changed

+221
-11
lines changed

2 files changed

+221
-11
lines changed

src/Sema.zig

+111-11
Original file line numberDiff line numberDiff line change
@@ -24626,6 +24626,7 @@ fn checkSimdBinOp(
2462624626
sema: *Sema,
2462724627
block: *Block,
2462824628
src: LazySrcLoc,
24629+
comptime air_tag: Air.Inst.Tag,
2462924630
uncasted_lhs: Air.Inst.Ref,
2463024631
uncasted_rhs: Air.Inst.Ref,
2463124632
lhs_src: LazySrcLoc,
@@ -24638,11 +24639,11 @@ fn checkSimdBinOp(
2463824639

2463924640
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
2464024641
const vec_len: ?usize = if (lhs_ty.zigTypeTag(zcu) == .vector) lhs_ty.vectorLen(zcu) else null;
24641-
const result_ty = try sema.resolvePeerTypes(block, src, &.{ uncasted_lhs, uncasted_rhs }, .{
24642+
const result_ty = try sema.resolvePeerTypesWithOp(block, src, air_tag, &.{ uncasted_lhs, uncasted_rhs }, .{
2464224643
.override = &[_]?LazySrcLoc{ lhs_src, rhs_src },
2464324644
});
24644-
const lhs = try sema.coerce(block, result_ty, uncasted_lhs, lhs_src);
24645-
const rhs = try sema.coerce(block, result_ty, uncasted_rhs, rhs_src);
24645+
const lhs = try sema.coerceWithOp(block, result_ty, uncasted_lhs, lhs_src, air_tag);
24646+
const rhs = try sema.coerceWithOp(block, result_ty, uncasted_rhs, rhs_src, air_tag);
2464624647

2464724648
return SimdBinOp{
2464824649
.len = vec_len,
@@ -25993,7 +25994,7 @@ fn analyzeMinMax(
2599325994
continue;
2599425995
};
2599525996

25996-
const simd_op = try sema.checkSimdBinOp(block, src, cur, operand, cur_minmax_src, operand_src);
25997+
const simd_op = try sema.checkSimdBinOp(block, src, air_tag, cur, operand, cur_minmax_src, operand_src);
2599725998
const cur_val = try sema.resolveLazyValue(simd_op.lhs_val.?); // cur_minmax is comptime-known
2599825999
const operand_val = try sema.resolveLazyValue(simd_op.rhs_val.?); // we checked the operand was resolvable above
2599926000

@@ -26085,7 +26086,7 @@ fn analyzeMinMax(
2608526086
const lhs_src = cur_minmax_src;
2608626087
const rhs = operands[idx];
2608726088
const rhs_src = operand_srcs[idx];
26088-
const simd_op = try sema.checkSimdBinOp(block, src, lhs, rhs, lhs_src, rhs_src);
26089+
const simd_op = try sema.checkSimdBinOp(block, src, air_tag, lhs, rhs, lhs_src, rhs_src);
2608926090
if (known_undef) {
2609026091
cur_minmax = try pt.undefRef(simd_op.result_ty);
2609126092
} else {
@@ -29455,6 +29456,20 @@ pub fn coerce(
2945529456
};
2945629457
}
2945729458

29459+
pub fn coerceWithOp(
29460+
sema: *Sema,
29461+
block: *Block,
29462+
dest_ty_unresolved: Type,
29463+
inst: Air.Inst.Ref,
29464+
inst_src: LazySrcLoc,
29465+
op_tag: Air.Inst.Tag,
29466+
) CompileError!Air.Inst.Ref {
29467+
return sema.coerceExtra(block, dest_ty_unresolved, inst, inst_src, .{ .opt_op_tag = op_tag }) catch |err| switch (err) {
29468+
error.NotCoercible => unreachable,
29469+
else => |e| return e,
29470+
};
29471+
}
29472+
2945829473
const CoersionError = CompileError || error{
2945929474
/// When coerce is called recursively, this error should be returned instead of using `fail`
2946029475
/// to ensure correct types in compile errors.
@@ -29468,6 +29483,8 @@ const CoerceOpts = struct {
2946829483
is_ret: bool = false,
2946929484
/// Should coercion to comptime_int emit an error message.
2947029485
no_cast_to_comptime_int: bool = false,
29486+
/// The tag of operator in which the coerce is called
29487+
opt_op_tag: ?Air.Inst.Tag = null,
2947129488

2947229489
param_src: struct {
2947329490
func_inst: Air.Inst.Ref = .none,
@@ -29854,6 +29871,13 @@ fn coerceExtra(
2985429871
if (maybe_inst_val) |val| {
2985529872
// comptime-known integer to other number
2985629873
if (!(try sema.intFitsInType(val, dest_ty, null))) {
29874+
if (opts.opt_op_tag) |op_tag| {
29875+
switch (op_tag) {
29876+
.min => return Air.internedToRef((try dest_ty.maxInt(pt, dest_ty)).toIntern()),
29877+
.max => return pt.intRef(dest_ty, 0),
29878+
else => {},
29879+
}
29880+
}
2985729881
if (!opts.report_err) return error.NotCoercible;
2985829882
return sema.fail(block, inst_src, "type '{}' cannot represent integer value '{}'", .{ dest_ty.fmt(pt), val.fmtValueSema(pt, sema) });
2985929883
}
@@ -29881,6 +29905,30 @@ fn coerceExtra(
2988129905
try sema.requireRuntimeBlock(block, inst_src, null);
2988229906
return block.addTyOp(.intcast, dest_ty, inst);
2988329907
}
29908+
29909+
if (opts.opt_op_tag) |op_tag| {
29910+
switch (op_tag) {
29911+
.min => {
29912+
if (src_info.signedness != dst_info.signedness and dst_info.signedness == .signed) {
29913+
std.debug.assert(dst_info.bits <= src_info.bits);
29914+
try sema.requireRuntimeBlock(block, inst_src, null);
29915+
const max_int_inst = Air.internedToRef((try dest_ty.maxInt(pt, inst_ty)).toIntern());
29916+
const min_inst = try block.addBinOp(.min, inst, max_int_inst);
29917+
return block.addTyOp(.intcast, dest_ty, min_inst);
29918+
}
29919+
},
29920+
.max => {
29921+
if (src_info.signedness != dst_info.signedness and dst_info.signedness == .unsigned) {
29922+
std.debug.assert(dst_info.bits >= src_info.bits);
29923+
try sema.requireRuntimeBlock(block, inst_src, null);
29924+
const zero_inst = try pt.intRef(inst_ty, 0);
29925+
const max_inst = try block.addBinOp(.max, inst, zero_inst);
29926+
return block.addTyOp(.intcast, dest_ty, max_inst);
29927+
}
29928+
},
29929+
else => {},
29930+
}
29931+
}
2988429932
},
2988529933
else => {},
2988629934
},
@@ -30033,9 +30081,9 @@ fn coerceExtra(
3003330081
}
3003430082
}
3003530083

30036-
return sema.coerceArrayLike(block, dest_ty, dest_ty_src, inst, inst_src);
30084+
return sema.coerceArrayLike(block, dest_ty, dest_ty_src, inst, inst_src, opts.opt_op_tag);
3003730085
},
30038-
.vector => return sema.coerceArrayLike(block, dest_ty, dest_ty_src, inst, inst_src),
30086+
.vector => return sema.coerceArrayLike(block, dest_ty, dest_ty_src, inst, inst_src, opts.opt_op_tag),
3003930087
.@"struct" => {
3004030088
if (inst_ty.isTuple(zcu)) {
3004130089
return sema.coerceTupleToArray(block, dest_ty, dest_ty_src, inst, inst_src);
@@ -30044,7 +30092,7 @@ fn coerceExtra(
3004430092
else => {},
3004530093
},
3004630094
.vector => switch (inst_ty.zigTypeTag(zcu)) {
30047-
.array, .vector => return sema.coerceArrayLike(block, dest_ty, dest_ty_src, inst, inst_src),
30095+
.array, .vector => return sema.coerceArrayLike(block, dest_ty, dest_ty_src, inst, inst_src, opts.opt_op_tag),
3004830096
.@"struct" => {
3004930097
if (inst_ty.isTuple(zcu)) {
3005030098
return sema.coerceTupleToArray(block, dest_ty, dest_ty_src, inst, inst_src);
@@ -31927,6 +31975,7 @@ fn coerceArrayLike(
3192731975
dest_ty_src: LazySrcLoc,
3192831976
inst: Air.Inst.Ref,
3192931977
inst_src: LazySrcLoc,
31978+
opt_op_tag: ?Air.Inst.Tag,
3193031979
) !Air.Inst.Ref {
3193131980
const pt = sema.pt;
3193231981
const zcu = pt.zcu;
@@ -31975,6 +32024,31 @@ fn coerceArrayLike(
3197532024
try sema.requireRuntimeBlock(block, inst_src, null);
3197632025
return block.addTyOp(.intcast, dest_ty, inst);
3197732026
}
32027+
32028+
if (opt_op_tag) |op_tag| {
32029+
switch (op_tag) {
32030+
.min => {
32031+
if (src_info.signedness != dst_info.signedness and dst_info.signedness == .signed) {
32032+
std.debug.assert(dst_info.bits <= src_info.bits);
32033+
try sema.requireRuntimeBlock(block, inst_src, null);
32034+
const max_int_inst = Air.internedToRef((try dest_ty.maxInt(pt, inst_ty)).toIntern());
32035+
const min_inst = try block.addBinOp(.min, inst, max_int_inst);
32036+
return block.addTyOp(.intcast, dest_ty, min_inst);
32037+
}
32038+
},
32039+
.max => {
32040+
if (src_info.signedness != dst_info.signedness and dst_info.signedness == .unsigned) {
32041+
std.debug.assert(dst_info.bits >= src_info.bits);
32042+
try sema.requireRuntimeBlock(block, inst_src, null);
32043+
const zeros = try sema.splat(inst_ty, try pt.intValue(inst_elem_ty, 0));
32044+
const zero_inst = Air.internedToRef(zeros.toIntern());
32045+
const max_inst = try block.addBinOp(.max, inst, zero_inst);
32046+
return block.addTyOp(.intcast, dest_ty, max_inst);
32047+
}
32048+
},
32049+
else => {},
32050+
}
32051+
}
3197832052
},
3197932053
.float => if (inst_elem_ty.isRuntimeFloat()) {
3198032054
// float widening
@@ -31998,7 +32072,10 @@ fn coerceArrayLike(
3199832072
const src = inst_src; // TODO better source location
3199932073
const elem_src = inst_src; // TODO better source location
3200032074
const elem_ref = try sema.elemValArray(block, src, inst_src, inst, elem_src, index_ref, true);
32001-
const coerced = try sema.coerce(block, dest_elem_ty, elem_ref, elem_src);
32075+
const coerced = if (opt_op_tag) |op_tag|
32076+
try sema.coerceWithOp(block, dest_elem_ty, elem_ref, elem_src, op_tag)
32077+
else
32078+
try sema.coerce(block, dest_elem_ty, elem_ref, elem_src);
3200232079
ref.* = coerced;
3200332080
if (runtime_src == null) {
3200432081
if (try sema.resolveValue(coerced)) |elem_val| {
@@ -34073,6 +34150,17 @@ fn resolvePeerTypes(
3407334150
src: LazySrcLoc,
3407434151
instructions: []const Air.Inst.Ref,
3407534152
candidate_srcs: PeerTypeCandidateSrc,
34153+
) !Type {
34154+
return resolvePeerTypesWithOp(sema, block, src, null, instructions, candidate_srcs);
34155+
}
34156+
34157+
fn resolvePeerTypesWithOp(
34158+
sema: *Sema,
34159+
block: *Block,
34160+
src: LazySrcLoc,
34161+
comptime opt_op_tag: ?Air.Inst.Tag,
34162+
instructions: []const Air.Inst.Ref,
34163+
candidate_srcs: PeerTypeCandidateSrc,
3407634164
) !Type {
3407734165
switch (instructions.len) {
3407834166
0 => return Type.noreturn,
@@ -34099,7 +34187,7 @@ fn resolvePeerTypes(
3409934187
val.* = try sema.resolveValue(inst);
3410034188
}
3410134189

34102-
switch (try sema.resolvePeerTypesInner(block, src, peer_tys, peer_vals)) {
34190+
switch (try sema.resolvePeerTypesInner(block, src, opt_op_tag, peer_tys, peer_vals)) {
3410334191
.success => |ty| return ty,
3410434192
else => |result| {
3410534193
const msg = try result.report(sema, block, src, instructions, candidate_srcs);
@@ -34112,6 +34200,7 @@ fn resolvePeerTypesInner(
3411234200
sema: *Sema,
3411334201
block: *Block,
3411434202
src: LazySrcLoc,
34203+
comptime opt_op_tag: ?Air.Inst.Tag,
3411534204
peer_tys: []?Type,
3411634205
peer_vals: []?Value,
3411734206
) !PeerResolveResult {
@@ -34197,6 +34286,7 @@ fn resolvePeerTypesInner(
3419734286
const final_payload = switch (try sema.resolvePeerTypesInner(
3419834287
block,
3419934288
src,
34289+
opt_op_tag,
3420034290
peer_tys,
3420134291
peer_vals,
3420234292
)) {
@@ -34235,6 +34325,7 @@ fn resolvePeerTypesInner(
3423534325
const child_ty = switch (try sema.resolvePeerTypesInner(
3423634326
block,
3423734327
src,
34328+
opt_op_tag,
3423834329
peer_tys,
3423934330
peer_vals,
3424034331
)) {
@@ -34384,6 +34475,7 @@ fn resolvePeerTypesInner(
3438434475
const child_ty = switch (try sema.resolvePeerTypesInner(
3438534476
block,
3438634477
src,
34478+
opt_op_tag,
3438734479
peer_tys,
3438834480
peer_vals,
3438934481
)) {
@@ -34989,6 +35081,14 @@ fn resolvePeerTypesInner(
3498935081
return .{ .success = peer_tys[idx_signed.?].? };
3499035082
}
3499135083

35084+
if (opt_op_tag) |op_tag| {
35085+
switch (op_tag) {
35086+
.min => return .{ .success = peer_tys[idx_signed.?].? },
35087+
.max => return .{ .success = peer_tys[idx_unsigned.?].? },
35088+
else => {},
35089+
}
35090+
}
35091+
3499235092
// TODO: this is for compatibility with legacy behavior. Before this version of PTR was
3499335093
// implemented, the algorithm very often returned false positives, with the expectation
3499435094
// that you'd just hit a coercion error later. One of these was that for integers, the
@@ -35107,7 +35207,7 @@ fn resolvePeerTypesInner(
3510735207
}
3510835208

3510935209
// Resolve field type recursively
35110-
field_ty.* = switch (try sema.resolvePeerTypesInner(block, src, sub_peer_tys, sub_peer_vals)) {
35210+
field_ty.* = switch (try sema.resolvePeerTypesInner(block, src, opt_op_tag, sub_peer_tys, sub_peer_vals)) {
3511135211
.success => |ty| ty.toIntern(),
3511235212
else => |result| {
3511335213
const result_buf = try sema.arena.create(PeerResolveResult);

test/behavior/maximum_minimum.zig

+110
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,116 @@ test "@min/@max more than two vector arguments" {
183183
try expectEqual(@Vector(2, u32){ 5, 2 }, @max(x, y, z));
184184
}
185185

186+
test "@min/@max with incompatible arbitrary integer types" {
187+
const x: i32 = -30;
188+
const y: u32 = 0x8000_0010;
189+
const z: u36 = 0x2_0000_0010;
190+
191+
const M = struct {
192+
fn min(lhs: i32, rhs: u32) i32 {
193+
return @min(lhs, rhs);
194+
}
195+
196+
fn min3(fst: i32, snd: u32, thrd: u36) i32 {
197+
return @min(fst, snd, thrd);
198+
}
199+
200+
fn max(lhs: i32, rhs: u32) u32 {
201+
return @max(lhs, rhs);
202+
}
203+
204+
fn max3(fst: i32, snd: u32, thrd: u36) u36 {
205+
return @max(fst, snd, thrd);
206+
}
207+
};
208+
209+
// test min for comptime value
210+
const min = @min(x, y);
211+
try expectEqual(i6, @TypeOf(min));
212+
try expectEqual(-30, min);
213+
const min3 = @min(x, y, z);
214+
try expectEqual(i6, @TypeOf(min3));
215+
try expectEqual(-30, min3);
216+
217+
// test min for runtime value
218+
const m_min = M.min(x, y);
219+
try expectEqual(-30, m_min);
220+
const m_min3 = M.min3(x, y, z);
221+
try expectEqual(-30, m_min3);
222+
223+
// test max for comptime value
224+
const max = @max(x, y);
225+
try expectEqual(u32, @TypeOf(max));
226+
try expectEqual(0x8000_0010, max);
227+
const max3 = @max(x, y, z);
228+
try expectEqual(u34, @TypeOf(max3));
229+
try expectEqual(0x2_0000_0010, max3);
230+
231+
// test max for runtime value
232+
const m_max = M.max(x, y);
233+
try expectEqual(0x8000_0010, m_max);
234+
const m_max3 = M.max3(x, y, z);
235+
try expectEqual(0x2_0000_0010, m_max3);
236+
}
237+
238+
test "@min/@max vector with incompatible arbitrary integer types" {
239+
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
240+
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
241+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
242+
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
243+
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
244+
245+
const x: @Vector(2, i16) = @splat(-30);
246+
const y: @Vector(2, u16) = @splat(0x8010);
247+
const z: @Vector(2, u32) = @splat(0x2_0010);
248+
249+
const M = struct {
250+
fn min(lhs: @Vector(2, i16), rhs: @Vector(2, u16)) @Vector(2, i16) {
251+
return @min(lhs, rhs);
252+
}
253+
254+
fn min3(fst: @Vector(2, i16), snd: @Vector(2, u16), thrd: @Vector(2, u32)) @Vector(2, i16) {
255+
return @min(fst, snd, thrd);
256+
}
257+
258+
fn max(lhs: @Vector(2, i16), rhs: @Vector(2, u16)) @Vector(2, u16) {
259+
return @max(lhs, rhs);
260+
}
261+
262+
fn max3(fst: @Vector(2, i16), snd: @Vector(2, u16), thrd: @Vector(2, u32)) @Vector(2, u32) {
263+
return @max(fst, snd, thrd);
264+
}
265+
};
266+
267+
// test min for comptime value
268+
const min = @min(x, y);
269+
try expectEqual(@Vector(2, i6), @TypeOf(min));
270+
try expectEqual(@as(@Vector(2, i16), @splat(-30)), min);
271+
const min3 = @min(x, y, z);
272+
try expectEqual(@Vector(2, i6), @TypeOf(min3));
273+
try expectEqual(@as(@Vector(2, i16), @splat(-30)), min3);
274+
275+
// test min for runtime value
276+
const m_min = M.min(x, y);
277+
try expectEqual(@as(@Vector(2, i16), @splat(-30)), m_min);
278+
const m_min3 = M.min3(x, y, z);
279+
try expectEqual(@as(@Vector(2, i16), @splat(-30)), m_min3);
280+
281+
// test max for comptime value
282+
const max = @max(x, y);
283+
try expectEqual(@Vector(2, u16), @TypeOf(max));
284+
try expectEqual(@as(@Vector(2, u16), @splat(0x8010)), max);
285+
const max3 = @max(x, y, z);
286+
try expectEqual(@Vector(2, u18), @TypeOf(max3));
287+
try expectEqual(@as(@Vector(2, u18), @splat(0x2_0010)), max3);
288+
289+
// test max for runtime value
290+
const m_max = M.max(x, y);
291+
try expectEqual(@as(@Vector(2, u16), @splat(0x8010)), m_max);
292+
const m_max3 = M.max3(x, y, z);
293+
try expectEqual(@as(@Vector(2, u32), @splat(0x2_0010)), m_max3);
294+
}
295+
186296
test "@min/@max notices bounds" {
187297
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
188298
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO

0 commit comments

Comments
 (0)