Commit cdeea3b094

Robin Voetter <robin@voetter.nl>
2021-07-25 05:34:11
minimum/maximum builtins
1 parent 50a29f7
doc/langref.html.in
@@ -7988,6 +7988,17 @@ test "@hasDecl" {
       </p>
       {#header_close#}
 
+      {#header_open|@maximum#}
+      <pre>{#syntax#}@maximum(a: T, b: T) T{#endsyntax#}</pre>
+      <p>
+      Returns the maximum value of {#syntax#}a{#endsyntax#} and {#syntax#}b{#endsyntax#}. This builtin accepts integers, floats, and vectors of either. In the latter case, the operation is performed element wise.
+      </p>
+      <p>
+      NaNs are handled as follows: if one of the operands of a (pairwise) operation is NaN, the other operand is returned. If both operands are NaN, NaN is returned.
+      </p>
+      {#see_also|@minimum|SIMD|Vectors#}
+      {#header_close#}
+
       {#header_open|@memcpy#}
       <pre>{#syntax#}@memcpy(noalias dest: [*]u8, noalias source: [*]const u8, byte_count: usize){#endsyntax#}</pre>
       <p>
@@ -8025,6 +8036,17 @@ mem.copy(u8, dest[0..byte_count], source[0..byte_count]);{#endsyntax#}</pre>
 mem.set(u8, dest, c);{#endsyntax#}</pre>
       {#header_close#}
 
+      {#header_open|@minimum#}
+      <pre>{#syntax#}@minimum(a: T, b: T) T{#endsyntax#}</pre>
+      <p>
+      Returns the minimum value of {#syntax#}a{#endsyntax#} and {#syntax#}b{#endsyntax#}. This builtin accepts integers, floats, and vectors of either. In the latter case, the operation is performed element wise.
+      </p>
+      <p>
+      NaNs are handled as follows: if one of the operands of a (pairwise) operation is NaN, the other operand is returned. If both operands are NaN, NaN is returned.
+      </p>
+      {#see_also|@maximum|SIMD|Vectors#}
+      {#header_close#}
+
       {#header_open|@wasmMemorySize#}
       <pre>{#syntax#}@wasmMemorySize(index: u32) u32{#endsyntax#}</pre>
       <p>
src/stage1/all_types.hpp
@@ -1796,6 +1796,8 @@ enum BuiltinFnId {
     BuiltinFnIdWasmMemoryGrow,
     BuiltinFnIdSrc,
     BuiltinFnIdReduce,
+    BuiltinFnIdMaximum,
+    BuiltinFnIdMinimum,
 };
 
 struct BuiltinFnEntry {
@@ -2938,6 +2940,8 @@ enum IrBinOp {
     IrBinOpRemMod,
     IrBinOpArrayCat,
     IrBinOpArrayMult,
+    IrBinOpMaximum,
+    IrBinOpMinimum,
 };
 
 struct Stage1ZirInstBinOp {
src/stage1/astgen.cpp
@@ -4686,6 +4686,21 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast
                     arg0_value, arg1_value);
                 return ir_lval_wrap(ag, scope, splat, lval, result_loc);
             }
+        case BuiltinFnIdMaximum:
+            {
+                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
+                Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
+                if (arg0_value == ag->codegen->invalid_inst_src)
+                    return arg0_value;
+
+                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
+                Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
+                if (arg1_value == ag->codegen->invalid_inst_src)
+                    return arg1_value;
+
+                Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpMaximum, arg0_value, arg1_value, true);
+                return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
+            }
         case BuiltinFnIdMemcpy:
             {
                 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
@@ -4726,6 +4741,21 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast
                 Stage1ZirInst *ir_memset = ir_build_memset_src(ag, scope, node, arg0_value, arg1_value, arg2_value);
                 return ir_lval_wrap(ag, scope, ir_memset, lval, result_loc);
             }
+        case BuiltinFnIdMinimum:
+            {
+                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
+                Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
+                if (arg0_value == ag->codegen->invalid_inst_src)
+                    return arg0_value;
+
+                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
+                Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
+                if (arg1_value == ag->codegen->invalid_inst_src)
+                    return arg1_value;
+
+                Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpMinimum, arg0_value, arg1_value, true);
+                return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
+            }
         case BuiltinFnIdWasmMemorySize:
             {
                 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
src/stage1/bigfloat.cpp
@@ -191,6 +191,30 @@ void bigfloat_sqrt(BigFloat *dest, const BigFloat *op) {
     f128M_sqrt(&op->value, &dest->value);
 }
 
+void bigfloat_min(BigFloat *dest, const BigFloat *op1, const BigFloat *op2) {
+    if (bigfloat_is_nan(op1)) {
+        bigfloat_init_bigfloat(dest, op2);
+    } else if (bigfloat_is_nan(op2)) {
+        bigfloat_init_bigfloat(dest, op1);
+    } else if (f128M_lt(&op1->value, &op2->value)) {
+        bigfloat_init_bigfloat(dest, op1);
+    } else {
+        bigfloat_init_bigfloat(dest, op2);
+    }
+}
+
+void bigfloat_max(BigFloat *dest, const BigFloat *op1, const BigFloat *op2) {
+    if (bigfloat_is_nan(op1)) {
+        bigfloat_init_bigfloat(dest, op2);
+    } else if (bigfloat_is_nan(op2)) {
+        bigfloat_init_bigfloat(dest, op1);
+    } else if (f128M_lt(&op1->value, &op2->value)) {
+        bigfloat_init_bigfloat(dest, op2);
+    } else {
+        bigfloat_init_bigfloat(dest, op1);
+    }
+}
+
 bool bigfloat_is_nan(const BigFloat *op) {
     return f128M_isSignalingNaN(&op->value);
 }
src/stage1/bigfloat.hpp
@@ -45,9 +45,12 @@ void bigfloat_div_floor(BigFloat *dest, const BigFloat *op1, const BigFloat *op2
 void bigfloat_rem(BigFloat *dest, const BigFloat *op1, const BigFloat *op2);
 void bigfloat_mod(BigFloat *dest, const BigFloat *op1, const BigFloat *op2);
 void bigfloat_sqrt(BigFloat *dest, const BigFloat *op);
+void bigfloat_min(BigFloat *dest, const BigFloat *op1, const BigFloat *op2);
+void bigfloat_max(BigFloat *dest, const BigFloat *op1, const BigFloat *op2);
 void bigfloat_append_buf(Buf *buf, const BigFloat *op);
 Cmp bigfloat_cmp(const BigFloat *op1, const BigFloat *op2);
 
+
 bool bigfloat_is_nan(const BigFloat *op);
 
 // convenience functions
src/stage1/bigint.cpp
@@ -448,6 +448,26 @@ bool mul_u64_overflow(uint64_t op1, uint64_t op2, uint64_t *result) {
 }
 #endif
 
+void bigint_max(BigInt* dest, const BigInt *op1, const BigInt *op2) {
+    switch (bigint_cmp(op1, op2)) {
+        case CmpEQ:
+        case CmpLT:
+            return bigint_init_bigint(dest, op2);
+        case CmpGT:
+            return bigint_init_bigint(dest, op1);
+    }
+}
+
+void bigint_min(BigInt* dest, const BigInt *op1, const BigInt *op2) {
+    switch (bigint_cmp(op1, op2)) {
+        case CmpEQ:
+        case CmpLT:
+            return bigint_init_bigint(dest, op1);
+        case CmpGT:
+            return bigint_init_bigint(dest, op2);
+    }
+}
+
 void bigint_add(BigInt *dest, const BigInt *op1, const BigInt *op2) {
     if (op1->digit_count == 0) {
         return bigint_init_bigint(dest, op2);
src/stage1/bigint.hpp
@@ -56,6 +56,8 @@ bool bigint_fits_in_bits(const BigInt *bn, size_t bit_count, bool is_signed);
 void bigint_write_twos_complement(const BigInt *big_int, uint8_t *buf, size_t bit_count, bool is_big_endian);
 void bigint_read_twos_complement(BigInt *dest, const uint8_t *buf, size_t bit_count, bool is_big_endian,
         bool is_signed);
+void bigint_max(BigInt* dest, const BigInt *op1, const BigInt *op2);
+void bigint_min(BigInt* dest, const BigInt *op1, const BigInt *op2);
 void bigint_add(BigInt *dest, const BigInt *op1, const BigInt *op2);
 void bigint_add_wrap(BigInt *dest, const BigInt *op1, const BigInt *op2, size_t bit_count, bool is_signed);
 void bigint_sub(BigInt *dest, const BigInt *op1, const BigInt *op2);
src/stage1/codegen.cpp
@@ -3248,6 +3248,30 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, Stage1Air *executable,
         case IrBinOpRemMod:
             return gen_rem(g, want_runtime_safety, ir_want_fast_math(g, &bin_op_instruction->base),
                     op1_value, op2_value, operand_type, RemKindMod);
+        case IrBinOpMaximum:
+            if (scalar_type->id == ZigTypeIdFloat) {
+                return ZigLLVMBuildMaxNum(g->builder, op1_value, op2_value, "");
+            } else if (scalar_type->id == ZigTypeIdInt) {
+                if (scalar_type->data.integral.is_signed) {
+                    return ZigLLVMBuildSMax(g->builder, op1_value, op2_value, "");
+                } else {
+                    return ZigLLVMBuildUMax(g->builder, op1_value, op2_value, "");
+                }
+            } else {
+                zig_unreachable();
+            }
+        case IrBinOpMinimum:
+            if (scalar_type->id == ZigTypeIdFloat) {
+                return ZigLLVMBuildMinNum(g->builder, op1_value, op2_value, "");
+            } else if (scalar_type->id == ZigTypeIdInt) {
+                if (scalar_type->data.integral.is_signed) {
+                    return ZigLLVMBuildSMin(g->builder, op1_value, op2_value, "");
+                } else {
+                    return ZigLLVMBuildUMin(g->builder, op1_value, op2_value, "");
+                }
+            } else {
+                zig_unreachable();
+            }
     }
     zig_unreachable();
 }
@@ -8990,6 +9014,8 @@ static void define_builtin_fns(CodeGen *g) {
     create_builtin_fn(g, BuiltinFnIdWasmMemoryGrow, "wasmMemoryGrow", 2);
     create_builtin_fn(g, BuiltinFnIdSrc, "src", 0);
     create_builtin_fn(g, BuiltinFnIdReduce, "reduce", 2);
+    create_builtin_fn(g, BuiltinFnIdMaximum, "maximum", 2);
+    create_builtin_fn(g, BuiltinFnIdMinimum, "minimum", 2);
 }
 
 static const char *bool_to_str(bool b) {
src/stage1/ir.cpp
@@ -3311,6 +3311,108 @@ static void float_mod(ZigValue *out_val, ZigValue *op1, ZigValue *op2) {
     }
 }
 
+static void float_max(ZigValue *out_val, ZigValue *op1, ZigValue *op2) {
+    assert(op1->type == op2->type);
+    out_val->type = op1->type;
+    if (op1->type->id == ZigTypeIdComptimeFloat) {
+        bigfloat_max(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat);
+    } else if (op1->type->id == ZigTypeIdFloat) {
+        switch (op1->type->data.floating.bit_count) {
+            case 16:
+                if (zig_f16_isNaN(op1->data.x_f16)) {
+                    out_val->data.x_f16 = op2->data.x_f16;
+                } else if (zig_f16_isNaN(op2->data.x_f16)) {
+                    out_val->data.x_f16 = op1->data.x_f16;
+                } else {
+                    out_val->data.x_f16 = f16_lt(op1->data.x_f16, op2->data.x_f16) ? op2->data.x_f16 : op1->data.x_f16;
+                }
+                return;
+            case 32:
+                if (op1->data.x_f32 != op1->data.x_f32) {
+                    out_val->data.x_f32 = op2->data.x_f32;
+                } else if (op2->data.x_f32 != op2->data.x_f32) {
+                    out_val->data.x_f32 = op1->data.x_f32;
+                } else {
+                    out_val->data.x_f32 = op1->data.x_f32 > op2->data.x_f32 ? op1->data.x_f32 : op2->data.x_f32;
+                }
+                return;
+            case 64:
+                if (op1->data.x_f64 != op1->data.x_f64) {
+                    out_val->data.x_f64 = op2->data.x_f64;
+                } else if (op2->data.x_f64 != op2->data.x_f64) {
+                    out_val->data.x_f64 = op1->data.x_f64;
+                } else {
+                    out_val->data.x_f64 = op1->data.x_f64 > op2->data.x_f64 ? op1->data.x_f64 : op2->data.x_f64;
+                }
+                return;
+            case 128:
+                if (zig_f128_isNaN(&op1->data.x_f128)) {
+                    out_val->data.x_f128 = op2->data.x_f128;
+                } else if (zig_f128_isNaN(&op2->data.x_f128)) {
+                    out_val->data.x_f128 = op1->data.x_f128;
+                } else {
+                    out_val->data.x_f128 = f128M_lt(&op1->data.x_f128, &op2->data.x_f128) ? op2->data.x_f128 : op1->data.x_f128;
+                }
+                return;
+            default:
+                zig_unreachable();
+        }
+    } else {
+        zig_unreachable();
+    }
+}
+
+static void float_min(ZigValue *out_val, ZigValue *op1, ZigValue *op2) {
+    assert(op1->type == op2->type);
+    out_val->type = op1->type;
+    if (op1->type->id == ZigTypeIdComptimeFloat) {
+        bigfloat_min(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat);
+    } else if (op1->type->id == ZigTypeIdFloat) {
+        switch (op1->type->data.floating.bit_count) {
+            case 16:
+                if (zig_f16_isNaN(op1->data.x_f16)) {
+                    out_val->data.x_f16 = op2->data.x_f16;
+                } else if (zig_f16_isNaN(op2->data.x_f16)) {
+                    out_val->data.x_f16 = op1->data.x_f16;
+                } else {
+                    out_val->data.x_f16 = f16_lt(op1->data.x_f16, op2->data.x_f16) ? op1->data.x_f16 : op2->data.x_f16;
+                }
+                return;
+            case 32:
+                if (op1->data.x_f32 != op1->data.x_f32) {
+                    out_val->data.x_f32 = op2->data.x_f32;
+                } else if (op2->data.x_f32 != op2->data.x_f32) {
+                    out_val->data.x_f32 = op1->data.x_f32;
+                } else {
+                    out_val->data.x_f32 = op1->data.x_f32 < op2->data.x_f32 ? op1->data.x_f32 : op2->data.x_f32;
+                }
+                return;
+            case 64:
+                if (op1->data.x_f64 != op1->data.x_f64) {
+                    out_val->data.x_f64 = op2->data.x_f64;
+                } else if (op2->data.x_f64 != op2->data.x_f64) {
+                    out_val->data.x_f64 = op1->data.x_f64;
+                } else {
+                    out_val->data.x_f64 = op1->data.x_f32 < op2->data.x_f64 ? op1->data.x_f64 : op2->data.x_f64;
+                }
+                return;
+            case 128:
+                if (zig_f128_isNaN(&op1->data.x_f128)) {
+                    out_val->data.x_f128 = op2->data.x_f128;
+                } else if (zig_f128_isNaN(&op2->data.x_f128)) {
+                    out_val->data.x_f128 = op1->data.x_f128;
+                } else {
+                    out_val->data.x_f128 = f128M_lt(&op1->data.x_f128, &op2->data.x_f128) ? op1->data.x_f128 : op2->data.x_f128;
+                }
+                return;
+            default:
+                zig_unreachable();
+        }
+    } else {
+        zig_unreachable();
+    }
+}
+
 static void float_negate(ZigValue *out_val, ZigValue *op) {
     out_val->type = op->type;
     if (op->type->id == ZigTypeIdComptimeFloat) {
@@ -9704,6 +9806,20 @@ static ErrorMsg *ir_eval_math_op_scalar(IrAnalyze *ira, Scope *scope, AstNode *s
                 float_mod(out_val, op1_val, op2_val);
             }
             break;
+        case IrBinOpMaximum:
+            if (is_int) {
+                bigint_max(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
+            } else {
+                float_max(out_val, op1_val, op2_val);
+            }
+            break;
+        case IrBinOpMinimum:
+            if (is_int) {
+                bigint_min(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
+            } else {
+                float_min(out_val, op1_val, op2_val);
+            }
+            break;
     }
 
     if (type_entry->id == ZigTypeIdInt) {
@@ -9904,6 +10020,8 @@ static bool ok_float_op(IrBinOp op) {
         case IrBinOpRemRem:
         case IrBinOpRemMod:
         case IrBinOpRemUnspecified:
+        case IrBinOpMaximum:
+        case IrBinOpMinimum:
             return true;
 
         case IrBinOpBoolOr:
@@ -10894,6 +11012,8 @@ static Stage1AirInst *ir_analyze_instruction_bin_op(IrAnalyze *ira, Stage1ZirIns
         case IrBinOpRemUnspecified:
         case IrBinOpRemRem:
         case IrBinOpRemMod:
+        case IrBinOpMaximum:
+        case IrBinOpMinimum:
             return ir_analyze_bin_op_math(ira, bin_op_instruction);
         case IrBinOpArrayCat:
             return ir_analyze_array_cat(ira, bin_op_instruction);
src/stage1/ir_print.cpp
@@ -733,6 +733,10 @@ static const char *ir_bin_op_id_str(IrBinOp op_id) {
             return "++";
         case IrBinOpArrayMult:
             return "**";
+        case IrBinOpMaximum:
+            return "@maximum";
+        case IrBinOpMinimum:
+            return "@minimum";
     }
     zig_unreachable();
 }
src/AstGen.zig
@@ -2098,8 +2098,10 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: ast.Node.Index) Inner
             .builtin_call,
             .field_ptr_type,
             .field_parent_ptr,
+            .maximum,
             .memcpy,
             .memset,
+            .minimum,
             .builtin_async_call,
             .c_import,
             .@"resume",
@@ -7227,6 +7229,25 @@ fn builtinCall(
             return rvalue(gz, rl, result, node);
         },
 
+        .maximum => {
+            const a = try expr(gz, scope, .none, params[0]);
+            const b = try expr(gz, scope, .none, params[1]);
+            const result = try gz.addPlNode(.maximum, node, Zir.Inst.Bin{
+                .lhs = a,
+                .rhs = b,
+            });
+            return rvalue(gz, rl, result, node);
+        },
+        .minimum => {
+            const a = try expr(gz, scope, .none, params[0]);
+            const b = try expr(gz, scope, .none, params[1]);
+            const result = try gz.addPlNode(.minimum, node, Zir.Inst.Bin{
+                .lhs = a,
+                .rhs = b,
+            });
+            return rvalue(gz, rl, result, node);
+        },
+
         .add_with_overflow => return overflowArithmetic(gz, scope, rl, node, params, .add_with_overflow),
         .sub_with_overflow => return overflowArithmetic(gz, scope, rl, node, params, .sub_with_overflow),
         .mul_with_overflow => return overflowArithmetic(gz, scope, rl, node, params, .mul_with_overflow),
src/BuiltinFn.zig
@@ -57,8 +57,10 @@ pub const Tag = enum {
     int_to_error,
     int_to_float,
     int_to_ptr,
+    maximum,
     memcpy,
     memset,
+    minimum,
     wasm_memory_size,
     wasm_memory_grow,
     mod,
@@ -518,6 +520,13 @@ pub const list = list: {
                 .param_count = 2,
             },
         },
+        .{
+            "@maximum",
+            .{
+                .tag = .maximum,
+                .param_count = 2,
+            },
+        },
         .{
             "@memcpy",
             .{
@@ -532,6 +541,13 @@ pub const list = list: {
                 .param_count = 3,
             },
         },
+        .{
+            "@minimum",
+            .{
+                .tag = .minimum,
+                .param_count = 2,
+            },
+        },
         .{
             "@wasmMemorySize",
             .{
src/Sema.zig
@@ -346,8 +346,10 @@ pub fn analyzeBody(
             .builtin_call                 => try sema.zirBuiltinCall(block, inst),
             .field_ptr_type               => try sema.zirFieldPtrType(block, inst),
             .field_parent_ptr             => try sema.zirFieldParentPtr(block, inst),
+            .maximum                      => try sema.zirMaximum(block, inst),
             .memcpy                       => try sema.zirMemcpy(block, inst),
             .memset                       => try sema.zirMemset(block, inst),
+            .minimum                      => try sema.zirMinimum(block, inst),
             .builtin_async_call           => try sema.zirBuiltinAsyncCall(block, inst),
             .@"resume"                    => try sema.zirResume(block, inst),
             .@"await"                     => try sema.zirAwait(block, inst, false),
@@ -6148,6 +6150,12 @@ fn zirFieldParentPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Com
     return sema.mod.fail(&block.base, src, "TODO: Sema.zirFieldParentPtr", .{});
 }
 
+fn zirMaximum(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+    const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+    const src = inst_data.src();
+    return sema.mod.fail(&block.base, src, "TODO: Sema.zirMaximum", .{});
+}
+
 fn zirMemcpy(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const src = inst_data.src();
@@ -6160,6 +6168,12 @@ fn zirMemset(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro
     return sema.mod.fail(&block.base, src, "TODO: Sema.zirMemset", .{});
 }
 
+fn zirMinimum(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+    const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+    const src = inst_data.src();
+    return sema.mod.fail(&block.base, src, "TODO: Sema.zirMinimum", .{});
+}
+
 fn zirBuiltinAsyncCall(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const src = inst_data.src();
src/zig_llvm.cpp
@@ -458,6 +458,36 @@ LLVMValueRef ZigLLVMBuildMemSet(LLVMBuilderRef B, LLVMValueRef Ptr, LLVMValueRef
     return wrap(call_inst);
 }
 
+LLVMValueRef ZigLLVMBuildMaxNum(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name) {
+    CallInst *call_inst = unwrap(B)->CreateMaxNum(unwrap(LHS), unwrap(RHS), name);
+    return wrap(call_inst);
+}
+
+LLVMValueRef ZigLLVMBuildMinNum(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name) {
+    CallInst *call_inst = unwrap(B)->CreateMinNum(unwrap(LHS), unwrap(RHS), name);
+    return wrap(call_inst);
+}
+
+LLVMValueRef ZigLLVMBuildUMax(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name) {
+    CallInst *call_inst = unwrap(B)->CreateBinaryIntrinsic(Intrinsic::umax, unwrap(LHS), unwrap(RHS), nullptr, name);
+    return wrap(call_inst);
+}
+
+LLVMValueRef ZigLLVMBuildUMin(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name) {
+    CallInst *call_inst = unwrap(B)->CreateBinaryIntrinsic(Intrinsic::umin, unwrap(LHS), unwrap(RHS), nullptr, name);
+    return wrap(call_inst);
+}
+
+LLVMValueRef ZigLLVMBuildSMax(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name) {
+    CallInst *call_inst = unwrap(B)->CreateBinaryIntrinsic(Intrinsic::smax, unwrap(LHS), unwrap(RHS), nullptr, name);
+    return wrap(call_inst);
+}
+
+LLVMValueRef ZigLLVMBuildSMin(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name) {
+    CallInst *call_inst = unwrap(B)->CreateBinaryIntrinsic(Intrinsic::smin, unwrap(LHS), unwrap(RHS), nullptr, name);
+    return wrap(call_inst);
+}
+
 void ZigLLVMFnSetSubprogram(LLVMValueRef fn, ZigLLVMDISubprogram *subprogram) {
     assert( isa<Function>(unwrap(fn)) );
     Function *unwrapped_function = reinterpret_cast<Function*>(unwrap(fn));
src/zig_llvm.h
@@ -129,6 +129,14 @@ ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildMemCpy(LLVMBuilderRef B, LLVMValueRef Dst,
 ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildMemSet(LLVMBuilderRef B, LLVMValueRef Ptr, LLVMValueRef Val, LLVMValueRef Size,
         unsigned Align, bool isVolatile);
 
+ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildMaxNum(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name);
+ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildMinNum(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name);
+
+ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildUMax(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name);
+ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildUMin(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name);
+ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildSMax(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name);
+ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildSMin(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name);
+
 ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildCmpXchg(LLVMBuilderRef builder, LLVMValueRef ptr, LLVMValueRef cmp,
         LLVMValueRef new_val, LLVMAtomicOrdering success_ordering,
         LLVMAtomicOrdering failure_ordering, bool is_weak);
@@ -142,6 +150,7 @@ ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildLShrExact(LLVMBuilderRef builder, LLVMValu
 ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildAShrExact(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS,
         const char *name);
 
+
 ZIG_EXTERN_C struct ZigLLVMDIType *ZigLLVMCreateDebugPointerType(struct ZigLLVMDIBuilder *dibuilder,
         struct ZigLLVMDIType *pointee_type, uint64_t size_in_bits, uint64_t align_in_bits, const char *name);
 
src/Zir.zig
@@ -915,12 +915,18 @@ pub const Inst = struct {
         /// Implements the `@fieldParentPtr` builtin.
         /// Uses the `pl_node` union field with payload `FieldParentPtr`.
         field_parent_ptr,
+        /// Implements the `@maximum` builtin.
+        /// Uses the `pl_node` union field with payload `Bin`
+        maximum,
         /// Implements the `@memcpy` builtin.
         /// Uses the `pl_node` union field with payload `Memcpy`.
         memcpy,
         /// Implements the `@memset` builtin.
         /// Uses the `pl_node` union field with payload `Memset`.
         memset,
+        /// Implements the `@minimum` builtin.
+        /// Uses the `pl_node` union field with payload `Bin`
+        minimum,
         /// Implements the `@asyncCall` builtin.
         /// Uses the `pl_node` union field with payload `AsyncCall`.
         builtin_async_call,
@@ -1192,8 +1198,10 @@ pub const Inst = struct {
                 .builtin_call,
                 .field_ptr_type,
                 .field_parent_ptr,
+                .maximum,
                 .memcpy,
                 .memset,
+                .minimum,
                 .builtin_async_call,
                 .c_import,
                 .@"resume",
@@ -1463,8 +1471,10 @@ pub const Inst = struct {
                 .builtin_call = .pl_node,
                 .field_ptr_type = .bin,
                 .field_parent_ptr = .pl_node,
+                .maximum = .pl_node,
                 .memcpy = .pl_node,
                 .memset = .pl_node,
+                .minimum = .pl_node,
                 .builtin_async_call = .pl_node,
                 .c_import = .pl_node,
 
@@ -3020,6 +3030,8 @@ const Writer = struct {
             .bitcast,
             .bitcast_result_ptr,
             .vector_type,
+            .maximum,
+            .minimum,
             => try self.writePlNodeBin(stream, inst),
 
             .@"export" => try self.writePlNodeExport(stream, inst),
test/behavior/maximum_minimum.zig
@@ -0,0 +1,58 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const mem = std.mem;
+const expect = std.testing.expect;
+const expectEqual = std.testing.expectEqual;
+const Vector = std.meta.Vector;
+
+test "@maximum" {
+    const S = struct {
+        fn doTheTest() !void {
+            try expectEqual(@as(i32, 10), @maximum(@as(i32, -3), @as(i32, 10)));
+            try expectEqual(@as(f32, 3.2), @maximum(@as(f32, 3.2), @as(f32, 0.68)));
+
+            var a: Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 };
+            var b: Vector(4, i32) = [4]i32{ 1, 2147483647, 3, 4 };
+            var x = @maximum(a, b);
+            try expect(mem.eql(i32, &@as([4]i32, x), &[4]i32{ 2147483647, 2147483647, 30, 40 }));
+
+            var c: Vector(4, f32) = [4]f32{ 0, 0.4, -2.4, 7.8 };
+            var d: Vector(4, f32) = [4]f32{ -0.23, 0.42, -0.64, 0.9 };
+            var y = @maximum(c, d);
+            try expect(mem.eql(f32, &@as([4]f32, y), &[4]f32{ 0, 0.42, -0.64, 7.8 }));
+
+            var e: Vector(2, f32) = [2]f32{ 0, std.math.qnan_f32 };
+            var f: Vector(2, f32) = [2]f32{ std.math.qnan_f32, 0 };
+            var z = @maximum(e, f);
+            try expect(mem.eql(f32, &@as([2]f32, z), &[2]f32{ 0, 0 }));
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "@minimum" {
+    const S = struct {
+        fn doTheTest() !void {
+            try expectEqual(@as(i32, -3), @minimum(@as(i32, -3), @as(i32, 10)));
+            try expectEqual(@as(f32, 0.68), @minimum(@as(f32, 3.2), @as(f32, 0.68)));
+
+            var a: Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 };
+            var b: Vector(4, i32) = [4]i32{ 1, 2147483647, 3, 4 };
+            var x = @minimum(a, b);
+            try expect(mem.eql(i32, &@as([4]i32, x), &[4]i32{ 1, -2, 3, 4 }));
+
+            var c: Vector(4, f32) = [4]f32{ 0, 0.4, -2.4, 7.8 };
+            var d: Vector(4, f32) = [4]f32{ -0.23, 0.42, -0.64, 0.9 };
+            var y = @minimum(c, d);
+            try expect(mem.eql(f32, &@as([4]f32, y), &[4]f32{ -0.23, 0.4, -2.4, 0.9 }));
+
+            var e: Vector(2, f32) = [2]f32{ 0, std.math.qnan_f32 };
+            var f: Vector(2, f32) = [2]f32{ std.math.qnan_f32, 0 };
+            var z = @maximum(e, f);
+            try expect(mem.eql(f32, &@as([2]f32, z), &[2]f32{ 0, 0 }));
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
test/behavior.zig
@@ -105,6 +105,7 @@ test {
         _ = @import("behavior/inttoptr.zig");
         _ = @import("behavior/ir_block_deps.zig");
         _ = @import("behavior/math.zig");
+        _ = @import("behavior/maximum_minimum.zig");
         _ = @import("behavior/merge_error_sets.zig");
         _ = @import("behavior/misc.zig");
         _ = @import("behavior/muladd.zig");