diff --git a/doc/langref.html.in b/doc/langref.html.in index a805f9a2ac..38b91468e2 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -7988,6 +7988,17 @@ test "@hasDecl" {

{#header_close#} + {#header_open|@maximum#} +
{#syntax#}@maximum(a: T, b: T) T{#endsyntax#}
+

+ 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. +

+

+ 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. +

+ {#see_also|@minimum|SIMD|Vectors#} + {#header_close#} + {#header_open|@memcpy#}
{#syntax#}@memcpy(noalias dest: [*]u8, noalias source: [*]const u8, byte_count: usize){#endsyntax#}

@@ -8025,6 +8036,17 @@ mem.copy(u8, dest[0..byte_count], source[0..byte_count]);{#endsyntax#} mem.set(u8, dest, c);{#endsyntax#} {#header_close#} + {#header_open|@minimum#} +

{#syntax#}@minimum(a: T, b: T) T{#endsyntax#}
+

+ 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. +

+

+ 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. +

+ {#see_also|@maximum|SIMD|Vectors#} + {#header_close#} + {#header_open|@wasmMemorySize#}
{#syntax#}@wasmMemorySize(index: u32) u32{#endsyntax#}

diff --git a/src/AstGen.zig b/src/AstGen.zig index 656b960c2f..34f906fab1 100644 --- a/src/AstGen.zig +++ b/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), diff --git a/src/BuiltinFn.zig b/src/BuiltinFn.zig index 07371b3192..8f23ec86d7 100644 --- a/src/BuiltinFn.zig +++ b/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", .{ diff --git a/src/Sema.zig b/src/Sema.zig index 6f8975d086..67cfc09b53 100644 --- a/src/Sema.zig +++ b/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(); diff --git a/src/Zir.zig b/src/Zir.zig index 003b43d9e0..2b88a3415d 100644 --- a/src/Zir.zig +++ b/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), diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index b4b2740dec..a72faa15ce 100644 --- a/src/stage1/all_types.hpp +++ b/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 { diff --git a/src/stage1/astgen.cpp b/src/stage1/astgen.cpp index e8f68c82bb..a5afe32d8b 100644 --- a/src/stage1/astgen.cpp +++ b/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); diff --git a/src/stage1/bigfloat.cpp b/src/stage1/bigfloat.cpp index 56bf2637e3..e5f21e34ea 100644 --- a/src/stage1/bigfloat.cpp +++ b/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); } diff --git a/src/stage1/bigfloat.hpp b/src/stage1/bigfloat.hpp index 52e92d20f4..ffaff320e9 100644 --- a/src/stage1/bigfloat.hpp +++ b/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 diff --git a/src/stage1/bigint.cpp b/src/stage1/bigint.cpp index 3ab6d08d79..5c8efad698 100644 --- a/src/stage1/bigint.cpp +++ b/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); diff --git a/src/stage1/bigint.hpp b/src/stage1/bigint.hpp index 67f3ca1368..53e07f9284 100644 --- a/src/stage1/bigint.hpp +++ b/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); diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index fc2651c8f7..d1bea6990a 100644 --- a/src/stage1/codegen.cpp +++ b/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) { diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index 9a2e50fef7..e9bb7b1d15 100644 --- a/src/stage1/ir.cpp +++ 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); diff --git a/src/stage1/ir_print.cpp b/src/stage1/ir_print.cpp index e83da8565c..96e924b768 100644 --- a/src/stage1/ir_print.cpp +++ b/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(); } diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index d5d6f9f670..d0bc24ed1b 100644 --- a/src/zig_llvm.cpp +++ b/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(unwrap(fn)) ); Function *unwrapped_function = reinterpret_cast(unwrap(fn)); diff --git a/src/zig_llvm.h b/src/zig_llvm.h index a771491138..f49c2662c6 100644 --- a/src/zig_llvm.h +++ b/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); diff --git a/test/behavior.zig b/test/behavior.zig index 8459e499d7..101ee2ce53 100644 --- a/test/behavior.zig +++ b/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"); diff --git a/test/behavior/maximum_minimum.zig b/test/behavior/maximum_minimum.zig new file mode 100644 index 0000000000..5fef818f2b --- /dev/null +++ b/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(); +}