authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-09-28 18:55:43-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-09-28 19:19:28-07:00
log54675824449d16029fdf6a1873e78cb8f2147f60
treea3495ecdbbca9a963f514938f20003f1aeb69b64
parent71da169c67ad544bd1d4dfc4bfff9fe302e8284d

saturating arithmetic modifications

* Remove the builtins `@addWithSaturation`, `@subWithSaturation`, `@mulWithSaturation`, and `@shlWithSaturation` now that we have first-class syntax for saturating arithmetic. * langref: Clarify the behavior of `@shlExact`. * Ast: rename `bit_shift_left` to `shl` and `bit_shift_right` to `shr` for consistency. * Air: rename to include underscore separator with consistency with the rest of the ops. * Air: add shl_exact instruction * Use non-extended tags for saturating arithmetic, to keep it simple so that all the arithmetic operations can be done the same way. - Sema: unify analyzeArithmetic with analyzeSatArithmetic - implement comptime `+|`, `-|`, and `*|` - allow float operands to saturating arithmetic * `<<|` allows any integer type for the RHS. * C backend: fix rebase conflicts * LLVM backend: reduce the amount of branching for arithmetic ops * zig.h: fix magic number not matching actual size of C integer types

23 files changed, 616 insertions(+), 664 deletions(-)

doc/langref.html.in+7-56
......@@ -1407,7 +1407,6 @@ a +|= b{#endsyntax#}</pre></th>
14071407 <td>Saturating Addition.
14081408 <ul>
14091409 <li>Invokes {#link|Peer Type Resolution#} for the operands.</li>
1410 <li>See also {#link|@addWithSaturation#}.</li>
14111410 </ul>
14121411 </td>
14131412 <td>
......@@ -1464,7 +1463,6 @@ a -|= b{#endsyntax#}</pre></th>
14641463 <td>Saturating Subtraction.
14651464 <ul>
14661465 <li>Invokes {#link|Peer Type Resolution#} for the operands.</li>
1467 <li>See also {#link|@subWithSaturation#}.</li>
14681466 </ul>
14691467 </td>
14701468 <td>
......@@ -1556,7 +1554,6 @@ a *|= b{#endsyntax#}</pre></th>
15561554 <td>Saturating Multiplication.
15571555 <ul>
15581556 <li>Invokes {#link|Peer Type Resolution#} for the operands.</li>
1559 <li>See also {#link|@mulWithSaturation#}.</li>
15601557 </ul>
15611558 </td>
15621559 <td>
......@@ -7235,15 +7232,6 @@ fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 {
72357232 If no overflow or underflow occurs, returns {#syntax#}false{#endsyntax#}.
72367233 </p>
72377234 {#header_close#}
7238 {#header_open|@addWithSaturation#}
7239 <pre>{#syntax#}@addWithSaturation(a: T, b: T) T{#endsyntax#}</pre>
7240 <p>
7241 Returns {#syntax#}a + b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
7242 </p>
7243 <p>
7244 The syntax {#syntax#}a +| b{#endsyntax#} is equivalent to calling {#syntax#}@addWithSaturation(a, b){#endsyntax#}.
7245 </p>
7246 {#header_close#}
72477235 {#header_open|@alignCast#}
72487236 <pre>{#syntax#}@alignCast(comptime alignment: u29, ptr: anytype) anytype{#endsyntax#}</pre>
72497237 <p>
......@@ -8365,21 +8353,6 @@ test "@wasmMemoryGrow" {
83658353 </p>
83668354 {#header_close#}
83678355
8368 {#header_open|@mulWithSaturation#}
8369 <pre>{#syntax#}@mulWithSaturation(a: T, b: T) T{#endsyntax#}</pre>
8370 <p>
8371 Returns {#syntax#}a * b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
8372 </p>
8373 <p>
8374 The syntax {#syntax#}a *| b{#endsyntax#} is equivalent to calling {#syntax#}@mulWithSaturation(a, b){#endsyntax#}.
8375 </p>
8376 <p>
8377 NOTE: Currently there is a bug in the llvm.smul.fix.sat intrinsic which affects {#syntax#}@mulWithSaturation{#endsyntax#} of signed integers.
8378 This may result in an incorrect sign bit when there is overflow. This will be fixed in zig's 0.9.0 release.
8379 Check <a href="https://github.com/ziglang/zig/issues/9643">this issue</a> for more information.
8380 </p>
8381 {#header_close#}
8382
83838356 {#header_open|@panic#}
83848357 <pre>{#syntax#}@panic(message: []const u8) noreturn{#endsyntax#}</pre>
83858358 <p>
......@@ -8597,14 +8570,16 @@ test "@setRuntimeSafety" {
85978570 {#header_open|@shlExact#}
85988571 <pre>{#syntax#}@shlExact(value: T, shift_amt: Log2T) T{#endsyntax#}</pre>
85998572 <p>
8600 Performs the left shift operation ({#syntax#}<<{#endsyntax#}). Caller guarantees
8601 that the shift will not shift any 1 bits out.
8573 Performs the left shift operation ({#syntax#}<<{#endsyntax#}).
8574 For unsigned integers, the result is {#link|undefined#} if any 1 bits
8575 are shifted out. For signed integers, the result is {#link|undefined#} if
8576 any bits that disagree with the resultant sign bit are shifted out.
86028577 </p>
86038578 <p>
86048579 The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits.
86058580 This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior.
86068581 </p>
8607 {#see_also|@shrExact|@shlWithOverflow|@shlWithSaturation#}
8582 {#see_also|@shrExact|@shlWithOverflow#}
86088583 {#header_close#}
86098584
86108585 {#header_open|@shlWithOverflow#}
......@@ -8618,23 +8593,9 @@ test "@setRuntimeSafety" {
86188593 The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits.
86198594 This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior.
86208595 </p>
8621 {#see_also|@shlExact|@shrExact|@shlWithSaturation#}
8596 {#see_also|@shlExact|@shrExact#}
86228597 {#header_close#}
86238598
8624 {#header_open|@shlWithSaturation#}
8625 <pre>{#syntax#}@shlWithSaturation(a: T, shift_amt: T) T{#endsyntax#}</pre>
8626 <p>
8627 Returns {#syntax#}a << b{#endsyntax#}. The result will be clamped between type minimum and maximum.
8628 </p>
8629 <p>
8630 The syntax {#syntax#}a <<| b{#endsyntax#} is equivalent to calling {#syntax#}@shlWithSaturation(a, b){#endsyntax#}.
8631 </p>
8632 <p>
8633 Unlike other @shl builtins, shift_amt doesn't need to be a Log2T as saturated overshifting is well defined.
8634 </p>
8635 {#see_also|@shlExact|@shrExact|@shlWithOverflow#}
8636 {#header_close#}
8637
86388599 {#header_open|@shrExact#}
86398600 <pre>{#syntax#}@shrExact(value: T, shift_amt: Log2T) T{#endsyntax#}</pre>
86408601 <p>
......@@ -8645,7 +8606,7 @@ test "@setRuntimeSafety" {
86458606 The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits.
86468607 This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior.
86478608 </p>
8648 {#see_also|@shlExact|@shlWithOverflow|@shlWithSaturation#}
8609 {#see_also|@shlExact|@shlWithOverflow#}
86498610 {#header_close#}
86508611
86518612 {#header_open|@shuffle#}
......@@ -8945,16 +8906,6 @@ fn doTheTest() !void {
89458906 </p>
89468907 {#header_close#}
89478908
8948 {#header_open|@subWithSaturation#}
8949 <pre>{#syntax#}@subWithSaturation(a: T, b: T) T{#endsyntax#}</pre>
8950 <p>
8951 Returns {#syntax#}a - b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
8952 </p>
8953 <p>
8954 The syntax {#syntax#}a -| b{#endsyntax#} is equivalent to calling {#syntax#}@subWithSaturation(a, b){#endsyntax#}.
8955 </p>
8956 {#header_close#}
8957
89588909 {#header_open|@tagName#}
89598910 <pre>{#syntax#}@tagName(value: anytype) [:0]const u8{#endsyntax#}</pre>
89608911 <p>
lib/std/zig/Ast.zig+18-18
......@@ -395,9 +395,9 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex {
395395 .assign_mod,
396396 .assign_add,
397397 .assign_sub,
398 .assign_bit_shift_left,
399 .assign_bit_shift_left_sat,
400 .assign_bit_shift_right,
398 .assign_shl,
399 .assign_shl_sat,
400 .assign_shr,
401401 .assign_bit_and,
402402 .assign_bit_xor,
403403 .assign_bit_or,
......@@ -422,9 +422,9 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex {
422422 .sub_wrap,
423423 .add_sat,
424424 .sub_sat,
425 .bit_shift_left,
426 .bit_shift_left_sat,
427 .bit_shift_right,
425 .shl,
426 .shl_sat,
427 .shr,
428428 .bit_and,
429429 .bit_xor,
430430 .bit_or,
......@@ -659,9 +659,9 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex {
659659 .assign_mod,
660660 .assign_add,
661661 .assign_sub,
662 .assign_bit_shift_left,
663 .assign_bit_shift_left_sat,
664 .assign_bit_shift_right,
662 .assign_shl,
663 .assign_shl_sat,
664 .assign_shr,
665665 .assign_bit_and,
666666 .assign_bit_xor,
667667 .assign_bit_or,
......@@ -686,9 +686,9 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex {
686686 .sub_wrap,
687687 .add_sat,
688688 .sub_sat,
689 .bit_shift_left,
690 .bit_shift_left_sat,
691 .bit_shift_right,
689 .shl,
690 .shl_sat,
691 .shr,
692692 .bit_and,
693693 .bit_xor,
694694 .bit_or,
......@@ -2540,11 +2540,11 @@ pub const Node = struct {
25402540 /// `lhs -= rhs`. main_token is op.
25412541 assign_sub,
25422542 /// `lhs <<= rhs`. main_token is op.
2543 assign_bit_shift_left,
2543 assign_shl,
25442544 /// `lhs <<|= rhs`. main_token is op.
2545 assign_bit_shift_left_sat,
2545 assign_shl_sat,
25462546 /// `lhs >>= rhs`. main_token is op.
2547 assign_bit_shift_right,
2547 assign_shr,
25482548 /// `lhs &= rhs`. main_token is op.
25492549 assign_bit_and,
25502550 /// `lhs ^= rhs`. main_token is op.
......@@ -2594,11 +2594,11 @@ pub const Node = struct {
25942594 /// `lhs -| rhs`. main_token is the `-|`.
25952595 sub_sat,
25962596 /// `lhs << rhs`. main_token is the `<<`.
2597 bit_shift_left,
2597 shl,
25982598 /// `lhs <<| rhs`. main_token is the `<<|`.
2599 bit_shift_left_sat,
2599 shl_sat,
26002600 /// `lhs >> rhs`. main_token is the `>>`.
2601 bit_shift_right,
2601 shr,
26022602 /// `lhs & rhs`. main_token is the `&`.
26032603 bit_and,
26042604 /// `lhs ^ rhs`. main_token is the `^`.
lib/std/zig/parse.zig+6-6
......@@ -1268,9 +1268,9 @@ const Parser = struct {
12681268 .percent_equal => .assign_mod,
12691269 .plus_equal => .assign_add,
12701270 .minus_equal => .assign_sub,
1271 .angle_bracket_angle_bracket_left_equal => .assign_bit_shift_left,
1272 .angle_bracket_angle_bracket_left_pipe_equal => .assign_bit_shift_left_sat,
1273 .angle_bracket_angle_bracket_right_equal => .assign_bit_shift_right,
1271 .angle_bracket_angle_bracket_left_equal => .assign_shl,
1272 .angle_bracket_angle_bracket_left_pipe_equal => .assign_shl_sat,
1273 .angle_bracket_angle_bracket_right_equal => .assign_shr,
12741274 .ampersand_equal => .assign_bit_and,
12751275 .caret_equal => .assign_bit_xor,
12761276 .pipe_equal => .assign_bit_or,
......@@ -1346,9 +1346,9 @@ const Parser = struct {
13461346 .keyword_orelse = .{ .prec = 40, .tag = .@"orelse" },
13471347 .keyword_catch = .{ .prec = 40, .tag = .@"catch" },
13481348
1349 .angle_bracket_angle_bracket_left = .{ .prec = 50, .tag = .bit_shift_left },
1350 .angle_bracket_angle_bracket_left_pipe = .{ .prec = 50, .tag = .bit_shift_left_sat },
1351 .angle_bracket_angle_bracket_right = .{ .prec = 50, .tag = .bit_shift_right },
1349 .angle_bracket_angle_bracket_left = .{ .prec = 50, .tag = .shl },
1350 .angle_bracket_angle_bracket_left_pipe = .{ .prec = 50, .tag = .shl_sat },
1351 .angle_bracket_angle_bracket_right = .{ .prec = 50, .tag = .shr },
13521352
13531353 .plus = .{ .prec = 60, .tag = .add },
13541354 .minus = .{ .prec = 60, .tag = .sub },
lib/std/zig/render.zig+10-10
......@@ -339,9 +339,9 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index,
339339 .assign,
340340 .assign_bit_and,
341341 .assign_bit_or,
342 .assign_bit_shift_left,
343 .assign_bit_shift_left_sat,
344 .assign_bit_shift_right,
342 .assign_shl,
343 .assign_shl_sat,
344 .assign_shr,
345345 .assign_bit_xor,
346346 .assign_div,
347347 .assign_sub,
......@@ -357,9 +357,9 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index,
357357 .bang_equal,
358358 .bit_and,
359359 .bit_or,
360 .bit_shift_left,
361 .bit_shift_left_sat,
362 .bit_shift_right,
360 .shl,
361 .shl_sat,
362 .shr,
363363 .bit_xor,
364364 .bool_and,
365365 .bool_or,
......@@ -2528,8 +2528,8 @@ fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool {
25282528 .assign,
25292529 .assign_bit_and,
25302530 .assign_bit_or,
2531 .assign_bit_shift_left,
2532 .assign_bit_shift_right,
2531 .assign_shl,
2532 .assign_shr,
25332533 .assign_bit_xor,
25342534 .assign_div,
25352535 .assign_sub,
......@@ -2542,8 +2542,8 @@ fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool {
25422542 .bang_equal,
25432543 .bit_and,
25442544 .bit_or,
2545 .bit_shift_left,
2546 .bit_shift_right,
2545 .shl,
2546 .shr,
25472547 .bit_xor,
25482548 .bool_and,
25492549 .bool_or,
src/Air.zig+12-6
......@@ -48,7 +48,7 @@ pub const Inst = struct {
4848 /// Both operands are guaranteed to be the same type, and the result type
4949 /// is the same as both operands.
5050 /// Uses the `bin_op` field.
51 addsat,
51 add_sat,
5252 /// Float or integer subtraction. For integers, wrapping is undefined behavior.
5353 /// Both operands are guaranteed to be the same type, and the result type
5454 /// is the same as both operands.
......@@ -63,7 +63,7 @@ pub const Inst = struct {
6363 /// Both operands are guaranteed to be the same type, and the result type
6464 /// is the same as both operands.
6565 /// Uses the `bin_op` field.
66 subsat,
66 sub_sat,
6767 /// Float or integer multiplication. For integers, wrapping is undefined behavior.
6868 /// Both operands are guaranteed to be the same type, and the result type
6969 /// is the same as both operands.
......@@ -78,7 +78,7 @@ pub const Inst = struct {
7878 /// Both operands are guaranteed to be the same type, and the result type
7979 /// is the same as both operands.
8080 /// Uses the `bin_op` field.
81 mulsat,
81 mul_sat,
8282 /// Integer or float division. For integers, wrapping is undefined behavior.
8383 /// Both operands are guaranteed to be the same type, and the result type
8484 /// is the same as both operands.
......@@ -125,6 +125,11 @@ pub const Inst = struct {
125125 /// Shift left. `<<`
126126 /// Uses the `bin_op` field.
127127 shl,
128 /// Shift left; For unsigned integers, the shift produces a poison value if it shifts
129 /// out any non-zero bits. For signed integers, the shift produces a poison value if
130 /// it shifts out any bits that disagree with the resultant sign bit.
131 /// Uses the `bin_op` field.
132 shl_exact,
128133 /// Shift left saturating. `<<|`
129134 /// Uses the `bin_op` field.
130135 shl_sat,
......@@ -586,13 +591,13 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
586591
587592 .add,
588593 .addwrap,
589 .addsat,
594 .add_sat,
590595 .sub,
591596 .subwrap,
592 .subsat,
597 .sub_sat,
593598 .mul,
594599 .mulwrap,
595 .mulsat,
600 .mul_sat,
596601 .div,
597602 .rem,
598603 .mod,
......@@ -603,6 +608,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
603608 .ptr_sub,
604609 .shr,
605610 .shl,
611 .shl_exact,
606612 .shl_sat,
607613 => return air.typeOf(datas[inst].bin_op.lhs),
608614
src/AstGen.zig+54-99
......@@ -317,9 +317,9 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins
317317 .assign,
318318 .assign_bit_and,
319319 .assign_bit_or,
320 .assign_bit_shift_left,
321 .assign_bit_shift_left_sat,
322 .assign_bit_shift_right,
320 .assign_shl,
321 .assign_shl_sat,
322 .assign_shr,
323323 .assign_bit_xor,
324324 .assign_div,
325325 .assign_sub,
......@@ -345,9 +345,9 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins
345345 .mod,
346346 .bit_and,
347347 .bit_or,
348 .bit_shift_left,
349 .bit_shift_left_sat,
350 .bit_shift_right,
348 .shl,
349 .shl_sat,
350 .shr,
351351 .bit_xor,
352352 .bang_equal,
353353 .equal_equal,
......@@ -530,15 +530,15 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
530530 return rvalue(gz, rl, .void_value, node);
531531 },
532532
533 .assign_bit_shift_left => {
533 .assign_shl => {
534534 try assignShift(gz, scope, node, .shl);
535535 return rvalue(gz, rl, .void_value, node);
536536 },
537 .assign_bit_shift_left_sat => {
538 try assignOpExt(gz, scope, node, .shl_with_saturation, Zir.Inst.SaturatingArithmetic);
537 .assign_shl_sat => {
538 try assignShiftSat(gz, scope, node);
539539 return rvalue(gz, rl, .void_value, node);
540540 },
541 .assign_bit_shift_right => {
541 .assign_shr => {
542542 try assignShift(gz, scope, node, .shr);
543543 return rvalue(gz, rl, .void_value, node);
544544 },
......@@ -568,7 +568,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
568568 return rvalue(gz, rl, .void_value, node);
569569 },
570570 .assign_sub_sat => {
571 try assignOpExt(gz, scope, node, .sub_with_saturation, Zir.Inst.SaturatingArithmetic);
571 try assignOp(gz, scope, node, .sub_sat);
572572 return rvalue(gz, rl, .void_value, node);
573573 },
574574 .assign_mod => {
......@@ -584,7 +584,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
584584 return rvalue(gz, rl, .void_value, node);
585585 },
586586 .assign_add_sat => {
587 try assignOpExt(gz, scope, node, .add_with_saturation, Zir.Inst.SaturatingArithmetic);
587 try assignOp(gz, scope, node, .add_sat);
588588 return rvalue(gz, rl, .void_value, node);
589589 },
590590 .assign_mul => {
......@@ -596,28 +596,27 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
596596 return rvalue(gz, rl, .void_value, node);
597597 },
598598 .assign_mul_sat => {
599 try assignOpExt(gz, scope, node, .mul_with_saturation, Zir.Inst.SaturatingArithmetic);
599 try assignOp(gz, scope, node, .mul_sat);
600600 return rvalue(gz, rl, .void_value, node);
601601 },
602602
603603 // zig fmt: off
604 .bit_shift_left => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl),
605 .bit_shift_right => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr),
604 .shl => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl),
605 .shr => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr),
606606
607607 .add => return simpleBinOp(gz, scope, rl, node, .add),
608608 .add_wrap => return simpleBinOp(gz, scope, rl, node, .addwrap),
609 .add_sat => return simpleBinOp(gz, scope, rl, node, .add_sat),
609610 .sub => return simpleBinOp(gz, scope, rl, node, .sub),
610611 .sub_wrap => return simpleBinOp(gz, scope, rl, node, .subwrap),
612 .sub_sat => return simpleBinOp(gz, scope, rl, node, .sub_sat),
611613 .mul => return simpleBinOp(gz, scope, rl, node, .mul),
612614 .mul_wrap => return simpleBinOp(gz, scope, rl, node, .mulwrap),
615 .mul_sat => return simpleBinOp(gz, scope, rl, node, .mul_sat),
613616 .div => return simpleBinOp(gz, scope, rl, node, .div),
614617 .mod => return simpleBinOp(gz, scope, rl, node, .mod_rem),
618 .shl_sat => return simpleBinOp(gz, scope, rl, node, .shl_sat),
615619
616 .add_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation, Zir.Inst.SaturatingArithmetic),
617 .sub_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation, Zir.Inst.SaturatingArithmetic),
618 .mul_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation, Zir.Inst.SaturatingArithmetic),
619 .bit_shift_left_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation, Zir.Inst.SaturatingArithmetic),
620
621620 .bit_and => {
622621 const current_ampersand_token = main_tokens[node];
623622 if (token_tags[current_ampersand_token + 1] == .ampersand) {
......@@ -1928,8 +1927,8 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod
19281927
19291928 .assign => try assign(gz, scope, statement),
19301929
1931 .assign_bit_shift_left => try assignShift(gz, scope, statement, .shl),
1932 .assign_bit_shift_right => try assignShift(gz, scope, statement, .shr),
1930 .assign_shl => try assignShift(gz, scope, statement, .shl),
1931 .assign_shr => try assignShift(gz, scope, statement, .shr),
19331932
19341933 .assign_bit_and => try assignOp(gz, scope, statement, .bit_and),
19351934 .assign_bit_or => try assignOp(gz, scope, statement, .bit_or),
......@@ -1979,6 +1978,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
19791978 // ZIR instructions that might be a type other than `noreturn` or `void`.
19801979 .add,
19811980 .addwrap,
1981 .add_sat,
19821982 .param,
19831983 .param_comptime,
19841984 .param_anytype,
......@@ -2045,12 +2045,15 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
20452045 .mod_rem,
20462046 .mul,
20472047 .mulwrap,
2048 .mul_sat,
20482049 .ref,
20492050 .shl,
2051 .shl_sat,
20502052 .shr,
20512053 .str,
20522054 .sub,
20532055 .subwrap,
2056 .sub_sat,
20542057 .negate,
20552058 .negate_wrap,
20562059 .typeof,
......@@ -2715,55 +2718,30 @@ fn assignOp(
27152718 _ = try gz.addBin(.store, lhs_ptr, result);
27162719}
27172720
2718fn simpleBinOpExt(
2719 gz: *GenZir,
2720 scope: *Scope,
2721 rl: ResultLoc,
2722 infix_node: Ast.Node.Index,
2723 lhs_node: Ast.Node.Index,
2724 rhs_node: Ast.Node.Index,
2725 tag: Zir.Inst.Extended,
2726 comptime T: type,
2727) InnerError!Zir.Inst.Ref {
2728 const lhs = try expr(gz, scope, .none, lhs_node);
2729 const rhs = try expr(gz, scope, .none, rhs_node);
2730 const result = try gz.addExtendedPayload(tag, T{
2731 .node = gz.nodeIndexToRelative(infix_node),
2732 .lhs = lhs,
2733 .rhs = rhs,
2734 });
2735 return rvalue(gz, rl, result, infix_node);
2736}
2737
2738fn assignOpExt(
2721fn assignShift(
27392722 gz: *GenZir,
27402723 scope: *Scope,
27412724 infix_node: Ast.Node.Index,
2742 op_inst_tag: Zir.Inst.Extended,
2743 comptime T: type,
2725 op_inst_tag: Zir.Inst.Tag,
27442726) InnerError!void {
2727 try emitDbgNode(gz, infix_node);
27452728 const astgen = gz.astgen;
27462729 const tree = astgen.tree;
27472730 const node_datas = tree.nodes.items(.data);
27482731
27492732 const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs);
27502733 const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
2751 const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node);
2752 const rhs = try expr(gz, scope, .{ .coerced_ty = lhs_type }, node_datas[infix_node].rhs);
2753 const result = try gz.addExtendedPayload(op_inst_tag, T{
2754 .node = gz.nodeIndexToRelative(infix_node),
2734 const rhs_type = try gz.addUnNode(.typeof_log2_int_type, lhs, infix_node);
2735 const rhs = try expr(gz, scope, .{ .ty = rhs_type }, node_datas[infix_node].rhs);
2736
2737 const result = try gz.addPlNode(op_inst_tag, infix_node, Zir.Inst.Bin{
27552738 .lhs = lhs,
27562739 .rhs = rhs,
27572740 });
27582741 _ = try gz.addBin(.store, lhs_ptr, result);
27592742}
27602743
2761fn assignShift(
2762 gz: *GenZir,
2763 scope: *Scope,
2764 infix_node: Ast.Node.Index,
2765 op_inst_tag: Zir.Inst.Tag,
2766) InnerError!void {
2744fn assignShiftSat(gz: *GenZir, scope: *Scope, infix_node: Ast.Node.Index) InnerError!void {
27672745 try emitDbgNode(gz, infix_node);
27682746 const astgen = gz.astgen;
27692747 const tree = astgen.tree;
......@@ -2771,10 +2749,10 @@ fn assignShift(
27712749
27722750 const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs);
27732751 const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
2774 const rhs_type = try gz.addUnNode(.typeof_log2_int_type, lhs, infix_node);
2775 const rhs = try expr(gz, scope, .{ .ty = rhs_type }, node_datas[infix_node].rhs);
2752 // Saturating shift-left allows any integer type for both the LHS and RHS.
2753 const rhs = try expr(gz, scope, .none, node_datas[infix_node].rhs);
27762754
2777 const result = try gz.addPlNode(op_inst_tag, infix_node, Zir.Inst.Bin{
2755 const result = try gz.addPlNode(.shl_sat, infix_node, Zir.Inst.Bin{
27782756 .lhs = lhs,
27792757 .rhs = rhs,
27802758 });
......@@ -7556,11 +7534,6 @@ fn builtinCall(
75567534 return rvalue(gz, rl, result, node);
75577535 },
75587536
7559 .add_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .add_with_saturation),
7560 .sub_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .sub_with_saturation),
7561 .mul_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .mul_with_saturation),
7562 .shl_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .shl_with_saturation),
7563
75647537 .atomic_load => {
75657538 const int_type = try typeExpr(gz, scope, params[0]);
75667539 // TODO allow this pointer type to be volatile
......@@ -7955,24 +7928,6 @@ fn overflowArithmetic(
79557928 return rvalue(gz, rl, result, node);
79567929}
79577930
7958fn saturatingArithmetic(
7959 gz: *GenZir,
7960 scope: *Scope,
7961 rl: ResultLoc,
7962 node: Ast.Node.Index,
7963 params: []const Ast.Node.Index,
7964 tag: Zir.Inst.Extended,
7965) InnerError!Zir.Inst.Ref {
7966 const lhs = try expr(gz, scope, .none, params[0]);
7967 const rhs = try expr(gz, scope, .none, params[1]);
7968 const result = try gz.addExtendedPayload(tag, Zir.Inst.SaturatingArithmetic{
7969 .node = gz.nodeIndexToRelative(node),
7970 .lhs = lhs,
7971 .rhs = rhs,
7972 });
7973 return rvalue(gz, rl, result, node);
7974}
7975
79767931fn callExpr(
79777932 gz: *GenZir,
79787933 scope: *Scope,
......@@ -8198,9 +8153,9 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool
81988153 .assign,
81998154 .assign_bit_and,
82008155 .assign_bit_or,
8201 .assign_bit_shift_left,
8202 .assign_bit_shift_left_sat,
8203 .assign_bit_shift_right,
8156 .assign_shl,
8157 .assign_shl_sat,
8158 .assign_shr,
82048159 .assign_bit_xor,
82058160 .assign_div,
82068161 .assign_sub,
......@@ -8216,9 +8171,9 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool
82168171 .bang_equal,
82178172 .bit_and,
82188173 .bit_or,
8219 .bit_shift_left,
8220 .bit_shift_left_sat,
8221 .bit_shift_right,
8174 .shl,
8175 .shl_sat,
8176 .shr,
82228177 .bit_xor,
82238178 .bool_and,
82248179 .bool_or,
......@@ -8439,9 +8394,9 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) enum { never
84398394 .assign,
84408395 .assign_bit_and,
84418396 .assign_bit_or,
8442 .assign_bit_shift_left,
8443 .assign_bit_shift_left_sat,
8444 .assign_bit_shift_right,
8397 .assign_shl,
8398 .assign_shl_sat,
8399 .assign_shr,
84458400 .assign_bit_xor,
84468401 .assign_div,
84478402 .assign_sub,
......@@ -8457,9 +8412,9 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) enum { never
84578412 .bang_equal,
84588413 .bit_and,
84598414 .bit_or,
8460 .bit_shift_left,
8461 .bit_shift_left_sat,
8462 .bit_shift_right,
8415 .shl,
8416 .shl_sat,
8417 .shr,
84638418 .bit_xor,
84648419 .bool_and,
84658420 .bool_or,
......@@ -8619,9 +8574,9 @@ fn nodeImpliesRuntimeBits(tree: *const Ast, start_node: Ast.Node.Index) bool {
86198574 .assign,
86208575 .assign_bit_and,
86218576 .assign_bit_or,
8622 .assign_bit_shift_left,
8623 .assign_bit_shift_left_sat,
8624 .assign_bit_shift_right,
8577 .assign_shl,
8578 .assign_shl_sat,
8579 .assign_shr,
86258580 .assign_bit_xor,
86268581 .assign_div,
86278582 .assign_sub,
......@@ -8637,9 +8592,9 @@ fn nodeImpliesRuntimeBits(tree: *const Ast, start_node: Ast.Node.Index) bool {
86378592 .bang_equal,
86388593 .bit_and,
86398594 .bit_or,
8640 .bit_shift_left,
8641 .bit_shift_left_sat,
8642 .bit_shift_right,
8595 .shl,
8596 .shl_sat,
8597 .shr,
86438598 .bit_xor,
86448599 .bool_and,
86458600 .bool_or,
src/BuiltinFn.zig-32
......@@ -2,7 +2,6 @@ const std = @import("std");
22
33pub const Tag = enum {
44 add_with_overflow,
5 add_with_saturation,
65 align_cast,
76 align_of,
87 as,
......@@ -66,7 +65,6 @@ pub const Tag = enum {
6665 wasm_memory_grow,
6766 mod,
6867 mul_with_overflow,
69 mul_with_saturation,
7068 panic,
7169 pop_count,
7270 ptr_cast,
......@@ -81,12 +79,10 @@ pub const Tag = enum {
8179 set_runtime_safety,
8280 shl_exact,
8381 shl_with_overflow,
84 shl_with_saturation,
8582 shr_exact,
8683 shuffle,
8784 size_of,
8885 splat,
89 sub_with_saturation,
9086 reduce,
9187 src,
9288 sqrt,
......@@ -531,34 +527,6 @@ pub const list = list: {
531527 .param_count = 2,
532528 },
533529 },
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 },
562530 .{
563531 "@memcpy",
564532 .{
src/Liveness.zig+4-3
......@@ -226,13 +226,13 @@ fn analyzeInst(
226226 switch (inst_tags[inst]) {
227227 .add,
228228 .addwrap,
229 .addsat,
229 .add_sat,
230230 .sub,
231231 .subwrap,
232 .subsat,
232 .sub_sat,
233233 .mul,
234234 .mulwrap,
235 .mulsat,
235 .mul_sat,
236236 .div,
237237 .rem,
238238 .mod,
......@@ -255,6 +255,7 @@ fn analyzeInst(
255255 .ptr_elem_val,
256256 .ptr_ptr_elem_val,
257257 .shl,
258 .shl_exact,
258259 .shl_sat,
259260 .shr,
260261 .atomic_store_unordered,
src/Sema.zig+113-121
......@@ -246,7 +246,6 @@ pub fn analyzeBody(
246246 .ptr_type_simple => try sema.zirPtrTypeSimple(block, inst),
247247 .ref => try sema.zirRef(block, inst),
248248 .ret_err_value_code => try sema.zirRetErrValueCode(block, inst),
249 .shl => try sema.zirShl(block, inst),
250249 .shr => try sema.zirShr(block, inst),
251250 .slice_end => try sema.zirSliceEnd(block, inst),
252251 .slice_sentinel => try sema.zirSliceSentinel(block, inst),
......@@ -319,7 +318,6 @@ pub fn analyzeBody(
319318 .div_exact => try sema.zirDivExact(block, inst),
320319 .div_floor => try sema.zirDivFloor(block, inst),
321320 .div_trunc => try sema.zirDivTrunc(block, inst),
322 .shl_exact => try sema.zirShlExact(block, inst),
323321 .shr_exact => try sema.zirShrExact(block, inst),
324322 .bit_offset_of => try sema.zirBitOffsetOf(block, inst),
325323 .offset_of => try sema.zirOffsetOf(block, inst),
......@@ -363,14 +361,21 @@ pub fn analyzeBody(
363361
364362 .add => try sema.zirArithmetic(block, inst, .add),
365363 .addwrap => try sema.zirArithmetic(block, inst, .addwrap),
364 .add_sat => try sema.zirArithmetic(block, inst, .add_sat),
366365 .div => try sema.zirArithmetic(block, inst, .div),
367366 .mod_rem => try sema.zirArithmetic(block, inst, .mod_rem),
368367 .mod => try sema.zirArithmetic(block, inst, .mod),
369368 .rem => try sema.zirArithmetic(block, inst, .rem),
370369 .mul => try sema.zirArithmetic(block, inst, .mul),
371370 .mulwrap => try sema.zirArithmetic(block, inst, .mulwrap),
371 .mul_sat => try sema.zirArithmetic(block, inst, .mul_sat),
372372 .sub => try sema.zirArithmetic(block, inst, .sub),
373373 .subwrap => try sema.zirArithmetic(block, inst, .subwrap),
374 .sub_sat => try sema.zirArithmetic(block, inst, .sub_sat),
375
376 .shl => try sema.zirShl(block, inst, .shl),
377 .shl_exact => try sema.zirShl(block, inst, .shl_exact),
378 .shl_sat => try sema.zirShl(block, inst, .shl_sat),
374379
375380 // Instructions that we know to *always* be noreturn based solely on their tag.
376381 // These functions match the return type of analyzeBody so that we can
......@@ -694,11 +699,6 @@ fn zirExtended(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
694699 .c_define => return sema.zirCDefine( block, extended),
695700 .wasm_memory_size => return sema.zirWasmMemorySize( block, extended),
696701 .wasm_memory_grow => return sema.zirWasmMemoryGrow( block, extended),
697 .add_with_saturation,
698 .sub_with_saturation,
699 .mul_with_saturation,
700 .shl_with_saturation,
701 => return sema.zirSatArithmetic( block, extended),
702702 // zig fmt: on
703703 }
704704}
......@@ -5875,7 +5875,12 @@ fn zirRetErrValueCode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co
58755875 return sema.mod.fail(&block.base, sema.src, "TODO implement zirRetErrValueCode", .{});
58765876}
58775877
5878fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
5878fn zirShl(
5879 sema: *Sema,
5880 block: *Scope.Block,
5881 inst: Zir.Inst.Index,
5882 air_tag: Air.Inst.Tag,
5883) CompileError!Air.Inst.Ref {
58795884 const tracy = trace(@src());
58805885 defer tracy.end();
58815886
......@@ -5886,6 +5891,8 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A
58865891 const lhs = sema.resolveInst(extra.lhs);
58875892 const rhs = sema.resolveInst(extra.rhs);
58885893
5894 // TODO coerce rhs if air_tag is not shl_sat
5895
58895896 const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs);
58905897 const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs);
58915898
......@@ -5901,6 +5908,12 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A
59015908 return sema.addConstant(lhs_ty, lhs_val);
59025909 }
59035910 const val = try lhs_val.shl(rhs_val, sema.arena);
5911 switch (air_tag) {
5912 .shl_exact => return sema.mod.fail(&block.base, lhs_src, "TODO implement Sema for comptime shl_exact", .{}),
5913 .shl_sat => return sema.mod.fail(&block.base, lhs_src, "TODO implement Sema for comptime shl_sat", .{}),
5914 .shl => {},
5915 else => unreachable,
5916 }
59045917 return sema.addConstant(lhs_ty, val);
59055918 } else rs: {
59065919 if (maybe_rhs_val) |rhs_val| {
......@@ -5909,8 +5922,10 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A
59095922 break :rs lhs_src;
59105923 };
59115924
5925 // TODO: insert runtime safety check for shl_exact
5926
59125927 try sema.requireRuntimeBlock(block, runtime_src);
5913 return block.addBinOp(.shl, lhs, rhs);
5928 return block.addBinOp(air_tag, lhs, rhs);
59145929}
59155930
59165931fn zirShr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
......@@ -6201,105 +6216,6 @@ fn zirOverflowArithmetic(
62016216 return sema.mod.fail(&block.base, src, "TODO implement Sema.zirOverflowArithmetic", .{});
62026217}
62036218
6204fn zirSatArithmetic(
6205 sema: *Sema,
6206 block: *Scope.Block,
6207 extended: Zir.Inst.Extended.InstData,
6208) CompileError!Air.Inst.Ref {
6209 const tracy = trace(@src());
6210 defer tracy.end();
6211
6212 const extra = sema.code.extraData(Zir.Inst.SaturatingArithmetic, extended.operand).data;
6213 sema.src = .{ .node_offset_bin_op = extra.node };
6214 const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = extra.node };
6215 const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = extra.node };
6216 const lhs = sema.resolveInst(extra.lhs);
6217 const rhs = sema.resolveInst(extra.rhs);
6218
6219 return sema.analyzeSatArithmetic(block, lhs, rhs, sema.src, lhs_src, rhs_src, extended);
6220}
6221
6222fn analyzeSatArithmetic(
6223 sema: *Sema,
6224 block: *Scope.Block,
6225 lhs: Air.Inst.Ref,
6226 rhs: Air.Inst.Ref,
6227 src: LazySrcLoc,
6228 lhs_src: LazySrcLoc,
6229 rhs_src: LazySrcLoc,
6230 extended: Zir.Inst.Extended.InstData,
6231) CompileError!Air.Inst.Ref {
6232 const lhs_ty = sema.typeOf(lhs);
6233 const rhs_ty = sema.typeOf(rhs);
6234 const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison();
6235 const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison();
6236 if (lhs_zig_ty_tag == .Vector and rhs_zig_ty_tag == .Vector) {
6237 if (lhs_ty.arrayLen() != rhs_ty.arrayLen()) {
6238 return sema.mod.fail(&block.base, src, "vector length mismatch: {d} and {d}", .{
6239 lhs_ty.arrayLen(), rhs_ty.arrayLen(),
6240 });
6241 }
6242 return sema.mod.fail(&block.base, src, "TODO implement support for vectors in zirBinOp", .{});
6243 } else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) {
6244 return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{
6245 lhs_ty, rhs_ty,
6246 });
6247 }
6248
6249 if (lhs_zig_ty_tag == .Pointer or rhs_zig_ty_tag == .Pointer)
6250 return sema.mod.fail(&block.base, src, "TODO implement support for pointers in zirSatArithmetic", .{});
6251
6252 const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
6253 const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ .override = &[_]LazySrcLoc{ lhs_src, rhs_src } });
6254 const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
6255 const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
6256
6257 const scalar_type = if (resolved_type.zigTypeTag() == .Vector)
6258 resolved_type.elemType()
6259 else
6260 resolved_type;
6261
6262 const scalar_tag = scalar_type.zigTypeTag();
6263
6264 const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
6265
6266 if (!is_int)
6267 return sema.mod.fail(&block.base, src, "invalid operands to binary expression: '{s}' and '{s}'", .{
6268 @tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag),
6269 });
6270
6271 if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| {
6272 if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| {
6273 if (lhs_val.isUndef() or rhs_val.isUndef()) {
6274 return sema.addConstUndef(resolved_type);
6275 }
6276 // incase rhs is 0, simply return lhs without doing any calculations
6277 if (rhs_val.compareWithZero(.eq)) {
6278 switch (extended.opcode) {
6279 .add_with_saturation, .sub_with_saturation => return sema.addConstant(scalar_type, lhs_val),
6280 else => {},
6281 }
6282 }
6283
6284 return sema.mod.fail(&block.base, src, "TODO implement comptime saturating arithmetic for operand '{s}'", .{@tagName(extended.opcode)});
6285 } else {
6286 try sema.requireRuntimeBlock(block, rhs_src);
6287 }
6288 } else {
6289 try sema.requireRuntimeBlock(block, lhs_src);
6290 }
6291
6292 const air_tag: Air.Inst.Tag = switch (extended.opcode) {
6293 .add_with_saturation => .addsat,
6294 .sub_with_saturation => .subsat,
6295 .mul_with_saturation => .mulsat,
6296 .shl_with_saturation => .shl_sat,
6297 else => return sema.mod.fail(&block.base, src, "TODO implement arithmetic for extended opcode '{s}'", .{@tagName(extended.opcode)}),
6298 };
6299
6300 return block.addBinOp(air_tag, casted_lhs, casted_rhs);
6301}
6302
63036219fn analyzeArithmetic(
63046220 sema: *Sema,
63056221 block: *Scope.Block,
......@@ -6441,8 +6357,7 @@ fn analyzeArithmetic(
64416357 },
64426358 .addwrap => {
64436359 // Integers only; floats are checked above.
6444 // If either of the operands are zero, then the other operand is
6445 // returned, even if it is undefined.
6360 // If either of the operands are zero, the other operand is returned.
64466361 // If either of the operands are undefined, the result is undefined.
64476362 if (maybe_lhs_val) |lhs_val| {
64486363 if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) {
......@@ -6464,6 +6379,30 @@ fn analyzeArithmetic(
64646379 } else break :rs .{ .src = lhs_src, .air_tag = .addwrap };
64656380 } else break :rs .{ .src = rhs_src, .air_tag = .addwrap };
64666381 },
6382 .add_sat => {
6383 // For both integers and floats:
6384 // If either of the operands are zero, then the other operand is returned.
6385 // If either of the operands are undefined, the result is undefined.
6386 if (maybe_lhs_val) |lhs_val| {
6387 if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) {
6388 return casted_rhs;
6389 }
6390 }
6391 if (maybe_rhs_val) |rhs_val| {
6392 if (rhs_val.isUndef()) {
6393 return sema.addConstUndef(scalar_type);
6394 }
6395 if (rhs_val.compareWithZero(.eq)) {
6396 return casted_lhs;
6397 }
6398 if (maybe_lhs_val) |lhs_val| {
6399 return sema.addConstant(
6400 scalar_type,
6401 try lhs_val.numberAddSat(rhs_val, scalar_type, sema.arena, target),
6402 );
6403 } else break :rs .{ .src = lhs_src, .air_tag = .add_sat };
6404 } else break :rs .{ .src = rhs_src, .air_tag = .add_sat };
6405 },
64676406 .sub => {
64686407 // For integers:
64696408 // If the rhs is zero, then the other operand is
......@@ -6531,6 +6470,30 @@ fn analyzeArithmetic(
65316470 } else break :rs .{ .src = rhs_src, .air_tag = .subwrap };
65326471 } else break :rs .{ .src = lhs_src, .air_tag = .subwrap };
65336472 },
6473 .sub_sat => {
6474 // For both integers and floats:
6475 // If the RHS is zero, result is LHS.
6476 // If either of the operands are undefined, result is undefined.
6477 if (maybe_rhs_val) |rhs_val| {
6478 if (rhs_val.isUndef()) {
6479 return sema.addConstUndef(scalar_type);
6480 }
6481 if (rhs_val.compareWithZero(.eq)) {
6482 return casted_lhs;
6483 }
6484 }
6485 if (maybe_lhs_val) |lhs_val| {
6486 if (lhs_val.isUndef()) {
6487 return sema.addConstUndef(scalar_type);
6488 }
6489 if (maybe_rhs_val) |rhs_val| {
6490 return sema.addConstant(
6491 scalar_type,
6492 try lhs_val.numberSubSat(rhs_val, scalar_type, sema.arena, target),
6493 );
6494 } else break :rs .{ .src = rhs_src, .air_tag = .sub_sat };
6495 } else break :rs .{ .src = lhs_src, .air_tag = .sub_sat };
6496 },
65346497 .div => {
65356498 // For integers:
65366499 // If the lhs is zero, then zero is returned regardless of rhs.
......@@ -6649,10 +6612,9 @@ fn analyzeArithmetic(
66496612 },
66506613 .mulwrap => {
66516614 // Integers only; floats are handled above.
6652 // If either of the operands are zero, the result is zero.
6653 // If either of the operands are one, the result is the other
6654 // operand, even if it is undefined.
6655 // If either of the operands are undefined, the result is undefined.
6615 // If either of the operands are zero, result is zero.
6616 // If either of the operands are one, result is the other operand.
6617 // If either of the operands are undefined, result is undefined.
66566618 if (maybe_lhs_val) |lhs_val| {
66576619 if (!lhs_val.isUndef()) {
66586620 if (lhs_val.compareWithZero(.eq)) {
......@@ -6684,6 +6646,42 @@ fn analyzeArithmetic(
66846646 } else break :rs .{ .src = lhs_src, .air_tag = .mulwrap };
66856647 } else break :rs .{ .src = rhs_src, .air_tag = .mulwrap };
66866648 },
6649 .mul_sat => {
6650 // For both integers and floats:
6651 // If either of the operands are zero, result is zero.
6652 // If either of the operands are one, result is the other operand.
6653 // If either of the operands are undefined, result is undefined.
6654 if (maybe_lhs_val) |lhs_val| {
6655 if (!lhs_val.isUndef()) {
6656 if (lhs_val.compareWithZero(.eq)) {
6657 return sema.addConstant(scalar_type, Value.zero);
6658 }
6659 if (lhs_val.compare(.eq, Value.one, scalar_type)) {
6660 return casted_rhs;
6661 }
6662 }
6663 }
6664 if (maybe_rhs_val) |rhs_val| {
6665 if (rhs_val.isUndef()) {
6666 return sema.addConstUndef(scalar_type);
6667 }
6668 if (rhs_val.compareWithZero(.eq)) {
6669 return sema.addConstant(scalar_type, Value.zero);
6670 }
6671 if (rhs_val.compare(.eq, Value.one, scalar_type)) {
6672 return casted_lhs;
6673 }
6674 if (maybe_lhs_val) |lhs_val| {
6675 if (lhs_val.isUndef()) {
6676 return sema.addConstUndef(scalar_type);
6677 }
6678 return sema.addConstant(
6679 scalar_type,
6680 try lhs_val.numberMulSat(rhs_val, scalar_type, sema.arena, target),
6681 );
6682 } else break :rs .{ .src = lhs_src, .air_tag = .mul_sat };
6683 } else break :rs .{ .src = rhs_src, .air_tag = .mul_sat };
6684 },
66876685 .mod_rem => {
66886686 // For integers:
66896687 // Either operand being undef is a compile error because there exists
......@@ -7933,7 +7931,7 @@ fn analyzeRet(
79337931fn floatOpAllowed(tag: Zir.Inst.Tag) bool {
79347932 // extend this swich as additional operators are implemented
79357933 return switch (tag) {
7936 .add, .sub, .mul, .div, .mod, .rem, .mod_rem => true,
7934 .add, .add_sat, .sub, .sub_sat, .mul, .mul_sat, .div, .mod, .rem, .mod_rem => true,
79377935 else => false,
79387936 };
79397937}
......@@ -8600,12 +8598,6 @@ fn zirDivTrunc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
86008598 return sema.mod.fail(&block.base, src, "TODO: Sema.zirDivTrunc", .{});
86018599}
86028600
8603fn zirShlExact(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
8604 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
8605 const src = inst_data.src();
8606 return sema.mod.fail(&block.base, src, "TODO: Sema.zirShlExact", .{});
8607}
8608
86098601fn zirShrExact(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
86108602 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
86118603 const src = inst_data.src();
src/Zir.zig+71-72
......@@ -126,6 +126,64 @@ pub const Inst = struct {
126126 /// Twos complement wrapping integer addition.
127127 /// Uses the `pl_node` union field. Payload is `Bin`.
128128 addwrap,
129 /// Saturating addition.
130 /// Uses the `pl_node` union field. Payload is `Bin`.
131 add_sat,
132 /// Arithmetic subtraction. Asserts no integer overflow.
133 /// Uses the `pl_node` union field. Payload is `Bin`.
134 sub,
135 /// Twos complement wrapping integer subtraction.
136 /// Uses the `pl_node` union field. Payload is `Bin`.
137 subwrap,
138 /// Saturating subtraction.
139 /// Uses the `pl_node` union field. Payload is `Bin`.
140 sub_sat,
141 /// Arithmetic multiplication. Asserts no integer overflow.
142 /// Uses the `pl_node` union field. Payload is `Bin`.
143 mul,
144 /// Twos complement wrapping integer multiplication.
145 /// Uses the `pl_node` union field. Payload is `Bin`.
146 mulwrap,
147 /// Saturating multiplication.
148 /// Uses the `pl_node` union field. Payload is `Bin`.
149 mul_sat,
150 /// Implements the `@divExact` builtin.
151 /// Uses the `pl_node` union field with payload `Bin`.
152 div_exact,
153 /// Implements the `@divFloor` builtin.
154 /// Uses the `pl_node` union field with payload `Bin`.
155 div_floor,
156 /// Implements the `@divTrunc` builtin.
157 /// Uses the `pl_node` union field with payload `Bin`.
158 div_trunc,
159 /// Implements the `@mod` builtin.
160 /// Uses the `pl_node` union field with payload `Bin`.
161 mod,
162 /// Implements the `@rem` builtin.
163 /// Uses the `pl_node` union field with payload `Bin`.
164 rem,
165 /// Ambiguously remainder division or modulus. If the computation would possibly have
166 /// a different value depending on whether the operation is remainder division or modulus,
167 /// a compile error is emitted. Otherwise the computation is performed.
168 /// Uses the `pl_node` union field. Payload is `Bin`.
169 mod_rem,
170 /// Integer shift-left. Zeroes are shifted in from the right hand side.
171 /// Uses the `pl_node` union field. Payload is `Bin`.
172 shl,
173 /// Implements the `@shlExact` builtin.
174 /// Uses the `pl_node` union field with payload `Bin`.
175 shl_exact,
176 /// Saturating shift-left.
177 /// Uses the `pl_node` union field. Payload is `Bin`.
178 shl_sat,
179 /// Integer shift-right. Arithmetic or logical depending on the signedness of
180 /// the integer type.
181 /// Uses the `pl_node` union field. Payload is `Bin`.
182 shr,
183 /// Implements the `@shrExact` builtin.
184 /// Uses the `pl_node` union field with payload `Bin`.
185 shr_exact,
186
129187 /// Declares a parameter of the current function. Used for:
130188 /// * debug info
131189 /// * checking shadowing against declarations in the current namespace
......@@ -471,12 +529,6 @@ pub const Inst = struct {
471529 /// String Literal. Makes an anonymous Decl and then takes a pointer to it.
472530 /// Uses the `str` union field.
473531 str,
474 /// Arithmetic subtraction. Asserts no integer overflow.
475 /// Uses the `pl_node` union field. Payload is `Bin`.
476 sub,
477 /// Twos complement wrapping integer subtraction.
478 /// Uses the `pl_node` union field. Payload is `Bin`.
479 subwrap,
480532 /// Arithmetic negation. Asserts no integer overflow.
481533 /// Same as sub with a lhs of 0, split into a separate instruction to save memory.
482534 /// Uses `un_node`.
......@@ -802,46 +854,6 @@ pub const Inst = struct {
802854 /// Implements the `@bitReverse` builtin. Uses the `un_node` union field.
803855 bit_reverse,
804856
805 /// Implements the `@divExact` builtin.
806 /// Uses the `pl_node` union field with payload `Bin`.
807 div_exact,
808 /// Implements the `@divFloor` builtin.
809 /// Uses the `pl_node` union field with payload `Bin`.
810 div_floor,
811 /// Implements the `@divTrunc` builtin.
812 /// Uses the `pl_node` union field with payload `Bin`.
813 div_trunc,
814 /// Implements the `@mod` builtin.
815 /// Uses the `pl_node` union field with payload `Bin`.
816 mod,
817 /// Implements the `@rem` builtin.
818 /// Uses the `pl_node` union field with payload `Bin`.
819 rem,
820 /// Ambiguously remainder division or modulus. If the computation would possibly have
821 /// a different value depending on whether the operation is remainder division or modulus,
822 /// a compile error is emitted. Otherwise the computation is performed.
823 /// Uses the `pl_node` union field. Payload is `Bin`.
824 mod_rem,
825 /// Arithmetic multiplication. Asserts no integer overflow.
826 /// Uses the `pl_node` union field. Payload is `Bin`.
827 mul,
828 /// Twos complement wrapping integer multiplication.
829 /// Uses the `pl_node` union field. Payload is `Bin`.
830 mulwrap,
831
832 /// Integer shift-left. Zeroes are shifted in from the right hand side.
833 /// Uses the `pl_node` union field. Payload is `Bin`.
834 shl,
835 /// Implements the `@shlExact` builtin.
836 /// Uses the `pl_node` union field with payload `Bin`.
837 shl_exact,
838 /// Integer shift-right. Arithmetic or logical depending on the signedness of the integer type.
839 /// Uses the `pl_node` union field. Payload is `Bin`.
840 shr,
841 /// Implements the `@shrExact` builtin.
842 /// Uses the `pl_node` union field with payload `Bin`.
843 shr_exact,
844
845857 /// Implements the `@bitOffsetOf` builtin.
846858 /// Uses the `pl_node` union field with payload `Bin`.
847859 bit_offset_of,
......@@ -961,6 +973,7 @@ pub const Inst = struct {
961973 .param_anytype_comptime,
962974 .add,
963975 .addwrap,
976 .add_sat,
964977 .alloc,
965978 .alloc_mut,
966979 .alloc_comptime,
......@@ -1035,8 +1048,10 @@ pub const Inst = struct {
10351048 .mod_rem,
10361049 .mul,
10371050 .mulwrap,
1051 .mul_sat,
10381052 .ref,
10391053 .shl,
1054 .shl_sat,
10401055 .shr,
10411056 .store,
10421057 .store_node,
......@@ -1045,6 +1060,7 @@ pub const Inst = struct {
10451060 .str,
10461061 .sub,
10471062 .subwrap,
1063 .sub_sat,
10481064 .negate,
10491065 .negate_wrap,
10501066 .typeof,
......@@ -1218,6 +1234,14 @@ pub const Inst = struct {
12181234 break :list std.enums.directEnumArray(Tag, Data.FieldEnum, 0, .{
12191235 .add = .pl_node,
12201236 .addwrap = .pl_node,
1237 .add_sat = .pl_node,
1238 .sub = .pl_node,
1239 .subwrap = .pl_node,
1240 .sub_sat = .pl_node,
1241 .mul = .pl_node,
1242 .mulwrap = .pl_node,
1243 .mul_sat = .pl_node,
1244
12211245 .param = .pl_tok,
12221246 .param_comptime = .pl_tok,
12231247 .param_anytype = .str_tok,
......@@ -1297,8 +1321,6 @@ pub const Inst = struct {
12971321 .repeat_inline = .node,
12981322 .merge_error_sets = .pl_node,
12991323 .mod_rem = .pl_node,
1300 .mul = .pl_node,
1301 .mulwrap = .pl_node,
13021324 .ref = .un_tok,
13031325 .ret_node = .un_node,
13041326 .ret_load = .un_node,
......@@ -1315,8 +1337,6 @@ pub const Inst = struct {
13151337 .store_to_block_ptr = .bin,
13161338 .store_to_inferred_ptr = .bin,
13171339 .str = .str,
1318 .sub = .pl_node,
1319 .subwrap = .pl_node,
13201340 .negate = .un_node,
13211341 .negate_wrap = .un_node,
13221342 .typeof = .un_node,
......@@ -1437,6 +1457,7 @@ pub const Inst = struct {
14371457
14381458 .shl = .pl_node,
14391459 .shl_exact = .pl_node,
1460 .shl_sat = .pl_node,
14401461 .shr = .pl_node,
14411462 .shr_exact = .pl_node,
14421463
......@@ -1593,22 +1614,6 @@ pub const Inst = struct {
15931614 wasm_memory_size,
15941615 /// `operand` is payload index to `BinNode`.
15951616 wasm_memory_grow,
1596 /// Implements the `@addWithSaturation` builtin.
1597 /// `operand` is payload index to `SaturatingArithmetic`.
1598 /// `small` is unused.
1599 add_with_saturation,
1600 /// Implements the `@subWithSaturation` builtin.
1601 /// `operand` is payload index to `SaturatingArithmetic`.
1602 /// `small` is unused.
1603 sub_with_saturation,
1604 /// Implements the `@mulWithSaturation` builtin.
1605 /// `operand` is payload index to `SaturatingArithmetic`.
1606 /// `small` is unused.
1607 mul_with_saturation,
1608 /// Implements the `@shlWithSaturation` builtin.
1609 /// `operand` is payload index to `SaturatingArithmetic`.
1610 /// `small` is unused.
1611 shl_with_saturation,
16121617
16131618 pub const InstData = struct {
16141619 opcode: Extended,
......@@ -2788,12 +2793,6 @@ pub const Inst = struct {
27882793 ptr: Ref,
27892794 };
27902795
2791 pub const SaturatingArithmetic = struct {
2792 node: i32,
2793 lhs: Ref,
2794 rhs: Ref,
2795 };
2796
27972796 pub const Cmpxchg = struct {
27982797 ptr: Ref,
27992798 expected_value: Ref,
src/codegen.zig+40-16
......@@ -824,18 +824,20 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
824824
825825 switch (air_tags[inst]) {
826826 // zig fmt: off
827 .add, .ptr_add => try self.airAdd(inst),
828 .addwrap => try self.airAddWrap(inst),
829 .addsat => try self.airArithmeticOpSat(inst, "addsat"),
830 .sub, .ptr_sub => try self.airSub(inst),
831 .subwrap => try self.airSubWrap(inst),
832 .subsat => try self.airArithmeticOpSat(inst, "subsat"),
833 .mul => try self.airMul(inst),
834 .mulwrap => try self.airMulWrap(inst),
835 .mulsat => try self.airArithmeticOpSat(inst, "mulsat"),
836 .div => try self.airDiv(inst),
837 .rem => try self.airRem(inst),
838 .mod => try self.airMod(inst),
827 .add, .ptr_add => try self.airAdd(inst),
828 .addwrap => try self.airAddWrap(inst),
829 .add_sat => try self.airAddSat(inst),
830 .sub, .ptr_sub => try self.airSub(inst),
831 .subwrap => try self.airSubWrap(inst),
832 .sub_sat => try self.airSubSat(inst),
833 .mul => try self.airMul(inst),
834 .mulwrap => try self.airMulWrap(inst),
835 .mul_sat => try self.airMulSat(inst),
836 .div => try self.airDiv(inst),
837 .rem => try self.airRem(inst),
838 .mod => try self.airMod(inst),
839 .shl, .shl_exact => try self.airShl(inst),
840 .shl_sat => try self.airShlSat(inst),
839841
840842 .cmp_lt => try self.airCmp(inst, .lt),
841843 .cmp_lte => try self.airCmp(inst, .lte),
......@@ -850,8 +852,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
850852 .bit_or => try self.airBitOr(inst),
851853 .xor => try self.airXor(inst),
852854 .shr => try self.airShr(inst),
853 .shl => try self.airShl(inst),
854 .shl_sat => try self.airArithmeticOpSat(inst, "shl_sat"),
855855
856856 .alloc => try self.airAlloc(inst),
857857 .arg => try self.airArg(inst),
......@@ -1306,6 +1306,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
13061306 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
13071307 }
13081308
1309 fn airAddSat(self: *Self, inst: Air.Inst.Index) !void {
1310 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
1311 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
1312 else => return self.fail("TODO implement add_sat for {}", .{self.target.cpu.arch}),
1313 };
1314 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1315 }
1316
13091317 fn airSub(self: *Self, inst: Air.Inst.Index) !void {
13101318 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
13111319 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
......@@ -1324,10 +1332,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
13241332 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
13251333 }
13261334
1327 fn airArithmeticOpSat(self: *Self, inst: Air.Inst.Index, comptime name: []const u8) !void {
1335 fn airSubSat(self: *Self, inst: Air.Inst.Index) !void {
13281336 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
13291337 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
1330 else => return self.fail("TODO implement " ++ name ++ " for {}", .{self.target.cpu.arch}),
1338 else => return self.fail("TODO implement sub_sat for {}", .{self.target.cpu.arch}),
13311339 };
13321340 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
13331341 }
......@@ -1350,6 +1358,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
13501358 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
13511359 }
13521360
1361 fn airMulSat(self: *Self, inst: Air.Inst.Index) !void {
1362 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
1363 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
1364 else => return self.fail("TODO implement mul_sat for {}", .{self.target.cpu.arch}),
1365 };
1366 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1367 }
1368
13531369 fn airDiv(self: *Self, inst: Air.Inst.Index) !void {
13541370 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
13551371 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
......@@ -1412,6 +1428,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
14121428 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
14131429 }
14141430
1431 fn airShlSat(self: *Self, inst: Air.Inst.Index) !void {
1432 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
1433 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
1434 else => return self.fail("TODO implement shl_sat for {}", .{self.target.cpu.arch}),
1435 };
1436 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1437 }
1438
14151439 fn airShr(self: *Self, inst: Air.Inst.Index) !void {
14161440 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
14171441 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
src/codegen/c.zig+35-41
......@@ -883,25 +883,27 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
883883
884884 // TODO use a different strategy for add that communicates to the optimizer
885885 // that wrapping is UB.
886 .add, .ptr_add => try airBinOp( f, inst, " + "),
887 .addwrap => try airWrapOp(f, inst, " + ", "addw_"),
888 .addsat => return f.fail("TODO: C backend: implement codegen for addsat", .{}),
886 .add, .ptr_add => try airBinOp (f, inst, " + "),
889887 // TODO use a different strategy for sub that communicates to the optimizer
890888 // that wrapping is UB.
891 .sub, .ptr_sub => try airBinOp( f, inst, " - "),
892 .subwrap => try airWrapOp(f, inst, " - ", "subw_"),
893 .subsat => return f.fail("TODO: C backend: implement codegen for subsat", .{}),
889 .sub, .ptr_sub => try airBinOp (f, inst, " - "),
894890 // TODO use a different strategy for mul that communicates to the optimizer
895891 // that wrapping is UB.
896 .mul => try airBinOp( f, inst, " * "),
897 .mulwrap => try airWrapOp(f, inst, " * ", "mulw_"),
898 .mulsat => return f.fail("TODO: C backend: implement codegen for mulsat", .{}),
892 .mul => try airBinOp (f, inst, " * "),
899893 // TODO use a different strategy for div that communicates to the optimizer
900894 // that wrapping is UB.
901895 .div => try airBinOp( f, inst, " / "),
902896 .rem => try airBinOp( f, inst, " % "),
903 // TODO implement modulus division
904 .mod => try airBinOp( f, inst, " mod "),
897 .mod => try airBinOp( f, inst, " mod "), // TODO implement modulus division
898
899 .addwrap => try airWrapOp(f, inst, " + ", "addw_"),
900 .subwrap => try airWrapOp(f, inst, " - ", "subw_"),
901 .mulwrap => try airWrapOp(f, inst, " * ", "mulw_"),
902
903 .add_sat => try airSatOp(f, inst, "adds_"),
904 .sub_sat => try airSatOp(f, inst, "subs_"),
905 .mul_sat => try airSatOp(f, inst, "muls_"),
906 .shl_sat => try airSatOp(f, inst, "shls_"),
905907
906908 .cmp_eq => try airBinOp(f, inst, " == "),
907909 .cmp_gt => try airBinOp(f, inst, " > "),
......@@ -911,18 +913,14 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
911913 .cmp_neq => try airBinOp(f, inst, " != "),
912914
913915 // bool_and and bool_or are non-short-circuit operations
914 .bool_and => try airBinOp(f, inst, " & "),
915 .bool_or => try airBinOp(f, inst, " | "),
916 .bit_and => try airBinOp(f, inst, " & "),
917 .bit_or => try airBinOp(f, inst, " | "),
918 .xor => try airBinOp(f, inst, " ^ "),
919
920 .shr => try airBinOp(f, inst, " >> "),
921 .shl => try airBinOp(f, inst, " << "),
922 .shl_sat => return f.fail("TODO: C backend: implement codegen for mulsat", .{}),
923
924
925 .not => try airNot( f, inst),
916 .bool_and => try airBinOp(f, inst, " & "),
917 .bool_or => try airBinOp(f, inst, " | "),
918 .bit_and => try airBinOp(f, inst, " & "),
919 .bit_or => try airBinOp(f, inst, " | "),
920 .xor => try airBinOp(f, inst, " ^ "),
921 .shr => try airBinOp(f, inst, " >> "),
922 .shl, .shl_exact => try airBinOp(f, inst, " << "),
923 .not => try airNot (f, inst),
926924
927925 .optional_payload => try airOptionalPayload(f, inst),
928926 .optional_payload_ptr => try airOptionalPayload(f, inst),
......@@ -1314,27 +1312,23 @@ fn airWrapOp(
13141312 return ret;
13151313}
13161314
1317fn airSatOp(
1318 o: *Object,
1319 inst: Air.Inst.Index,
1320 fn_op: [*:0]const u8,
1321) !CValue {
1322 if (o.liveness.isUnused(inst))
1315fn airSatOp(f: *Function, inst: Air.Inst.Index, fn_op: [*:0]const u8) !CValue {
1316 if (f.liveness.isUnused(inst))
13231317 return CValue.none;
13241318
1325 const bin_op = o.air.instructions.items(.data)[inst].bin_op;
1326 const inst_ty = o.air.typeOfIndex(inst);
1327 const int_info = inst_ty.intInfo(o.dg.module.getTarget());
1319 const bin_op = f.air.instructions.items(.data)[inst].bin_op;
1320 const inst_ty = f.air.typeOfIndex(inst);
1321 const int_info = inst_ty.intInfo(f.object.dg.module.getTarget());
13281322 const bits = int_info.bits;
13291323
13301324 switch (bits) {
13311325 8, 16, 32, 64, 128 => {},
1332 else => return o.dg.fail("TODO: C backend: airSatOp for non power of 2 integers", .{}),
1326 else => return f.object.dg.fail("TODO: C backend: airSatOp for non power of 2 integers", .{}),
13331327 }
13341328
13351329 // if it's an unsigned int with non-arbitrary bit size then we can just add
13361330 if (bits > 64) {
1337 return o.dg.fail("TODO: C backend: airSatOp for large integers", .{});
1331 return f.object.dg.fail("TODO: C backend: airSatOp for large integers", .{});
13381332 }
13391333
13401334 var min_buf: [80]u8 = undefined;
......@@ -1382,11 +1376,11 @@ fn airSatOp(
13821376 },
13831377 };
13841378
1385 const lhs = try o.resolveInst(bin_op.lhs);
1386 const rhs = try o.resolveInst(bin_op.rhs);
1387 const w = o.writer();
1379 const lhs = try f.resolveInst(bin_op.lhs);
1380 const rhs = try f.resolveInst(bin_op.rhs);
1381 const w = f.object.writer();
13881382
1389 const ret = try o.allocLocal(inst_ty, .Mut);
1383 const ret = try f.allocLocal(inst_ty, .Mut);
13901384 try w.print(" = zig_{s}", .{fn_op});
13911385
13921386 switch (inst_ty.tag()) {
......@@ -1412,16 +1406,16 @@ fn airSatOp(
14121406 }
14131407
14141408 try w.writeByte('(');
1415 try o.writeCValue(w, lhs);
1409 try f.writeCValue(w, lhs);
14161410 try w.writeAll(", ");
1417 try o.writeCValue(w, rhs);
1411 try f.writeCValue(w, rhs);
14181412
14191413 if (int_info.signedness == .signed) {
14201414 try w.print(", {s}", .{min});
14211415 }
14221416
14231417 try w.print(", {s});", .{max});
1424 try o.indent_writer.insertNewline();
1418 try f.object.indent_writer.insertNewline();
14251419
14261420 return ret;
14271421}
src/codegen/llvm.zig+128-60
......@@ -1236,27 +1236,27 @@ pub const FuncGen = struct {
12361236 for (body) |inst| {
12371237 const opt_value: ?*const llvm.Value = switch (air_tags[inst]) {
12381238 // zig fmt: off
1239 .add => try self.airAdd(inst, .standard),
1240 .addwrap => try self.airAdd(inst, .wrapping),
1241 .addsat => try self.airAdd(inst, .saturated),
1242 .sub => try self.airSub(inst, .standard),
1243 .subwrap => try self.airSub(inst, .wrapping),
1244 .subsat => try self.airSub(inst, .saturated),
1245 .mul => try self.airMul(inst, .standard),
1246 .mulwrap => try self.airMul(inst, .wrapping),
1247 .mulsat => try self.airMul(inst, .saturated),
1248 .div => try self.airDiv(inst),
1249 .rem => try self.airRem(inst),
1250 .mod => try self.airMod(inst),
1251 .ptr_add => try self.airPtrAdd(inst),
1252 .ptr_sub => try self.airPtrSub(inst),
1239 .add => try self.airAdd(inst),
1240 .addwrap => try self.airAddWrap(inst),
1241 .add_sat => try self.airAddSat(inst),
1242 .sub => try self.airSub(inst),
1243 .subwrap => try self.airSubWrap(inst),
1244 .sub_sat => try self.airSubSat(inst),
1245 .mul => try self.airMul(inst),
1246 .mulwrap => try self.airMulWrap(inst),
1247 .mul_sat => try self.airMulSat(inst),
1248 .div => try self.airDiv(inst),
1249 .rem => try self.airRem(inst),
1250 .mod => try self.airMod(inst),
1251 .ptr_add => try self.airPtrAdd(inst),
1252 .ptr_sub => try self.airPtrSub(inst),
1253 .shl => try self.airShl(inst),
1254 .shl_sat => try self.airShlSat(inst),
1255 .shl_exact => try self.airShlExact(inst),
12531256
12541257 .bit_and, .bool_and => try self.airAnd(inst),
12551258 .bit_or, .bool_or => try self.airOr(inst),
12561259 .xor => try self.airXor(inst),
1257
1258 .shl => try self.airShl(inst, false),
1259 .shl_sat => try self.airShl(inst, true),
12601260 .shr => try self.airShr(inst),
12611261
12621262 .cmp_eq => try self.airCmp(inst, .eq),
......@@ -2028,10 +2028,8 @@ pub const FuncGen = struct {
20282028 return self.todo("implement llvm codegen for 'airWrapErrUnionErr'", .{});
20292029 }
20302030
2031 const ArithmeticType = enum { standard, wrapping, saturated };
2032 fn airAdd(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value {
2033 if (self.liveness.isUnused(inst))
2034 return null;
2031 fn airAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2032 if (self.liveness.isUnused(inst)) return null;
20352033
20362034 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
20372035 const lhs = try self.resolveInst(bin_op.lhs);
......@@ -2039,21 +2037,36 @@ pub const FuncGen = struct {
20392037 const inst_ty = self.air.typeOfIndex(inst);
20402038
20412039 if (inst_ty.isAnyFloat()) return self.builder.buildFAdd(lhs, rhs, "");
2042 if (ty == .wrapping)
2043 return self.builder.buildAdd(lhs, rhs, "")
2044 else if (ty == .saturated) {
2045 if (inst_ty.isSignedInt())
2046 return self.builder.buildSAddSat(lhs, rhs, "")
2047 else
2048 return self.builder.buildUAddSat(lhs, rhs, "");
2049 }
20502040 if (inst_ty.isSignedInt()) return self.builder.buildNSWAdd(lhs, rhs, "");
20512041 return self.builder.buildNUWAdd(lhs, rhs, "");
20522042 }
20532043
2054 fn airSub(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value {
2055 if (self.liveness.isUnused(inst))
2056 return null;
2044 fn airAddWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2045 if (self.liveness.isUnused(inst)) return null;
2046
2047 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2048 const lhs = try self.resolveInst(bin_op.lhs);
2049 const rhs = try self.resolveInst(bin_op.rhs);
2050
2051 return self.builder.buildAdd(lhs, rhs, "");
2052 }
2053
2054 fn airAddSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2055 if (self.liveness.isUnused(inst)) return null;
2056
2057 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2058 const lhs = try self.resolveInst(bin_op.lhs);
2059 const rhs = try self.resolveInst(bin_op.rhs);
2060 const inst_ty = self.air.typeOfIndex(inst);
2061
2062 if (inst_ty.isAnyFloat()) return self.todo("saturating float add", .{});
2063 if (inst_ty.isSignedInt()) return self.builder.buildSAddSat(lhs, rhs, "");
2064
2065 return self.builder.buildUAddSat(lhs, rhs, "");
2066 }
2067
2068 fn airSub(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2069 if (self.liveness.isUnused(inst)) return null;
20572070
20582071 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
20592072 const lhs = try self.resolveInst(bin_op.lhs);
......@@ -2061,21 +2074,35 @@ pub const FuncGen = struct {
20612074 const inst_ty = self.air.typeOfIndex(inst);
20622075
20632076 if (inst_ty.isAnyFloat()) return self.builder.buildFSub(lhs, rhs, "");
2064 if (ty == .wrapping)
2065 return self.builder.buildSub(lhs, rhs, "")
2066 else if (ty == .saturated) {
2067 if (inst_ty.isSignedInt())
2068 return self.builder.buildSSubSat(lhs, rhs, "")
2069 else
2070 return self.builder.buildUSubSat(lhs, rhs, "");
2071 }
20722077 if (inst_ty.isSignedInt()) return self.builder.buildNSWSub(lhs, rhs, "");
20732078 return self.builder.buildNUWSub(lhs, rhs, "");
20742079 }
20752080
2076 fn airMul(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value {
2077 if (self.liveness.isUnused(inst))
2078 return null;
2081 fn airSubWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2082 if (self.liveness.isUnused(inst)) return null;
2083
2084 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2085 const lhs = try self.resolveInst(bin_op.lhs);
2086 const rhs = try self.resolveInst(bin_op.rhs);
2087
2088 return self.builder.buildSub(lhs, rhs, "");
2089 }
2090
2091 fn airSubSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2092 if (self.liveness.isUnused(inst)) return null;
2093
2094 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2095 const lhs = try self.resolveInst(bin_op.lhs);
2096 const rhs = try self.resolveInst(bin_op.rhs);
2097 const inst_ty = self.air.typeOfIndex(inst);
2098
2099 if (inst_ty.isAnyFloat()) return self.todo("saturating float sub", .{});
2100 if (inst_ty.isSignedInt()) return self.builder.buildSSubSat(lhs, rhs, "");
2101 return self.builder.buildUSubSat(lhs, rhs, "");
2102 }
2103
2104 fn airMul(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2105 if (self.liveness.isUnused(inst)) return null;
20792106
20802107 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
20812108 const lhs = try self.resolveInst(bin_op.lhs);
......@@ -2083,18 +2110,33 @@ pub const FuncGen = struct {
20832110 const inst_ty = self.air.typeOfIndex(inst);
20842111
20852112 if (inst_ty.isAnyFloat()) return self.builder.buildFMul(lhs, rhs, "");
2086 if (ty == .wrapping)
2087 return self.builder.buildMul(lhs, rhs, "")
2088 else if (ty == .saturated) {
2089 if (inst_ty.isSignedInt())
2090 return self.builder.buildSMulFixSat(lhs, rhs, "")
2091 else
2092 return self.builder.buildUMulFixSat(lhs, rhs, "");
2093 }
20942113 if (inst_ty.isSignedInt()) return self.builder.buildNSWMul(lhs, rhs, "");
20952114 return self.builder.buildNUWMul(lhs, rhs, "");
20962115 }
20972116
2117 fn airMulWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2118 if (self.liveness.isUnused(inst)) return null;
2119
2120 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2121 const lhs = try self.resolveInst(bin_op.lhs);
2122 const rhs = try self.resolveInst(bin_op.rhs);
2123
2124 return self.builder.buildMul(lhs, rhs, "");
2125 }
2126
2127 fn airMulSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2128 if (self.liveness.isUnused(inst)) return null;
2129
2130 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2131 const lhs = try self.resolveInst(bin_op.lhs);
2132 const rhs = try self.resolveInst(bin_op.rhs);
2133 const inst_ty = self.air.typeOfIndex(inst);
2134
2135 if (inst_ty.isAnyFloat()) return self.todo("saturating float mul", .{});
2136 if (inst_ty.isSignedInt()) return self.builder.buildSMulFixSat(lhs, rhs, "");
2137 return self.builder.buildUMulFixSat(lhs, rhs, "");
2138 }
2139
20982140 fn airDiv(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
20992141 if (self.liveness.isUnused(inst))
21002142 return null;
......@@ -2200,9 +2242,25 @@ pub const FuncGen = struct {
22002242 return self.builder.buildXor(lhs, rhs, "");
22012243 }
22022244
2203 fn airShl(self: *FuncGen, inst: Air.Inst.Index, sat: bool) !?*const llvm.Value {
2204 if (self.liveness.isUnused(inst))
2205 return null;
2245 fn airShlExact(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2246 if (self.liveness.isUnused(inst)) return null;
2247
2248 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2249 const lhs = try self.resolveInst(bin_op.lhs);
2250 const rhs = try self.resolveInst(bin_op.rhs);
2251 const lhs_type = self.air.typeOf(bin_op.lhs);
2252 const tg = self.dg.module.getTarget();
2253 const casted_rhs = if (self.air.typeOf(bin_op.rhs).bitSize(tg) < lhs_type.bitSize(tg))
2254 self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "")
2255 else
2256 rhs;
2257 if (lhs_type.isSignedInt()) return self.builder.buildNSWShl(lhs, casted_rhs, "");
2258 return self.builder.buildNUWShl(lhs, casted_rhs, "");
2259 }
2260
2261 fn airShl(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2262 if (self.liveness.isUnused(inst)) return null;
2263
22062264 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
22072265 const lhs = try self.resolveInst(bin_op.lhs);
22082266 const rhs = try self.resolveInst(bin_op.rhs);
......@@ -2212,15 +2270,25 @@ pub const FuncGen = struct {
22122270 self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "")
22132271 else
22142272 rhs;
2215 if (sat) {
2216 return if (lhs_type.isSignedInt())
2217 self.builder.buildSShlSat(lhs, casted_rhs, "")
2218 else
2219 self.builder.buildUShlSat(lhs, casted_rhs, "");
2220 }
22212273 return self.builder.buildShl(lhs, casted_rhs, "");
22222274 }
22232275
2276 fn airShlSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2277 if (self.liveness.isUnused(inst)) return null;
2278
2279 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2280 const lhs = try self.resolveInst(bin_op.lhs);
2281 const rhs = try self.resolveInst(bin_op.rhs);
2282 const lhs_type = self.air.typeOf(bin_op.lhs);
2283 const tg = self.dg.module.getTarget();
2284 const casted_rhs = if (self.air.typeOf(bin_op.rhs).bitSize(tg) < lhs_type.bitSize(tg))
2285 self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "")
2286 else
2287 rhs;
2288 if (lhs_type.isSignedInt()) return self.builder.buildSShlSat(lhs, casted_rhs, "");
2289 return self.builder.buildUShlSat(lhs, casted_rhs, "");
2290 }
2291
22242292 fn airShr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
22252293 if (self.liveness.isUnused(inst))
22262294 return null;
src/codegen/llvm/bindings.zig+6
......@@ -469,6 +469,12 @@ pub const Builder = opaque {
469469 pub const buildShl = LLVMBuildShl;
470470 extern fn LLVMBuildShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
471471
472 pub const buildNUWShl = ZigLLVMBuildNUWShl;
473 extern fn ZigLLVMBuildNUWShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
474
475 pub const buildNSWShl = ZigLLVMBuildNSWShl;
476 extern fn ZigLLVMBuildNSWShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
477
472478 pub const buildSShlSat = ZigLLVMBuildSShlSat;
473479 extern fn ZigLLVMBuildSShlSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
474480
src/link/C/zig.h+4-7
......@@ -356,9 +356,6 @@ static inline long long zig_subw_longlong(long long lhs, long long rhs, long lon
356356 return (long long)(((unsigned long long)lhs) - ((unsigned long long)rhs));
357357}
358358
359/*
360 * Saturating aritmetic operations: add, sub, mul, shl
361 */
362359#define zig_add_sat_u(ZT, T) static inline T zig_adds_##ZT(T x, T y, T max) { \
363360 return (x > max - y) ? max : x + y; \
364361}
......@@ -449,7 +446,7 @@ zig_shl_sat_u(u32, uint32_t, 32)
449446zig_shl_sat_s(i32, int32_t, 31)
450447zig_shl_sat_u(u64, uint64_t, 64)
451448zig_shl_sat_s(i64, int64_t, 63)
452zig_shl_sat_s(isize, intptr_t, 63)
453zig_shl_sat_s(short, short, 15)
454zig_shl_sat_s(int, int, 31)
455zig_shl_sat_s(long, long, 63)
449zig_shl_sat_s(isize, intptr_t, ((sizeof(intptr_t)) * CHAR_BIT - 1))
450zig_shl_sat_s(short, short, ((sizeof(short )) * CHAR_BIT - 1))
451zig_shl_sat_s(int, int, ((sizeof(int )) * CHAR_BIT - 1))
452zig_shl_sat_s(long, long, ((sizeof(long )) * CHAR_BIT - 1))
src/print_air.zig+4-3
......@@ -104,13 +104,13 @@ const Writer = struct {
104104
105105 .add,
106106 .addwrap,
107 .addsat,
107 .add_sat,
108108 .sub,
109109 .subwrap,
110 .subsat,
110 .sub_sat,
111111 .mul,
112112 .mulwrap,
113 .mulsat,
113 .mul_sat,
114114 .div,
115115 .rem,
116116 .mod,
......@@ -133,6 +133,7 @@ const Writer = struct {
133133 .ptr_elem_val,
134134 .ptr_ptr_elem_val,
135135 .shl,
136 .shl_exact,
136137 .shl_sat,
137138 .shr,
138139 .set_union_tag,
src/print_zir.zig+4-18
......@@ -229,12 +229,15 @@ const Writer = struct {
229229
230230 .add,
231231 .addwrap,
232 .add_sat,
232233 .array_cat,
233234 .array_mul,
234235 .mul,
235236 .mulwrap,
237 .mul_sat,
236238 .sub,
237239 .subwrap,
240 .sub_sat,
238241 .cmp_lt,
239242 .cmp_lte,
240243 .cmp_eq,
......@@ -247,6 +250,7 @@ const Writer = struct {
247250 .mod_rem,
248251 .shl,
249252 .shl_exact,
253 .shl_sat,
250254 .shr,
251255 .shr_exact,
252256 .xor,
......@@ -400,12 +404,6 @@ const Writer = struct {
400404 .shl_with_overflow,
401405 => try self.writeOverflowArithmetic(stream, extended),
402406
403 .add_with_saturation,
404 .sub_with_saturation,
405 .mul_with_saturation,
406 .shl_with_saturation,
407 => try self.writeSaturatingArithmetic(stream, extended),
408
409407 .struct_decl => try self.writeStructDecl(stream, extended),
410408 .union_decl => try self.writeUnionDecl(stream, extended),
411409 .enum_decl => try self.writeEnumDecl(stream, extended),
......@@ -854,18 +852,6 @@ const Writer = struct {
854852 try self.writeSrc(stream, src);
855853 }
856854
857 fn writeSaturatingArithmetic(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void {
858 const extra = self.code.extraData(Zir.Inst.SaturatingArithmetic, extended.operand).data;
859 const src: LazySrcLoc = .{ .node_offset = extra.node };
860
861 try self.writeInstRef(stream, extra.lhs);
862 try stream.writeAll(", ");
863 try self.writeInstRef(stream, extra.rhs);
864 try stream.writeAll(", ");
865 try stream.writeAll(") ");
866 try self.writeSrc(stream, src);
867 }
868
869855 fn writePlNodeCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
870856 const inst_data = self.code.instructions.items(.data)[inst].pl_node;
871857 const extra = self.code.extraData(Zir.Inst.Call, inst_data.payload_index);
src/stage1/all_types.hpp-4
......@@ -1818,10 +1818,6 @@ enum BuiltinFnId {
18181818 BuiltinFnIdReduce,
18191819 BuiltinFnIdMaximum,
18201820 BuiltinFnIdMinimum,
1821 BuiltinFnIdSatAdd,
1822 BuiltinFnIdSatSub,
1823 BuiltinFnIdSatMul,
1824 BuiltinFnIdSatShl,
18251821};
18261822
18271823struct BuiltinFnEntry {
src/stage1/astgen.cpp-60
......@@ -4720,66 +4720,6 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast
47204720 Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpMaximum, arg0_value, arg1_value, true);
47214721 return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
47224722 }
4723 case BuiltinFnIdSatAdd:
4724 {
4725 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
4726 Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
4727 if (arg0_value == ag->codegen->invalid_inst_src)
4728 return arg0_value;
4729
4730 AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
4731 Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
4732 if (arg1_value == ag->codegen->invalid_inst_src)
4733 return arg1_value;
4734
4735 Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpAddSat, arg0_value, arg1_value, true);
4736 return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
4737 }
4738 case BuiltinFnIdSatSub:
4739 {
4740 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
4741 Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
4742 if (arg0_value == ag->codegen->invalid_inst_src)
4743 return arg0_value;
4744
4745 AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
4746 Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
4747 if (arg1_value == ag->codegen->invalid_inst_src)
4748 return arg1_value;
4749
4750 Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSubSat, arg0_value, arg1_value, true);
4751 return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
4752 }
4753 case BuiltinFnIdSatMul:
4754 {
4755 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
4756 Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
4757 if (arg0_value == ag->codegen->invalid_inst_src)
4758 return arg0_value;
4759
4760 AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
4761 Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
4762 if (arg1_value == ag->codegen->invalid_inst_src)
4763 return arg1_value;
4764
4765 Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpMultSat, arg0_value, arg1_value, true);
4766 return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
4767 }
4768 case BuiltinFnIdSatShl:
4769 {
4770 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
4771 Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
4772 if (arg0_value == ag->codegen->invalid_inst_src)
4773 return arg0_value;
4774
4775 AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
4776 Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
4777 if (arg1_value == ag->codegen->invalid_inst_src)
4778 return arg1_value;
4779
4780 Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpShlSat, arg0_value, arg1_value, true);
4781 return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
4782 }
47834723 case BuiltinFnIdMemcpy:
47844724 {
47854725 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
src/stage1/codegen.cpp-4
......@@ -9134,10 +9134,6 @@ static void define_builtin_fns(CodeGen *g) {
91349134 create_builtin_fn(g, BuiltinFnIdReduce, "reduce", 2);
91359135 create_builtin_fn(g, BuiltinFnIdMaximum, "maximum", 2);
91369136 create_builtin_fn(g, BuiltinFnIdMinimum, "minimum", 2);
9137 create_builtin_fn(g, BuiltinFnIdSatAdd, "addWithSaturation", 2);
9138 create_builtin_fn(g, BuiltinFnIdSatSub, "subWithSaturation", 2);
9139 create_builtin_fn(g, BuiltinFnIdSatMul, "mulWithSaturation", 2);
9140 create_builtin_fn(g, BuiltinFnIdSatShl, "shlWithSaturation", 2);
91419137}
91429138
91439139static const char *bool_to_str(bool b) {
src/translate_c/ast.zig+4-4
......@@ -1462,10 +1462,10 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
14621462 .mul_wrap_assign => return renderBinOp(c, node, .assign_mul_wrap, .asterisk_percent_equal, "*%="),
14631463 .div => return renderBinOpGrouped(c, node, .div, .slash, "/"),
14641464 .div_assign => return renderBinOp(c, node, .assign_div, .slash_equal, "/="),
1465 .shl => return renderBinOpGrouped(c, node, .bit_shift_left, .angle_bracket_angle_bracket_left, "<<"),
1466 .shl_assign => return renderBinOp(c, node, .assign_bit_shift_left, .angle_bracket_angle_bracket_left_equal, "<<="),
1467 .shr => return renderBinOpGrouped(c, node, .bit_shift_right, .angle_bracket_angle_bracket_right, ">>"),
1468 .shr_assign => return renderBinOp(c, node, .assign_bit_shift_right, .angle_bracket_angle_bracket_right_equal, ">>="),
1465 .shl => return renderBinOpGrouped(c, node, .shl, .angle_bracket_angle_bracket_left, "<<"),
1466 .shl_assign => return renderBinOp(c, node, .assign_shl, .angle_bracket_angle_bracket_left_equal, "<<="),
1467 .shr => return renderBinOpGrouped(c, node, .shr, .angle_bracket_angle_bracket_right, ">>"),
1468 .shr_assign => return renderBinOp(c, node, .assign_shr, .angle_bracket_angle_bracket_right_equal, ">>="),
14691469 .mod => return renderBinOpGrouped(c, node, .mod, .percent, "%"),
14701470 .mod_assign => return renderBinOp(c, node, .assign_mod, .percent_equal, "%="),
14711471 .@"and" => return renderBinOpGrouped(c, node, .bool_and, .keyword_and, "and"),
src/value.zig+87
......@@ -1588,6 +1588,35 @@ pub const Value = extern union {
15881588 return result;
15891589 }
15901590
1591 /// Supports both floats and ints; handles undefined.
1592 pub fn numberAddSat(
1593 lhs: Value,
1594 rhs: Value,
1595 ty: Type,
1596 arena: *Allocator,
1597 target: Target,
1598 ) !Value {
1599 if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
1600
1601 if (ty.isAnyFloat()) {
1602 // TODO: handle outside float range
1603 return floatAdd(lhs, rhs, ty, arena);
1604 }
1605 const result = try intAdd(lhs, rhs, arena);
1606
1607 const max = try ty.maxInt(arena, target);
1608 if (compare(result, .gt, max, ty)) {
1609 return max;
1610 }
1611
1612 const min = try ty.minInt(arena, target);
1613 if (compare(result, .lt, min, ty)) {
1614 return min;
1615 }
1616
1617 return result;
1618 }
1619
15911620 /// Supports both floats and ints; handles undefined.
15921621 pub fn numberSubWrap(
15931622 lhs: Value,
......@@ -1616,6 +1645,35 @@ pub const Value = extern union {
16161645 return result;
16171646 }
16181647
1648 /// Supports both floats and ints; handles undefined.
1649 pub fn numberSubSat(
1650 lhs: Value,
1651 rhs: Value,
1652 ty: Type,
1653 arena: *Allocator,
1654 target: Target,
1655 ) !Value {
1656 if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
1657
1658 if (ty.isAnyFloat()) {
1659 // TODO: handle outside float range
1660 return floatSub(lhs, rhs, ty, arena);
1661 }
1662 const result = try intSub(lhs, rhs, arena);
1663
1664 const max = try ty.maxInt(arena, target);
1665 if (compare(result, .gt, max, ty)) {
1666 return max;
1667 }
1668
1669 const min = try ty.minInt(arena, target);
1670 if (compare(result, .lt, min, ty)) {
1671 return min;
1672 }
1673
1674 return result;
1675 }
1676
16191677 /// Supports both floats and ints; handles undefined.
16201678 pub fn numberMulWrap(
16211679 lhs: Value,
......@@ -1644,6 +1702,35 @@ pub const Value = extern union {
16441702 return result;
16451703 }
16461704
1705 /// Supports both floats and ints; handles undefined.
1706 pub fn numberMulSat(
1707 lhs: Value,
1708 rhs: Value,
1709 ty: Type,
1710 arena: *Allocator,
1711 target: Target,
1712 ) !Value {
1713 if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
1714
1715 if (ty.isAnyFloat()) {
1716 // TODO: handle outside float range
1717 return floatMul(lhs, rhs, ty, arena);
1718 }
1719 const result = try intMul(lhs, rhs, arena);
1720
1721 const max = try ty.maxInt(arena, target);
1722 if (compare(result, .gt, max, ty)) {
1723 return max;
1724 }
1725
1726 const min = try ty.minInt(arena, target);
1727 if (compare(result, .lt, min, ty)) {
1728 return min;
1729 }
1730
1731 return result;
1732 }
1733
16471734 /// Supports both floats and ints; handles undefined.
16481735 pub fn numberMax(lhs: Value, rhs: Value, arena: *Allocator) !Value {
16491736 if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
test/behavior/saturating_arithmetic.zig+9-24
......@@ -32,7 +32,7 @@ fn testSaturatingOp(comptime op: Op, comptime T: type, test_data: [3]T) !void {
3232 }
3333}
3434
35test "@addWithSaturation" {
35test "saturating add" {
3636 const S = struct {
3737 fn doTheTest() !void {
3838 // .{a, b, expected a+b}
......@@ -50,22 +50,16 @@ test "@addWithSaturation" {
5050 try testSaturatingOp(.add, u128, .{ maxInt(u128), 1, maxInt(u128) });
5151
5252 const u8x3 = std.meta.Vector(3, u8);
53 try expectEqual(u8x3{ 255, 255, 255 }, @addWithSaturation(
54 u8x3{ 255, 254, 1 },
55 u8x3{ 1, 2, 255 },
56 ));
53 try expectEqual(u8x3{ 255, 255, 255 }, (u8x3{ 255, 254, 1 } +| u8x3{ 1, 2, 255 }));
5754 const i8x3 = std.meta.Vector(3, i8);
58 try expectEqual(i8x3{ 127, 127, 127 }, @addWithSaturation(
59 i8x3{ 127, 126, 1 },
60 i8x3{ 1, 2, 127 },
61 ));
55 try expectEqual(i8x3{ 127, 127, 127 }, (i8x3{ 127, 126, 1 } +| i8x3{ 1, 2, 127 }));
6256 }
6357 };
6458 try S.doTheTest();
6559 comptime try S.doTheTest();
6660}
6761
68test "@subWithSaturation" {
62test "saturating subtraction" {
6963 const S = struct {
7064 fn doTheTest() !void {
7165 // .{a, b, expected a-b}
......@@ -81,17 +75,14 @@ test "@subWithSaturation" {
8175 try testSaturatingOp(.sub, u128, .{ 0, maxInt(u128), 0 });
8276
8377 const u8x3 = std.meta.Vector(3, u8);
84 try expectEqual(u8x3{ 0, 0, 0 }, @subWithSaturation(
85 u8x3{ 0, 0, 0 },
86 u8x3{ 255, 255, 255 },
87 ));
78 try expectEqual(u8x3{ 0, 0, 0 }, (u8x3{ 0, 0, 0 } -| u8x3{ 255, 255, 255 }));
8879 }
8980 };
9081 try S.doTheTest();
9182 comptime try S.doTheTest();
9283}
9384
94test "@mulWithSaturation" {
85test "saturating multiplication" {
9586 // TODO: once #9660 has been solved, remove this line
9687 if (std.builtin.target.cpu.arch == .wasm32) return error.SkipZigTest;
9788
......@@ -112,10 +103,7 @@ test "@mulWithSaturation" {
112103 try testSaturatingOp(.mul, u128, .{ maxInt(u128), maxInt(u128), maxInt(u128) });
113104
114105 const u8x3 = std.meta.Vector(3, u8);
115 try expectEqual(u8x3{ 255, 255, 255 }, @mulWithSaturation(
116 u8x3{ 2, 2, 2 },
117 u8x3{ 255, 255, 255 },
118 ));
106 try expectEqual(u8x3{ 255, 255, 255 }, (u8x3{ 2, 2, 2 } *| u8x3{ 255, 255, 255 }));
119107 }
120108 };
121109
......@@ -123,7 +111,7 @@ test "@mulWithSaturation" {
123111 comptime try S.doTheTest();
124112}
125113
126test "@shlWithSaturation" {
114test "saturating shift-left" {
127115 const S = struct {
128116 fn doTheTest() !void {
129117 // .{a, b, expected a<<b}
......@@ -140,10 +128,7 @@ test "@shlWithSaturation" {
140128 try testSaturatingOp(.shl, u8, .{ 255, 1, 255 });
141129
142130 const u8x3 = std.meta.Vector(3, u8);
143 try expectEqual(u8x3{ 255, 255, 255 }, @shlWithSaturation(
144 u8x3{ 255, 255, 255 },
145 u8x3{ 1, 1, 1 },
146 ));
131 try expectEqual(u8x3{ 255, 255, 255 }, (u8x3{ 255, 255, 255 } <<| u8x3{ 1, 1, 1 }));
147132 }
148133 };
149134 try S.doTheTest();