def __teal__(self, options: "CompileOptions"): if not isinstance(self.start, Int): return TernaryExpr( Op.replace3, (TealType.bytes, TealType.uint64, TealType.bytes), TealType.bytes, self.original, self.start, self.replacement, ).__teal__(options) op = self.__get_op(options) verifyProgramVersion( op.min_version, options.version, "Program version too low to use op {}".format(op), ) s = cast(Int, self.start).value if op == Op.replace2: return TealBlock.FromOp(options, TealOp(self, op, s), self.original, self.replacement) elif op == Op.replace3: return TealBlock.FromOp(options, TealOp(self, op), self.original, self.start, self.replacement)
def __teal__(self, options: "CompileOptions"): if not isinstance(self.startArg, Int) or not isinstance( self.lenArg, Int): return TernaryExpr( Op.extract3, (TealType.bytes, TealType.uint64, TealType.uint64), TealType.bytes, self.stringArg, self.startArg, self.lenArg, ).__teal__(options) op = self.__get_op(options) verifyProgramVersion( op.min_version, options.version, "Program version too low to use op {}".format(op), ) s, l = cast(Int, self.startArg).value, cast(Int, self.lenArg).value if op == Op.extract: return TealBlock.FromOp(options, TealOp(self, op, s, l), self.stringArg) elif op == Op.extract3: return TealBlock.FromOp( options, TealOp(self, op), self.stringArg, self.startArg, self.lenArg, )
def __teal__(self, options: "CompileOptions"): from pyteal.ir import TealOp, Op, TealBlock if self.index_expression is not None: op = TealOp(self, Op.loads) return TealBlock.FromOp(options, op, self.index_expression) s = cast(ScratchSlot, self.slot) op = TealOp(self, Op.load, s) return TealBlock.FromOp(options, op)
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 __teal__(self, options: "CompileOptions"): verifyProgramVersion( Op.gaid.min_version, options.version, "Program version too low to use Gaid expression", ) if type(self.txnIndex) is int: op = TealOp(self, Op.gaid, self.txnIndex) return TealBlock.FromOp(options, op) op = TealOp(self, Op.gaids) return TealBlock.FromOp(options, op, cast(Expr, self.txnIndex))
def __teal__(self, options: "CompileOptions"): if type(self.index) is int: op = TealOp(self, Op.arg, self.index) return TealBlock.FromOp(options, op) verifyProgramVersion( Op.args.min_version, options.version, "Program version too low to use dynamic indexes with Arg", ) op = TealOp(self, Op.args) return TealBlock.FromOp(options, op, cast(Expr, self.index))
def __teal__(self, options: "CompileOptions"): op = self.__get_op(options) verifyProgramVersion( op.min_version, options.version, "Program version too low to use op {}".format(op), ) if op == Op.extract: # if possible, exploit optimization in the extract opcode that takes the suffix # when the length argument is 0 return TealBlock.FromOp( options, TealOp(self, op, cast(Int, self.startArg).value, 0), self.stringArg, ) elif op == Op.substring3: strBlockStart, strBlockEnd = self.stringArg.__teal__(options) nextBlockStart, nextBlockEnd = self.startArg.__teal__(options) strBlockEnd.setNextBlock(nextBlockStart) finalBlock = TealSimpleBlock([ TealOp(self, Op.dig, 1), TealOp(self, Op.len), TealOp(self, Op.substring3), ]) nextBlockEnd.setNextBlock(finalBlock) return strBlockStart, finalBlock
def collect_unoptimized_slots( subroutineBlocks: Dict[Optional[SubroutineDefinition], TealBlock] ) -> Set[ScratchSlot]: """Find and return all referenced ScratchSlots that need to be skipped during optimization. Args: subroutineBlocks: A mapping from subroutine to the subroutine's control flow graph. The key None is taken to mean the main program routine. Returns: A set which contains the slots used by DynamicScratchVars, all the reserved slots, and all global slots. """ unoptimized_slots: Set[ScratchSlot] = set() def collectSlotsFromBlock(block: TealBlock): for op in block.ops: for slot in op.getSlots(): # dynamic slot or reserved slot if op.op == Op.int or slot.isReservedSlot: unoptimized_slots.add(slot) for _, start in subroutineBlocks.items(): for block in TealBlock.Iterate(start): collectSlotsFromBlock(block) global_slots, _ = collectScratchSlots(subroutineBlocks) unoptimized_slots.update(global_slots) return unoptimized_slots
def __teal__(self, options: "CompileOptions"): if len(self.cond) > 1: asserts: list[Expr] = [] for cond in self.cond: asrt = Assert(cond, comment=self.comment) asrt.trace = cond.trace asserts.append(asrt) return Seq(*asserts).__teal__(options) if options.version >= Op.assert_.min_version: # use assert op if available conds: list[Expr] = [self.cond[0]] if self.comment is not None: conds.append(Comment(self.comment)) return TealBlock.FromOp(options, TealOp(self, Op.assert_), *conds) # if assert op is not available, use branches and err condStart, condEnd = self.cond[0].__teal__(options) end = TealSimpleBlock([]) errBlock = TealSimpleBlock([TealOp(self, Op.err)]) branchBlock = TealConditionalBlock([]) branchBlock.setTrueBlock(end) branchBlock.setFalseBlock(errBlock) condEnd.setNextBlock(branchBlock) return condStart, end
def __teal__(self, options: "CompileOptions"): verifyProgramVersion( self.op.min_version, options.version, "Program version too low to use op {}".format(self.op), ) return TealBlock.FromOp(options, TealOp(self, self.op), self.arg)
def _remove_extraneous_slot_access(start: TealBlock, remove: Set[ScratchSlot]): def keep_op(op: TealOp) -> bool: if type(op) != TealOp or (op.op != Op.store and op.op != Op.load): return True return not set(op.getSlots()).issubset(remove) for block in TealBlock.Iterate(start): block.ops = list(filter(keep_op, block.ops))
def __teal__(self, options: "CompileOptions"): if self.base == "utf8": payload = self.byte_str elif self.base == "base16": payload = "0x" + self.byte_str else: payload = "{}({})".format(self.base, self.byte_str) op = TealOp(self, Op.byte, payload) return TealBlock.FromOp(options, op)
def __teal__(self, options: "CompileOptions"): op = self.action.value verifyProgramVersion( op.min_version, options.version, "Program version too low to create inner transactions", ) return TealBlock.FromOp(options, TealOp(self, op))
def __teal__(self, options: "CompileOptions"): verifyProgramVersion( Op.itxn_field.min_version, options.version, "Program version too low to create inner transactions", ) return TealBlock.FromOp( options, TealOp(self, Op.itxn_field, self.field.arg_name), self.value )
def __teal__(self, options: "CompileOptions"): verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) verifyProgramVersion( self.op.min_version, options.version, "Program version too low to use op {}".format(self.op), ) op = TealOp(self, self.op, self.field.arg_name) return TealBlock.FromOp(options, op)
def __teal__(self, options: "CompileOptions"): verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) opToUse = self.staticOp if type(self.index) is int else self.dynamicOp if opToUse is None: raise TealCompileError("Dynamic array indexing not supported", self) verifyProgramVersion( opToUse.min_version, options.version, "Program version too low to use op {}".format(opToUse), ) if type(self.index) is int: op = TealOp(self, opToUse, self.field.arg_name, self.index) return TealBlock.FromOp(options, op) op = TealOp(self, opToUse, self.field.arg_name) return TealBlock.FromOp(options, op, cast(Expr, self.index))
def __teal__(self, options: "CompileOptions"): verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) verifyProgramVersion( Op.gitxn.min_version, options.version, "Program version too low to use gitxn", ) op = TealOp(self, Op.gitxn, self.txnIndex, self.field.arg_name) return TealBlock.FromOp(options, op)
def __teal__(self, options: "CompileOptions"): verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) if type(self.txnIndex) is int: verifyProgramVersion( Op.gtxn.min_version, options.version, "Program version too low to use gtxn", ) op = TealOp(self, Op.gtxn, self.txnIndex, self.field.arg_name) return TealBlock.FromOp(options, op) verifyProgramVersion( Op.gtxns.min_version, options.version, "Program version too low to index Gtxn with dynamic values", ) op = TealOp(self, Op.gtxns, self.field.arg_name) return TealBlock.FromOp(options, op, cast(Expr, self.txnIndex))
def __teal__(self, options: "CompileOptions"): verifyProgramVersion( Op.json_ref.min_version, options.version, "Program version too low to use op json_ref", ) verifyFieldVersion(self.type.arg_name, self.type.min_version, options.version) op = TealOp(self, Op.json_ref, self.type.arg_name) return TealBlock.FromOp(options, op, self.json_obj, self.key)
def _has_load_dependencies(cur_block: TealBlock, start: TealBlock, slot: ScratchSlot, pos: int) -> bool: for block in TealBlock.Iterate(start): for i, op in enumerate(block.ops): if block == cur_block and i == pos: continue if type(op) == TealOp and op.op == Op.load and slot in set( op.getSlots()): return True return False
def __teal__(self, options: "CompileOptions"): verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) if type(self.index) is int: opToUse = Op.gitxna else: opToUse = Op.gitxnas verifyProgramVersion( opToUse.min_version, options.version, "Program version too low to use op {}".format(opToUse), ) if type(self.index) is int: op = TealOp(self, opToUse, self.txnIndex, self.field.arg_name, self.index) return TealBlock.FromOp(options, op) op = TealOp(self, opToUse, self.txnIndex, self.field.arg_name) return TealBlock.FromOp(options, op, cast(Expr, self.index))
def apply_global_optimizations(start: TealBlock, options: OptimizeOptions) -> TealBlock: # limit number of iterations to length of teal program to avoid potential # infinite loops. for block in TealBlock.Iterate(start): for _ in range(len(block.ops)): prev_ops = block.ops.copy() if options.scratch_slots: _apply_slot_to_stack(block, start, options._skip_slots) if prev_ops == block.ops: break return start
def collectScratchSlots( subroutineBlocks: Dict[Optional[SubroutineDefinition], TealBlock] ) -> Tuple[Set[ScratchSlot], Dict[Optional[SubroutineDefinition], Set[ScratchSlot]]]: """Find and return all referenced ScratchSlots for each subroutine. Args: subroutineBlocks: A mapping from subroutine to the subroutine's control flow graph. The key None is taken to mean the main program routine. Returns: A tuple of a set containing all global slots and a dictionary whose keys are the same as subroutineBlocks, and whose values are the local slots of that subroutine. """ subroutineSlots: Dict[Optional[SubroutineDefinition], Set[ScratchSlot]] = dict() def collectSlotsFromBlock(block: TealBlock, slots: Set[ScratchSlot]): for op in block.ops: for slot in op.getSlots(): slots.add(slot) for subroutine, start in subroutineBlocks.items(): slots: Set[ScratchSlot] = set() for block in TealBlock.Iterate(start): collectSlotsFromBlock(block, slots) subroutineSlots[subroutine] = slots # all scratch slots referenced by more than 1 subroutine global_slots: Set[ScratchSlot] = set() # all scratch slots referenced by only 1 subroutine local_slots: Dict[Optional[SubroutineDefinition], Set[ScratchSlot]] = dict() for subroutine, slots in subroutineSlots.items(): allOtherSlots: Set[ScratchSlot] = set() for otherSubroutine, otherSubroutineSlots in subroutineSlots.items(): if subroutine is not otherSubroutine: allOtherSlots |= otherSubroutineSlots global_slots |= slots & allOtherSlots local_slots[subroutine] = slots - global_slots return global_slots, local_slots
def __teal__(self, options: "CompileOptions"): self.compile_check(options) tealOp = TealOp(self, self.op, *self.immediate_args) callStart, callEnd = TealBlock.FromOp(options, tealOp, *self.args) curEnd = callEnd # the list is reversed in order to preserve the ordering of the opcode's returned # values. ie the output to stack [A, B, C] should correspond to C->output_slots[2] # B->output_slots[1], and A->output_slots[0]. for slot in reversed(self.output_slots): store = slot.store() storeStart, storeEnd = store.__teal__(options) curEnd.setNextBlock(storeStart) curEnd = storeEnd return callStart, curEnd
def __teal__(self, options: "CompileOptions"): if options.currentSubroutine is not None: verifyProgramVersion( Op.retsub.min_version, options.version, "Program version too low to use subroutines", ) returnType = options.currentSubroutine.return_type if returnType == TealType.none: if self.value is not None: raise TealCompileError( "Cannot return a value from a subroutine with return type TealType.none", self, ) else: if self.value is None: raise TealCompileError( "A subroutine declares it returns a value, but no value is being returned", self, ) actualType = self.value.type_of() if not types_match(actualType, returnType): raise TealCompileError( "Incompatible return type from subroutine, expected {} but got {}" .format(returnType, actualType), self, ) op = Op.retsub else: if self.value is None: raise TealCompileError( "Return from main program must have an argument", self) actualType = self.value.type_of() if not types_match(actualType, TealType.uint64): raise TealCompileError( "Incompatible return type from main program, expected {} but got {}" .format(TealType.uint64, actualType), self, ) op = Op.return_ args = [] if self.value is None else [self.value] return TealBlock.FromOp(options, TealOp(self, op), *args)
def __teal__(self, options: "CompileOptions"): """ Generate the subroutine's start and end teal blocks. The subroutine's arguments are pushed on the stack to be picked up into local scratch variables. There are 4 cases to consider for the pushed arg expression: 1. (by-value) In the case of typical arguments of type Expr, the expression ITSELF is evaluated for the stack and will be stored in a local ScratchVar for subroutine evaluation 2. (by-reference) In the case of a by-reference argument of type ScratchVar, its SLOT INDEX is put on the stack and will be stored in a local DynamicScratchVar for subroutine evaluation 3. (ABI, or a special case in by-value) In this case, the storage of an ABI value are loaded to the stack and will be stored in a local ABI value for subroutine evaluation 4. (ABI output keyword argument) In this case, we do not place ABI values (encoding) on the stack. This is an *output-only* argument: in `evaluate_subroutine` an ABI typed instance for subroutine evaluation will be generated, and gets in to construct the subroutine implementation. """ verifyProgramVersion( Op.callsub.min_version, options.version, "Program version too low to use SubroutineCall expression", ) def handle_arg(arg: Expr | ScratchVar | abi.BaseType) -> Expr: if isinstance(arg, ScratchVar): return arg.index() elif isinstance(arg, Expr): return arg elif isinstance(arg, abi.BaseType): return arg.stored_value.load() else: raise TealInputError( f"cannot handle current arg: {arg} to put it on stack" ) op = TealOp(self, Op.callsub, self.subroutine) return TealBlock.FromOp(options, op, *[handle_arg(x) for x in self.args])
def __teal__(self, options: "CompileOptions"): op = TealOp(self, Op.err) return TealBlock.FromOp(options, op)
def __teal__(self, options: "CompileOptions"): return TealBlock.FromOp(options, TealOp(self, self.field.get_op()), *self.args)
def __teal__( self, options: "CompileOptions") -> Tuple[TealBlock, TealSimpleBlock]: op = TealOp(self, Op.comment, self.comment) return TealBlock.FromOp(options, op)
def __teal__(self, options: "CompileOptions"): verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) op = TealOp(self, Op.global_, self.field.arg_name) return TealBlock.FromOp(options, op)