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/doc/langref.html.in b/doc/langref.html.in index a5dfa5c9278a..2e69e3709720 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,23 @@ a +%= b{#endsyntax#}
{#syntax#}@as(u32, std.math.maxInt(u32)) +% 1 == 0{#endsyntax#}+
{#syntax#}a +| b +a +|= b{#endsyntax#}
{#syntax#}@as(u32, std.math.maxInt(u32)) +| 1 == @as(u32, std.math.maxInt(u32)){#endsyntax#}+
{#syntax#}a - b a -= b{#endsyntax#}
{#syntax#}@as(u32, 0) -% 1 == std.math.maxInt(u32){#endsyntax#}
{#syntax#}a -| b +a -|= b{#endsyntax#}
{#syntax#}@as(u32, 0) -| 1 == 0{#endsyntax#}+
{#syntax#}-a{#endsyntax#}
{#syntax#}@as(u8, 200) *% 2 == 144{#endsyntax#}
{#syntax#}a *| b +a *|= b{#endsyntax#}
{#syntax#}@as(u8, 200) *| 2 == 255{#endsyntax#}+
{#syntax#}a / b a /= b{#endsyntax#}
{#syntax#}1 << 8 == 256{#endsyntax#}
{#syntax#}a <<| b +a <<|= b{#endsyntax#}
{#syntax#}@as(u8, 1) <<| 8 == 255{#endsyntax#}+
{#syntax#}a >> b a >>= b{#endsyntax#}
{#syntax#}@addWithSaturation(a: T, b: T) T{#endsyntax#}-
- 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#}. -
- {#header_close#} {#header_open|@alignCast#}{#syntax#}@alignCast(comptime alignment: u29, ptr: anytype) anytype{#endsyntax#}
@@ -8293,22 +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. -
-- Once Saturating arithmetic. - is completed, the syntax {#syntax#}a *| b{#endsyntax#} will be 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#}
@@ -8526,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#} @@ -8547,24 +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. -
-- Once Saturating arithmetic. - is completed, the syntax {#syntax#}a <<| b{#endsyntax#} will be 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#}
@@ -8575,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#} @@ -8875,17 +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. -
-- Once Saturating arithmetic. - is completed, the syntax {#syntax#}a -| b{#endsyntax#} will be equivalent to calling {#syntax#}@subWithSaturation(a, b){#endsyntax#}. -
- {#header_close#} - {#header_open|@tagName#}{#syntax#}@tagName(value: anytype) [:0]const u8{#endsyntax#}
@@ -11839,6 +11859,7 @@ AssignOp / PLUSEQUAL / MINUSEQUAL / LARROW2EQUAL + / LARROW2PIPEEQUAL / RARROW2EQUAL / AMPERSANDEQUAL / CARETEQUAL @@ -11873,6 +11894,8 @@ AdditionOp / PLUS2 / PLUSPERCENT / MINUSPERCENT + / PLUSPIPE + / MINUSPIPE MultiplyOp <- PIPE2 @@ -11881,6 +11904,7 @@ MultiplyOp / PERCENT / ASTERISK2 / ASTERISKPERCENT + / ASTERISKPIPE PrefixOp <- EXCLAMATIONMARK @@ -12044,6 +12068,8 @@ ASTERISK2 <- '**' skip ASTERISKEQUAL <- '*=' skip ASTERISKPERCENT <- '*%' ![=] skip ASTERISKPERCENTEQUAL <- '*%=' skip +ASTERISKPIPE <- '*|' ![=] skip +ASTERISKPIPEEQUAL <- '*|=' skip CARET <- '^' ![=] skip CARETEQUAL <- '^=' skip COLON <- ':' skip @@ -12060,6 +12086,8 @@ EXCLAMATIONMARK <- '!' ![=] skip EXCLAMATIONMARKEQUAL <- '!=' skip LARROW <- '<' ![<=] skip LARROW2 <- '<<' ![=] skip +LARROW2PIPE <- '<<|' ![=] skip +LARROW2PIPEEQUAL <- '<<|=' ![=] skip LARROW2EQUAL <- '<<=' skip LARROWEQUAL <- '<=' skip LBRACE <- '{' skip @@ -12069,6 +12097,8 @@ MINUS <- '-' ![%=>] skip MINUSEQUAL <- '-=' skip MINUSPERCENT <- '-%' ![=] skip MINUSPERCENTEQUAL <- '-%=' skip +MINUSPIPE <- '-|' ![=] skip +MINUSPIPEEQUAL <- '-|=' skip MINUSRARROW <- '->' skip PERCENT <- '%' ![=] skip PERCENTEQUAL <- '%=' skip @@ -12080,6 +12110,8 @@ PLUS2 <- '++' skip PLUSEQUAL <- '+=' skip PLUSPERCENT <- '+%' ![=] skip PLUSPERCENTEQUAL <- '+%=' skip +PLUSPIPE <- '+|' ![=] skip +PLUSPIPEEQUAL <- '+|=' skip LETTERC <- 'c' skip QUESTIONMARK <- '?' skip RARROW <- '>' ![>=] skip diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 5838dcd37ac5..4ee3a4522156 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -395,14 +395,18 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex { .assign_mod, .assign_add, .assign_sub, - .assign_bit_shift_left, - .assign_bit_shift_right, + .assign_shl, + .assign_shl_sat, + .assign_shr, .assign_bit_and, .assign_bit_xor, .assign_bit_or, .assign_mul_wrap, .assign_add_wrap, .assign_sub_wrap, + .assign_mul_sat, + .assign_add_sat, + .assign_sub_sat, .assign, .merge_error_sets, .mul, @@ -410,13 +414,17 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex { .mod, .array_mult, .mul_wrap, + .mul_sat, .add, .sub, .array_cat, .add_wrap, .sub_wrap, - .bit_shift_left, - .bit_shift_right, + .add_sat, + .sub_sat, + .shl, + .shl_sat, + .shr, .bit_and, .bit_xor, .bit_or, @@ -651,14 +659,18 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex { .assign_mod, .assign_add, .assign_sub, - .assign_bit_shift_left, - .assign_bit_shift_right, + .assign_shl, + .assign_shl_sat, + .assign_shr, .assign_bit_and, .assign_bit_xor, .assign_bit_or, .assign_mul_wrap, .assign_add_wrap, .assign_sub_wrap, + .assign_mul_sat, + .assign_add_sat, + .assign_sub_sat, .assign, .merge_error_sets, .mul, @@ -666,13 +678,17 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex { .mod, .array_mult, .mul_wrap, + .mul_sat, .add, .sub, .array_cat, .add_wrap, .sub_wrap, - .bit_shift_left, - .bit_shift_right, + .add_sat, + .sub_sat, + .shl, + .shl_sat, + .shr, .bit_and, .bit_xor, .bit_or, @@ -2524,9 +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_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. @@ -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,10 +2589,16 @@ 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, + shl, + /// `lhs <<| rhs`. main_token is the `<<|`. + 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 f7697027a3e6..021b02845583 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -1268,14 +1268,18 @@ 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_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, .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, }; @@ -1342,14 +1346,17 @@ 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_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 }, .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/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) { diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 3029d38cb927..43579602515a 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -333,27 +333,33 @@ 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_right, + .assign_shl, + .assign_shl_sat, + .assign_shr, .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_right, + .shl, + .shl_sat, + .shr, .bit_xor, .bool_and, .bool_or, @@ -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]; @@ -2520,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, @@ -2534,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/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index 3ef6c9a6ba72..02fa3dd381f5 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; }, + '|' => { + state = .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; @@ -1936,6 +2015,24 @@ 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}); + + 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 { var tokenizer = Tokenizer.init(source); for (expected_tokens) |expected_token_id| { diff --git a/src/Air.zig b/src/Air.zig index b5d19127a0c3..f05c18e87a99 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. + 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. @@ -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. + 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. @@ -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. + 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. @@ -110,6 +125,14 @@ 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, /// Bitwise XOR. `^` /// Uses the `bin_op` field. xor, @@ -568,10 +591,13 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .add, .addwrap, + .add_sat, .sub, .subwrap, + .sub_sat, .mul, .mulwrap, + .mul_sat, .div, .rem, .mod, @@ -582,6 +608,8 @@ 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), .cmp_lt, diff --git a/src/AstGen.zig b/src/AstGen.zig index 15594ac27c34..847860630a10 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -317,29 +317,37 @@ 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_right, + .assign_shl, + .assign_shl_sat, + .assign_shr, .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_right, + .shl, + .shl_sat, + .shr, .bit_xor, .bang_equal, .equal_equal, @@ -522,11 +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_right => { + .assign_shl_sat => { + try assignShiftSat(gz, scope, node); + return rvalue(gz, rl, .void_value, node); + }, + .assign_shr => { 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 assignOp(gz, scope, node, .sub_sat); + 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 assignOp(gz, scope, node, .add_sat); + return rvalue(gz, rl, .void_value, node); + }, .assign_mul => { try assignOp(gz, scope, node, .mul); return rvalue(gz, rl, .void_value, node); @@ -575,19 +595,28 @@ 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 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), + .bit_and => { const current_ampersand_token = main_tokens[node]; if (token_tags[current_ampersand_token + 1] == .ampersand) { @@ -1898,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), @@ -1949,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, @@ -2015,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, @@ -2708,6 +2741,24 @@ fn assignShift( _ = try gz.addBin(.store, lhs_ptr, result); } +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; + 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); + // 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(.shl_sat, 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; @@ -7483,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 @@ -7882,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, @@ -8119,27 +8147,33 @@ 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_right, + .assign_shl, + .assign_shl_sat, + .assign_shr, .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_right, + .shl, + .shl_sat, + .shr, .bit_xor, .bool_and, .bool_or, @@ -8154,10 +8188,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,27 +8388,33 @@ 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_right, + .assign_shl, + .assign_shl_sat, + .assign_shr, .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_right, + .shl, + .shl_sat, + .shr, .bit_xor, .bool_and, .bool_or, @@ -8387,9 +8429,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,27 +8568,33 @@ 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_right, + .assign_shl, + .assign_shl_sat, + .assign_shr, .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_right, + .shl, + .shl_sat, + .shr, .bit_xor, .bool_and, .bool_or, @@ -8559,10 +8609,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/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 25dd29b0f657..93f28ad7b223 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -226,10 +226,13 @@ fn analyzeInst( switch (inst_tags[inst]) { .add, .addwrap, + .add_sat, .sub, .subwrap, + .sub_sat, .mul, .mulwrap, + .mul_sat, .div, .rem, .mod, @@ -252,6 +255,8 @@ fn analyzeInst( .ptr_elem_val, .ptr_ptr_elem_val, .shl, + .shl_exact, + .shl_sat, .shr, .atomic_store_unordered, .atomic_store_monotonic, diff --git a/src/Sema.zig b/src/Sema.zig index de94a8c6b892..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,10 +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=> 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), // zig fmt: on } } @@ -5874,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(); @@ -5885,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); @@ -5900,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| { @@ -5908,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 { @@ -6200,19 +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; - const src: LazySrcLoc = .{ .node_offset = extra.node }; - return sema.mod.fail(&block.base, src, "TODO implement Sema.zirSatArithmetic", .{}); -} - fn analyzeArithmetic( sema: *Sema, block: *Scope.Block, @@ -6354,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)) { @@ -6377,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 @@ -6444,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. @@ -6562,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)) { @@ -6597,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 @@ -7846,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, }; } @@ -8513,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 7c359e90c02a..79105dc4a785 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -824,15 +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), - .sub, .ptr_sub => try self.airSub(inst), - .subwrap => try self.airSubWrap(inst), - .mul => try self.airMul(inst), - .mulwrap => try self.airMulWrap(inst), - .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), @@ -847,7 +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), .alloc => try self.airAlloc(inst), .arg => try self.airArg(inst), @@ -1302,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) { @@ -1320,6 +1332,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } + 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 sub_sat 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) { @@ -1338,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) { @@ -1400,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 4964f17cd341..95ce95f2e593 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -883,22 +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_"), + .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_"), + .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_"), + .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, " > "), @@ -908,16 +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, " << "), - - .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), @@ -1309,6 +1312,114 @@ fn airWrapOp( return ret; } +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 = 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 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 f.object.dg.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: { + // 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, + 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 f.resolveInst(bin_op.lhs); + const rhs = try f.resolveInst(bin_op.rhs); + const w = f.object.writer(); + + const ret = try f.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 f.writeCValue(w, lhs); + try w.writeAll(", "); + try f.writeCValue(w, rhs); + + if (int_info.signedness == .signed) { + try w.print(", {s}", .{min}); + } + + try w.print(", {s});", .{max}); + try f.object.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 f65e0f6d723b..b27afa9b54a1 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1236,23 +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, 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), - .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), .shr => try self.airShr(inst), .cmp_eq => try self.airCmp(inst, .eq), @@ -2024,51 +2028,115 @@ 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 { - 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); 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.isAnyFloat()) return self.builder.buildFAdd(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 { - 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); 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.isAnyFloat()) return self.builder.buildFSub(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 { - 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.isRuntimeFloat()) return self.builder.buildFMul(lhs, rhs, ""); - if (wrap) return self.builder.buildMul(lhs, rhs, ""); + 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); + const rhs = try self.resolveInst(bin_op.rhs); + const inst_ty = self.air.typeOfIndex(inst); + + if (inst_ty.isAnyFloat()) return self.builder.buildFMul(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; @@ -2174,9 +2242,25 @@ pub const FuncGen = struct { return self.builder.buildXor(lhs, rhs, ""); } + 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; + 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); @@ -2189,6 +2273,22 @@ pub const FuncGen = struct { 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 9d32682260a3..4fac6656c826 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,18 @@ 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; + + 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/link/C/zig.h b/src/link/C/zig.h index b34068d1f247..72868e4400f3 100644 --- a/src/link/C/zig.h +++ b/src/link/C/zig.h @@ -356,3 +356,97 @@ 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)); } +#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) { \ + 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) { \ + 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(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, ((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 90df06760bfb..885c1b62bdc9 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -104,10 +104,13 @@ const Writer = struct { .add, .addwrap, + .add_sat, .sub, .subwrap, + .sub_sat, .mul, .mulwrap, + .mul_sat, .div, .rem, .mod, @@ -130,6 +133,8 @@ const Writer = struct { .ptr_elem_val, .ptr_ptr_elem_val, .shl, + .shl_exact, + .shl_sat, .shr, .set_union_tag, => try w.writeBinOp(s, inst), 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 13c37fc8399c..5b58766df9b2 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, @@ -1810,10 +1818,6 @@ enum BuiltinFnId { BuiltinFnIdReduce, BuiltinFnIdMaximum, BuiltinFnIdMinimum, - BuiltinFnIdSatAdd, - BuiltinFnIdSatSub, - BuiltinFnIdSatMul, - BuiltinFnIdSatShl, }; struct BuiltinFnEntry { @@ -2958,10 +2962,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..8fbd02c6887f 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: @@ -4704,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, IrBinOpSatAdd, 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, IrBinOpSatSub, 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, IrBinOpSatMul, 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, IrBinOpSatShl, 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 f84847a9fef9..a0f130b79e15 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, ""); @@ -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/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..47e324c933e3 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,30 @@ void tokenize(const char *source, Tokenization *out) { t.out->ids.last() = TokenIdBitShiftLeftEq; t.state = TokenizeState_start; break; + case '|': + 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 +1513,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 +1599,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 +1624,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/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 553e9ff21acd..91f9c17fb9d7 100644 --- a/test/behavior/saturating_arithmetic.zig +++ b/test/behavior/saturating_arithmetic.zig @@ -11,16 +11,28 @@ 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, + .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" { +test "saturating add" { const S = struct { fn doTheTest() !void { // .{a, b, expected a+b} @@ -38,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} @@ -69,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; @@ -100,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 })); } }; @@ -111,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<