authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2023-04-30 10:30:40+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-05-02 16:46:27-07:00
log152c7b1885a0ed8e5a2435ef7b51568b357eaea4
treebbc028d9f181ae476121fdc5b71b843887f0b237
parente9cbdb2cfd6ccc51b50e97e7df419451aa8a1c95

Implement multi-argument @min/@max and notice bounds

Resolves: #14039

8 files changed, 388 insertions(+), 69 deletions(-)

src/AstGen.zig+44-19
...@@ -7907,6 +7907,48 @@ fn typeOf(...@@ -7907,6 +7907,48 @@ fn typeOf(
7907 return rvalue(gz, ri, typeof_inst, node);7907 return rvalue(gz, ri, typeof_inst, node);
7908}7908}
79097909
7910fn minMax(
7911 gz: *GenZir,
7912 scope: *Scope,
7913 ri: ResultInfo,
7914 node: Ast.Node.Index,
7915 args: []const Ast.Node.Index,
7916 comptime op: enum { min, max },
7917) InnerError!Zir.Inst.Ref {
7918 const astgen = gz.astgen;
7919 if (args.len < 2) {
7920 return astgen.failNode(node, "expected at least 2 arguments, found 0", .{});
7921 }
7922 if (args.len == 2) {
7923 const tag: Zir.Inst.Tag = switch (op) {
7924 .min => .min,
7925 .max => .max,
7926 };
7927 const a = try expr(gz, scope, .{ .rl = .none }, args[0]);
7928 const b = try expr(gz, scope, .{ .rl = .none }, args[1]);
7929 const result = try gz.addPlNode(tag, node, Zir.Inst.Bin{
7930 .lhs = a,
7931 .rhs = b,
7932 });
7933 return rvalue(gz, ri, result, node);
7934 }
7935 const payload_index = try addExtra(astgen, Zir.Inst.NodeMultiOp{
7936 .src_node = gz.nodeIndexToRelative(node),
7937 });
7938 var extra_index = try reserveExtra(gz.astgen, args.len);
7939 for (args) |arg| {
7940 const arg_ref = try expr(gz, scope, .{ .rl = .none }, arg);
7941 astgen.extra.items[extra_index] = @enumToInt(arg_ref);
7942 extra_index += 1;
7943 }
7944 const tag: Zir.Inst.Extended = switch (op) {
7945 .min => .min_multi,
7946 .max => .max_multi,
7947 };
7948 const result = try gz.addExtendedMultiOpPayloadIndex(tag, payload_index, args.len);
7949 return rvalue(gz, ri, result, node);
7950}
7951
7910fn builtinCall(7952fn builtinCall(
7911 gz: *GenZir,7953 gz: *GenZir,
7912 scope: *Scope,7954 scope: *Scope,
...@@ -7997,6 +8039,8 @@ fn builtinCall(...@@ -7997,6 +8039,8 @@ fn builtinCall(
7997 .TypeOf => return typeOf( gz, scope, ri, node, params),8039 .TypeOf => return typeOf( gz, scope, ri, node, params),
7998 .union_init => return unionInit(gz, scope, ri, node, params),8040 .union_init => return unionInit(gz, scope, ri, node, params),
7999 .c_import => return cImport( gz, scope, node, params[0]),8041 .c_import => return cImport( gz, scope, node, params[0]),
8042 .min => return minMax( gz, scope, ri, node, params, .min),
8043 .max => return minMax( gz, scope, ri, node, params, .max),
8000 // zig fmt: on8044 // zig fmt: on
80018045
8002 .@"export" => {8046 .@"export" => {
...@@ -8358,25 +8402,6 @@ fn builtinCall(...@@ -8358,25 +8402,6 @@ fn builtinCall(
8358 return rvalue(gz, ri, result, node);8402 return rvalue(gz, ri, result, node);
8359 },8403 },
83608404
8361 .max => {
8362 const a = try expr(gz, scope, .{ .rl = .none }, params[0]);
8363 const b = try expr(gz, scope, .{ .rl = .none }, params[1]);
8364 const result = try gz.addPlNode(.max, node, Zir.Inst.Bin{
8365 .lhs = a,
8366 .rhs = b,
8367 });
8368 return rvalue(gz, ri, result, node);
8369 },
8370 .min => {
8371 const a = try expr(gz, scope, .{ .rl = .none }, params[0]);
8372 const b = try expr(gz, scope, .{ .rl = .none }, params[1]);
8373 const result = try gz.addPlNode(.min, node, Zir.Inst.Bin{
8374 .lhs = a,
8375 .rhs = b,
8376 });
8377 return rvalue(gz, ri, result, node);
8378 },
8379
8380 .add_with_overflow => return overflowArithmetic(gz, scope, ri, node, params, .add_with_overflow),8405 .add_with_overflow => return overflowArithmetic(gz, scope, ri, node, params, .add_with_overflow),
8381 .sub_with_overflow => return overflowArithmetic(gz, scope, ri, node, params, .sub_with_overflow),8406 .sub_with_overflow => return overflowArithmetic(gz, scope, ri, node, params, .sub_with_overflow),
8382 .mul_with_overflow => return overflowArithmetic(gz, scope, ri, node, params, .mul_with_overflow),8407 .mul_with_overflow => return overflowArithmetic(gz, scope, ri, node, params, .mul_with_overflow),
src/BuiltinFn.zig+2-2
...@@ -608,7 +608,7 @@ pub const list = list: {...@@ -608,7 +608,7 @@ pub const list = list: {
608 "@max",608 "@max",
609 .{609 .{
610 .tag = .max,610 .tag = .max,
611 .param_count = 2,611 .param_count = null,
612 },612 },
613 },613 },
614 .{614 .{
...@@ -629,7 +629,7 @@ pub const list = list: {...@@ -629,7 +629,7 @@ pub const list = list: {
629 "@min",629 "@min",
630 .{630 .{
631 .tag = .min,631 .tag = .min,
632 .param_count = 2,632 .param_count = null,
633 },633 },
634 },634 },
635 .{635 .{
src/Sema.zig+201-40
...@@ -1137,6 +1137,8 @@ fn analyzeBodyInner(...@@ -1137,6 +1137,8 @@ fn analyzeBodyInner(
1137 .asm_expr => try sema.zirAsm( block, extended, true),1137 .asm_expr => try sema.zirAsm( block, extended, true),
1138 .typeof_peer => try sema.zirTypeofPeer( block, extended),1138 .typeof_peer => try sema.zirTypeofPeer( block, extended),
1139 .compile_log => try sema.zirCompileLog( extended),1139 .compile_log => try sema.zirCompileLog( extended),
1140 .min_multi => try sema.zirMinMaxMulti( block, extended, .min),
1141 .max_multi => try sema.zirMinMaxMulti( block, extended, .max),
1140 .add_with_overflow => try sema.zirOverflowArithmetic(block, extended, extended.opcode),1142 .add_with_overflow => try sema.zirOverflowArithmetic(block, extended, extended.opcode),
1141 .sub_with_overflow => try sema.zirOverflowArithmetic(block, extended, extended.opcode),1143 .sub_with_overflow => try sema.zirOverflowArithmetic(block, extended, extended.opcode),
1142 .mul_with_overflow => try sema.zirOverflowArithmetic(block, extended, extended.opcode),1144 .mul_with_overflow => try sema.zirOverflowArithmetic(block, extended, extended.opcode),
...@@ -12143,7 +12145,7 @@ fn zirShl(...@@ -12143,7 +12145,7 @@ fn zirShl(
12143 lhs_ty,12145 lhs_ty,
12144 try lhs_ty.maxInt(sema.arena, target),12146 try lhs_ty.maxInt(sema.arena, target),
12145 );12147 );
12146 const rhs_limited = try sema.analyzeMinMax(block, rhs_src, rhs, max_int, .min, rhs_src, rhs_src);12148 const rhs_limited = try sema.analyzeMinMax(block, rhs_src, .min, &.{ rhs, max_int }, &.{ rhs_src, rhs_src });
12147 break :rhs try sema.intCast(block, src, lhs_ty, rhs_src, rhs_limited, rhs_src, false);12149 break :rhs try sema.intCast(block, src, lhs_ty, rhs_src, rhs_limited, rhs_src, false);
12148 } else {12150 } else {
12149 break :rhs rhs;12151 break :rhs rhs;
...@@ -21752,64 +21754,223 @@ fn zirMinMax(...@@ -21752,64 +21754,223 @@ fn zirMinMax(
21752 const rhs = try sema.resolveInst(extra.rhs);21754 const rhs = try sema.resolveInst(extra.rhs);
21753 try sema.checkNumericType(block, lhs_src, sema.typeOf(lhs));21755 try sema.checkNumericType(block, lhs_src, sema.typeOf(lhs));
21754 try sema.checkNumericType(block, rhs_src, sema.typeOf(rhs));21756 try sema.checkNumericType(block, rhs_src, sema.typeOf(rhs));
21755 return sema.analyzeMinMax(block, src, lhs, rhs, air_tag, lhs_src, rhs_src);21757 return sema.analyzeMinMax(block, src, air_tag, &.{ lhs, rhs }, &.{ lhs_src, rhs_src });
21758}
21759
21760fn zirMinMaxMulti(
21761 sema: *Sema,
21762 block: *Block,
21763 extended: Zir.Inst.Extended.InstData,
21764 comptime air_tag: Air.Inst.Tag,
21765) CompileError!Air.Inst.Ref {
21766 const extra = sema.code.extraData(Zir.Inst.NodeMultiOp, extended.operand);
21767 const src_node = extra.data.src_node;
21768 const src = LazySrcLoc.nodeOffset(src_node);
21769 const operands = sema.code.refSlice(extra.end, extended.small);
21770
21771 const air_refs = try sema.arena.alloc(Air.Inst.Ref, operands.len);
21772 const operand_srcs = try sema.arena.alloc(LazySrcLoc, operands.len);
21773
21774 for (operands, air_refs, operand_srcs, 0..) |zir_ref, *air_ref, *op_src, i| {
21775 op_src.* = switch (i) {
21776 0 => .{ .node_offset_builtin_call_arg0 = src_node },
21777 1 => .{ .node_offset_builtin_call_arg1 = src_node },
21778 2 => .{ .node_offset_builtin_call_arg2 = src_node },
21779 3 => .{ .node_offset_builtin_call_arg3 = src_node },
21780 4 => .{ .node_offset_builtin_call_arg4 = src_node },
21781 5 => .{ .node_offset_builtin_call_arg5 = src_node },
21782 else => src, // TODO: better source location
21783 };
21784 air_ref.* = try sema.resolveInst(zir_ref);
21785 try sema.checkNumericType(block, op_src.*, sema.typeOf(air_ref.*));
21786 }
21787
21788 return sema.analyzeMinMax(block, src, air_tag, air_refs, operand_srcs);
21756}21789}
2175721790
21758fn analyzeMinMax(21791fn analyzeMinMax(
21759 sema: *Sema,21792 sema: *Sema,
21760 block: *Block,21793 block: *Block,
21761 src: LazySrcLoc,21794 src: LazySrcLoc,
21762 lhs: Air.Inst.Ref,
21763 rhs: Air.Inst.Ref,
21764 comptime air_tag: Air.Inst.Tag,21795 comptime air_tag: Air.Inst.Tag,
21765 lhs_src: LazySrcLoc,21796 operands: []const Air.Inst.Ref,
21766 rhs_src: LazySrcLoc,21797 operand_srcs: []const LazySrcLoc,
21767) CompileError!Air.Inst.Ref {21798) CompileError!Air.Inst.Ref {
21768 const simd_op = try sema.checkSimdBinOp(block, src, lhs, rhs, lhs_src, rhs_src);21799 assert(operands.len == operand_srcs.len);
21800 assert(operands.len > 0);
2176921801
21770 // TODO @max(max_int, undefined) should return max_int21802 if (operands.len == 1) return operands[0];
2177121803
21772 const runtime_src = if (simd_op.lhs_val) |lhs_val| rs: {21804 const mod = sema.mod;
21773 if (lhs_val.isUndef()) return sema.addConstUndef(simd_op.result_ty);21805 const target = mod.getTarget();
21806 const opFunc = switch (air_tag) {
21807 .min => Value.numberMin,
21808 .max => Value.numberMax,
21809 else => unreachable,
21810 };
2177421811
21775 const rhs_val = simd_op.rhs_val orelse break :rs rhs_src;21812 // First, find all comptime-known arguments, and get their min/max
21813 var runtime_known = try std.DynamicBitSet.initFull(sema.arena, operands.len);
21814 var cur_minmax: ?Air.Inst.Ref = null;
21815 var cur_minmax_src: LazySrcLoc = undefined; // defined if cur_minmax not null
21816 for (operands, operand_srcs, 0..) |operand, operand_src, operand_idx| {
21817 // Resolve the value now to avoid redundant calls to `checkSimdBinOp` - we'll have to call
21818 // it in the runtime path anyway since the result type may have been refined
21819 const uncasted_operand_val = (try sema.resolveMaybeUndefVal(operand)) orelse continue;
21820 if (cur_minmax) |cur| {
21821 const simd_op = try sema.checkSimdBinOp(block, src, cur, operand, cur_minmax_src, operand_src);
21822 const cur_val = simd_op.lhs_val.?; // cur_minmax is comptime-known
21823 const operand_val = simd_op.rhs_val.?; // we checked the operand was resolvable above
21824
21825 runtime_known.unset(operand_idx);
21826
21827 if (cur_val.isUndef()) continue; // result is also undef
21828 if (operand_val.isUndef()) {
21829 cur_minmax = try sema.addConstUndef(simd_op.result_ty);
21830 continue;
21831 }
2177621832
21777 if (rhs_val.isUndef()) return sema.addConstUndef(simd_op.result_ty);21833 try sema.resolveLazyValue(cur_val);
21834 try sema.resolveLazyValue(operand_val);
2177821835
21779 try sema.resolveLazyValue(lhs_val);21836 const vec_len = simd_op.len orelse {
21780 try sema.resolveLazyValue(rhs_val);21837 const result_val = opFunc(cur_val, operand_val, target);
21838 cur_minmax = try sema.addConstant(simd_op.result_ty, result_val);
21839 continue;
21840 };
21841 var lhs_buf: Value.ElemValueBuffer = undefined;
21842 var rhs_buf: Value.ElemValueBuffer = undefined;
21843 const elems = try sema.arena.alloc(Value, vec_len);
21844 for (elems, 0..) |*elem, i| {
21845 const lhs_elem_val = cur_val.elemValueBuffer(mod, i, &lhs_buf);
21846 const rhs_elem_val = operand_val.elemValueBuffer(mod, i, &rhs_buf);
21847 elem.* = opFunc(lhs_elem_val, rhs_elem_val, target);
21848 }
21849 cur_minmax = try sema.addConstant(
21850 simd_op.result_ty,
21851 try Value.Tag.aggregate.create(sema.arena, elems),
21852 );
21853 } else {
21854 runtime_known.unset(operand_idx);
21855 cur_minmax = try sema.addConstant(sema.typeOf(operand), uncasted_operand_val);
21856 cur_minmax_src = operand_src;
21857 }
21858 }
21859
21860 const comptime_refined_ty: ?Type = if (cur_minmax) |ct_minmax_ref| refined: {
21861 // Refine the comptime-known result type based on the operation
21862 const val = (try sema.resolveMaybeUndefVal(ct_minmax_ref)).?;
21863 const orig_ty = sema.typeOf(ct_minmax_ref);
21864 const refined_ty = if (orig_ty.zigTypeTag() == .Vector) blk: {
21865 const elem_ty = orig_ty.childType();
21866 const len = orig_ty.vectorLen();
21867
21868 if (len == 0) break :blk orig_ty;
21869 if (elem_ty.isAnyFloat()) break :blk orig_ty; // can't refine floats
21870
21871 var cur_min: Value = try val.elemValue(mod, sema.arena, 0);
21872 var cur_max: Value = cur_min;
21873 for (1..len) |idx| {
21874 const elem_val = try val.elemValue(mod, sema.arena, idx);
21875 if (elem_val.isUndef()) break :blk orig_ty; // can't refine undef
21876 if (Value.order(elem_val, cur_min, target).compare(.lt)) cur_min = elem_val;
21877 if (Value.order(elem_val, cur_max, target).compare(.gt)) cur_max = elem_val;
21878 }
21879
21880 const refined_elem_ty = try Type.intFittingRange(target, sema.arena, cur_min, cur_max);
21881 break :blk try Type.vector(sema.arena, len, refined_elem_ty);
21882 } else blk: {
21883 if (orig_ty.isAnyFloat()) break :blk orig_ty; // can't refine floats
21884 if (val.isUndef()) break :blk orig_ty; // can't refine undef
21885 break :blk try Type.intFittingRange(target, sema.arena, val, val);
21886 };
21887
21888 // Apply the refined type to the current value - this isn't strictly necessary in the
21889 // runtime case since we'll refine again afterwards, but keeping things as small as possible
21890 // will allow us to emit more optimal AIR (if all the runtime operands have smaller types
21891 // than the non-refined comptime type).
21892 if (!refined_ty.eql(orig_ty, mod)) {
21893 if (std.debug.runtime_safety) {
21894 assert(try sema.intFitsInType(val, refined_ty, null));
21895 }
21896 cur_minmax = try sema.addConstant(refined_ty, val);
21897 }
21898
21899 break :refined refined_ty;
21900 } else null;
21901
21902 const runtime_idx = runtime_known.findFirstSet() orelse return cur_minmax.?;
21903 const runtime_src = operand_srcs[runtime_idx];
21904 try sema.requireRuntimeBlock(block, src, runtime_src);
21905
21906 // Now, iterate over runtime operands, emitting a min/max instruction for each. We'll refine the
21907 // type again at the end, based on the comptime-known bound.
21908
21909 // If the comptime-known part is undef we can avoid emitting actual instructions later
21910 const known_undef = if (cur_minmax) |operand| blk: {
21911 const val = (try sema.resolveMaybeUndefVal(operand)).?;
21912 break :blk val.isUndef();
21913 } else false;
21914
21915 if (cur_minmax == null) {
21916 // No comptime operands - use the first operand as the starting value
21917 assert(runtime_idx == 0);
21918 cur_minmax = operands[0];
21919 cur_minmax_src = runtime_src;
21920 runtime_known.unset(0); // don't look at this operand in the loop below
21921 }
21922
21923 var it = runtime_known.iterator(.{});
21924 while (it.next()) |idx| {
21925 const lhs = cur_minmax.?;
21926 const lhs_src = cur_minmax_src;
21927 const rhs = operands[idx];
21928 const rhs_src = operand_srcs[idx];
21929 const simd_op = try sema.checkSimdBinOp(block, src, lhs, rhs, lhs_src, rhs_src);
21930 if (known_undef) {
21931 cur_minmax = try sema.addConstant(simd_op.result_ty, Value.undef);
21932 } else {
21933 cur_minmax = try block.addBinOp(air_tag, simd_op.lhs, simd_op.rhs);
21934 }
21935 }
21936
21937 if (comptime_refined_ty) |comptime_ty| refine: {
21938 // Finally, refine the type based on the comptime-known bound.
21939 if (known_undef) break :refine; // can't refine undef
21940 const unrefined_ty = sema.typeOf(cur_minmax.?);
21941 const is_vector = unrefined_ty.zigTypeTag() == .Vector;
21942 const comptime_elem_ty = if (is_vector) comptime_ty.childType() else comptime_ty;
21943 const unrefined_elem_ty = if (is_vector) unrefined_ty.childType() else unrefined_ty;
21944
21945 if (unrefined_elem_ty.isAnyFloat()) break :refine; // we can't refine floats
2178121946
21782 const opFunc = switch (air_tag) {21947 // Compute the final bounds based on the runtime type and the comptime-known bound type
21783 .min => Value.numberMin,21948 const min_val = switch (air_tag) {
21784 .max => Value.numberMax,21949 .min => try unrefined_elem_ty.minInt(sema.arena, target),
21950 .max => try comptime_elem_ty.minInt(sema.arena, target), // @max(ct, rt) >= ct
21785 else => unreachable,21951 else => unreachable,
21786 };21952 };
21787 const target = sema.mod.getTarget();21953 const max_val = switch (air_tag) {
21788 const vec_len = simd_op.len orelse {21954 .min => try comptime_elem_ty.maxInt(sema.arena, target), // @min(ct, rt) <= ct
21789 const result_val = opFunc(lhs_val, rhs_val, target);21955 .max => try unrefined_elem_ty.maxInt(sema.arena, target),
21790 return sema.addConstant(simd_op.result_ty, result_val);21956 else => unreachable,
21791 };21957 };
21792 var lhs_buf: Value.ElemValueBuffer = undefined;21958
21793 var rhs_buf: Value.ElemValueBuffer = undefined;21959 // Find the smallest type which can contain these bounds
21794 const elems = try sema.arena.alloc(Value, vec_len);21960 const final_elem_ty = try Type.intFittingRange(target, sema.arena, min_val, max_val);
21795 for (elems, 0..) |*elem, i| {21961
21796 const lhs_elem_val = lhs_val.elemValueBuffer(sema.mod, i, &lhs_buf);21962 const final_ty = if (is_vector)
21797 const rhs_elem_val = rhs_val.elemValueBuffer(sema.mod, i, &rhs_buf);21963 try Type.vector(sema.arena, unrefined_ty.vectorLen(), final_elem_ty)
21798 elem.* = opFunc(lhs_elem_val, rhs_elem_val, target);21964 else
21799 }21965 final_elem_ty;
21800 return sema.addConstant(21966
21801 simd_op.result_ty,21967 if (!final_ty.eql(unrefined_ty, mod)) {
21802 try Value.Tag.aggregate.create(sema.arena, elems),21968 // We've reduced the type - cast the result down
21803 );21969 return block.addTyOp(.intcast, final_ty, cur_minmax.?);
21804 } else rs: {
21805 if (simd_op.rhs_val) |rhs_val| {
21806 if (rhs_val.isUndef()) return sema.addConstUndef(simd_op.result_ty);
21807 }21970 }
21808 break :rs lhs_src;21971 }
21809 };
2181021972
21811 try sema.requireRuntimeBlock(block, src, runtime_src);21973 return cur_minmax.?;
21812 return block.addBinOp(air_tag, simd_op.lhs, simd_op.rhs);
21813}21974}
2181421975
21815fn upgradeToArrayPtr(sema: *Sema, block: *Block, ptr: Air.Inst.Ref, len: u64) !Air.Inst.Ref {21976fn upgradeToArrayPtr(sema: *Sema, block: *Block, ptr: Air.Inst.Ref, len: u64) !Air.Inst.Ref {
src/Zir.zig+13-3
...@@ -927,10 +927,10 @@ pub const Inst = struct {...@@ -927,10 +927,10 @@ pub const Inst = struct {
927 /// Implements the `@memset` builtin.927 /// Implements the `@memset` builtin.
928 /// Uses the `pl_node` union field with payload `Bin`.928 /// Uses the `pl_node` union field with payload `Bin`.
929 memset,929 memset,
930 /// Implements the `@min` builtin.930 /// Implements the `@min` builtin for 2 args.
931 /// Uses the `pl_node` union field with payload `Bin`931 /// Uses the `pl_node` union field with payload `Bin`
932 min,932 min,
933 /// Implements the `@max` builtin.933 /// Implements the `@max` builtin for 2 args.
934 /// Uses the `pl_node` union field with payload `Bin`934 /// Uses the `pl_node` union field with payload `Bin`
935 max,935 max,
936 /// Implements the `@cImport` builtin.936 /// Implements the `@cImport` builtin.
...@@ -1905,10 +1905,20 @@ pub const Inst = struct {...@@ -1905,10 +1905,20 @@ pub const Inst = struct {
1905 compile_log,1905 compile_log,
1906 /// The builtin `@TypeOf` which returns the type after Peer Type Resolution1906 /// The builtin `@TypeOf` which returns the type after Peer Type Resolution
1907 /// of one or more params.1907 /// of one or more params.
1908 /// `operand` is payload index to `NodeMultiOp`.1908 /// `operand` is payload index to `TypeOfPeer`.
1909 /// `small` is `operands_len`.1909 /// `small` is `operands_len`.
1910 /// The AST node is the builtin call.1910 /// The AST node is the builtin call.
1911 typeof_peer,1911 typeof_peer,
1912 /// Implements the `@min` builtin for more than 2 args.
1913 /// `operand` is payload index to `NodeMultiOp`.
1914 /// `small` is `operands_len`.
1915 /// The AST node is the builtin call.
1916 min_multi,
1917 /// Implements the `@max` builtin for more than 2 args.
1918 /// `operand` is payload index to `NodeMultiOp`.
1919 /// `small` is `operands_len`.
1920 /// The AST node is the builtin call.
1921 max_multi,
1912 /// Implements the `@addWithOverflow` builtin.1922 /// Implements the `@addWithOverflow` builtin.
1913 /// `operand` is payload index to `BinNode`.1923 /// `operand` is payload index to `BinNode`.
1914 /// `small` is unused.1924 /// `small` is unused.
src/arch/x86_64/CodeGen.zig+4-4
...@@ -4298,7 +4298,7 @@ fn packedLoad(self: *Self, dst_mcv: MCValue, ptr_ty: Type, ptr_mcv: MCValue) Inn...@@ -4298,7 +4298,7 @@ fn packedLoad(self: *Self, dst_mcv: MCValue, ptr_ty: Type, ptr_mcv: MCValue) Inn
42984298
4299 const val_ty = ptr_info.pointee_type;4299 const val_ty = ptr_info.pointee_type;
4300 const val_abi_size = @intCast(u32, val_ty.abiSize(self.target.*));4300 const val_abi_size = @intCast(u32, val_ty.abiSize(self.target.*));
4301 const limb_abi_size = @min(val_abi_size, 8);4301 const limb_abi_size: u32 = @min(val_abi_size, 8);
4302 const limb_abi_bits = limb_abi_size * 8;4302 const limb_abi_bits = limb_abi_size * 8;
4303 const val_byte_off = @intCast(i32, ptr_info.bit_offset / limb_abi_bits * limb_abi_size);4303 const val_byte_off = @intCast(i32, ptr_info.bit_offset / limb_abi_bits * limb_abi_size);
4304 const val_bit_off = ptr_info.bit_offset % limb_abi_bits;4304 const val_bit_off = ptr_info.bit_offset % limb_abi_bits;
...@@ -4434,7 +4434,7 @@ fn packedStore(self: *Self, ptr_ty: Type, ptr_mcv: MCValue, src_mcv: MCValue) In...@@ -4434,7 +4434,7 @@ fn packedStore(self: *Self, ptr_ty: Type, ptr_mcv: MCValue, src_mcv: MCValue) In
4434 const ptr_info = ptr_ty.ptrInfo().data;4434 const ptr_info = ptr_ty.ptrInfo().data;
4435 const src_ty = ptr_ty.childType();4435 const src_ty = ptr_ty.childType();
44364436
4437 const limb_abi_size = @min(ptr_info.host_size, 8);4437 const limb_abi_size: u16 = @min(ptr_info.host_size, 8);
4438 const limb_abi_bits = limb_abi_size * 8;4438 const limb_abi_bits = limb_abi_size * 8;
44394439
4440 const src_bit_size = src_ty.bitSize(self.target.*);4440 const src_bit_size = src_ty.bitSize(self.target.*);
...@@ -4652,7 +4652,7 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void {...@@ -4652,7 +4652,7 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void {
4652 }4652 }
46534653
4654 const field_abi_size = @intCast(u32, field_ty.abiSize(self.target.*));4654 const field_abi_size = @intCast(u32, field_ty.abiSize(self.target.*));
4655 const limb_abi_size = @min(field_abi_size, 8);4655 const limb_abi_size: u32 = @min(field_abi_size, 8);
4656 const limb_abi_bits = limb_abi_size * 8;4656 const limb_abi_bits = limb_abi_size * 8;
4657 const field_byte_off = @intCast(i32, field_off / limb_abi_bits * limb_abi_size);4657 const field_byte_off = @intCast(i32, field_off / limb_abi_bits * limb_abi_size);
4658 const field_bit_off = field_off % limb_abi_bits;4658 const field_bit_off = field_off % limb_abi_bits;
...@@ -5875,7 +5875,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, ty: Type, dst_mcv: MCValue, s...@@ -5875,7 +5875,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, ty: Type, dst_mcv: MCValue, s
5875 },5875 },
5876 .memory, .indirect, .load_got, .load_direct, .load_tlv, .load_frame => {5876 .memory, .indirect, .load_got, .load_direct, .load_tlv, .load_frame => {
5877 const OpInfo = ?struct { addr_reg: Register, addr_lock: RegisterLock };5877 const OpInfo = ?struct { addr_reg: Register, addr_lock: RegisterLock };
5878 const limb_abi_size = @min(abi_size, 8);5878 const limb_abi_size: u32 = @min(abi_size, 8);
58795879
5880 const dst_info: OpInfo = switch (dst_mcv) {5880 const dst_info: OpInfo = switch (dst_mcv) {
5881 else => unreachable,5881 else => unreachable,
src/print_zir.zig+2
...@@ -482,6 +482,8 @@ const Writer = struct {...@@ -482,6 +482,8 @@ const Writer = struct {
482482
483 .compile_log => try self.writeNodeMultiOp(stream, extended),483 .compile_log => try self.writeNodeMultiOp(stream, extended),
484 .typeof_peer => try self.writeTypeofPeer(stream, extended),484 .typeof_peer => try self.writeTypeofPeer(stream, extended),
485 .min_multi => try self.writeNodeMultiOp(stream, extended),
486 .max_multi => try self.writeNodeMultiOp(stream, extended),
485487
486 .select => try self.writeSelect(stream, extended),488 .select => try self.writeSelect(stream, extended),
487489
src/type.zig+66-1
...@@ -6723,7 +6723,17 @@ pub const Type = extern union {...@@ -6723,7 +6723,17 @@ pub const Type = extern union {
67236723
6724 pub fn smallestUnsignedInt(arena: Allocator, max: u64) !Type {6724 pub fn smallestUnsignedInt(arena: Allocator, max: u64) !Type {
6725 const bits = smallestUnsignedBits(max);6725 const bits = smallestUnsignedBits(max);
6726 return switch (bits) {6726 return intWithBits(arena, false, bits);
6727 }
6728
6729 pub fn intWithBits(arena: Allocator, sign: bool, bits: u16) !Type {
6730 return if (sign) switch (bits) {
6731 8 => initTag(.i8),
6732 16 => initTag(.i16),
6733 32 => initTag(.i32),
6734 64 => initTag(.i64),
6735 else => return Tag.int_signed.create(arena, bits),
6736 } else switch (bits) {
6727 1 => initTag(.u1),6737 1 => initTag(.u1),
6728 8 => initTag(.u8),6738 8 => initTag(.u8),
6729 16 => initTag(.u16),6739 16 => initTag(.u16),
...@@ -6733,6 +6743,61 @@ pub const Type = extern union {...@@ -6733,6 +6743,61 @@ pub const Type = extern union {
6733 };6743 };
6734 }6744 }
67356745
6746 /// Given a value representing an integer, returns the number of bits necessary to represent
6747 /// this value in an integer. If `sign` is true, returns the number of bits necessary in a
6748 /// twos-complement integer; otherwise in an unsigned integer.
6749 /// Asserts that `val` is not undef. If `val` is negative, asserts that `sign` is true.
6750 pub fn intBitsForValue(target: Target, val: Value, sign: bool) u16 {
6751 assert(!val.isUndef());
6752 switch (val.tag()) {
6753 .int_big_positive => {
6754 const limbs = val.castTag(.int_big_positive).?.data;
6755 const big: std.math.big.int.Const = .{ .limbs = limbs, .positive = true };
6756 return @intCast(u16, big.bitCountAbs() + @boolToInt(sign));
6757 },
6758 .int_big_negative => {
6759 const limbs = val.castTag(.int_big_negative).?.data;
6760 // Zero is still a possibility, in which case unsigned is fine
6761 for (limbs) |limb| {
6762 if (limb != 0) break;
6763 } else return 0; // val == 0
6764 assert(sign);
6765 const big: std.math.big.int.Const = .{ .limbs = limbs, .positive = false };
6766 return @intCast(u16, big.bitCountTwosComp());
6767 },
6768 .int_i64 => {
6769 const x = val.castTag(.int_i64).?.data;
6770 if (x >= 0) return smallestUnsignedBits(@intCast(u64, x));
6771 assert(sign);
6772 return smallestUnsignedBits(@intCast(u64, -x - 1)) + 1;
6773 },
6774 else => {
6775 const x = val.toUnsignedInt(target);
6776 return smallestUnsignedBits(x) + @boolToInt(sign);
6777 },
6778 }
6779 }
6780
6781 /// Returns the smallest possible integer type containing both `min` and `max`. Asserts that neither
6782 /// value is undef.
6783 /// TODO: if #3806 is implemented, this becomes trivial
6784 pub fn intFittingRange(target: Target, arena: Allocator, min: Value, max: Value) !Type {
6785 assert(!min.isUndef());
6786 assert(!max.isUndef());
6787
6788 if (std.debug.runtime_safety) {
6789 assert(Value.order(min, max, target).compare(.lte));
6790 }
6791
6792 const sign = min.orderAgainstZero() == .lt;
6793
6794 const min_val_bits = intBitsForValue(target, min, sign);
6795 const max_val_bits = intBitsForValue(target, max, sign);
6796 const bits = @max(min_val_bits, max_val_bits);
6797
6798 return intWithBits(arena, sign, bits);
6799 }
6800
6736 /// This is only used for comptime asserts. Bump this number when you make a change6801 /// This is only used for comptime asserts. Bump this number when you make a change
6737 /// to packed struct layout to find out all the places in the codebase you need to edit!6802 /// to packed struct layout to find out all the places in the codebase you need to edit!
6738 pub const packed_struct_layout_version = 2;6803 pub const packed_struct_layout_version = 2;
test/behavior/maximum_minimum.zig+56
...@@ -106,3 +106,59 @@ test "@min/@max on lazy values" {...@@ -106,3 +106,59 @@ test "@min/@max on lazy values" {
106 const size = @max(@sizeOf(A), @sizeOf(B));106 const size = @max(@sizeOf(A), @sizeOf(B));
107 try expect(size == @sizeOf(B));107 try expect(size == @sizeOf(B));
108}108}
109
110test "@min/@max more than two arguments" {
111 const x: u32 = 30;
112 const y: u32 = 10;
113 const z: u32 = 20;
114 try expectEqual(@as(u32, 10), @min(x, y, z));
115 try expectEqual(@as(u32, 30), @max(x, y, z));
116}
117
118test "@min/@max more than two vector arguments" {
119 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
120 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
121 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
122 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
123 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
124
125 const x: @Vector(2, u32) = .{ 3, 2 };
126 const y: @Vector(2, u32) = .{ 4, 1 };
127 const z: @Vector(2, u32) = .{ 5, 0 };
128 try expectEqual(@Vector(2, u32){ 3, 0 }, @min(x, y, z));
129 try expectEqual(@Vector(2, u32){ 5, 2 }, @max(x, y, z));
130}
131
132test "@min/@max notices bounds" {
133 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
134 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
135 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
136
137 var x: u16 = 20;
138 const y = 30;
139 var z: u32 = 100;
140 const min = @min(x, y, z);
141 const max = @max(x, y, z);
142 try expectEqual(x, min);
143 try expectEqual(u5, @TypeOf(min));
144 try expectEqual(z, max);
145 try expectEqual(u32, @TypeOf(max));
146}
147
148test "@min/@max notices vector bounds" {
149 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
150 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
151 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
152 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
153 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
154
155 var x: @Vector(2, u16) = .{ 140, 40 };
156 const y: @Vector(2, u64) = .{ 5, 100 };
157 var z: @Vector(2, u32) = .{ 10, 300 };
158 const min = @min(x, y, z);
159 const max = @max(x, y, z);
160 try expectEqual(@Vector(2, u32){ 5, 40 }, min);
161 try expectEqual(@Vector(2, u7), @TypeOf(min));
162 try expectEqual(@Vector(2, u32){ 140, 300 }, max);
163 try expectEqual(@Vector(2, u32), @TypeOf(max));
164}