Example #1
0
    def __init__(
        self, numeratorFactors: List[Expr], denominatorFactors: List[Expr]
    ) -> None:
        """Create a new WideRatio expression with the given numerator and denominator factors.

        This will calculate :code:`(N_1 * N_2 * N_3 * ...) / (D_1 * D_2 * D_3 * ...)`, where each
        :code:`N_i` represents an element in :code:`numeratorFactors` and each :code:`D_i`
        represents an element in :code:`denominatorFactors`.

        Requires program version 5 or higher.

        Args:
            numeratorFactors: The factors in the numerator of the ratio. This list must have at
                least 1 element. If this list has exactly 1 element, then denominatorFactors must
                have more than 1 element (otherwise basic division should be used).
            denominatorFactors: The factors in the denominator of the ratio. This list must have at
                least 1 element.
        """
        super().__init__()
        if len(numeratorFactors) == 0 or len(denominatorFactors) == 0:
            raise TealInternalError(
                "At least 1 factor must be present in the numerator and denominator"
            )
        if len(numeratorFactors) == 1 and len(denominatorFactors) == 1:
            raise TealInternalError(
                "There is only a single factor in the numerator and denominator. Use basic division instead."
            )
        self.numeratorFactors = numeratorFactors
        self.denominatorFactors = denominatorFactors
Example #2
0
def extractBytesValue(op: TealOp) -> Union[str, bytes]:
    """Extract the constant value being loaded by a TealOp whose op is Op.byte.

    Returns:
        If the op is loading a template variable, returns the name of the variable as a string.
        Otherwise, returns the byte string that the op is loading.
    """
    if len(op.args) != 1 or type(op.args[0]) is not str:
        raise TealInternalError("Unexpected args in byte opcode: {}".format(
            op.args))

    value = op.args[0]
    if value.startswith("TMPL_"):
        return value
    if value.startswith('"') and value.endswith('"'):
        return unescapeStr(value).encode("utf-8")
    if value.startswith("0x"):
        return bytes.fromhex(value[2:])
    if value.startswith("base32(") and value.endswith(")"):
        return base64.b32decode(correctBase32Padding(value[len("base32("):-1]))
    if value.startswith("base64(") and value.endswith(")"):
        return base64.b64decode(value[len("base64("):-1])

    raise TealInternalError(
        "Unexpected format for byte value: {}".format(value))
Example #3
0
 def approval_cond(self) -> Expr | int:
     config_oc_pairs: list[tuple[CallConfig, EnumInt]] = [
         (self.no_op, OnComplete.NoOp),
         (self.opt_in, OnComplete.OptIn),
         (self.close_out, OnComplete.CloseOut),
         (self.update_application, OnComplete.UpdateApplication),
         (self.delete_application, OnComplete.DeleteApplication),
     ]
     if all(config == CallConfig.NEVER for config, _ in config_oc_pairs):
         return 0
     elif all(config == CallConfig.ALL for config, _ in config_oc_pairs):
         return 1
     else:
         cond_list = []
         for config, oc in config_oc_pairs:
             config_cond = config.approval_condition_under_config()
             match config_cond:
                 case Expr():
                     cond_list.append(And(Txn.on_completion() == oc, config_cond))
                 case 1:
                     cond_list.append(Txn.on_completion() == oc)
                 case 0:
                     continue
                 case _:
                     raise TealInternalError(
                         f"unexpected condition_under_config: {config_cond}"
                     )
         return Or(*cond_list)
Example #4
0
def _apply_slot_to_stack(cur_block: TealBlock, start: TealBlock,
                         skip_slots: Set[ScratchSlot]):
    slots_to_remove = set()
    # surprisingly, this slicing is totally safe - even if the list is empty.
    for i, op in enumerate(cur_block.ops[:-1]):
        if type(op) != TealOp or op.op != Op.store:
            continue

        if set(op.getSlots()).issubset(skip_slots):
            continue

        next_op = cur_block.ops[i + 1]
        if type(next_op) != TealOp or next_op.op != Op.load:
            continue

        cur_slots, next_slots = op.getSlots(), next_op.getSlots()
        if len(cur_slots) != 1 or len(next_slots) != 1:
            raise TealInternalError(
                "load/store op does not have exactly one slot argument")
        if cur_slots[0] != next_slots[0]:
            continue

        if not _has_load_dependencies(cur_block, start, cur_slots[0], i + 1):
            slots_to_remove.add(cur_slots[0])

    _remove_extraneous_slot_access(start, slots_to_remove)
Example #5
0
    def __init__(
        self, slot: Optional[ScratchSlot], value: Expr, index_expression: Expr = None
    ):
        """Create a new ScratchStore expression.

        Args:
            slot (optional): The slot to store the value in.
            value: The value to store.
            index_expression (optional): As an alternative to slot,
                an expression can be supplied for the slot index.
        """
        super().__init__()

        if (slot is None) == (index_expression is None):
            raise TealInternalError(
                "Exactly one of slot or index_expressions must be provided"
            )

        if index_expression:
            if not isinstance(index_expression, Expr):
                raise TealInputError(
                    "index_expression must be an Expr but was of type {}".format(
                        type(index_expression)
                    )
                )
            require_type(index_expression, TealType.uint64)

        self.slot = slot
        self.value = value
        self.index_expression = index_expression
Example #6
0
def extractIntValue(op: TealOp) -> Union[str, int]:
    """Extract the constant value being loaded by a TealOp whose op is Op.int.

    Returns:
        If the op is loading a template variable, returns the name of the variable as a string.
        Otherwise, returns the integer that the op is loading.
    """
    if len(op.args) != 1 or type(op.args[0]) not in (int, str):
        raise TealInternalError("Unexpected args in int opcode: {}".format(
            op.args))

    value = cast(Union[str, int], op.args[0])
    if type(value) is int or cast(str, value).startswith("TMPL_"):
        return value
    if value not in intEnumValues:
        raise TealInternalError(
            "Int constant not recognized: {}".format(value))
    return intEnumValues[cast(str, value)]
Example #7
0
    def assemble(self) -> str:
        from pyteal.ast import ScratchSlot, SubroutineDefinition

        parts = [str(self.op)]
        for arg in self.args:
            if isinstance(arg, ScratchSlot):
                raise TealInternalError("Slot not assigned: {}".format(arg))

            if isinstance(arg, SubroutineDefinition):
                raise TealInternalError("Subroutine not resolved: {}".format(arg))

            if isinstance(arg, int):
                parts.append(str(arg))
            elif isinstance(arg, LabelReference):
                parts.append(arg.getLabel())
            else:
                parts.append(arg)

        return " ".join(parts)
Example #8
0
    def txn_type_enum(self) -> Expr:
        """Get the integer transaction type value this TransactionTypeSpec represents.

        See :any:`TxnType` for the complete list.

        If this is a generic TransactionTypeSpec, i.e. type :code:`txn`, this method will raise an error, since this type does not represent a single transaction type.
        """
        raise TealInternalError(
            "abi.TransactionTypeSpec does not represent a specific transaction type"
        )
Example #9
0
def extractMethodSigValue(op: TealOp) -> bytes:
    """Extract the constant value being loaded by a TealOp whose op is Op.method.

    Returns:
        The bytes of method selector computed from the method signature that the op is loading.
    """
    if len(op.args) != 1 or type(op.args[0]) != str:
        raise TealInternalError("Unexpected args in method opcode: {}".format(
            op.args))

    methodSignature = cast(str, op.args[0])
    if methodSignature[0] == methodSignature[
            -1] and methodSignature.startswith('"'):
        methodSignature = methodSignature[1:-1]
    else:
        raise TealInternalError(
            "Method signature opcode error: signature {} not wrapped with double-quotes"
            .format(methodSignature))
    methodSelector = encoding.checksum(bytes(methodSignature, "utf-8"))[:4]
    return methodSelector
Example #10
0
 def approval_condition_under_config(self) -> Expr | int:
     match self:
         case CallConfig.NEVER:
             return 0
         case CallConfig.CALL:
             return Txn.application_id() != Int(0)
         case CallConfig.CREATE:
             return Txn.application_id() == Int(0)
         case CallConfig.ALL:
             return 1
         case _:
             raise TealInternalError(f"unexpected CallConfig {self}")
Example #11
0
    def __teal__(self, options: "CompileOptions"):
        from pyteal.ir import TealOp, Op, TealBlock

        if self.index_expression is not None:
            op = TealOp(self, Op.stores)
            return TealBlock.FromOp(options, op, self.index_expression, self.value)

        if not isinstance(self.slot, ScratchSlot):
            raise TealInternalError(
                "cannot handle slot of type {}".format(type(self.slot))
            )
        op = TealOp(self, Op.store, self.slot)
        return TealBlock.FromOp(options, op, self.value)
Example #12
0
def extractAddrValue(op: TealOp) -> Union[str, bytes]:
    """Extract the constant value being loaded by a TealOp whose op is Op.addr.

    Returns:
        If the op is loading a template variable, returns the name of the variable as a string.
        Otherwise, returns the bytes of the public key of the address that the op is loading.
    """
    if len(op.args) != 1 or type(op.args[0]) != str:
        raise TealInternalError("Unexpected args in addr opcode: {}".format(
            op.args))

    value = cast(str, op.args[0])
    if not value.startswith("TMPL_"):
        value = encoding.decode_address(value)
    return value
Example #13
0
def correctBase32Padding(s: str) -> str:
    content = s.split("=")[0]
    trailing = len(content) % 8

    if trailing == 2:
        content += "=" * 6
    elif trailing == 4:
        content += "=" * 4
    elif trailing == 5:
        content += "=" * 3
    elif trailing == 7:
        content += "="
    elif trailing != 0:
        raise TealInternalError("Invalid base32 content")

    return content
Example #14
0
 def approval_construction(self) -> Optional[Expr]:
     oc_action_pair: list[tuple[EnumInt, OnCompleteAction]] = [
         (OnComplete.NoOp, self.no_op),
         (OnComplete.OptIn, self.opt_in),
         (OnComplete.CloseOut, self.close_out),
         (OnComplete.UpdateApplication, self.update_application),
         (OnComplete.DeleteApplication, self.delete_application),
     ]
     if all(oca.is_empty() for _, oca in oc_action_pair):
         return None
     conditions_n_branches: list[CondNode] = list()
     for oc, oca in oc_action_pair:
         if oca.is_empty():
             continue
         wrapped_handler = ASTBuilder.wrap_handler(
             False,
             cast(Expr | SubroutineFnWrapper | ABIReturnSubroutine, oca.action),
         )
         match oca.call_config:
             case CallConfig.ALL:
                 cond_body = wrapped_handler
             case CallConfig.CALL | CallConfig.CREATE:
                 cond_body = Seq(
                     Assert(
                         cast(
                             Expr, oca.call_config.approval_condition_under_config()
                         )
                     ),
                     wrapped_handler,
                 )
             case _:
                 raise TealInternalError(
                     f"Unexpected CallConfig: {oca.call_config!r}"
                 )
         conditions_n_branches.append(
             CondNode(
                 Txn.on_completion() == oc,
                 cond_body,
             )
         )
     return Cond(*[[n.condition, n.branch] for n in conditions_n_branches])
Example #15
0
def sortBlocks(start: TealBlock, end: TealBlock) -> List[TealBlock]:
    """Topologically sort the graph which starts with the input TealBlock.

    Args:
        start: The starting point of the graph to sort.

    Returns:
        An ordered list of TealBlocks that is sorted such that every block is guaranteed to appear
        in the list before all of its outgoing blocks.
    """
    S = [start]
    order = []
    visited = set()  # I changed visited to a set to be more efficient
    while len(S) != 0:
        n = S.pop()

        if id(n) in visited:
            continue

        S += n.getOutgoing()

        order.append(n)
        visited.add(id(n))

    endIndex = -1
    for i, block in enumerate(order):
        if block is end:
            endIndex = i
            break

    if endIndex == -1:
        raise TealInternalError("End block not present")

    order.pop(endIndex)
    order.append(end)

    return order
Example #16
0
def flattenBlocks(blocks: List[TealBlock]) -> List[TealComponent]:
    """Lowers a list of TealBlocks into a list of TealComponents.

    Args:
        blocks: The blocks to lower.
    """
    codeblocks = []
    references: DefaultDict[int, int] = defaultdict(int)

    labelRefs: Dict[int, LabelReference] = dict()

    def indexToLabel(index: int) -> LabelReference:
        if index not in labelRefs:
            labelRefs[index] = LabelReference("l{}".format(index))
        return labelRefs[index]

    def blockIndexByReference(block: TealBlock) -> int:
        for i, b in enumerate(blocks):
            if block is b:
                return i
        raise ValueError("Block not present in list: {}".format(block))

    for i, block in enumerate(blocks):
        code = list(block.ops)
        codeblocks.append(code)
        if block.isTerminal():
            continue

        if type(block) is TealSimpleBlock:
            assert block.nextBlock is not None

            nextIndex = blockIndexByReference(block.nextBlock)

            if nextIndex != i + 1:
                references[nextIndex] += 1
                code.append(TealOp(None, Op.b, indexToLabel(nextIndex)))

        elif type(block) is TealConditionalBlock:
            assert block.trueBlock is not None
            assert block.falseBlock is not None

            trueIndex = blockIndexByReference(block.trueBlock)
            falseIndex = blockIndexByReference(block.falseBlock)

            if falseIndex == i + 1:
                references[trueIndex] += 1
                code.append(TealOp(None, Op.bnz, indexToLabel(trueIndex)))
                continue

            if trueIndex == i + 1:
                references[falseIndex] += 1
                code.append(TealOp(None, Op.bz, indexToLabel(falseIndex)))
                continue

            references[trueIndex] += 1
            code.append(TealOp(None, Op.bnz, indexToLabel(trueIndex)))

            references[falseIndex] += 1
            code.append(TealOp(None, Op.b, indexToLabel(falseIndex)))
        else:
            raise TealInternalError("Unrecognized block type: {}".format(
                type(block)))

    teal: List[TealComponent] = []
    for i, code in enumerate(codeblocks):
        if references[i] != 0:
            teal.append(TealLabel(None, indexToLabel(i)))
        teal += code

    return teal
Example #17
0
def createConstantBlocks(ops: List[TealComponent]) -> List[TealComponent]:
    """Convert TEAL code from using pseudo-ops for constants to using assembled constant blocks.

    This conversion will assemble constants to be as space-efficient as possible.

    Args:
        ops: A list of TealComponents to convert.

    Returns:
        A list of TealComponent that are functionally the same as the input, but with all constants
        loaded either through blocks or the `pushint`/`pushbytes` single-use ops.
    """
    intFreqs: Dict[Union[str, int], int] = OrderedDict()
    byteFreqs: Dict[Union[str, bytes], int] = OrderedDict()

    for op in ops:
        if not isinstance(op, TealOp):
            continue

        basicOp = op.getOp()

        if basicOp == Op.int:
            intValue = extractIntValue(op)
            intFreqs[intValue] = intFreqs.get(intValue, 0) + 1
        elif basicOp == Op.byte:
            byteValue = extractBytesValue(op)
            byteFreqs[byteValue] = byteFreqs.get(byteValue, 0) + 1
        elif basicOp == Op.addr:
            addrValue = extractAddrValue(op)
            byteFreqs[addrValue] = byteFreqs.get(addrValue, 0) + 1
        elif basicOp == Op.method_signature:
            methodValue = extractMethodSigValue(op)
            byteFreqs[methodValue] = byteFreqs.get(methodValue, 0) + 1

    assembled: List[TealComponent] = []

    # because we used OrderedDicts and python sorting is stable, constants with the same frequency
    # will remain in the same order, i.e. first defined, first in block
    sortedInts = sorted(intFreqs, key=lambda x: intFreqs[x], reverse=True)
    sortedBytes = sorted(byteFreqs, key=lambda x: byteFreqs[x], reverse=True)

    # Use Op.pushint if the constant does not occur in the top 4 most frequent and is smaller than
    # 2 ** 7 to improve performance and save block space.
    intBlock = [
        val for i, val in enumerate(sortedInts)
        if intFreqs[val] > 1 and (i < 4 or isinstance(val, str) or val >= 2**7)
    ]

    byteBlock = [("0x" + b.hex()) if type(b) is bytes else cast(str, b)
                 for b in sortedBytes if byteFreqs[b] > 1]

    if len(intBlock) != 0:
        assembled.append(TealOp(None, Op.intcblock, *intBlock))

    if len(byteBlock) != 0:
        assembled.append(TealOp(None, Op.bytecblock, *byteBlock))

    for op in ops:
        if isinstance(op, TealOp):
            basicOp = op.getOp()

            if basicOp == Op.int:
                intValue = extractIntValue(op)
                if intValue not in intBlock:
                    assembled.append(
                        TealOp(op.expr, Op.pushint, intValue, "//", *op.args))
                    continue

                index = intBlock.index(intValue)
                if index == 0:
                    assembled.append(TealOp(op.expr, Op.intc_0, "//",
                                            *op.args))
                elif index == 1:
                    assembled.append(TealOp(op.expr, Op.intc_1, "//",
                                            *op.args))
                elif index == 2:
                    assembled.append(TealOp(op.expr, Op.intc_2, "//",
                                            *op.args))
                elif index == 3:
                    assembled.append(TealOp(op.expr, Op.intc_3, "//",
                                            *op.args))
                else:
                    assembled.append(
                        TealOp(op.expr, Op.intc, index, "//", *op.args))
                continue

            if (basicOp == Op.byte or basicOp == Op.addr
                    or basicOp == Op.method_signature):
                if basicOp == Op.byte:
                    byteValue = extractBytesValue(op)
                elif basicOp == Op.addr:
                    byteValue = extractAddrValue(op)
                elif basicOp == Op.method_signature:
                    byteValue = extractMethodSigValue(op)
                else:
                    raise TealInternalError(
                        "Expect a byte-like constant opcode, get {}".format(
                            op))

                if byteFreqs[byteValue] == 1:
                    encodedValue = (("0x" + byteValue.hex())
                                    if type(byteValue) is bytes else cast(
                                        str, byteValue))
                    assembled.append(
                        TealOp(op.expr, Op.pushbytes, encodedValue, "//",
                               *op.args))
                    continue

                index = sortedBytes.index(byteValue)
                if index == 0:
                    assembled.append(
                        TealOp(op.expr, Op.bytec_0, "//", *op.args))
                elif index == 1:
                    assembled.append(
                        TealOp(op.expr, Op.bytec_1, "//", *op.args))
                elif index == 2:
                    assembled.append(
                        TealOp(op.expr, Op.bytec_2, "//", *op.args))
                elif index == 3:
                    assembled.append(
                        TealOp(op.expr, Op.bytec_3, "//", *op.args))
                else:
                    assembled.append(
                        TealOp(op.expr, Op.bytec, index, "//", *op.args))
                continue

        assembled.append(op)

    return assembled
Example #18
0
def assignScratchSlotsToSubroutines(
    subroutineBlocks: Dict[Optional[SubroutineDefinition], TealBlock],
) -> Dict[Optional[SubroutineDefinition], Set[int]]:
    """Assign scratch slot values for an entire program.

    Args:
        subroutineBlocks: A mapping from subroutine to the control flow graph of the subroutine's
            blocks. The key None is taken to mean the main program routine. The values of this
            map will be modified in order to assign specific slot values to all referenced scratch
            slots.

    Raises:
        TealInternalError: if the scratch slots referenced by the program do not fit into 256 slots,
            or if multiple ScratchSlots request the same slot ID.

    Returns:
        A dictionary whose keys are the same as subroutineBlocks, and whose values are sets of
        integers representing the assigned IDs of slots which appear only in that subroutine
        (subroutine local slots).
    """
    global_slots, local_slots = collectScratchSlots(subroutineBlocks)
    # all scratch slots referenced by the program
    allSlots: Set[ScratchSlot] = global_slots | cast(
        Set[ScratchSlot], set()).union(*local_slots.values())

    slotAssignments: Dict[ScratchSlot, int] = dict()
    slotIds: Set[int] = set()

    for slot in allSlots:
        if not slot.isReservedSlot:
            continue

        # If there are two unique slots with same IDs, raise an error
        if slot.id in slotIds:
            raise TealInternalError(
                "Slot ID {} has been assigned multiple times".format(slot.id))
        slotIds.add(slot.id)

    if len(allSlots) > NUM_SLOTS:
        # TODO: identify which slots can be reused
        # subroutines which never invoke each other can use the same slot ID for local slots
        raise TealInternalError(
            "Too many slots in use: {}, maximum is {}".format(
                len(allSlots), NUM_SLOTS))

    # verify that all local slots are assigned to before being loaded.
    # TODO: for simplicity, the current implementation does not perform this check with global slots
    # as well, but that would be a good improvement
    for subroutine, start in subroutineBlocks.items():
        errors = start.validateSlots(slotsInUse=global_slots)
        if len(errors) > 0:
            msg = "Encountered {} error{} when assigning slots to subroutine".format(
                len(errors), "s" if len(errors) != 1 else "")
            raise TealInternalError(msg) from errors[0]

    nextSlotIndex = 0
    for slot in sorted(allSlots, key=lambda slot: slot.id):
        # Find next vacant slot that compiler can assign to
        while nextSlotIndex in slotIds:
            nextSlotIndex += 1

        if slot.isReservedSlot:
            # Slot ids under 256 are manually reserved slots
            slotAssignments[slot] = slot.id
        else:
            slotAssignments[slot] = nextSlotIndex
            slotIds.add(nextSlotIndex)

    for start in subroutineBlocks.values():
        for block in TealBlock.Iterate(start):
            for op in block.ops:
                for slot in op.getSlots():
                    op.assignSlot(slot, slotAssignments[slot])

    assignedLocalSlots: Dict[Optional[SubroutineDefinition], Set[int]] = dict()
    for subroutine, slots in local_slots.items():
        assignedLocalSlots[subroutine] = set(slotAssignments[slot]
                                             for slot in slots)

    return assignedLocalSlots
Example #19
0
 def exitLoop(self) -> Tuple[List[TealSimpleBlock], List[TealSimpleBlock]]:
     if len(self.breakBlocksStack) == 0 or len(
             self.continueBlocksStack) == 0:
         raise TealInternalError("Cannot exit loop when no loop is active")
     return self.breakBlocksStack.pop(), self.continueBlocksStack.pop()
Example #20
0
 def addLoopContinueBlock(self, block: TealSimpleBlock) -> None:
     if len(self.continueBlocksStack) == 0:
         raise TealInternalError(
             "Cannot add continue block when no loop is active")
     self.continueBlocksStack[-1].append(block)
Example #21
0
def compileTeal(
    ast: Expr,
    mode: Mode,
    *,
    version: int = DEFAULT_PROGRAM_VERSION,
    assembleConstants: bool = False,
    optimize: OptimizeOptions = None,
) -> str:
    """Compile a PyTeal expression into TEAL assembly.

    Args:
        ast: The PyTeal expression to assemble.
        mode: The mode of the program to assemble. Must be Signature or Application.
        version (optional): The program version used to assemble the program. This will determine which
            expressions and fields are able to be used in the program and how expressions compile to
            TEAL opcodes. Defaults to 2 if not included.
        assembleConstants (optional): When true, the compiler will produce a program with fully
            assembled constants, rather than using the pseudo-ops `int`, `byte`, and `addr`. These
            constants will be assembled in the most space-efficient way, so enabling this may reduce
            the compiled program's size. Enabling this option requires a minimum program version of 3.
            Defaults to false.
        optimize (optional): OptimizeOptions that determine which optimizations will be applied.

    Returns:
        A TEAL assembly program compiled from the input expression.

    Raises:
        TealInputError: if an operation in ast is not supported by the supplied mode and version.
        TealInternalError: if an internal error is encounter during compilation.
    """
    if (not (MIN_PROGRAM_VERSION <= version <= MAX_PROGRAM_VERSION)
            or type(version) is not int):
        raise TealInputError(
            "Unsupported program version: {}. Excepted an integer in the range [{}, {}]"
            .format(version, MIN_PROGRAM_VERSION, MAX_PROGRAM_VERSION))

    options = CompileOptions(mode=mode, version=version, optimize=optimize)

    subroutineGraph: Dict[SubroutineDefinition,
                          Set[SubroutineDefinition]] = dict()
    subroutine_start_blocks: Dict[Optional[SubroutineDefinition],
                                  TealBlock] = dict()
    subroutine_end_blocks: Dict[Optional[SubroutineDefinition],
                                TealBlock] = dict()
    compileSubroutine(ast, options, subroutineGraph, subroutine_start_blocks,
                      subroutine_end_blocks)

    # note: optimizations are off by default, in which case, apply_global_optimizations
    # won't make any changes. Because the optimizer is invoked on a subroutine's
    # control flow graph, the optimizer requires context across block boundaries. This
    # is necessary for the dependency checking of local slots. Global slots, slots
    # used by DynamicScratchVar, and reserved slots are not optimized.
    if options.optimize.scratch_slots:
        options.optimize._skip_slots = collect_unoptimized_slots(
            subroutine_start_blocks)
        for start in subroutine_start_blocks.values():
            apply_global_optimizations(start, options.optimize)

    localSlotAssignments = assignScratchSlotsToSubroutines(
        subroutine_start_blocks)

    subroutineMapping: Dict[Optional[SubroutineDefinition],
                            List[TealComponent]] = sort_subroutine_blocks(
                                subroutine_start_blocks, subroutine_end_blocks)

    spillLocalSlotsDuringRecursion(version, subroutineMapping, subroutineGraph,
                                   localSlotAssignments)

    subroutineLabels = resolveSubroutines(subroutineMapping)
    teal = flattenSubroutines(subroutineMapping, subroutineLabels)

    verifyOpsForVersion(teal, options.version)
    verifyOpsForMode(teal, options.mode)

    if assembleConstants:
        if version < 3:
            raise TealInternalError(
                "The minimum program version required to enable assembleConstants is 3. The current version is {}"
                .format(version))
        teal = createConstantBlocks(teal)

    lines = ["#pragma version {}".format(version)]
    lines += [i.assemble() for i in teal]
    return "\n".join(lines)
Example #22
0
def compileSubroutine(
    ast: Expr,
    options: CompileOptions,
    subroutineGraph: Dict[SubroutineDefinition, Set[SubroutineDefinition]],
    subroutine_start_blocks: Dict[Optional[SubroutineDefinition], TealBlock],
    subroutine_end_blocks: Dict[Optional[SubroutineDefinition], TealBlock],
) -> None:
    currentSubroutine = (cast(SubroutineDeclaration, ast).subroutine
                         if isinstance(ast, SubroutineDeclaration) else None)

    if not ast.has_return():
        if ast.type_of() == TealType.none:
            ret_expr = Return()
            ret_expr.trace = ast.trace
            seq_expr = Seq([ast, ret_expr])
            seq_expr.trace = ret_expr.trace
            ast = seq_expr
        else:
            ret_expr = Return(ast)
            ret_expr.trace = ast.trace
            ast = ret_expr

    options.setSubroutine(currentSubroutine)

    start, end = ast.__teal__(options)
    start.addIncoming()
    start.validateTree()

    if (currentSubroutine is not None
            and currentSubroutine.get_declaration().deferred_expr is not None):
        # this represents code that should be inserted before each retsub op
        deferred_expr = cast(Expr,
                             currentSubroutine.get_declaration().deferred_expr)

        for block in TealBlock.Iterate(start):
            if not any(op.getOp() == Op.retsub for op in block.ops):
                continue

            if len(block.ops) != 1:
                # we expect all retsub ops to be in their own block at this point since
                # TealBlock.NormalizeBlocks has not yet been used
                raise TealInternalError(
                    f"Expected retsub to be the only op in the block, but there are {len(block.ops)} ops"
                )

            # we invoke __teal__ here and not outside of this loop because the same block cannot be
            # added in multiple places to the control flow graph
            deferred_start, deferred_end = deferred_expr.__teal__(options)
            deferred_start.addIncoming()
            deferred_start.validateTree()

            # insert deferred blocks between the previous block(s) and this one
            deferred_start.incoming = block.incoming
            block.incoming = [deferred_end]
            deferred_end.nextBlock = block

            for prev in deferred_start.incoming:
                prev.replaceOutgoing(block, deferred_start)

            if block is start:
                # this is the start block, replace start
                start = deferred_start

    start.validateTree()

    start = TealBlock.NormalizeBlocks(start)
    start.validateTree()

    subroutine_start_blocks[currentSubroutine] = start
    subroutine_end_blocks[currentSubroutine] = end

    referencedSubroutines: Set[SubroutineDefinition] = set()
    for block in TealBlock.Iterate(start):
        for stmt in block.ops:
            for subroutine in stmt.getSubroutines():
                referencedSubroutines.add(subroutine)

    if currentSubroutine is not None:
        subroutineGraph[currentSubroutine] = referencedSubroutines

    newSubroutines = referencedSubroutines - subroutine_start_blocks.keys()
    for subroutine in sorted(newSubroutines,
                             key=lambda subroutine: subroutine.id):
        compileSubroutine(
            subroutine.get_declaration(),
            options,
            subroutineGraph,
            subroutine_start_blocks,
            subroutine_end_blocks,
        )
Example #23
0
def multiplyFactors(
    expr: Expr, factors: List[Expr], options: "CompileOptions"
) -> Tuple[TealSimpleBlock, TealSimpleBlock]:
    if len(factors) == 0:
        raise TealInternalError("Received 0 factors")

    start = TealSimpleBlock([])

    fac0Start, fac0End = factors[0].__teal__(options)

    if len(factors) == 1:
        # need to use 0 as high word
        highword = TealSimpleBlock([TealOp(expr, Op.int, 0)])

        start.setNextBlock(highword)
        highword.setNextBlock(fac0Start)

        end = fac0End
    else:
        start.setNextBlock(fac0Start)

        fac1Start, fac1End = factors[1].__teal__(options)
        fac0End.setNextBlock(fac1Start)

        multiplyFirst2 = TealSimpleBlock([TealOp(expr, Op.mulw)])
        fac1End.setNextBlock(multiplyFirst2)

        end = multiplyFirst2
        for factor in factors[2:]:
            facXStart, facXEnd = factor.__teal__(options)
            end.setNextBlock(facXStart)

            # stack is [..., A, B, C], where C is current factor
            # need to pop all A,B,C from stack and push X,Y, where X and Y are:
            #       X * 2**64 + Y = (A * 2**64 + B) * C
            # <=>   X * 2**64 + Y = A * C * 2**64 + B * C
            # <=>   X = A * C + highword(B * C)
            #       Y = lowword(B * C)
            multiply = TealSimpleBlock(
                [
                    TealOp(expr, Op.uncover, 2),  # stack: [..., B, C, A]
                    TealOp(expr, Op.dig, 1),  # stack: [..., B, C, A, C]
                    TealOp(expr, Op.mul),  # stack: [..., B, C, A*C]
                    TealOp(expr, Op.cover, 2),  # stack: [..., A*C, B, C]
                    TealOp(
                        expr, Op.mulw
                    ),  # stack: [..., A*C, highword(B*C), lowword(B*C)]
                    TealOp(
                        expr, Op.cover, 2
                    ),  # stack: [..., lowword(B*C), A*C, highword(B*C)]
                    TealOp(
                        expr, Op.add
                    ),  # stack: [..., lowword(B*C), A*C+highword(B*C)]
                    TealOp(
                        expr, Op.swap
                    ),  # stack: [..., A*C+highword(B*C), lowword(B*C)]
                ]
            )

            facXEnd.setNextBlock(multiply)
            end = multiply

    return start, end