Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Wide arithmetic #541

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f141687
refactor: lint
PabloLION Sep 24, 2022
4ae1973
feat: add operator Addw with test
PabloLION Sep 24, 2022
d2ed2a1
ref: split multiexpr to a new file
PabloLION Sep 24, 2022
5f60659
ref: lint
PabloLION Sep 25, 2022
345a71e
test: add an invalid test case for addw
PabloLION Sep 25, 2022
0a2cdeb
ref: rename multiexpr to wideexpr
PabloLION Sep 25, 2022
64ab039
ref: rewrite `AddW` with `WideExpr`
PabloLION Sep 25, 2022
a2f5085
feat: add `mulw`, `expw`, `divmodw` w/ their tests
PabloLION Sep 25, 2022
ba4fb9f
ref: rename Divw to `DivW`
PabloLION Sep 25, 2022
fae9223
ref: move test_divw_invalid_version
PabloLION Sep 25, 2022
4b96a4c
update on u128 add/mul arithmetic
ahangsu Mar 7, 2022
0bebacb
push what I have local
ahangsu Mar 9, 2022
80b1765
update 128b minus
ahangsu Mar 9, 2022
496f8d5
minor
ahangsu Mar 9, 2022
1393346
per Jason comments
ahangsu Mar 9, 2022
f8fe8af
update full arith support
ahangsu Mar 10, 2022
cadaba8
minor
ahangsu Mar 10, 2022
0f4abb2
minor
ahangsu Mar 10, 2022
81d3808
minor
ahangsu Mar 10, 2022
5586a7a
comply with ide hints:
ahangsu Mar 10, 2022
76efcb1
fmt
ahangsu Mar 10, 2022
8ef5563
minor fixes here n there
ahangsu Mar 11, 2022
d891c4d
update reduce To method with lowest scratch use
ahangsu Mar 11, 2022
78f038c
remove redundant abstract teal method
ahangsu Mar 11, 2022
0fa2dc8
update divmodw support
ahangsu Mar 11, 2022
0de4d3e
update toUint64 check highword == 0
ahangsu Mar 11, 2022
d01dd38
minor
ahangsu Mar 14, 2022
bc83f88
ref: rebase and import for test
PabloLION Sep 23, 2022
876d78a
chore: regenerate init
PabloLION Sep 23, 2022
8bd36f2
ref: rearrange some codes before rebase
PabloLION Sep 26, 2022
7f54238
tag: rebase to PR#543
PabloLION Sep 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion pyteal/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ __all__ = [
"AccountParam",
"AccountParamObject",
"Add",
"AddW",
"Addr",
"And",
"App",
Expand Down Expand Up @@ -90,7 +91,8 @@ __all__ = [
"DEFAULT_PROGRAM_VERSION",
"DEFAULT_TEAL_VERSION",
"Div",
"Divw",
"DivModW",
"DivW",
"DynamicScratchVar",
"EcdsaCurve",
"EcdsaDecompress",
Expand All @@ -102,6 +104,7 @@ __all__ = [
"Eq",
"Err",
"Exp",
"ExpW",
"Expr",
"Extract",
"ExtractUint16",
Expand Down Expand Up @@ -151,6 +154,7 @@ __all__ = [
"Mod",
"Mode",
"Mul",
"MulW",
"MultiValue",
"NUM_SLOTS",
"NaryExpr",
Expand Down Expand Up @@ -218,7 +222,11 @@ __all__ = [
"VrfVerify",
"While",
"WideRatio",
"WideUint128",
"abi",
"compileTeal",
"expW",
"pragma",
"prodW",
"sumW",
]
15 changes: 12 additions & 3 deletions pyteal/ast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@

# ternary ops
from pyteal.ast.ternaryexpr import (
Divw,
DivW,
Ed25519Verify,
Ed25519Verify_Bare,
SetBit,
Expand All @@ -124,7 +124,7 @@

# more ops
from pyteal.ast.naryexpr import NaryExpr, Add, And, Mul, Or, Concat
from pyteal.ast.widemath import WideRatio
from pyteal.ast.widemath import WideRatio, sumW, prodW, expW, WideUint128

# control flow
from pyteal.ast.if_ import If
Expand Down Expand Up @@ -157,6 +157,7 @@
from pyteal.ast.scratchvar import DynamicScratchVar, ScratchVar
from pyteal.ast.maybe import MaybeValue
from pyteal.ast.multi import MultiValue
from pyteal.ast.wideexpr import AddW, MulW, ExpW, DivModW
from pyteal.ast.opup import OpUp, OpUpMode
from pyteal.ast.ecdsa import EcdsaCurve, EcdsaVerify, EcdsaDecompress, EcdsaRecover
from pyteal.ast.router import (
Expand Down Expand Up @@ -240,7 +241,7 @@
"Div",
"Mod",
"Exp",
"Divw",
"DivW",
"BitwiseAnd",
"BitwiseOr",
"BitwiseXor",
Expand All @@ -266,6 +267,10 @@
"Or",
"Concat",
"WideRatio",
"sumW",
"prodW",
"expW",
"WideUint128",
"If",
"Cond",
"Seq",
Expand Down Expand Up @@ -330,4 +335,8 @@
"EcdsaRecover",
"JsonRef",
"VrfVerify",
"AddW",
"MulW",
"ExpW",
"DivModW",
]
4 changes: 2 additions & 2 deletions pyteal/ast/maybe.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ def __init__(
op: Op,
type: TealType,
*,
immediate_args: List[Union[int, str]] = None,
args: List[Expr] = None
immediate_args: List[Union[int, str]] | None = None,
args: List[Expr] | None = None
):
"""Create a new MaybeValue.

Expand Down
8 changes: 4 additions & 4 deletions pyteal/ast/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ def __init__(
op: Op,
types: List[TealType],
*,
immediate_args: List[Union[int, str]] = None,
args: List[Expr] = None,
immediate_args: List[Union[int, str]] | None = None,
args: List[Expr] | None = None,
compile_check: Callable[["CompileOptions"], None] = lambda _: None,
):
"""Create a new MultiValue.
Expand All @@ -41,8 +41,8 @@ def __init__(
self.output_slots = [ScratchSlot() for _ in self.types]

def outputReducer(self, reducer: Callable[..., Expr]) -> Expr:
input = [slot.load(self.types[i]) for i, slot in enumerate(self.output_slots)]
return Seq(self, reducer(*input))
inputs = [slot.load(self.types[i]) for i, slot in enumerate(self.output_slots)]
return Seq(self, reducer(*inputs))

def __str__(self):
ret_str = "(({}".format(self.op)
Expand Down
10 changes: 10 additions & 0 deletions pyteal/ast/multi_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
options = pt.CompileOptions()


def TOO_MANY_ARGS(arg_len):
return ValueError("Too many arguments. Expected 0-2, got {}".format(len(arg_len)))


def __test_single(expr: pt.MultiValue):
assert expr.output_slots[0] != expr.output_slots[1]

Expand Down Expand Up @@ -54,6 +58,8 @@ def __test_single_conditional(
arg_2, after_arg_2 = args[1].__teal__(options)
after_arg_1.setNextBlock(arg_2)
after_arg_2.setNextBlock(expected_call)
else:
raise TOO_MANY_ARGS(len(args))

expected.addIncoming()
expected = pt.TealBlock.NormalizeBlocks(expected)
Expand Down Expand Up @@ -94,6 +100,8 @@ def __test_single_assert(expr: pt.MultiValue, op, args: List[pt.Expr], iargs, re
arg_2, after_arg_2 = args[1].__teal__(options)
after_arg_1.setNextBlock(arg_2)
after_arg_2.setNextBlock(expected_call)
else:
raise TOO_MANY_ARGS(len(args))

expected.addIncoming()
expected = pt.TealBlock.NormalizeBlocks(expected)
Expand Down Expand Up @@ -136,6 +144,8 @@ def __test_single_with_vars(
arg_2, after_arg_2 = args[1].__teal__(options)
after_arg_1.setNextBlock(arg_2)
after_arg_2.setNextBlock(expected_call)
else:
raise TOO_MANY_ARGS(len(args))

expected.addIncoming()
expected = pt.TealBlock.NormalizeBlocks(expected)
Expand Down
2 changes: 1 addition & 1 deletion pyteal/ast/ternaryexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def SetByte(value: Expr, index: Expr, newByteValue: Expr) -> TernaryExpr:
)


def Divw(hi: Expr, lo: Expr, y: Expr) -> TernaryExpr:
def DivW(hi: Expr, lo: Expr, y: Expr) -> TernaryExpr:
"""
Performs wide division by interpreting `hi` and `lo` as a uint128 value.

Expand Down
13 changes: 9 additions & 4 deletions pyteal/ast/ternaryexpr_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def test_set_byte_invalid():

def test_divw():
args = [pt.Int(0), pt.Int(90), pt.Int(30)]
expr = pt.Divw(args[0], args[1], args[2])
expr = pt.DivW(args[0], args[1], args[2])
assert expr.type_of() == pt.TealType.uint64

expected = pt.TealSimpleBlock(
Expand All @@ -197,10 +197,15 @@ def test_divw():

def test_divw_invalid():
with pytest.raises(pt.TealTypeError):
pt.Divw(pt.Bytes("10"), pt.Int(0), pt.Int(1))
pt.DivW(pt.Bytes("10"), pt.Int(0), pt.Int(1))

with pytest.raises(pt.TealTypeError):
pt.Divw(pt.Int(10), pt.Bytes("0"), pt.Int(1))
pt.DivW(pt.Int(10), pt.Bytes("0"), pt.Int(1))

with pytest.raises(pt.TealTypeError):
pt.Divw(pt.Int(10), pt.Int(0), pt.Bytes("1"))
pt.DivW(pt.Int(10), pt.Int(0), pt.Bytes("1"))


def test_divw_invalid_version():
with pytest.raises(pt.TealInputError):
pt.DivW(pt.Int(2), pt.Int(2), pt.Int(2)).__teal__(avm5Options) # needs >=6
121 changes: 121 additions & 0 deletions pyteal/ast/wideexpr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from typing import TYPE_CHECKING, List

from pyteal.ast.expr import Expr
from pyteal.ast.multi import MultiValue
from pyteal.errors import verifyProgramVersion
from pyteal.ir import Op
from pyteal.types import TealType, require_type

if TYPE_CHECKING:
from pyteal.compiler import CompileOptions


class WideExpr(MultiValue):
"""Base class for WideInt Operations

This type of expression produces WideInt(MultiValue).
"""

def __init__(
self,
op: Op,
args: List[Expr],
):
"""Create a new WideExpr, whose returned type is always a MultiValue of [TealType.uint64, TealType.uint64].

Args:
op: The operation that returns values.
args: Stack arguments for the op.
min_version: The minimum TEAL version required to use this expression.
"""

super().__init__(
op=op,
types=[TealType.uint64, TealType.uint64],
args=args,
immediate_args=None,
)

for arg in args:
require_type(arg, TealType.uint64)

def __teal__(self, options: "CompileOptions"):
verifyProgramVersion(
self.op.min_version,
options.version,
"Program version too low to use op {}".format(self.op),
)

return super().__teal__(options)


"""Binary MultiValue operations"""


def AddW(adder: Expr, adder_: Expr) -> MultiValue:
"""Add two 64-bit integers.

Produces a MultiValue with two outputs: the sum and the carry-bit.

Args:
adder: Must evaluate to uint64.
adder_: Must evaluate to uint64.
"""
return WideExpr(Op.addw, [adder, adder_])


def MulW(factor: Expr, factor_: Expr) -> MultiValue:
"""Multiply two 64-bit integers.

Produces a MultiValue with two outputs: the product and the carry-bit.

Args:
factor: Must evaluate to uint64.
factor_: Must evaluate to uint64.
"""

return WideExpr(Op.mulw, [factor, factor_])


def ExpW(base: Expr, exponent: Expr) -> MultiValue:
"""Raise a 64-bit integer to a power.

Produces a MultiValue with two outputs: the result and the carry-bit.

Args:
base: Must evaluate to uint64.
exponent: Must evaluate to uint64.
"""

return WideExpr(Op.expw, [base, exponent])


def DivModW(
dividendHigh: Expr, dividendLow: Expr, divisorHigh: Expr, divisorLow: Expr
) -> MultiValue:
"""Divide two wide-64-bit integers.

Produces a MultiValue with four outputs: the quotient and its carry-bit, the remainder and its carry-bit.

Stack:
..., A: uint64, B: uint64, C: uint64, D: uint64 --> ..., W: uint64, X: uint64, Y: uint64, Z: uint64
Where W,X = (A,B / C,D); Y,Z = (A,B modulo C,D)

Example:
All ints should be initialized with Int(). For readability, we didn't use Int() in the example.
DivModW(0, 10, 0, 5) = (0, 2, 0, 0) # 10 / 5 = 2, 10 % 5 = 0
DivModW(0, 10, 0, 3) = (0, 3, 0, 1) # 10 / 3 = 3, 10 % 3 = 1
DivModW(5, 14, 0, 5) = (1, 2, 0, 4) # ((5<<64)+14) / 5 = (1<<64)+2, ((5<<64)+14) % 5 = 4
DivModW(7, 29, 1, 3) = (0, 7, 0, 8) # ((7<<64)+29) / ((1<<64)+3) = 7, ((7<<64)+29) % ((1<<64)+3) = 8

Args:
dividendHigh: Must evaluate to uint64.
dividendLow: Must evaluate to uint64.
divisorHigh: Must evaluate to uint64.
divisorLow: Must evaluate to uint64.
"""

return WideExpr(
Op.divmodw,
[dividendHigh, dividendLow, divisorHigh, divisorLow],
)
Loading