| author | |
| committer | |
| log | 21a5769afefb47553391ae1ef801f64a58253c33 |
| tree | 8f6d1a219c881c1fc34f839421fc000c1fe8d527 |
| parent | 4f0aa7d639e099b18df583cb984412037fbb1dbe |
| signature |
- 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 wasm3217 files changed, 613 insertions(+), 3 deletions(-)
doc/langref.html.in+55-3| ... | ... | @@ -7031,6 +7031,16 @@ fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 { |
| 7031 | 7031 | If no overflow or underflow occurs, returns {#syntax#}false{#endsyntax#}. |
| 7032 | 7032 | </p> |
| 7033 | 7033 | {#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#} | |
| 7034 | 7044 | {#header_open|@alignCast#} |
| 7035 | 7045 | <pre>{#syntax#}@alignCast(comptime alignment: u29, ptr: anytype) anytype{#endsyntax#}</pre> |
| 7036 | 7046 | <p> |
| ... | ... | @@ -8143,6 +8153,22 @@ test "@wasmMemoryGrow" { |
| 8143 | 8153 | If no overflow or underflow occurs, returns {#syntax#}false{#endsyntax#}. |
| 8144 | 8154 | </p> |
| 8145 | 8155 | {#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#} | |
| 8146 | 8172 | |
| 8147 | 8173 | {#header_open|@panic#} |
| 8148 | 8174 | <pre>{#syntax#}@panic(message: []const u8) noreturn{#endsyntax#}</pre> |
| ... | ... | @@ -8368,7 +8394,7 @@ test "@setRuntimeSafety" { |
| 8368 | 8394 | The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits. |
| 8369 | 8395 | This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior. |
| 8370 | 8396 | </p> |
| 8371 | {#see_also|@shrExact|@shlWithOverflow#} | |
| 8397 | {#see_also|@shrExact|@shlWithOverflow|@shlWithSaturation#} | |
| 8372 | 8398 | {#header_close#} |
| 8373 | 8399 | |
| 8374 | 8400 | {#header_open|@shlWithOverflow#} |
| ... | ... | @@ -8382,7 +8408,22 @@ test "@setRuntimeSafety" { |
| 8382 | 8408 | The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits. |
| 8383 | 8409 | This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior. |
| 8384 | 8410 | </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#} | |
| 8386 | 8427 | {#header_close#} |
| 8387 | 8428 | |
| 8388 | 8429 | {#header_open|@shrExact#} |
| ... | ... | @@ -8395,7 +8436,7 @@ test "@setRuntimeSafety" { |
| 8395 | 8436 | The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits. |
| 8396 | 8437 | This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior. |
| 8397 | 8438 | </p> |
| 8398 | {#see_also|@shlExact|@shlWithOverflow#} | |
| 8439 | {#see_also|@shlExact|@shlWithOverflow|@shlWithSaturation#} | |
| 8399 | 8440 | {#header_close#} |
| 8400 | 8441 | |
| 8401 | 8442 | {#header_open|@shuffle#} |
| ... | ... | @@ -8694,6 +8735,17 @@ fn doTheTest() !void { |
| 8694 | 8735 | If no overflow or underflow occurs, returns {#syntax#}false{#endsyntax#}. |
| 8695 | 8736 | </p> |
| 8696 | 8737 | {#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#} | |
| 8697 | 8749 | |
| 8698 | 8750 | {#header_open|@tagName#} |
| 8699 | 8751 | <pre>{#syntax#}@tagName(value: anytype) [:0]const u8{#endsyntax#}</pre> |
src/AstGen.zig+23| ... | ... | @@ -7301,6 +7301,11 @@ fn builtinCall( |
| 7301 | 7301 | return rvalue(gz, rl, result, node); |
| 7302 | 7302 | }, |
| 7303 | 7303 | |
| 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 | ||
| 7304 | 7309 | .atomic_load => { |
| 7305 | 7310 | const int_type = try typeExpr(gz, scope, params[0]); |
| 7306 | 7311 | const ptr_type = try gz.add(.{ .tag = .ptr_type_simple, .data = .{ |
| ... | ... | @@ -7693,6 +7698,24 @@ fn overflowArithmetic( |
| 7693 | 7698 | return rvalue(gz, rl, result, node); |
| 7694 | 7699 | } |
| 7695 | 7700 | |
| 7701 | fn 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 | ||
| 7696 | 7719 | fn callExpr( |
| 7697 | 7720 | gz: *GenZir, |
| 7698 | 7721 | scope: *Scope, |
src/BuiltinFn.zig+32| ... | ... | @@ -2,6 +2,7 @@ const std = @import("std"); |
| 2 | 2 | |
| 3 | 3 | pub const Tag = enum { |
| 4 | 4 | add_with_overflow, |
| 5 | add_with_saturation, | |
| 5 | 6 | align_cast, |
| 6 | 7 | align_of, |
| 7 | 8 | as, |
| ... | ... | @@ -65,6 +66,7 @@ pub const Tag = enum { |
| 65 | 66 | wasm_memory_grow, |
| 66 | 67 | mod, |
| 67 | 68 | mul_with_overflow, |
| 69 | mul_with_saturation, | |
| 68 | 70 | panic, |
| 69 | 71 | pop_count, |
| 70 | 72 | ptr_cast, |
| ... | ... | @@ -79,10 +81,12 @@ pub const Tag = enum { |
| 79 | 81 | set_runtime_safety, |
| 80 | 82 | shl_exact, |
| 81 | 83 | shl_with_overflow, |
| 84 | shl_with_saturation, | |
| 82 | 85 | shr_exact, |
| 83 | 86 | shuffle, |
| 84 | 87 | size_of, |
| 85 | 88 | splat, |
| 89 | sub_with_saturation, | |
| 86 | 90 | reduce, |
| 87 | 91 | src, |
| 88 | 92 | sqrt, |
| ... | ... | @@ -527,6 +531,34 @@ pub const list = list: { |
| 527 | 531 | .param_count = 2, |
| 528 | 532 | }, |
| 529 | 533 | }, |
| 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 | }, | |
| 530 | 562 | .{ |
| 531 | 563 | "@memcpy", |
| 532 | 564 | .{ |
src/Sema.zig+17| ... | ... | @@ -570,6 +570,10 @@ fn zirExtended(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr |
| 570 | 570 | .c_define => return sema.zirCDefine( block, extended), |
| 571 | 571 | .wasm_memory_size => return sema.zirWasmMemorySize( block, extended), |
| 572 | 572 | .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), | |
| 573 | 577 | // zig fmt: on |
| 574 | 578 | } |
| 575 | 579 | } |
| ... | ... | @@ -5691,6 +5695,19 @@ fn zirOverflowArithmetic( |
| 5691 | 5695 | return sema.mod.fail(&block.base, src, "TODO implement Sema.zirOverflowArithmetic", .{}); |
| 5692 | 5696 | } |
| 5693 | 5697 | |
| 5698 | fn 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 | ||
| 5694 | 5711 | fn analyzeArithmetic( |
| 5695 | 5712 | sema: *Sema, |
| 5696 | 5713 | block: *Scope.Block, |
src/Zir.zig+39| ... | ... | @@ -1629,6 +1629,22 @@ pub const Inst = struct { |
| 1629 | 1629 | wasm_memory_size, |
| 1630 | 1630 | /// `operand` is payload index to `BinNode`. |
| 1631 | 1631 | 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, | |
| 1632 | 1648 | |
| 1633 | 1649 | pub const InstData = struct { |
| 1634 | 1650 | opcode: Extended, |
| ... | ... | @@ -2751,6 +2767,12 @@ pub const Inst = struct { |
| 2751 | 2767 | ptr: Ref, |
| 2752 | 2768 | }; |
| 2753 | 2769 | |
| 2770 | pub const SaturatingArithmetic = struct { | |
| 2771 | node: i32, | |
| 2772 | lhs: Ref, | |
| 2773 | rhs: Ref, | |
| 2774 | }; | |
| 2775 | ||
| 2754 | 2776 | pub const Cmpxchg = struct { |
| 2755 | 2777 | ptr: Ref, |
| 2756 | 2778 | expected_value: Ref, |
| ... | ... | @@ -3231,6 +3253,11 @@ const Writer = struct { |
| 3231 | 3253 | .shl_with_overflow, |
| 3232 | 3254 | => try self.writeOverflowArithmetic(stream, extended), |
| 3233 | 3255 | |
| 3256 | .add_with_saturation, | |
| 3257 | .sub_with_saturation, | |
| 3258 | .mul_with_saturation, | |
| 3259 | .shl_with_saturation, | |
| 3260 | => try self.writeSaturatingArithmetic(stream, extended), | |
| 3234 | 3261 | .struct_decl => try self.writeStructDecl(stream, extended), |
| 3235 | 3262 | .union_decl => try self.writeUnionDecl(stream, extended), |
| 3236 | 3263 | .enum_decl => try self.writeEnumDecl(stream, extended), |
| ... | ... | @@ -3584,6 +3611,18 @@ const Writer = struct { |
| 3584 | 3611 | try self.writeSrc(stream, src); |
| 3585 | 3612 | } |
| 3586 | 3613 | |
| 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 | ||
| 3587 | 3626 | fn writePlNodeCall(self: *Writer, stream: anytype, inst: Inst.Index) !void { |
| 3588 | 3627 | const inst_data = self.code.instructions.items(.data)[inst].pl_node; |
| 3589 | 3628 | const extra = self.code.extraData(Inst.Call, inst_data.payload_index); |
src/stage1/all_types.hpp+8| ... | ... | @@ -1802,6 +1802,10 @@ enum BuiltinFnId { |
| 1802 | 1802 | BuiltinFnIdReduce, |
| 1803 | 1803 | BuiltinFnIdMaximum, |
| 1804 | 1804 | BuiltinFnIdMinimum, |
| 1805 | BuiltinFnIdSatAdd, | |
| 1806 | BuiltinFnIdSatSub, | |
| 1807 | BuiltinFnIdSatMul, | |
| 1808 | BuiltinFnIdSatShl, | |
| 1805 | 1809 | }; |
| 1806 | 1810 | |
| 1807 | 1811 | struct BuiltinFnEntry { |
| ... | ... | @@ -2946,6 +2950,10 @@ enum IrBinOp { |
| 2946 | 2950 | IrBinOpArrayMult, |
| 2947 | 2951 | IrBinOpMaximum, |
| 2948 | 2952 | IrBinOpMinimum, |
| 2953 | IrBinOpSatAdd, | |
| 2954 | IrBinOpSatSub, | |
| 2955 | IrBinOpSatMul, | |
| 2956 | IrBinOpSatShl, | |
| 2949 | 2957 | }; |
| 2950 | 2958 | |
| 2951 | 2959 | struct Stage1ZirInstBinOp { |
src/stage1/astgen.cpp+60| ... | ... | @@ -4704,6 +4704,66 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast |
| 4704 | 4704 | Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpMaximum, arg0_value, arg1_value, true); |
| 4705 | 4705 | return ir_lval_wrap(ag, scope, bin_op, lval, result_loc); |
| 4706 | 4706 | } |
| 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 | } | |
| 4707 | 4767 | case BuiltinFnIdMemcpy: |
| 4708 | 4768 | { |
| 4709 | 4769 | 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) { |
| 468 | 468 | } |
| 469 | 469 | } |
| 470 | 470 | |
| 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] | |
| 474 | void 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 | ||
| 529 | void 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 | ||
| 534 | void 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 | ||
| 539 | void 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 | ||
| 544 | void 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 | ||
| 471 | 549 | void bigint_add(BigInt *dest, const BigInt *op1, const BigInt *op2) { |
| 472 | 550 | if (op1->digit_count == 0) { |
| 473 | 551 | 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); |
| 105 | 105 | uint32_t bigint_hash(BigInt const *x); |
| 106 | 106 | bool bigint_eql(BigInt const *a, BigInt const *b); |
| 107 | 107 | |
| 108 | void bigint_add_sat(BigInt* dest, const BigInt *op1, const BigInt *op2, uint32_t bit_count, bool is_signed); | |
| 109 | void bigint_sub_sat(BigInt* dest, const BigInt *op1, const BigInt *op2, uint32_t bit_count, bool is_signed); | |
| 110 | void bigint_mul_sat(BigInt* dest, const BigInt *op1, const BigInt *op2, uint32_t bit_count, bool is_signed); | |
| 111 | void bigint_shl_sat(BigInt* dest, const BigInt *op1, const BigInt *op2, uint32_t bit_count, bool is_signed); | |
| 108 | 112 | #endif |
src/stage1/codegen.cpp+44| ... | ... | @@ -3335,6 +3335,46 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, Stage1Air *executable, |
| 3335 | 3335 | } else { |
| 3336 | 3336 | zig_unreachable(); |
| 3337 | 3337 | } |
| 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 | } | |
| 3338 | 3378 | } |
| 3339 | 3379 | zig_unreachable(); |
| 3340 | 3380 | } |
| ... | ... | @@ -9096,6 +9136,10 @@ static void define_builtin_fns(CodeGen *g) { |
| 9096 | 9136 | create_builtin_fn(g, BuiltinFnIdReduce, "reduce", 2); |
| 9097 | 9137 | create_builtin_fn(g, BuiltinFnIdMaximum, "maximum", 2); |
| 9098 | 9138 | 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); | |
| 9099 | 9143 | } |
| 9100 | 9144 | |
| 9101 | 9145 | static 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 |
| 9820 | 9820 | float_min(out_val, op1_val, op2_val); |
| 9821 | 9821 | } |
| 9822 | 9822 | 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; | |
| 9823 | 9851 | } |
| 9824 | 9852 | |
| 9825 | 9853 | if (type_entry->id == ZigTypeIdInt) { |
| ... | ... | @@ -10041,6 +10069,10 @@ static bool ok_float_op(IrBinOp op) { |
| 10041 | 10069 | case IrBinOpBitShiftRightExact: |
| 10042 | 10070 | case IrBinOpAddWrap: |
| 10043 | 10071 | case IrBinOpSubWrap: |
| 10072 | case IrBinOpSatAdd: | |
| 10073 | case IrBinOpSatSub: | |
| 10074 | case IrBinOpSatMul: | |
| 10075 | case IrBinOpSatShl: | |
| 10044 | 10076 | case IrBinOpMultWrap: |
| 10045 | 10077 | case IrBinOpArrayCat: |
| 10046 | 10078 | case IrBinOpArrayMult: |
| ... | ... | @@ -11014,6 +11046,10 @@ static Stage1AirInst *ir_analyze_instruction_bin_op(IrAnalyze *ira, Stage1ZirIns |
| 11014 | 11046 | case IrBinOpRemMod: |
| 11015 | 11047 | case IrBinOpMaximum: |
| 11016 | 11048 | case IrBinOpMinimum: |
| 11049 | case IrBinOpSatAdd: | |
| 11050 | case IrBinOpSatSub: | |
| 11051 | case IrBinOpSatMul: | |
| 11052 | case IrBinOpSatShl: | |
| 11017 | 11053 | return ir_analyze_bin_op_math(ira, bin_op_instruction); |
| 11018 | 11054 | case IrBinOpArrayCat: |
| 11019 | 11055 | 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) { |
| 737 | 737 | return "@maximum"; |
| 738 | 738 | case IrBinOpMinimum: |
| 739 | 739 | 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"; | |
| 740 | 748 | } |
| 741 | 749 | zig_unreachable(); |
| 742 | 750 | } |
src/zig_llvm.cpp+52| ... | ... | @@ -488,6 +488,58 @@ LLVMValueRef ZigLLVMBuildSMin(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef R |
| 488 | 488 | return wrap(call_inst); |
| 489 | 489 | } |
| 490 | 490 | |
| 491 | LLVMValueRef 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 | ||
| 496 | LLVMValueRef 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 | ||
| 501 | LLVMValueRef 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 | ||
| 506 | LLVMValueRef 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 | ||
| 511 | LLVMValueRef 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 | ||
| 522 | LLVMValueRef 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 | ||
| 533 | LLVMValueRef 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 | ||
| 538 | LLVMValueRef 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 | ||
| 491 | 543 | void ZigLLVMFnSetSubprogram(LLVMValueRef fn, ZigLLVMDISubprogram *subprogram) { |
| 492 | 544 | assert( isa<Function>(unwrap(fn)) ); |
| 493 | 545 | 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 |
| 136 | 136 | ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildUMin(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name); |
| 137 | 137 | ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildSMax(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name); |
| 138 | 138 | ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildSMin(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name); |
| 139 | ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildUAddSat(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name); | |
| 140 | ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildSAddSat(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name); | |
| 141 | ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildUSubSat(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name); | |
| 142 | ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildSSubSat(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name); | |
| 143 | ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildSMulFixSat(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name); | |
| 144 | ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildUMulFixSat(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS, const char *name); | |
| 145 | ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildUShlSat(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name); | |
| 146 | ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildSShlSat(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char* name); | |
| 147 | ||
| 139 | 148 | |
| 140 | 149 | ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildCmpXchg(LLVMBuilderRef builder, LLVMValueRef ptr, LLVMValueRef cmp, |
| 141 | 150 | LLVMValueRef new_val, LLVMAtomicOrdering success_ordering, |
test/behavior.zig+1| ... | ... | @@ -125,6 +125,7 @@ test { |
| 125 | 125 | _ = @import("behavior/pub_enum.zig"); |
| 126 | 126 | _ = @import("behavior/ref_var_in_if_after_if_2nd_switch_prong.zig"); |
| 127 | 127 | _ = @import("behavior/reflection.zig"); |
| 128 | _ = @import("behavior/saturating_arithmetic.zig"); | |
| 128 | 129 | _ = @import("behavior/shuffle.zig"); |
| 129 | 130 | _ = @import("behavior/select.zig"); |
| 130 | 131 | _ = @import("behavior/sizeof_and_typeof.zig"); |
test/behavior/saturating_arithmetic.zig created+139| ... | ... | @@ -0,0 +1,139 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const mem = std.mem; | |
| 4 | const expectEqual = std.testing.expectEqual; | |
| 5 | const Vector = std.meta.Vector; | |
| 6 | const minInt = std.math.minInt; | |
| 7 | const maxInt = std.math.maxInt; | |
| 8 | ||
| 9 | const Op = enum { add, sub, mul, shl }; | |
| 10 | fn 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 | ||
| 23 | test "@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 | ||
| 56 | test "@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 | ||
| 82 | test "@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 | ||
| 114 | test "@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 { |
| 8838 | 8838 | "tmp.zig:2:9: note: declared mutable here", |
| 8839 | 8839 | "tmp.zig:3:12: note: crosses namespace boundary here", |
| 8840 | 8840 | }); |
| 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 | }); | |
| 8841 | 8849 | } |