-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
618 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,321 @@ | ||
const.SIGN_BIT=2147483648 # 1 << 31 | ||
const.MIN=SIGN_BIT | ||
const.NEG1=4294967295 # u32::MAX | ||
|
||
# Returns `1` if `a` has its sign bit set, else `0` | ||
# | ||
# This function consumes `a`. | ||
export.is_signed # [a] | ||
push.SIGN_BIT u32checked_and push.SIGN_BIT eq | ||
end | ||
|
||
# Adds `a` to `b`, asserting that both inputs are valid i32. | ||
# | ||
# Returns the result modulo 2^32, plus a boolean indicating whether or not the subtraction underflowed. | ||
export.overflowing_add # [b, a] | ||
u32assert2 | ||
|
||
# is `b` signed? | ||
dup.0 exec.is_signed # [is_b_signed, b, a] | ||
|
||
# is `a` signed? | ||
dup.2 exec.is_signed # [is_a_signed, is_b_signed, b, a] | ||
|
||
# compute result | ||
movup.3 movup.3 u32wrapping_add | ||
|
||
# is `result` signed? | ||
dup.0 exec.is_signed # [is_result_signed, result, is_a_signed, is_b_signed] | ||
|
||
# if `b` and `result` have the same sign, which is different than the sign of `a`, | ||
# then overflow has occurred. In all other cases, the result is valid | ||
movup.3 # [is_b_signed, is_result_signed, result, is_a_signed] | ||
dup.1 eq # [do_b_and_result_have_same_sign, is_result_signed, result, is_a_signed] | ||
swap.1 movup.3 # [is_a_signed, is_result_signed, do_b_and_result_have_same_sign, result] | ||
neq # [do_a_and_result_have_different_sign, do_b_and_result_have_same_sign, result] | ||
and # [overflowed, result] | ||
end | ||
|
||
# Adds `b` to `a`, asserting that both inputs are valid i32, | ||
# and asserting that the addition does not overflow. | ||
export.checked_add # [b, a] | ||
exec.overflowing_add # [overflowed, result] | ||
assertz # [result] | ||
end | ||
|
||
# Get the negation of `a` | ||
# | ||
# This operation is checked, so if the input is not a valid i32, | ||
# or if the negation is not a valid i32, execution traps | ||
export.checked_neg # [a] | ||
# assert that the negation is representable | ||
dup.0 push.MIN eq assertz | ||
u32checked_not u32wrapping_add.1 | ||
end | ||
|
||
# Get the negation of `a` | ||
# | ||
# This operation is unchecked, so if the input is not a valid i32 the behavior is undefined | ||
export.unchecked_neg # [a] | ||
u32checked_not u32wrapping_add.1 | ||
end | ||
|
||
# Subtracts `b` from `a` | ||
# | ||
# This operation will fail if `b` is not a valid i32, or if `result` is not a valid i32 | ||
export.unchecked_sub # [b, a] | ||
u32checked_not | ||
add | ||
add.1 # [!b + a + 1] | ||
end | ||
|
||
# Subtracts `b` from `a`, asserting that both inputs are valid i32. | ||
# | ||
# Returns the result modulo 2^32, plus a boolean indicating whether or not the subtraction underflowed. | ||
export.overflowing_sub # [b, a] | ||
u32assert2 | ||
|
||
# is `b` signed? | ||
dup.0 exec.is_signed # [is_b_signed, b, a] | ||
|
||
# is `a` signed? | ||
dup.2 exec.is_signed # [is_a_signed, is_b_signed, b, a] | ||
|
||
# compute result | ||
movup.3 movup.3 u32wrapping_sub | ||
|
||
# is `result` signed? | ||
dup.0 exec.is_signed # [is_result_signed, result, is_a_signed, is_b_signed] | ||
|
||
# if `b` and `result` have the same sign, which is different than the sign of `a`, | ||
# then overflow has occurred. In all other cases, the result is valid | ||
movup.3 # [is_b_signed, is_result_signed, result, is_a_signed] | ||
dup.1 eq # [do_b_and_result_have_same_sign, is_result_signed, result, is_a_signed] | ||
swap.1 movup.3 # [is_a_signed, is_result_signed, do_b_and_result_have_same_sign, result] | ||
neq # [do_a_and_result_have_different_sign, do_b_and_result_have_same_sign, result] | ||
and # [overflowed, result] | ||
end | ||
|
||
# Subtracts `b` from `a`, asserting that both inputs are valid i32, | ||
# and asserting that the subtraction does not underflow. | ||
export.checked_sub # [b, a] | ||
exec.overflowing_sub # [overflowed, result] | ||
assertz # [result] | ||
end | ||
|
||
# Multiplies `a` by `b`, asserting that both inputs are valid i32. | ||
# | ||
# Returns the result modulo 2^32, plus a boolean indicating whether or not the multiplication overflowed. | ||
export.overflowing_mul # [b, a] | ||
u32assert2 | ||
|
||
# is `b` signed? | ||
dup.0 exec.is_signed # [is_b_signed, b, a] | ||
|
||
# is `a` signed? | ||
dup.2 exec.is_signed # [is_a_signed, is_b_signed, b, a] | ||
|
||
# compute result | ||
movup.3 movup.3 u32wrapping_mul | ||
|
||
# if `b` and `result` have the same sign, which is different than the sign of `a`, | ||
# then overflow has occurred. In all other cases, the result is valid | ||
movup.3 # [is_b_signed, is_result_signed, result, is_a_signed] | ||
dup.1 eq # [do_b_and_result_have_same_sign, is_result_signed, result, is_a_signed] | ||
swap.1 movup.3 # [is_a_signed, is_result_signed, do_b_and_result_have_same_sign, result] | ||
neq # [do_a_and_result_have_different_sign, do_b_and_result_have_same_sign, result] | ||
and # [overflowed, result] | ||
end | ||
|
||
# Multiplies `a` by `b`, asserting that both inputs are valid i32, | ||
# and asserting that the multiplication does not underflow. | ||
export.checked_mul # [b, a] | ||
exec.overflowing_mul # [overflowed, result] | ||
assertz # [result] | ||
end | ||
|
||
# Divides `a` by `b`, asserting that both inputs are valid i32 | ||
export.checked_div # [b, a] | ||
u32assert2 | ||
|
||
# get positive dividend | ||
dup.1 exec.unchecked_neg # [-a, b, a] | ||
dup.2 swap.1 # [-a, a, b, a] | ||
movup.3 exec.is_signed # [is_a_signed, -a, a, b] | ||
dup.0 movdn.4 cdrop # [|a|, b, is_a_signed] | ||
|
||
# get positive divisor | ||
dup.1 exec.unchecked_neg # [-b, |a|, b, is_a_signed] | ||
dup.2 swap.1 # [-b, b, |a|, b, is_a_signed] | ||
movup.3 exec.is_signed # [is_b_signed, -b, b, |a|, is_a_signed] | ||
dup.0 movdn.4 cdrop # [|b|, |a|, is_a_signed, is_b_signed] | ||
|
||
# divide | ||
u32unchecked_div # [|a / b|, is_a_signed, is_b_signed] | ||
|
||
# if the signs differ, negate the result | ||
movdn.2 neq # [signs_differ, |a / b|] | ||
dup.1 exec.unchecked_neg # [-|a / b|, signs_differ, |a / b|] | ||
swap.1 cdrop # [result] | ||
end | ||
|
||
# Given two i32 values in two's complement representation, compare them, | ||
# returning -1 if `a` < `b`, 0 if equal, and 1 if `a` > `b`. | ||
# | ||
export.icmp # [b, a] | ||
dup.1 # [a, b, a] | ||
dup.1 # [b, a, b, a] | ||
|
||
# get the most-significant bit of `b` | ||
push.SIGN_BIT # [1<<31, b, a, b, a] | ||
u32checked_and # [b_msb, a, b, a] | ||
|
||
# get the most-significant bit of `a` | ||
swap.1 # [a, b_msb, b, a] | ||
push.SIGN_BIT # [1<<31, a, b_msb, b, a] | ||
u32checked_and # [a_msb, b_msb, b, a] | ||
|
||
eq.0 # [a_msb == 0, b_msb, b, a] | ||
swap.1 eq.0 # [b_msb == 0, a_msb == 0, b, a] | ||
swap.1 dup.1 neq # [a_msb != b_msb, b_msb == 0, b, a] | ||
|
||
# if a_msb != b_msb, then a > b (if a_msb == 0), or a < b (if a_msb == 1) | ||
if.true # [b_msb == 0, b, a] | ||
movdn.2 drop drop # [b_msb == 0] | ||
push.1 push.NEG1 # [-1, 1, b_msb == 0] | ||
swap.2 # [b_msb == 0, 1, -1] | ||
cdrop # [1 or -1] | ||
else # [b_msb == 0, b, a] | ||
# a_msb == b_msb, so we can compare the remaining bits lexicographically, | ||
# which we get for free via the lt/gt ops | ||
drop # [b, a] | ||
dup.1 dup.1 # [b, a, b, a] | ||
u32unchecked_gt movdn.2 # [b, a, a > b] | ||
u32unchecked_lt # [a < b, a > b] | ||
push.0 push.NEG1 push.1 | ||
swap.3 # [a < b, -1, 0, 1, a > b] | ||
cdrop # [-1 or 0, 1, a > b] | ||
swap.2 # [a > b, 1, -1 or 0] | ||
cdrop # [1 or -1 or 0] | ||
end | ||
end | ||
|
||
# Given two i32 values in two's complement representation, return 1 if `a < b`, else 0 | ||
export.is_lt # [b, a] | ||
exec.icmp push.NEG1 eq | ||
end | ||
|
||
# Given two i32 values in two's complement representation, return 1 if `a <= b`, else 0 | ||
export.is_lte # [b, a] | ||
exec.icmp neq.1 | ||
end | ||
|
||
# Given two i32 values in two's complement representation, return 1 if `a > b`, else 0 | ||
export.is_gt # [b, a] | ||
exec.icmp eq.1 | ||
end | ||
|
||
# Given two i32 values in two's complement representation, return 1 if `a >= b`, else 0 | ||
export.is_gte # [b, a] | ||
exec.icmp push.NEG1 neq | ||
end | ||
|
||
# Compute 2^n, where `n` must be less than 31, or the result will overflow i32::MAX | ||
export.pow2 # [n] | ||
dup.0 | ||
push.31 | ||
u32checked_lt # [n < 31, pow] | ||
assert # [n] | ||
push.1 swap.1 # [n, 1] | ||
u32checked_shl # [1 << n] | ||
end | ||
|
||
# Compute a^b, where `b` must be a positive i32 value < 31 | ||
export.ipow # [b, a] | ||
dup.0 eq.0 # [b == 0, b, a] | ||
dup.2 eq.0 # [a == 0, b == 0, b, a] | ||
or # [a == 0 || b == 0, b, a] | ||
# if a == 0, the result is always 0; otherwise if b == 0, then the result is always 1 | ||
if.true | ||
eq.0 # [b == 0, a] | ||
push.1 push.0 # [0, 1, b == 0, a] | ||
swap.2 # [b == 0, 1, 0, a] | ||
cdrop # [1 or 0, a] | ||
swap.1 drop # [1 or 0] | ||
else # [b, a] | ||
# for all other values, we do exponentiation by squaring | ||
push.1 # [acc, b, a] | ||
dup.1 # [b, acc, b, a] | ||
push.1 # [1, b, acc, b, a] | ||
u32checked_gt # [b > 1, acc, b, a] | ||
while.true # [acc, b, a => base] | ||
dup.2 dup.1 # [acc, base, acc, b, base] | ||
mul # [base * acc, acc, b, base] | ||
dup.2 # [b, base * acc, acc, b, base] | ||
push.1 # [1, b, base * acc, acc, b, base] | ||
u32checked_and # [b & 1, base * acc, acc, b, base] | ||
eq.1 # [b & 1 == 1, base * acc, acc, b, base] | ||
cdrop # [acc, b, base] | ||
u32assert | ||
swap.1 # [b, acc, base] | ||
u32checked_div.2 # [b /= 2, acc, base] | ||
movup.2 dup.0 # [base, base, b, acc] | ||
u32checked_mul # [base * base, b, acc] | ||
swap.1 # [b, base, acc] | ||
movup.2 # [acc, b, base] | ||
dup.1 push.1 # [1, b, acc, b, base] | ||
u32checked_gt # [b > 1, acc, b, base] | ||
end | ||
swap.1 drop # [acc, base] | ||
u32checked_mul # [acc * base] | ||
end | ||
end | ||
|
||
# Arithmetic shift-right, i.e. `a >> b` preserves the signedness of the value | ||
# | ||
# This implementation is checked, so it will assert if the inputs are invalid | ||
export.checked_shr # [b, a] | ||
# validate the shift is valid | ||
dup.0 push.32 | ||
u32checked_lt # [b < 32, b, a] | ||
assert | ||
|
||
# if the input is zero, the output is always zero, | ||
# and if the shift is zero, the input is returned unchanged | ||
dup.0 eq.0 # [b == 0, b, a] | ||
dup.2 eq.0 # [a == 0, b == 0, b, a] | ||
or # [a == 0 || b == 0, b, a] | ||
if.true | ||
# return `a` if `b == 0`, otherwise `a == 0` so return 0 | ||
eq.0 # [b == 0, a] | ||
swap.1 push.0 # [0, a, b == 0] | ||
swap.2 # [b == 0, a, 0] | ||
cdrop # [a or 0] | ||
else # [b, a] | ||
# get the signedness of the value | ||
dup.1 # [a, b, a] | ||
push.SIGN_BIT # [1<<31, a, b, a] | ||
u32checked_and push.SIGN_BIT eq # [is_signed, b, a] | ||
|
||
# if the value is signed, we must sign-extend the result, | ||
# otherwise we can treat it as an unsigned shift | ||
if.true # [b, a] | ||
swap.1 # [a, b] | ||
dup.1 # [b, a, b] | ||
u32checked_shr # [shifted, b] | ||
|
||
# compute the extension mask | ||
push.1 dup.2 # [b, 1, shifted, b] | ||
u32unchecked_shl | ||
sub.1 # [(1 << b) - 1, shifted, b] | ||
|
||
# shift the mask into place | ||
push.32 movup.3 # [b, 32, mask, shifted] | ||
sub # [32 - b, mask, shifted] | ||
u32unchecked_shl # [mask << (32 - b), shifted] | ||
u32checked_or # [shifted | mask] | ||
else | ||
u32checked_shr | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.