mirror of
https://github.com/ziglang/zig.git
synced 2025-12-28 09:03:21 +00:00
Merge pull request #9679 from travisstaloch/sat-arith-operators
sat-arithmetic: add operator support
This commit is contained in:
commit
cf90cb7218
@ -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,
|
||||
|
||||
@ -1244,8 +1244,9 @@ fn divide(a: i32, b: i32) i32 {
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
Zig supports arbitrary bit-width integers, referenced by using
|
||||
@ -1395,6 +1396,23 @@ a +%= b{#endsyntax#}</pre></th>
|
||||
<pre>{#syntax#}@as(u32, std.math.maxInt(u32)) +% 1 == 0{#endsyntax#}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><pre>{#syntax#}a +| b
|
||||
a +|= b{#endsyntax#}</pre></th>
|
||||
<td>
|
||||
<ul>
|
||||
<li>{#link|Integers#}</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>Saturating Addition.
|
||||
<ul>
|
||||
<li>Invokes {#link|Peer Type Resolution#} for the operands.</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<pre>{#syntax#}@as(u32, std.math.maxInt(u32)) +| 1 == @as(u32, std.math.maxInt(u32)){#endsyntax#}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><pre>{#syntax#}a - b
|
||||
a -= b{#endsyntax#}</pre></th>
|
||||
@ -1434,6 +1452,23 @@ a -%= b{#endsyntax#}</pre></th>
|
||||
<pre>{#syntax#}@as(u32, 0) -% 1 == std.math.maxInt(u32){#endsyntax#}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><pre>{#syntax#}a -| b
|
||||
a -|= b{#endsyntax#}</pre></th>
|
||||
<td>
|
||||
<ul>
|
||||
<li>{#link|Integers#}</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>Saturating Subtraction.
|
||||
<ul>
|
||||
<li>Invokes {#link|Peer Type Resolution#} for the operands.</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<pre>{#syntax#}@as(u32, 0) -| 1 == 0{#endsyntax#}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><pre>{#syntax#}-a{#endsyntax#}</pre></th>
|
||||
<td>
|
||||
@ -1508,6 +1543,23 @@ a *%= b{#endsyntax#}</pre></th>
|
||||
<pre>{#syntax#}@as(u8, 200) *% 2 == 144{#endsyntax#}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><pre>{#syntax#}a *| b
|
||||
a *|= b{#endsyntax#}</pre></th>
|
||||
<td>
|
||||
<ul>
|
||||
<li>{#link|Integers#}</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>Saturating Multiplication.
|
||||
<ul>
|
||||
<li>Invokes {#link|Peer Type Resolution#} for the operands.</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<pre>{#syntax#}@as(u8, 200) *| 2 == 255{#endsyntax#}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><pre>{#syntax#}a / b
|
||||
a /= b{#endsyntax#}</pre></th>
|
||||
@ -1577,6 +1629,24 @@ a <<= b{#endsyntax#}</pre></th>
|
||||
<pre>{#syntax#}1 << 8 == 256{#endsyntax#}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><pre>{#syntax#}a <<| b
|
||||
a <<|= b{#endsyntax#}</pre></th>
|
||||
<td>
|
||||
<ul>
|
||||
<li>{#link|Integers#}</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>Saturating Bit Shift Left.
|
||||
<ul>
|
||||
<li>See also {#link|@shlExact#}.</li>
|
||||
<li>See also {#link|@shlWithOverflow#}.</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<pre>{#syntax#}@as(u8, 1) <<| 8 == 255{#endsyntax#}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><pre>{#syntax#}a >> b
|
||||
a >>= b{#endsyntax#}</pre></th>
|
||||
@ -1968,14 +2038,14 @@ const B = error{Two};
|
||||
a!b
|
||||
x{}
|
||||
!x -x -%x ~x &x ?x
|
||||
* / % ** *% ||
|
||||
+ - ++ +% -%
|
||||
<< >>
|
||||
* / % ** *% *| ||
|
||||
+ - ++ +% -% +| -|
|
||||
<< >> <<|
|
||||
& ^ | orelse catch
|
||||
== != < > <= >=
|
||||
and
|
||||
or
|
||||
= *= /= %= += -= <<= >>= &= ^= |={#endsyntax#}</pre>
|
||||
= *= *%= *|= /= %= += +%= +|= -= -%= -|= <<= <<|= >>= &= ^= |={#endsyntax#}</pre>
|
||||
{#header_close#}
|
||||
{#header_close#}
|
||||
{#header_open|Arrays#}
|
||||
@ -7162,16 +7232,6 @@ fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 {
|
||||
If no overflow or underflow occurs, returns {#syntax#}false{#endsyntax#}.
|
||||
</p>
|
||||
{#header_close#}
|
||||
{#header_open|@addWithSaturation#}
|
||||
<pre>{#syntax#}@addWithSaturation(a: T, b: T) T{#endsyntax#}</pre>
|
||||
<p>
|
||||
Returns {#syntax#}a + b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
|
||||
</p>
|
||||
<p>
|
||||
Once <a href="https://github.com/ziglang/zig/issues/1284">Saturating arithmetic</a>.
|
||||
is completed, the syntax {#syntax#}a +| b{#endsyntax#} will be equivalent to calling {#syntax#}@addWithSaturation(a, b){#endsyntax#}.
|
||||
</p>
|
||||
{#header_close#}
|
||||
{#header_open|@alignCast#}
|
||||
<pre>{#syntax#}@alignCast(comptime alignment: u29, ptr: anytype) anytype{#endsyntax#}</pre>
|
||||
<p>
|
||||
@ -8293,22 +8353,6 @@ test "@wasmMemoryGrow" {
|
||||
</p>
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@mulWithSaturation#}
|
||||
<pre>{#syntax#}@mulWithSaturation(a: T, b: T) T{#endsyntax#}</pre>
|
||||
<p>
|
||||
Returns {#syntax#}a * b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
|
||||
</p>
|
||||
<p>
|
||||
Once <a href="https://github.com/ziglang/zig/issues/1284">Saturating arithmetic</a>.
|
||||
is completed, the syntax {#syntax#}a *| b{#endsyntax#} will be equivalent to calling {#syntax#}@mulWithSaturation(a, b){#endsyntax#}.
|
||||
</p>
|
||||
<p>
|
||||
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 <a href="https://github.com/ziglang/zig/issues/9643">this issue</a> for more information.
|
||||
</p>
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@panic#}
|
||||
<pre>{#syntax#}@panic(message: []const u8) noreturn{#endsyntax#}</pre>
|
||||
<p>
|
||||
@ -8526,14 +8570,16 @@ test "@setRuntimeSafety" {
|
||||
{#header_open|@shlExact#}
|
||||
<pre>{#syntax#}@shlExact(value: T, shift_amt: Log2T) T{#endsyntax#}</pre>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
{#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.
|
||||
</p>
|
||||
{#see_also|@shlExact|@shrExact|@shlWithSaturation#}
|
||||
{#see_also|@shlExact|@shrExact#}
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@shlWithSaturation#}
|
||||
<pre>{#syntax#}@shlWithSaturation(a: T, shift_amt: T) T{#endsyntax#}</pre>
|
||||
<p>
|
||||
Returns {#syntax#}a << b{#endsyntax#}. The result will be clamped between type minimum and maximum.
|
||||
</p>
|
||||
<p>
|
||||
Once <a href="https://github.com/ziglang/zig/issues/1284">Saturating arithmetic</a>.
|
||||
is completed, the syntax {#syntax#}a <<| b{#endsyntax#} will be equivalent to calling {#syntax#}@shlWithSaturation(a, b){#endsyntax#}.
|
||||
</p>
|
||||
<p>
|
||||
Unlike other @shl builtins, shift_amt doesn't need to be a Log2T as saturated overshifting is well defined.
|
||||
</p>
|
||||
{#see_also|@shlExact|@shrExact|@shlWithOverflow#}
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@shrExact#}
|
||||
<pre>{#syntax#}@shrExact(value: T, shift_amt: Log2T) T{#endsyntax#}</pre>
|
||||
<p>
|
||||
@ -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.
|
||||
</p>
|
||||
{#see_also|@shlExact|@shlWithOverflow|@shlWithSaturation#}
|
||||
{#see_also|@shlExact|@shlWithOverflow#}
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@shuffle#}
|
||||
@ -8875,17 +8906,6 @@ fn doTheTest() !void {
|
||||
</p>
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@subWithSaturation#}
|
||||
<pre>{#syntax#}@subWithSaturation(a: T, b: T) T{#endsyntax#}</pre>
|
||||
<p>
|
||||
Returns {#syntax#}a - b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
|
||||
</p>
|
||||
<p>
|
||||
Once <a href="https://github.com/ziglang/zig/issues/1284">Saturating arithmetic</a>.
|
||||
is completed, the syntax {#syntax#}a -| b{#endsyntax#} will be equivalent to calling {#syntax#}@subWithSaturation(a, b){#endsyntax#}.
|
||||
</p>
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@tagName#}
|
||||
<pre>{#syntax#}@tagName(value: anytype) [:0]const u8{#endsyntax#}</pre>
|
||||
<p>
|
||||
@ -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
|
||||
|
||||
@ -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 `^`.
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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| {
|
||||
|
||||
28
src/Air.zig
28
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,
|
||||
|
||||
142
src/AstGen.zig
142
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,
|
||||
|
||||
@ -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",
|
||||
.{
|
||||
|
||||
@ -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,
|
||||
|
||||
147
src/Sema.zig
147
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();
|
||||
|
||||
143
src/Zir.zig
143
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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.isRuntimeFloat()) return self.builder.buildFSub(lhs, rhs, "");
|
||||
if (wrap) return self.builder.buildSub(lhs, rhs, "");
|
||||
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.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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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"),
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<<b}
|
||||
@ -128,10 +128,7 @@ test "@shlWithSaturation" {
|
||||
try testSaturatingOp(.shl, u8, .{ 255, 1, 255 });
|
||||
|
||||
const u8x3 = std.meta.Vector(3, u8);
|
||||
try expectEqual(u8x3{ 255, 255, 255 }, @shlWithSaturation(
|
||||
u8x3{ 255, 255, 255 },
|
||||
u8x3{ 1, 1, 1 },
|
||||
));
|
||||
try expectEqual(u8x3{ 255, 255, 255 }, (u8x3{ 255, 255, 255 } <<| u8x3{ 1, 1, 1 }));
|
||||
}
|
||||
};
|
||||
try S.doTheTest();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user