From 29f41896ed9d99e82a88f4b63efa182ca0d2f93c Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Thu, 2 Sep 2021 13:50:24 -0700
Subject: [PATCH 01/17] sat-arithmetic: add operator support
- adds initial support for the operators +|, -|, *|, <<|, +|=, -|=, *|=, <<|=
- uses operators in addition to builtins in behavior test
- adds binOpExt() and assignBinOpExt() to AstGen.zig. these need to be audited
---
lib/std/zig/Ast.zig | 32 ++++++
lib/std/zig/parse.zig | 8 ++
lib/std/zig/render.zig | 8 ++
lib/std/zig/tokenizer.zig | 79 +++++++++++++++
src/Air.zig | 22 +++++
src/AstGen.zig | 124 +++++++++++++++++++++++-
src/Liveness.zig | 4 +
src/codegen.zig | 12 +++
src/codegen/c.zig | 3 +
src/codegen/llvm.zig | 66 +++++++++----
src/codegen/llvm/bindings.zig | 24 +++++
src/print_air.zig | 4 +
src/stage1/all_types.hpp | 16 ++-
src/stage1/astgen.cpp | 24 ++++-
src/stage1/codegen.cpp | 8 +-
src/stage1/ir.cpp | 24 ++---
src/stage1/ir_print.cpp | 8 +-
src/stage1/parser.cpp | 16 +++
src/stage1/tokenizer.cpp | 85 ++++++++++++++++
src/stage1/tokenizer.hpp | 8 ++
test/behavior/saturating_arithmetic.zig | 35 +++++--
21 files changed, 556 insertions(+), 54 deletions(-)
diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig
index 5838dcd37ac5..3632551d17b8 100644
--- a/lib/std/zig/Ast.zig
+++ b/lib/std/zig/Ast.zig
@@ -396,6 +396,7 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex {
.assign_add,
.assign_sub,
.assign_bit_shift_left,
+ .assign_bit_shift_left_sat,
.assign_bit_shift_right,
.assign_bit_and,
.assign_bit_xor,
@@ -403,6 +404,9 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex {
.assign_mul_wrap,
.assign_add_wrap,
.assign_sub_wrap,
+ .assign_mul_sat,
+ .assign_add_sat,
+ .assign_sub_sat,
.assign,
.merge_error_sets,
.mul,
@@ -410,12 +414,16 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex {
.mod,
.array_mult,
.mul_wrap,
+ .mul_sat,
.add,
.sub,
.array_cat,
.add_wrap,
.sub_wrap,
+ .add_sat,
+ .sub_sat,
.bit_shift_left,
+ .bit_shift_left_sat,
.bit_shift_right,
.bit_and,
.bit_xor,
@@ -652,6 +660,7 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex {
.assign_add,
.assign_sub,
.assign_bit_shift_left,
+ .assign_bit_shift_left_sat,
.assign_bit_shift_right,
.assign_bit_and,
.assign_bit_xor,
@@ -659,6 +668,9 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex {
.assign_mul_wrap,
.assign_add_wrap,
.assign_sub_wrap,
+ .assign_mul_sat,
+ .assign_add_sat,
+ .assign_sub_sat,
.assign,
.merge_error_sets,
.mul,
@@ -666,12 +678,16 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex {
.mod,
.array_mult,
.mul_wrap,
+ .mul_sat,
.add,
.sub,
.array_cat,
.add_wrap,
.sub_wrap,
+ .add_sat,
+ .sub_sat,
.bit_shift_left,
+ .bit_shift_left_sat,
.bit_shift_right,
.bit_and,
.bit_xor,
@@ -2525,6 +2541,8 @@ pub const Node = struct {
assign_sub,
/// `lhs <<= rhs`. main_token is op.
assign_bit_shift_left,
+ /// `lhs <<|= rhs`. main_token is op.
+ assign_bit_shift_left_sat,
/// `lhs >>= rhs`. main_token is op.
assign_bit_shift_right,
/// `lhs &= rhs`. main_token is op.
@@ -2539,6 +2557,12 @@ pub const Node = struct {
assign_add_wrap,
/// `lhs -%= rhs`. main_token is op.
assign_sub_wrap,
+ /// `lhs *|= rhs`. main_token is op.
+ assign_mul_sat,
+ /// `lhs +|= rhs`. main_token is op.
+ assign_add_sat,
+ /// `lhs -|= rhs`. main_token is op.
+ assign_sub_sat,
/// `lhs = rhs`. main_token is op.
assign,
/// `lhs || rhs`. main_token is the `||`.
@@ -2553,6 +2577,8 @@ pub const Node = struct {
array_mult,
/// `lhs *% rhs`. main_token is the `*%`.
mul_wrap,
+ /// `lhs *| rhs`. main_token is the `*%`.
+ mul_sat,
/// `lhs + rhs`. main_token is the `+`.
add,
/// `lhs - rhs`. main_token is the `-`.
@@ -2563,8 +2589,14 @@ pub const Node = struct {
add_wrap,
/// `lhs -% rhs`. main_token is the `-%`.
sub_wrap,
+ /// `lhs +| rhs`. main_token is the `+|`.
+ add_sat,
+ /// `lhs -| rhs`. main_token is the `-|`.
+ sub_sat,
/// `lhs << rhs`. main_token is the `<<`.
bit_shift_left,
+ /// `lhs <<| rhs`. main_token is the `<<|`.
+ bit_shift_left_sat,
/// `lhs >> rhs`. main_token is the `>>`.
bit_shift_right,
/// `lhs & rhs`. main_token is the `&`.
diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig
index f7697027a3e6..a2780b522599 100644
--- a/lib/std/zig/parse.zig
+++ b/lib/std/zig/parse.zig
@@ -1269,6 +1269,7 @@ const Parser = struct {
.plus_equal => .assign_add,
.minus_equal => .assign_sub,
.angle_bracket_angle_bracket_left_equal => .assign_bit_shift_left,
+ .angle_bracket_angle_bracket_left_pipe_equal => .assign_bit_shift_left_sat,
.angle_bracket_angle_bracket_right_equal => .assign_bit_shift_right,
.ampersand_equal => .assign_bit_and,
.caret_equal => .assign_bit_xor,
@@ -1276,6 +1277,9 @@ const Parser = struct {
.asterisk_percent_equal => .assign_mul_wrap,
.plus_percent_equal => .assign_add_wrap,
.minus_percent_equal => .assign_sub_wrap,
+ .asterisk_pipe_equal => .assign_mul_sat,
+ .plus_pipe_equal => .assign_add_sat,
+ .minus_pipe_equal => .assign_sub_sat,
.equal => .assign,
else => return expr,
};
@@ -1343,6 +1347,7 @@ const Parser = struct {
.keyword_catch = .{ .prec = 40, .tag = .@"catch" },
.angle_bracket_angle_bracket_left = .{ .prec = 50, .tag = .bit_shift_left },
+ .angle_bracket_angle_bracket_left_pipe = .{ .prec = 50, .tag = .bit_shift_left_sat },
.angle_bracket_angle_bracket_right = .{ .prec = 50, .tag = .bit_shift_right },
.plus = .{ .prec = 60, .tag = .add },
@@ -1350,6 +1355,8 @@ const Parser = struct {
.plus_plus = .{ .prec = 60, .tag = .array_cat },
.plus_percent = .{ .prec = 60, .tag = .add_wrap },
.minus_percent = .{ .prec = 60, .tag = .sub_wrap },
+ .plus_pipe = .{ .prec = 60, .tag = .add_sat },
+ .minus_pipe = .{ .prec = 60, .tag = .sub_sat },
.pipe_pipe = .{ .prec = 70, .tag = .merge_error_sets },
.asterisk = .{ .prec = 70, .tag = .mul },
@@ -1357,6 +1364,7 @@ const Parser = struct {
.percent = .{ .prec = 70, .tag = .mod },
.asterisk_asterisk = .{ .prec = 70, .tag = .array_mult },
.asterisk_percent = .{ .prec = 70, .tag = .mul_wrap },
+ .asterisk_pipe = .{ .prec = 70, .tag = .mul_sat },
});
fn parseExprPrecedence(p: *Parser, min_prec: i32) Error!Node.Index {
diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig
index 3029d38cb927..47f019d1cf9b 100644
--- a/lib/std/zig/render.zig
+++ b/lib/std/zig/render.zig
@@ -333,26 +333,32 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index,
.add,
.add_wrap,
+ .add_sat,
.array_cat,
.array_mult,
.assign,
.assign_bit_and,
.assign_bit_or,
.assign_bit_shift_left,
+ .assign_bit_shift_left_sat,
.assign_bit_shift_right,
.assign_bit_xor,
.assign_div,
.assign_sub,
.assign_sub_wrap,
+ .assign_sub_sat,
.assign_mod,
.assign_add,
.assign_add_wrap,
+ .assign_add_sat,
.assign_mul,
.assign_mul_wrap,
+ .assign_mul_sat,
.bang_equal,
.bit_and,
.bit_or,
.bit_shift_left,
+ .bit_shift_left_sat,
.bit_shift_right,
.bit_xor,
.bool_and,
@@ -367,8 +373,10 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index,
.mod,
.mul,
.mul_wrap,
+ .mul_sat,
.sub,
.sub_wrap,
+ .sub_sat,
.@"orelse",
=> {
const infix = datas[node];
diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig
index 3ef6c9a6ba72..6afe7750d35f 100644
--- a/lib/std/zig/tokenizer.zig
+++ b/lib/std/zig/tokenizer.zig
@@ -103,15 +103,21 @@ pub const Token = struct {
plus_equal,
plus_percent,
plus_percent_equal,
+ plus_pipe,
+ plus_pipe_equal,
minus,
minus_equal,
minus_percent,
minus_percent_equal,
+ minus_pipe,
+ minus_pipe_equal,
asterisk,
asterisk_equal,
asterisk_asterisk,
asterisk_percent,
asterisk_percent_equal,
+ asterisk_pipe,
+ asterisk_pipe_equal,
arrow,
colon,
slash,
@@ -124,6 +130,8 @@ pub const Token = struct {
angle_bracket_left_equal,
angle_bracket_angle_bracket_left,
angle_bracket_angle_bracket_left_equal,
+ angle_bracket_angle_bracket_left_pipe,
+ angle_bracket_angle_bracket_left_pipe_equal,
angle_bracket_right,
angle_bracket_right_equal,
angle_bracket_angle_bracket_right,
@@ -227,15 +235,21 @@ pub const Token = struct {
.plus_equal => "+=",
.plus_percent => "+%",
.plus_percent_equal => "+%=",
+ .plus_pipe => "+|",
+ .plus_pipe_equal => "+|=",
.minus => "-",
.minus_equal => "-=",
.minus_percent => "-%",
.minus_percent_equal => "-%=",
+ .minus_pipe => "-|",
+ .minus_pipe_equal => "-|=",
.asterisk => "*",
.asterisk_equal => "*=",
.asterisk_asterisk => "**",
.asterisk_percent => "*%",
.asterisk_percent_equal => "*%=",
+ .asterisk_pipe => "*|",
+ .asterisk_pipe_equal => "*|=",
.arrow => "->",
.colon => ":",
.slash => "/",
@@ -248,6 +262,8 @@ pub const Token = struct {
.angle_bracket_left_equal => "<=",
.angle_bracket_angle_bracket_left => "<<",
.angle_bracket_angle_bracket_left_equal => "<<=",
+ .angle_bracket_angle_bracket_left_pipe => "<<|",
+ .angle_bracket_angle_bracket_left_pipe_equal => "<<|=",
.angle_bracket_right => ">",
.angle_bracket_right_equal => ">=",
.angle_bracket_angle_bracket_right => ">>",
@@ -352,8 +368,10 @@ pub const Tokenizer = struct {
pipe,
minus,
minus_percent,
+ minus_pipe,
asterisk,
asterisk_percent,
+ asterisk_pipe,
slash,
line_comment_start,
line_comment,
@@ -382,8 +400,10 @@ pub const Tokenizer = struct {
percent,
plus,
plus_percent,
+ plus_pipe,
angle_bracket_left,
angle_bracket_angle_bracket_left,
+ angle_bracket_angle_bracket_left_pipe,
angle_bracket_right,
angle_bracket_angle_bracket_right,
period,
@@ -584,6 +604,9 @@ pub const Tokenizer = struct {
'%' => {
state = .asterisk_percent;
},
+ '|' => {
+ state = .asterisk_pipe;
+ },
else => {
result.tag = .asterisk;
break;
@@ -602,6 +625,18 @@ pub const Tokenizer = struct {
},
},
+ .asterisk_pipe => switch (c) {
+ '=' => {
+ result.tag = .asterisk_pipe_equal;
+ self.index += 1;
+ break;
+ },
+ else => {
+ result.tag = .asterisk_pipe;
+ break;
+ },
+ },
+
.percent => switch (c) {
'=' => {
result.tag = .percent_equal;
@@ -628,6 +663,9 @@ pub const Tokenizer = struct {
'%' => {
state = .plus_percent;
},
+ '|' => {
+ state = .plus_pipe;
+ },
else => {
result.tag = .plus;
break;
@@ -646,6 +684,18 @@ pub const Tokenizer = struct {
},
},
+ .plus_pipe => switch (c) {
+ '=' => {
+ result.tag = .plus_pipe_equal;
+ self.index += 1;
+ break;
+ },
+ else => {
+ result.tag = .plus_pipe;
+ break;
+ },
+ },
+
.caret => switch (c) {
'=' => {
result.tag = .caret_equal;
@@ -903,6 +953,9 @@ pub const Tokenizer = struct {
'%' => {
state = .minus_percent;
},
+ '|' => {
+ state = .minus_pipe;
+ },
else => {
result.tag = .minus;
break;
@@ -920,6 +973,17 @@ pub const Tokenizer = struct {
break;
},
},
+ .minus_pipe => switch (c) {
+ '=' => {
+ result.tag = .minus_pipe_equal;
+ self.index += 1;
+ break;
+ },
+ else => {
+ result.tag = .minus_pipe;
+ break;
+ },
+ },
.angle_bracket_left => switch (c) {
'<' => {
@@ -942,12 +1006,27 @@ pub const Tokenizer = struct {
self.index += 1;
break;
},
+ '|' => {
+ result.tag = .angle_bracket_angle_bracket_left_pipe;
+ },
else => {
result.tag = .angle_bracket_angle_bracket_left;
break;
},
},
+ .angle_bracket_angle_bracket_left_pipe => switch (c) {
+ '=' => {
+ result.tag = .angle_bracket_angle_bracket_left_pipe_equal;
+ self.index += 1;
+ break;
+ },
+ else => {
+ result.tag = .angle_bracket_angle_bracket_left_pipe;
+ break;
+ },
+ },
+
.angle_bracket_right => switch (c) {
'>' => {
state = .angle_bracket_angle_bracket_right;
diff --git a/src/Air.zig b/src/Air.zig
index b5d19127a0c3..b7d3938352dc 100644
--- a/src/Air.zig
+++ b/src/Air.zig
@@ -44,6 +44,11 @@ pub const Inst = struct {
/// is the same as both operands.
/// Uses the `bin_op` field.
addwrap,
+ /// Saturating integer addition.
+ /// Both operands are guaranteed to be the same type, and the result type
+ /// is the same as both operands.
+ /// Uses the `bin_op` field.
+ addsat,
/// Float or integer subtraction. For integers, wrapping is undefined behavior.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
@@ -54,6 +59,11 @@ pub const Inst = struct {
/// is the same as both operands.
/// Uses the `bin_op` field.
subwrap,
+ /// Saturating integer subtraction.
+ /// Both operands are guaranteed to be the same type, and the result type
+ /// is the same as both operands.
+ /// Uses the `bin_op` field.
+ subsat,
/// Float or integer multiplication. For integers, wrapping is undefined behavior.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
@@ -64,6 +74,11 @@ pub const Inst = struct {
/// is the same as both operands.
/// Uses the `bin_op` field.
mulwrap,
+ /// Saturating integer multiplication.
+ /// Both operands are guaranteed to be the same type, and the result type
+ /// is the same as both operands.
+ /// Uses the `bin_op` field.
+ mulsat,
/// Integer or float division. For integers, wrapping is undefined behavior.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
@@ -110,6 +125,9 @@ pub const Inst = struct {
/// Shift left. `<<`
/// Uses the `bin_op` field.
shl,
+ /// Shift left saturating. `<<|`
+ /// Uses the `bin_op` field.
+ shl_sat,
/// Bitwise XOR. `^`
/// Uses the `bin_op` field.
xor,
@@ -568,10 +586,13 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.add,
.addwrap,
+ .addsat,
.sub,
.subwrap,
+ .subsat,
.mul,
.mulwrap,
+ .mulsat,
.div,
.rem,
.mod,
@@ -582,6 +603,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.ptr_sub,
.shr,
.shl,
+ .shl_sat,
=> return air.typeOf(datas[inst].bin_op.lhs),
.cmp_lt,
diff --git a/src/AstGen.zig b/src/AstGen.zig
index 15594ac27c34..b3af3eb86b81 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -318,27 +318,35 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins
.assign_bit_and,
.assign_bit_or,
.assign_bit_shift_left,
+ .assign_bit_shift_left_sat,
.assign_bit_shift_right,
.assign_bit_xor,
.assign_div,
.assign_sub,
.assign_sub_wrap,
+ .assign_sub_sat,
.assign_mod,
.assign_add,
.assign_add_wrap,
+ .assign_add_sat,
.assign_mul,
.assign_mul_wrap,
+ .assign_mul_sat,
.add,
.add_wrap,
+ .add_sat,
.sub,
.sub_wrap,
+ .sub_sat,
.mul,
.mul_wrap,
+ .mul_sat,
.div,
.mod,
.bit_and,
.bit_or,
.bit_shift_left,
+ .bit_shift_left_sat,
.bit_shift_right,
.bit_xor,
.bang_equal,
@@ -526,6 +534,10 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
try assignShift(gz, scope, node, .shl);
return rvalue(gz, rl, .void_value, node);
},
+ .assign_bit_shift_left_sat => {
+ try assignBinOpExt(gz, scope, node, .shl_with_saturation);
+ return rvalue(gz, rl, .void_value, node);
+ },
.assign_bit_shift_right => {
try assignShift(gz, scope, node, .shr);
return rvalue(gz, rl, .void_value, node);
@@ -555,6 +567,10 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
try assignOp(gz, scope, node, .subwrap);
return rvalue(gz, rl, .void_value, node);
},
+ .assign_sub_sat => {
+ try assignBinOpExt(gz, scope, node, .sub_with_saturation);
+ return rvalue(gz, rl, .void_value, node);
+ },
.assign_mod => {
try assignOp(gz, scope, node, .mod_rem);
return rvalue(gz, rl, .void_value, node);
@@ -567,6 +583,10 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
try assignOp(gz, scope, node, .addwrap);
return rvalue(gz, rl, .void_value, node);
},
+ .assign_add_sat => {
+ try assignBinOpExt(gz, scope, node, .add_with_saturation);
+ return rvalue(gz, rl, .void_value, node);
+ },
.assign_mul => {
try assignOp(gz, scope, node, .mul);
return rvalue(gz, rl, .void_value, node);
@@ -575,17 +595,25 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
try assignOp(gz, scope, node, .mulwrap);
return rvalue(gz, rl, .void_value, node);
},
+ .assign_mul_sat => {
+ try assignBinOpExt(gz, scope, node, .mul_with_saturation);
+ return rvalue(gz, rl, .void_value, node);
+ },
// zig fmt: off
- .bit_shift_left => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl),
- .bit_shift_right => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr),
+ .bit_shift_left => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl),
+ .bit_shift_left_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation),
+ .bit_shift_right => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr),
.add => return simpleBinOp(gz, scope, rl, node, .add),
.add_wrap => return simpleBinOp(gz, scope, rl, node, .addwrap),
+ .add_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation),
.sub => return simpleBinOp(gz, scope, rl, node, .sub),
.sub_wrap => return simpleBinOp(gz, scope, rl, node, .subwrap),
+ .sub_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation),
.mul => return simpleBinOp(gz, scope, rl, node, .mul),
.mul_wrap => return simpleBinOp(gz, scope, rl, node, .mulwrap),
+ .mul_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation),
.div => return simpleBinOp(gz, scope, rl, node, .div),
.mod => return simpleBinOp(gz, scope, rl, node, .mod_rem),
.bit_and => {
@@ -2685,6 +2713,31 @@ fn assignOp(
_ = try gz.addBin(.store, lhs_ptr, result);
}
+// TODO: is there an existing method to accomplish this?
+// TODO: likely rename this to indicate rhs type coercion or add more params to make it more general
+fn assignBinOpExt(
+ gz: *GenZir,
+ scope: *Scope,
+ infix_node: Ast.Node.Index,
+ op_inst_tag: Zir.Inst.Extended,
+) InnerError!void {
+ try emitDbgNode(gz, infix_node);
+ const astgen = gz.astgen;
+ const tree = astgen.tree;
+ const node_datas = tree.nodes.items(.data);
+
+ const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs);
+ const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
+ const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node);
+ const rhs = try expr(gz, scope, .{ .coerced_ty = lhs_type }, node_datas[infix_node].rhs);
+ const result = try gz.addExtendedPayload(op_inst_tag, Zir.Inst.BinNode{
+ .node = gz.nodeIndexToRelative(infix_node),
+ .lhs = lhs,
+ .rhs = rhs,
+ });
+ _ = try gz.addBin(.store, lhs_ptr, result);
+}
+
fn assignShift(
gz: *GenZir,
scope: *Scope,
@@ -2708,6 +2761,29 @@ fn assignShift(
_ = try gz.addBin(.store, lhs_ptr, result);
}
+fn assignShiftSat(
+ gz: *GenZir,
+ scope: *Scope,
+ infix_node: ast.Node.Index,
+ op_inst_tag: Zir.Inst.Tag,
+) InnerError!void {
+ try emitDbgNode(gz, infix_node);
+ const astgen = gz.astgen;
+ const tree = astgen.tree;
+ const node_datas = tree.nodes.items(.data);
+
+ const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs);
+ const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
+ const rhs_type = try gz.addUnNode(.typeof, lhs, infix_node);
+ const rhs = try expr(gz, scope, .{ .ty = rhs_type }, node_datas[infix_node].rhs);
+
+ const result = try gz.addPlNode(op_inst_tag, infix_node, Zir.Inst.Bin{
+ .lhs = lhs,
+ .rhs = rhs,
+ });
+ _ = try gz.addBin(.store, lhs_ptr, result);
+}
+
fn boolNot(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerError!Zir.Inst.Ref {
const astgen = gz.astgen;
const tree = astgen.tree;
@@ -7827,6 +7903,26 @@ fn shiftOp(
return rvalue(gz, rl, result, node);
}
+// TODO: is there an existing way to do this?
+// TODO: likely rename this to reflect result_loc == .none or add more params to make it more general
+fn binOpExt(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: Ast.Node.Index,
+ lhs_node: Ast.Node.Index,
+ rhs_node: Ast.Node.Index,
+ tag: Zir.Inst.Extended,
+) InnerError!Zir.Inst.Ref {
+ const lhs = try expr(gz, scope, .none, lhs_node);
+ const rhs = try expr(gz, scope, .none, rhs_node);
+ const result = try gz.addExtendedPayload(tag, Zir.Inst.Bin{
+ .lhs = lhs,
+ .rhs = rhs,
+ });
+ return rvalue(gz, rl, result, node);
+}
+
fn cImport(
gz: *GenZir,
scope: *Scope,
@@ -8119,26 +8215,32 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool
.asm_simple,
.add,
.add_wrap,
+ .add_sat,
.array_cat,
.array_mult,
.assign,
.assign_bit_and,
.assign_bit_or,
.assign_bit_shift_left,
+ .assign_bit_shift_left_sat,
.assign_bit_shift_right,
.assign_bit_xor,
.assign_div,
.assign_sub,
.assign_sub_wrap,
+ .assign_sub_sat,
.assign_mod,
.assign_add,
.assign_add_wrap,
+ .assign_add_sat,
.assign_mul,
.assign_mul_wrap,
+ .assign_mul_sat,
.bang_equal,
.bit_and,
.bit_or,
.bit_shift_left,
+ .bit_shift_left_sat,
.bit_shift_right,
.bit_xor,
.bool_and,
@@ -8154,10 +8256,12 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool
.mod,
.mul,
.mul_wrap,
+ .mul_sat,
.switch_range,
.field_access,
.sub,
.sub_wrap,
+ .sub_sat,
.slice,
.slice_open,
.slice_sentinel,
@@ -8352,26 +8456,32 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) enum { never
.tagged_union_enum_tag_trailing,
.add,
.add_wrap,
+ .add_sat,
.array_cat,
.array_mult,
.assign,
.assign_bit_and,
.assign_bit_or,
.assign_bit_shift_left,
+ .assign_bit_shift_left_sat,
.assign_bit_shift_right,
.assign_bit_xor,
.assign_div,
.assign_sub,
.assign_sub_wrap,
+ .assign_sub_sat,
.assign_mod,
.assign_add,
.assign_add_wrap,
+ .assign_add_sat,
.assign_mul,
.assign_mul_wrap,
+ .assign_mul_sat,
.bang_equal,
.bit_and,
.bit_or,
.bit_shift_left,
+ .bit_shift_left_sat,
.bit_shift_right,
.bit_xor,
.bool_and,
@@ -8387,9 +8497,11 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) enum { never
.mod,
.mul,
.mul_wrap,
+ .mul_sat,
.switch_range,
.sub,
.sub_wrap,
+ .sub_sat,
.slice,
.slice_open,
.slice_sentinel,
@@ -8524,26 +8636,32 @@ fn nodeImpliesRuntimeBits(tree: *const Ast, start_node: Ast.Node.Index) bool {
.asm_simple,
.add,
.add_wrap,
+ .add_sat,
.array_cat,
.array_mult,
.assign,
.assign_bit_and,
.assign_bit_or,
.assign_bit_shift_left,
+ .assign_bit_shift_left_sat,
.assign_bit_shift_right,
.assign_bit_xor,
.assign_div,
.assign_sub,
.assign_sub_wrap,
+ .assign_sub_sat,
.assign_mod,
.assign_add,
.assign_add_wrap,
+ .assign_add_sat,
.assign_mul,
.assign_mul_wrap,
+ .assign_mul_sat,
.bang_equal,
.bit_and,
.bit_or,
.bit_shift_left,
+ .bit_shift_left_sat,
.bit_shift_right,
.bit_xor,
.bool_and,
@@ -8559,10 +8677,12 @@ fn nodeImpliesRuntimeBits(tree: *const Ast, start_node: Ast.Node.Index) bool {
.mod,
.mul,
.mul_wrap,
+ .mul_sat,
.switch_range,
.field_access,
.sub,
.sub_wrap,
+ .sub_sat,
.slice,
.slice_open,
.slice_sentinel,
diff --git a/src/Liveness.zig b/src/Liveness.zig
index 25dd29b0f657..c34153b76fba 100644
--- a/src/Liveness.zig
+++ b/src/Liveness.zig
@@ -226,10 +226,13 @@ fn analyzeInst(
switch (inst_tags[inst]) {
.add,
.addwrap,
+ .addsat,
.sub,
.subwrap,
+ .subsat,
.mul,
.mulwrap,
+ .mulsat,
.div,
.rem,
.mod,
@@ -252,6 +255,7 @@ fn analyzeInst(
.ptr_elem_val,
.ptr_ptr_elem_val,
.shl,
+ .shl_sat,
.shr,
.atomic_store_unordered,
.atomic_store_monotonic,
diff --git a/src/codegen.zig b/src/codegen.zig
index 7c359e90c02a..a1f812388f15 100644
--- a/src/codegen.zig
+++ b/src/codegen.zig
@@ -826,10 +826,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// zig fmt: off
.add, .ptr_add => try self.airAdd(inst),
.addwrap => try self.airAddWrap(inst),
+ .addsat => try self.airArithmeticOpSat(inst, "addsat"),
.sub, .ptr_sub => try self.airSub(inst),
.subwrap => try self.airSubWrap(inst),
+ .subsat => try self.airArithmeticOpSat(inst, "subsat"),
.mul => try self.airMul(inst),
.mulwrap => try self.airMulWrap(inst),
+ .mulsat => try self.airArithmeticOpSat(inst, "mulsat"),
.div => try self.airDiv(inst),
.rem => try self.airRem(inst),
.mod => try self.airMod(inst),
@@ -848,6 +851,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.xor => try self.airXor(inst),
.shr => try self.airShr(inst),
.shl => try self.airShl(inst),
+ .shl_sat => try self.airArithmeticOpSat(inst, "shl_sat"),
.alloc => try self.airAlloc(inst),
.arg => try self.airArg(inst),
@@ -1320,6 +1324,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}
+ fn airArithmeticOpSat(self: *Self, inst: Air.Inst.Index, comptime name: []const u8) !void {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+ else => return self.fail("TODO implement " ++ name ++ " for {}", .{self.target.cpu.arch}),
+ };
+ return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+ }
+
fn airMul(self: *Self, inst: Air.Inst.Index) !void {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index 4964f17cd341..dce0c10b4cf8 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -885,14 +885,17 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
// that wrapping is UB.
.add, .ptr_add => try airBinOp( f, inst, " + "),
.addwrap => try airWrapOp(f, inst, " + ", "addw_"),
+ .addsat => return o.dg.fail("TODO: C backend: implement codegen for addsat", .{}),
// TODO use a different strategy for sub that communicates to the optimizer
// that wrapping is UB.
.sub, .ptr_sub => try airBinOp( f, inst, " - "),
.subwrap => try airWrapOp(f, inst, " - ", "subw_"),
+ .subsat => return o.dg.fail("TODO: C backend: implement codegen for subsat", .{}),
// TODO use a different strategy for mul that communicates to the optimizer
// that wrapping is UB.
.mul => try airBinOp( f, inst, " * "),
.mulwrap => try airWrapOp(f, inst, " * ", "mulw_"),
+ .mulsat => return o.dg.fail("TODO: C backend: implement codegen for mulsat", .{}),
// TODO use a different strategy for div that communicates to the optimizer
// that wrapping is UB.
.div => try airBinOp( f, inst, " / "),
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index f65e0f6d723b..c21985d1c5c3 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -1236,12 +1236,15 @@ pub const FuncGen = struct {
for (body) |inst| {
const opt_value: ?*const llvm.Value = switch (air_tags[inst]) {
// zig fmt: off
- .add => try self.airAdd(inst, false),
- .addwrap => try self.airAdd(inst, true),
- .sub => try self.airSub(inst, false),
- .subwrap => try self.airSub(inst, true),
- .mul => try self.airMul(inst, false),
- .mulwrap => try self.airMul(inst, true),
+ .add => try self.airAdd(inst, .standard),
+ .addwrap => try self.airAdd(inst, .wrapping),
+ .addsat => try self.airAdd(inst, .saturated),
+ .sub => try self.airSub(inst, .standard),
+ .subwrap => try self.airSub(inst, .wrapping),
+ .subsat => try self.airSub(inst, .saturated),
+ .mul => try self.airMul(inst, .standard),
+ .mulwrap => try self.airMul(inst, .wrapping),
+ .mulsat => try self.airMul(inst, .saturated),
.div => try self.airDiv(inst),
.rem => try self.airRem(inst),
.mod => try self.airMod(inst),
@@ -1252,7 +1255,8 @@ pub const FuncGen = struct {
.bit_or, .bool_or => try self.airOr(inst),
.xor => try self.airXor(inst),
- .shl => try self.airShl(inst),
+ .shl => try self.airShl(inst, false),
+ .shl_sat => try self.airShl(inst, true),
.shr => try self.airShr(inst),
.cmp_eq => try self.airCmp(inst, .eq),
@@ -2024,7 +2028,8 @@ pub const FuncGen = struct {
return self.todo("implement llvm codegen for 'airWrapErrUnionErr'", .{});
}
- fn airAdd(self: *FuncGen, inst: Air.Inst.Index, wrap: bool) !?*const llvm.Value {
+ const ArithmeticType = enum { standard, wrapping, saturated };
+ fn airAdd(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
@@ -2033,13 +2038,20 @@ pub const FuncGen = struct {
const rhs = try self.resolveInst(bin_op.rhs);
const inst_ty = self.air.typeOfIndex(inst);
- if (inst_ty.isRuntimeFloat()) return self.builder.buildFAdd(lhs, rhs, "");
- if (wrap) return self.builder.buildAdd(lhs, rhs, "");
+ if (inst_ty.isFloat()) return self.builder.buildFAdd(lhs, rhs, "");
+ if (ty == .wrapping)
+ return self.builder.buildAdd(lhs, rhs, "")
+ else if (ty == .saturated) {
+ if (inst_ty.isSignedInt())
+ return self.builder.buildSAddSat(lhs, rhs, "")
+ else
+ return self.builder.buildUAddSat(lhs, rhs, "");
+ }
if (inst_ty.isSignedInt()) return self.builder.buildNSWAdd(lhs, rhs, "");
return self.builder.buildNUWAdd(lhs, rhs, "");
}
- fn airSub(self: *FuncGen, inst: Air.Inst.Index, wrap: bool) !?*const llvm.Value {
+ fn airSub(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
@@ -2048,13 +2060,20 @@ pub const FuncGen = struct {
const rhs = try self.resolveInst(bin_op.rhs);
const inst_ty = self.air.typeOfIndex(inst);
- if (inst_ty.isRuntimeFloat()) return self.builder.buildFSub(lhs, rhs, "");
- if (wrap) return self.builder.buildSub(lhs, rhs, "");
+ if (inst_ty.isFloat()) return self.builder.buildFSub(lhs, rhs, "");
+ if (ty == .wrapping)
+ return self.builder.buildSub(lhs, rhs, "")
+ else if (ty == .saturated) {
+ if (inst_ty.isSignedInt())
+ return self.builder.buildSSubSat(lhs, rhs, "")
+ else
+ return self.builder.buildUSubSat(lhs, rhs, "");
+ }
if (inst_ty.isSignedInt()) return self.builder.buildNSWSub(lhs, rhs, "");
return self.builder.buildNUWSub(lhs, rhs, "");
}
- fn airMul(self: *FuncGen, inst: Air.Inst.Index, wrap: bool) !?*const llvm.Value {
+ fn airMul(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
@@ -2063,8 +2082,15 @@ pub const FuncGen = struct {
const rhs = try self.resolveInst(bin_op.rhs);
const inst_ty = self.air.typeOfIndex(inst);
- if (inst_ty.isRuntimeFloat()) return self.builder.buildFMul(lhs, rhs, "");
- if (wrap) return self.builder.buildMul(lhs, rhs, "");
+ if (inst_ty.isFloat()) return self.builder.buildFMul(lhs, rhs, "");
+ if (ty == .wrapping)
+ return self.builder.buildMul(lhs, rhs, "")
+ else if (ty == .saturated) {
+ if (inst_ty.isSignedInt())
+ return self.builder.buildSMulFixSat(lhs, rhs, "")
+ else
+ return self.builder.buildUMulFixSat(lhs, rhs, "");
+ }
if (inst_ty.isSignedInt()) return self.builder.buildNSWMul(lhs, rhs, "");
return self.builder.buildNUWMul(lhs, rhs, "");
}
@@ -2174,7 +2200,7 @@ pub const FuncGen = struct {
return self.builder.buildXor(lhs, rhs, "");
}
- fn airShl(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ fn airShl(self: *FuncGen, inst: Air.Inst.Index, sat: bool) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
@@ -2186,6 +2212,12 @@ pub const FuncGen = struct {
self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "")
else
rhs;
+ if (sat) {
+ return if (lhs_type.isSignedInt())
+ self.builder.buildSShlSat(lhs, casted_rhs, "")
+ else
+ self.builder.buildUShlSat(lhs, casted_rhs, "");
+ }
return self.builder.buildShl(lhs, casted_rhs, "");
}
diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig
index 9d32682260a3..178c38123559 100644
--- a/src/codegen/llvm/bindings.zig
+++ b/src/codegen/llvm/bindings.zig
@@ -397,6 +397,12 @@ pub const Builder = opaque {
pub const buildNUWAdd = LLVMBuildNUWAdd;
extern fn LLVMBuildNUWAdd(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+ pub const buildSAddSat = ZigLLVMBuildSAddSat;
+ extern fn ZigLLVMBuildSAddSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+ pub const buildUAddSat = ZigLLVMBuildUAddSat;
+ extern fn ZigLLVMBuildUAddSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
pub const buildFSub = LLVMBuildFSub;
extern fn LLVMBuildFSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
@@ -409,6 +415,12 @@ pub const Builder = opaque {
pub const buildNUWSub = LLVMBuildNUWSub;
extern fn LLVMBuildNUWSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+ pub const buildSSubSat = ZigLLVMBuildSSubSat;
+ extern fn ZigLLVMBuildSSubSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+ pub const buildUSubSat = ZigLLVMBuildUSubSat;
+ extern fn ZigLLVMBuildUSubSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
pub const buildFMul = LLVMBuildFMul;
extern fn LLVMBuildFMul(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
@@ -421,6 +433,12 @@ pub const Builder = opaque {
pub const buildNUWMul = LLVMBuildNUWMul;
extern fn LLVMBuildNUWMul(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+ pub const buildSMulFixSat = ZigLLVMBuildSMulFixSat;
+ extern fn ZigLLVMBuildSMulFixSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+ pub const buildUMulFixSat = ZigLLVMBuildUMulFixSat;
+ extern fn ZigLLVMBuildUMulFixSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
pub const buildUDiv = LLVMBuildUDiv;
extern fn LLVMBuildUDiv(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
@@ -451,6 +469,12 @@ pub const Builder = opaque {
pub const buildShl = LLVMBuildShl;
extern fn LLVMBuildShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+ pub const buildSShlSat = ZigLLVMBuildSShlSat;
+ extern fn ZigLLVMBuildSShlSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+ pub const buildUShlSat = ZigLLVMBuildUShlSat;
+ extern fn ZigLLVMBuildUShlSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
pub const buildOr = LLVMBuildOr;
extern fn LLVMBuildOr(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
diff --git a/src/print_air.zig b/src/print_air.zig
index 90df06760bfb..7d178b52f38b 100644
--- a/src/print_air.zig
+++ b/src/print_air.zig
@@ -104,10 +104,13 @@ const Writer = struct {
.add,
.addwrap,
+ .addsat,
.sub,
.subwrap,
+ .subsat,
.mul,
.mulwrap,
+ .mulsat,
.div,
.rem,
.mod,
@@ -130,6 +133,7 @@ const Writer = struct {
.ptr_elem_val,
.ptr_ptr_elem_val,
.shl,
+ .shl_sat,
.shr,
.set_union_tag,
=> try w.writeBinOp(s, inst),
diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp
index 13c37fc8399c..e31a7015b09d 100644
--- a/src/stage1/all_types.hpp
+++ b/src/stage1/all_types.hpp
@@ -812,14 +812,18 @@ enum BinOpType {
BinOpTypeInvalid,
BinOpTypeAssign,
BinOpTypeAssignTimes,
+ BinOpTypeAssignTimesSat,
BinOpTypeAssignTimesWrap,
BinOpTypeAssignDiv,
BinOpTypeAssignMod,
BinOpTypeAssignPlus,
+ BinOpTypeAssignPlusSat,
BinOpTypeAssignPlusWrap,
BinOpTypeAssignMinus,
+ BinOpTypeAssignMinusSat,
BinOpTypeAssignMinusWrap,
BinOpTypeAssignBitShiftLeft,
+ BinOpTypeAssignBitShiftLeftSat,
BinOpTypeAssignBitShiftRight,
BinOpTypeAssignBitAnd,
BinOpTypeAssignBitXor,
@@ -836,12 +840,16 @@ enum BinOpType {
BinOpTypeBinXor,
BinOpTypeBinAnd,
BinOpTypeBitShiftLeft,
+ BinOpTypeBitShiftLeftSat,
BinOpTypeBitShiftRight,
BinOpTypeAdd,
+ BinOpTypeAddSat,
BinOpTypeAddWrap,
BinOpTypeSub,
+ BinOpTypeSubSat,
BinOpTypeSubWrap,
BinOpTypeMult,
+ BinOpTypeMultSat,
BinOpTypeMultWrap,
BinOpTypeDiv,
BinOpTypeMod,
@@ -2958,10 +2966,10 @@ enum IrBinOp {
IrBinOpArrayMult,
IrBinOpMaximum,
IrBinOpMinimum,
- IrBinOpSatAdd,
- IrBinOpSatSub,
- IrBinOpSatMul,
- IrBinOpSatShl,
+ IrBinOpAddSat,
+ IrBinOpSubSat,
+ IrBinOpMultSat,
+ IrBinOpShlSat,
};
struct Stage1ZirInstBinOp {
diff --git a/src/stage1/astgen.cpp b/src/stage1/astgen.cpp
index 9e5d9da9ee6a..14808dd0a271 100644
--- a/src/stage1/astgen.cpp
+++ b/src/stage1/astgen.cpp
@@ -3672,6 +3672,8 @@ static Stage1ZirInst *astgen_bin_op(Stage1AstGen *ag, Scope *scope, AstNode *nod
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpMult), lval, result_loc);
case BinOpTypeAssignTimesWrap:
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpMultWrap), lval, result_loc);
+ case BinOpTypeAssignTimesSat:
+ return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpMultSat), lval, result_loc);
case BinOpTypeAssignDiv:
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpDivUnspecified), lval, result_loc);
case BinOpTypeAssignMod:
@@ -3680,12 +3682,18 @@ static Stage1ZirInst *astgen_bin_op(Stage1AstGen *ag, Scope *scope, AstNode *nod
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpAdd), lval, result_loc);
case BinOpTypeAssignPlusWrap:
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpAddWrap), lval, result_loc);
+ case BinOpTypeAssignPlusSat:
+ return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpAddSat), lval, result_loc);
case BinOpTypeAssignMinus:
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpSub), lval, result_loc);
case BinOpTypeAssignMinusWrap:
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpSubWrap), lval, result_loc);
+ case BinOpTypeAssignMinusSat:
+ return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpSubSat), lval, result_loc);
case BinOpTypeAssignBitShiftLeft:
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpBitShiftLeftLossy), lval, result_loc);
+ case BinOpTypeAssignBitShiftLeftSat:
+ return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpShlSat), lval, result_loc);
case BinOpTypeAssignBitShiftRight:
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpBitShiftRightLossy), lval, result_loc);
case BinOpTypeAssignBitAnd:
@@ -3718,20 +3726,28 @@ static Stage1ZirInst *astgen_bin_op(Stage1AstGen *ag, Scope *scope, AstNode *nod
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpBinAnd), lval, result_loc);
case BinOpTypeBitShiftLeft:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpBitShiftLeftLossy), lval, result_loc);
+ case BinOpTypeBitShiftLeftSat:
+ return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpShlSat), lval, result_loc);
case BinOpTypeBitShiftRight:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpBitShiftRightLossy), lval, result_loc);
case BinOpTypeAdd:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpAdd), lval, result_loc);
case BinOpTypeAddWrap:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpAddWrap), lval, result_loc);
+ case BinOpTypeAddSat:
+ return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpAddSat), lval, result_loc);
case BinOpTypeSub:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpSub), lval, result_loc);
case BinOpTypeSubWrap:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpSubWrap), lval, result_loc);
+ case BinOpTypeSubSat:
+ return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpSubSat), lval, result_loc);
case BinOpTypeMult:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpMult), lval, result_loc);
case BinOpTypeMultWrap:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpMultWrap), lval, result_loc);
+ case BinOpTypeMultSat:
+ return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpMultSat), lval, result_loc);
case BinOpTypeDiv:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpDivUnspecified), lval, result_loc);
case BinOpTypeMod:
@@ -4716,7 +4732,7 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast
if (arg1_value == ag->codegen->invalid_inst_src)
return arg1_value;
- Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSatAdd, arg0_value, arg1_value, true);
+ Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpAddSat, arg0_value, arg1_value, true);
return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
}
case BuiltinFnIdSatSub:
@@ -4731,7 +4747,7 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast
if (arg1_value == ag->codegen->invalid_inst_src)
return arg1_value;
- Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSatSub, arg0_value, arg1_value, true);
+ Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSubSat, arg0_value, arg1_value, true);
return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
}
case BuiltinFnIdSatMul:
@@ -4746,7 +4762,7 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast
if (arg1_value == ag->codegen->invalid_inst_src)
return arg1_value;
- Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSatMul, arg0_value, arg1_value, true);
+ Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpMultSat, arg0_value, arg1_value, true);
return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
}
case BuiltinFnIdSatShl:
@@ -4761,7 +4777,7 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast
if (arg1_value == ag->codegen->invalid_inst_src)
return arg1_value;
- Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSatShl, arg0_value, arg1_value, true);
+ Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpShlSat, arg0_value, arg1_value, true);
return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
}
case BuiltinFnIdMemcpy:
diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp
index f84847a9fef9..eade84335495 100644
--- a/src/stage1/codegen.cpp
+++ b/src/stage1/codegen.cpp
@@ -3333,7 +3333,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, Stage1Air *executable,
} else {
zig_unreachable();
}
- case IrBinOpSatAdd:
+ case IrBinOpAddSat:
if (scalar_type->id == ZigTypeIdInt) {
if (scalar_type->data.integral.is_signed) {
return ZigLLVMBuildSAddSat(g->builder, op1_value, op2_value, "");
@@ -3343,7 +3343,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, Stage1Air *executable,
} else {
zig_unreachable();
}
- case IrBinOpSatSub:
+ case IrBinOpSubSat:
if (scalar_type->id == ZigTypeIdInt) {
if (scalar_type->data.integral.is_signed) {
return ZigLLVMBuildSSubSat(g->builder, op1_value, op2_value, "");
@@ -3353,7 +3353,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, Stage1Air *executable,
} else {
zig_unreachable();
}
- case IrBinOpSatMul:
+ case IrBinOpMultSat:
if (scalar_type->id == ZigTypeIdInt) {
if (scalar_type->data.integral.is_signed) {
return ZigLLVMBuildSMulFixSat(g->builder, op1_value, op2_value, "");
@@ -3363,7 +3363,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, Stage1Air *executable,
} else {
zig_unreachable();
}
- case IrBinOpSatShl:
+ case IrBinOpShlSat:
if (scalar_type->id == ZigTypeIdInt) {
if (scalar_type->data.integral.is_signed) {
return ZigLLVMBuildSShlSat(g->builder, op1_value, op2_value, "");
diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp
index b853961beb72..2f2cfe08f39e 100644
--- a/src/stage1/ir.cpp
+++ b/src/stage1/ir.cpp
@@ -9820,28 +9820,28 @@ static ErrorMsg *ir_eval_math_op_scalar(IrAnalyze *ira, Scope *scope, AstNode *s
float_min(out_val, op1_val, op2_val);
}
break;
- case IrBinOpSatAdd:
+ case IrBinOpAddSat:
if (is_int) {
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);
} else {
zig_unreachable();
}
break;
- case IrBinOpSatSub:
+ case IrBinOpSubSat:
if (is_int) {
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);
} else {
zig_unreachable();
}
break;
- case IrBinOpSatMul:
+ case IrBinOpMultSat:
if (is_int) {
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);
} else {
zig_unreachable();
}
break;
- case IrBinOpSatShl:
+ case IrBinOpShlSat:
if (is_int) {
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);
} else {
@@ -10069,10 +10069,10 @@ static bool ok_float_op(IrBinOp op) {
case IrBinOpBitShiftRightExact:
case IrBinOpAddWrap:
case IrBinOpSubWrap:
- case IrBinOpSatAdd:
- case IrBinOpSatSub:
- case IrBinOpSatMul:
- case IrBinOpSatShl:
+ case IrBinOpAddSat:
+ case IrBinOpSubSat:
+ case IrBinOpMultSat:
+ case IrBinOpShlSat:
case IrBinOpMultWrap:
case IrBinOpArrayCat:
case IrBinOpArrayMult:
@@ -11046,10 +11046,10 @@ static Stage1AirInst *ir_analyze_instruction_bin_op(IrAnalyze *ira, Stage1ZirIns
case IrBinOpRemMod:
case IrBinOpMaximum:
case IrBinOpMinimum:
- case IrBinOpSatAdd:
- case IrBinOpSatSub:
- case IrBinOpSatMul:
- case IrBinOpSatShl:
+ case IrBinOpAddSat:
+ case IrBinOpSubSat:
+ case IrBinOpMultSat:
+ case IrBinOpShlSat:
return ir_analyze_bin_op_math(ira, bin_op_instruction);
case IrBinOpArrayCat:
return ir_analyze_array_cat(ira, bin_op_instruction);
diff --git a/src/stage1/ir_print.cpp b/src/stage1/ir_print.cpp
index a76d3e4d5afd..f92f146d840d 100644
--- a/src/stage1/ir_print.cpp
+++ b/src/stage1/ir_print.cpp
@@ -737,13 +737,13 @@ static const char *ir_bin_op_id_str(IrBinOp op_id) {
return "@maximum";
case IrBinOpMinimum:
return "@minimum";
- case IrBinOpSatAdd:
+ case IrBinOpAddSat:
return "@addWithSaturation";
- case IrBinOpSatSub:
+ case IrBinOpSubSat:
return "@subWithSaturation";
- case IrBinOpSatMul:
+ case IrBinOpMultSat:
return "@mulWithSaturation";
- case IrBinOpSatShl:
+ case IrBinOpShlSat:
return "@shlWithSaturation";
}
zig_unreachable();
diff --git a/src/stage1/parser.cpp b/src/stage1/parser.cpp
index f7061bb23275..fdc0777aff1c 100644
--- a/src/stage1/parser.cpp
+++ b/src/stage1/parser.cpp
@@ -2381,6 +2381,7 @@ static AstNode *ast_parse_switch_item(ParseContext *pc) {
// / PLUSEQUAL
// / MINUSEQUAL
// / LARROW2EQUAL
+// / LARROW2PIPEEQUAL
// / RARROW2EQUAL
// / AMPERSANDEQUAL
// / CARETEQUAL
@@ -2388,6 +2389,9 @@ static AstNode *ast_parse_switch_item(ParseContext *pc) {
// / ASTERISKPERCENTEQUAL
// / PLUSPERCENTEQUAL
// / MINUSPERCENTEQUAL
+// / ASTERISKPIPEEQUAL
+// / PLUSPIPEEQUAL
+// / MINUSPIPEEQUAL
// / EQUAL
static AstNode *ast_parse_assign_op(ParseContext *pc) {
// In C, we have `T arr[N] = {[i] = T{}};` but it doesn't
@@ -2396,17 +2400,21 @@ static AstNode *ast_parse_assign_op(ParseContext *pc) {
table[TokenIdBitAndEq] = BinOpTypeAssignBitAnd;
table[TokenIdBitOrEq] = BinOpTypeAssignBitOr;
table[TokenIdBitShiftLeftEq] = BinOpTypeAssignBitShiftLeft;
+ table[TokenIdBitShiftLeftPipeEq] = BinOpTypeAssignBitShiftLeftSat;
table[TokenIdBitShiftRightEq] = BinOpTypeAssignBitShiftRight;
table[TokenIdBitXorEq] = BinOpTypeAssignBitXor;
table[TokenIdDivEq] = BinOpTypeAssignDiv;
table[TokenIdEq] = BinOpTypeAssign;
table[TokenIdMinusEq] = BinOpTypeAssignMinus;
table[TokenIdMinusPercentEq] = BinOpTypeAssignMinusWrap;
+ table[TokenIdMinusPipeEq] = BinOpTypeAssignMinusSat;
table[TokenIdModEq] = BinOpTypeAssignMod;
table[TokenIdPlusEq] = BinOpTypeAssignPlus;
table[TokenIdPlusPercentEq] = BinOpTypeAssignPlusWrap;
+ table[TokenIdPlusPipeEq] = BinOpTypeAssignPlusSat;
table[TokenIdTimesEq] = BinOpTypeAssignTimes;
table[TokenIdTimesPercentEq] = BinOpTypeAssignTimesWrap;
+ table[TokenIdTimesPipeEq] = BinOpTypeAssignTimesSat;
BinOpType op = table[pc->token_ids[pc->current_token]];
if (op != BinOpTypeInvalid) {
@@ -2483,10 +2491,12 @@ static AstNode *ast_parse_bitwise_op(ParseContext *pc) {
// BitShiftOp
// <- LARROW2
+// / LARROW2PIPE
// / RARROW2
static AstNode *ast_parse_bit_shift_op(ParseContext *pc) {
BinOpType table[TokenIdCount] = {};
table[TokenIdBitShiftLeft] = BinOpTypeBitShiftLeft;
+ table[TokenIdBitShiftLeftPipe] = BinOpTypeBitShiftLeftSat;
table[TokenIdBitShiftRight] = BinOpTypeBitShiftRight;
BinOpType op = table[pc->token_ids[pc->current_token]];
@@ -2506,6 +2516,8 @@ static AstNode *ast_parse_bit_shift_op(ParseContext *pc) {
// / PLUS2
// / PLUSPERCENT
// / MINUSPERCENT
+// / PLUSPIPE
+// / MINUSPIPE
static AstNode *ast_parse_addition_op(ParseContext *pc) {
BinOpType table[TokenIdCount] = {};
table[TokenIdPlus] = BinOpTypeAdd;
@@ -2513,6 +2525,8 @@ static AstNode *ast_parse_addition_op(ParseContext *pc) {
table[TokenIdPlusPlus] = BinOpTypeArrayCat;
table[TokenIdPlusPercent] = BinOpTypeAddWrap;
table[TokenIdMinusPercent] = BinOpTypeSubWrap;
+ table[TokenIdPlusPipe] = BinOpTypeAddSat;
+ table[TokenIdMinusPipe] = BinOpTypeSubSat;
BinOpType op = table[pc->token_ids[pc->current_token]];
if (op != BinOpTypeInvalid) {
@@ -2532,6 +2546,7 @@ static AstNode *ast_parse_addition_op(ParseContext *pc) {
// / PERCENT
// / ASTERISK2
// / ASTERISKPERCENT
+// / ASTERISKPIPE
static AstNode *ast_parse_multiply_op(ParseContext *pc) {
BinOpType table[TokenIdCount] = {};
table[TokenIdBarBar] = BinOpTypeMergeErrorSets;
@@ -2540,6 +2555,7 @@ static AstNode *ast_parse_multiply_op(ParseContext *pc) {
table[TokenIdPercent] = BinOpTypeMod;
table[TokenIdStarStar] = BinOpTypeArrayMult;
table[TokenIdTimesPercent] = BinOpTypeMultWrap;
+ table[TokenIdTimesPipe] = BinOpTypeMultSat;
BinOpType op = table[pc->token_ids[pc->current_token]];
if (op != BinOpTypeInvalid) {
diff --git a/src/stage1/tokenizer.cpp b/src/stage1/tokenizer.cpp
index f10579c96631..356019392722 100644
--- a/src/stage1/tokenizer.cpp
+++ b/src/stage1/tokenizer.cpp
@@ -226,8 +226,10 @@ enum TokenizeState {
TokenizeState_pipe,
TokenizeState_minus,
TokenizeState_minus_percent,
+ TokenizeState_minus_pipe,
TokenizeState_asterisk,
TokenizeState_asterisk_percent,
+ TokenizeState_asterisk_pipe,
TokenizeState_slash,
TokenizeState_line_comment_start,
TokenizeState_line_comment,
@@ -257,8 +259,10 @@ enum TokenizeState {
TokenizeState_percent,
TokenizeState_plus,
TokenizeState_plus_percent,
+ TokenizeState_plus_pipe,
TokenizeState_angle_bracket_left,
TokenizeState_angle_bracket_angle_bracket_left,
+ TokenizeState_angle_bracket_angle_bracket_left_pipe,
TokenizeState_angle_bracket_right,
TokenizeState_angle_bracket_angle_bracket_right,
TokenizeState_period,
@@ -548,6 +552,9 @@ void tokenize(const char *source, Tokenization *out) {
case '%':
t.state = TokenizeState_asterisk_percent;
break;
+ case '|':
+ t.state = TokenizeState_asterisk_pipe;
+ break;
default:
t.state = TokenizeState_start;
continue;
@@ -568,6 +575,21 @@ void tokenize(const char *source, Tokenization *out) {
continue;
}
break;
+ case TokenizeState_asterisk_pipe:
+ switch (c) {
+ case 0:
+ t.out->ids.last() = TokenIdTimesPipe;
+ goto eof;
+ case '=':
+ t.out->ids.last() = TokenIdTimesPipeEq;
+ t.state = TokenizeState_start;
+ break;
+ default:
+ t.out->ids.last() = TokenIdTimesPipe;
+ t.state = TokenizeState_start;
+ continue;
+ }
+ break;
case TokenizeState_percent:
switch (c) {
case 0:
@@ -596,6 +618,9 @@ void tokenize(const char *source, Tokenization *out) {
case '%':
t.state = TokenizeState_plus_percent;
break;
+ case '|':
+ t.state = TokenizeState_plus_pipe;
+ break;
default:
t.state = TokenizeState_start;
continue;
@@ -616,6 +641,21 @@ void tokenize(const char *source, Tokenization *out) {
continue;
}
break;
+ case TokenizeState_plus_pipe:
+ switch (c) {
+ case 0:
+ t.out->ids.last() = TokenIdPlusPipe;
+ goto eof;
+ case '=':
+ t.out->ids.last() = TokenIdPlusPipeEq;
+ t.state = TokenizeState_start;
+ break;
+ default:
+ t.out->ids.last() = TokenIdPlusPipe;
+ t.state = TokenizeState_start;
+ continue;
+ }
+ break;
case TokenizeState_caret:
switch (c) {
case 0:
@@ -891,6 +931,9 @@ void tokenize(const char *source, Tokenization *out) {
case '%':
t.state = TokenizeState_minus_percent;
break;
+ case '|':
+ t.state = TokenizeState_minus_pipe;
+ break;
default:
t.state = TokenizeState_start;
continue;
@@ -911,6 +954,21 @@ void tokenize(const char *source, Tokenization *out) {
continue;
}
break;
+ case TokenizeState_minus_pipe:
+ switch (c) {
+ case 0:
+ t.out->ids.last() = TokenIdMinusPipe;
+ goto eof;
+ case '=':
+ t.out->ids.last() = TokenIdMinusPipeEq;
+ t.state = TokenizeState_start;
+ break;
+ default:
+ t.out->ids.last() = TokenIdMinusPipe;
+ t.state = TokenizeState_start;
+ continue;
+ }
+ break;
case TokenizeState_angle_bracket_left:
switch (c) {
case 0:
@@ -936,12 +994,31 @@ void tokenize(const char *source, Tokenization *out) {
t.out->ids.last() = TokenIdBitShiftLeftEq;
t.state = TokenizeState_start;
break;
+ case '|':
+ // t.out->ids.last() = TokenIdBitShiftLeftPipe;
+ t.state = TokenizeState_angle_bracket_angle_bracket_left_pipe;
+ break;
default:
t.out->ids.last() = TokenIdBitShiftLeft;
t.state = TokenizeState_start;
continue;
}
break;
+ case TokenizeState_angle_bracket_angle_bracket_left_pipe:
+ switch (c) {
+ case 0:
+ t.out->ids.last() = TokenIdBitShiftLeftPipe;
+ goto eof;
+ case '=':
+ t.out->ids.last() = TokenIdBitShiftLeftPipeEq;
+ t.state = TokenizeState_start;
+ break;
+ default:
+ t.out->ids.last() = TokenIdBitShiftLeftPipe;
+ t.state = TokenizeState_start;
+ continue;
+ }
+ break;
case TokenizeState_angle_bracket_right:
switch (c) {
case 0:
@@ -1437,6 +1514,8 @@ const char * token_name(TokenId id) {
case TokenIdBitOrEq: return "|=";
case TokenIdBitShiftLeft: return "<<";
case TokenIdBitShiftLeftEq: return "<<=";
+ case TokenIdBitShiftLeftPipe: return "<<|";
+ case TokenIdBitShiftLeftPipeEq: return "<<|=";
case TokenIdBitShiftRight: return ">>";
case TokenIdBitShiftRightEq: return ">>=";
case TokenIdBitXorEq: return "^=";
@@ -1521,12 +1600,16 @@ const char * token_name(TokenId id) {
case TokenIdMinusEq: return "-=";
case TokenIdMinusPercent: return "-%";
case TokenIdMinusPercentEq: return "-%=";
+ case TokenIdMinusPipe: return "-|";
+ case TokenIdMinusPipeEq: return "-|=";
case TokenIdModEq: return "%=";
case TokenIdPercent: return "%";
case TokenIdPlus: return "+";
case TokenIdPlusEq: return "+=";
case TokenIdPlusPercent: return "+%";
case TokenIdPlusPercentEq: return "+%=";
+ case TokenIdPlusPipe: return "+|";
+ case TokenIdPlusPipeEq: return "+|=";
case TokenIdPlusPlus: return "++";
case TokenIdRBrace: return "}";
case TokenIdRBracket: return "]";
@@ -1542,6 +1625,8 @@ const char * token_name(TokenId id) {
case TokenIdTimesEq: return "*=";
case TokenIdTimesPercent: return "*%";
case TokenIdTimesPercentEq: return "*%=";
+ case TokenIdTimesPipe: return "*|";
+ case TokenIdTimesPipeEq: return "*|=";
case TokenIdBuiltin: return "Builtin";
case TokenIdCount:
zig_unreachable();
diff --git a/src/stage1/tokenizer.hpp b/src/stage1/tokenizer.hpp
index 0e196597ebc1..56605c1764e6 100644
--- a/src/stage1/tokenizer.hpp
+++ b/src/stage1/tokenizer.hpp
@@ -23,6 +23,8 @@ enum TokenId : uint8_t {
TokenIdBitOrEq,
TokenIdBitShiftLeft,
TokenIdBitShiftLeftEq,
+ TokenIdBitShiftLeftPipe,
+ TokenIdBitShiftLeftPipeEq,
TokenIdBitShiftRight,
TokenIdBitShiftRightEq,
TokenIdBitXorEq,
@@ -108,12 +110,16 @@ enum TokenId : uint8_t {
TokenIdMinusEq,
TokenIdMinusPercent,
TokenIdMinusPercentEq,
+ TokenIdMinusPipe,
+ TokenIdMinusPipeEq,
TokenIdModEq,
TokenIdPercent,
TokenIdPlus,
TokenIdPlusEq,
TokenIdPlusPercent,
TokenIdPlusPercentEq,
+ TokenIdPlusPipe,
+ TokenIdPlusPipeEq,
TokenIdPlusPlus,
TokenIdRBrace,
TokenIdRBracket,
@@ -129,6 +135,8 @@ enum TokenId : uint8_t {
TokenIdTimesEq,
TokenIdTimesPercent,
TokenIdTimesPercentEq,
+ TokenIdTimesPipe,
+ TokenIdTimesPipeEq,
TokenIdCount,
};
diff --git a/test/behavior/saturating_arithmetic.zig b/test/behavior/saturating_arithmetic.zig
index 553e9ff21acd..7a28ed182d8a 100644
--- a/test/behavior/saturating_arithmetic.zig
+++ b/test/behavior/saturating_arithmetic.zig
@@ -11,13 +11,34 @@ fn testSaturatingOp(comptime op: Op, comptime T: type, test_data: [3]T) !void {
const a = test_data[0];
const b = test_data[1];
const expected = test_data[2];
- const actual = switch (op) {
- .add => @addWithSaturation(a, b),
- .sub => @subWithSaturation(a, b),
- .mul => @mulWithSaturation(a, b),
- .shl => @shlWithSaturation(a, b),
- };
- try expectEqual(expected, actual);
+ {
+ const actual = switch (op) {
+ .add => @addWithSaturation(a, b),
+ .sub => @subWithSaturation(a, b),
+ .mul => @mulWithSaturation(a, b),
+ .shl => @shlWithSaturation(a, b),
+ };
+ try expectEqual(expected, actual);
+ }
+ {
+ const actual = switch (op) {
+ .add => a +| b,
+ .sub => a -| b,
+ .mul => a *| b,
+ .shl => a <<| b,
+ };
+ try expectEqual(expected, actual);
+ }
+ {
+ var actual = a;
+ switch (op) {
+ .add => actual +|= b,
+ .sub => actual -|= b,
+ .mul => actual *|= b,
+ .shl => actual <<|= b,
+ }
+ try expectEqual(expected, actual);
+ }
}
test "@addWithSaturation" {
From b9a95f2dd94e6175322d3388c3936eb600ec90ea Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Wed, 8 Sep 2021 15:19:03 -0700
Subject: [PATCH 02/17] sat-arithmetic: add c backend support
- modify AstGen binOpExt()/assignBinOpExt() to accept generic extended payload T
- rework Sema zirSatArithmetic() to use existing sema.analyzeArithmetic() by adding an `opt_extended` parameter.
- add airSatOp() to codegen/c.zig
- add saturating functions to src/link/C/zig.h
---
src/AstGen.zig | 62 +++++++++++-----------
src/Sema.zig | 25 ++++++---
src/codegen/c.zig | 120 +++++++++++++++++++++++++++++++++++++++++--
src/codegen/llvm.zig | 6 +--
src/link/C/zig.h | 93 +++++++++++++++++++++++++++++++++
5 files changed, 262 insertions(+), 44 deletions(-)
diff --git a/src/AstGen.zig b/src/AstGen.zig
index b3af3eb86b81..25452cb38644 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -535,7 +535,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_bit_shift_left_sat => {
- try assignBinOpExt(gz, scope, node, .shl_with_saturation);
+ try assignBinOpExt(gz, scope, node, .shl_with_saturation, Zir.Inst.SaturatingArithmetic);
return rvalue(gz, rl, .void_value, node);
},
.assign_bit_shift_right => {
@@ -568,7 +568,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_sub_sat => {
- try assignBinOpExt(gz, scope, node, .sub_with_saturation);
+ try assignBinOpExt(gz, scope, node, .sub_with_saturation, Zir.Inst.SaturatingArithmetic);
return rvalue(gz, rl, .void_value, node);
},
.assign_mod => {
@@ -584,7 +584,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_add_sat => {
- try assignBinOpExt(gz, scope, node, .add_with_saturation);
+ try assignBinOpExt(gz, scope, node, .add_with_saturation, Zir.Inst.SaturatingArithmetic);
return rvalue(gz, rl, .void_value, node);
},
.assign_mul => {
@@ -596,24 +596,24 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_mul_sat => {
- try assignBinOpExt(gz, scope, node, .mul_with_saturation);
+ try assignBinOpExt(gz, scope, node, .mul_with_saturation, Zir.Inst.SaturatingArithmetic);
return rvalue(gz, rl, .void_value, node);
},
// zig fmt: off
.bit_shift_left => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl),
- .bit_shift_left_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation),
+ .bit_shift_left_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation, Zir.Inst.SaturatingArithmetic),
.bit_shift_right => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr),
.add => return simpleBinOp(gz, scope, rl, node, .add),
.add_wrap => return simpleBinOp(gz, scope, rl, node, .addwrap),
- .add_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation),
+ .add_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation, Zir.Inst.SaturatingArithmetic),
.sub => return simpleBinOp(gz, scope, rl, node, .sub),
.sub_wrap => return simpleBinOp(gz, scope, rl, node, .subwrap),
- .sub_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation),
+ .sub_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation, Zir.Inst.SaturatingArithmetic),
.mul => return simpleBinOp(gz, scope, rl, node, .mul),
.mul_wrap => return simpleBinOp(gz, scope, rl, node, .mulwrap),
- .mul_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation),
+ .mul_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation, Zir.Inst.SaturatingArithmetic),
.div => return simpleBinOp(gz, scope, rl, node, .div),
.mod => return simpleBinOp(gz, scope, rl, node, .mod_rem),
.bit_and => {
@@ -2713,6 +2713,28 @@ fn assignOp(
_ = try gz.addBin(.store, lhs_ptr, result);
}
+// TODO: is there an existing way to do this?
+// TODO: likely rename this to reflect result_loc == .none or add more params to make it more general
+fn binOpExt(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ infix_node: Ast.Node.Index,
+ lhs_node: Ast.Node.Index,
+ rhs_node: Ast.Node.Index,
+ tag: Zir.Inst.Extended,
+ comptime T: type,
+) InnerError!Zir.Inst.Ref {
+ const lhs = try expr(gz, scope, .none, lhs_node);
+ const rhs = try expr(gz, scope, .none, rhs_node);
+ const result = try gz.addExtendedPayload(tag, T{
+ .node = gz.nodeIndexToRelative(infix_node),
+ .lhs = lhs,
+ .rhs = rhs,
+ });
+ return rvalue(gz, rl, result, infix_node);
+}
+
// TODO: is there an existing method to accomplish this?
// TODO: likely rename this to indicate rhs type coercion or add more params to make it more general
fn assignBinOpExt(
@@ -2720,8 +2742,8 @@ fn assignBinOpExt(
scope: *Scope,
infix_node: Ast.Node.Index,
op_inst_tag: Zir.Inst.Extended,
+ comptime T: type,
) InnerError!void {
- try emitDbgNode(gz, infix_node);
const astgen = gz.astgen;
const tree = astgen.tree;
const node_datas = tree.nodes.items(.data);
@@ -2730,7 +2752,7 @@ fn assignBinOpExt(
const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node);
const rhs = try expr(gz, scope, .{ .coerced_ty = lhs_type }, node_datas[infix_node].rhs);
- const result = try gz.addExtendedPayload(op_inst_tag, Zir.Inst.BinNode{
+ const result = try gz.addExtendedPayload(op_inst_tag, T{
.node = gz.nodeIndexToRelative(infix_node),
.lhs = lhs,
.rhs = rhs,
@@ -7903,26 +7925,6 @@ fn shiftOp(
return rvalue(gz, rl, result, node);
}
-// TODO: is there an existing way to do this?
-// TODO: likely rename this to reflect result_loc == .none or add more params to make it more general
-fn binOpExt(
- gz: *GenZir,
- scope: *Scope,
- rl: ResultLoc,
- node: Ast.Node.Index,
- lhs_node: Ast.Node.Index,
- rhs_node: Ast.Node.Index,
- tag: Zir.Inst.Extended,
-) InnerError!Zir.Inst.Ref {
- const lhs = try expr(gz, scope, .none, lhs_node);
- const rhs = try expr(gz, scope, .none, rhs_node);
- const result = try gz.addExtendedPayload(tag, Zir.Inst.Bin{
- .lhs = lhs,
- .rhs = rhs,
- });
- return rvalue(gz, rl, result, node);
-}
-
fn cImport(
gz: *GenZir,
scope: *Scope,
diff --git a/src/Sema.zig b/src/Sema.zig
index de94a8c6b892..a41d330285e7 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -694,10 +694,11 @@ fn zirExtended(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
.c_define => return sema.zirCDefine( block, extended),
.wasm_memory_size => return sema.zirWasmMemorySize( block, extended),
.wasm_memory_grow => return sema.zirWasmMemoryGrow( block, extended),
- .add_with_saturation=> return sema.zirSatArithmetic( block, extended),
- .sub_with_saturation=> return sema.zirSatArithmetic( block, extended),
- .mul_with_saturation=> return sema.zirSatArithmetic( block, extended),
- .shl_with_saturation=> return sema.zirSatArithmetic( block, extended),
+ .add_with_saturation,
+ .sub_with_saturation,
+ .mul_with_saturation,
+ .shl_with_saturation,
+ => return sema.zirSatArithmetic( block, extended),
// zig fmt: on
}
}
@@ -6163,7 +6164,7 @@ fn zirNegate(
const lhs = sema.resolveInst(.zero);
const rhs = sema.resolveInst(inst_data.operand);
- return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src);
+ return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src, null);
}
fn zirArithmetic(
@@ -6183,7 +6184,7 @@ fn zirArithmetic(
const lhs = sema.resolveInst(extra.lhs);
const rhs = sema.resolveInst(extra.rhs);
- return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src);
+ return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src, null);
}
fn zirOverflowArithmetic(
@@ -6209,10 +6210,17 @@ fn zirSatArithmetic(
defer tracy.end();
const extra = sema.code.extraData(Zir.Inst.SaturatingArithmetic, extended.operand).data;
- const src: LazySrcLoc = .{ .node_offset = extra.node };
- return sema.mod.fail(&block.base, src, "TODO implement Sema.zirSatArithmetic", .{});
+ sema.src = .{ .node_offset_bin_op = extra.node };
+ const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = extra.node };
+ const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = extra.node };
+ const lhs = sema.resolveInst(extra.lhs);
+ const rhs = sema.resolveInst(extra.rhs);
+
+ return sema.analyzeArithmetic(block, .extended, lhs, rhs, sema.src, lhs_src, rhs_src, extended);
}
+// TODO: audit - not sure if its a good idea to reuse this, adding `opt_extended` param
+// FIXME: somehow, rhs of <<| is required to be Log2T. this should accept T
fn analyzeArithmetic(
sema: *Sema,
block: *Scope.Block,
@@ -6223,6 +6231,7 @@ fn analyzeArithmetic(
src: LazySrcLoc,
lhs_src: LazySrcLoc,
rhs_src: LazySrcLoc,
+ opt_extended: ?Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index dce0c10b4cf8..9ded6fe0e832 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -885,17 +885,17 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
// that wrapping is UB.
.add, .ptr_add => try airBinOp( f, inst, " + "),
.addwrap => try airWrapOp(f, inst, " + ", "addw_"),
- .addsat => return o.dg.fail("TODO: C backend: implement codegen for addsat", .{}),
+ .addsat => return f.fail("TODO: C backend: implement codegen for addsat", .{}),
// TODO use a different strategy for sub that communicates to the optimizer
// that wrapping is UB.
.sub, .ptr_sub => try airBinOp( f, inst, " - "),
.subwrap => try airWrapOp(f, inst, " - ", "subw_"),
- .subsat => return o.dg.fail("TODO: C backend: implement codegen for subsat", .{}),
+ .subsat => return f.fail("TODO: C backend: implement codegen for subsat", .{}),
// TODO use a different strategy for mul that communicates to the optimizer
// that wrapping is UB.
.mul => try airBinOp( f, inst, " * "),
.mulwrap => try airWrapOp(f, inst, " * ", "mulw_"),
- .mulsat => return o.dg.fail("TODO: C backend: implement codegen for mulsat", .{}),
+ .mulsat => return f.fail("TODO: C backend: implement codegen for mulsat", .{}),
// TODO use a different strategy for div that communicates to the optimizer
// that wrapping is UB.
.div => try airBinOp( f, inst, " / "),
@@ -919,6 +919,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.shr => try airBinOp(f, inst, " >> "),
.shl => try airBinOp(f, inst, " << "),
+ .shl_sat => return f.fail("TODO: C backend: implement codegen for mulsat", .{}),
+
.not => try airNot( f, inst),
@@ -1312,6 +1314,118 @@ fn airWrapOp(
return ret;
}
+fn airSatOp(
+ o: *Object,
+ inst: Air.Inst.Index,
+ str_op: [*:0]const u8,
+ fn_op: [*:0]const u8,
+) !CValue {
+ if (o.liveness.isUnused(inst))
+ return CValue.none;
+
+ const bin_op = o.air.instructions.items(.data)[inst].bin_op;
+ const inst_ty = o.air.typeOfIndex(inst);
+ const int_info = inst_ty.intInfo(o.dg.module.getTarget());
+ const bits = int_info.bits;
+
+ // if it's an unsigned int with non-arbitrary bit size then we can just add
+ const ok_bits = switch (bits) {
+ 8, 16, 32, 64, 128 => true,
+ else => false,
+ };
+
+ if (bits > 64) {
+ return f.fail("TODO: C backend: airSatOp for large integers", .{});
+ }
+
+ var min_buf: [80]u8 = undefined;
+ const min = switch (int_info.signedness) {
+ .unsigned => "0",
+ else => switch (inst_ty.tag()) {
+ .c_short => "SHRT_MIN",
+ .c_int => "INT_MIN",
+ .c_long => "LONG_MIN",
+ .c_longlong => "LLONG_MIN",
+ .isize => "INTPTR_MIN",
+ else => blk: {
+ const val = -1 * std.math.pow(i65, 2, @intCast(i65, bits - 1));
+ break :blk std.fmt.bufPrint(&min_buf, "{d}", .{val}) catch |err| switch (err) {
+ error.NoSpaceLeft => unreachable,
+ else => |e| return e,
+ };
+ },
+ },
+ };
+
+ var max_buf: [80]u8 = undefined;
+ const max = switch (inst_ty.tag()) {
+ .c_short => "SHRT_MAX",
+ .c_ushort => "USHRT_MAX",
+ .c_int => "INT_MAX",
+ .c_uint => "UINT_MAX",
+ .c_long => "LONG_MAX",
+ .c_ulong => "ULONG_MAX",
+ .c_longlong => "LLONG_MAX",
+ .c_ulonglong => "ULLONG_MAX",
+ .isize => "INTPTR_MAX",
+ .usize => "UINTPTR_MAX",
+ else => blk: {
+ const pow_bits = switch (int_info.signedness) {
+ .signed => bits - 1,
+ .unsigned => bits,
+ };
+ const val = std.math.pow(u65, 2, pow_bits) - 1;
+ break :blk std.fmt.bufPrint(&max_buf, "{}", .{val}) catch |err| switch (err) {
+ error.NoSpaceLeft => unreachable,
+ else => |e| return e,
+ };
+ },
+ };
+
+ const lhs = try o.resolveInst(bin_op.lhs);
+ const rhs = try o.resolveInst(bin_op.rhs);
+ const w = o.writer();
+
+ const ret = try o.allocLocal(inst_ty, .Mut);
+ try w.print(" = zig_{s}", .{fn_op});
+
+ switch (inst_ty.tag()) {
+ .isize => try w.writeAll("isize"),
+ .c_short => try w.writeAll("short"),
+ .c_int => try w.writeAll("int"),
+ .c_long => try w.writeAll("long"),
+ .c_longlong => try w.writeAll("longlong"),
+ else => {
+ const prefix_byte: u8 = switch (int_info.signedness) {
+ .signed => 'i',
+ .unsigned => 'u',
+ };
+ for ([_]u8{ 8, 16, 32, 64 }) |nbits| {
+ if (bits <= nbits) {
+ try w.print("{c}{d}", .{ prefix_byte, nbits });
+ break;
+ }
+ } else {
+ unreachable;
+ }
+ },
+ }
+
+ try w.writeByte('(');
+ try o.writeCValue(w, lhs);
+ try w.writeAll(", ");
+ try o.writeCValue(w, rhs);
+
+ if (int_info.signedness == .signed) {
+ try w.print(", {s}", .{min});
+ }
+
+ try w.print(", {s});", .{max});
+ try o.indent_writer.insertNewline();
+
+ return ret;
+}
+
fn airNot(f: *Function, inst: Air.Inst.Index) !CValue {
if (f.liveness.isUnused(inst))
return CValue.none;
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index c21985d1c5c3..733984a9d627 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -2038,7 +2038,7 @@ pub const FuncGen = struct {
const rhs = try self.resolveInst(bin_op.rhs);
const inst_ty = self.air.typeOfIndex(inst);
- if (inst_ty.isFloat()) return self.builder.buildFAdd(lhs, rhs, "");
+ if (inst_ty.isAnyFloat()) return self.builder.buildFAdd(lhs, rhs, "");
if (ty == .wrapping)
return self.builder.buildAdd(lhs, rhs, "")
else if (ty == .saturated) {
@@ -2060,7 +2060,7 @@ pub const FuncGen = struct {
const rhs = try self.resolveInst(bin_op.rhs);
const inst_ty = self.air.typeOfIndex(inst);
- if (inst_ty.isFloat()) return self.builder.buildFSub(lhs, rhs, "");
+ if (inst_ty.isAnyFloat()) return self.builder.buildFSub(lhs, rhs, "");
if (ty == .wrapping)
return self.builder.buildSub(lhs, rhs, "")
else if (ty == .saturated) {
@@ -2082,7 +2082,7 @@ pub const FuncGen = struct {
const rhs = try self.resolveInst(bin_op.rhs);
const inst_ty = self.air.typeOfIndex(inst);
- if (inst_ty.isFloat()) return self.builder.buildFMul(lhs, rhs, "");
+ if (inst_ty.isAnyFloat()) return self.builder.buildFMul(lhs, rhs, "");
if (ty == .wrapping)
return self.builder.buildMul(lhs, rhs, "")
else if (ty == .saturated) {
diff --git a/src/link/C/zig.h b/src/link/C/zig.h
index b34068d1f247..84b1c3dac612 100644
--- a/src/link/C/zig.h
+++ b/src/link/C/zig.h
@@ -356,3 +356,96 @@ static inline long long zig_subw_longlong(long long lhs, long long rhs, long lon
return (long long)(((unsigned long long)lhs) - ((unsigned long long)rhs));
}
+/*
+ * Saturating aritmetic operations: add, sub, mul, shl
+ */
+#define zig_add_sat_u(ZT, T) static inline T zig_adds_##ZT(T x, T y, T max) { \
+ return (x > max - y) ? max : x + y; \
+}
+
+#define zig_add_sat_s(ZT, T, T2) static inline T zig_adds_##ZT(T2 x, T2 y, T2 min, T2 max) { \
+ T2 res = x + y; \
+ return (res < min) ? min : (res > max) ? max : res; \
+}
+
+zig_add_sat_u( u8, uint8_t)
+zig_add_sat_s( i8, int8_t, int16_t)
+zig_add_sat_u(u16, uint16_t)
+zig_add_sat_s(i16, int16_t, int32_t)
+zig_add_sat_u(u32, uint32_t)
+zig_add_sat_s(i32, int32_t, int64_t)
+zig_add_sat_u(u64, uint64_t)
+zig_add_sat_s(i64, int64_t, int128_t)
+zig_add_sat_s(isize, intptr_t, int128_t)
+zig_add_sat_s(short, short, int)
+zig_add_sat_s(int, int, long)
+zig_add_sat_s(long, long, long long)
+
+#define zig_sub_sat_u(ZT, T) static inline T zig_subs_##ZT(T x, T y, T max) { \
+ return (x > max + y) ? max : x - y; \
+}
+
+#define zig_sub_sat_s(ZT, T, T2) static inline T zig_subs_##ZT(T2 x, T2 y, T2 min, T2 max) { \
+ T2 res = x - y; \
+ return (res < min) ? min : (res > max) ? max : res; \
+}
+
+zig_sub_sat_u( u8, uint8_t)
+zig_sub_sat_s( i8, int8_t, int16_t)
+zig_sub_sat_u(u16, uint16_t)
+zig_sub_sat_s(i16, int16_t, int32_t)
+zig_sub_sat_u(u32, uint32_t)
+zig_sub_sat_s(i32, int32_t, int64_t)
+zig_sub_sat_u(u64, uint64_t)
+zig_sub_sat_s(i64, int64_t, int128_t)
+zig_sub_sat_s(isize, intptr_t, int128_t)
+zig_sub_sat_s(short, short, int)
+zig_sub_sat_s(int, int, long)
+zig_sub_sat_s(long, long, long long)
+
+
+#define zig_mul_sat_u(ZT, T, T2) static inline T zig_muls_##ZT(T2 x, T2 y, T2 max) { \
+ T2 res = x * y; \
+ return (res > max) ? max : res; \
+}
+
+#define zig_mul_sat_s(ZT, T, T2) static inline T zig_muls_##ZT(T2 x, T2 y, T2 min, T2 max) { \
+ T2 res = x * y; \
+ return (res < min) ? min : (res > max) ? max : res; \
+}
+
+zig_mul_sat_u(u8, uint8_t, uint16_t)
+zig_mul_sat_s(i8, int8_t, int16_t)
+zig_mul_sat_u(u16, uint16_t, uint32_t)
+zig_mul_sat_s(i16, int16_t, int32_t)
+zig_mul_sat_u(u32, uint32_t, uint64_t)
+zig_mul_sat_s(i32, int32_t, int64_t)
+zig_mul_sat_u(u64, uint64_t, uint128_t)
+zig_mul_sat_s(i64, int64_t, int128_t)
+zig_mul_sat_s(isize, intptr_t, int128_t)
+zig_mul_sat_s(short, short, int)
+zig_mul_sat_s(int, int, long)
+zig_mul_sat_s(long, long, long long)
+
+#define zig_shl_sat_u(ZT, T, bits) static inline T zig_shls_##ZT(T x, T y, T max) { \
+ T leading_zeros = __builtin_clz(x); \
+ return (leading_zeros + y > bits) ? max : x << y; \
+}
+
+#define zig_shl_sat_s(ZT, T, bits) static inline T zig_shls_##ZT(T x, T y, T min, T max) { \
+ T leading_zeros = __builtin_clz(x & ~max); \
+ return (leading_zeros + y > bits) ? max : x << y; \
+}
+
+zig_shl_sat_u(u8, uint8_t, 8)
+zig_shl_sat_s(i8, int8_t, 7)
+zig_shl_sat_u(u16, uint16_t, 16)
+zig_shl_sat_s(i16, int16_t, 15)
+zig_shl_sat_u(u32, uint32_t, 32)
+zig_shl_sat_s(i32, int32_t, 31)
+zig_shl_sat_u(u64, uint64_t, 64)
+zig_shl_sat_s(i64, int64_t, 63)
+zig_shl_sat_s(isize, intptr_t, 63)
+zig_shl_sat_s(short, short, 15)
+zig_shl_sat_s(int, int, 31)
+zig_shl_sat_s(long, long, 63)
\ No newline at end of file
From dcbc52ec85d00fbd3603c314aaaab98fb3866892 Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Wed, 8 Sep 2021 15:58:37 -0700
Subject: [PATCH 03/17] sat-arithmetic: correctly tokenize <<|, <<|=
- set state rather than result.tag in tokenizer.zig
- add test to tokenizer.zig for <<, <<|, <<|=
---
lib/std/zig/tokenizer.zig | 8 +++++++-
src/Air.zig | 6 +++---
src/stage1/tokenizer.cpp | 1 -
3 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig
index 6afe7750d35f..a7442b8b25b9 100644
--- a/lib/std/zig/tokenizer.zig
+++ b/lib/std/zig/tokenizer.zig
@@ -1007,7 +1007,7 @@ pub const Tokenizer = struct {
break;
},
'|' => {
- result.tag = .angle_bracket_angle_bracket_left_pipe;
+ state = .angle_bracket_angle_bracket_left_pipe;
},
else => {
result.tag = .angle_bracket_angle_bracket_left;
@@ -2015,6 +2015,12 @@ test "tokenizer - invalid token with unfinished escape right before eof" {
try testTokenize("'\\u", &.{.invalid});
}
+test "tokenizer - saturating" {
+ try testTokenize("<<", &.{.angle_bracket_angle_bracket_left});
+ try testTokenize("<<|", &.{.angle_bracket_angle_bracket_left_pipe});
+ try testTokenize("<<|=", &.{.angle_bracket_angle_bracket_left_pipe_equal});
+}
+
fn testTokenize(source: [:0]const u8, expected_tokens: []const Token.Tag) !void {
var tokenizer = Tokenizer.init(source);
for (expected_tokens) |expected_token_id| {
diff --git a/src/Air.zig b/src/Air.zig
index b7d3938352dc..00f223ad21fa 100644
--- a/src/Air.zig
+++ b/src/Air.zig
@@ -44,7 +44,7 @@ pub const Inst = struct {
/// is the same as both operands.
/// Uses the `bin_op` field.
addwrap,
- /// Saturating integer addition.
+ /// Saturating integer addition.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
/// Uses the `bin_op` field.
@@ -59,7 +59,7 @@ pub const Inst = struct {
/// is the same as both operands.
/// Uses the `bin_op` field.
subwrap,
- /// Saturating integer subtraction.
+ /// Saturating integer subtraction.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
/// Uses the `bin_op` field.
@@ -74,7 +74,7 @@ pub const Inst = struct {
/// is the same as both operands.
/// Uses the `bin_op` field.
mulwrap,
- /// Saturating integer multiplication.
+ /// Saturating integer multiplication.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
/// Uses the `bin_op` field.
diff --git a/src/stage1/tokenizer.cpp b/src/stage1/tokenizer.cpp
index 356019392722..47e324c933e3 100644
--- a/src/stage1/tokenizer.cpp
+++ b/src/stage1/tokenizer.cpp
@@ -995,7 +995,6 @@ void tokenize(const char *source, Tokenization *out) {
t.state = TokenizeState_start;
break;
case '|':
- // t.out->ids.last() = TokenIdBitShiftLeftPipe;
t.state = TokenizeState_angle_bracket_angle_bracket_left_pipe;
break;
default:
From bdb90a07bbf0fdedca71f5deace7087bc562b437 Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Wed, 8 Sep 2021 16:30:11 -0700
Subject: [PATCH 04/17] sat-arithmetic: fixups zig fmt / astcheck
---
src/AstGen.zig | 2 +-
src/codegen/c.zig | 11 +++++------
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/AstGen.zig b/src/AstGen.zig
index 25452cb38644..d3235ace53a8 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -2786,7 +2786,7 @@ fn assignShift(
fn assignShiftSat(
gz: *GenZir,
scope: *Scope,
- infix_node: ast.Node.Index,
+ infix_node: Ast.Node.Index,
op_inst_tag: Zir.Inst.Tag,
) InnerError!void {
try emitDbgNode(gz, infix_node);
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index 9ded6fe0e832..37e19d9e1aa2 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -1317,7 +1317,6 @@ fn airWrapOp(
fn airSatOp(
o: *Object,
inst: Air.Inst.Index,
- str_op: [*:0]const u8,
fn_op: [*:0]const u8,
) !CValue {
if (o.liveness.isUnused(inst))
@@ -1328,12 +1327,12 @@ fn airSatOp(
const int_info = inst_ty.intInfo(o.dg.module.getTarget());
const bits = int_info.bits;
- // if it's an unsigned int with non-arbitrary bit size then we can just add
- const ok_bits = switch (bits) {
- 8, 16, 32, 64, 128 => true,
- else => false,
- };
+ switch (bits) {
+ 8, 16, 32, 64, 128 => {},
+ else => return o.dg.fail("TODO: C backend: airSatOp for non power of 2 integers", .{}),
+ }
+ // if it's an unsigned int with non-arbitrary bit size then we can just add
if (bits > 64) {
return f.fail("TODO: C backend: airSatOp for large integers", .{});
}
From 6ba9f7474f6999e9239ce6459549667439945bf2 Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Wed, 8 Sep 2021 18:47:11 -0700
Subject: [PATCH 05/17] sat-arithmetic: fix docgen
---
doc/docgen.zig | 8 ++++++++
src/link/C/zig.h | 2 +-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/doc/docgen.zig b/doc/docgen.zig
index a3ae53a65e39..148a8bedb78c 100644
--- a/doc/docgen.zig
+++ b/doc/docgen.zig
@@ -1058,15 +1058,21 @@ fn tokenizeAndPrintRaw(
.plus_equal,
.plus_percent,
.plus_percent_equal,
+ .plus_pipe,
+ .plus_pipe_equal,
.minus,
.minus_equal,
.minus_percent,
.minus_percent_equal,
+ .minus_pipe,
+ .minus_pipe_equal,
.asterisk,
.asterisk_equal,
.asterisk_asterisk,
.asterisk_percent,
.asterisk_percent_equal,
+ .asterisk_pipe,
+ .asterisk_pipe_equal,
.arrow,
.colon,
.slash,
@@ -1079,6 +1085,8 @@ fn tokenizeAndPrintRaw(
.angle_bracket_left_equal,
.angle_bracket_angle_bracket_left,
.angle_bracket_angle_bracket_left_equal,
+ .angle_bracket_angle_bracket_left_pipe,
+ .angle_bracket_angle_bracket_left_pipe_equal,
.angle_bracket_right,
.angle_bracket_right_equal,
.angle_bracket_angle_bracket_right,
diff --git a/src/link/C/zig.h b/src/link/C/zig.h
index 84b1c3dac612..cb2349249001 100644
--- a/src/link/C/zig.h
+++ b/src/link/C/zig.h
@@ -448,4 +448,4 @@ zig_shl_sat_s(i64, int64_t, 63)
zig_shl_sat_s(isize, intptr_t, 63)
zig_shl_sat_s(short, short, 15)
zig_shl_sat_s(int, int, 31)
-zig_shl_sat_s(long, long, 63)
\ No newline at end of file
+zig_shl_sat_s(long, long, 63)
From 0f246257be5029e7bb73ac9a5ff356171007bc7a Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Wed, 8 Sep 2021 20:59:55 -0700
Subject: [PATCH 06/17] sat-arithmetic: update langref
---
doc/langref.html.in | 97 ++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 91 insertions(+), 6 deletions(-)
diff --git a/doc/langref.html.in b/doc/langref.html.in
index a5dfa5c9278a..b6f49dab626a 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -1244,8 +1244,9 @@ fn divide(a: i32, b: i32) i32 {
Operators such as {#syntax#}+{#endsyntax#} and {#syntax#}-{#endsyntax#} cause undefined behavior on
- integer overflow. Also available are operations such as {#syntax#}+%{#endsyntax#} and
- {#syntax#}-%{#endsyntax#} which are defined to have wrapping arithmetic on all targets.
+ integer overflow. Alternative operators are provided for wrapping and saturating arithmetic on all targets.
+ {#syntax#}+%{#endsyntax#} and {#syntax#}-%{#endsyntax#} perform wrapping arithmetic
+ while {#syntax#}+|{#endsyntax#} and {#syntax#}-|{#endsyntax#} perform saturating arithmetic.
Zig supports arbitrary bit-width integers, referenced by using
@@ -1395,6 +1396,24 @@ a +%= b{#endsyntax#}
{#syntax#}@as(u32, std.math.maxInt(u32)) +% 1 == 0{#endsyntax#}
+
+ {#syntax#}a +| b
+a +|= b{#endsyntax#} |
+
+
+ |
+ Saturating Addition.
+
+ - Invokes {#link|Peer Type Resolution#} for the operands.
+ - See also {#link|@addWithSaturation#}.
+
+ |
+
+ {#syntax#}@as(u32, std.math.maxInt(u32)) +| 1 == @as(u32, std.math.maxInt(u32)){#endsyntax#}
+ |
+
{#syntax#}a - b
a -= b{#endsyntax#} |
@@ -1434,6 +1453,24 @@ a -%= b{#endsyntax#}
{#syntax#}@as(u32, 0) -% 1 == std.math.maxInt(u32){#endsyntax#}
+
+ {#syntax#}a -| b
+a -|= b{#endsyntax#} |
+
+
+ |
+ Saturating Subtraction.
+
+ - Invokes {#link|Peer Type Resolution#} for the operands.
+ - See also {#link|@subWithSaturation#}.
+
+ |
+
+ {#syntax#}@as(u32, 0) -| 1 == 0{#endsyntax#}
+ |
+
{#syntax#}-a{#endsyntax#} |
@@ -1508,6 +1545,24 @@ a *%= b{#endsyntax#}
{#syntax#}@as(u8, 200) *% 2 == 144{#endsyntax#}
|
+
+ {#syntax#}a *| b
+a *|= b{#endsyntax#} |
+
+
+ |
+ Saturating Multiplication.
+
+ - Invokes {#link|Peer Type Resolution#} for the operands.
+ - See also {#link|@mulWithSaturation#}.
+
+ |
+
+ {#syntax#}@as(u8, 200) *| 2 == 255{#endsyntax#}
+ |
+
{#syntax#}a / b
a /= b{#endsyntax#} |
@@ -1577,6 +1632,24 @@ a <<= b{#endsyntax#}
{#syntax#}1 << 8 == 256{#endsyntax#}
+
+ {#syntax#}a <<| b
+a <<|= b{#endsyntax#} |
+
+
+ |
+ Saturating Bit Shift Left.
+
+ - See also {#link|@shlExact#}.
+ - See also {#link|@shlWithOverflow#}.
+
+ |
+
+ {#syntax#}@as(u8, 1) <<| 8 == 255{#endsyntax#}
+ |
+
{#syntax#}a >> b
a >>= b{#endsyntax#} |
@@ -1968,14 +2041,14 @@ const B = error{Two};
a!b
x{}
!x -x -%x ~x &x ?x
-* / % ** *% ||
-+ - ++ +% -%
-<< >>
+* / % ** *% *| ||
++ - ++ +% -% +| -|
+<< >> <<|
& ^ | orelse catch
== != < > <= >=
and
or
-= *= /= %= += -= <<= >>= &= ^= |={#endsyntax#}
+= *= *%= *|= /= %= += +%= +|= -= -%= -|= <<= <<|= >>= &= ^= |={#endsyntax#}
{#header_close#}
{#header_close#}
{#header_open|Arrays#}
@@ -11839,6 +11912,7 @@ AssignOp
/ PLUSEQUAL
/ MINUSEQUAL
/ LARROW2EQUAL
+ / LARROW2PIPEEQUAL
/ RARROW2EQUAL
/ AMPERSANDEQUAL
/ CARETEQUAL
@@ -11873,6 +11947,8 @@ AdditionOp
/ PLUS2
/ PLUSPERCENT
/ MINUSPERCENT
+ / PLUSPIPE
+ / MINUSPIPE
MultiplyOp
<- PIPE2
@@ -11881,6 +11957,7 @@ MultiplyOp
/ PERCENT
/ ASTERISK2
/ ASTERISKPERCENT
+ / ASTERISKPIPE
PrefixOp
<- EXCLAMATIONMARK
@@ -12044,6 +12121,8 @@ ASTERISK2 <- '**' skip
ASTERISKEQUAL <- '*=' skip
ASTERISKPERCENT <- '*%' ![=] skip
ASTERISKPERCENTEQUAL <- '*%=' skip
+ASTERISKPIPE <- '*|' ![=] skip
+ASTERISKPIPEEQUAL <- '*|=' skip
CARET <- '^' ![=] skip
CARETEQUAL <- '^=' skip
COLON <- ':' skip
@@ -12060,6 +12139,8 @@ EXCLAMATIONMARK <- '!' ![=] skip
EXCLAMATIONMARKEQUAL <- '!=' skip
LARROW <- '<' ![<=] skip
LARROW2 <- '<<' ![=] skip
+LARROW2PIPE <- '<<|' ![=] skip
+LARROW2PIPEEQUAL <- '<<|=' ![=] skip
LARROW2EQUAL <- '<<=' skip
LARROWEQUAL <- '<=' skip
LBRACE <- '{' skip
@@ -12069,6 +12150,8 @@ MINUS <- '-' ![%=>] skip
MINUSEQUAL <- '-=' skip
MINUSPERCENT <- '-%' ![=] skip
MINUSPERCENTEQUAL <- '-%=' skip
+MINUSPIPE <- '-|' ![=] skip
+MINUSPIPEEQUAL <- '-|=' skip
MINUSRARROW <- '->' skip
PERCENT <- '%' ![=] skip
PERCENTEQUAL <- '%=' skip
@@ -12080,6 +12163,8 @@ PLUS2 <- '++' skip
PLUSEQUAL <- '+=' skip
PLUSPERCENT <- '+%' ![=] skip
PLUSPERCENTEQUAL <- '+%=' skip
+PLUSPIPE <- '+|' ![=] skip
+PLUSPIPEEQUAL <- '+|=' skip
LETTERC <- 'c' skip
QUESTIONMARK <- '?' skip
RARROW <- '>' ![>=] skip
From 1d86eae5269edcac5f32d166f13ed27483f07688 Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Thu, 9 Sep 2021 13:07:59 -0700
Subject: [PATCH 07/17] sat-arithmetic: langref - remove syntax disclaimer
---
doc/langref.html.in | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/doc/langref.html.in b/doc/langref.html.in
index b6f49dab626a..5b3c26b93761 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -7241,8 +7241,7 @@ fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 {
Returns {#syntax#}a + b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
- Once Saturating arithmetic.
- is completed, the syntax {#syntax#}a +| b{#endsyntax#} will be equivalent to calling {#syntax#}@addWithSaturation(a, b){#endsyntax#}.
+ The syntax {#syntax#}a +| b{#endsyntax#} is equivalent to calling {#syntax#}@addWithSaturation(a, b){#endsyntax#}.
{#header_close#}
{#header_open|@alignCast#}
@@ -8372,8 +8371,7 @@ test "@wasmMemoryGrow" {
Returns {#syntax#}a * b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
- Once Saturating arithmetic.
- is completed, the syntax {#syntax#}a *| b{#endsyntax#} will be equivalent to calling {#syntax#}@mulWithSaturation(a, b){#endsyntax#}.
+ The syntax {#syntax#}a *| b{#endsyntax#} is equivalent to calling {#syntax#}@mulWithSaturation(a, b){#endsyntax#}.
NOTE: Currently there is a bug in the llvm.smul.fix.sat intrinsic which affects {#syntax#}@mulWithSaturation{#endsyntax#} of signed integers.
@@ -8629,8 +8627,7 @@ test "@setRuntimeSafety" {
Returns {#syntax#}a << b{#endsyntax#}. The result will be clamped between type minimum and maximum.
- Once Saturating arithmetic.
- is completed, the syntax {#syntax#}a <<| b{#endsyntax#} will be equivalent to calling {#syntax#}@shlWithSaturation(a, b){#endsyntax#}.
+ The syntax {#syntax#}a <<| b{#endsyntax#} is equivalent to calling {#syntax#}@shlWithSaturation(a, b){#endsyntax#}.
Unlike other @shl builtins, shift_amt doesn't need to be a Log2T as saturated overshifting is well defined.
@@ -8954,8 +8951,7 @@ fn doTheTest() !void {
Returns {#syntax#}a - b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
- Once Saturating arithmetic.
- is completed, the syntax {#syntax#}a -| b{#endsyntax#} will be equivalent to calling {#syntax#}@subWithSaturation(a, b){#endsyntax#}.
+ The syntax {#syntax#}a -| b{#endsyntax#} is equivalent to calling {#syntax#}@subWithSaturation(a, b){#endsyntax#}.
{#header_close#}
From 487059535242e2b94303502806feaa99d560c63b Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Thu, 9 Sep 2021 14:17:59 -0700
Subject: [PATCH 08/17] sat-arithmetic: add additional tokenizer tests
---
lib/std/zig/tokenizer.zig | 12 ++++++++++++
src/codegen/c.zig | 1 +
2 files changed, 13 insertions(+)
diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig
index a7442b8b25b9..02fa3dd381f5 100644
--- a/lib/std/zig/tokenizer.zig
+++ b/lib/std/zig/tokenizer.zig
@@ -2019,6 +2019,18 @@ test "tokenizer - saturating" {
try testTokenize("<<", &.{.angle_bracket_angle_bracket_left});
try testTokenize("<<|", &.{.angle_bracket_angle_bracket_left_pipe});
try testTokenize("<<|=", &.{.angle_bracket_angle_bracket_left_pipe_equal});
+
+ try testTokenize("*", &.{.asterisk});
+ try testTokenize("*|", &.{.asterisk_pipe});
+ try testTokenize("*|=", &.{.asterisk_pipe_equal});
+
+ try testTokenize("+", &.{.plus});
+ try testTokenize("+|", &.{.plus_pipe});
+ try testTokenize("+|=", &.{.plus_pipe_equal});
+
+ try testTokenize("-", &.{.minus});
+ try testTokenize("-|", &.{.minus_pipe});
+ try testTokenize("-|=", &.{.minus_pipe_equal});
}
fn testTokenize(source: [:0]const u8, expected_tokens: []const Token.Tag) !void {
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index 37e19d9e1aa2..6101740eea0b 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -1347,6 +1347,7 @@ fn airSatOp(
.c_longlong => "LLONG_MIN",
.isize => "INTPTR_MIN",
else => blk: {
+ // compute the type minimum based on the bitcount (bits)
const val = -1 * std.math.pow(i65, 2, @intCast(i65, bits - 1));
break :blk std.fmt.bufPrint(&min_buf, "{d}", .{val}) catch |err| switch (err) {
error.NoSpaceLeft => unreachable,
From fd8383545adc5f202e8098bd13b3bda3481ad235 Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Fri, 10 Sep 2021 15:38:49 -0700
Subject: [PATCH 09/17] sat-arithmetic: langref - use tags
---
doc/langref.html.in | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/doc/langref.html.in b/doc/langref.html.in
index 5b3c26b93761..e750797997e2 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -1397,8 +1397,8 @@ a +%= b{#endsyntax#} |
- {#syntax#}a +| b
-a +|= b{#endsyntax#} |
+ {#syntax#}a +| b
+a +|= b{#endsyntax#} |
- {#link|Integers#}
@@ -1454,8 +1454,8 @@ a -%= b{#endsyntax#}
|
- {#syntax#}a -| b
-a -|= b{#endsyntax#} |
+ {#syntax#}a -| b
+a -|= b{#endsyntax#} |
- {#link|Integers#}
@@ -1546,8 +1546,8 @@ a *%= b{#endsyntax#}
|
- {#syntax#}a *| b
-a *|= b{#endsyntax#} |
+ {#syntax#}a *| b
+a *|= b{#endsyntax#} |
- {#link|Integers#}
@@ -1633,8 +1633,8 @@ a <<= b{#endsyntax#}
|
- {#syntax#}a <<| b
-a <<|= b{#endsyntax#} |
+ {#syntax#}a <<| b
+a <<|= b{#endsyntax#} |
- {#link|Integers#}
From 68050852fac6940d04e15900f135e6fc88845f9b Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Fri, 10 Sep 2021 15:41:43 -0700
Subject: [PATCH 10/17] sat-arithmetic: minor formatting changes
---
lib/std/zig/Ast.zig | 2 +-
src/codegen/llvm.zig | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig
index 3632551d17b8..b69da459d34b 100644
--- a/lib/std/zig/Ast.zig
+++ b/lib/std/zig/Ast.zig
@@ -2577,7 +2577,7 @@ pub const Node = struct {
array_mult,
/// `lhs *% rhs`. main_token is the `*%`.
mul_wrap,
- /// `lhs *| rhs`. main_token is the `*%`.
+ /// `lhs *| rhs`. main_token is the `*|`.
mul_sat,
/// `lhs + rhs`. main_token is the `+`.
add,
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index 733984a9d627..cdd19146b5e5 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -1256,7 +1256,7 @@ pub const FuncGen = struct {
.xor => try self.airXor(inst),
.shl => try self.airShl(inst, false),
- .shl_sat => try self.airShl(inst, true),
+ .shl_sat => try self.airShl(inst, true),
.shr => try self.airShr(inst),
.cmp_eq => try self.airCmp(inst, .eq),
From cd8d8add9153b17b4579c2e8951ac3f3f42e1bcd Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Tue, 14 Sep 2021 18:26:28 -0700
Subject: [PATCH 11/17] sat-arithmetic: fix shl methods in cbe
---
src/link/C/zig.h | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/src/link/C/zig.h b/src/link/C/zig.h
index cb2349249001..5c9d750729aa 100644
--- a/src/link/C/zig.h
+++ b/src/link/C/zig.h
@@ -428,17 +428,21 @@ zig_mul_sat_s(int, int, long)
zig_mul_sat_s(long, long, long long)
#define zig_shl_sat_u(ZT, T, bits) static inline T zig_shls_##ZT(T x, T y, T max) { \
- T leading_zeros = __builtin_clz(x); \
- return (leading_zeros + y > bits) ? max : x << y; \
+ if(x == 0) return 0; \
+ T bits_set = 64 - __builtin_clzll(x); \
+ return (bits_set + y > bits) ? max : x << y; \
}
#define zig_shl_sat_s(ZT, T, bits) static inline T zig_shls_##ZT(T x, T y, T min, T max) { \
- T leading_zeros = __builtin_clz(x & ~max); \
- return (leading_zeros + y > bits) ? max : x << y; \
+ if(x == 0) return 0; \
+ T x_twos_comp = x < 0 ? -x : x; \
+ T bits_set = 64 - __builtin_clzll(x_twos_comp); \
+ T min_or_max = (x < 0) ? min : max; \
+ return (y + bits_set > bits ) ? min_or_max : x << y; \
}
-zig_shl_sat_u(u8, uint8_t, 8)
-zig_shl_sat_s(i8, int8_t, 7)
+zig_shl_sat_u(u8, uint8_t, 8)
+zig_shl_sat_s(i8, int8_t, 7)
zig_shl_sat_u(u16, uint16_t, 16)
zig_shl_sat_s(i16, int16_t, 15)
zig_shl_sat_u(u32, uint32_t, 32)
From baaec94fe427efad4fe46ee3ffde53184cbd0ae9 Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Tue, 14 Sep 2021 18:34:52 -0700
Subject: [PATCH 12/17] sat-arithmetic: create Sema.analyzeSatArithmetic
- similar to Sema.analyzeArithmetic but uses accepts Zir.Inst.Extended.InstData
- missing support for Pointer types and comptime arithmetic
---
src/AstGen.zig | 26 +++++++--------
src/Sema.zig | 90 ++++++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 96 insertions(+), 20 deletions(-)
diff --git a/src/AstGen.zig b/src/AstGen.zig
index d3235ace53a8..9dc09ecd27b8 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -535,7 +535,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_bit_shift_left_sat => {
- try assignBinOpExt(gz, scope, node, .shl_with_saturation, Zir.Inst.SaturatingArithmetic);
+ try assignOpExt(gz, scope, node, .shl_with_saturation, Zir.Inst.SaturatingArithmetic);
return rvalue(gz, rl, .void_value, node);
},
.assign_bit_shift_right => {
@@ -568,7 +568,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_sub_sat => {
- try assignBinOpExt(gz, scope, node, .sub_with_saturation, Zir.Inst.SaturatingArithmetic);
+ try assignOpExt(gz, scope, node, .sub_with_saturation, Zir.Inst.SaturatingArithmetic);
return rvalue(gz, rl, .void_value, node);
},
.assign_mod => {
@@ -584,7 +584,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_add_sat => {
- try assignBinOpExt(gz, scope, node, .add_with_saturation, Zir.Inst.SaturatingArithmetic);
+ try assignOpExt(gz, scope, node, .add_with_saturation, Zir.Inst.SaturatingArithmetic);
return rvalue(gz, rl, .void_value, node);
},
.assign_mul => {
@@ -596,26 +596,28 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_mul_sat => {
- try assignBinOpExt(gz, scope, node, .mul_with_saturation, Zir.Inst.SaturatingArithmetic);
+ try assignOpExt(gz, scope, node, .mul_with_saturation, Zir.Inst.SaturatingArithmetic);
return rvalue(gz, rl, .void_value, node);
},
// zig fmt: off
.bit_shift_left => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl),
- .bit_shift_left_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation, Zir.Inst.SaturatingArithmetic),
.bit_shift_right => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr),
.add => return simpleBinOp(gz, scope, rl, node, .add),
.add_wrap => return simpleBinOp(gz, scope, rl, node, .addwrap),
- .add_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation, Zir.Inst.SaturatingArithmetic),
.sub => return simpleBinOp(gz, scope, rl, node, .sub),
.sub_wrap => return simpleBinOp(gz, scope, rl, node, .subwrap),
- .sub_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation, Zir.Inst.SaturatingArithmetic),
.mul => return simpleBinOp(gz, scope, rl, node, .mul),
.mul_wrap => return simpleBinOp(gz, scope, rl, node, .mulwrap),
- .mul_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation, Zir.Inst.SaturatingArithmetic),
.div => return simpleBinOp(gz, scope, rl, node, .div),
.mod => return simpleBinOp(gz, scope, rl, node, .mod_rem),
+
+ .add_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation, Zir.Inst.SaturatingArithmetic),
+ .sub_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation, Zir.Inst.SaturatingArithmetic),
+ .mul_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation, Zir.Inst.SaturatingArithmetic),
+ .bit_shift_left_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation, Zir.Inst.SaturatingArithmetic),
+
.bit_and => {
const current_ampersand_token = main_tokens[node];
if (token_tags[current_ampersand_token + 1] == .ampersand) {
@@ -2713,9 +2715,7 @@ fn assignOp(
_ = try gz.addBin(.store, lhs_ptr, result);
}
-// TODO: is there an existing way to do this?
-// TODO: likely rename this to reflect result_loc == .none or add more params to make it more general
-fn binOpExt(
+fn simpleBinOpExt(
gz: *GenZir,
scope: *Scope,
rl: ResultLoc,
@@ -2735,9 +2735,7 @@ fn binOpExt(
return rvalue(gz, rl, result, infix_node);
}
-// TODO: is there an existing method to accomplish this?
-// TODO: likely rename this to indicate rhs type coercion or add more params to make it more general
-fn assignBinOpExt(
+fn assignOpExt(
gz: *GenZir,
scope: *Scope,
infix_node: Ast.Node.Index,
diff --git a/src/Sema.zig b/src/Sema.zig
index a41d330285e7..be10b6d66329 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -6164,7 +6164,7 @@ fn zirNegate(
const lhs = sema.resolveInst(.zero);
const rhs = sema.resolveInst(inst_data.operand);
- return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src, null);
+ return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src);
}
fn zirArithmetic(
@@ -6184,7 +6184,7 @@ fn zirArithmetic(
const lhs = sema.resolveInst(extra.lhs);
const rhs = sema.resolveInst(extra.rhs);
- return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src, null);
+ return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src);
}
fn zirOverflowArithmetic(
@@ -6216,11 +6216,90 @@ fn zirSatArithmetic(
const lhs = sema.resolveInst(extra.lhs);
const rhs = sema.resolveInst(extra.rhs);
- return sema.analyzeArithmetic(block, .extended, lhs, rhs, sema.src, lhs_src, rhs_src, extended);
+ return sema.analyzeSatArithmetic(block, lhs, rhs, sema.src, lhs_src, rhs_src, extended);
+}
+
+fn analyzeSatArithmetic(
+ sema: *Sema,
+ block: *Scope.Block,
+ lhs: Air.Inst.Ref,
+ rhs: Air.Inst.Ref,
+ src: LazySrcLoc,
+ lhs_src: LazySrcLoc,
+ rhs_src: LazySrcLoc,
+ extended: Zir.Inst.Extended.InstData,
+) CompileError!Air.Inst.Ref {
+ const lhs_ty = sema.typeOf(lhs);
+ const rhs_ty = sema.typeOf(rhs);
+ const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison();
+ const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison();
+ if (lhs_zig_ty_tag == .Vector and rhs_zig_ty_tag == .Vector) {
+ if (lhs_ty.arrayLen() != rhs_ty.arrayLen()) {
+ return sema.mod.fail(&block.base, src, "vector length mismatch: {d} and {d}", .{
+ lhs_ty.arrayLen(), rhs_ty.arrayLen(),
+ });
+ }
+ return sema.mod.fail(&block.base, src, "TODO implement support for vectors in zirBinOp", .{});
+ } else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) {
+ return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{
+ lhs_ty, rhs_ty,
+ });
+ }
+
+ if (lhs_zig_ty_tag == .Pointer or rhs_zig_ty_tag == .Pointer)
+ return sema.mod.fail(&block.base, src, "TODO implement support for pointers in zirSatArithmetic", .{});
+
+ const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
+ const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ .override = &[_]LazySrcLoc{ lhs_src, rhs_src } });
+ const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
+ const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
+
+ const scalar_type = if (resolved_type.zigTypeTag() == .Vector)
+ resolved_type.elemType()
+ else
+ resolved_type;
+
+ const scalar_tag = scalar_type.zigTypeTag();
+
+ const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
+
+ if (!is_int)
+ return sema.mod.fail(&block.base, src, "invalid operands to binary expression: '{s}' and '{s}'", .{
+ @tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag),
+ });
+
+ if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| {
+ if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| {
+ if (lhs_val.isUndef() or rhs_val.isUndef()) {
+ return sema.addConstUndef(resolved_type);
+ }
+ // incase rhs is 0, simply return lhs without doing any calculations
+ if (rhs_val.compareWithZero(.eq)) {
+ switch (extended.opcode) {
+ .add_with_saturation, .sub_with_saturation => return sema.addConstant(scalar_type, lhs_val),
+ else => {},
+ }
+ }
+
+ return sema.mod.fail(&block.base, src, "TODO implement comptime saturating arithmetic for operand '{s}'", .{@tagName(extended.opcode)});
+ } else {
+ try sema.requireRuntimeBlock(block, rhs_src);
+ }
+ } else {
+ try sema.requireRuntimeBlock(block, lhs_src);
+ }
+
+ const air_tag: Air.Inst.Tag = switch (extended.opcode) {
+ .add_with_saturation => .addsat,
+ .sub_with_saturation => .subsat,
+ .mul_with_saturation => .mulsat,
+ .shl_with_saturation => .shl_sat,
+ else => return sema.mod.fail(&block.base, src, "TODO implement arithmetic for extended opcode '{s}'", .{@tagName(extended.opcode)}),
+ };
+
+ return block.addBinOp(air_tag, casted_lhs, casted_rhs);
}
-// TODO: audit - not sure if its a good idea to reuse this, adding `opt_extended` param
-// FIXME: somehow, rhs of <<| is required to be Log2T. this should accept T
fn analyzeArithmetic(
sema: *Sema,
block: *Scope.Block,
@@ -6231,7 +6310,6 @@ fn analyzeArithmetic(
src: LazySrcLoc,
lhs_src: LazySrcLoc,
rhs_src: LazySrcLoc,
- opt_extended: ?Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
From 38703dc9c2dccc43c77ec8dcfe0df936cced9d7a Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Tue, 14 Sep 2021 18:40:28 -0700
Subject: [PATCH 13/17] sat-arithmetic: don't test builtins in behavior tests
- not necessary as we are testing the operators
---
test/behavior/saturating_arithmetic.zig | 9 ---------
1 file changed, 9 deletions(-)
diff --git a/test/behavior/saturating_arithmetic.zig b/test/behavior/saturating_arithmetic.zig
index 7a28ed182d8a..5d7a229c3c9a 100644
--- a/test/behavior/saturating_arithmetic.zig
+++ b/test/behavior/saturating_arithmetic.zig
@@ -11,15 +11,6 @@ fn testSaturatingOp(comptime op: Op, comptime T: type, test_data: [3]T) !void {
const a = test_data[0];
const b = test_data[1];
const expected = test_data[2];
- {
- const actual = switch (op) {
- .add => @addWithSaturation(a, b),
- .sub => @subWithSaturation(a, b),
- .mul => @mulWithSaturation(a, b),
- .shl => @shlWithSaturation(a, b),
- };
- try expectEqual(expected, actual);
- }
{
const actual = switch (op) {
.add => a +| b,
From 51673bcb315d837ee0fe80dc50571c2c07d80a2c Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Wed, 22 Sep 2021 00:14:08 -0700
Subject: [PATCH 14/17] get build passing again after rebase
---
src/codegen/c.zig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index 6101740eea0b..1afa81b70f0c 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -1334,7 +1334,7 @@ fn airSatOp(
// if it's an unsigned int with non-arbitrary bit size then we can just add
if (bits > 64) {
- return f.fail("TODO: C backend: airSatOp for large integers", .{});
+ return o.dg.fail("TODO: C backend: airSatOp for large integers", .{});
}
var min_buf: [80]u8 = undefined;
From 6cb7906394281370173cd1c1fc79ad93562005a0 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Tue, 28 Sep 2021 16:55:42 -0700
Subject: [PATCH 15/17] add missing zig fmt test for saturating arithmetic
---
lib/std/zig/parser_test.zig | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig
index 2f79cc175cc6..57f081decb78 100644
--- a/lib/std/zig/parser_test.zig
+++ b/lib/std/zig/parser_test.zig
@@ -4739,6 +4739,26 @@ test "zig fmt: assignment with inline for and inline while" {
);
}
+test "zig fmt: saturating arithmetic" {
+ try testCanonical(
+ \\test {
+ \\ const actual = switch (op) {
+ \\ .add => a +| b,
+ \\ .sub => a -| b,
+ \\ .mul => a *| b,
+ \\ .shl => a <<| b,
+ \\ };
+ \\ switch (op) {
+ \\ .add => actual +|= b,
+ \\ .sub => actual -|= b,
+ \\ .mul => actual *|= b,
+ \\ .shl => actual <<|= b,
+ \\ }
+ \\}
+ \\
+ );
+}
+
test "zig fmt: insert trailing comma if there are comments between switch values" {
try testTransform(
\\const a = switch (b) {
From 71da169c67ad544bd1d4dfc4bfff9fe302e8284d Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Tue, 28 Sep 2021 16:55:57 -0700
Subject: [PATCH 16/17] AstGen: delete dead code
---
src/AstGen.zig | 23 -----------------------
1 file changed, 23 deletions(-)
diff --git a/src/AstGen.zig b/src/AstGen.zig
index 9dc09ecd27b8..92087a7719f8 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -2781,29 +2781,6 @@ fn assignShift(
_ = try gz.addBin(.store, lhs_ptr, result);
}
-fn assignShiftSat(
- gz: *GenZir,
- scope: *Scope,
- infix_node: Ast.Node.Index,
- op_inst_tag: Zir.Inst.Tag,
-) InnerError!void {
- try emitDbgNode(gz, infix_node);
- const astgen = gz.astgen;
- const tree = astgen.tree;
- const node_datas = tree.nodes.items(.data);
-
- const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs);
- const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
- const rhs_type = try gz.addUnNode(.typeof, lhs, infix_node);
- const rhs = try expr(gz, scope, .{ .ty = rhs_type }, node_datas[infix_node].rhs);
-
- const result = try gz.addPlNode(op_inst_tag, infix_node, Zir.Inst.Bin{
- .lhs = lhs,
- .rhs = rhs,
- });
- _ = try gz.addBin(.store, lhs_ptr, result);
-}
-
fn boolNot(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerError!Zir.Inst.Ref {
const astgen = gz.astgen;
const tree = astgen.tree;
From 54675824449d16029fdf6a1873e78cb8f2147f60 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Tue, 28 Sep 2021 18:55:43 -0700
Subject: [PATCH 17/17] 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
---
doc/langref.html.in | 63 +------
lib/std/zig/Ast.zig | 36 ++--
lib/std/zig/parse.zig | 12 +-
lib/std/zig/render.zig | 20 +-
src/Air.zig | 18 +-
src/AstGen.zig | 153 ++++++----------
src/BuiltinFn.zig | 32 ----
src/Liveness.zig | 7 +-
src/Sema.zig | 234 ++++++++++++------------
src/Zir.zig | 143 +++++++--------
src/codegen.zig | 56 ++++--
src/codegen/c.zig | 76 ++++----
src/codegen/llvm.zig | 188 +++++++++++++------
src/codegen/llvm/bindings.zig | 6 +
src/link/C/zig.h | 11 +-
src/print_air.zig | 7 +-
src/print_zir.zig | 22 +--
src/stage1/all_types.hpp | 4 -
src/stage1/astgen.cpp | 60 ------
src/stage1/codegen.cpp | 4 -
src/translate_c/ast.zig | 8 +-
src/value.zig | 87 +++++++++
test/behavior/saturating_arithmetic.zig | 33 +---
23 files changed, 616 insertions(+), 664 deletions(-)
diff --git a/doc/langref.html.in b/doc/langref.html.in
index e750797997e2..2e69e3709720 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -1407,7 +1407,6 @@ a +|= b{#endsyntax#}
Saturating Addition.
- Invokes {#link|Peer Type Resolution#} for the operands.
- - See also {#link|@addWithSaturation#}.
|
@@ -1464,7 +1463,6 @@ a -|= b{#endsyntax#}
| Saturating Subtraction.
- Invokes {#link|Peer Type Resolution#} for the operands.
- - See also {#link|@subWithSaturation#}.
|
@@ -1556,7 +1554,6 @@ a *|= b{#endsyntax#}
| Saturating Multiplication.
- Invokes {#link|Peer Type Resolution#} for the operands.
- - See also {#link|@mulWithSaturation#}.
|
@@ -7235,15 +7232,6 @@ fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 {
If no overflow or underflow occurs, returns {#syntax#}false{#endsyntax#}.
{#header_close#}
- {#header_open|@addWithSaturation#}
- {#syntax#}@addWithSaturation(a: T, b: T) T{#endsyntax#}
-
- Returns {#syntax#}a + b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
-
-
- The syntax {#syntax#}a +| b{#endsyntax#} is equivalent to calling {#syntax#}@addWithSaturation(a, b){#endsyntax#}.
-
- {#header_close#}
{#header_open|@alignCast#}
{#syntax#}@alignCast(comptime alignment: u29, ptr: anytype) anytype{#endsyntax#}
@@ -8365,21 +8353,6 @@ test "@wasmMemoryGrow" {
{#header_close#}
- {#header_open|@mulWithSaturation#}
- {#syntax#}@mulWithSaturation(a: T, b: T) T{#endsyntax#}
-
- Returns {#syntax#}a * b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
-
-
- The syntax {#syntax#}a *| b{#endsyntax#} is equivalent to calling {#syntax#}@mulWithSaturation(a, b){#endsyntax#}.
-
-
- NOTE: Currently there is a bug in the llvm.smul.fix.sat intrinsic which affects {#syntax#}@mulWithSaturation{#endsyntax#} of signed integers.
- This may result in an incorrect sign bit when there is overflow. This will be fixed in zig's 0.9.0 release.
- Check this issue for more information.
-
- {#header_close#}
-
{#header_open|@panic#}
{#syntax#}@panic(message: []const u8) noreturn{#endsyntax#}
@@ -8597,14 +8570,16 @@ test "@setRuntimeSafety" {
{#header_open|@shlExact#}
{#syntax#}@shlExact(value: T, shift_amt: Log2T) T{#endsyntax#}
- Performs the left shift operation ({#syntax#}<<{#endsyntax#}). Caller guarantees
- that the shift will not shift any 1 bits out.
+ Performs the left shift operation ({#syntax#}<<{#endsyntax#}).
+ For unsigned integers, the result is {#link|undefined#} if any 1 bits
+ are shifted out. For signed integers, the result is {#link|undefined#} if
+ any bits that disagree with the resultant sign bit are shifted out.
The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits.
This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior.
- {#see_also|@shrExact|@shlWithOverflow|@shlWithSaturation#}
+ {#see_also|@shrExact|@shlWithOverflow#}
{#header_close#}
{#header_open|@shlWithOverflow#}
@@ -8618,23 +8593,9 @@ test "@setRuntimeSafety" {
The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits.
This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior.
- {#see_also|@shlExact|@shrExact|@shlWithSaturation#}
+ {#see_also|@shlExact|@shrExact#}
{#header_close#}
- {#header_open|@shlWithSaturation#}
- {#syntax#}@shlWithSaturation(a: T, shift_amt: T) T{#endsyntax#}
-
- Returns {#syntax#}a << b{#endsyntax#}. The result will be clamped between type minimum and maximum.
-
-
- The syntax {#syntax#}a <<| b{#endsyntax#} is equivalent to calling {#syntax#}@shlWithSaturation(a, b){#endsyntax#}.
-
-
- Unlike other @shl builtins, shift_amt doesn't need to be a Log2T as saturated overshifting is well defined.
-
- {#see_also|@shlExact|@shrExact|@shlWithOverflow#}
- {#header_close#}
-
{#header_open|@shrExact#}
{#syntax#}@shrExact(value: T, shift_amt: Log2T) T{#endsyntax#}
@@ -8645,7 +8606,7 @@ test "@setRuntimeSafety" {
The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits.
This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior.
- {#see_also|@shlExact|@shlWithOverflow|@shlWithSaturation#}
+ {#see_also|@shlExact|@shlWithOverflow#}
{#header_close#}
{#header_open|@shuffle#}
@@ -8945,16 +8906,6 @@ fn doTheTest() !void {
{#header_close#}
- {#header_open|@subWithSaturation#}
- {#syntax#}@subWithSaturation(a: T, b: T) T{#endsyntax#}
-
- Returns {#syntax#}a - b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
-
-
- The syntax {#syntax#}a -| b{#endsyntax#} is equivalent to calling {#syntax#}@subWithSaturation(a, b){#endsyntax#}.
-
- {#header_close#}
-
{#header_open|@tagName#}
{#syntax#}@tagName(value: anytype) [:0]const u8{#endsyntax#}
diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig
index b69da459d34b..4ee3a4522156 100644
--- a/lib/std/zig/Ast.zig
+++ b/lib/std/zig/Ast.zig
@@ -395,9 +395,9 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex {
.assign_mod,
.assign_add,
.assign_sub,
- .assign_bit_shift_left,
- .assign_bit_shift_left_sat,
- .assign_bit_shift_right,
+ .assign_shl,
+ .assign_shl_sat,
+ .assign_shr,
.assign_bit_and,
.assign_bit_xor,
.assign_bit_or,
@@ -422,9 +422,9 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex {
.sub_wrap,
.add_sat,
.sub_sat,
- .bit_shift_left,
- .bit_shift_left_sat,
- .bit_shift_right,
+ .shl,
+ .shl_sat,
+ .shr,
.bit_and,
.bit_xor,
.bit_or,
@@ -659,9 +659,9 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex {
.assign_mod,
.assign_add,
.assign_sub,
- .assign_bit_shift_left,
- .assign_bit_shift_left_sat,
- .assign_bit_shift_right,
+ .assign_shl,
+ .assign_shl_sat,
+ .assign_shr,
.assign_bit_and,
.assign_bit_xor,
.assign_bit_or,
@@ -686,9 +686,9 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex {
.sub_wrap,
.add_sat,
.sub_sat,
- .bit_shift_left,
- .bit_shift_left_sat,
- .bit_shift_right,
+ .shl,
+ .shl_sat,
+ .shr,
.bit_and,
.bit_xor,
.bit_or,
@@ -2540,11 +2540,11 @@ pub const Node = struct {
/// `lhs -= rhs`. main_token is op.
assign_sub,
/// `lhs <<= rhs`. main_token is op.
- assign_bit_shift_left,
+ assign_shl,
/// `lhs <<|= rhs`. main_token is op.
- assign_bit_shift_left_sat,
+ assign_shl_sat,
/// `lhs >>= rhs`. main_token is op.
- assign_bit_shift_right,
+ assign_shr,
/// `lhs &= rhs`. main_token is op.
assign_bit_and,
/// `lhs ^= rhs`. main_token is op.
@@ -2594,11 +2594,11 @@ pub const Node = struct {
/// `lhs -| rhs`. main_token is the `-|`.
sub_sat,
/// `lhs << rhs`. main_token is the `<<`.
- bit_shift_left,
+ shl,
/// `lhs <<| rhs`. main_token is the `<<|`.
- bit_shift_left_sat,
+ shl_sat,
/// `lhs >> rhs`. main_token is the `>>`.
- bit_shift_right,
+ shr,
/// `lhs & rhs`. main_token is the `&`.
bit_and,
/// `lhs ^ rhs`. main_token is the `^`.
diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig
index a2780b522599..021b02845583 100644
--- a/lib/std/zig/parse.zig
+++ b/lib/std/zig/parse.zig
@@ -1268,9 +1268,9 @@ const Parser = struct {
.percent_equal => .assign_mod,
.plus_equal => .assign_add,
.minus_equal => .assign_sub,
- .angle_bracket_angle_bracket_left_equal => .assign_bit_shift_left,
- .angle_bracket_angle_bracket_left_pipe_equal => .assign_bit_shift_left_sat,
- .angle_bracket_angle_bracket_right_equal => .assign_bit_shift_right,
+ .angle_bracket_angle_bracket_left_equal => .assign_shl,
+ .angle_bracket_angle_bracket_left_pipe_equal => .assign_shl_sat,
+ .angle_bracket_angle_bracket_right_equal => .assign_shr,
.ampersand_equal => .assign_bit_and,
.caret_equal => .assign_bit_xor,
.pipe_equal => .assign_bit_or,
@@ -1346,9 +1346,9 @@ const Parser = struct {
.keyword_orelse = .{ .prec = 40, .tag = .@"orelse" },
.keyword_catch = .{ .prec = 40, .tag = .@"catch" },
- .angle_bracket_angle_bracket_left = .{ .prec = 50, .tag = .bit_shift_left },
- .angle_bracket_angle_bracket_left_pipe = .{ .prec = 50, .tag = .bit_shift_left_sat },
- .angle_bracket_angle_bracket_right = .{ .prec = 50, .tag = .bit_shift_right },
+ .angle_bracket_angle_bracket_left = .{ .prec = 50, .tag = .shl },
+ .angle_bracket_angle_bracket_left_pipe = .{ .prec = 50, .tag = .shl_sat },
+ .angle_bracket_angle_bracket_right = .{ .prec = 50, .tag = .shr },
.plus = .{ .prec = 60, .tag = .add },
.minus = .{ .prec = 60, .tag = .sub },
diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig
index 47f019d1cf9b..43579602515a 100644
--- a/lib/std/zig/render.zig
+++ b/lib/std/zig/render.zig
@@ -339,9 +339,9 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index,
.assign,
.assign_bit_and,
.assign_bit_or,
- .assign_bit_shift_left,
- .assign_bit_shift_left_sat,
- .assign_bit_shift_right,
+ .assign_shl,
+ .assign_shl_sat,
+ .assign_shr,
.assign_bit_xor,
.assign_div,
.assign_sub,
@@ -357,9 +357,9 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index,
.bang_equal,
.bit_and,
.bit_or,
- .bit_shift_left,
- .bit_shift_left_sat,
- .bit_shift_right,
+ .shl,
+ .shl_sat,
+ .shr,
.bit_xor,
.bool_and,
.bool_or,
@@ -2528,8 +2528,8 @@ fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool {
.assign,
.assign_bit_and,
.assign_bit_or,
- .assign_bit_shift_left,
- .assign_bit_shift_right,
+ .assign_shl,
+ .assign_shr,
.assign_bit_xor,
.assign_div,
.assign_sub,
@@ -2542,8 +2542,8 @@ fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool {
.bang_equal,
.bit_and,
.bit_or,
- .bit_shift_left,
- .bit_shift_right,
+ .shl,
+ .shr,
.bit_xor,
.bool_and,
.bool_or,
diff --git a/src/Air.zig b/src/Air.zig
index 00f223ad21fa..f05c18e87a99 100644
--- a/src/Air.zig
+++ b/src/Air.zig
@@ -48,7 +48,7 @@ pub const Inst = struct {
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
/// Uses the `bin_op` field.
- addsat,
+ add_sat,
/// Float or integer subtraction. For integers, wrapping is undefined behavior.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
@@ -63,7 +63,7 @@ pub const Inst = struct {
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
/// Uses the `bin_op` field.
- subsat,
+ sub_sat,
/// Float or integer multiplication. For integers, wrapping is undefined behavior.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
@@ -78,7 +78,7 @@ pub const Inst = struct {
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
/// Uses the `bin_op` field.
- mulsat,
+ mul_sat,
/// Integer or float division. For integers, wrapping is undefined behavior.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
@@ -125,6 +125,11 @@ pub const Inst = struct {
/// Shift left. `<<`
/// Uses the `bin_op` field.
shl,
+ /// Shift left; For unsigned integers, the shift produces a poison value if it shifts
+ /// out any non-zero bits. For signed integers, the shift produces a poison value if
+ /// it shifts out any bits that disagree with the resultant sign bit.
+ /// Uses the `bin_op` field.
+ shl_exact,
/// Shift left saturating. `<<|`
/// Uses the `bin_op` field.
shl_sat,
@@ -586,13 +591,13 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.add,
.addwrap,
- .addsat,
+ .add_sat,
.sub,
.subwrap,
- .subsat,
+ .sub_sat,
.mul,
.mulwrap,
- .mulsat,
+ .mul_sat,
.div,
.rem,
.mod,
@@ -603,6 +608,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.ptr_sub,
.shr,
.shl,
+ .shl_exact,
.shl_sat,
=> return air.typeOf(datas[inst].bin_op.lhs),
diff --git a/src/AstGen.zig b/src/AstGen.zig
index 92087a7719f8..847860630a10 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -317,9 +317,9 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins
.assign,
.assign_bit_and,
.assign_bit_or,
- .assign_bit_shift_left,
- .assign_bit_shift_left_sat,
- .assign_bit_shift_right,
+ .assign_shl,
+ .assign_shl_sat,
+ .assign_shr,
.assign_bit_xor,
.assign_div,
.assign_sub,
@@ -345,9 +345,9 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins
.mod,
.bit_and,
.bit_or,
- .bit_shift_left,
- .bit_shift_left_sat,
- .bit_shift_right,
+ .shl,
+ .shl_sat,
+ .shr,
.bit_xor,
.bang_equal,
.equal_equal,
@@ -530,15 +530,15 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
- .assign_bit_shift_left => {
+ .assign_shl => {
try assignShift(gz, scope, node, .shl);
return rvalue(gz, rl, .void_value, node);
},
- .assign_bit_shift_left_sat => {
- try assignOpExt(gz, scope, node, .shl_with_saturation, Zir.Inst.SaturatingArithmetic);
+ .assign_shl_sat => {
+ try assignShiftSat(gz, scope, node);
return rvalue(gz, rl, .void_value, node);
},
- .assign_bit_shift_right => {
+ .assign_shr => {
try assignShift(gz, scope, node, .shr);
return rvalue(gz, rl, .void_value, node);
},
@@ -568,7 +568,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_sub_sat => {
- try assignOpExt(gz, scope, node, .sub_with_saturation, Zir.Inst.SaturatingArithmetic);
+ try assignOp(gz, scope, node, .sub_sat);
return rvalue(gz, rl, .void_value, node);
},
.assign_mod => {
@@ -584,7 +584,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_add_sat => {
- try assignOpExt(gz, scope, node, .add_with_saturation, Zir.Inst.SaturatingArithmetic);
+ try assignOp(gz, scope, node, .add_sat);
return rvalue(gz, rl, .void_value, node);
},
.assign_mul => {
@@ -596,28 +596,27 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_mul_sat => {
- try assignOpExt(gz, scope, node, .mul_with_saturation, Zir.Inst.SaturatingArithmetic);
+ try assignOp(gz, scope, node, .mul_sat);
return rvalue(gz, rl, .void_value, node);
},
// zig fmt: off
- .bit_shift_left => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl),
- .bit_shift_right => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr),
+ .shl => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl),
+ .shr => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr),
.add => return simpleBinOp(gz, scope, rl, node, .add),
.add_wrap => return simpleBinOp(gz, scope, rl, node, .addwrap),
+ .add_sat => return simpleBinOp(gz, scope, rl, node, .add_sat),
.sub => return simpleBinOp(gz, scope, rl, node, .sub),
.sub_wrap => return simpleBinOp(gz, scope, rl, node, .subwrap),
+ .sub_sat => return simpleBinOp(gz, scope, rl, node, .sub_sat),
.mul => return simpleBinOp(gz, scope, rl, node, .mul),
.mul_wrap => return simpleBinOp(gz, scope, rl, node, .mulwrap),
+ .mul_sat => return simpleBinOp(gz, scope, rl, node, .mul_sat),
.div => return simpleBinOp(gz, scope, rl, node, .div),
.mod => return simpleBinOp(gz, scope, rl, node, .mod_rem),
+ .shl_sat => return simpleBinOp(gz, scope, rl, node, .shl_sat),
- .add_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation, Zir.Inst.SaturatingArithmetic),
- .sub_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation, Zir.Inst.SaturatingArithmetic),
- .mul_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation, Zir.Inst.SaturatingArithmetic),
- .bit_shift_left_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation, Zir.Inst.SaturatingArithmetic),
-
.bit_and => {
const current_ampersand_token = main_tokens[node];
if (token_tags[current_ampersand_token + 1] == .ampersand) {
@@ -1928,8 +1927,8 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod
.assign => try assign(gz, scope, statement),
- .assign_bit_shift_left => try assignShift(gz, scope, statement, .shl),
- .assign_bit_shift_right => try assignShift(gz, scope, statement, .shr),
+ .assign_shl => try assignShift(gz, scope, statement, .shl),
+ .assign_shr => try assignShift(gz, scope, statement, .shr),
.assign_bit_and => try assignOp(gz, scope, statement, .bit_and),
.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
// ZIR instructions that might be a type other than `noreturn` or `void`.
.add,
.addwrap,
+ .add_sat,
.param,
.param_comptime,
.param_anytype,
@@ -2045,12 +2045,15 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
.mod_rem,
.mul,
.mulwrap,
+ .mul_sat,
.ref,
.shl,
+ .shl_sat,
.shr,
.str,
.sub,
.subwrap,
+ .sub_sat,
.negate,
.negate_wrap,
.typeof,
@@ -2715,55 +2718,30 @@ fn assignOp(
_ = try gz.addBin(.store, lhs_ptr, result);
}
-fn simpleBinOpExt(
- gz: *GenZir,
- scope: *Scope,
- rl: ResultLoc,
- infix_node: Ast.Node.Index,
- lhs_node: Ast.Node.Index,
- rhs_node: Ast.Node.Index,
- tag: Zir.Inst.Extended,
- comptime T: type,
-) InnerError!Zir.Inst.Ref {
- const lhs = try expr(gz, scope, .none, lhs_node);
- const rhs = try expr(gz, scope, .none, rhs_node);
- const result = try gz.addExtendedPayload(tag, T{
- .node = gz.nodeIndexToRelative(infix_node),
- .lhs = lhs,
- .rhs = rhs,
- });
- return rvalue(gz, rl, result, infix_node);
-}
-
-fn assignOpExt(
+fn assignShift(
gz: *GenZir,
scope: *Scope,
infix_node: Ast.Node.Index,
- op_inst_tag: Zir.Inst.Extended,
- comptime T: type,
+ op_inst_tag: Zir.Inst.Tag,
) InnerError!void {
+ try emitDbgNode(gz, infix_node);
const astgen = gz.astgen;
const tree = astgen.tree;
const node_datas = tree.nodes.items(.data);
const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs);
const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
- const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node);
- const rhs = try expr(gz, scope, .{ .coerced_ty = lhs_type }, node_datas[infix_node].rhs);
- const result = try gz.addExtendedPayload(op_inst_tag, T{
- .node = gz.nodeIndexToRelative(infix_node),
+ const rhs_type = try gz.addUnNode(.typeof_log2_int_type, lhs, infix_node);
+ const rhs = try expr(gz, scope, .{ .ty = rhs_type }, node_datas[infix_node].rhs);
+
+ const result = try gz.addPlNode(op_inst_tag, infix_node, Zir.Inst.Bin{
.lhs = lhs,
.rhs = rhs,
});
_ = try gz.addBin(.store, lhs_ptr, result);
}
-fn assignShift(
- gz: *GenZir,
- scope: *Scope,
- infix_node: Ast.Node.Index,
- op_inst_tag: Zir.Inst.Tag,
-) InnerError!void {
+fn assignShiftSat(gz: *GenZir, scope: *Scope, infix_node: Ast.Node.Index) InnerError!void {
try emitDbgNode(gz, infix_node);
const astgen = gz.astgen;
const tree = astgen.tree;
@@ -2771,10 +2749,10 @@ fn assignShift(
const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs);
const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
- const rhs_type = try gz.addUnNode(.typeof_log2_int_type, lhs, infix_node);
- const rhs = try expr(gz, scope, .{ .ty = rhs_type }, node_datas[infix_node].rhs);
+ // Saturating shift-left allows any integer type for both the LHS and RHS.
+ const rhs = try expr(gz, scope, .none, node_datas[infix_node].rhs);
- const result = try gz.addPlNode(op_inst_tag, infix_node, Zir.Inst.Bin{
+ const result = try gz.addPlNode(.shl_sat, infix_node, Zir.Inst.Bin{
.lhs = lhs,
.rhs = rhs,
});
@@ -7556,11 +7534,6 @@ fn builtinCall(
return rvalue(gz, rl, result, node);
},
- .add_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .add_with_saturation),
- .sub_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .sub_with_saturation),
- .mul_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .mul_with_saturation),
- .shl_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .shl_with_saturation),
-
.atomic_load => {
const int_type = try typeExpr(gz, scope, params[0]);
// TODO allow this pointer type to be volatile
@@ -7955,24 +7928,6 @@ fn overflowArithmetic(
return rvalue(gz, rl, result, node);
}
-fn saturatingArithmetic(
- gz: *GenZir,
- scope: *Scope,
- rl: ResultLoc,
- node: Ast.Node.Index,
- params: []const Ast.Node.Index,
- tag: Zir.Inst.Extended,
-) InnerError!Zir.Inst.Ref {
- const lhs = try expr(gz, scope, .none, params[0]);
- const rhs = try expr(gz, scope, .none, params[1]);
- const result = try gz.addExtendedPayload(tag, Zir.Inst.SaturatingArithmetic{
- .node = gz.nodeIndexToRelative(node),
- .lhs = lhs,
- .rhs = rhs,
- });
- return rvalue(gz, rl, result, node);
-}
-
fn callExpr(
gz: *GenZir,
scope: *Scope,
@@ -8198,9 +8153,9 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool
.assign,
.assign_bit_and,
.assign_bit_or,
- .assign_bit_shift_left,
- .assign_bit_shift_left_sat,
- .assign_bit_shift_right,
+ .assign_shl,
+ .assign_shl_sat,
+ .assign_shr,
.assign_bit_xor,
.assign_div,
.assign_sub,
@@ -8216,9 +8171,9 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool
.bang_equal,
.bit_and,
.bit_or,
- .bit_shift_left,
- .bit_shift_left_sat,
- .bit_shift_right,
+ .shl,
+ .shl_sat,
+ .shr,
.bit_xor,
.bool_and,
.bool_or,
@@ -8439,9 +8394,9 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) enum { never
.assign,
.assign_bit_and,
.assign_bit_or,
- .assign_bit_shift_left,
- .assign_bit_shift_left_sat,
- .assign_bit_shift_right,
+ .assign_shl,
+ .assign_shl_sat,
+ .assign_shr,
.assign_bit_xor,
.assign_div,
.assign_sub,
@@ -8457,9 +8412,9 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) enum { never
.bang_equal,
.bit_and,
.bit_or,
- .bit_shift_left,
- .bit_shift_left_sat,
- .bit_shift_right,
+ .shl,
+ .shl_sat,
+ .shr,
.bit_xor,
.bool_and,
.bool_or,
@@ -8619,9 +8574,9 @@ fn nodeImpliesRuntimeBits(tree: *const Ast, start_node: Ast.Node.Index) bool {
.assign,
.assign_bit_and,
.assign_bit_or,
- .assign_bit_shift_left,
- .assign_bit_shift_left_sat,
- .assign_bit_shift_right,
+ .assign_shl,
+ .assign_shl_sat,
+ .assign_shr,
.assign_bit_xor,
.assign_div,
.assign_sub,
@@ -8637,9 +8592,9 @@ fn nodeImpliesRuntimeBits(tree: *const Ast, start_node: Ast.Node.Index) bool {
.bang_equal,
.bit_and,
.bit_or,
- .bit_shift_left,
- .bit_shift_left_sat,
- .bit_shift_right,
+ .shl,
+ .shl_sat,
+ .shr,
.bit_xor,
.bool_and,
.bool_or,
diff --git a/src/BuiltinFn.zig b/src/BuiltinFn.zig
index e415d27a3ab9..8f23ec86d7ce 100644
--- a/src/BuiltinFn.zig
+++ b/src/BuiltinFn.zig
@@ -2,7 +2,6 @@ const std = @import("std");
pub const Tag = enum {
add_with_overflow,
- add_with_saturation,
align_cast,
align_of,
as,
@@ -66,7 +65,6 @@ pub const Tag = enum {
wasm_memory_grow,
mod,
mul_with_overflow,
- mul_with_saturation,
panic,
pop_count,
ptr_cast,
@@ -81,12 +79,10 @@ pub const Tag = enum {
set_runtime_safety,
shl_exact,
shl_with_overflow,
- shl_with_saturation,
shr_exact,
shuffle,
size_of,
splat,
- sub_with_saturation,
reduce,
src,
sqrt,
@@ -531,34 +527,6 @@ pub const list = list: {
.param_count = 2,
},
},
- .{
- "@addWithSaturation",
- .{
- .tag = .add_with_saturation,
- .param_count = 2,
- },
- },
- .{
- "@subWithSaturation",
- .{
- .tag = .sub_with_saturation,
- .param_count = 2,
- },
- },
- .{
- "@mulWithSaturation",
- .{
- .tag = .mul_with_saturation,
- .param_count = 2,
- },
- },
- .{
- "@shlWithSaturation",
- .{
- .tag = .shl_with_saturation,
- .param_count = 2,
- },
- },
.{
"@memcpy",
.{
diff --git a/src/Liveness.zig b/src/Liveness.zig
index c34153b76fba..93f28ad7b223 100644
--- a/src/Liveness.zig
+++ b/src/Liveness.zig
@@ -226,13 +226,13 @@ fn analyzeInst(
switch (inst_tags[inst]) {
.add,
.addwrap,
- .addsat,
+ .add_sat,
.sub,
.subwrap,
- .subsat,
+ .sub_sat,
.mul,
.mulwrap,
- .mulsat,
+ .mul_sat,
.div,
.rem,
.mod,
@@ -255,6 +255,7 @@ fn analyzeInst(
.ptr_elem_val,
.ptr_ptr_elem_val,
.shl,
+ .shl_exact,
.shl_sat,
.shr,
.atomic_store_unordered,
diff --git a/src/Sema.zig b/src/Sema.zig
index be10b6d66329..f106d7ea9e38 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -246,7 +246,6 @@ pub fn analyzeBody(
.ptr_type_simple => try sema.zirPtrTypeSimple(block, inst),
.ref => try sema.zirRef(block, inst),
.ret_err_value_code => try sema.zirRetErrValueCode(block, inst),
- .shl => try sema.zirShl(block, inst),
.shr => try sema.zirShr(block, inst),
.slice_end => try sema.zirSliceEnd(block, inst),
.slice_sentinel => try sema.zirSliceSentinel(block, inst),
@@ -319,7 +318,6 @@ pub fn analyzeBody(
.div_exact => try sema.zirDivExact(block, inst),
.div_floor => try sema.zirDivFloor(block, inst),
.div_trunc => try sema.zirDivTrunc(block, inst),
- .shl_exact => try sema.zirShlExact(block, inst),
.shr_exact => try sema.zirShrExact(block, inst),
.bit_offset_of => try sema.zirBitOffsetOf(block, inst),
.offset_of => try sema.zirOffsetOf(block, inst),
@@ -363,14 +361,21 @@ pub fn analyzeBody(
.add => try sema.zirArithmetic(block, inst, .add),
.addwrap => try sema.zirArithmetic(block, inst, .addwrap),
+ .add_sat => try sema.zirArithmetic(block, inst, .add_sat),
.div => try sema.zirArithmetic(block, inst, .div),
.mod_rem => try sema.zirArithmetic(block, inst, .mod_rem),
.mod => try sema.zirArithmetic(block, inst, .mod),
.rem => try sema.zirArithmetic(block, inst, .rem),
.mul => try sema.zirArithmetic(block, inst, .mul),
.mulwrap => try sema.zirArithmetic(block, inst, .mulwrap),
+ .mul_sat => try sema.zirArithmetic(block, inst, .mul_sat),
.sub => try sema.zirArithmetic(block, inst, .sub),
.subwrap => try sema.zirArithmetic(block, inst, .subwrap),
+ .sub_sat => try sema.zirArithmetic(block, inst, .sub_sat),
+
+ .shl => try sema.zirShl(block, inst, .shl),
+ .shl_exact => try sema.zirShl(block, inst, .shl_exact),
+ .shl_sat => try sema.zirShl(block, inst, .shl_sat),
// Instructions that we know to *always* be noreturn based solely on their tag.
// 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
.c_define => return sema.zirCDefine( block, extended),
.wasm_memory_size => return sema.zirWasmMemorySize( block, extended),
.wasm_memory_grow => return sema.zirWasmMemoryGrow( block, extended),
- .add_with_saturation,
- .sub_with_saturation,
- .mul_with_saturation,
- .shl_with_saturation,
- => return sema.zirSatArithmetic( block, extended),
// zig fmt: on
}
}
@@ -5875,7 +5875,12 @@ fn zirRetErrValueCode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co
return sema.mod.fail(&block.base, sema.src, "TODO implement zirRetErrValueCode", .{});
}
-fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+fn zirShl(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: Zir.Inst.Index,
+ air_tag: Air.Inst.Tag,
+) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
@@ -5886,6 +5891,8 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A
const lhs = sema.resolveInst(extra.lhs);
const rhs = sema.resolveInst(extra.rhs);
+ // TODO coerce rhs if air_tag is not shl_sat
+
const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs);
const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs);
@@ -5901,6 +5908,12 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A
return sema.addConstant(lhs_ty, lhs_val);
}
const val = try lhs_val.shl(rhs_val, sema.arena);
+ switch (air_tag) {
+ .shl_exact => return sema.mod.fail(&block.base, lhs_src, "TODO implement Sema for comptime shl_exact", .{}),
+ .shl_sat => return sema.mod.fail(&block.base, lhs_src, "TODO implement Sema for comptime shl_sat", .{}),
+ .shl => {},
+ else => unreachable,
+ }
return sema.addConstant(lhs_ty, val);
} else rs: {
if (maybe_rhs_val) |rhs_val| {
@@ -5909,8 +5922,10 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A
break :rs lhs_src;
};
+ // TODO: insert runtime safety check for shl_exact
+
try sema.requireRuntimeBlock(block, runtime_src);
- return block.addBinOp(.shl, lhs, rhs);
+ return block.addBinOp(air_tag, lhs, rhs);
}
fn zirShr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -6201,105 +6216,6 @@ fn zirOverflowArithmetic(
return sema.mod.fail(&block.base, src, "TODO implement Sema.zirOverflowArithmetic", .{});
}
-fn zirSatArithmetic(
- sema: *Sema,
- block: *Scope.Block,
- extended: Zir.Inst.Extended.InstData,
-) CompileError!Air.Inst.Ref {
- const tracy = trace(@src());
- defer tracy.end();
-
- const extra = sema.code.extraData(Zir.Inst.SaturatingArithmetic, extended.operand).data;
- sema.src = .{ .node_offset_bin_op = extra.node };
- const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = extra.node };
- const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = extra.node };
- const lhs = sema.resolveInst(extra.lhs);
- const rhs = sema.resolveInst(extra.rhs);
-
- return sema.analyzeSatArithmetic(block, lhs, rhs, sema.src, lhs_src, rhs_src, extended);
-}
-
-fn analyzeSatArithmetic(
- sema: *Sema,
- block: *Scope.Block,
- lhs: Air.Inst.Ref,
- rhs: Air.Inst.Ref,
- src: LazySrcLoc,
- lhs_src: LazySrcLoc,
- rhs_src: LazySrcLoc,
- extended: Zir.Inst.Extended.InstData,
-) CompileError!Air.Inst.Ref {
- const lhs_ty = sema.typeOf(lhs);
- const rhs_ty = sema.typeOf(rhs);
- const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison();
- const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison();
- if (lhs_zig_ty_tag == .Vector and rhs_zig_ty_tag == .Vector) {
- if (lhs_ty.arrayLen() != rhs_ty.arrayLen()) {
- return sema.mod.fail(&block.base, src, "vector length mismatch: {d} and {d}", .{
- lhs_ty.arrayLen(), rhs_ty.arrayLen(),
- });
- }
- return sema.mod.fail(&block.base, src, "TODO implement support for vectors in zirBinOp", .{});
- } else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) {
- return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{
- lhs_ty, rhs_ty,
- });
- }
-
- if (lhs_zig_ty_tag == .Pointer or rhs_zig_ty_tag == .Pointer)
- return sema.mod.fail(&block.base, src, "TODO implement support for pointers in zirSatArithmetic", .{});
-
- const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
- const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ .override = &[_]LazySrcLoc{ lhs_src, rhs_src } });
- const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
- const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
-
- const scalar_type = if (resolved_type.zigTypeTag() == .Vector)
- resolved_type.elemType()
- else
- resolved_type;
-
- const scalar_tag = scalar_type.zigTypeTag();
-
- const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
-
- if (!is_int)
- return sema.mod.fail(&block.base, src, "invalid operands to binary expression: '{s}' and '{s}'", .{
- @tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag),
- });
-
- if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| {
- if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| {
- if (lhs_val.isUndef() or rhs_val.isUndef()) {
- return sema.addConstUndef(resolved_type);
- }
- // incase rhs is 0, simply return lhs without doing any calculations
- if (rhs_val.compareWithZero(.eq)) {
- switch (extended.opcode) {
- .add_with_saturation, .sub_with_saturation => return sema.addConstant(scalar_type, lhs_val),
- else => {},
- }
- }
-
- return sema.mod.fail(&block.base, src, "TODO implement comptime saturating arithmetic for operand '{s}'", .{@tagName(extended.opcode)});
- } else {
- try sema.requireRuntimeBlock(block, rhs_src);
- }
- } else {
- try sema.requireRuntimeBlock(block, lhs_src);
- }
-
- const air_tag: Air.Inst.Tag = switch (extended.opcode) {
- .add_with_saturation => .addsat,
- .sub_with_saturation => .subsat,
- .mul_with_saturation => .mulsat,
- .shl_with_saturation => .shl_sat,
- else => return sema.mod.fail(&block.base, src, "TODO implement arithmetic for extended opcode '{s}'", .{@tagName(extended.opcode)}),
- };
-
- return block.addBinOp(air_tag, casted_lhs, casted_rhs);
-}
-
fn analyzeArithmetic(
sema: *Sema,
block: *Scope.Block,
@@ -6441,8 +6357,7 @@ fn analyzeArithmetic(
},
.addwrap => {
// Integers only; floats are checked above.
- // If either of the operands are zero, then the other operand is
- // returned, even if it is undefined.
+ // If either of the operands are zero, the other operand is returned.
// If either of the operands are undefined, the result is undefined.
if (maybe_lhs_val) |lhs_val| {
if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) {
@@ -6464,6 +6379,30 @@ fn analyzeArithmetic(
} else break :rs .{ .src = lhs_src, .air_tag = .addwrap };
} else break :rs .{ .src = rhs_src, .air_tag = .addwrap };
},
+ .add_sat => {
+ // For both integers and floats:
+ // If either of the operands are zero, then the other operand is returned.
+ // If either of the operands are undefined, the result is undefined.
+ if (maybe_lhs_val) |lhs_val| {
+ if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) {
+ return casted_rhs;
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return casted_lhs;
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.numberAddSat(rhs_val, scalar_type, sema.arena, target),
+ );
+ } else break :rs .{ .src = lhs_src, .air_tag = .add_sat };
+ } else break :rs .{ .src = rhs_src, .air_tag = .add_sat };
+ },
.sub => {
// For integers:
// If the rhs is zero, then the other operand is
@@ -6531,6 +6470,30 @@ fn analyzeArithmetic(
} else break :rs .{ .src = rhs_src, .air_tag = .subwrap };
} else break :rs .{ .src = lhs_src, .air_tag = .subwrap };
},
+ .sub_sat => {
+ // For both integers and floats:
+ // If the RHS is zero, result is LHS.
+ // If either of the operands are undefined, result is undefined.
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return casted_lhs;
+ }
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.numberSubSat(rhs_val, scalar_type, sema.arena, target),
+ );
+ } else break :rs .{ .src = rhs_src, .air_tag = .sub_sat };
+ } else break :rs .{ .src = lhs_src, .air_tag = .sub_sat };
+ },
.div => {
// For integers:
// If the lhs is zero, then zero is returned regardless of rhs.
@@ -6649,10 +6612,9 @@ fn analyzeArithmetic(
},
.mulwrap => {
// Integers only; floats are handled above.
- // If either of the operands are zero, the result is zero.
- // If either of the operands are one, the result is the other
- // operand, even if it is undefined.
- // If either of the operands are undefined, the result is undefined.
+ // If either of the operands are zero, result is zero.
+ // If either of the operands are one, result is the other operand.
+ // If either of the operands are undefined, result is undefined.
if (maybe_lhs_val) |lhs_val| {
if (!lhs_val.isUndef()) {
if (lhs_val.compareWithZero(.eq)) {
@@ -6684,6 +6646,42 @@ fn analyzeArithmetic(
} else break :rs .{ .src = lhs_src, .air_tag = .mulwrap };
} else break :rs .{ .src = rhs_src, .air_tag = .mulwrap };
},
+ .mul_sat => {
+ // For both integers and floats:
+ // If either of the operands are zero, result is zero.
+ // If either of the operands are one, result is the other operand.
+ // If either of the operands are undefined, result is undefined.
+ if (maybe_lhs_val) |lhs_val| {
+ if (!lhs_val.isUndef()) {
+ if (lhs_val.compareWithZero(.eq)) {
+ return sema.addConstant(scalar_type, Value.zero);
+ }
+ if (lhs_val.compare(.eq, Value.one, scalar_type)) {
+ return casted_rhs;
+ }
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return sema.addConstant(scalar_type, Value.zero);
+ }
+ if (rhs_val.compare(.eq, Value.one, scalar_type)) {
+ return casted_lhs;
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.numberMulSat(rhs_val, scalar_type, sema.arena, target),
+ );
+ } else break :rs .{ .src = lhs_src, .air_tag = .mul_sat };
+ } else break :rs .{ .src = rhs_src, .air_tag = .mul_sat };
+ },
.mod_rem => {
// For integers:
// Either operand being undef is a compile error because there exists
@@ -7933,7 +7931,7 @@ fn analyzeRet(
fn floatOpAllowed(tag: Zir.Inst.Tag) bool {
// extend this swich as additional operators are implemented
return switch (tag) {
- .add, .sub, .mul, .div, .mod, .rem, .mod_rem => true,
+ .add, .add_sat, .sub, .sub_sat, .mul, .mul_sat, .div, .mod, .rem, .mod_rem => true,
else => false,
};
}
@@ -8600,12 +8598,6 @@ fn zirDivTrunc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
return sema.mod.fail(&block.base, src, "TODO: Sema.zirDivTrunc", .{});
}
-fn zirShlExact(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
- const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
- const src = inst_data.src();
- return sema.mod.fail(&block.base, src, "TODO: Sema.zirShlExact", .{});
-}
-
fn zirShrExact(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
diff --git a/src/Zir.zig b/src/Zir.zig
index 7c171e736d43..1da53a526e98 100644
--- a/src/Zir.zig
+++ b/src/Zir.zig
@@ -126,6 +126,64 @@ pub const Inst = struct {
/// Twos complement wrapping integer addition.
/// Uses the `pl_node` union field. Payload is `Bin`.
addwrap,
+ /// Saturating addition.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ add_sat,
+ /// Arithmetic subtraction. Asserts no integer overflow.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ sub,
+ /// Twos complement wrapping integer subtraction.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ subwrap,
+ /// Saturating subtraction.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ sub_sat,
+ /// Arithmetic multiplication. Asserts no integer overflow.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ mul,
+ /// Twos complement wrapping integer multiplication.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ mulwrap,
+ /// Saturating multiplication.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ mul_sat,
+ /// Implements the `@divExact` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`.
+ div_exact,
+ /// Implements the `@divFloor` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`.
+ div_floor,
+ /// Implements the `@divTrunc` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`.
+ div_trunc,
+ /// Implements the `@mod` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`.
+ mod,
+ /// Implements the `@rem` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`.
+ rem,
+ /// Ambiguously remainder division or modulus. If the computation would possibly have
+ /// a different value depending on whether the operation is remainder division or modulus,
+ /// a compile error is emitted. Otherwise the computation is performed.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ mod_rem,
+ /// Integer shift-left. Zeroes are shifted in from the right hand side.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ shl,
+ /// Implements the `@shlExact` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`.
+ shl_exact,
+ /// Saturating shift-left.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ shl_sat,
+ /// Integer shift-right. Arithmetic or logical depending on the signedness of
+ /// the integer type.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ shr,
+ /// Implements the `@shrExact` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`.
+ shr_exact,
+
/// Declares a parameter of the current function. Used for:
/// * debug info
/// * checking shadowing against declarations in the current namespace
@@ -471,12 +529,6 @@ pub const Inst = struct {
/// String Literal. Makes an anonymous Decl and then takes a pointer to it.
/// Uses the `str` union field.
str,
- /// Arithmetic subtraction. Asserts no integer overflow.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- sub,
- /// Twos complement wrapping integer subtraction.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- subwrap,
/// Arithmetic negation. Asserts no integer overflow.
/// Same as sub with a lhs of 0, split into a separate instruction to save memory.
/// Uses `un_node`.
@@ -802,46 +854,6 @@ pub const Inst = struct {
/// Implements the `@bitReverse` builtin. Uses the `un_node` union field.
bit_reverse,
- /// Implements the `@divExact` builtin.
- /// Uses the `pl_node` union field with payload `Bin`.
- div_exact,
- /// Implements the `@divFloor` builtin.
- /// Uses the `pl_node` union field with payload `Bin`.
- div_floor,
- /// Implements the `@divTrunc` builtin.
- /// Uses the `pl_node` union field with payload `Bin`.
- div_trunc,
- /// Implements the `@mod` builtin.
- /// Uses the `pl_node` union field with payload `Bin`.
- mod,
- /// Implements the `@rem` builtin.
- /// Uses the `pl_node` union field with payload `Bin`.
- rem,
- /// Ambiguously remainder division or modulus. If the computation would possibly have
- /// a different value depending on whether the operation is remainder division or modulus,
- /// a compile error is emitted. Otherwise the computation is performed.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- mod_rem,
- /// Arithmetic multiplication. Asserts no integer overflow.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- mul,
- /// Twos complement wrapping integer multiplication.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- mulwrap,
-
- /// Integer shift-left. Zeroes are shifted in from the right hand side.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- shl,
- /// Implements the `@shlExact` builtin.
- /// Uses the `pl_node` union field with payload `Bin`.
- shl_exact,
- /// Integer shift-right. Arithmetic or logical depending on the signedness of the integer type.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- shr,
- /// Implements the `@shrExact` builtin.
- /// Uses the `pl_node` union field with payload `Bin`.
- shr_exact,
-
/// Implements the `@bitOffsetOf` builtin.
/// Uses the `pl_node` union field with payload `Bin`.
bit_offset_of,
@@ -961,6 +973,7 @@ pub const Inst = struct {
.param_anytype_comptime,
.add,
.addwrap,
+ .add_sat,
.alloc,
.alloc_mut,
.alloc_comptime,
@@ -1035,8 +1048,10 @@ pub const Inst = struct {
.mod_rem,
.mul,
.mulwrap,
+ .mul_sat,
.ref,
.shl,
+ .shl_sat,
.shr,
.store,
.store_node,
@@ -1045,6 +1060,7 @@ pub const Inst = struct {
.str,
.sub,
.subwrap,
+ .sub_sat,
.negate,
.negate_wrap,
.typeof,
@@ -1218,6 +1234,14 @@ pub const Inst = struct {
break :list std.enums.directEnumArray(Tag, Data.FieldEnum, 0, .{
.add = .pl_node,
.addwrap = .pl_node,
+ .add_sat = .pl_node,
+ .sub = .pl_node,
+ .subwrap = .pl_node,
+ .sub_sat = .pl_node,
+ .mul = .pl_node,
+ .mulwrap = .pl_node,
+ .mul_sat = .pl_node,
+
.param = .pl_tok,
.param_comptime = .pl_tok,
.param_anytype = .str_tok,
@@ -1297,8 +1321,6 @@ pub const Inst = struct {
.repeat_inline = .node,
.merge_error_sets = .pl_node,
.mod_rem = .pl_node,
- .mul = .pl_node,
- .mulwrap = .pl_node,
.ref = .un_tok,
.ret_node = .un_node,
.ret_load = .un_node,
@@ -1315,8 +1337,6 @@ pub const Inst = struct {
.store_to_block_ptr = .bin,
.store_to_inferred_ptr = .bin,
.str = .str,
- .sub = .pl_node,
- .subwrap = .pl_node,
.negate = .un_node,
.negate_wrap = .un_node,
.typeof = .un_node,
@@ -1437,6 +1457,7 @@ pub const Inst = struct {
.shl = .pl_node,
.shl_exact = .pl_node,
+ .shl_sat = .pl_node,
.shr = .pl_node,
.shr_exact = .pl_node,
@@ -1593,22 +1614,6 @@ pub const Inst = struct {
wasm_memory_size,
/// `operand` is payload index to `BinNode`.
wasm_memory_grow,
- /// Implements the `@addWithSaturation` builtin.
- /// `operand` is payload index to `SaturatingArithmetic`.
- /// `small` is unused.
- add_with_saturation,
- /// Implements the `@subWithSaturation` builtin.
- /// `operand` is payload index to `SaturatingArithmetic`.
- /// `small` is unused.
- sub_with_saturation,
- /// Implements the `@mulWithSaturation` builtin.
- /// `operand` is payload index to `SaturatingArithmetic`.
- /// `small` is unused.
- mul_with_saturation,
- /// Implements the `@shlWithSaturation` builtin.
- /// `operand` is payload index to `SaturatingArithmetic`.
- /// `small` is unused.
- shl_with_saturation,
pub const InstData = struct {
opcode: Extended,
@@ -2788,12 +2793,6 @@ pub const Inst = struct {
ptr: Ref,
};
- pub const SaturatingArithmetic = struct {
- node: i32,
- lhs: Ref,
- rhs: Ref,
- };
-
pub const Cmpxchg = struct {
ptr: Ref,
expected_value: Ref,
diff --git a/src/codegen.zig b/src/codegen.zig
index a1f812388f15..79105dc4a785 100644
--- a/src/codegen.zig
+++ b/src/codegen.zig
@@ -824,18 +824,20 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
switch (air_tags[inst]) {
// zig fmt: off
- .add, .ptr_add => try self.airAdd(inst),
- .addwrap => try self.airAddWrap(inst),
- .addsat => try self.airArithmeticOpSat(inst, "addsat"),
- .sub, .ptr_sub => try self.airSub(inst),
- .subwrap => try self.airSubWrap(inst),
- .subsat => try self.airArithmeticOpSat(inst, "subsat"),
- .mul => try self.airMul(inst),
- .mulwrap => try self.airMulWrap(inst),
- .mulsat => try self.airArithmeticOpSat(inst, "mulsat"),
- .div => try self.airDiv(inst),
- .rem => try self.airRem(inst),
- .mod => try self.airMod(inst),
+ .add, .ptr_add => try self.airAdd(inst),
+ .addwrap => try self.airAddWrap(inst),
+ .add_sat => try self.airAddSat(inst),
+ .sub, .ptr_sub => try self.airSub(inst),
+ .subwrap => try self.airSubWrap(inst),
+ .sub_sat => try self.airSubSat(inst),
+ .mul => try self.airMul(inst),
+ .mulwrap => try self.airMulWrap(inst),
+ .mul_sat => try self.airMulSat(inst),
+ .div => try self.airDiv(inst),
+ .rem => try self.airRem(inst),
+ .mod => try self.airMod(inst),
+ .shl, .shl_exact => try self.airShl(inst),
+ .shl_sat => try self.airShlSat(inst),
.cmp_lt => try self.airCmp(inst, .lt),
.cmp_lte => try self.airCmp(inst, .lte),
@@ -850,8 +852,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.bit_or => try self.airBitOr(inst),
.xor => try self.airXor(inst),
.shr => try self.airShr(inst),
- .shl => try self.airShl(inst),
- .shl_sat => try self.airArithmeticOpSat(inst, "shl_sat"),
.alloc => try self.airAlloc(inst),
.arg => try self.airArg(inst),
@@ -1306,6 +1306,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}
+ fn airAddSat(self: *Self, inst: Air.Inst.Index) !void {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+ else => return self.fail("TODO implement add_sat for {}", .{self.target.cpu.arch}),
+ };
+ return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+ }
+
fn airSub(self: *Self, inst: Air.Inst.Index) !void {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
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 {
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}
- fn airArithmeticOpSat(self: *Self, inst: Air.Inst.Index, comptime name: []const u8) !void {
+ fn airSubSat(self: *Self, inst: Air.Inst.Index) !void {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
- else => return self.fail("TODO implement " ++ name ++ " for {}", .{self.target.cpu.arch}),
+ else => return self.fail("TODO implement sub_sat for {}", .{self.target.cpu.arch}),
};
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}
@@ -1350,6 +1358,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}
+ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+ else => return self.fail("TODO implement mul_sat for {}", .{self.target.cpu.arch}),
+ };
+ return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+ }
+
fn airDiv(self: *Self, inst: Air.Inst.Index) !void {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
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 {
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}
+ fn airShlSat(self: *Self, inst: Air.Inst.Index) !void {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+ else => return self.fail("TODO implement shl_sat for {}", .{self.target.cpu.arch}),
+ };
+ return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+ }
+
fn airShr(self: *Self, inst: Air.Inst.Index) !void {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index 1afa81b70f0c..95ce95f2e593 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -883,25 +883,27 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
// TODO use a different strategy for add that communicates to the optimizer
// that wrapping is UB.
- .add, .ptr_add => try airBinOp( f, inst, " + "),
- .addwrap => try airWrapOp(f, inst, " + ", "addw_"),
- .addsat => return f.fail("TODO: C backend: implement codegen for addsat", .{}),
+ .add, .ptr_add => try airBinOp (f, inst, " + "),
// TODO use a different strategy for sub that communicates to the optimizer
// that wrapping is UB.
- .sub, .ptr_sub => try airBinOp( f, inst, " - "),
- .subwrap => try airWrapOp(f, inst, " - ", "subw_"),
- .subsat => return f.fail("TODO: C backend: implement codegen for subsat", .{}),
+ .sub, .ptr_sub => try airBinOp (f, inst, " - "),
// TODO use a different strategy for mul that communicates to the optimizer
// that wrapping is UB.
- .mul => try airBinOp( f, inst, " * "),
- .mulwrap => try airWrapOp(f, inst, " * ", "mulw_"),
- .mulsat => return f.fail("TODO: C backend: implement codegen for mulsat", .{}),
+ .mul => try airBinOp (f, inst, " * "),
// TODO use a different strategy for div that communicates to the optimizer
// that wrapping is UB.
.div => try airBinOp( f, inst, " / "),
.rem => try airBinOp( f, inst, " % "),
- // TODO implement modulus division
- .mod => try airBinOp( f, inst, " mod "),
+ .mod => try airBinOp( f, inst, " mod "), // TODO implement modulus division
+
+ .addwrap => try airWrapOp(f, inst, " + ", "addw_"),
+ .subwrap => try airWrapOp(f, inst, " - ", "subw_"),
+ .mulwrap => try airWrapOp(f, inst, " * ", "mulw_"),
+
+ .add_sat => try airSatOp(f, inst, "adds_"),
+ .sub_sat => try airSatOp(f, inst, "subs_"),
+ .mul_sat => try airSatOp(f, inst, "muls_"),
+ .shl_sat => try airSatOp(f, inst, "shls_"),
.cmp_eq => try airBinOp(f, inst, " == "),
.cmp_gt => try airBinOp(f, inst, " > "),
@@ -911,18 +913,14 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.cmp_neq => try airBinOp(f, inst, " != "),
// bool_and and bool_or are non-short-circuit operations
- .bool_and => try airBinOp(f, inst, " & "),
- .bool_or => try airBinOp(f, inst, " | "),
- .bit_and => try airBinOp(f, inst, " & "),
- .bit_or => try airBinOp(f, inst, " | "),
- .xor => try airBinOp(f, inst, " ^ "),
-
- .shr => try airBinOp(f, inst, " >> "),
- .shl => try airBinOp(f, inst, " << "),
- .shl_sat => return f.fail("TODO: C backend: implement codegen for mulsat", .{}),
-
-
- .not => try airNot( f, inst),
+ .bool_and => try airBinOp(f, inst, " & "),
+ .bool_or => try airBinOp(f, inst, " | "),
+ .bit_and => try airBinOp(f, inst, " & "),
+ .bit_or => try airBinOp(f, inst, " | "),
+ .xor => try airBinOp(f, inst, " ^ "),
+ .shr => try airBinOp(f, inst, " >> "),
+ .shl, .shl_exact => try airBinOp(f, inst, " << "),
+ .not => try airNot (f, inst),
.optional_payload => try airOptionalPayload(f, inst),
.optional_payload_ptr => try airOptionalPayload(f, inst),
@@ -1314,27 +1312,23 @@ fn airWrapOp(
return ret;
}
-fn airSatOp(
- o: *Object,
- inst: Air.Inst.Index,
- fn_op: [*:0]const u8,
-) !CValue {
- if (o.liveness.isUnused(inst))
+fn airSatOp(f: *Function, inst: Air.Inst.Index, fn_op: [*:0]const u8) !CValue {
+ if (f.liveness.isUnused(inst))
return CValue.none;
- const bin_op = o.air.instructions.items(.data)[inst].bin_op;
- const inst_ty = o.air.typeOfIndex(inst);
- const int_info = inst_ty.intInfo(o.dg.module.getTarget());
+ const bin_op = f.air.instructions.items(.data)[inst].bin_op;
+ const inst_ty = f.air.typeOfIndex(inst);
+ const int_info = inst_ty.intInfo(f.object.dg.module.getTarget());
const bits = int_info.bits;
switch (bits) {
8, 16, 32, 64, 128 => {},
- else => return o.dg.fail("TODO: C backend: airSatOp for non power of 2 integers", .{}),
+ else => return f.object.dg.fail("TODO: C backend: airSatOp for non power of 2 integers", .{}),
}
// if it's an unsigned int with non-arbitrary bit size then we can just add
if (bits > 64) {
- return o.dg.fail("TODO: C backend: airSatOp for large integers", .{});
+ return f.object.dg.fail("TODO: C backend: airSatOp for large integers", .{});
}
var min_buf: [80]u8 = undefined;
@@ -1382,11 +1376,11 @@ fn airSatOp(
},
};
- const lhs = try o.resolveInst(bin_op.lhs);
- const rhs = try o.resolveInst(bin_op.rhs);
- const w = o.writer();
+ const lhs = try f.resolveInst(bin_op.lhs);
+ const rhs = try f.resolveInst(bin_op.rhs);
+ const w = f.object.writer();
- const ret = try o.allocLocal(inst_ty, .Mut);
+ const ret = try f.allocLocal(inst_ty, .Mut);
try w.print(" = zig_{s}", .{fn_op});
switch (inst_ty.tag()) {
@@ -1412,16 +1406,16 @@ fn airSatOp(
}
try w.writeByte('(');
- try o.writeCValue(w, lhs);
+ try f.writeCValue(w, lhs);
try w.writeAll(", ");
- try o.writeCValue(w, rhs);
+ try f.writeCValue(w, rhs);
if (int_info.signedness == .signed) {
try w.print(", {s}", .{min});
}
try w.print(", {s});", .{max});
- try o.indent_writer.insertNewline();
+ try f.object.indent_writer.insertNewline();
return ret;
}
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index cdd19146b5e5..b27afa9b54a1 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -1236,27 +1236,27 @@ pub const FuncGen = struct {
for (body) |inst| {
const opt_value: ?*const llvm.Value = switch (air_tags[inst]) {
// zig fmt: off
- .add => try self.airAdd(inst, .standard),
- .addwrap => try self.airAdd(inst, .wrapping),
- .addsat => try self.airAdd(inst, .saturated),
- .sub => try self.airSub(inst, .standard),
- .subwrap => try self.airSub(inst, .wrapping),
- .subsat => try self.airSub(inst, .saturated),
- .mul => try self.airMul(inst, .standard),
- .mulwrap => try self.airMul(inst, .wrapping),
- .mulsat => try self.airMul(inst, .saturated),
- .div => try self.airDiv(inst),
- .rem => try self.airRem(inst),
- .mod => try self.airMod(inst),
- .ptr_add => try self.airPtrAdd(inst),
- .ptr_sub => try self.airPtrSub(inst),
+ .add => try self.airAdd(inst),
+ .addwrap => try self.airAddWrap(inst),
+ .add_sat => try self.airAddSat(inst),
+ .sub => try self.airSub(inst),
+ .subwrap => try self.airSubWrap(inst),
+ .sub_sat => try self.airSubSat(inst),
+ .mul => try self.airMul(inst),
+ .mulwrap => try self.airMulWrap(inst),
+ .mul_sat => try self.airMulSat(inst),
+ .div => try self.airDiv(inst),
+ .rem => try self.airRem(inst),
+ .mod => try self.airMod(inst),
+ .ptr_add => try self.airPtrAdd(inst),
+ .ptr_sub => try self.airPtrSub(inst),
+ .shl => try self.airShl(inst),
+ .shl_sat => try self.airShlSat(inst),
+ .shl_exact => try self.airShlExact(inst),
.bit_and, .bool_and => try self.airAnd(inst),
.bit_or, .bool_or => try self.airOr(inst),
.xor => try self.airXor(inst),
-
- .shl => try self.airShl(inst, false),
- .shl_sat => try self.airShl(inst, true),
.shr => try self.airShr(inst),
.cmp_eq => try self.airCmp(inst, .eq),
@@ -2028,10 +2028,8 @@ pub const FuncGen = struct {
return self.todo("implement llvm codegen for 'airWrapErrUnionErr'", .{});
}
- const ArithmeticType = enum { standard, wrapping, saturated };
- fn airAdd(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value {
- if (self.liveness.isUnused(inst))
- return null;
+ fn airAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const lhs = try self.resolveInst(bin_op.lhs);
@@ -2039,21 +2037,36 @@ pub const FuncGen = struct {
const inst_ty = self.air.typeOfIndex(inst);
if (inst_ty.isAnyFloat()) return self.builder.buildFAdd(lhs, rhs, "");
- if (ty == .wrapping)
- return self.builder.buildAdd(lhs, rhs, "")
- else if (ty == .saturated) {
- if (inst_ty.isSignedInt())
- return self.builder.buildSAddSat(lhs, rhs, "")
- else
- return self.builder.buildUAddSat(lhs, rhs, "");
- }
if (inst_ty.isSignedInt()) return self.builder.buildNSWAdd(lhs, rhs, "");
return self.builder.buildNUWAdd(lhs, rhs, "");
}
- fn airSub(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value {
- if (self.liveness.isUnused(inst))
- return null;
+ fn airAddWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+
+ return self.builder.buildAdd(lhs, rhs, "");
+ }
+
+ fn airAddSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+ const inst_ty = self.air.typeOfIndex(inst);
+
+ if (inst_ty.isAnyFloat()) return self.todo("saturating float add", .{});
+ if (inst_ty.isSignedInt()) return self.builder.buildSAddSat(lhs, rhs, "");
+
+ return self.builder.buildUAddSat(lhs, rhs, "");
+ }
+
+ fn airSub(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const lhs = try self.resolveInst(bin_op.lhs);
@@ -2061,21 +2074,35 @@ pub const FuncGen = struct {
const inst_ty = self.air.typeOfIndex(inst);
if (inst_ty.isAnyFloat()) return self.builder.buildFSub(lhs, rhs, "");
- if (ty == .wrapping)
- return self.builder.buildSub(lhs, rhs, "")
- else if (ty == .saturated) {
- if (inst_ty.isSignedInt())
- return self.builder.buildSSubSat(lhs, rhs, "")
- else
- return self.builder.buildUSubSat(lhs, rhs, "");
- }
if (inst_ty.isSignedInt()) return self.builder.buildNSWSub(lhs, rhs, "");
return self.builder.buildNUWSub(lhs, rhs, "");
}
- fn airMul(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value {
- if (self.liveness.isUnused(inst))
- return null;
+ fn airSubWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+
+ return self.builder.buildSub(lhs, rhs, "");
+ }
+
+ fn airSubSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+ const inst_ty = self.air.typeOfIndex(inst);
+
+ if (inst_ty.isAnyFloat()) return self.todo("saturating float sub", .{});
+ if (inst_ty.isSignedInt()) return self.builder.buildSSubSat(lhs, rhs, "");
+ return self.builder.buildUSubSat(lhs, rhs, "");
+ }
+
+ fn airMul(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const lhs = try self.resolveInst(bin_op.lhs);
@@ -2083,18 +2110,33 @@ pub const FuncGen = struct {
const inst_ty = self.air.typeOfIndex(inst);
if (inst_ty.isAnyFloat()) return self.builder.buildFMul(lhs, rhs, "");
- if (ty == .wrapping)
- return self.builder.buildMul(lhs, rhs, "")
- else if (ty == .saturated) {
- if (inst_ty.isSignedInt())
- return self.builder.buildSMulFixSat(lhs, rhs, "")
- else
- return self.builder.buildUMulFixSat(lhs, rhs, "");
- }
if (inst_ty.isSignedInt()) return self.builder.buildNSWMul(lhs, rhs, "");
return self.builder.buildNUWMul(lhs, rhs, "");
}
+ fn airMulWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+
+ return self.builder.buildMul(lhs, rhs, "");
+ }
+
+ fn airMulSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+ const inst_ty = self.air.typeOfIndex(inst);
+
+ if (inst_ty.isAnyFloat()) return self.todo("saturating float mul", .{});
+ if (inst_ty.isSignedInt()) return self.builder.buildSMulFixSat(lhs, rhs, "");
+ return self.builder.buildUMulFixSat(lhs, rhs, "");
+ }
+
fn airDiv(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
@@ -2200,9 +2242,25 @@ pub const FuncGen = struct {
return self.builder.buildXor(lhs, rhs, "");
}
- fn airShl(self: *FuncGen, inst: Air.Inst.Index, sat: bool) !?*const llvm.Value {
- if (self.liveness.isUnused(inst))
- return null;
+ fn airShlExact(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+ const lhs_type = self.air.typeOf(bin_op.lhs);
+ const tg = self.dg.module.getTarget();
+ const casted_rhs = if (self.air.typeOf(bin_op.rhs).bitSize(tg) < lhs_type.bitSize(tg))
+ self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "")
+ else
+ rhs;
+ if (lhs_type.isSignedInt()) return self.builder.buildNSWShl(lhs, casted_rhs, "");
+ return self.builder.buildNUWShl(lhs, casted_rhs, "");
+ }
+
+ fn airShl(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const lhs = try self.resolveInst(bin_op.lhs);
const rhs = try self.resolveInst(bin_op.rhs);
@@ -2212,15 +2270,25 @@ pub const FuncGen = struct {
self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "")
else
rhs;
- if (sat) {
- return if (lhs_type.isSignedInt())
- self.builder.buildSShlSat(lhs, casted_rhs, "")
- else
- self.builder.buildUShlSat(lhs, casted_rhs, "");
- }
return self.builder.buildShl(lhs, casted_rhs, "");
}
+ fn airShlSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+ const lhs_type = self.air.typeOf(bin_op.lhs);
+ const tg = self.dg.module.getTarget();
+ const casted_rhs = if (self.air.typeOf(bin_op.rhs).bitSize(tg) < lhs_type.bitSize(tg))
+ self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "")
+ else
+ rhs;
+ if (lhs_type.isSignedInt()) return self.builder.buildSShlSat(lhs, casted_rhs, "");
+ return self.builder.buildUShlSat(lhs, casted_rhs, "");
+ }
+
fn airShr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig
index 178c38123559..4fac6656c826 100644
--- a/src/codegen/llvm/bindings.zig
+++ b/src/codegen/llvm/bindings.zig
@@ -469,6 +469,12 @@ pub const Builder = opaque {
pub const buildShl = LLVMBuildShl;
extern fn LLVMBuildShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+ pub const buildNUWShl = ZigLLVMBuildNUWShl;
+ extern fn ZigLLVMBuildNUWShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+ pub const buildNSWShl = ZigLLVMBuildNSWShl;
+ extern fn ZigLLVMBuildNSWShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
pub const buildSShlSat = ZigLLVMBuildSShlSat;
extern fn ZigLLVMBuildSShlSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
diff --git a/src/link/C/zig.h b/src/link/C/zig.h
index 5c9d750729aa..72868e4400f3 100644
--- a/src/link/C/zig.h
+++ b/src/link/C/zig.h
@@ -356,9 +356,6 @@ static inline long long zig_subw_longlong(long long lhs, long long rhs, long lon
return (long long)(((unsigned long long)lhs) - ((unsigned long long)rhs));
}
-/*
- * Saturating aritmetic operations: add, sub, mul, shl
- */
#define zig_add_sat_u(ZT, T) static inline T zig_adds_##ZT(T x, T y, T max) { \
return (x > max - y) ? max : x + y; \
}
@@ -449,7 +446,7 @@ zig_shl_sat_u(u32, uint32_t, 32)
zig_shl_sat_s(i32, int32_t, 31)
zig_shl_sat_u(u64, uint64_t, 64)
zig_shl_sat_s(i64, int64_t, 63)
-zig_shl_sat_s(isize, intptr_t, 63)
-zig_shl_sat_s(short, short, 15)
-zig_shl_sat_s(int, int, 31)
-zig_shl_sat_s(long, long, 63)
+zig_shl_sat_s(isize, intptr_t, ((sizeof(intptr_t)) * CHAR_BIT - 1))
+zig_shl_sat_s(short, short, ((sizeof(short )) * CHAR_BIT - 1))
+zig_shl_sat_s(int, int, ((sizeof(int )) * CHAR_BIT - 1))
+zig_shl_sat_s(long, long, ((sizeof(long )) * CHAR_BIT - 1))
diff --git a/src/print_air.zig b/src/print_air.zig
index 7d178b52f38b..885c1b62bdc9 100644
--- a/src/print_air.zig
+++ b/src/print_air.zig
@@ -104,13 +104,13 @@ const Writer = struct {
.add,
.addwrap,
- .addsat,
+ .add_sat,
.sub,
.subwrap,
- .subsat,
+ .sub_sat,
.mul,
.mulwrap,
- .mulsat,
+ .mul_sat,
.div,
.rem,
.mod,
@@ -133,6 +133,7 @@ const Writer = struct {
.ptr_elem_val,
.ptr_ptr_elem_val,
.shl,
+ .shl_exact,
.shl_sat,
.shr,
.set_union_tag,
diff --git a/src/print_zir.zig b/src/print_zir.zig
index 3834a694e992..5ffd6619afde 100644
--- a/src/print_zir.zig
+++ b/src/print_zir.zig
@@ -229,12 +229,15 @@ const Writer = struct {
.add,
.addwrap,
+ .add_sat,
.array_cat,
.array_mul,
.mul,
.mulwrap,
+ .mul_sat,
.sub,
.subwrap,
+ .sub_sat,
.cmp_lt,
.cmp_lte,
.cmp_eq,
@@ -247,6 +250,7 @@ const Writer = struct {
.mod_rem,
.shl,
.shl_exact,
+ .shl_sat,
.shr,
.shr_exact,
.xor,
@@ -400,12 +404,6 @@ const Writer = struct {
.shl_with_overflow,
=> try self.writeOverflowArithmetic(stream, extended),
- .add_with_saturation,
- .sub_with_saturation,
- .mul_with_saturation,
- .shl_with_saturation,
- => try self.writeSaturatingArithmetic(stream, extended),
-
.struct_decl => try self.writeStructDecl(stream, extended),
.union_decl => try self.writeUnionDecl(stream, extended),
.enum_decl => try self.writeEnumDecl(stream, extended),
@@ -854,18 +852,6 @@ const Writer = struct {
try self.writeSrc(stream, src);
}
- fn writeSaturatingArithmetic(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void {
- const extra = self.code.extraData(Zir.Inst.SaturatingArithmetic, extended.operand).data;
- const src: LazySrcLoc = .{ .node_offset = extra.node };
-
- try self.writeInstRef(stream, extra.lhs);
- try stream.writeAll(", ");
- try self.writeInstRef(stream, extra.rhs);
- try stream.writeAll(", ");
- try stream.writeAll(") ");
- try self.writeSrc(stream, src);
- }
-
fn writePlNodeCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Zir.Inst.Call, inst_data.payload_index);
diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp
index e31a7015b09d..5b58766df9b2 100644
--- a/src/stage1/all_types.hpp
+++ b/src/stage1/all_types.hpp
@@ -1818,10 +1818,6 @@ enum BuiltinFnId {
BuiltinFnIdReduce,
BuiltinFnIdMaximum,
BuiltinFnIdMinimum,
- BuiltinFnIdSatAdd,
- BuiltinFnIdSatSub,
- BuiltinFnIdSatMul,
- BuiltinFnIdSatShl,
};
struct BuiltinFnEntry {
diff --git a/src/stage1/astgen.cpp b/src/stage1/astgen.cpp
index 14808dd0a271..8fbd02c6887f 100644
--- a/src/stage1/astgen.cpp
+++ b/src/stage1/astgen.cpp
@@ -4720,66 +4720,6 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast
Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpMaximum, arg0_value, arg1_value, true);
return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
}
- case BuiltinFnIdSatAdd:
- {
- AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
- Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
- if (arg0_value == ag->codegen->invalid_inst_src)
- return arg0_value;
-
- AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
- Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
- if (arg1_value == ag->codegen->invalid_inst_src)
- return arg1_value;
-
- Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpAddSat, arg0_value, arg1_value, true);
- return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
- }
- case BuiltinFnIdSatSub:
- {
- AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
- Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
- if (arg0_value == ag->codegen->invalid_inst_src)
- return arg0_value;
-
- AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
- Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
- if (arg1_value == ag->codegen->invalid_inst_src)
- return arg1_value;
-
- Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSubSat, arg0_value, arg1_value, true);
- return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
- }
- case BuiltinFnIdSatMul:
- {
- AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
- Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
- if (arg0_value == ag->codegen->invalid_inst_src)
- return arg0_value;
-
- AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
- Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
- if (arg1_value == ag->codegen->invalid_inst_src)
- return arg1_value;
-
- Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpMultSat, arg0_value, arg1_value, true);
- return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
- }
- case BuiltinFnIdSatShl:
- {
- AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
- Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
- if (arg0_value == ag->codegen->invalid_inst_src)
- return arg0_value;
-
- AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
- Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
- if (arg1_value == ag->codegen->invalid_inst_src)
- return arg1_value;
-
- Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpShlSat, arg0_value, arg1_value, true);
- return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
- }
case BuiltinFnIdMemcpy:
{
AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp
index eade84335495..a0f130b79e15 100644
--- a/src/stage1/codegen.cpp
+++ b/src/stage1/codegen.cpp
@@ -9134,10 +9134,6 @@ static void define_builtin_fns(CodeGen *g) {
create_builtin_fn(g, BuiltinFnIdReduce, "reduce", 2);
create_builtin_fn(g, BuiltinFnIdMaximum, "maximum", 2);
create_builtin_fn(g, BuiltinFnIdMinimum, "minimum", 2);
- create_builtin_fn(g, BuiltinFnIdSatAdd, "addWithSaturation", 2);
- create_builtin_fn(g, BuiltinFnIdSatSub, "subWithSaturation", 2);
- create_builtin_fn(g, BuiltinFnIdSatMul, "mulWithSaturation", 2);
- create_builtin_fn(g, BuiltinFnIdSatShl, "shlWithSaturation", 2);
}
static const char *bool_to_str(bool b) {
diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig
index d0fe6d1b31a9..dbd9367d1abe 100644
--- a/src/translate_c/ast.zig
+++ b/src/translate_c/ast.zig
@@ -1462,10 +1462,10 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
.mul_wrap_assign => return renderBinOp(c, node, .assign_mul_wrap, .asterisk_percent_equal, "*%="),
.div => return renderBinOpGrouped(c, node, .div, .slash, "/"),
.div_assign => return renderBinOp(c, node, .assign_div, .slash_equal, "/="),
- .shl => return renderBinOpGrouped(c, node, .bit_shift_left, .angle_bracket_angle_bracket_left, "<<"),
- .shl_assign => return renderBinOp(c, node, .assign_bit_shift_left, .angle_bracket_angle_bracket_left_equal, "<<="),
- .shr => return renderBinOpGrouped(c, node, .bit_shift_right, .angle_bracket_angle_bracket_right, ">>"),
- .shr_assign => return renderBinOp(c, node, .assign_bit_shift_right, .angle_bracket_angle_bracket_right_equal, ">>="),
+ .shl => return renderBinOpGrouped(c, node, .shl, .angle_bracket_angle_bracket_left, "<<"),
+ .shl_assign => return renderBinOp(c, node, .assign_shl, .angle_bracket_angle_bracket_left_equal, "<<="),
+ .shr => return renderBinOpGrouped(c, node, .shr, .angle_bracket_angle_bracket_right, ">>"),
+ .shr_assign => return renderBinOp(c, node, .assign_shr, .angle_bracket_angle_bracket_right_equal, ">>="),
.mod => return renderBinOpGrouped(c, node, .mod, .percent, "%"),
.mod_assign => return renderBinOp(c, node, .assign_mod, .percent_equal, "%="),
.@"and" => return renderBinOpGrouped(c, node, .bool_and, .keyword_and, "and"),
diff --git a/src/value.zig b/src/value.zig
index 29d8fa8db902..73a2b3a49f0c 100644
--- a/src/value.zig
+++ b/src/value.zig
@@ -1588,6 +1588,35 @@ pub const Value = extern union {
return result;
}
+ /// Supports both floats and ints; handles undefined.
+ pub fn numberAddSat(
+ lhs: Value,
+ rhs: Value,
+ ty: Type,
+ arena: *Allocator,
+ target: Target,
+ ) !Value {
+ if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
+
+ if (ty.isAnyFloat()) {
+ // TODO: handle outside float range
+ return floatAdd(lhs, rhs, ty, arena);
+ }
+ const result = try intAdd(lhs, rhs, arena);
+
+ const max = try ty.maxInt(arena, target);
+ if (compare(result, .gt, max, ty)) {
+ return max;
+ }
+
+ const min = try ty.minInt(arena, target);
+ if (compare(result, .lt, min, ty)) {
+ return min;
+ }
+
+ return result;
+ }
+
/// Supports both floats and ints; handles undefined.
pub fn numberSubWrap(
lhs: Value,
@@ -1616,6 +1645,35 @@ pub const Value = extern union {
return result;
}
+ /// Supports both floats and ints; handles undefined.
+ pub fn numberSubSat(
+ lhs: Value,
+ rhs: Value,
+ ty: Type,
+ arena: *Allocator,
+ target: Target,
+ ) !Value {
+ if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
+
+ if (ty.isAnyFloat()) {
+ // TODO: handle outside float range
+ return floatSub(lhs, rhs, ty, arena);
+ }
+ const result = try intSub(lhs, rhs, arena);
+
+ const max = try ty.maxInt(arena, target);
+ if (compare(result, .gt, max, ty)) {
+ return max;
+ }
+
+ const min = try ty.minInt(arena, target);
+ if (compare(result, .lt, min, ty)) {
+ return min;
+ }
+
+ return result;
+ }
+
/// Supports both floats and ints; handles undefined.
pub fn numberMulWrap(
lhs: Value,
@@ -1644,6 +1702,35 @@ pub const Value = extern union {
return result;
}
+ /// Supports both floats and ints; handles undefined.
+ pub fn numberMulSat(
+ lhs: Value,
+ rhs: Value,
+ ty: Type,
+ arena: *Allocator,
+ target: Target,
+ ) !Value {
+ if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
+
+ if (ty.isAnyFloat()) {
+ // TODO: handle outside float range
+ return floatMul(lhs, rhs, ty, arena);
+ }
+ const result = try intMul(lhs, rhs, arena);
+
+ const max = try ty.maxInt(arena, target);
+ if (compare(result, .gt, max, ty)) {
+ return max;
+ }
+
+ const min = try ty.minInt(arena, target);
+ if (compare(result, .lt, min, ty)) {
+ return min;
+ }
+
+ return result;
+ }
+
/// Supports both floats and ints; handles undefined.
pub fn numberMax(lhs: Value, rhs: Value, arena: *Allocator) !Value {
if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
diff --git a/test/behavior/saturating_arithmetic.zig b/test/behavior/saturating_arithmetic.zig
index 5d7a229c3c9a..91f9c17fb9d7 100644
--- a/test/behavior/saturating_arithmetic.zig
+++ b/test/behavior/saturating_arithmetic.zig
@@ -32,7 +32,7 @@ fn testSaturatingOp(comptime op: Op, comptime T: type, test_data: [3]T) !void {
}
}
-test "@addWithSaturation" {
+test "saturating add" {
const S = struct {
fn doTheTest() !void {
// .{a, b, expected a+b}
@@ -50,22 +50,16 @@ test "@addWithSaturation" {
try testSaturatingOp(.add, u128, .{ maxInt(u128), 1, maxInt(u128) });
const u8x3 = std.meta.Vector(3, u8);
- try expectEqual(u8x3{ 255, 255, 255 }, @addWithSaturation(
- u8x3{ 255, 254, 1 },
- u8x3{ 1, 2, 255 },
- ));
+ try expectEqual(u8x3{ 255, 255, 255 }, (u8x3{ 255, 254, 1 } +| u8x3{ 1, 2, 255 }));
const i8x3 = std.meta.Vector(3, i8);
- try expectEqual(i8x3{ 127, 127, 127 }, @addWithSaturation(
- i8x3{ 127, 126, 1 },
- i8x3{ 1, 2, 127 },
- ));
+ try expectEqual(i8x3{ 127, 127, 127 }, (i8x3{ 127, 126, 1 } +| i8x3{ 1, 2, 127 }));
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
-test "@subWithSaturation" {
+test "saturating subtraction" {
const S = struct {
fn doTheTest() !void {
// .{a, b, expected a-b}
@@ -81,17 +75,14 @@ test "@subWithSaturation" {
try testSaturatingOp(.sub, u128, .{ 0, maxInt(u128), 0 });
const u8x3 = std.meta.Vector(3, u8);
- try expectEqual(u8x3{ 0, 0, 0 }, @subWithSaturation(
- u8x3{ 0, 0, 0 },
- u8x3{ 255, 255, 255 },
- ));
+ try expectEqual(u8x3{ 0, 0, 0 }, (u8x3{ 0, 0, 0 } -| u8x3{ 255, 255, 255 }));
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
-test "@mulWithSaturation" {
+test "saturating multiplication" {
// TODO: once #9660 has been solved, remove this line
if (std.builtin.target.cpu.arch == .wasm32) return error.SkipZigTest;
@@ -112,10 +103,7 @@ test "@mulWithSaturation" {
try testSaturatingOp(.mul, u128, .{ maxInt(u128), maxInt(u128), maxInt(u128) });
const u8x3 = std.meta.Vector(3, u8);
- try expectEqual(u8x3{ 255, 255, 255 }, @mulWithSaturation(
- u8x3{ 2, 2, 2 },
- u8x3{ 255, 255, 255 },
- ));
+ try expectEqual(u8x3{ 255, 255, 255 }, (u8x3{ 2, 2, 2 } *| u8x3{ 255, 255, 255 }));
}
};
@@ -123,7 +111,7 @@ test "@mulWithSaturation" {
comptime try S.doTheTest();
}
-test "@shlWithSaturation" {
+test "saturating shift-left" {
const S = struct {
fn doTheTest() !void {
// .{a, b, expected a< | |