authorgravatar for twostepted@gmail.comTravis Staloch <twostepted@gmail.com> 2021-09-01 11:17:45-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-09-01 14:17:45-04:00
log21a5769afefb47553391ae1ef801f64a58253c33
tree8f6d1a219c881c1fc34f839421fc000c1fe8d527
parent4f0aa7d639e099b18df583cb984412037fbb1dbe
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

saturating arithmetic builtins: add, sub, mul, shl (#9619)

- adds 1 simple behavior tests for each which does integer and vector ops at runtime and comptime - adds bigint_*_sat() methods for each - use CreateIntrinsic() which accepts a variable number of arguments to pass the scale parameter * update langref - added case to test/compile_errors.zig given floats - explain upstream bug in llvm.smul.fix.sat and link to #9643 in langref and commented out test cases * sat-arithmetic: skip mul tests if arch == .wasm32 because ci is erroring with 'LLVM ERROR: Unable to expand fixed point multiplication' when compiling for wasm32

17 files changed, 613 insertions(+), 3 deletions(-)

doc/langref.html.in+55-3
......@@ -7031,6 +7031,16 @@ fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 {
70317031 If no overflow or underflow occurs, returns {#syntax#}false{#endsyntax#}.
70327032 </p>
70337033 {#header_close#}
7034 {#header_open|@addWithSaturation#}
7035 <pre>{#syntax#}@addWithSaturation(a: T, b: T) T{#endsyntax#}</pre>
7036 <p>
7037 Returns {#syntax#}a + b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
7038 </p>
7039 <p>
7040 Once <a href="https://github.com/ziglang/zig/issues/1284">Saturating arithmetic</a>.
7041 is completed, the syntax {#syntax#}a +| b{#endsyntax#} will be equivalent to calling {#syntax#}@addWithSaturation(a, b){#endsyntax#}.
7042 </p>
7043 {#header_close#}
70347044 {#header_open|@alignCast#}
70357045 <pre>{#syntax#}@alignCast(comptime alignment: u29, ptr: anytype) anytype{#endsyntax#}</pre>
70367046 <p>
......@@ -8143,6 +8153,22 @@ test "@wasmMemoryGrow" {
81438153 If no overflow or underflow occurs, returns {#syntax#}false{#endsyntax#}.
81448154 </p>
81458155 {#header_close#}
8156
8157 {#header_open|@mulWithSaturation#}
8158 <pre>{#syntax#}@mulWithSaturation(a: T, b: T) T{#endsyntax#}</pre>
8159 <p>
8160 Returns {#syntax#}a * b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
8161 </p>
8162 <p>
8163 Once <a href="https://github.com/ziglang/zig/issues/1284">Saturating arithmetic</a>.
8164 is completed, the syntax {#syntax#}a *| b{#endsyntax#} will be equivalent to calling {#syntax#}@mulWithSaturation(a, b){#endsyntax#}.
8165 </p>
8166 <p>
8167 NOTE: Currently there is a bug in the llvm.smul.fix.sat intrinsic which affects {#syntax#}@mulWithSaturation{#endsyntax#} of signed integers.
8168 This may result in an incorrect sign bit when there is overflow. This will be fixed in zig's 0.9.0 release.
8169 Check <a href="https://github.com/ziglang/zig/issues/9643">this issue</a> for more information.
8170 </p>
8171 {#header_close#}
81468172
81478173 {#header_open|@panic#}
81488174 <pre>{#syntax#}@panic(message: []const u8) noreturn{#endsyntax#}</pre>
......@@ -8368,7 +8394,7 @@ test "@setRuntimeSafety" {
83688394 The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits.
83698395 This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior.
83708396 </p>
8371 {#see_also|@shrExact|@shlWithOverflow#}
8397 {#see_also|@shrExact|@shlWithOverflow|@shlWithSaturation#}
83728398 {#header_close#}
83738399
83748400 {#header_open|@shlWithOverflow#}
......@@ -8382,7 +8408,22 @@ test "@setRuntimeSafety" {
83828408 The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits.
83838409 This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior.
83848410 </p>
8385 {#see_also|@shlExact|@shrExact#}
8411 {#see_also|@shlExact|@shrExact|@shlWithSaturation#}
8412 {#header_close#}
8413
8414 {#header_open|@shlWithSaturation#}
8415 <pre>{#syntax#}@shlWithSaturation(a: T, shift_amt: T) T{#endsyntax#}</pre>
8416 <p>
8417 Returns {#syntax#}a << b{#endsyntax#}. The result will be clamped between type minimum and maximum.
8418 </p>
8419 <p>
8420 Once <a href="https://github.com/ziglang/zig/issues/1284">Saturating arithmetic</a>.
8421 is completed, the syntax {#syntax#}a <<| b{#endsyntax#} will be equivalent to calling {#syntax#}@shlWithSaturation(a, b){#endsyntax#}.
8422 </p>
8423 <p>
8424 Unlike other @shl builtins, shift_amt doesn't need to be a Log2T as saturated overshifting is well defined.
8425 </p>
8426 {#see_also|@shlExact|@shrExact|@shlWithOverflow#}
83868427 {#header_close#}
83878428
83888429 {#header_open|@shrExact#}
......@@ -8395,7 +8436,7 @@ test "@setRuntimeSafety" {
83958436 The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits.
83968437 This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior.
83978438 </p>
8398 {#see_also|@shlExact|@shlWithOverflow#}
8439 {#see_also|@shlExact|@shlWithOverflow|@shlWithSaturation#}
83998440 {#header_close#}
84008441
84018442 {#header_open|@shuffle#}
......@@ -8694,6 +8735,17 @@ fn doTheTest() !void {
86948735 If no overflow or underflow occurs, returns {#syntax#}false{#endsyntax#}.
86958736 </p>
86968737 {#header_close#}
8738
8739 {#header_open|@subWithSaturation#}
8740 <pre>{#syntax#}@subWithSaturation(a: T, b: T) T{#endsyntax#}</pre>
8741 <p>
8742 Returns {#syntax#}a - b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
8743 </p>
8744 <p>
8745 Once <a href="https://github.com/ziglang/zig/issues/1284">Saturating arithmetic</a>.
8746 is completed, the syntax {#syntax#}a -| b{#endsyntax#} will be equivalent to calling {#syntax#}@subWithSaturation(a, b){#endsyntax#}.
8747 </p>
8748 {#header_close#}
86978749
86988750 {#header_open|@tagName#}
86998751 <pre>{#syntax#}@tagName(value: anytype) [:0]const u8{#endsyntax#}</pre>
src/AstGen.zig+23
......@@ -7301,6 +7301,11 @@ fn builtinCall(
73017301 return rvalue(gz, rl, result, node);
73027302 },
73037303
7304 .add_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .add_with_saturation),
7305 .sub_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .sub_with_saturation),
7306 .mul_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .mul_with_saturation),
7307 .shl_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .shl_with_saturation),
7308
73047309 .atomic_load => {
73057310 const int_type = try typeExpr(gz, scope, params[0]);
73067311 const ptr_type = try gz.add(.{ .tag = .ptr_type_simple, .data = .{
......@@ -7693,6 +7698,24 @@ fn overflowArithmetic(
76937698 return rvalue(gz, rl, result, node);
76947699}
76957700
7701fn saturatingArithmetic(
7702 gz: *GenZir,
7703 scope: *Scope,
7704 rl: ResultLoc,
7705 node: ast.Node.Index,
7706 params: []const ast.Node.Index,
7707 tag: Zir.Inst.Extended,
7708) InnerError!Zir.Inst.Ref {
7709 const lhs = try expr(gz, scope, .none, params[0]);
7710 const rhs = try expr(gz, scope, .none, params[1]);
7711 const result = try gz.addExtendedPayload(tag, Zir.Inst.SaturatingArithmetic{
7712 .node = gz.nodeIndexToRelative(node),
7713 .lhs = lhs,
7714 .rhs = rhs,
7715 });
7716 return rvalue(gz, rl, result, node);
7717}
7718
76967719fn callExpr(
76977720 gz: *GenZir,
76987721 scope: *Scope,
src/BuiltinFn.zig+32
......@@ -2,6 +2,7 @@ const std = @import("std");
22
33pub const Tag = enum {
44 add_with_overflow,
5 add_with_saturation,
56 align_cast,
67 align_of,
78 as,
......@@ -65,6 +66,7 @@ pub const Tag = enum {
6566 wasm_memory_grow,
6667 mod,
6768 mul_with_overflow,
69 mul_with_saturation,
6870 panic,
6971 pop_count,
7072 ptr_cast,
......@@ -79,10 +81,12 @@ pub const Tag = enum {
7981 set_runtime_safety,
8082 shl_exact,
8183 shl_with_overflow,
84 shl_with_saturation,
8285 shr_exact,
8386 shuffle,
8487 size_of,
8588 splat,
89 sub_with_saturation,
8690 reduce,
8791 src,
8892 sqrt,
......@@ -527,6 +531,34 @@ pub const list = list: {
527531 .param_count = 2,
528532 },
529533 },
534 .{
535 "@addWithSaturation",
536 .{
537 .tag = .add_with_saturation,
538 .param_count = 2,
539 },
540 },
541 .{
542 "@subWithSaturation",
543 .{
544 .tag = .sub_with_saturation,
545 .param_count = 2,
546 },
547 },
548 .{
549 "@mulWithSaturation",
550 .{
551 .tag = .mul_with_saturation,
552 .param_count = 2,
553 },
554 },
555 .{
556 "@shlWithSaturation",
557 .{
558 .tag = .shl_with_saturation,
559 .param_count = 2,
560 },
561 },
530562 .{
531563 "@memcpy",
532564 .{
src/Sema.zig+17
......@@ -570,6 +570,10 @@ fn zirExtended(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
570570 .c_define => return sema.zirCDefine( block, extended),
571571 .wasm_memory_size => return sema.zirWasmMemorySize( block, extended),
572572 .wasm_memory_grow => return sema.zirWasmMemoryGrow( block, extended),
573 .add_with_saturation=> return sema.zirSatArithmetic( block, extended),
574 .sub_with_saturation=> return sema.zirSatArithmetic( block, extended),
575 .mul_with_saturation=> return sema.zirSatArithmetic( block, extended),
576 .shl_with_saturation=> return sema.zirSatArithmetic( block, extended),
573577 // zig fmt: on
574578 }
575579}
......@@ -5691,6 +5695,19 @@ fn zirOverflowArithmetic(
56915695 return sema.mod.fail(&block.base, src, "TODO implement Sema.zirOverflowArithmetic", .{});
56925696}
56935697
5698fn zirSatArithmetic(
5699 sema: *Sema,
5700 block: *Scope.Block,
5701 extended: Zir.Inst.Extended.InstData,
5702) CompileError!Air.Inst.Ref {
5703 const tracy = trace(@src());
5704 defer tracy.end();
5705
5706 const extra = sema.code.extraData(Zir.Inst.SaturatingArithmetic, extended.operand).data;
5707 const src: LazySrcLoc = .{ .node_offset = extra.node };
5708 return sema.mod.fail(&block.base, src, "TODO implement Sema.zirSatArithmetic", .{});
5709}
5710
56945711fn analyzeArithmetic(
56955712 sema: *Sema,
56965713 block: *Scope.Block,
src/Zir.zig+39
......@@ -1629,6 +1629,22 @@ pub const Inst = struct {
16291629 wasm_memory_size,
16301630 /// `operand` is payload index to `BinNode`.
16311631 wasm_memory_grow,
1632 /// Implements the `@addWithSaturation` builtin.
1633 /// `operand` is payload index to `SaturatingArithmetic`.
1634 /// `small` is unused.
1635 add_with_saturation,
1636 /// Implements the `@subWithSaturation` builtin.
1637 /// `operand` is payload index to `SaturatingArithmetic`.
1638 /// `small` is unused.
1639 sub_with_saturation,
1640 /// Implements the `@mulWithSaturation` builtin.
1641 /// `operand` is payload index to `SaturatingArithmetic`.
1642 /// `small` is unused.
1643 mul_with_saturation,
1644 /// Implements the `@shlWithSaturation` builtin.
1645 /// `operand` is payload index to `SaturatingArithmetic`.
1646 /// `small` is unused.
1647 shl_with_saturation,
16321648
16331649 pub const InstData = struct {
16341650 opcode: Extended,
......@@ -2751,6 +2767,12 @@ pub const Inst = struct {
27512767 ptr: Ref,
27522768 };
27532769
2770 pub const SaturatingArithmetic = struct {
2771 node: i32,
2772 lhs: Ref,
2773 rhs: Ref,
2774 };
2775
27542776 pub const Cmpxchg = struct {
27552777 ptr: Ref,
27562778 expected_value: Ref,
......@@ -3231,6 +3253,11 @@ const Writer = struct {
32313253 .shl_with_overflow,
32323254 => try self.writeOverflowArithmetic(stream, extended),
32333255
3256 .add_with_saturation,
3257 .sub_with_saturation,
3258 .mul_with_saturation,
3259 .shl_with_saturation,
3260 => try self.writeSaturatingArithmetic(stream, extended),
32343261 .struct_decl => try self.writeStructDecl(stream, extended),
32353262 .union_decl => try self.writeUnionDecl(stream, extended),
32363263 .enum_decl => try self.writeEnumDecl(stream, extended),
......@@ -3584,6 +3611,18 @@ const Writer = struct {
35843611 try self.writeSrc(stream, src);
35853612 }
35863613
3614 fn writeSaturatingArithmetic(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void {
3615 const extra = self.code.extraData(Zir.Inst.SaturatingArithmetic, extended.operand).data;
3616 const src: LazySrcLoc = .{ .node_offset = extra.node };
3617
3618 try self.writeInstRef(stream, extra.lhs);
3619 try stream.writeAll(", ");
3620 try self.writeInstRef(stream, extra.rhs);
3621 try stream.writeAll(", ");
3622 try stream.writeAll(") ");
3623 try self.writeSrc(stream, src);
3624 }
3625
35873626 fn writePlNodeCall(self: *Writer, stream: anytype, inst: Inst.Index) !void {
35883627 const inst_data = self.code.instructions.items(.data)[inst].pl_node;
35893628 const extra = self.code.extraData(Inst.Call, inst_data.payload_index);
src/stage1/all_types.hpp+8
......@@ -1802,6 +1802,10 @@ enum BuiltinFnId {
18021802 BuiltinFnIdReduce,
18031803 BuiltinFnIdMaximum,
18041804 BuiltinFnIdMinimum,
1805 BuiltinFnIdSatAdd,
1806 BuiltinFnIdSatSub,
1807 BuiltinFnIdSatMul,
1808 BuiltinFnIdSatShl,
18051809};
18061810
18071811struct BuiltinFnEntry {
......@@ -2946,6 +2950,10 @@ enum IrBinOp {
29462950 IrBinOpArrayMult,
29472951 IrBinOpMaximum,
29482952 IrBinOpMinimum,
2953 IrBinOpSatAdd,
2954 IrBinOpSatSub,
2955 IrBinOpSatMul,
2956 IrBinOpSatShl,
29492957};
29502958
29512959struct Stage1ZirInstBinOp {
src/stage1/astgen.cpp+60
......@@ -4704,6 +4704,66 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast
47044704 Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpMaximum, arg0_value, arg1_value, true);
47054705 return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
47064706 }
4707 case BuiltinFnIdSatAdd:
4708 {
4709 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
4710 Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
4711 if (arg0_value == ag->codegen->invalid_inst_src)
4712 return arg0_value;
4713
4714 AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
4715 Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
4716 if (arg1_value == ag->codegen->invalid_inst_src)
4717 return arg1_value;
4718
4719 Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSatAdd, arg0_value, arg1_value, true);
4720 return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
4721 }
4722 case BuiltinFnIdSatSub:
4723 {
4724 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
4725 Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
4726 if (arg0_value == ag->codegen->invalid_inst_src)
4727 return arg0_value;
4728
4729 AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
4730 Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
4731 if (arg1_value == ag->codegen->invalid_inst_src)
4732 return arg1_value;
4733
4734 Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSatSub, arg0_value, arg1_value, true);
4735 return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
4736 }
4737 case BuiltinFnIdSatMul:
4738 {
4739 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
4740 Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
4741 if (arg0_value == ag->codegen->invalid_inst_src)
4742 return arg0_value;
4743
4744 AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
4745 Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
4746 if (arg1_value == ag->codegen->invalid_inst_src)
4747 return arg1_value;
4748
4749 Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSatMul, arg0_value, arg1_value, true);
4750 return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
4751 }
4752 case BuiltinFnIdSatShl:
4753 {
4754 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
4755 Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
4756 if (arg0_value == ag->codegen->invalid_inst_src)
4757 return arg0_value;
4758
4759 AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
4760 Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
4761 if (arg1_value == ag->codegen->invalid_inst_src)
4762 return arg1_value;
4763
4764 Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSatShl, arg0_value, arg1_value, true);
4765 return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
4766 }
47074767 case BuiltinFnIdMemcpy:
47084768 {
47094769 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
src/stage1/bigint.cpp+78
......@@ -468,6 +468,84 @@ void bigint_min(BigInt* dest, const BigInt *op1, const BigInt *op2) {
468468 }
469469}
470470
471/// clamps op within bit_count/signedness boundaries
472/// signed bounds are [-2^(bit_count-1)..2^(bit_count-1)-1]
473/// unsigned bounds are [0..2^bit_count-1]
474void bigint_clamp_by_bitcount(BigInt* dest, uint32_t bit_count, bool is_signed) {
475 // compute the number of bits required to store the value, and use that
476 // to decide whether to clamp the result
477 bool is_negative = dest->is_negative;
478 // to workaround the fact this bits_needed calculation would yield 65 or more for
479 // all negative numbers, set is_negative to false. this is a cheap way to find
480 // bits_needed(abs(dest)).
481 dest->is_negative = false;
482 // because we've set is_negative to false, we have to account for the extra bit here
483 // by adding 1 additional bit_needed when (is_negative && !is_signed).
484 size_t full_bits = dest->digit_count * 64;
485 size_t leading_zero_count = bigint_clz(dest, full_bits);
486 size_t bits_needed = full_bits - leading_zero_count + (is_negative && !is_signed);
487
488 bit_count -= is_signed;
489 if(bits_needed > bit_count) {
490 BigInt one;
491 bigint_init_unsigned(&one, 1);
492 BigInt bit_count_big;
493 bigint_init_unsigned(&bit_count_big, bit_count);
494
495 if(is_signed) {
496 if(is_negative) {
497 BigInt bound;
498 bigint_shl(&bound, &one, &bit_count_big);
499 bigint_deinit(dest);
500 *dest = bound;
501 } else {
502 BigInt bound;
503 bigint_shl(&bound, &one, &bit_count_big);
504 BigInt bound_sub_one;
505 bigint_sub(&bound_sub_one, &bound, &one);
506 bigint_deinit(&bound);
507 bigint_deinit(dest);
508 *dest = bound_sub_one;
509 }
510 } else {
511 if(is_negative) {
512 bigint_deinit(dest);
513 bigint_init_unsigned(dest, 0);
514 return; // skips setting is_negative which would be invalid
515 } else {
516 BigInt bound;
517 bigint_shl(&bound, &one, &bit_count_big);
518 BigInt bound_sub_one;
519 bigint_sub(&bound_sub_one, &bound, &one);
520 bigint_deinit(&bound);
521 bigint_deinit(dest);
522 *dest = bound_sub_one;
523 }
524 }
525 }
526 dest->is_negative = is_negative;
527}
528
529void bigint_add_sat(BigInt* dest, const BigInt *op1, const BigInt *op2, uint32_t bit_count, bool is_signed) {
530 bigint_add(dest, op1, op2);
531 bigint_clamp_by_bitcount(dest, bit_count, is_signed);
532}
533
534void bigint_sub_sat(BigInt* dest, const BigInt *op1, const BigInt *op2, uint32_t bit_count, bool is_signed) {
535 bigint_sub(dest, op1, op2);
536 bigint_clamp_by_bitcount(dest, bit_count, is_signed);
537}
538
539void bigint_mul_sat(BigInt* dest, const BigInt *op1, const BigInt *op2, uint32_t bit_count, bool is_signed) {
540 bigint_mul(dest, op1, op2);
541 bigint_clamp_by_bitcount(dest, bit_count, is_signed);
542}
543
544void bigint_shl_sat(BigInt* dest, const BigInt *op1, const BigInt *op2, uint32_t bit_count, bool is_signed) {
545 bigint_shl(dest, op1, op2);
546 bigint_clamp_by_bitcount(dest, bit_count, is_signed);
547}
548
471549void bigint_add(BigInt *dest, const BigInt *op1, const BigInt *op2) {
472550 if (op1->digit_count == 0) {
473551 return bigint_init_bigint(dest, op2);
src/stage1/bigint.hpp+4
......@@ -105,4 +105,8 @@ bool mul_u64_overflow(uint64_t op1, uint64_t op2, uint64_t *result);
105105uint32_t bigint_hash(BigInt const *x);
106106bool bigint_eql(BigInt const *a, BigInt const *b);
107107
108void bigint_add_sat(BigInt* dest, const BigInt *op1, const BigInt *op2, uint32_t bit_count, bool is_signed);
109void bigint_sub_sat(BigInt* dest, const BigInt *op1, const BigInt *op2, uint32_t bit_count, bool is_signed);
110void bigint_mul_sat(BigInt* dest, const BigInt *op1, const BigInt *op2, uint32_t bit_count, bool is_signed);
111void bigint_shl_sat(BigInt* dest, const BigInt *op1, const BigInt *op2, uint32_t bit_count, bool is_signed);
108112#endif
src/stage1/codegen.cpp+44
......@@ -3335,6 +3335,46 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, Stage1Air *executable,
33353335 } else {
33363336 zig_unreachable();
33373337 }
3338 case IrBinOpSatAdd:
3339 if (scalar_type->id == ZigTypeIdInt) {
3340 if (scalar_type->data.integral.is_signed) {
3341 return ZigLLVMBuildSAddSat(g->builder, op1_value, op2_value, "");
3342 } else {
3343 return ZigLLVMBuildUAddSat(g->builder, op1_value, op2_value, "");
3344 }
3345 } else {
3346 zig_unreachable();
3347 }
3348 case IrBinOpSatSub:
3349 if (scalar_type->id == ZigTypeIdInt) {
3350 if (scalar_type->data.integral.is_signed) {
3351 return ZigLLVMBuildSSubSat(g->builder, op1_value, op2_value, "");
3352 } else {
3353 return ZigLLVMBuildUSubSat(g->builder, op1_value, op2_value, "");
3354 }
3355 } else {
3356 zig_unreachable();
3357 }
3358 case IrBinOpSatMul:
3359 if (scalar_type->id == ZigTypeIdInt) {
3360 if (scalar_type->data.integral.is_signed) {
3361 return ZigLLVMBuildSMulFixSat(g->builder, op1_value, op2_value, "");
3362 } else {
3363 return ZigLLVMBuildUMulFixSat(g->builder, op1_value, op2_value, "");
3364 }
3365 } else {
3366 zig_unreachable();
3367 }
3368 case IrBinOpSatShl:
3369 if (scalar_type->id == ZigTypeIdInt) {
3370 if (scalar_type->data.integral.is_signed) {
3371 return ZigLLVMBuildSShlSat(g->builder, op1_value, op2_value, "");
3372 } else {
3373 return ZigLLVMBuildUShlSat(g->builder, op1_value, op2_value, "");
3374 }
3375 } else {
3376 zig_unreachable();
3377 }
33383378 }
33393379 zig_unreachable();
33403380}
......@@ -9096,6 +9136,10 @@ static void define_builtin_fns(CodeGen *g) {
90969136 create_builtin_fn(g, BuiltinFnIdReduce, "reduce", 2);
90979137 create_builtin_fn(g, BuiltinFnIdMaximum, "maximum", 2);
90989138 create_builtin_fn(g, BuiltinFnIdMinimum, "minimum", 2);
9139 create_builtin_fn(g, BuiltinFnIdSatAdd, "addWithSaturation", 2);
9140 create_builtin_fn(g, BuiltinFnIdSatSub, "subWithSaturation", 2);
9141 create_builtin_fn(g, BuiltinFnIdSatMul, "mulWithSaturation", 2);
9142 create_builtin_fn(g, BuiltinFnIdSatShl, "shlWithSaturation", 2);
90999143}
91009144
91019145static const char *bool_to_str(bool b) {
src/stage1/ir.cpp+36
......@@ -9820,6 +9820,34 @@ static ErrorMsg *ir_eval_math_op_scalar(IrAnalyze *ira, Scope *scope, AstNode *s
98209820 float_min(out_val, op1_val, op2_val);
98219821 }
98229822 break;
9823 case IrBinOpSatAdd:
9824 if (is_int) {
9825 bigint_add_sat(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint, type_entry->data.integral.bit_count, type_entry->data.integral.is_signed);
9826 } else {
9827 zig_unreachable();
9828 }
9829 break;
9830 case IrBinOpSatSub:
9831 if (is_int) {
9832 bigint_sub_sat(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint, type_entry->data.integral.bit_count, type_entry->data.integral.is_signed);
9833 } else {
9834 zig_unreachable();
9835 }
9836 break;
9837 case IrBinOpSatMul:
9838 if (is_int) {
9839 bigint_mul_sat(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint, type_entry->data.integral.bit_count, type_entry->data.integral.is_signed);
9840 } else {
9841 zig_unreachable();
9842 }
9843 break;
9844 case IrBinOpSatShl:
9845 if (is_int) {
9846 bigint_shl_sat(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint, type_entry->data.integral.bit_count, type_entry->data.integral.is_signed);
9847 } else {
9848 zig_unreachable();
9849 }
9850 break;
98239851 }
98249852
98259853 if (type_entry->id == ZigTypeIdInt) {
......@@ -10041,6 +10069,10 @@ static bool ok_float_op(IrBinOp op) {
1004110069 case IrBinOpBitShiftRightExact:
1004210070 case IrBinOpAddWrap:
1004310071 case IrBinOpSubWrap:
10072 case IrBinOpSatAdd:
10073 case IrBinOpSatSub:
10074 case IrBinOpSatMul:
10075 case IrBinOpSatShl:
1004410076 case IrBinOpMultWrap:
1004510077 case IrBinOpArrayCat:
1004610078 case IrBinOpArrayMult:
......@@ -11014,6 +11046,10 @@ static Stage1AirInst *ir_analyze_instruction_bin_op(IrAnalyze *ira, Stage1ZirIns
1101411046 case IrBinOpRemMod:
1101511047 case IrBinOpMaximum:
1101611048 case IrBinOpMinimum:
11049 case IrBinOpSatAdd:
11050 case IrBinOpSatSub:
11051 case IrBinOpSatMul:
11052 case IrBinOpSatShl:
1101711053 return ir_analyze_bin_op_math(ira, bin_op_instruction);
1101811054 case IrBinOpArrayCat:
1101911055 return ir_analyze_array_cat(ira, bin_op_instruction);
src/stage1/ir_print.cpp+8
......@@ -737,6 +737,14 @@ static const char *ir_bin_op_id_str(IrBinOp op_id) {
737737 return "@maximum";
738738 case IrBinOpMinimum:
739739 return "@minimum";
740 case IrBinOpSatAdd:
741 return "@addWithSaturation";
742 case IrBinOpSatSub:
743 return "@subWithSaturation";
744 case IrBinOpSatMul:
745 return "@mulWithSaturation";
746 case IrBinOpSatShl:
747 return "@shlWithSaturation";
740748 }
741749 zig_unreachable();
742750}
src/zig_llvm.cpp+52
......@@ -488,6 +488,58 @@ LLVMValueRef ZigLLVMBuildSMin(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef R
488488 return wrap(call_inst);
489489}
490490
491LLVMValueRef ZigLLVMBuildSAddSat(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name) {
492 CallInst *call_inst = unwrap(B)->CreateBinaryIntrinsic(Intrinsic::sadd_sat, unwrap(LHS), unwrap(RHS), nullptr, name);
493 return wrap(call_inst);
494}
495
496LLVMValueRef ZigLLVMBuildUAddSat(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name) {
497 CallInst *call_inst = unwrap(B)->CreateBinaryIntrinsic(Intrinsic::uadd_sat, unwrap(LHS), unwrap(RHS), nullptr, name);
498 return wrap(call_inst);
499}
500
501LLVMValueRef ZigLLVMBuildSSubSat(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name) {
502 CallInst *call_inst = unwrap(B)->CreateBinaryIntrinsic(Intrinsic::ssub_sat, unwrap(LHS), unwrap(RHS), nullptr, name);
503 return wrap(call_inst);
504}
505
506LLVMValueRef ZigLLVMBuildUSubSat(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name) {
507 CallInst *call_inst = unwrap(B)->CreateBinaryIntrinsic(Intrinsic::usub_sat, unwrap(LHS), unwrap(RHS), nullptr, name);
508 return wrap(call_inst);
509}
510
511LLVMValueRef ZigLLVMBuildSMulFixSat(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name) {
512 llvm::Type* types[1] = {
513 unwrap(LHS)->getType(),
514 };
515 // pass scale = 0 as third argument
516 llvm::Value* values[3] = {unwrap(LHS), unwrap(RHS), unwrap(B)->getInt32(0)};
517
518 CallInst *call_inst = unwrap(B)->CreateIntrinsic(Intrinsic::smul_fix_sat, types, values, nullptr, name);
519 return wrap(call_inst);
520}
521
522LLVMValueRef ZigLLVMBuildUMulFixSat(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name) {
523 llvm::Type* types[1] = {
524 unwrap(LHS)->getType(),
525 };
526 // pass scale = 0 as third argument
527 llvm::Value* values[3] = {unwrap(LHS), unwrap(RHS), unwrap(B)->getInt32(0)};
528
529 CallInst *call_inst = unwrap(B)->CreateIntrinsic(Intrinsic::umul_fix_sat, types, values, nullptr, name);
530 return wrap(call_inst);
531}
532
533LLVMValueRef ZigLLVMBuildSShlSat(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name) {
534 CallInst *call_inst = unwrap(B)->CreateBinaryIntrinsic(Intrinsic::sshl_sat, unwrap(LHS), unwrap(RHS), nullptr, name);
535 return wrap(call_inst);
536}
537
538LLVMValueRef ZigLLVMBuildUShlSat(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name) {
539 CallInst *call_inst = unwrap(B)->CreateBinaryIntrinsic(Intrinsic::ushl_sat, unwrap(LHS), unwrap(RHS), nullptr, name);
540 return wrap(call_inst);
541}
542
491543void ZigLLVMFnSetSubprogram(LLVMValueRef fn, ZigLLVMDISubprogram *subprogram) {
492544 assert( isa<Function>(unwrap(fn)) );
493545 Function *unwrapped_function = reinterpret_cast<Function*>(unwrap(fn));
src/zig_llvm.h+9
......@@ -136,6 +136,15 @@ ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildUMax(LLVMBuilderRef builder, LLVMValueRef
136136ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildUMin(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name);
137137ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildSMax(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name);
138138ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildSMin(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name);
139ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildUAddSat(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name);
140ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildSAddSat(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name);
141ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildUSubSat(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name);
142ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildSSubSat(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name);
143ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildSMulFixSat(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name);
144ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildUMulFixSat(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name);
145ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildUShlSat(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name);
146ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildSShlSat(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name);
147
139148
140149ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildCmpXchg(LLVMBuilderRef builder, LLVMValueRef ptr, LLVMValueRef cmp,
141150 LLVMValueRef new_val, LLVMAtomicOrdering success_ordering,
test/behavior.zig+1
......@@ -125,6 +125,7 @@ test {
125125 _ = @import("behavior/pub_enum.zig");
126126 _ = @import("behavior/ref_var_in_if_after_if_2nd_switch_prong.zig");
127127 _ = @import("behavior/reflection.zig");
128 _ = @import("behavior/saturating_arithmetic.zig");
128129 _ = @import("behavior/shuffle.zig");
129130 _ = @import("behavior/select.zig");
130131 _ = @import("behavior/sizeof_and_typeof.zig");
test/behavior/saturating_arithmetic.zig created+139
......@@ -0,0 +1,139 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const mem = std.mem;
4const expectEqual = std.testing.expectEqual;
5const Vector = std.meta.Vector;
6const minInt = std.math.minInt;
7const maxInt = std.math.maxInt;
8
9const Op = enum { add, sub, mul, shl };
10fn testSaturatingOp(comptime op: Op, comptime T: type, test_data: [3]T) !void {
11 const a = test_data[0];
12 const b = test_data[1];
13 const expected = test_data[2];
14 const actual = switch (op) {
15 .add => @addWithSaturation(a, b),
16 .sub => @subWithSaturation(a, b),
17 .mul => @mulWithSaturation(a, b),
18 .shl => @shlWithSaturation(a, b),
19 };
20 try expectEqual(expected, actual);
21}
22
23test "@addWithSaturation" {
24 const S = struct {
25 fn doTheTest() !void {
26 // .{a, b, expected a+b}
27 try testSaturatingOp(.add, i8, .{ -3, 10, 7 });
28 try testSaturatingOp(.add, i8, .{ -128, -128, -128 });
29 try testSaturatingOp(.add, i2, .{ 1, 1, 1 });
30 try testSaturatingOp(.add, i64, .{ maxInt(i64), 1, maxInt(i64) });
31 try testSaturatingOp(.add, i128, .{ maxInt(i128), -maxInt(i128), 0 });
32 try testSaturatingOp(.add, i128, .{ minInt(i128), maxInt(i128), -1 });
33 try testSaturatingOp(.add, i8, .{ 127, 127, 127 });
34 try testSaturatingOp(.add, u8, .{ 3, 10, 13 });
35 try testSaturatingOp(.add, u8, .{ 255, 255, 255 });
36 try testSaturatingOp(.add, u2, .{ 3, 2, 3 });
37 try testSaturatingOp(.add, u3, .{ 7, 1, 7 });
38 try testSaturatingOp(.add, u128, .{ maxInt(u128), 1, maxInt(u128) });
39
40 const u8x3 = std.meta.Vector(3, u8);
41 try expectEqual(u8x3{ 255, 255, 255 }, @addWithSaturation(
42 u8x3{ 255, 254, 1 },
43 u8x3{ 1, 2, 255 },
44 ));
45 const i8x3 = std.meta.Vector(3, i8);
46 try expectEqual(i8x3{ 127, 127, 127 }, @addWithSaturation(
47 i8x3{ 127, 126, 1 },
48 i8x3{ 1, 2, 127 },
49 ));
50 }
51 };
52 try S.doTheTest();
53 comptime try S.doTheTest();
54}
55
56test "@subWithSaturation" {
57 const S = struct {
58 fn doTheTest() !void {
59 // .{a, b, expected a-b}
60 try testSaturatingOp(.sub, i8, .{ -3, 10, -13 });
61 try testSaturatingOp(.sub, i8, .{ -128, -128, 0 });
62 try testSaturatingOp(.sub, i8, .{ -1, 127, -128 });
63 try testSaturatingOp(.sub, i64, .{ minInt(i64), 1, minInt(i64) });
64 try testSaturatingOp(.sub, i128, .{ maxInt(i128), -1, maxInt(i128) });
65 try testSaturatingOp(.sub, i128, .{ minInt(i128), -maxInt(i128), -1 });
66 try testSaturatingOp(.sub, u8, .{ 10, 3, 7 });
67 try testSaturatingOp(.sub, u8, .{ 0, 255, 0 });
68 try testSaturatingOp(.sub, u5, .{ 0, 31, 0 });
69 try testSaturatingOp(.sub, u128, .{ 0, maxInt(u128), 0 });
70
71 const u8x3 = std.meta.Vector(3, u8);
72 try expectEqual(u8x3{ 0, 0, 0 }, @subWithSaturation(
73 u8x3{ 0, 0, 0 },
74 u8x3{ 255, 255, 255 },
75 ));
76 }
77 };
78 try S.doTheTest();
79 comptime try S.doTheTest();
80}
81
82test "@mulWithSaturation" {
83 // TODO: once #9660 has been solved, remove this line
84 if (std.builtin.target.cpu.arch == .wasm32) return error.SkipZigTest;
85
86 const S = struct {
87 fn doTheTest() !void {
88 // .{a, b, expected a*b}
89 try testSaturatingOp(.mul, i8, .{ -3, 10, -30 });
90 try testSaturatingOp(.mul, i4, .{ 2, 4, 7 });
91 try testSaturatingOp(.mul, i8, .{ 2, 127, 127 });
92 // TODO: uncomment these after #9643 has been solved - this should happen at 0.9.0/llvm-13 release
93 // try testSaturatingOp(.mul, i8, .{ -128, -128, 127 });
94 // try testSaturatingOp(.mul, i8, .{ maxInt(i8), maxInt(i8), maxInt(i8) });
95 try testSaturatingOp(.mul, i16, .{ maxInt(i16), -1, minInt(i16) + 1 });
96 try testSaturatingOp(.mul, i128, .{ maxInt(i128), -1, minInt(i128) + 1 });
97 try testSaturatingOp(.mul, i128, .{ minInt(i128), -1, maxInt(i128) });
98 try testSaturatingOp(.mul, u8, .{ 10, 3, 30 });
99 try testSaturatingOp(.mul, u8, .{ 2, 255, 255 });
100 try testSaturatingOp(.mul, u128, .{ maxInt(u128), maxInt(u128), maxInt(u128) });
101
102 const u8x3 = std.meta.Vector(3, u8);
103 try expectEqual(u8x3{ 255, 255, 255 }, @mulWithSaturation(
104 u8x3{ 2, 2, 2 },
105 u8x3{ 255, 255, 255 },
106 ));
107 }
108 };
109
110 try S.doTheTest();
111 comptime try S.doTheTest();
112}
113
114test "@shlWithSaturation" {
115 const S = struct {
116 fn doTheTest() !void {
117 // .{a, b, expected a<<b}
118 try testSaturatingOp(.shl, i8, .{ 1, 2, 4 });
119 try testSaturatingOp(.shl, i8, .{ 127, 1, 127 });
120 try testSaturatingOp(.shl, i8, .{ -128, 1, -128 });
121 // TODO: remove this check once #9668 is completed
122 if (std.builtin.target.cpu.arch != .wasm32) {
123 // skip testing ints > 64 bits on wasm due to miscompilation / wasmtime ci error
124 try testSaturatingOp(.shl, i128, .{ maxInt(i128), 64, maxInt(i128) });
125 try testSaturatingOp(.shl, u128, .{ maxInt(u128), 64, maxInt(u128) });
126 }
127 try testSaturatingOp(.shl, u8, .{ 1, 2, 4 });
128 try testSaturatingOp(.shl, u8, .{ 255, 1, 255 });
129
130 const u8x3 = std.meta.Vector(3, u8);
131 try expectEqual(u8x3{ 255, 255, 255 }, @shlWithSaturation(
132 u8x3{ 255, 255, 255 },
133 u8x3{ 1, 1, 1 },
134 ));
135 }
136 };
137 try S.doTheTest();
138 comptime try S.doTheTest();
139}
test/compile_errors.zig+8
......@@ -8838,4 +8838,12 @@ pub fn addCases(ctx: *TestContext) !void {
88388838 "tmp.zig:2:9: note: declared mutable here",
88398839 "tmp.zig:3:12: note: crosses namespace boundary here",
88408840 });
8841
8842 ctx.objErrStage1("Issue #9619: saturating arithmetic builtins should fail to compile when given floats",
8843 \\pub fn main() !void {
8844 \\ _ = @addWithSaturation(@as(f32, 1.0), @as(f32, 1.0));
8845 \\}
8846 , &[_][]const u8{
8847 "error: invalid operands to binary expression: 'f32' and 'f32'",
8848 });
88418849}