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#}
+ +
    +
  • {#link|Integers#}
  • +
+ + 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#}
+ +
    +
  • {#link|Integers#}
  • +
+ + 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#}
+ +
    +
  • {#link|Integers#}
  • +
+ + 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#}
+ +
    +
  • {#link|Integers#}
  • +
+ + 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<