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
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))
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)
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)
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
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)]
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)
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" )
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
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}")
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)
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
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
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])
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
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
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
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
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()
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)
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)
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, )
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