| author | |
| committer | |
| log | 152c7b1885a0ed8e5a2435ef7b51568b357eaea4 |
| tree | bbc028d9f181ae476121fdc5b71b843887f0b237 |
| parent | e9cbdb2cfd6ccc51b50e97e7df419451aa8a1c95 |
Resolves: #140398 files changed, 388 insertions(+), 69 deletions(-)
src/AstGen.zig+44-19| ... | ... | @@ -7907,6 +7907,48 @@ fn typeOf( |
| 7907 | 7907 | return rvalue(gz, ri, typeof_inst, node); |
| 7908 | 7908 | } |
| 7909 | 7909 | |
| 7910 | fn 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 | ||
| 7910 | 7952 | fn builtinCall( |
| 7911 | 7953 | gz: *GenZir, |
| 7912 | 7954 | scope: *Scope, |
| ... | ... | @@ -7997,6 +8039,8 @@ fn builtinCall( |
| 7997 | 8039 | .TypeOf => return typeOf( gz, scope, ri, node, params), |
| 7998 | 8040 | .union_init => return unionInit(gz, scope, ri, node, params), |
| 7999 | 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 | 8044 | // zig fmt: on |
| 8001 | 8045 | |
| 8002 | 8046 | .@"export" => { |
| ... | ... | @@ -8358,25 +8402,6 @@ fn builtinCall( |
| 8358 | 8402 | return rvalue(gz, ri, result, node); |
| 8359 | 8403 | }, |
| 8360 | 8404 | |
| 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 | 8405 | .add_with_overflow => return overflowArithmetic(gz, scope, ri, node, params, .add_with_overflow), |
| 8381 | 8406 | .sub_with_overflow => return overflowArithmetic(gz, scope, ri, node, params, .sub_with_overflow), |
| 8382 | 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 | 608 | "@max", |
| 609 | 609 | .{ |
| 610 | 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 | 629 | "@min", |
| 630 | 630 | .{ |
| 631 | 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 | 1137 | .asm_expr => try sema.zirAsm( block, extended, true), |
| 1138 | 1138 | .typeof_peer => try sema.zirTypeofPeer( block, extended), |
| 1139 | 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 | 1142 | .add_with_overflow => try sema.zirOverflowArithmetic(block, extended, extended.opcode), |
| 1141 | 1143 | .sub_with_overflow => try sema.zirOverflowArithmetic(block, extended, extended.opcode), |
| 1142 | 1144 | .mul_with_overflow => try sema.zirOverflowArithmetic(block, extended, extended.opcode), |
| ... | ... | @@ -12143,7 +12145,7 @@ fn zirShl( |
| 12143 | 12145 | lhs_ty, |
| 12144 | 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 | 12149 | break :rhs try sema.intCast(block, src, lhs_ty, rhs_src, rhs_limited, rhs_src, false); |
| 12148 | 12150 | } else { |
| 12149 | 12151 | break :rhs rhs; |
| ... | ... | @@ -21752,64 +21754,223 @@ fn zirMinMax( |
| 21752 | 21754 | const rhs = try sema.resolveInst(extra.rhs); |
| 21753 | 21755 | try sema.checkNumericType(block, lhs_src, sema.typeOf(lhs)); |
| 21754 | 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 | ||
| 21760 | fn 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 | } |
| 21757 | 21790 | |
| 21758 | 21791 | fn analyzeMinMax( |
| 21759 | 21792 | sema: *Sema, |
| 21760 | 21793 | block: *Block, |
| 21761 | 21794 | src: LazySrcLoc, |
| 21762 | lhs: Air.Inst.Ref, | |
| 21763 | rhs: Air.Inst.Ref, | |
| 21764 | 21795 | comptime air_tag: Air.Inst.Tag, |
| 21765 | lhs_src: LazySrcLoc, | |
| 21766 | rhs_src: LazySrcLoc, | |
| 21796 | operands: []const Air.Inst.Ref, | |
| 21797 | operand_srcs: []const LazySrcLoc, | |
| 21767 | 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); | |
| 21769 | 21801 | |
| 21770 | // TODO @max(max_int, undefined) should return max_int | |
| 21802 | if (operands.len == 1) return operands[0]; | |
| 21771 | 21803 | |
| 21772 | const runtime_src = if (simd_op.lhs_val) |lhs_val| rs: { | |
| 21773 | if (lhs_val.isUndef()) return sema.addConstUndef(simd_op.result_ty); | |
| 21804 | const mod = sema.mod; | |
| 21805 | const target = mod.getTarget(); | |
| 21806 | const opFunc = switch (air_tag) { | |
| 21807 | .min => Value.numberMin, | |
| 21808 | .max => Value.numberMax, | |
| 21809 | else => unreachable, | |
| 21810 | }; | |
| 21774 | 21811 | |
| 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 | } | |
| 21776 | 21832 | |
| 21777 | if (rhs_val.isUndef()) return sema.addConstUndef(simd_op.result_ty); | |
| 21833 | try sema.resolveLazyValue(cur_val); | |
| 21834 | try sema.resolveLazyValue(operand_val); | |
| 21778 | 21835 | |
| 21779 | try sema.resolveLazyValue(lhs_val); | |
| 21780 | try sema.resolveLazyValue(rhs_val); | |
| 21836 | const vec_len = simd_op.len orelse { | |
| 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 | |
| 21781 | 21946 | |
| 21782 | const opFunc = switch (air_tag) { | |
| 21783 | .min => Value.numberMin, | |
| 21784 | .max => Value.numberMax, | |
| 21947 | // Compute the final bounds based on the runtime type and the comptime-known bound type | |
| 21948 | const min_val = switch (air_tag) { | |
| 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 | 21951 | else => unreachable, |
| 21786 | 21952 | }; |
| 21787 | const target = sema.mod.getTarget(); | |
| 21788 | const vec_len = simd_op.len orelse { | |
| 21789 | const result_val = opFunc(lhs_val, rhs_val, target); | |
| 21790 | return sema.addConstant(simd_op.result_ty, result_val); | |
| 21953 | const max_val = switch (air_tag) { | |
| 21954 | .min => try comptime_elem_ty.maxInt(sema.arena, target), // @min(ct, rt) <= ct | |
| 21955 | .max => try unrefined_elem_ty.maxInt(sema.arena, target), | |
| 21956 | else => unreachable, | |
| 21791 | 21957 | }; |
| 21792 | var lhs_buf: Value.ElemValueBuffer = undefined; | |
| 21793 | var rhs_buf: Value.ElemValueBuffer = undefined; | |
| 21794 | const elems = try sema.arena.alloc(Value, vec_len); | |
| 21795 | for (elems, 0..) |*elem, i| { | |
| 21796 | const lhs_elem_val = lhs_val.elemValueBuffer(sema.mod, i, &lhs_buf); | |
| 21797 | const rhs_elem_val = rhs_val.elemValueBuffer(sema.mod, i, &rhs_buf); | |
| 21798 | elem.* = opFunc(lhs_elem_val, rhs_elem_val, target); | |
| 21799 | } | |
| 21800 | return sema.addConstant( | |
| 21801 | simd_op.result_ty, | |
| 21802 | try Value.Tag.aggregate.create(sema.arena, elems), | |
| 21803 | ); | |
| 21804 | } else rs: { | |
| 21805 | if (simd_op.rhs_val) |rhs_val| { | |
| 21806 | if (rhs_val.isUndef()) return sema.addConstUndef(simd_op.result_ty); | |
| 21958 | ||
| 21959 | // Find the smallest type which can contain these bounds | |
| 21960 | const final_elem_ty = try Type.intFittingRange(target, sema.arena, min_val, max_val); | |
| 21961 | ||
| 21962 | const final_ty = if (is_vector) | |
| 21963 | try Type.vector(sema.arena, unrefined_ty.vectorLen(), final_elem_ty) | |
| 21964 | else | |
| 21965 | final_elem_ty; | |
| 21966 | ||
| 21967 | if (!final_ty.eql(unrefined_ty, mod)) { | |
| 21968 | // We've reduced the type - cast the result down | |
| 21969 | return block.addTyOp(.intcast, final_ty, cur_minmax.?); | |
| 21807 | 21970 | } |
| 21808 | break :rs lhs_src; | |
| 21809 | }; | |
| 21971 | } | |
| 21810 | 21972 | |
| 21811 | try sema.requireRuntimeBlock(block, src, runtime_src); | |
| 21812 | return block.addBinOp(air_tag, simd_op.lhs, simd_op.rhs); | |
| 21973 | return cur_minmax.?; | |
| 21813 | 21974 | } |
| 21814 | 21975 | |
| 21815 | 21976 | fn 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 | 927 | /// Implements the `@memset` builtin. |
| 928 | 928 | /// Uses the `pl_node` union field with payload `Bin`. |
| 929 | 929 | memset, |
| 930 | /// Implements the `@min` builtin. | |
| 930 | /// Implements the `@min` builtin for 2 args. | |
| 931 | 931 | /// Uses the `pl_node` union field with payload `Bin` |
| 932 | 932 | min, |
| 933 | /// Implements the `@max` builtin. | |
| 933 | /// Implements the `@max` builtin for 2 args. | |
| 934 | 934 | /// Uses the `pl_node` union field with payload `Bin` |
| 935 | 935 | max, |
| 936 | 936 | /// Implements the `@cImport` builtin. |
| ... | ... | @@ -1905,10 +1905,20 @@ pub const Inst = struct { |
| 1905 | 1905 | compile_log, |
| 1906 | 1906 | /// The builtin `@TypeOf` which returns the type after Peer Type Resolution |
| 1907 | 1907 | /// of one or more params. |
| 1908 | /// `operand` is payload index to `NodeMultiOp`. | |
| 1908 | /// `operand` is payload index to `TypeOfPeer`. | |
| 1909 | 1909 | /// `small` is `operands_len`. |
| 1910 | 1910 | /// The AST node is the builtin call. |
| 1911 | 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 | 1922 | /// Implements the `@addWithOverflow` builtin. |
| 1913 | 1923 | /// `operand` is payload index to `BinNode`. |
| 1914 | 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 | 4298 | |
| 4299 | 4299 | const val_ty = ptr_info.pointee_type; |
| 4300 | 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 | 4302 | const limb_abi_bits = limb_abi_size * 8; |
| 4303 | 4303 | const val_byte_off = @intCast(i32, ptr_info.bit_offset / limb_abi_bits * limb_abi_size); |
| 4304 | 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 | 4434 | const ptr_info = ptr_ty.ptrInfo().data; |
| 4435 | 4435 | const src_ty = ptr_ty.childType(); |
| 4436 | 4436 | |
| 4437 | const limb_abi_size = @min(ptr_info.host_size, 8); | |
| 4437 | const limb_abi_size: u16 = @min(ptr_info.host_size, 8); | |
| 4438 | 4438 | const limb_abi_bits = limb_abi_size * 8; |
| 4439 | 4439 | |
| 4440 | 4440 | const src_bit_size = src_ty.bitSize(self.target.*); |
| ... | ... | @@ -4652,7 +4652,7 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { |
| 4652 | 4652 | } |
| 4653 | 4653 | |
| 4654 | 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 | 4656 | const limb_abi_bits = limb_abi_size * 8; |
| 4657 | 4657 | const field_byte_off = @intCast(i32, field_off / limb_abi_bits * limb_abi_size); |
| 4658 | 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 | 5875 | }, |
| 5876 | 5876 | .memory, .indirect, .load_got, .load_direct, .load_tlv, .load_frame => { |
| 5877 | 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); | |
| 5879 | 5879 | |
| 5880 | 5880 | const dst_info: OpInfo = switch (dst_mcv) { |
| 5881 | 5881 | else => unreachable, |
src/print_zir.zig+2| ... | ... | @@ -482,6 +482,8 @@ const Writer = struct { |
| 482 | 482 | |
| 483 | 483 | .compile_log => try self.writeNodeMultiOp(stream, extended), |
| 484 | 484 | .typeof_peer => try self.writeTypeofPeer(stream, extended), |
| 485 | .min_multi => try self.writeNodeMultiOp(stream, extended), | |
| 486 | .max_multi => try self.writeNodeMultiOp(stream, extended), | |
| 485 | 487 | |
| 486 | 488 | .select => try self.writeSelect(stream, extended), |
| 487 | 489 |
src/type.zig+66-1| ... | ... | @@ -6723,7 +6723,17 @@ pub const Type = extern union { |
| 6723 | 6723 | |
| 6724 | 6724 | pub fn smallestUnsignedInt(arena: Allocator, max: u64) !Type { |
| 6725 | 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 | 6737 | 1 => initTag(.u1), |
| 6728 | 6738 | 8 => initTag(.u8), |
| 6729 | 6739 | 16 => initTag(.u16), |
| ... | ... | @@ -6733,6 +6743,61 @@ pub const Type = extern union { |
| 6733 | 6743 | }; |
| 6734 | 6744 | } |
| 6735 | 6745 | |
| 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 | 6801 | /// This is only used for comptime asserts. Bump this number when you make a change |
| 6737 | 6802 | /// to packed struct layout to find out all the places in the codebase you need to edit! |
| 6738 | 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 | 106 | const size = @max(@sizeOf(A), @sizeOf(B)); |
| 107 | 107 | try expect(size == @sizeOf(B)); |
| 108 | 108 | } |
| 109 | ||
| 110 | test "@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 | ||
| 118 | test "@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 | ||
| 132 | test "@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 | ||
| 148 | test "@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 | } |